Use f2py to compile Python module

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:

  1. 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.
  2. Optional: edit the signature file to optimize wrappers.
  3. Read the signature file and write a Python C API module containing Fortran/C/Python bindings.
  4. 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

  1. Compile each module first:

    gfortran -c mod2.f90
    gfortran -c mod1.f90
    

    NOTE that mod2.f90 goes first as it is called in mod1.f90.

  2. Then create the wrapper signature:

    f2py mod1.f90 -m mod1 -h mod1.pyf
    

    Edit mod1.pyf if needed.

  3. Then compile:

    f2py -c mo1.pyf mod2.f90 mod1.f90
    

    NOTE here the order of mod1.f90 and mod2.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
    ...

One comment

  1. […] 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. […]

Leave a Reply