book
Authors: Kjell Jorner, …
In recent years, Python has taken over as the most commonly used language in applied science. Although Python itself as a scripting language is quite slow, it often serves as a glue between computationally demanding procedures written in compiled languages such as Fortran or C. Prime examples of this approch are the NumPy and SciPy packages that power applications such as machine learning via scikit-learn and quantum chemistry via PySCF.
There are a number of reasons why you would want to interface Python and Fortran, such as:
There are two main approaches to accessing Fortran from Python:
Behind the scenes, the automatic interface generators actually create a C interface for you, so the approaches don't really differ that much technically. But the user experience and ease of use is different.
Using your Fortran code in Python really isn't that difficult Here are two basic examples of the different approaches.
We start with a simple Fortran function that adds two numbers:
integer function add(a, b)
integer, intent(in) :: a, b
add = a + b
end function add
We place this function in a file called add.f90
and use f2py
to wrap it into a Python module called add
python -m numpy.f2py -c add.f90 -m add
A shared library file is created, in this case on MacOS with Python 3.9 it is called add.cpython-39-darwin.so
, but the exact name is not important.
This library can be imported directly into Python and we can use the function as a conventional Python function:
>>> from add import add
>>> add(1, 2)
3
We start with the same function from the previous section, but now we need to adapt it to be interoperable with C:
module mod_add
use, intrinsic :: iso_c_binding, only: c_int
implicit none
contains
integer(c_int) function add(a, b) bind(c)
integer(c_int), value, intent(in) :: a, b
add = a + b
end function add
end module mod_add
First, we needed to wrap the function in a module, mod_add
that we put in a file named mod_add.f90
.
Second, we needed to add a number of code elements to ensure C interoperability:
use, intrinsic :: iso_c_binding, only: c_int
c_int
integer type from the intrinsic module iso_c_binding
which provides support for C interoperability.integer(c_int)
bind(c)
value
We now need to compile the function into a shared library, here using gfortran
:
gfortran -shared -fPIC mod_add.f90 -o mod_add.so
This creates the shared library file mod_add.so
We can now import and use the function in Python with the built-in ctypes
library:
>>> from ctypes import CDLL
>>> lib = CDLL("mod_add.so")
>>> lib.add(1, 2)
3
Actually, we were a bit sloppy in the previous example as we should really match our variable types to conform to those of our Fortran function:
>>> from ctypes import CDLL, c_int
>>> lib = CDLL("mod_add.so")
>>> lib.add(c_int(1), c_int(2))
3
At this point you might be thinking that the explicit C interface seems awfully complicated compared to automatic interface generation with f2py
.
Indeed, f2py
is very convenient when wrapping individual functions or subroutines, but currently has some important limitations such as lack of support for derived types.
Additionally, when writing more elaborate APIs, the explicit C approach is much more flexible and often preferable.
It also gives you a bonus C interface that could be used from other programming languages than Python.