It is stated in a lot of places that F2PY does not "support derived types", however, it is unclear to me whether this means that either
To my use case the first point is an inconvenience but the second rather a deal breaker.
As an example, consider this module that computes a vector sum:
module derived_types
implicit none
public :: point, add
type :: point
real :: x
real :: y
end type point
contains
type(point) function add(p1, p2)
type(point), intent(in) :: p1
type(point), intent(in) :: p2
add%x = p1%x + p2%x
add%y = p1%y + p2%y
end function add
end module derived_types
module expose
use derived_types, only: point, add
implicit none
contains
subroutine vector_sum(x1, y1, x2, y2, x3, y3)
real, intent(in) :: x1, y1, x2, y2
real, intent(out) :: x3, y3
type(point) :: p1, p2, p3
p1 = point(x1, y1)
p2 = point(x2, y2)
p3 = add(p1, p2)
x3 = p3%x
y3 = p3%y
end subroutine vector_sum
end module expose
Subroutine vector_sum
shall be exposed to Python. The derived type point
is not to be passed between Python and Fortran.
This works as plain Fortran program (with an appropriate program block added) but F2PY fails:
f2py -c ff.f90 only: vector_sum
running build
running config_cc
unifing config_cc, config, build_clib, build_ext, build commands --compiler options
running config_fc
unifing config_fc, config, build_clib, build_ext, build commands --fcompiler options
running build_src
build_src
building extension "untitled" sources
f2py options: ['only:', 'vector_sum', ':']
f2py:> /tmp/tmpjdq8b9dq/src.linux-x86_64-3.8/untitledmodule.c
creating /tmp/tmpjdq8b9dq/src.linux-x86_64-3.8
Reading fortran codes...
Reading file 'ff.f90' (format:free)
Line #7 in ff.f90:"type :: point "
analyzeline: No name/args pattern found for line.
Post-processing...
Block: untitled
Block: derived_types
Block: unknown_type
Block: expose
Block: vector_sum
Block: run
Post-processing (stage 2)...
Block: untitled
Block: unknown_interface
Block: derived_types
Block: unknown_type
Block: expose
Block: vector_sum
Block: run
Building modules...
Building module "untitled"...
Constructing F90 module support for "derived_types"...
Variables: point add
getctype: No C-type found in "{'attrspec': ['public']}", assuming void.
Traceback (most recent call last):
File "/home/me/.pyenv/versions/anaconda3-2020.11/lib/python3.8/site-packages/numpy/f2py/f90mod_rules.py", line 143, in buildhooks
at = capi_maps.c2capi_map[ct]
KeyError: 'void'
Can such a thing be done using F2PY at all?
After some tinkering, I figured it would probably be easiest to compile the code that defines all derived types into a static library first, i.e.:
libff.f90
containing:
module derived_types
implicit none
public :: point, add
type :: point
real :: x
real :: y
end type point
contains
type(point) function add(p1, p2)
type(point), intent(in) :: p1
type(point), intent(in) :: p2
add%x = p1%x + p2%x
add%y = p1%y + p2%y
end function add
end module derived_types
Compile with gfortran -free -c libff.f90
to produce libff.o
.
Then the code that uses the derived types from the pre-compiled module can be compiled with F2PY when linking the library, i.e.:
ff.f90
containing:
module expose
use derived_types, only: point, add
implicit none
contains
subroutine vector_sum(x1, y1, x2, y2, x3, y3)
real, intent(in) :: x1, y1, x2, y2
real, intent(out) :: x3, y3
type(point) :: p1, p2, p3
p1 = point(x1, y1)
p2 = point(x2, y2)
p3 = add(p1, p2)
x3 = p3%x
y3 = p3%y
end subroutine vector_sum
end module expose
After using F2PY to build the Python extention with f2py -c ff.f90 libff.o -m ff
, we can import it in Python with from ff.expose import vector_sum
.
Lesson learned: Put the definition of derived types into a pre-compiled library then compile only the code using these types with F2PY.