Rationale
Fantastic as Python is, it has its own drawbacks, and one of the
largest drawbacks is execution speed. Pure Python is quite slow, and this is why
one should use numpy
whenever possible, because it runs C/Fortran code under
the hood.
In case some algorithms are not provided by numpy
, scipy
or some
well established packages, one can implement the algorithm in Fortran or
C, and use a tool called f2py to convert the Fortran/C code into a
Python module.
General f2py usage
Wrapping Frotran/C to python using f2py consists of the following steps:
- create the signature file, containing descriptions of the function/subroutine. In case of Fortran, f2py can create the initial signature file by scanning the Fortran source code and catching all relevant info needed to create wrapper functions.
- Optional: edit the signature file to optimize wrappers.
- Read the signature file and write a Python C API module containing Fortran/C/Python bindings.
- Compile all sources and builds an extension module containing the wrappers.
Depending on a particular situation, these steps can be carried out in one command or step-by-step.
Below is a sample Fortran 77 program for illustration:
C FILE: FIB1.F
SUBROUTINE FIB(A,N)
C
C CALCULATE FIRST N FIBONACCI NUMBERS
C
INTEGER N
REAL*8 A(N)
DO I=1,N
IF (I.EQ.1) THEN
A(I) = 0.0D0
ELSEIF (I.EQ.2) THEN
A(I) = 1.0D0
ELSE
A(I) = A(I-1) + A(I-2)
ENDIF
ENDDO
END
C END FILE FIB1.F
The quick way
To wrap the Fortran subroutine FIB
to python, run
f2py -c fib1.f -m fib1
This builds an extension module (fib1.so
) in the current directory.
To access the module in Python, use
import fib1
print fib1.fib.__doc__
NOTE that f2py
implements basic compatibility checks between related arguments
in order to avoid any unexpected crashes.
When a numpy
array, that is Fortran contiguous and has a dtype
corresponding to presumed Fortran type, is used as an input array
argument, then its C pointer is directly passed to Fortran.
Otherwise, f2py makes a contiguous copy (with a proper dtype) of the input array and passes C pointer of the copy to Fortran subroutine.
The smart way
1. Create signature file from fib1.f
f2py fib1.f -m fib2 -h fib2.pyf
This saves the signature file (-h
flag) to fib2.pyf
. Its content is shown below:
! -*- f90 -*-
python module fib2 ! in
interface ! in :fib2
subroutine fib(a,n) ! in :fib2:fib1.f
real*8 dimension(n) :: a
integer optional,check(len(a)>=n),depend(a) :: n=len(a)
end subroutine fib
end interface
end python module fib2
! This file was auto-generated with f2py (version:2.28.198-1366).
! See http://cens.ioc.ee/projects/f2py2e/
2. Edit the signature file
We will teach f2py that the argument n
is an input argument (use intent(in)
attribute) and that the result, i.e. the contents of a
after calling Fortran function FIB
, should be returned to Python (use intent(out)
attribute).
In addition, an array a
should be created dynamically using the size given by he input argument n
(use depend(n)
attribute) to indicate dependence relation.
The content of a modified version of fib1.pyf
(saved as fib2.pyf
) is as follows:
! -*- f90 -*-
python module fib2
interface
subroutine fib(a,n)
real*8 dimension(n),intent(out),depend(n) :: a
integer intent(in) :: n
end subroutine fib
end interface
end python module fib2
3. Build the extension module
f2py -c fib2.pyf fib2.f
4. Use in Python
import fib2
print(fib2.fib(8))
Link multiple source files
Scenario
Need to create a wrapper for a fortran module (e.g. mod1.f90
), which
calls subprogams in another source file (e.g. mod2.f90
).
Solution
-
Compile each module first:
gfortran -c mod2.f90 gfortran -c mod1.f90
NOTE that
mod2.f90
goes first as it is called inmod1.f90
. -
Then create the wrapper signature:
f2py mod1.f90 -m mod1 -h mod1.pyf
Edit
mod1.pyf
if needed. -
Then compile:
f2py -c mo1.pyf mod2.f90 mod1.f90
NOTE here the order of
mod1.f90
andmod2.f90
doesn’t seem to matter.
Link with library
scenario 1
Need to create a wrapper using f2py
for a fortran module
(e.g. module.f90
), which calls a method (e.g. dbdsqr
) in a library
(e.g. LAPACK
).
solution
No need to wrap the entire LAPACK
library, only the routines you
want. These routines are connected with fortran calls under the hood.
But it requires you to provide the path to link to the library.
E.g.
f2py -L/path/to/lapack -llapack -m module -c module.f
scenario 2
Need to create a wrapper using fftw3
for a fortran module (e.g. fftconv2d.f90
), which includes fftw3
by include "fftw3.f"
.
The compilation method:
f2py -c -L/usr/lib -lfftw3 -I/usr/include -m fftconv2d fftconv2d.f90
A pitfall regarding precision control
In defining precision levels in modules in the following manner:
module mod
implicit none
integer, parameter :: ikind = selected_real_kind(15)
public sub
contains
subroutine sub(x,...)
implicit none
...
real(kind=ikind), dimension(:) :: x
...
This works in Fortran, but doesn’t work well with f2py
: after
passing in the data, the values would go nuts. The solution is not to
define ikind
outside subroutines, instead, put it inside:
module mod
implicit none
public sub
contains
subroutine sub(x,...)
implicit none
integer, parameter :: ikind = selected_real_kind(15)
real(kind=ikind), dimension(:) :: x
...
Also note that the following "nested" definition doesn’t work either:
module mod
implicit none
integer, parameter :: ikind0 = 15
public sub
contains
subroutine sub(x,...)
implicit none
integer, parameter :: ikind = selected_real_kind(ikind0)
real(kind=ikind), dimension(:) :: x
...
[…] Lastly, one can use f2py to compile the Fortran code into a Python module. For more information regarding this process, check out this post Use f2py to compile Python module. […]