1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
|
.. _f2py-examples:
F2PY examples
=============
Below are some examples of F2PY usage. This list is not comprehensive, but can
be used as a starting point when wrapping your own code.
.. note::
The best place to look for examples is the `NumPy issue tracker`_, or the
test cases for ``f2py``. Some more use cases are in
:ref:`f2py-boilerplating`.
F2PY walkthrough: a basic extension module
------------------------------------------
Creating source for a basic extension module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Consider the following subroutine, contained in a file named :file:`add.f`
.. literalinclude:: ./code/add.f
:language: fortran
This routine simply adds the elements in two contiguous arrays and places the
result in a third. The memory for all three arrays must be provided by the
calling routine. A very basic interface to this routine can be automatically
generated by f2py::
python -m numpy.f2py -m add add.f
This command will produce an extension module named :file:`addmodule.c` in the
current directory. This extension module can now be compiled and used from
Python just like any other extension module.
Creating a compiled extension module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can also get f2py to both compile :file:`add.f` along with the produced
extension module leaving only a shared-library extension file that can
be imported from Python::
python -m numpy.f2py -c -m add add.f
This command produces a Python extension module compatible with your platform.
This module may then be imported from Python. It will contain a method for each
subroutine in ``add``. The docstring of each method contains information about
how the module method may be called:
.. code-block:: python
>>> import add
>>> print(add.zadd.__doc__)
zadd(a,b,c,n)
Wrapper for ``zadd``.
Parameters
----------
a : input rank-1 array('D') with bounds (*)
b : input rank-1 array('D') with bounds (*)
c : input rank-1 array('D') with bounds (*)
n : input int
Improving the basic interface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default interface is a very literal translation of the Fortran code into
Python. The Fortran array arguments are converted to NumPy arrays and the
integer argument should be mapped to a ``C`` integer. The interface will attempt
to convert all arguments to their required types (and shapes) and issue an error
if unsuccessful. However, because ``f2py`` knows nothing about the semantics of
the arguments (such that ``C`` is an output and ``n`` should really match the
array sizes), it is possible to abuse this function in ways that can cause
Python to crash. For example:
.. code-block:: python
>>> add.zadd([1, 2, 3], [1, 2], [3, 4], 1000)
will cause a program crash on most systems. Under the hood, the lists are being
converted to arrays but then the underlying ``add`` function is told to cycle
way beyond the borders of the allocated memory.
In order to improve the interface, ``f2py`` supports directives. This is
accomplished by constructing a signature file. It is usually best to start from
the interfaces that ``f2py`` produces in that file, which correspond to the
default behavior. To get ``f2py`` to generate the interface file use the ``-h``
option::
python -m numpy.f2py -h add.pyf -m add add.f
This command creates the ``add.pyf`` file in the current directory. The section
of this file corresponding to ``zadd`` is:
.. literalinclude:: ./code/add.pyf
:language: fortran
By placing intent directives and checking code, the interface can be cleaned up
quite a bit so the Python module method is both easier to use and more robust to
malformed inputs.
.. literalinclude:: ./code/add-edited.pyf
:language: fortran
The intent directive, intent(out) is used to tell f2py that ``c`` is
an output variable and should be created by the interface before being
passed to the underlying code. The intent(hide) directive tells f2py
to not allow the user to specify the variable, ``n``, but instead to
get it from the size of ``a``. The depend( ``a`` ) directive is
necessary to tell f2py that the value of n depends on the input a (so
that it won't try to create the variable n until the variable a is
created).
After modifying ``add.pyf``, the new Python module file can be generated
by compiling both ``add.f`` and ``add.pyf``::
python -m numpy.f2py -c add.pyf add.f
The new interface's docstring is:
.. code-block:: python
>>> import add
>>> print(add.zadd.__doc__)
c = zadd(a,b)
Wrapper for ``zadd``.
Parameters
----------
a : input rank-1 array('D') with bounds (n)
b : input rank-1 array('D') with bounds (n)
Returns
-------
c : rank-1 array('D') with bounds (n)
Now, the function can be called in a much more robust way:
.. code-block::
>>> add.zadd([1, 2, 3], [4, 5, 6])
array([5.+0.j, 7.+0.j, 9.+0.j])
Notice the automatic conversion to the correct format that occurred.
Inserting directives in Fortran source
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The robust interface of the previous section can also be generated automatically
by placing the variable directives as special comments in the original Fortran
code.
.. note::
For projects where the Fortran code is being actively developed, this may be
preferred.
Thus, if the source code is modified to contain:
.. literalinclude:: ./code/add-improved.f
:language: fortran
Then, one can compile the extension module using::
python -m numpy.f2py -c -m add add.f
The resulting signature for the function add.zadd is exactly the same
one that was created previously. If the original source code had
contained ``A(N)`` instead of ``A(*)`` and so forth with ``B`` and ``C``,
then nearly the same interface can be obtained by placing the
``INTENT(OUT) :: C`` comment line in the source code. The only difference
is that ``N`` would be an optional input that would default to the length
of ``A``.
A filtering example
-------------------
This example shows a function that filters a two-dimensional array of double
precision floating-point numbers using a fixed averaging filter. The advantage
of using Fortran to index into multi-dimensional arrays should be clear from
this example.
.. literalinclude:: ./code/filter.f
:language: fortran
This code can be compiled and linked into an extension module named
filter using::
python -m numpy.f2py -c -m filter filter.f
This will produce an extension module in the current directory with a method
named ``dfilter2d`` that returns a filtered version of the input.
``depends`` keyword example
---------------------------
Consider the following code, saved in the file ``myroutine.f90``:
.. literalinclude:: ./code/myroutine.f90
:language: fortran
Wrapping this with ``python -m numpy.f2py -c myroutine.f90 -m myroutine``, we
can do the following in Python::
>>> import numpy as np
>>> import myroutine
>>> x = myroutine.s(2, 3, np.array([5, 6, 7]))
>>> x
array([[5., 0., 0.],
[0., 0., 0.]])
Now, instead of generating the extension module directly, we will create a
signature file for this subroutine first. This is a common pattern for
multi-step extension module generation. In this case, after running
.. code-block:: python
python -m numpy.f2py myroutine.f90 -m myroutine -h myroutine.pyf
the following signature file is generated:
.. literalinclude:: ./code/myroutine.pyf
:language: fortran
Now, if we run ``python -m numpy.f2py -c myroutine.pyf myroutine.f90`` we see an
error; note that the signature file included a ``depend(m,n)`` statement for
``x`` which is not necessary. Indeed, editing the file above to read
.. literalinclude:: ./code/myroutine-edited.pyf
:language: fortran
and running ``f2py -c myroutine.pyf myroutine.f90`` yields correct results.
Read more
---------
* `Wrapping C codes using f2py <https://scipy.github.io/old-wiki/pages/Cookbook/f2py_and_NumPy.html>`_
* `F2py section on the SciPy Cookbook <https://scipy-cookbook.readthedocs.io/items/F2Py.html>`_
* `F2py example: Interactive System for Ice sheet Simulation <http://websrv.cs.umt.edu/isis/index.php/F2py_example>`_
* `"Interfacing With Other Languages" section on the SciPy Cookbook.
<https://scipy-cookbook.readthedocs.io/items/idx_interfacing_with_other_languages.html>`_
.. _`NumPy issue tracker`: https://github.com/numpy/numpy/issues?q=is%3Aissue+label%3A%22component%3A+numpy.f2py%22+is%3Aclosed
.. _`test cases`: https://github.com/numpy/numpy/tree/main/doc/source/f2py/code
|