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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
|
.. _f2py-getting-started:
======================================
Three ways to wrap - getting started
======================================
Wrapping Fortran or C functions to Python using F2PY consists of the
following steps:
* Creating the so-called :doc:`signature file <signature-file>` that contains
descriptions of wrappers to Fortran or C functions, also called the signatures
of the functions. For Fortran routines, F2PY can create an initial signature
file by scanning Fortran source codes and tracking all relevant information
needed to create wrapper functions.
* Optionally, F2PY-created signature files can be edited to optimize wrapper
functions, which can make them "smarter" and more "Pythonic".
* F2PY reads a signature file and writes a Python C/API module containing
Fortran/C/Python bindings.
* F2PY compiles all sources and builds an extension module containing
the wrappers.
* In building the extension modules, F2PY uses ``numpy_distutils`` which
supports a number of Fortran 77/90/95 compilers, including Gnu, Intel, Sun
Fortran, SGI MIPSpro, Absoft, NAG, Compaq etc. For different build systems,
see :ref:`f2py-bldsys`.
* Depending on your operating system, you may need to install the Python
development headers (which provide the file ``Python.h``) separately. In
Linux Debian-based distributions this package should be called ``python3-dev``,
in Fedora-based distributions it is ``python3-devel``. For macOS, depending
how Python was installed, your mileage may vary. In Windows, the headers are
typically installed already.
Depending on the situation, these steps can be carried out in a single composite
command or step-by-step; in which case some steps can be omitted or combined
with others.
Below, we describe three typical approaches of using F2PY. These can be read in
order of increasing effort, but also cater to different access levels depending
on whether the Fortran code can be freely modified.
The following example Fortran 77 code will be used for
illustration, save it as ``fib1.f``:
.. literalinclude:: ./code/fib1.f
:language: fortran
.. note::
F2PY parses Fortran/C signatures to build wrapper functions to be used with
Python. However, it is not a compiler, and does not check for additional
errors in source code, nor does it implement the entire language standards.
Some errors may pass silently (or as warnings) and need to be verified by the
user.
The quick way
==============
The quickest way to wrap the Fortran subroutine ``FIB`` for use in Python is to
run
::
python -m numpy.f2py -c fib1.f -m fib1
or, alternatively, if the ``f2py`` command-line tool is available,
::
f2py -c fib1.f -m fib1
.. note::
Because the ``f2py`` command might not be available in all system, notably on
Windows, we will use the ``python -m numpy.f2py`` command throughout this
guide.
This command compiles and wraps ``fib1.f`` (``-c``) to create the extension
module ``fib1.so`` (``-m``) in the current directory. A list of command line
options can be seen by executing ``python -m numpy.f2py``. Now, in Python the
Fortran subroutine ``FIB`` is accessible via ``fib1.fib``::
>>> import numpy as np
>>> import fib1
>>> print(fib1.fib.__doc__)
fib(a,[n])
Wrapper for ``fib``.
Parameters
----------
a : input rank-1 array('d') with bounds (n)
Other Parameters
----------------
n : input int, optional
Default: len(a)
>>> a = np.zeros(8, 'd')
>>> fib1.fib(a)
>>> print(a)
[ 0. 1. 1. 2. 3. 5. 8. 13.]
.. note::
* Note that F2PY recognized that the second argument ``n`` is the
dimension of the first array argument ``a``. Since by default all
arguments are input-only arguments, F2PY concludes that ``n`` can
be optional with the default value ``len(a)``.
* One can use different values for optional ``n``::
>>> a1 = np.zeros(8, 'd')
>>> fib1.fib(a1, 6)
>>> print(a1)
[ 0. 1. 1. 2. 3. 5. 0. 0.]
but an exception is raised when it is incompatible with the input
array ``a``::
>>> fib1.fib(a, 10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
fib.error: (len(a)>=n) failed for 1st keyword n: fib:n=10
>>>
F2PY implements basic compatibility checks between related
arguments in order to avoid unexpected crashes.
* When a NumPy array that is :term:`Fortran <Fortran order>`
:term:`contiguous` and has a ``dtype`` corresponding to a presumed Fortran
type is used as an input array argument, then its C pointer is directly
passed to Fortran.
Otherwise, F2PY makes a contiguous copy (with the proper ``dtype``) of the
input array and passes a C pointer of the copy to the Fortran subroutine. As
a result, any possible changes to the (copy of) input array have no effect
on the original argument, as demonstrated below::
>>> a = np.ones(8, 'i')
>>> fib1.fib(a)
>>> print(a)
[1 1 1 1 1 1 1 1]
Clearly, this is unexpected, as Fortran typically passes by reference. That
the above example worked with ``dtype=float`` is considered accidental.
F2PY provides an ``intent(inplace)`` attribute that modifies the attributes
of an input array so that any changes made by the Fortran routine will be
reflected in the input argument. For example, if one specifies the
``intent(inplace) a`` directive (see :ref:`f2py-attributes` for details),
then the example above would read::
>>> a = np.ones(8, 'i')
>>> fib1.fib(a)
>>> print(a)
[ 0. 1. 1. 2. 3. 5. 8. 13.]
However, the recommended way to have changes made by Fortran subroutine
propagate to Python is to use the ``intent(out)`` attribute. That approach
is more efficient and also cleaner.
* The usage of ``fib1.fib`` in Python is very similar to using ``FIB`` in
Fortran. However, using *in situ* output arguments in Python is poor style,
as there are no safety mechanisms in Python to protect against wrong
argument types. When using Fortran or C, compilers discover any type
mismatches during the compilation process, but in Python the types must be
checked at runtime. Consequently, using *in situ* output arguments in Python
may lead to difficult to find bugs, not to mention the fact that the
codes will be less readable when all required type checks are implemented.
Though the approach to wrapping Fortran routines for Python discussed so far
is very straightforward, it has several drawbacks (see the comments above).
The drawbacks are due to the fact that there is no way for F2PY to determine
the actual intention of the arguments; that is, there is ambiguity in
distinguishing between input and output arguments. Consequently, F2PY assumes
that all arguments are input arguments by default.
There are ways (see below) to remove this ambiguity by "teaching" F2PY about
the true intentions of function arguments, and F2PY is then able to generate
more explicit, easier to use, and less error prone wrappers for Fortran
functions.
The smart way
==============
If we want to have more control over how F2PY will treat the interface to our
Fortran code, we can apply the wrapping steps one by one.
* First, we create a signature file from ``fib1.f`` by running:
::
python -m numpy.f2py fib1.f -m fib2 -h fib1.pyf
The signature file is saved to ``fib1.pyf`` (see the ``-h`` flag) and its
contents are shown below.
.. literalinclude:: ./code/fib1.pyf
:language: fortran
* Next, we'll teach F2PY that the argument ``n`` is an input argument (using the
``intent(in)`` attribute) and that the result, i.e., the contents of ``a``
after calling the Fortran function ``FIB``, should be returned to Python
(using the ``intent(out)`` attribute). In addition, an array ``a`` should be
created dynamically using the size determined by the input argument ``n``
(using the ``depend(n)`` attribute to indicate this dependence relation).
The contents of a suitably modified version of ``fib1.pyf`` (saved as
``fib2.pyf``) are as follows:
.. literalinclude:: ./code/fib2.pyf
:language: fortran
* Finally, we build the extension module with ``numpy.distutils`` by running:
::
python -m numpy.f2py -c fib2.pyf fib1.f
In Python::
>>> import fib2
>>> print(fib2.fib.__doc__)
a = fib(n)
Wrapper for ``fib``.
Parameters
----------
n : input int
Returns
-------
a : rank-1 array('d') with bounds (n)
>>> print(fib2.fib(8))
[ 0. 1. 1. 2. 3. 5. 8. 13.]
.. note::
* The signature of ``fib2.fib`` now more closely corresponds to the intention
of the Fortran subroutine ``FIB``: given the number ``n``, ``fib2.fib``
returns the first ``n`` Fibonacci numbers as a NumPy array. The new Python
signature ``fib2.fib`` also rules out the unexpected behaviour in
``fib1.fib``.
* Note that by default, using a single ``intent(out)`` also implies
``intent(hide)``. Arguments that have the ``intent(hide)`` attribute
specified will not be listed in the argument list of a wrapper function.
For more details, see :doc:`signature-file`.
The quick and smart way
========================
The "smart way" of wrapping Fortran functions, as explained above, is
suitable for wrapping (e.g. third party) Fortran codes for which
modifications to their source codes are not desirable nor even
possible.
However, if editing Fortran codes is acceptable, then the generation of an
intermediate signature file can be skipped in most cases. F2PY specific
attributes can be inserted directly into Fortran source codes using F2PY
directives. A F2PY directive consists of special comment lines (starting with
``Cf2py`` or ``!f2py``, for example) which are ignored by Fortran compilers but
interpreted by F2PY as normal lines.
Consider a modified version of the previous Fortran code with F2PY directives,
saved as ``fib3.f``:
.. literalinclude:: ./code/fib3.f
:language: fortran
Building the extension module can be now carried out in one command::
python -m numpy.f2py -c -m fib3 fib3.f
Notice that the resulting wrapper to ``FIB`` is as "smart" (unambiguous) as in
the previous case::
>>> import fib3
>>> print(fib3.fib.__doc__)
a = fib(n)
Wrapper for ``fib``.
Parameters
----------
n : input int
Returns
-------
a : rank-1 array('d') with bounds (n)
>>> print(fib3.fib(8))
[ 0. 1. 1. 2. 3. 5. 8. 13.]
|