F90wrap
F90 to Python interface generator with derived type support
Install / Use
/learn @jameskermode/F90wrapREADME
f90wrap: Fortran to Python interface generator with derived type support
f90wrap is a tool to automatically generate Python extension modules which interface to Fortran code that makes use of derived types. It builds on the capabilities of the popular f2py utility by generating a simpler Fortran 90 interface to the original Fortran code which is then suitable for wrapping with f2py, together with a higher-level Pythonic wrapper that makes the existance of an additional layer transparent to the final user.
Copyright (C) James Kermode 2011-2021. Released under the GNU Lesser General Public License, version 3. Parts originally based on f90doc - automatic documentation generator for Fortran 90. Copyright (C) 2004 Ian Rutt.
If you would like to license the source code under different terms, please contact James Kermode james.kermode@gmail.com
Dependencies
- Python 3.9+ (Python 2.7 no longer supported)
- Recent version of numpy which includes
f2py - Fortran compiler - tested with
gfortran4.6+ and recentifort12+
Installation
For the latest stable release, install with pip:
pip install f90wrap
There is also a conda package on conda-forge:
conda install -c conda-forge f90wrap
For the development version, installation is as follows:
pip install git+https://github.com/jameskermode/f90wrap
Note that if your Fortran 90 compiler has a non-standard name
(e.g. gfortran-9) then you need to set the F90 environment variable
prior to installing f90wrap to ensure it uses the correct one, e.g.
F90=gfortran-9 pip install f90wrap
Examples and Testing
To test the installation, run make test from the examples/
directory. You may find the code in the various examples useful.
Citing f90wrap
If you find f90wrap useful in academic work, please cite the following
(open access) publication:
J. R. Kermode, f90wrap: an automated tool for constructing deep Python interfaces to modern Fortran codes. J. Phys. Condens. Matter (2020) doi:10.1088/1361-648X/ab82d2
BibTeX entry:
@ARTICLE{Kermode2020-f90wrap,
title = "f90wrap: an automated tool for constructing deep Python
interfaces to modern Fortran codes",
author = "Kermode, James R",
journal = "J. Phys. Condens. Matter",
month = mar,
year = 2020,
keywords = "Fortran; Interfacing; Interoperability; Python; Wrapping codes;
f2py",
language = "en",
issn = "0953-8984, 1361-648X",
pmid = "32209737",
doi = "10.1088/1361-648X/ab82d2"
}
Case studies
f90wrap has been used to wrap the following large-scale scientific applications:
- QUIP - molecular dynamics code
- CASTEP - CasPyTep wrappers for electronic structure code
- QEpy - Python wrapper for Quantum Espresso electronic structure code
See this Jupyter notebook from a recent seminar for more details.
Usage
To use f90wrap to wrap a set of Fortran 90 source files and produce
wrappers suitable for input to f2py use:
f90wrap -m MODULE F90_FILES
where MODULE is the name of the Python module you want to produce (e.g.
the name of the Fortran code you are wrapping) and F90_FILES is a list
of Fortran 90 source files containing the modules, types and subroutines
you would like to expose via Python.
This will produce two types of output: Fortran 90 wrapper files suitable
for input to f2py to produce a low-level Python extension module, and a
high-level Python module desinged to be used together with the
f2py-generated module to give a more Pythonic interface.
One Fortran 90 wrapper file is written for each source file, named
f90wrap_F90_FILE.f90, plus possibly an extra file named
f90wrap_toplevel.f90 if there are any subroutines or functions defined
outside of modules in F90_FILES.
To use f2py to compile these wrappers into an extension module, use:
f2py -c -m _MODULE OBJ_FILES f90wrap_*.f90 *.o
where _MODULE is the name of the low-level extension module.
Optionally, you can replace f2py with f2py-f90wrap, which is a
slightly modified version of f2py included in this distribution
that introduces the following features:
- Allow the Fortran
present()intrinsic function to work correctly with optional arguments. If an argument to an f2py wrapped function is optional and is not given, replace it withNULL. - Allow Fortran routines to raise a RuntimeError exception with a
message by calling an external function
f90wrap_abort(). This is implemented using asetjmp()/longjmp()trap. - Allow Fortran routines to be interrupted with
Ctrl+Cby installing a custom interrupt handler before the call into Fortran is made. After the Fortran routine returns, the previous interrupt handler is restored.
Direct-C mode extensions
Quick build: f90wrap --build -m mymodule source.f90
Manual compilation: f90wrap --direct-c -m mymodule source.f90
Python package (pyproject.toml + setup.py):
[build-system]
requires = ["setuptools", "numpy", "f90wrap"]
[project]
name = "mypackage"
version = "0.1.0"
[tool.setuptools.packages]
find = {}
# setup.py
from setuptools import setup
from f90wrap.setuptools_ext import F90WrapExtension, build_ext_cmdclass
setup(ext_modules=[F90WrapExtension("mymodule", ["src/mymodule.f90"])],
cmdclass=build_ext_cmdclass())
Result: import mypackage then use mypackage.mymodule
Notes
- Unlike standard
f2py,f90wrapconverts allintent(out)arrays tointent(in, out). This was a deliberate design decision to allow allocatable and automatic arrays of unknown output size to be used. It is hard in general to work out what size array needs to be allocated, so relying on the the user to pre-allocate from Python is the safest solution. - Scalar arguments without
intentare treated asintent(in)byf2py. To haveinoutscalars, you need to callf90wrapwith the--default-to-inoutflag and declare the python variables as 1-length numpy arrays (numpy.zeros(1)for example). - Pointer arguments are not supported.
- Arrays of derived types are currently not fully supported: a workaround is provided for 1D-fixed-length arrays, i.e.
type(a), dimension(b) :: c. In this case, the super-typeType_a_Xb_Arraywill be created, and the array of types can be accessed throughc.items. Note that dimension b can not be:, but can be a parameter. - Doxygen documentation in Fortran sources are parsed and given as docstring in corresponding python interfaces. Doxygen support is partial and keyword support is limited to
brief,details,file,authorandcopyright.
Troubleshooting
NumPy 2.0+ and Meson Backend Issues
Starting with NumPy 2.0, the f2py build system transitioned from distutils
to meson. This can cause issues with f90wrap, particularly when Fortran module
files (.mod files) cannot be found during compilation.
Symptom: Errors like "Cannot open module file 'modulename.mod' for reading" during the f2py-f90wrap compilation step.
Workarounds:
-
Set FFLAGS to include current directory (recommended):
export FFLAGS="-I$(pwd)" f2py-f90wrap -c -m _mymodule f90wrap_*.f90 *.o -
Use the
--build-diroption with f2py-f90wrap:f2py-f90wrap --build-dir build -c -m _mymodule f90wrap_*.f90 mymodule.f90Note: When using
--build-dir, pass the original.f90source files instead of pre-compiled.ofiles, as the meson backend needs to compile them in the build directory where the.modfiles will be generated. -
Downgrade to NumPy < 2.0 (temporary):
pip install 'numpy<2.0'
Note: The f2py-f90wrap script includes patches to help with the meson
backend, including automatic handling of include paths and library directories
when using --build-dir. If you encounter issues, please report them at
https://github.com/jameskermode/f90wrap/issues
How f90wrap works
There are five steps in the process of wrapping a Fortran 90 routine to allow it to be called from Python.
- The Fortran source files are scanned, building up an abstract symbol tree (AST) which describes all the modules, types, subroutines and functions found.
- The AST is transformed to remove nodes which should not be wrapped (e.g. private symbols in modules, routines with arguments of a derived type not defined in the project, etc.)
- The
f90wrap.f90wrapgen.F90WrapperGeneratorclass is used to write a simplified Fortran 90 prototype for each routine, with derived type arguments replaced by integer arrays containing a representation of a pointer to the derived type, in the manner described in Pletzer2008. This allows opaque references to the true Fortran derived type data structures to be passed back and forth between Python and Fortran. - Standard mode (default): f2py is used to combine the F90 wrappers and the original compiled functions into a Python extension module (optionally, f2py can be replaced by f2py-f90wrap, a slightly modified version which adds support f
