Slide 1

Slide 1 text

MAINTAINING FORTRAN IN PYTHON IN PERPETUITY ROHIT GOSWAMI .and. MELISSA MENDONCA .and. RALF GOMMERS .and. THIRUMALAI SHAKTIVEL .and. PEARU PETERSON

Slide 2

Slide 2 text

BRIEF INTRODUCTION 3

Slide 3

Slide 3 text

HELLO! Find me here: Who? Rohit Goswami MInstP Doctoral Researcher, University of Iceland, Faculty of Physical Sciences Software Engineer, Quansight Labs, Austin TX https://rgoswami.me 4

Slide 4

Slide 4 text

LOGISTICS All contents are Slides are in presentations/scipy22/F2PYmaint hosted on GitHub Questions are welcome at the end, or interrupt me 5

Slide 5

Slide 5 text

PROGRAMMING LANGUAGES 7

Slide 6

Slide 6 text

MOTIVATION –> Through the magic of automated coding and standards “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 8

Slide 7

Slide 7 text

LANGUAGE STANDARDS “The standard is the contract between the compiler writer and the application developer.”clermanModernFortranStyle2012 9

Slide 8

Slide 8 text

CHANGING STANDARDS character(10) BLAH*8 character*8 :: BLAH_ONE(10) character(8) :: BLAH_ONE(10) #!/usr/bin/env python print("Hello World") print "Hello World" 10

Slide 9

Slide 9 text

FORTRAN, C, PYTHON F2003 Introduced the ISO_C_BINDING F2008 C_PTR for void * and more F2018 Brought interop for exotic Fortran features which via C descriptors Interop Described in great detail on : fortran90.org 12

Slide 10

Slide 10 text

F2PY 14

Slide 11

Slide 11 text

HISTORY Developed by Pearu Peterson July 9, 1999 f2py.py –> Fortran to Python Interface Generator (FPIG) January 22, 2000 f2py2e –> Fortran to Python Interface Generator, 2nd edition. July 19, 2007 numpy.f2py –> f2py2e moved to NumPy project. This is current stable code of f2py. Used extensively for F77 , :) petersonF2PYToolConnecting2009 NumPy waltNumPyArrayStructure2011 SciPy virtanenSciPyFundamentalAlgorithms2020 MsSpec sebilleauMsSpec1MultipleScattering2011 15

Slide 12

Slide 12 text

DESIGN A best effort wrapper Specifications via .pyf or inline comments Not a compiler Can rewrite code :) 16

Slide 13

Slide 13 text

EXPLORATIONS IN F77 18

Slide 14

Slide 14 text

FIBONACCI C 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();" 19

Slide 15

Slide 15 text

UP THE MAGICIAN’S SLEEVE Generated files 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 20

Slide 16

Slide 16 text

COMPLEXITY NumPy Distutils Which can then be built simply with: Not fun for non python projects 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 ]) python setup.py build ag -g .so # build/lib.macosx-10.9-x86_64-3.9/fib.cpython-39-darwin.so 21

Slide 17

Slide 17 text

(SOME) PLANNED FEATURES 23

Slide 18

Slide 18 text

MESON AND f2py 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) 24

Slide 19

Slide 19 text

COMMAND LINE INTERFACE The F2PY UX is rather distinctive -c flag acts as a compiler / build-tool Otherwise works to generate C wrappers and Signature files Rewrite ongoing [GSoC] Supports Namami Shankar argparse based design 25

Slide 20

Slide 20 text

DERIVED TYPES Soon to be a NEP : Tested by cmocka e.g. bind(c) types https://github.com/HaoZeke/f2py_derived_nep map to classes meson 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) 26

Slide 21

Slide 21 text

MOVING BEYOND bind(c) Generate Fortran wrappers to manipulate types All allocations handled in Fortran Python only calls wrappers to aforementioned types 27

Slide 22

Slide 22 text

LOGICAL RESTRUCTURE Current flat hierarchy –> intimidating Don’t need much Fortran for front-end work codegen Creates wrappers csrcs fortranobject.{c,h} for builds frontend Parser, type inference stds Includes f77, f90, and pyf specs utils For testing and more tests For sanity (no term) WIP : https://github.com/numpy/numpy/pull/20481 28

Slide 23

Slide 23 text

MAINTENANCE 30

Slide 24

Slide 24 text

CONCEPTS Fortran 202X standard 668 pages Python-C API description 320 pages NumPy-C API description 103 pages C++ 2020 (N4849) standard 1815 pages C11 (N1570) standard 701 pages 31

Slide 25

Slide 25 text

TEST ISOLATION Write a test for each bug Typically already in the issue! 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) 32

Slide 26

Slide 26 text

FRONT-END Covers (parser, code-gen) pdb and editable installs are good choices Tough otherwise (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 e.g. –> Negative bounds https://github.com/numpy/numpy/pull/21256/files 33

Slide 27

Slide 27 text

BACK-END Covers (C wrappers, code-gen) gdb works well Sometimes with CPython extensions (see the sprint) Manipulate the generated C code Then move back to code-gen in Python e.g. –> Respect value https://github.com/numpy/numpy/pull/21807/files 34

Slide 28

Slide 28 text

SUPPORTING value ATTRIBUTES Fortran 2003 supports call-by-value f2py wrappers pass by reference Details Very well reported issue here 35

Slide 29

Slide 29 text

VERIFY BUG # 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_fortfun const PyObject *capi_self, PyObject *capi_args, PyObject *capi_keywds, void (*f2py_func)(int*,int*)) { (*f2py_func)(&x,&y); // Passed by refe 36

Slide 30

Slide 30 text

FIXUP 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_fortfun const PyObject *capi_self, PyObject *capi_args, PyObject *capi_keywds, void (*f2py_func)(int,int*)) { (*f2py_func)(x,&y); 37

Slide 31

Slide 31 text

MODIFY PARSER value should be recognized by crackfortran Add a breakpoint and stare So it is already recognized.. name_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'}} 38

Slide 32

Slide 32 text

AUGMENT AUX FUNCTIONS Handle the attribute within aux.py Also conditionally modify signatures + call conventions def isattr_value(var): return 'value' in var.get('attrspec', []) def getcallprotoargument(rout, cb_map={}): ... if not isattr_value(var): ctype = ctype + '*' 39

Slide 33

Slide 33 text

GENERATE WRAPPERS This is similar to the handling of the existing intent(c) In rules.py 'callfortran': {l_or(isintent_c, isattr_value): '#varname#,', l_not(l_or(isintent_c, isattr_value)): 'varname#,'}, 40

Slide 34

Slide 34 text

FINAL TASKS Add a test Solicit reviews Ask about a release note Profit! 41

Slide 35

Slide 35 text

CONCLUSIONS 43

Slide 36

Slide 36 text

ROAD-MAP Updating the test suite, using cmocka Rewriting the C wrappers for newer standards Build tool support [Namami Shankar] np.distutils is going the way of the dodo Implementing newer standards (90, 95, 2003, 2008, 2018, 2020Y) Automating guarantees Documentation and more interop with NumPy-C crackfortran works via dictionaries and strings.. Perhaps a more abstract semantic representation… Or concepts from G3 F2PY [Pearu] Handle parallelism (proposed) do concurrent and ufuncs 44

Slide 37

Slide 37 text

WHY DOES FORTRAN STAY IN PYTHON ANYWAY? To write efficient wrappers without being a language lawyer 45

Slide 38

Slide 38 text

HOW DOES FORTRAN STAY IN PYTHON ANYWAY? With all your help (Issues, users, developers, non-code contribs) 46

Slide 39

Slide 39 text

THE END 48

Slide 40

Slide 40 text

ACKNOWLEDGMENTS as my supervisor, as my co-supervisor at GSI Tech. ( , and ) NumPy Team (Matti, Ross, Sebastian, Chuck, Inessa, etc.) Family, pets, Groupmembers, audience Prof. Hannes Jónsson Prof. Birgir Hrafnkelsson Dr. Ondřej Čertík Quansight Labs Dr. Ralf Gommers Dr. Melissa Weber Mendonça Dr. Pearu Peterson 49

Slide 41

Slide 41 text

THANKS! 50