.and.
Melissa Mendonca .and.
Ralf Gommers .and.
Thirumalai Shaktivel .and.
Pearu PetersonCreated: 2022-07-15 Fri 12:39
presentations/scipy22/F2PYmaint
“If a program or package (the words are used interchangeably) is to have a long life and to be of wide application in its field, it is essential for it to be easily moved from one machine to another.
It used to be common to dismiss such movement with the statement, ‘There is no such thing as a machine-independent program.’
Nonetheless, a great many packages do now move from one machine to another”lyonUsingAnsFortran1980
–> Through the magic of automated coding and standards
“The standard is the contract between the compiler writer and the application developer.”clermanModernFortranStyle2012
character(10) BLAH*8 character*8 :: BLAH_ONE(10) character(8) :: BLAH_ONE(10)
#!/usr/bin/env python print("Hello World") print "Hello World"
ISO_C_BINDING
C_PTR
for void *
and moreC descriptors
f2py.py
–> Fortran to Python Interface Generator (FPIG)f2py2e
–> Fortran to Python Interface Generator, 2nd edition.numpy.f2py
–> f2py2e moved to NumPy project. This is current stable code of f2py.
.pyf
or inline commentsC FILE: FIB1.F SUBROUTINE FIB(A,N) C CALCULATE FIRST N FIBONACCI NUMBERS INTEGER N REAL*8 A(N) DO I=1,N IF (I.EQ.1) THEN A(I) = 0.0D0 ELSEIF (I.EQ.2) THEN A(I) = 1.0D0 ELSE A(I) = A(I-1) + A(I-2) ENDIF ENDDO END C END FILE FIB1.F
f2py -m fib -c fib1.f python -c "import fib; import numpy as np; a=np.zeros(7); fib.fib(a); print(a); exit();"
mkdir blah f2py -m fib -c fib1.f --build-dir blah tree blah blah ├── blah │ └── src.macosx-10.9-x86_64-3.9 │ ├── blah │ │ └── src.macosx-10.9-x86_64-3.9 │ │ ├── fortranobject.o │ │ └── fortranobject.o.d │ ├── fibmodule.o │ └── fibmodule.o.d ├── fib1.o └── src.macosx-10.9-x86_64-3.9 ├── blah │ └── src.macosx-10.9-x86_64-3.9 │ ├── fortranobject.c │ └── fortranobject.h └── fibmodule.c 7 directories, 8 files
wc -l fortranobject.c fortranobject.h fibmodule.c 1107 fortranobject.c 132 fortranobject.h 372 fibmodule.c 1611 total
from numpy.distutils.core import Extension, setup fibby = Extension(name = 'fib', sources = ['fib1.f']) if __name__ == "__main__": setup(name = 'fib', ext_modules = [ fibby ])
Which can then be built simply with:
python setup.py build ag -g .so # build/lib.macosx-10.9-x86_64-3.9/fib.cpython-39-darwin.so
python
projectsf2py
project('test_builds', 'c', version : '0.1') add_languages('fortran') py_mod = import('python') py3 = py_mod.find_installation() py3_dep = py3.dependency() incnp = run_command(py3, ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'], check : true ).stdout().strip()
inc_np = include_directories(incnp) py3.extension_module('fib1', 'fib1.f', 'fib1module.c', 'fortranobject.c', include_directories: inc_np, dependencies : py3_dep, install : true)
-c
flag acts as a compiler / build-toolC wrappers
and Signature filesargparse
based design
cmocka
bind(c)
types map to classesmeson setup bbdir meson compile -C bbdir python -c "import bbdir.pycart as pycart; aak = pycart.pycart(1,10,2); print(aak); aak.unitstep(); print(aak)" pycart(x: 1.000000, y: 10.000000, z: 2.000000) Modifying derived type pycart(x: 2.000000, y: 11.000000, z: 3.000000)
bind(c)
fortranobject.{c,h}
for builds
class TestNegativeBounds(util.F2PyTest): # Check that negative bounds work correctly sources = [util.getpath("tests", "src", "negative_bounds", "issue_20853.f90")] @pytest.mark.slow def test_negbound(self): xvec = np.arange(12) xlow = -6 xhigh = 4 # Calculate the upper bound, # Keeping the 1 index in mind def ubound(xl, xh): return xh - xl + 1 rval = self.module.foo(is_=xlow, ie_=xhigh, arr=xvec[:ubound(xlow, xhigh)]) expval = np.arange(11, dtype = np.float32) assert np.allclose(rval, expval)
pdb
and editable installs are good choices
global
variables)micromamba create -f environment.yml micromamba activate numpy-dev pip install -e . # EDITABLE MODE # Add a breakpoint anywhere breakpoint() # Profit f2py -m blah buggy.f90
gdb
works well
C
code
value
value
attributesf2py
wrappers pass by reference# Make wrappers f2py -m foo blah.f90 meson bbdir meson compile -C bbdir cd bbdir python -c "import foo; print( foo.fortfuncs.square(3) );" 170676880
static PyObject *f2py_rout_foo_fortfuncs_square( const PyObject *capi_self, PyObject *capi_args, PyObject *capi_keywds, void (*f2py_func)(int*,int*)) { (*f2py_func)(&x,&y); // Passed by reference!
C
wrapper# Make wrappers meson compile -C bbdir cd bbdir python -c "import foo; print( foo.fortfuncs.square(3) );" 9
static PyObject *f2py_rout_foo_fortfuncs_square( const PyObject *capi_self, PyObject *capi_args, PyObject *capi_keywds, void (*f2py_func)(int,int*)) { (*f2py_func)(x,&y);
value
should be recognized by crackfortran
breakpoint
and starename_match = re.compile(r'[A-Za-z][\w$]*').match breakpoint() for v in list(vars.keys()): >>> c # till fortfuncs is processed >>> pp vars {'x': {'attrspec': ['intent(in)', 'value'], 'typespec': 'integer'}, 'y': {'attrspec': ['intent(out)'], 'typespec': 'integer'}}
aux.py
def isattr_value(var): return 'value' in var.get('attrspec', []) def getcallprotoargument(rout, cb_map={}): ... if not isattr_value(var): ctype = ctype + '*'
intent(c)
rules.py
'callfortran': {l_or(isintent_c, isattr_value): '#varname#,', l_not(l_or(isintent_c, isattr_value)): '&#varname#,'},
cmocka
np.distutils
is going the way of the dodocrackfortran
works via dictionaries and strings..
do concurrent
and ufuncs
To write efficient wrappers without being a language lawyer
With all your help (Issues, users, developers, non-code contribs)