Back to Numpy

F2PY examples

doc/source/f2py/f2py-examples.rst

2.5.0.dev08.2 KB
Original Source

.. _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>`_
* `"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