Skip to main content
`Ctrl`+`K`

# Boilerplate reduction and templating

## Using FYPP for binding generic interfaces

`f2py` doesn’t currently support binding interface blocks. However, there are workarounds in use. Perhaps the best known is the usage of `tempita` for using `.pyf.src` files as is done in the bindings which are part of scipy. tempita support has been removed and is no longer recommended in any case.

Note

The reason interfaces cannot be supported within `f2py` itself is because they don’t correspond to exported symbols in compiled libraries.

    ❯ nm gen.o
     0000000000000078 T __add_mod_MOD_add_complex
     0000000000000000 T __add_mod_MOD_add_complex_dp
     0000000000000150 T __add_mod_MOD_add_integer
     0000000000000124 T __add_mod_MOD_add_real
     00000000000000ee T __add_mod_MOD_add_real_dp

Here we will discuss a few techniques to leverage `f2py` in conjunction with fypp to emulate generic interfaces and to ease the binding of multiple (similar) functions.

### Basic example: Addition module

Let us build on the example (from the user guide, F2PY examples) of a subroutine which takes in two arrays and returns its sum.

    C
          SUBROUTINE ZADD(A,B,C,N)
    C
          DOUBLE COMPLEX A(*)
          DOUBLE COMPLEX B(*)
          DOUBLE COMPLEX C(*)
          INTEGER N
          DO 20 J = 1, N
             C(J) = A(J)+B(J)
     20   CONTINUE
          END

We will recast this into modern fortran:

    module adder
        implicit none
    contains

        subroutine zadd(a, b, c, n)
            integer, intent(in) :: n
            double complex, intent(in) :: a(n), b(n)
            double complex, intent(out) :: c(n)
            integer :: j
            do j = 1, n
                c(j) = a(j) + b(j)
            end do
        end subroutine zadd

    end module adder

We could go on as in the original example, adding intents by hand among other things, however in production often there are other concerns. For one, we can template via FYPP the construction of similar functions:

    module adder
        implicit none
    contains

    #:def add_subroutine(dtype_prefix, dtype)
        subroutine ${dtype_prefix}$add(a, b, c, n)
            integer, intent(in) :: n
            ${dtype}$, intent(in) :: a(n), b(n)
            ${dtype}$ :: c(n)
            integer :: j
            do j = 1, n
                c(j) = a(j) + b(j)
            end do
        end subroutine ${dtype_prefix}$add

    #:enddef

    #:for dtype_prefix, dtype in [('i', 'integer'), ('s', 'real'), ('d', 'real(kind=8)'), ('c', 'complex'), ('z', 'double complex')]
        @:add_subroutine(${dtype_prefix}$, ${dtype}$)
    #:endfor

    end module adder

This can be pre-processed to generate the full fortran code:

    ❯ fypp gen_adder.f90.fypp > adder.f90

As to be expected, this can be wrapped by `f2py` subsequently.

Now we will consider maintaining the bindings in a separate file. Note the following basic `.pyf` which can be generated for a single subroutine via `f2py -m adder adder_base.f90 -h adder.pyf`:

    !    -*- f90 -*-
    ! Note: the context of this file is case sensitive.

    python module adder ! in
        interface  ! in :adder
            module adder ! in :adder:adder_base.f90
                subroutine zadd(a,b,c,n) ! in :adder:adder_base.f90:adder
                    double complex dimension(n),intent(in) :: a
                    double complex dimension(n),intent(in),depend(n) :: b
                    double complex dimension(n),intent(out),depend(n) :: c
                    integer, optional,intent(in),check(shape(a, 0) == n),depend(a) :: n=shape(a, 0)
                end subroutine zadd
            end module adder
        end interface
    end python module adder

    ! This file was auto-generated with f2py (version:2.0.0.dev0+git20240101.bab7280).
    ! See:
    ! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e

With the docstring:

    c = zadd(a,b,[n])

    Wrapper for ``zadd``.

    Parameters
    ----------
    a : input rank-1 array('D') with bounds (n)
    b : input rank-1 array('D') with bounds (n)

    Other Parameters
    ----------------
    n : input int, optional
        Default: shape(a, 0)

    Returns
    -------
    c : rank-1 array('D') with bounds (n)

Which is already pretty good. However, `n` should never be passed in the first place so we will make some minor adjustments.

    !    -*- f90 -*-
    ! Note: the context of this file is case sensitive.

    python module adder ! in
        interface  ! in :adder
            module adder ! in :adder:adder_base.f90
                subroutine zadd(a,b,c,n) ! in :adder:adder_base.f90:adder
                    integer intent(hide),depend(a) :: n=len(a)
                    double complex dimension(n),intent(in) :: a
                    double complex dimension(n),intent(in),depend(n) :: b
                    double complex dimension(n),intent(out),depend(n) :: c
                end subroutine zadd
            end module adder
        end interface
    end python module adder

    ! This file was auto-generated with f2py (version:2.0.0.dev0+git20240101.bab7280).
    ! See:
    ! https://numpy.org/doc/stable/f2py/

Which corresponds to:

    In [3]: ?adder.adder.zadd
    Call signature: adder.adder.zadd(*args, **kwargs)
    Type:           fortran
    String form:    <fortran function zadd>
    Docstring:
    c = zadd(a,b)

    Wrapper for ``zadd``.

    Parameters
    ----------
    a : input rank-1 array('D') with bounds (n)
    b : input rank-1 array('D') with bounds (n)

    Returns
    -------
    c : rank-1 array('D') with bounds (n)

Finally, we can template over this in a similar manner, to attain the original goal of having bindings which make use of `f2py` directives and have minimal spurious repetition.

    !    -*- f90 -*-
    ! Note: the context of this file is case sensitive.

    python module adder ! in
        interface  ! in :adder
            module adder ! in :adder:adder_base.f90
    #:def add_subroutine(dtype_prefix, dtype)
                subroutine ${dtype_prefix}$add(a,b,c,n) ! in :adder:adder_base.f90:adder
                    integer intent(hide),depend(a) :: n=len(a)
                    ${dtype}$ dimension(n),intent(in) :: a
                    ${dtype}$ dimension(n),intent(in),depend(n) :: b
                    ${dtype}$ dimension(n),intent(out),depend(n) :: c
                end subroutine ${dtype_prefix}$add

    #:enddef

    #:for dtype_prefix, dtype in [('i', 'integer'), ('s', 'real'), ('d', 'real(kind=8)'), ('c', 'complex'), ('z', 'complex(kind=8)')]
        @:add_subroutine(${dtype_prefix}$, ${dtype}$)
    #:endfor
            end module adder
        end interface
    end python module adder

    ! This file was auto-generated with f2py (version:2.0.0.dev0+git20240101.bab7280).
    ! See:
    ! https://numpy.org/doc/stable/f2py/

Usage boils down to:

    fypp gen_adder.f90.fypp > adder.f90
    fypp adder.pyf.fypp > adder.pyf
    f2py -m adder -c adder.pyf adder.f90 --backend meson