#+TITLE: Maintaining Fortran in Python in Perpetuity #+SUBTITLE: ~f2py~ and NumPy #+AUTHOR: Rohit Goswami ~.and.~ Melissa Mendonca ~.and.~ Ralf Gommers ~.and.~ Thirumalai Shaktivel ~.and.~ Pearu Peterson # I need the footnotes to be inlined #+STARTUP: fninline #+EXCLUDE_TAGS: noexport #+BEGIN_SRC emacs-lisp :exports none :eval always (require 'ox-extra) (ox-extras-activate '(ignore-headlines)) (eval unpackaged/org-export-html-with-useful-ids-mode) ;; Stop using citeproc-org by default (setq org-export-before-parsing-hook '(org-attach-expand-links)) #+END_SRC #+RESULTS: * Configuration :ignoreheading:ignore: :PROPERTIES: :VISIBILITY: folded :END: # Kanged from https://gitlab.com/oer/oer-reveal/blob/master/org/config.org # Also https://gitlab.com/oer/emacs-reveal-howto/-/blob/master/howto.org ** General Options :ignoreheading:ignore: # No Table of contents, no section numbers #+OPTIONS: toc:nil num:nil # Enable smart quotes #+OPTIONS: ':t ** RevealJS Options :ignoreheading:ignore: # Enable: browser history, fragment IDs in URLs, mouse wheel, links between presentations #+OPTIONS: reveal_history:t reveal_fragmentinurl:t reveal_slide_number:h/v #+OPTIONS: reveal_mousewheel:t reveal_inter_presentation_links:t # Disable separate PDF pages for each fragment. Just use one per slide. #+OPTIONS: reveal_pdfseparatefragments:nil # Display notes on separate page for PDF export. #+REVEAL_EXPORT_NOTES_TO_PDF: separate-page # Transition styles: none/fade/slide/convex/concave/zoom/cube #+REVEAL_TRANS: fade # Set a base theme, then override #+REVEAL_THEME: robot-lung #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/extras/rlExtras.css #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/extras/oerFragments.css #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/extras/noImgBoxes.css #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/extras/betterCite.css #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/extras/moreCode.css #+REVEAL_MARGIN: 0.2 #+REVEAL_PREAMBLE:
#+REVEAL_PLUGINS: (notes search zoom) # The following variables are non-standard. # Do not display TOC-progress on title slide. #+REVEAL_TITLE_SLIDE_STATE: no-toc-progress # Do not display TOC-progress on TOC slide. #+REVEAL_TOC_SLIDE_STATE: no-toc-progress # Do not include TOC slide in TOC-progress. #+REVEAL_TOC_SLIDE_CLASS: no-toc-progress # Use different heading for TOC. #+REVEAL_TOC_SLIDE_TITLE: Agenda ** External Resources :ignoreheading:ignore: # Note that doom-emacs sets this variable # https://github.com/hlissner/doom-emacs/blob/develop/modules/lang/org/contrib/present.el #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/rjs/plugin/accessibility/helper.css #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/rjs/plugin/toc-progress/toc-progress.css #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/rjs/dist/theme/toc-style.css #+REVEAL_EXTRA_CSS: /Users/rgoswami/.config/doom/reveal/rjs/dist/theme/fonts/source-sans-pro/source-sans-pro.css # Allow to selectively hide links. # #+REVEAL_EXTRA_SCRIPTS: ("/Users/rgoswami/.config/doom/reveal/rjs/dist/theme/hidelinks.js") #+REVEAL_EXTRA_SCRIPTS: ("/Users/rgoswami/.config/doom/reveal/rjs/dist/theme/hidelinks.js" "/Users/rgoswami/.config/doom/reveal/sfeir-school-theme/dist/js/sfeir-theme.js") # The following creates an empty footer, for which the css style defines # a height that agrees with the TOC-progress footer’s height. # In this way, the footer’s height is taken into account by reveal.js’s # size calculations. #+REVEAL_SLIDE_FOOTER:%s
@@ @@latex:\\verb|%s|@@" (org-html-encode-plain-text Modern Documentation across languages) Modern Documentation across languages))
#+MACRO: redcode (eval (format "@@html:%s
@@ @@latex:\\rverb|%s|@@" (org-html-encode-plain-text Modern Documentation across languages) Modern Documentation across languages))
#+MACRO: greencode (eval (format "@@html:%s
@@ @@latex:\\gverb|%s|@@" (org-html-encode-plain-text Modern Documentation across languages) Modern Documentation across languages))
#+MACRO: bluecode (eval (format "@@html:%s
@@ @@latex:\\bverb|%s|@@" (org-html-encode-plain-text Modern Documentation across languages) Modern Documentation across languages))
** References :ignoreheading:ignore:
bibliographystyle:unsrt
#+LATEX_HEADER: \addbibresource{./refs.bib}
** LaTeX Options :ignoreheading:ignore:
# Setup for PDF generation via LaTeX export.
#+LATEX_CLASS_OPTIONS: [a4paper]
#+LATEX_HEADER: \usepackage[backend=biber,style=alphabetic]{biblatex}
#+LATEX_HEADER: \newenvironment{notes}{\par\footnotesize}{\par}
#+LATEX_HEADER: \newenvironment{NOTES}{\par\footnotesize}{\par}
#+LATEX_HEADER: \newenvironment{leftcol}{\begin{minipage}{.49\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{rightcol}{\begin{minipage}{.49\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{leftcol30}{\begin{minipage}{.29\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{leftcol40}{\begin{minipage}{.39\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{leftcol60}{\begin{minipage}{.59\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{leftcol70}{\begin{minipage}{.69\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{rightcol30}{\begin{minipage}{.29\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{rightcol40}{\begin{minipage}{.39\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{rightcol60}{\begin{minipage}{.59\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \newenvironment{rightcol70}{\begin{minipage}{.69\textwidth}}{\end{minipage}}
#+LATEX_HEADER: \usepackage{newunicodechar}
#+LATEX_HEADER: \newunicodechar{≈}{$\approx$}
#+LATEX_HEADER: \newunicodechar{⋮}{\vdots}
#+LATEX_HEADER: \newunicodechar{ }{~}
#+LATEX_HEADER: \usepackage{xcolor}
#+LATEX_HEADER: \definecolor{darkred}{rgb}{0.3, 0.0, 0.0}
#+LATEX_HEADER: \definecolor{darkgreen}{rgb}{0.0, 0.3, 0.1}
#+LATEX_HEADER: \definecolor{darkblue}{rgb}{0.0, 0.1, 0.3}
#+LATEX_HEADER: \definecolor{darkorange}{rgb}{1.0, 0.55, 0.0}
#+LATEX_HEADER: \definecolor{sienna}{rgb}{0.53, 0.18, 0.09}
#+LATEX_HEADER: \hypersetup{colorlinks,linkcolor=darkblue,citecolor=darkblue,urlcolor=darkgreen}
#+LATEX_HEADER: \usepackage{newverbs}
#+LATEX_HEADER: \newverbcommand{\rverb}{\color{darkred}}{}
#+LATEX_HEADER: \newverbcommand{\gverb}{\color{darkgreen}}{}
#+LATEX_HEADER: \newverbcommand{\bverb}{\color{darkblue}}{}
* Start Here :ignoreheading:ignore:
* Brief Introduction
** Hello!
- Find me here: https://rgoswami.me
- Who?
+ *Rohit Goswami* MInstP
- Doctoral Researcher, Science Institute, University of Iceland
- Software Engineer, Quansight Labs, Austin TX
#+begin_leftcol
[[file:logos/physUoI.png]]
#+ATTR_HTML: :width 50% :height 50%
file:logos/rannisLogo.png
#+ATTR_HTML: :width 40% :height 40%
[[file:logos/ccLogo.png]]
#+end_leftcol
#+begin_rightcol
#+ATTR_HTML: :width 60% :height 40%
[[file:logos/scipyLogo22.png]]
#+ATTR_HTML: :width 50% :height 40%
[[file:logos/quansightlabs.jpeg]]
#+end_rightcol
** Logistics
#+ATTR_REVEAL: :frag appear
- All contents are [[https://github.com/HaoZeke/haozeke.github.io][hosted on GitHub]]
+ Slides are in ~presentations/scipy22/F2PYmaint~
# #+ATTR_REVEAL: :frag appear
# - Questions are welcome *at the end*, or interrupt me
* Programming Languages
** Motivation
#+begin_quote
“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”cite:lyonUsingAnsFortran1980
#+end_quote
--> Through the magic of *automated coding* and *standards*
** Language Standards
#+begin_quote
“The standard is the contract between the compiler writer and the application developer.”cite:clermanModernFortranStyle2012
#+end_quote
#+BEGIN_SRC ditaa :file images/hello-program.png :cmdline -r -s 2.5 :cache yes
+------+ +----------+ assembly +-----------+
| Code | --> | Compiler | ----------> | Assembler | ---+
+------+ +----------+ +-----------+ |
relocatable machine code |
+----------------------------<-------------------------+
|
|
| +--------+ executable +--------+ +--------+
+---> | Linker | -----------> | Loader | --> | Memory |
+--------+ +--------+ +--------+
#+END_SRC
#+RESULTS[f1fc83b64fe81184a3a817828e6dd0aec3714f25]:
[[file:images/hello-program.png]]
** Changing Standards
#+begin_leftcol
#+begin_src fortran
character(10) BLAH*8
character*8 :: BLAH_ONE(10)
character(8) :: BLAH_ONE(10)
#+end_src
#+begin_src python
#!/usr/bin/env python
print("Hello World")
print "Hello World"
#+end_src
#+end_leftcol
#+begin_rightcol
#+DOWNLOADED: screenshot @ 2021-09-08 23:12:16
[[file:images/Why_Care_About_New_Standards/2021-09-08_23-12-16_screenshot.png]]
#+end_rightcol
# ** F77 ∉ F90 always
# #+ATTR_HTML: :width 70% :height 70%
# [[file:images/Why_Care_About_New_Standards/2021-09-08_23-14-26_screenshot.png]]
# #+ATTR_HTML: :width 70% :height 70%
# [[file:images/Why_Care_About_New_Standards/2021-09-08_23-14-38_screenshot.png]]
* 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 [[https://www.fortran90.org/src/best-practices.html#python-interface][fortran90.org]]:
#+begin_src ditaa :file images/hello-stdfcpy.png :cmdline -r -s 1.5 :cache yes
+--------------+ iso_c_binding +------------+
| Fortran Code | --------------> | Call in C |
+--------------+ compiler type +------------+
checking |
v
+--------+ +--------+
| Python | | Cython |
| Code |<--------------| cffi |
+--------+ +--------+
#+end_src
#+RESULTS[c649e6871e7d2b61cff7d210af3db78f85b0fcc5]:
[[file:images/hello-stdfcpy.png]]
* F2PY
** History
- Developed by Pearu Peterson cite:petersonF2PYToolConnecting2009
+ 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
+ [[https://numpy.org/][NumPy]] cite:waltNumPyArrayStructure2011, [[https://scipy.org/][SciPy]] cite:virtanenSciPyFundamentalAlgorithms2020
+ [[https://msspec.cnrs.fr/][MsSpec]] cite:sebilleauMsSpec1MultipleScattering2011 :)
** Design
#+BEGIN_SRC ditaa :file images/hello-f2py.png :cmdline -r -s 2.5 :cache yes
+------+ +--------------+ uses C syntax in pyf
| Code | --> | crackfortran | ---------->----------+
+------+ +--------------+ |
match rules, generate wrappers, build library |
+----------------------------<---------------------+
| +---------+ callbacks +--------+
| | CPython | <---------| Python |
+---> | Library | --------->| Code |
+---------+ +--------+
#+END_SRC
#+RESULTS[7f80fadd5cfd09443054d3693a2d688d9a0b639d]:
[[file:images/hello-f2py.png]]
- A *best effort* wrapper
+ Specifications via ~.pyf~ or inline comments
+ *Not* a compiler
- Can rewrite code :)
* Explorations in F77
** Fibonacci
#+begin_src fortran
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
#+end_src
#+begin_src bash
f2py -m fib -c fib1.f
python -c "import fib; import numpy as np;
a=np.zeros(7); fib.fib(a); print(a); exit();"
#+end_src
** Up the magician's sleeve
**** Generated files
#+begin_src bash
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
#+end_src
*** Complexity
#+begin_src bash
wc -l fortranobject.c fortranobject.h fibmodule.c
1107 fortranobject.c
132 fortranobject.h
372 fibmodule.c
1611 total
#+end_src
**** NumPy Distutils
#+begin_src python
from numpy.distutils.core import Extension, setup
fibby = Extension(name = 'fib',
sources = ['fib1.f'])
if __name__ == "__main__":
setup(name = 'fib', ext_modules = [ fibby ])
#+end_src
Which can then be built simply with:
#+begin_src bash
python setup.py build
ag -g .so
# build/lib.macosx-10.9-x86_64-3.9/fib.cpython-39-darwin.so
#+end_src
- Not fun for non ~python~ projects
* (Some) Planned Features
** Meson and ~f2py~
#+begin_leftcol
#+begin_src meson
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()
#+end_src
#+end_leftcol
#+begin_rightcol
#+begin_src meson
inc_np = include_directories(incnp)
py3.extension_module('fib1',
'fib1.f',
'fib1module.c',
'fortranobject.c',
include_directories: inc_np,
dependencies : py3_dep,
install : true)
#+end_src
#+end_rightcol
** 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
#+ATTR_HTML: :width 30% :height 30%
[[file:images/namami.jpeg]]
** Derived Types
- Soon to be a NEP : https://github.com/HaoZeke/f2py_derived_nep
- Tested by ~cmocka~
- e.g. ~bind(c)~ types [[https://github.com/HaoZeke/f2py_derived_nep/blob/main/examples/bindc/pyclass/pyclassderived.c][map to classes]]
#+begin_src bash
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)
#+end_src
** Moving beyond ~bind(c)~
- Generate Fortran wrappers to manipulate types
+ All allocations handled in Fortran
- Python only calls wrappers to aforementioned types
** Logical restructure
#+ATTR_REVEAL: :frag appear
- Current flat hierarchy --> intimidating
+ Don't need much Fortran for front-end work
#+ATTR_REVEAL: :frag appear
- codegen :: Creates wrappers
#+ATTR_REVEAL: :frag appear
- csrcs :: ~fortranobject.{c,h}~ for builds
#+ATTR_REVEAL: :frag appear
- frontend :: Parser, type inference
#+ATTR_REVEAL: :frag appear
- stds :: Includes *f77*, *f90*, and *pyf* specs
#+ATTR_REVEAL: :frag appear
- utils :: For testing and more
#+ATTR_REVEAL: :frag appear
- tests :: For sanity
- WIP : https://github.com/numpy/numpy/pull/20481
* Maintenance
** Concepts
#+ATTR_REVEAL: :frag appear
#+ATTR_HTML: :width 50% :height 50%
file:images/blackSand.jpg
#+ATTR_REVEAL: :frag appear
- Fortran 202X standard :: 668 pages
#+ATTR_REVEAL: :frag appear
- Python-C API description :: 320 pages
#+ATTR_REVEAL: :frag appear
- NumPy-C API description :: 103 pages
#+ATTR_REVEAL: :frag appear
- C++ 2020 (N4849) standard :: 1815 pages
#+ATTR_REVEAL: :frag appear
- C11 (N1570) standard :: 701 pages
** Test isolation
#+ATTR_REVEAL: :frag appear
- Write a test for each bug
+ Typically already in the issue!
#+begin_src python
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)
#+end_src
** Front-end
- Covers *(parser, code-gen)*
- ~pdb~ and editable installs are good choices
+ Tough otherwise (~global~ variables)
#+begin_src python
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
#+end_src
#+ATTR_REVEAL: :frag appear
- e.g. https://github.com/numpy/numpy/pull/21256/files --> Negative bounds
** Back-end
- Covers *(C wrappers, code-gen)*
#+ATTR_REVEAL: :frag appear
- ~gdb~ works well
+ Sometimes with CPython extensions (see the *sprint*)
#+ATTR_REVEAL: :frag appear
- Manipulate the generated ~C~ code
+ Then move back to code-gen in Python
#+ATTR_REVEAL: :frag appear
- e.g. https://github.com/numpy/numpy/pull/21807/files --> Respect ~value~
** Supporting ~value~ attributes
- Fortran 2003 supports call-by-value
+ ~f2py~ wrappers pass by reference
- Details [[https://github.com/numpy/numpy/issues/21665][here]]
+ Very well reported issue
** Verify Bug
#+begin_leftcol
#+begin_src bash
# 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
#+end_src
#+end_leftcol
#+begin_rightcol
#+begin_src C
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!
#+end_src
#+end_rightcol
** Fixup ~C~ wrapper
#+begin_leftcol
#+begin_src bash
# Make wrappers
meson compile -C bbdir
cd bbdir
python -c "import foo;
print(
foo.fortfuncs.square(3)
);"
9
#+end_src
#+end_leftcol
#+begin_rightcol
#+begin_src C
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);
#+end_src
#+end_rightcol
** Modify Parser
- ~value~ should be recognized by ~crackfortran~
+ Add a ~breakpoint~ and stare
#+begin_src python
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'}}
#+end_src
- So it is already recognized..
** Augment aux functions
- Handle the attribute within ~aux.py~
+ Also conditionally modify signatures + call conventions
#+begin_src python
def isattr_value(var):
return 'value' in var.get('attrspec', [])
def getcallprotoargument(rout, cb_map={}):
...
if not isattr_value(var):
ctype = ctype + '*'
#+end_src
** Generate wrappers
- This is similar to the handling of the existing ~intent(c)~
+ In ~rules.py~
#+begin_src python
'callfortran': {l_or(isintent_c, isattr_value): '#varname#,',
l_not(l_or(isintent_c, isattr_value)): 'varname#,'},
#+end_src
** Final tasks
- Add a test
- Solicit reviews
- Ask about a release note
- *Profit!*
* Conclusions
** 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~
** Why does Fortran stay in Python anyway?
:PROPERTIES:
:reveal_background: #c9eae7
:END:
#+begin_quote
To write *efficient* wrappers without being a language lawyer
#+end_quote
** How does Fortran stay in Python anyway?
:PROPERTIES:
:reveal_background: #c9eae7
:END:
#+begin_quote
With *all your help* (Issues, users, developers, non-code contribs)
#+end_quote
* The End
** Acknowledgments
#+DOWNLOADED: screenshot @ 2021-09-09 00:22:06
[[file:images/Acknowledgements/2021-09-09_00-22-06_screenshot.png]]
- [[https://notendur.hi.is//~hj/indexE.html][Prof. Hannes Jónsson]] as my supervisor, [[https://english.hi.is/staff/birgirhr][Prof. Birgir Hrafnkelsson]] as my co-supervisor
- [[https://ondrejcertik.com/][Dr. Ondřej Čertík]] at GSI Tech.
- [[https://quansight.com/labs][Quansight Labs]] ([[https://rgommers.github.io/][Dr. Ralf Gommers]], [[https://melissawm.github.io/about-me/][Dr. Melissa Weber Mendonça]] and [[https://github.com/pearu][Dr. Pearu Peterson]])
- NumPy Team (Matti, Ross, Sebastian, Chuck, Inessa, etc.)
- Family, pets, Groupmembers, *audience*
# ** Bibliography
# :PROPERTIES:
# :CUSTOM_ID: bibliography
# :END:
# printbibliography:refs.bib
** Thanks!
:PROPERTIES:
:reveal_background: #c9eae7
:END:
# Local Variables:
# indent-tabs-mode: nil
# org-src-preserve-indentation: t
# End: