Python bindings for Fortran
Project description
gfort2py
Library to allow calling Fortran code from Python. Requires gfortran>=8.0, Works with python >= 3.10
Build
Installing locally:
python -m pip install .
or install via pypi
python -m pip install --upgrade --user gfort2py
For a full list of supported platforms see the support documentation.
Why use this over other Fortran to Python translators?
gfort2py has three main aims:
- Make it trivially easy to call Fortran code from Python
- Minimise the number of changes needed in the Fortran code to make this work.
- Support as many Fortran features as possible.
We achieve this by tightly coupling the code to the gfortran compiler, by doing so we can easily embed assumptions about how advanced Fortran features work which makes development easier and minimises the number of changes needed on the Fortran side.
gfort2py uses the gfortran mod files to translate your Fortran code's ABI to Python-compatible types using Python's ctypes library.
By using the mod file we can determine the call signature of all procedures, components of derived types, and the size and shapes of all module-level variables. As long as your code is inside a Fortran module, no other changes are needed to your Fortran code.
The downside to this approach is that we are tightly tied to gfortran's ABI, which means we cannot support other non-gfortran compilers and we do not support all versions of gfortran. When gfortran next breaks its ABI (which happens rarely, the last break was gfortran 15, before that was 8) we will re-evaluate our supported gfortran versions.
Using
There are two ways to load Fortran code into Python:
fFort or compile. The recommended way
is via fFort for interfacing with existing code,
while compile is more suitable for wrapping short snippets of Fortran code
fFort
Your Fortran code must be inside a module and then compiled as a shared library.
On linux:
gfortran -fPIC -shared -c file.f90
gfortran -fPIC -shared -o libfile.so file.f90
On MacOS:
gfortran -dynamiclib -c file.f90
gfortran -dynamiclib -o libfile.dylib file.f90
On Windows:
gfortran -shared -c file.f90
gfortran -shared -o libfile.dll file.f90
If the shared library needs other
shared libraries you may need to set the LD_LIBRARY_PATH environment variable, and it is also recommended to run chrpath on the shared
libraries so you can access them from anywhere.
Python side
import gfort2py as gf
SHARED_LIB_NAME=f'./test_mod.{gf.lib_ext()}' # Handle whether on Linux, Mac, or Windows
MOD_FILE_NAME='tester.mod'
x=gf.fFort(SHARED_LIB_NAME,MOD_FILE_NAME)
compile
import gfort2py as gf
fstr = """
integer function myfunc(x,y)
integer :: x,y
myfunc = x+y
end function myfunc
"""
x = gf.compile(string=fstr)
The Fortran code can also be in a file in which case:
import gfort2py as gf
x = gf.compile(file='my_fortran_file.f90')
In either case the code will be compiled into a Fortran module and then into a shared library. Any Fortran code is valid as long as it can be inserted into a Fortran Module. This code MUST NOT be inside a module.
Additional options available for compile:
- FC: str Path to gfortran compiler
- FFLAGS: str Additional Fortran compile options.
- LDLIBS: str Any additional libraries needed to be linked in (-l)
- LDFLAGS: str Locations of additional libraries (-L)
- INCLUDE_FLAGS: str Include directory flags (-I)
NOTE: The interface to compile is currently considered unstable and may change.
Interface
x now contains all variables, parameters and procedures from the module (tab completable), and is independent of how the Fortran code was loaded.
Functions
y = x.func_name(a,b,c)
Will call the Fortran function with variables a,b,c and returns the result in y.
y will be a named tuple containing (result, args). result is a Python object for the return value (None for subroutines), and args is a dict containing all arguments passed to the procedure (both those with intent(in), which are unchanged, and those with intent(inout/out), which may change).
Variables
x.some_var = 1
Sets a module variable to 1, will attempt to coerce it to the Fortran type
x.some_var
Will return a Python object
Optional arguments that are not present should be passed as a Python None.
Arrays
Arrays should be passed as a NumPy array of the correct size and shape.
Remember that Fortran by default has 1-based array numbering while Numpy is 0-based.
If a procedure expects an unallocated array, then pass None as the argument, otherwise pass an array of the correct shape.
For CHARACTER arrays, use NumPy string dtypes that match the Fortran kind:
character(kind=1, len=N): pass a byte-string array (for exampledtype="S10").character(kind=4, len=N): pass a unicode array (for exampledtype=np.str_).
For fixed-length CHARACTER arrays, values are truncated or space-padded to the declared Fortran length.
Unicode CHARACTER support
Unicode CHARACTER values using selected_char_kind('ISO_10646') are supported for:
- Module variables and parameters
- Scalar procedure arguments and function return values
- Explicit-size CHARACTER arrays
- Allocatable module CHARACTER arrays
Example Fortran declarations:
integer, parameter :: CK = selected_char_kind('ISO_10646')
character(kind=CK, len=100) :: uni_set
character(kind=CK, len=100), dimension(3) :: uni_arr
character(kind=CK, len=100), dimension(:), allocatable :: uni_alloc_arr
Example Python usage:
import numpy as np
import gfort2py as gf
x = gf.fFort("./tests/build/unicode." + gf.lib_ext(), "./tests/build/unicode.mod")
x.uni_set = "漢字Ω"
print(x.uni_set.strip())
x.uni_arr = np.array(["🚀🚀🚀", "🌍🌍🌍", "✨✨✨"], dtype=np.str_)
print(x.uni_arr)
x.alloc_uni_alloc_arr()
print(x.uni_alloc_arr)
Current limitation: unicode CHARACTER dummy arguments with dimension(:) (assumed-shape) or dimension(..) (assumed-rank) are not considered stable yet.
Derived types
Derived types can be set with a dict
x.my_dt={'x':1,'y':'abc'}
y=x.my_dt
y['x']
y['y']
If the derived type contains another derived type then you can set a dict in a dict
x.my_dt={'x':1,'y':{'a':1}}
When setting the components of a derived type you do not need to specify all of them at the same time.
If you have an array of derived types
type(my_type), dimension(5) :: my_dt
type(my_type), dimension(5,5) :: my_dt2
Elements can be accessed via an index:
x.my_dt[0]['x']
x.my_dt2[0,0]['x']
You can only access one component at a time (i.e no striding [:]).
Derived types that are dummy arguments to a procedure are returned as a fDT type. This is a dict-like object where the components
can only be accessed via the item interface ['x'] and not as attributes .x. This was done so that we do not have a name collision
between Python functions (keys, items etc) and any Fortran-derived type components.
You can pass a fDT as an argument to a procedure.
Arrays of derived types can be set (or allocatad) with a list of dicts:
x.my_dt= [
[{"a_int": 1}, {"a_int": 2}],
[{"a_int": 3}, {"a_int": 4}],
]
Type-bound procedures
Type-bound procedures declared inside a Fortran derived type are available as Python callables on derived-type objects.
For nopass bindings, call the method with its declared arguments:
y = x.p_proc.proc_no_pass(3)
For pass(this) bindings, the passed object is inserted automatically.
Do not pass this explicitly:
x.p_proc.proc_pass(9)
Type-bound methods are also resolved on extended types:
x.p_proc_extend.proc_no_pass(4)
x.p_proc_extend.proc_pass(6)
Polymorphic CLASS(...) passed-object dummies are supported for
type-bound PASS calls.
Current limitation: procedures with polymorphic CLASS(...) array dummy
arguments (for example class(t), dimension(:)) still require class-wrapper
objects produced by gfort2py. Passing plain Python placeholders (like [])
for these dummies raises TypeError.
Quad precision variables
Quad precision (REAL128) variables are not natively supported by Python thus we need a different way to handle them. For now that is the pyQuadp library which can be installed from PyPi with:
python -m pip install pyquadp
or from a git checkout:
python -m pip install .[quad]
For more details see pyQuadp's documentation, but briefly you can create a
quad precision variable from an int, float, or string. On return you will receive a qfloat type. This qfloat type acts like a Python Number, so you can do things like add, multiply, subtract etc this Number with other Numbers (including non-qfloat types).
Guaranteed support currently includes:
- Module variables
- Parameters
- Procedure arguments (including array arguments)
Quad functions returning a scalar are not supported. Either return a 1-element quad array, return the value as a dummy argument, or set a module variable with the return value.
pyQuadp is currently an optional requirement, you must manually install it, it does not get auto-installed when gfort2py is installed. If you try to access a quad precision variable without pyQuadp you should get a TypeError.
Callback arguments
To pass a Fortran function as a callback argument to another function then pass the function directly:
y = x.callback_function(1)
y = x.another_function(x.callback_function)
Currently only Fortran functions can be passed. No checking is done to ensure that the callback function has the correct signature to be a callback to the second function.
The callback can also be created in Python at runtime (but must be valid Fortran):
fstr = """
integer function callback(x)
integer :: x
write(*,*) x
callback = 3*x
end function callback
"""
f = gf.compile(fstr)
y = x.another_function(f.callback)
Testing
python -m pip install .[test]
pytest -v
To run unit tests
Supported features
The items below are the currently supported with test-covered behavior.
Module symbols
- Scalars, parameters, and characters
- Explicit-size and allocatable arrays
- Assumed shape arrays
- Derived types, including nested derived types
- Explicit and allocatable arrays of derived types
- Strings and unicode strings (kind=4), including allocatable arrays
- Quad module symbols and arguments when
pyquadpis installed and compiler support is available - Polymorphic class values used via the documented object wrappers
Procedure calls
- Scalar, string, explicit-array, assumed-size, assumed-shape, assumed-rank, and allocatable-array arguments
- Derived type arguments and returns
- Pointer, optional, value, and keyword arguments
- Functions passed as callback arguments
- Type-bound procedures (including
pass/nopass) - Unary expression-based shape resolution (for example
dimension(n+1))
Known issues/missing features
- Unicode assumed-shape/assumed-rank character dummy arrays
- Elemental/generic procedure behavior
- Common blocks
- Operator overload and procedure overloading
- Paramterised derived types
Accessing module file data
For direct parsing and inspection of .mod files, use https://github.com/rjfarmer/gfModParser.
import gfModParser as gp
module = gp.Module("file.mod")
print(module.keys())
print(module["a_variable"])
Contributing
Bug reports are of course welcome and PR's should target the main branch.
For those wanting to get more involved, adding Fortran examples to the test suite of currently untested or unsupported features would be helpful. Bonus points if you also provide a Python test case (that can be marked @pytest.mark.skip if it does not work) that demonstrates the proposed interface to the new Fortran feature. Features with test cases will move higher in the order of things I add to the code.
See how to write a test case for details on how to write test cases.
AI contributions are welcome, though please disclose if you did use an AI. Keep changes minimal and focused and remember to add test cases.
For those wanting to go further and add the new feature themselves open a bug report and we can chat about what needs doing.
Debugging
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gfort2py-3.0.0.tar.gz.
File metadata
- Download URL: gfort2py-3.0.0.tar.gz
- Upload date:
- Size: 119.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c770606ade9b0a03300027e999f1208ffa1315fcfb735aef019279ff2803b75
|
|
| MD5 |
ccb7c2add3ba3ba54801c366ba4d4503
|
|
| BLAKE2b-256 |
632a44eb826734c2444e5209e4c99e832c91cb4c7853faa7d5e5df1b37b848fa
|
Provenance
The following attestation bundles were made for gfort2py-3.0.0.tar.gz:
Publisher:
pypi.yml on rjfarmer/gfort2py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gfort2py-3.0.0.tar.gz -
Subject digest:
5c770606ade9b0a03300027e999f1208ffa1315fcfb735aef019279ff2803b75 - Sigstore transparency entry: 1696171501
- Sigstore integration time:
-
Permalink:
rjfarmer/gfort2py@c6bbcc8a867c2356387edce8cf4cb801638b2fba -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/rjfarmer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@c6bbcc8a867c2356387edce8cf4cb801638b2fba -
Trigger Event:
push
-
Statement type:
File details
Details for the file gfort2py-3.0.0-py3-none-any.whl.
File metadata
- Download URL: gfort2py-3.0.0-py3-none-any.whl
- Upload date:
- Size: 53.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
022567d0c96cc2e8130ee26d9305b14444ace799fff00c9823cca37aa50acf25
|
|
| MD5 |
decde0991a93d164315a3fa6d6d52a80
|
|
| BLAKE2b-256 |
8c5bddf482f8a87797521e164a9ea610f3ca3762900a3130de91300b774f8876
|
Provenance
The following attestation bundles were made for gfort2py-3.0.0-py3-none-any.whl:
Publisher:
pypi.yml on rjfarmer/gfort2py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gfort2py-3.0.0-py3-none-any.whl -
Subject digest:
022567d0c96cc2e8130ee26d9305b14444ace799fff00c9823cca37aa50acf25 - Sigstore transparency entry: 1696171570
- Sigstore integration time:
-
Permalink:
rjfarmer/gfort2py@c6bbcc8a867c2356387edce8cf4cb801638b2fba -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/rjfarmer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@c6bbcc8a867c2356387edce8cf4cb801638b2fba -
Trigger Event:
push
-
Statement type: