How to Call Fortran from Python

by Martin D. Maas, Ph.D

Combining the incredible flexibility of Python with Fortran for high-performance number-crunching is an excellent idea, especially if you already have some legacy Fortran code hanging around. Here's how to do it.

Combine the numerical performance of Fortran with the wide scope and flexibility of Python

There are basically two basic ways to call Fortran from Python:

  • Use an automatic wrapper such as F2py or fmodpy. This is the simplest way, as the required wrappers are produced automatically.
  • Combine iso_c_bindings on the Fortran side, with ctypes or Cython on the Python side. This is the most general way of using Fortran in Python.

For a thorough explanation of what is going on under the hood in the different alternatives, a good reference is the Numpy user manual section on ‘using Python as glue’, which you can find here.

F2py is the most established wrapper-generation library, as it is actually a part of Numpy, but regrettably supports a limited subset of Fortran — it is great for older Fortran code, though. You might also want to try out the newer fmodpy, that supports modern Fortran.

In this tutorial, we will go through some quick examples. In particular, we will test the different methods of passing a complex Numpy array to Fortran.

F2PY Example

This method is absolutely straightforward. Let’s say we have the following Fortran function,

!   my_fortran_lib.f90:
function foo(a) result(b)
    implicit none
    real(kind=8), intent(in)    :: a(:,:)
    complex(kind=8)             :: b(size(a,1),size(a,2))
    b = exp((0,1)*a)
end function foo

To compile it and create a Python extension module, use the following command: (pay attention to the “3” in F2py3 in case you intend to use Python 3).

f2py3 -c -m myflib my_fortran_lib.f90

That’s it, now the function is available to import it as a Python module. Simply call it by

import myflib
import numpy as np

a = np.array([[1,2,3,4], [5,6,7,8]], order='F')

Also, it is important to remember that C and Fortran employ different array orderings. Python normally employs the c-style row-major-order, while in Fortran the column-major-order is employed. By setting the Numpy order property to ‘F’ we are complying with the Fortran ordering.

iso_c_bindings and ctypes example

The Fortran 2003 standard introduced iso_c_bindings as a means to improve interoperability with C. On the Python side, ctypes is a part of the stdlib which implements interfaces to foreign functions. These two tools can be combined to create an interface that enables Fortran and Python compatibility.

One automatic wrapper library that is based on this approach is fmodpy, which might prove valuable in calling modern Fortran from Python.

In any case, if fmodpy doesn’t work out of the box, or for any other reason a manual wrapper has to be written anyway, the best way to do it is by using iso_c_bindings and ctpyes.

2.1 Getting started

As this is significantly more complicated than F2py we will start with a simple (yet meaningful) example, to get familiar with the basics. We will build a function that takes a double-precision float and adds the number 2.

As a first step, we need to declare the C types of each argument of our Fortran functions or subroutines. A good reference on how to translate each variable type can be found in the GCC docs here

In a file named my_fortran_lib.f90 we write:

function sum2(a) result(b) bind(c, name='sum2')
    use iso_c_binding
    implicit none
    real(c_double), intent(in)  :: a
    real(c_double)              :: b
    b = a + 2.d0
end function sum2

We now compile this code as a shared library, with the command

gfortran -shared my_fortran_lib.f90 -o

Now we have a shared library and we need to import it into Python. We use ctypes for this matter. It is also important to remember that, by default, Fortran passes values by reference.

import ctypes as ct

# import the shared library
fortlib = ct.CDLL('') 

# Specify arguments and result types
fortlib.sum2.argtypes = [ct.POINTER(ct.c_double)]
fortlib.sum2.restype = ct.c_double

# Create a double and pass it to Fotran (by reference)
a = ct.c_double(5)
b = fortlib.sum2(ct.byref(a))

2.2 Dealing with complex numbers

Things start getting trickier here. Even though Fortran interoperability with C supports a ‘c_double_complex’ type which is part of the C99 standard, this is not supported by Python’s ctypes, at least at the time of writing this article. Follow this issue for updates on this matter. The workaround, for the moment, is to rely on wrapper functions, which can be easily coded in Fortran.

2.3 Dealing with multidimensional arrays

It is usually the case that we want to pass a large Numpy array to Fortran, as in our F2PY example, which also returns an array.

An important thing to notice is that the return type of a C-interoperable Fortran function can’t be an array. That’s because Fortran arrays are quite different from C arrays. The workaround is to rely on subroutines instead.

Our Fortran subroutine should look something like the following:

subroutine double_array(x,N) bind(C, name="double_array")
    use iso_c_binding
    implicit none
    integer(c_int), intent(in), value   :: N
    real(c_double), intent(inout)       :: x(N,N)
    x  = exp(x)
end subroutine double_array

After compiling it as a shared library in the same way as before, we must build a Python interface. For this matter, we can resort to Numpy.ctypeslib to help us in building the required interface.

import ctypes as ct
import numpy as np

# import the shared library
fortlib = ct.CDLL('') 
f = fortlib.double_array

# Specify arguments type as a pointer to double and an integer
f.argtypes=[ct.POINTER(ct.c_double), ct.c_int]

# Create a double array, pass it to Fotran as a pointer
x = np.ones((3,3), order="F")
x_ptr = x.ctypes.data_as(ct.POINTER(ct.c_double))

# Call function
rint = f(x_ptr, ct.c_int(3))

Do you need help with Fortran?

You might be also interested in my article on How to Work with Legacy Fortran Code: A Short Guideline.

If you are looking for general advice, or have a small bug, etc, a great resource to ask for help is the Fortran Forum.

You can also hire a freelancer! Make sure to contact them and ask them a question on how they can help you.