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
f2py
gfortran
4.6+ and recent ifort
12+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
To test the installation, run make test
from the examples/
directory. You may find the code in the various examples useful.
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"
}
f90wrap has been used to wrap the following large-scale scientific applications:
See this Jupyter notebook from a recent seminar for more details.
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:
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 with NULL
.f90wrap_abort
().
This is implemented using a setjmp()/longjmp()
trap.Ctrl+C
by installing
a custom interrupt handler before the call into Fortran is made.
After the Fortran routine returns, the previous interrupt handler
is restored.f2py
, f90wrap
converts all intent(out)
arrays to
intent(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.intent
are treated as intent(in)
by f2py
. To have inout
scalars, you need to call f90wrap
with the --default-to-inout
flag and declare the python variables as 1-length numpy arrays (numpy.zeros(1)
for example).type(a), dimension(b) :: c
. In this case, the super-type Type_a_Xb_Array
will be created, and the array of types can be accessed through c.items
. Note that dimension b can not be :
, but can be a parameter.brief
, details
, file
, author
and copyright
.There are five steps in the process of wrapping a Fortran 90 routine to allow it to be called from Python.
f90wrap.f90wrapgen.F90WrapperGenerator
class 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.f90wrap.pywrapgen.PythonWrapperGenerator
class is used to
write a thin object-oriented layer on top of the f2py generated
wrapper functions which handles conversion between Python object
instances and Fortran derived-type variables, converting arguments
back and forth automatically.Additional command line arguments can be passed to f90wrap to customize
how the wrappers are generated. See the examples/
directory to see how
some of the options are used:
-h, --help show this help message and exit
-v, --verbose set verbosity level [default: None]
-V, --version show program's version number and exit
-p PREFIX, --prefix PREFIX
Prefix to prepend to arguments and subroutines.
-c [CALLBACK [CALLBACK ...]], --callback [CALLBACK [CALLBACK ...]]
Names of permitted callback routines.
-C [CONSTRUCTORS [CONSTRUCTORS ...]], --constructors [CONSTRUCTORS [CONSTRUCTORS ...]]
Names of constructor routines.
-D [DESTRUCTORS [DESTRUCTORS ...]], --destructors [DESTRUCTORS [DESTRUCTORS ...]]
Names of destructor routines.
-k KIND_MAP, --kind-map KIND_MAP
File containting Python dictionary in f2py_f2cmap
format
-s STRING_LENGTHS, --string-lengths STRING_LENGTHS
"File containing Python dictionary mapping string
length names to values
-S DEFAULT_STRING_LENGTH, --default-string-length DEFAULT_STRING_LENGTH
Default length of character strings
-i INIT_LINES, --init-lines INIT_LINES
File containing Python dictionary mapping type names
to necessary initialisation code
-I INIT_FILE, --init-file INIT_FILE
Python source file containing code to be added to
autogenerated __init__.py
-A ARGUMENT_NAME_MAP, --argument-name-map ARGUMENT_NAME_MAP
File containing Python dictionary to rename Fortran
arguments
--short-names SHORT_NAMES
File containing Python dictionary mapping full type
names to abbreviations
--py-mod-names PY_MOD_NAMES
File containing Python dictionary mapping Fortran
module names to Python ones
--class-names CLASS_NAMES
File containing Python dictionary mapping Fortran type
names to Python classes
--joint-modules JOINT_MODULES
File containing Python dictionary mapping modules
defining times to list of additional modules defining
methods
-m MOD_NAME, --mod-name MOD_NAME
Name of output extension module (without .so
extension).
-M, --move-methods Convert routines with derived type instance as first
agument into class methods
--shorten-routine-names
Remove type name prefix from routine names, e.g.
cell_symmetrise() -> symmetrise()
-P, --package Generate a Python package instead of a single module
-a ABORT_FUNC, --abort-func ABORT_FUNC
Name of Fortran subroutine to invoke if a fatal error
occurs
--only [ONLY [ONLY ...]]
Subroutines to include in wrapper
--skip [SKIP [SKIP ...]]
Subroutines to exclude modules and subroutines from
wrapper
--skip-types [SKIP_TYPES [SKIP_TYPES ...]]
Subroutines to exclude types from wrapper
--force-public [FORCE_PUBLIC [FORCE_PUBLIC ...]]
Names which are forced to be make public
--default-to-inout Sets all arguments without intent to intent(inout)
--conf-file CONF_FILE
Use Python configuration script to set options
--documentation-plugin DOCUMENTATION_PLUGIN
Use Python script for expanding the documentation of
functions and subroutines. All lines of the given tree
object are passed to it with a reference to its
documentation
--py-max-line-length PY_MAX_LINE_LENGTH
Maximum length of lines in python files written.
Default: 80
--f90-max-line-length F90_MAX_LINE_LENGTH
Maximum length of lines in fortan files written.
Default: 120
--type-check Check for type/shape matching of Python argument with the wrapped Fortran subroutine
James Kermode jameskermode
Wheels are built on push and pull requests to master
using cibuildwheel
with this workflow.
To make a release candidate create a tag with a suffix such as -rc1
for the first attempt,
push to trigger the build:
git commit -m 'release v0.x.z.rc1'
git tag v0.x.y.rc1
git push --tags
If all goes well, the .whl
files will show up as assets within a new GitHub
release. The installation process can now be tested locally.
Once everything works correctly, make a full release (i.e. create a tag named
just v0.x.y
without the -rc1
suffix). This will trigger the upload of wheels
and source distribution to PyPI.