How to determine C interoperability with Arrays

158 Views Asked by At

I currently need to wrap Fortran subroutines into Python. I want to do it using Cython, and I'm working on smaller tasks for now to get some basic understanding of what I'm doing before working on the actual scripts, as they are very large involving dozens of derived types and subroutines.

I'm very new to Fortran, C, and Cython, and I'm trying to wrap a basic derived type and subroutine. However when I try to compile, I get errors as my derived type is "not interoperable".

The initial module can be found below: "summation.f90"

module gfunc_module
implicit none 

type :: geo
    real, dimension(1:2) :: coordinates
    real :: weight 

end type 

contains

subroutine gfunc(v1, v2, final_v)
    type(geo), intent(in) :: v1, v2
    type(geo), intent(out) ::final_v

    final_v%coordinates = v1%coordinates * v1%weight + v2%coordinates + v2%weight
    final_v%weight = v1%weight + v2%weight

end subroutine
end module

Then, I wrap it using iso_c_binding: (pysummation.f90)

module gfunc1_interface
use iso_c_binding
use gfunc_module
implicit none

type, bind(c) :: geo 
    real(c_float), dimension(1:2) :: coordinates 
    real(c_float) :: weight

contains
subroutine c_gfunc(v1, v2, final_v) bind(c)
    type(geo), intent(in) :: v1, v2
    type(geo), intent(out) :: final_v
    call gfunc(v1, v2, final_v)
end subroutine

end module 

Then, I write a header file: (pysummation.h)

struct geo {
    float coordinates[2];
    float weight;
}

extern void c_gfunc(geo *v1, geo *v2, geo *final_v);

My cython file (pysummation.pyx):

cdef extern from "pysummation.h":
    cdef struct geo: 
        float coordinates[2]
        float weight
    void c_gfunc(geo *v1, geo *v2, geo *final_v)

def f(geo v1, geo v2):
    cdef:
        geo value
    c_gfunc(<geo*>&v1, <geo*>&v2, <geo*>&value)
    return value 

And finally, my setup file (setup.py):

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
# This line only needed if building with NumPy in Cython file.
from numpy import get_include
from os import system

# compile the fortran modules without linking
fortran_mod_comp = 'gfortran summation.f90 -c -o summation.o -O3 -fPIC'
print fortran_mod_comp
system(fortran_mod_comp)
shared_obj_comp = 'gfortran pysummation.f90 -c -o pysummation.o -O3 -fPIC'
print shared_obj_comp
system(shared_obj_comp)

ext_modules = [Extension(# module name:
                         'pygfunc',
                         # source file:
                         ['pysummation.pyx'],
                         # other compile args for gcc
                         extra_compile_args=['-fPIC', '-O3'],
                         # other files to link to
                         extra_link_args=['summation.o', 'pysummation.o'])]

setup(name = 'pygfunc',
      cmdclass = {'build_ext': build_ext},
      # Needed if building with NumPy.
      # This includes the NumPy headers when compiling.
      include_dirs = [get_include()],
      ext_modules = ext_modules)

When I try to compile my scripts, I get the following errors:

  1. Derived type definition of 'geo' at (1) has already been defined.

I know that because I told Fortran to use my module, which already defined the "geo" type, but I thought that because I wanted to use iso_c_binding, I'd have to redefine it.

The second error I get is

  1. Variable 'v1' is a dummy argument to the BIND(C) procedure, but is not c interoperable because derived type 'geo' is not C interoperable.

This occurs for all variables of type(geo). I'm just wondering if someone could shed some light on how c interoperability works with regards to arrays and derived types?

I recognize that there are probably dozens of other errors in my code. However I'd like to at least work through these few before I start troubleshooting others.

1

There are 1 best solutions below

2
HAL9000 On

So, to summarize, in python you are trying to bind to a c-function, that again is bound to a fortran function? Right? And at the moment you are having trouble creating the c-wrapper to the fortran function? Right?

I am not very familiar with fortran, but it seems that the fortran compiler is complaining about two different definitions of the type geo. And indeed you do have two definitions. One in summation.f90, and one in pysummation.f90. I would recommend to rename the one in pysummation.f90 to c_geo. That also means that the parameters v1, v2, final_v of c_gfunc should be of type c_geo and not geo. And naturally you have to convert v1 and v2 to geo before passing them to gfunc, and then convert the return value back.

As I said, I don't write fortan, so this code is more pseudo code rather than a correct fortran program:

module gfunc1_interface
use iso_c_binding
use gfunc_module
implicit none

type, bind(c) :: c_geo 
    real(c_float), dimension(1:2) :: coordinates 
    real(c_float) :: weight

contains
subroutine geo_c2f(cv, fv)
    type(c_geo), intent(in) :: cv
    type(geo), intent(out) :: fv
    fv%coordinates = cv%coordinates
    fv%weight = cv%weight 
end subroutine
subroutine geo_f2c(fv, cv)
    type(geo), intent(in) :: fv
    type(c_geo), intent(out) :: cv
    cv%coordinates = fv%coordinates
    cv%weight = fv%weight 
end subroutine

subroutine c_gfunc(v1, v2, final_v) bind(c)
    type(c_geo), intent(in) :: v1, v2
    type(c_geo), intent(out) :: final_v

    type(geo) :: v1f, v2f, final_vf

    call geo_c2f(v1, v1f)
    call geo_c2f(v2, v2f)

    call gfunc(v1f, v2f, final_vf)

    call geo_f2c(final_vf, final_v)
end subroutine

end module

Some other hints:

Your pysummation.h is wrong if you expect it to be readable by a c-compiler. struct keyword is mandatory when declaring variable/parameters of type struct:

extern void c_gfunc(struct geo *v1, struct geo *v2, struct geo *final_v);

If v1 and v2 are not supposed to modify what they are pointing at, I would also add const keywords:

extern void c_gfunc(const struct geo *v1, const struct geo *v2, struct geo *final_v);

And finally:

I would also recommend that you test your c-wrapper in c before you try to re-wrap it for python.