pythonnumpyfortranf2py

F2PY wrap procedure that uses derived types only internally


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?


Solution

  • 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.