Package: gfort2py
gfort2pyLibrary to allow calling Fortran code from Python. Requires gfortran>=8.0, Works with python >= 3.7BuildInstalling locally:python-mpipinstall.or install via pypipython-mpipinstall--upgrade--usergfort2pyFor a full list of supportetd platformssee 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 PythonMinimise 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 use the gfortranmodfiles to translate your Fortran code's ABI to Python-compatible types using Python's ctype library.
By using themodfile 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 can not 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 8) we will re-evaluate our supported gfortran versions.UsingThere are two ways to load Fortran code into Python.
EitherfFortorcompile. The recommended way
is viafFortfor interfacing with existing code,
whilecompileis more suitable for wrapping short snippets of Fortran codefFortYour Fortran code must be inside a module and then compiled as a shared library.On linux:gfortran-fPIC-shared-cfile.f90
gfortran-fPIC-shared-olibfile.sofile.f90On MacOS:gfortran-dynamiclib-cfile.f90
gfortran-dynamiclib-olibfile.dylibfile.f90On Windows:gfortran-shared-cfile.f90
gfortran-shared-olibfile.dllfile.f90If the shared library needs other
shared libraries you may need to set theLD_LIBRARY_PATHenvironment variable, and it is also recommended to run chrpath on the shared
libraries so you can access them from anywhere.Python sideimportgfort2pyasgfSHARED_LIB_NAME=f'./test_mod.{gf.lib_ext()}'# Handle whether on Linux, Mac, or WindowsMOD_FILE_NAME='tester.mod'x=gf.fFort(SHARED_LIB_NAME,MOD_FILE_NAME)NOTE:The mod data is cached to speed up re-reading the data. To control this pass cache_folder tofFort.
A value of False disables caching, a string sets the folder location, while leaving the argument as None defaults to platformdirsuser_cache_dircompileimportgfort2pyasgffstr="""integer function myfunc(x,y)integer :: x,ymyfunc = x+yend function myfunc"""x=gf.compile(string=fstr)The Fortran code can also be in a file in which case:importgfort2pyasgfx=gf.compile(file='my_fortran_file.f90')In either casee the code will be compilied 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 (Its optional whether you need to wrap things inmodule/end module, if you do not then that is done automatically for you).Additional options available forcompile:FC: str Path to gfortran compilierFFLAGS: str Additional Fortran compile options. This defaults to -O2.LDLIBS: str Any additional libraries needed to be linked in (-l)LDFLAGS: str Locations of addtional libraries (-L)ouput: str Location to save intermediate files to. Defaults toNonewhich saves files in a temporary location. Otherwise save to the location specified.NOTE:The interface to compile is currently considered unstable and may change.Interfacexnow contains all variables, parameters and procedures from the module (tab completable), and is independant on how the Fortran code was loaded.Functionsy=x.func_name(a,b,c)Will call the Fortran function with variablesa,b,cand returns the result iny.ywill be  named tuple which contains (result, args). Whereresultis a python object for the return value (0 if a subroutine) and where args is a dict containing all arguments passed to the procedure (both those with intent (in) which will be unchanged and intent(inout/out) which may have changed).Variablesx.some_var=1Sets a module variable to 1, will attempt to coerce it to the Fortran typex.some_varWill return a Python objectOptional arguments that are not present should be passed as a PythonNone.ArraysArrays 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.Derived typesDerived types can be set with a dictx.my_dt={'x':1,'y':'abc'}y=x.my_dty['x']y['y']If the derived type contains another derived type then you can set a dict in a dictx.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 typestype(my_type),dimension(5)::my_dttype(my_type),dimension(5,5)::my_dt2Elements 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 [:]). Allocatable derived types are not yet supported.Derived types that are dummy arguments to a procedure are returned as afDTtype. 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,itemsetc) and any Fortran-derived type components.You can pass afDTas an argument to a procedure.Quad precision variablesQuad precision (REAL128) variables are not natively supported by Python thus we need a different way to handle them. For now that is thepyQuadp librarywhich can be installed from PyPi with:python-mpipinstallpyquadpor from a git checkout:python-mpipinstall.[qaud]For more details see pyQuadp's documentation, but briefly you can create a
quad precision variable from anint,float, orstring. On return you will receive aqfloattype. Thisqfloattype acts like a Python Number, so you can do things like add, multiply, subtract etc this Number with other Numbers (including non-qfloattypes).We currently only support scalar Quad's and scalar complex Quad's. Arrays of
quad precision values is planned but not yet supported. Quad values can also not be returned as a function result (this is a limitation inctypeswhich we have no control over). Thus a quad precision value can only occur in:Module variablesParametersProcedure argumentspyQuadpis currently an optional requirement, you must manually install it, it does not get auto-installed whengfort2pyis installed. If you try to access a quad precision variable withoutpyQuadpyou should get aTypeError.Callback argumentsTo 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 and also be created in Python at runtime (but must be valid Fortran):fstr="""integer function callback(x)integer :: xwrite(*,*) xcallback = 3*xend function callback"""f=gf.compile(fstr)y=x.another_function(f.callback)Testingpython-mpipinstall.[test]pytest-vTo run unit testsThings that workModule variablesScalarsParametersCharactersExplicit size arraysComplex numbers (Scalar and parameters)Getting a pointerGetting the value of a pointerAllocatable arraysDerived typesNested derived typesExplicit Arrays of derived typesAllocatable Arrays of derived typesProcedure pointers inside derived typesDerived types with dimension(:) array components (pointer, allocatable, target)Allocatable strings (partial)Explicit Arrays of stringsAllocatable arrays of stringsClassesAbstract interfacesCommon blocks (partial)EquivalencesNamelistsQuad precision variablesfunction overloadingProceduresBasic calling (no arguments)Argument passing (scalars)Argument passing (strings)Argument passing (explicit arrays)Argument passing (assumed size arrays)Argument passing (assumed shape arrays)Argument passing (allocatable arrays)Argument passing (derived types)Argument intents (in, out, inout and none)Passing characters of fixed size (len=10 or len=* etc)Functions that return a character as their resultAllocatable strings (Only for things that do not get altered inside the procedure)Explicit arrays of stringsAllocatable arrays of stringsPointer argumentsOptional argumentsValue argumentsKeyword argumentsGeneric/Elemental functionsFunctions as an argumentUnary operations (arguments that involve an expression to evaluate like dimension(n+1) or dimension((2*n)+1))Functions returning an explicit array as their resultAccessing common block elementsThere's no direct way to access the common block elements, but if you declare the common block as a module variable you may access the elements by their name:modulemy_modimplicit noneinteger::a,b,ccommon/comm1/a,b,cElements in the common block can thus be accessed as:x.ax.bx.cAccessing module file dataFor those wanting to explore the module file format, there is a routinemod_infoavailable from the top-levelgfort2pymodule:module=gf.mod_info('file.mod')That will parse the mod file and convert it into an intermediate format insidemodule.Variables or procedures can be looked up via the item interface (I also recommend using pprint for easier viewing):from pprint import pprint

pprint(module['a_variable'])Accessing the list of all available components can be had viamodule.keys().You can also do:module=gf.mod_info('file.mod',json=True)module['a_variable']Then when you access each component the return value will be JSON-formatted. Note you can currently only access each component as JSON not the whole module file as JSON at the moment.ContributingBug 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.skipif 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.Seehow to write a test casefor details on how to write 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.DebuggingFor those wanting to dig further intogfort2py.Assuming that you loaded things as:x=gf.fFort(SO,MOD)You can find out the available Fortran variables/procedures module information with:var=x._module['variable_name']VariablesFor variables you can create afVar(the object that handles converting too and from Python to Fortran) with:fvar=gf.fVar.fVar(var,x._module)Note that at this point thefvarhas no idea where to look for the variable. If you want to access its value in a module thenfvar.in_dll(x._lib)print(fvar.value)and you can then set the value (after callingin_dll) with:fvar.value=valueProceduresFor a procedure you can do:proc=x._module['procedure_name']and its Fortran object is:fproc=gf.fProc.fProc(x._lib,proc,x._module)Calling the procedure is then:fproc(*args,**kwargs)To access the arguments of a procedure then:args=[x._module[i.ref]foriinproc.args().symbol]The return value of a function is accessed via:return_arg=x._module[proc.return_arg()]Derived typesTo access a derived type (the type definition not an instance of the type)dt_type=x._module['Derived_name']Note the capitalization, derived types start with a capital.Similar to a variable, to access an instance of a derived type variable its:dt_var=x._module['dt_variable_name']dt_fvar=gf.fVar.fVar(dt_var,x._module)