I am experimenting with using f2py
to encapsulate C code.
As a first test I have prepared two very basic C source files:
module1.c
double multiply(double a, double b);
double multiply(double a, double b) {
return a*b;
}
module2.c
double divide(double a, double b);
double divide(double a, double b) {
return a/b;
}
I compile both files as follows:
$ gcc -c -fPIC -o module1.o module1.c
$ gcc -c -fPIC -o module2.o module2.c
Then I prepare the following Fortran module wrapper for both functions:
fortran_module.f90
! TEST FORTRAN WRAPPER
module fortran_module
use iso_c_binding
! Interface to C routine
! double get_price_step(double price);
interface
real(c_double) function multiply(a, b) bind(C)
use iso_c_binding
real(c_double), value :: a,b
end function
real(c_double) function divide(a, b) bind(C)
use iso_c_binding
real(c_double), value :: a,b
end function
end interface
end module
I compile the module using:
$ f2py -c -m fortran_module fortran_module.f90 module1.o module2.o
And I get the following output:
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 "fortran_module" sources
f2py options: []
f2py:> /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_modulemodule.c
creating /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7
Reading fortran codes...
Reading file 'fortran_module.f90' (format:free)
Post-processing...
Block: fortran_module
Block: fortran_module
In: :fortran_module:fortran_module.f90:fortran_module
get_useparameters: no module iso_c_binding info used by fortran_module
In: :fortran_module:fortran_module.f90:fortran_module:unknown_interface
get_useparameters: no module iso_c_binding info used by unknown_interface
Block: multiply
In: :fortran_module:fortran_module.f90:fortran_module:unknown_interface:multiply
get_useparameters: no module iso_c_binding info used by multiply
Block: divide
In: :fortran_module:fortran_module.f90:fortran_module:unknown_interface:divide
get_useparameters: no module iso_c_binding info used by divide
Post-processing (stage 2)...
Block: fortran_module
Block: unknown_interface
Block: fortran_module
Block: unknown_interface
Block: multiply
Block: divide
Building modules...
Building module "fortran_module"...
Constructing F90 module support for "fortran_module"...
Skipping interface unknown_interface
Wrote C/API module "fortran_module" to file "/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_modulemodule.c"
Fortran 90 wrappers are saved to "/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_module-f2pywrappers2.f90"
adding '/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortranobject.c' to sources.
adding '/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7' to include_dirs.
copying /usr/local/lib/python3.7/site-packages/numpy/f2py/src/fortranobject.c -> /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7
copying /usr/local/lib/python3.7/site-packages/numpy/f2py/src/fortranobject.h -> /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7
adding '/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_module-f2pywrappers2.f90' to sources.
build_src: building npy-pkg config files
running build_ext
customize UnixCCompiler
C compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -O2 -pipe -fstack-protector-strong -fno-strict-aliasing -fPIC
creating /tmp/tmpjgjjhjtm/tmp
creating /tmp/tmpjgjjhjtm/tmp/tmpjgjjhjtm
compile options: '-MMD -MF /tmp/tmpjgjjhjtm/file.c.d -c'
cc: /tmp/tmpjgjjhjtm/file.c
customize UnixCCompiler using build_ext
get_default_fcompiler: matching types: '['gnu', 'gnu95']'
customize GnuFCompiler
Found executable /usr/local/bin/gfort
ran9
gnu: no Fortran 90 compiler found
gnu: no Fortran 90 compiler found
customize Gnu95FCompiler
customize Gnu95FCompiler
customize Gnu95FCompiler using build_ext
building 'fortran_module' extension
compiling C sources
C compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -O2 -pipe -fstack-protector-strong -fno-strict-aliasing -fPIC
creating /tmp/tmp9aa3w8b0/tmp
creating /tmp/tmp9aa3w8b0/tmp/tmp9aa3w8b0
creating /tmp/tmp9aa3w8b0/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7
compile options: '-I/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7 -I/usr/local/lib/python3.7/site-packages/numpy/core/include -I/usr/local/include/python3.7m -c'
cc: /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortranobject.c
cc: /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_modulemodule.c
In file included from /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_modulemodule.c:15:
In file included from /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortranobject.h:13:
In file included from /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortranobject.c:2:
In file included from /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortranobject.h:13:
In file included from /usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4:
In file included from /usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12:
In file included from /usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1822:
/usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: "Using deprecated NumPy API, disable it with "
"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-W#warnings]
#warning "Using deprecated NumPy API, disable it with " \
^
In file included from /usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4:
In file included from /usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12:
In file included from /usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1822:
/usr/local/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: "Using deprecated NumPy API, disable it with "
"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-W#warnings]
#warning "Using deprecated NumPy API, disable it with " \
^
1 warning generated.
1 warning generated.
compiling Fortran 90 module sources
Fortran f77 compiler: /usr/local/bin/gfortran9 -Wall -g -ffixed-form -fno-second-underscore -fPIC -O3 -funroll-loops
Fortran f90 compiler: /usr/local/bin/gfortran9 -Wall -g -fno-second-underscore -fPIC -O3 -funroll-loops
Fortran fix compiler: /usr/local/bin/gfortran9 -Wall -g -ffixed-form -fno-second-underscore -Wall -g -fno-second-underscore -fPIC -O3 -funroll-loops
compile options: '-I/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7 -I/usr/local/lib/python3.7/site-packages/numpy/core/include -I/usr/local/include/python3.7m -c'
extra options: '-J/tmp/tmp9aa3w8b0/ -I/tmp/tmp9aa3w8b0/'
gfortran9:f90: fortran_module.f90
compiling Fortran sources
Fortran f77 compiler: /usr/local/bin/gfortran9 -Wall -g -ffixed-form -fno-second-underscore -fPIC -O3 -funroll-loops
Fortran f90 compiler: /usr/local/bin/gfortran9 -Wall -g -fno-second-underscore -fPIC -O3 -funroll-loops
Fortran fix compiler: /usr/local/bin/gfortran9 -Wall -g -ffixed-form -fno-second-underscore -Wall -g -fno-second-underscore -fPIC -O3 -funroll-loops
compile options: '-I/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7 -I/usr/local/lib/python3.7/site-packages/numpy/core/include -I/usr/local/include/python3.7m -c'
extra options: '-J/tmp/tmp9aa3w8b0/ -I/tmp/tmp9aa3w8b0/'
gfortran9:f90: /tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_module-f2pywrappers2.f90
/usr/local/bin/gfortran9 -Wall -g -Wall -g -shared /tmp/tmp9aa3w8b0/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_modulemodule.o /tmp/tmp9aa3w8b0/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortranobject.o /tmp/tmp9aa3w8b0/fortran_module.o /tmp/tmp9aa3w8b0/tmp/tmp9aa3w8b0/src.freebsd-12.2-RC1-amd64-3.7/fortran_module-f2pywrappers2.o module1.o module2.o -L/usr/local/lib/gcc9/gcc/x86_64-portbld-freebsd12.1/9.3.0 -L/usr/local/lib -lpython3.7m -lgfortran -o ./fortran_module.so
Removing build directory /tmp/tmp9aa3w8b0
This creates fortran_module.so
in the directory.
Then, back in Python I try to do:
import fortran_module
But I get a segmentation fault:
$ /usr/local/bin/python3.7
Python 3.7.9 (default, Oct 3 2020, 01:29:35)
[Clang 8.0.1 (tags/RELEASE_801/final 366581)] on freebsd12
Type "help", "copyright", "credits" or "license" for more information.
readline: ~/.inputrc: line 1: nobeep: unknown variable name
readline: ~/.inputrc: line 1: nobeep: unknown variable name
>>> import fortran_module
Segmentation fault (core dumped)
Any help or hint on what I am doing wrong is welcomed. I am using gcc
and FreeBSD
(I abandoned the idea of using clang
anf flang
as FreeBSD's flang seems not to support iso_c_bindings
).
Note: I have also tried using --fcompiler=gnu95
as parameter of f2py
trying to ensure that flang
or clang
are not used.
Assuming thatf2py
cannot generate Python binding for Fortran routines with bind(C)
, a workaround might be to define a usual (non-bind(C)) routine and call C routines from inside (so somewhat similar to this page for using derived types). For example, a possible code may look like this (but clearly with more overhead for function calls...)
clib.c
double multiply(double a, double b) {
return a * b;
}
fmod.f90
module fmod
use iso_c_binding
implicit none
contains
function multiply(a, b) result(res)
real(8) :: a, b, res !! "8" just for test
interface
real(c_double) function c_multiply(a, b) bind(C,name="multiply")
import
real(c_double), value :: a,b
end
end interface
res = c_multiply(a, b)
end
end module
Compile:
$ gcc -c -fPIC clib.c -o clib.o
$ python3.9 -m numpy.f2py -c fmod.f90 clib.o -m py_fmod
Run:
$ python3.9
>>> import py_fmod
>>> print( py_fmod.fmod.__doc__ )
res = multiply(a,b)
Wrapper for ``multiply``.
Parameters
----------
a : input float
b : input float
Returns
-------
res : float
>>> py_fmod.fmod.multiply( a= 2.0, b= 3.0 )
6.0
I guess it will also be possible to define all the interface blocks in a separate Fortran module file, compile it independently from f2py
, and use
it from routines compiled with f2py
(so, separating the iso_c_binding + interface block from the f2py compilation). Indeed, it seems problematic if such an interface block is defined directly in the module header part for f2py (which seems very similar to the problem of derived types etc).