Tutorials
Blog
About

Fortran, Python

How to Call Fortran from Python

by Martin D. Maas, Ph.D
@MartinDMaas

Last updated: 2021-05-01

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, esencially, two basic ways to call Fortran from 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.

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')
myflib.foo(a)

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.

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 myflib.so

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('myflib.so') 

# 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))
print(b)

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('myflib.so') 
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))
print(x)

Ask me a question or send me your comments!

Don't hesitate to ask me any question about the topics I cover on this blog!

Click here to reach out!


Selected Posts