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
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/f2py/advanced/boilerplating.html