Access Fortran Common Variables from Cython

402 Views Asked by At

My question is virtually identical to this one. However, I'm looking for a solution that uses Cython instead of ctypes.

I'm wrapping some legacy F77 code for using in Python. I've written wrappers for the subroutines, using a module and the iso_c_bindings, that I can then use from Cython. This works well for calling the subroutines, passing data as arguments, etc. However, I'd now like to access the common block data in the library directly from Cython.

So my question is two parts:

A) Can I access the common block data using Cython directly as in the ctypes example above? I so how? I gather I'm supposed to reference the common block as a struct using cdef extern, but I'm not sure how to point to the library data.

B) Would I be better off, and not sacrifice performance, by simply writing setter/getter functions in my wrapper module? This was suggested in the responses to the ctypes question referenced above.

2

There are 2 best solutions below

1
On BEST ANSWER

A) After trial & error, it seems that the following code works with python3.5/gfortran4.8/cython0.25 on Linux x86_64, so could you try it to see whether it works or not...?

fort.f90:

module mymod
    use iso_c_binding
    implicit none
contains

subroutine fortsub() bind(c)
    double precision x( 2 )
    real             y( 3 )
    real             z( 4 )
    integer          n( 5 )
    common /mycom/ x, y, z, n

    data z / 100.0, 200.0, 300.0, 400.0 /  !! initialize only z(:) (for check)

    print *, "(fort) x(:) = ", x(:)
    print *, "(fort) y(:) = ", y(:)
    print *, "(fort) z(:) = ", z(:)
    print *, "(fort) n(:) = ", n(:)
end subroutine

end module

fort.h:

extern void fortsub( void );   /* or fortsub_() if bind(c) is not used */

extern struct Mycom {
    double x[ 2 ];
    float  y[ 3 ];
    float  z[ 4 ];
    int    n[ 5 ];
} mycom_;

test.pyx:

cdef extern from "fort.h":
    void fortsub()

    struct Mycom:
        double x[ 2 ]
        float  y[ 3 ]
        int    n[ 5 ]

    Mycom mycom_;

def go():

    mycom_.x[:] = [ 1.0, 2.0 ]
    mycom_.y[:] = [ 11.0, 12.0, 13.0 ]
    mycom_.n[:] = [ 1, 2, 3, 4, 5 ]

    fortsub()

setup.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
from os import system

system( 'gfortran -c fort.f90 -o fort.o -fPIC' )

ext_modules = [Extension( 'test', ['test.pyx'],
                          extra_compile_args = ['-fPIC'],
                          extra_link_args = ['fort.o', '-lgfortran'] )]

setup( name = 'test',
       cmdclass = {'build_ext': build_ext},
       ext_modules = ext_modules )

compile:

$ python setup.py build_ext --inplace

test:

$ python
>>> import test
>>> test.go()
 (fort) x(:) =    1.0000000000000000        2.0000000000000000     
 (fort) y(:) =    11.0000000       12.0000000       13.0000000    
 (fort) z(:) =    100.000000       200.000000       300.000000       400.000000    
 (fort) n(:) =            1           2           3           4           5

Here, please note that I did not include z in test.pyx to check whether we can declare only selected variables in the common block. Also, some compiler options may be necessary to make the alignment of common variables consistent between C and Fortran (this YoLinux page may be useful).


B) I guess it would depend on the amount of computation peformed by the Fortran routine... If the routine is heavy (takes at least a few minutes), copy operations in getter/setter may be no problem. On the other hand, if the routine finishes quickly while called a huge number of times, then the overhead may be non-negligible...

For efficiency, it might be useful to pass pointer variables from Cython to Fortran, get the address of selected common variables somehow by c_loc(), and access them via pointers on the Cython side directly (though not still sure if it works...) But if there is no problem of memory alignment (for the compiler used), it may be more straight-forward to use structs as above.

3
On

Since you're already familiar with modular programming, I suggest you put the common block in a module and import the variables when access is required:

module common_block_mod
    common /myCommonBlock/ var, var2, var3
    save ! This is not necessary in Fortran 2008+
end module common_block_mod

You can now import the variables when access is required.

subroutine foo()
    use common_block_mod
    !.. do stuff
end subroutine foo

You can read more about this approach at http://iprc.soest.hawaii.edu/users/furue/improve-fortran.html