File size: 8,811 Bytes
c011401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
======================================
 Three ways to wrap - getting started
======================================

Wrapping Fortran or C functions to Python using F2PY consists of the
following steps:

* Creating the so-called signature file that contains descriptions of
  wrappers to Fortran or C functions, also called as signatures of the
  functions. In the case of Fortran routines, F2PY can create initial
  signature file by scanning Fortran source codes and
  catching all relevant information needed to create wrapper
  functions.

* Optionally, F2PY created signature files can be edited to optimize
  wrappers functions, 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 extension modules, F2PY uses
  ``numpy_distutils`` that supports a number of Fortran 77/90/95
  compilers, including Gnu, Intel,
  Sun Fortre, SGI MIPSpro, Absoft, NAG, Compaq etc. compilers.

Depending on a particular situation, these steps can be carried out
either by just in one command or step-by-step, some steps can be
omitted or combined with others.

Below I'll describe three typical approaches of using F2PY.
The following `example Fortran 77 code`__ will be used for
illustration:

.. include:: fib1.f
   :literal:

__ fib1.f

The quick way
==============

The quickest way to wrap the Fortran subroutine ``FIB`` to Python is
to run

::

  f2py -c fib1.f -m fib1

This command builds (see ``-c`` flag, execute ``f2py`` without
arguments to see the explanation of command line options) an extension
module ``fib1.so`` (see ``-m`` flag) to the current directory. Now, in
Python the Fortran subroutine ``FIB`` is accessible via ``fib1.fib``::

  >>> import numpy
  >>> import fib1
  >>> print fib1.fib.__doc__
  fib - Function signature:
    fib(a,[n])
  Required arguments:
    a : input rank-1 array('d') with bounds (n)
  Optional arguments:
    n := len(a) input int

  >>> a = numpy.zeros(8,'d')
  >>> fib1.fib(a)
  >>> print a
  [  0.   1.   1.   2.   3.   5.   8.  13.]

.. note::

  * Note that F2PY found 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 = numpy.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)
      fib:n=10
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      fib.error: (len(a)>=n) failed for 1st keyword n
      >>>

    This demonstrates one of the useful features in F2PY, that it,
    F2PY implements basic compatibility checks between related
    arguments in order to avoid any unexpected crashes.

  * When a Numpy array, that is Fortran contiguous and has a dtype
    corresponding to 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 a proper dtype) of
    the input array and passes C pointer of the copy to Fortran
    subroutine. As a result, any possible changes to the (copy of)
    input array have no effect to the original argument, as
    demonstrated below::

      >>> a = numpy.ones(8,'i')
      >>> fib1.fib(a)
      >>> print a
      [1 1 1 1 1 1 1 1]

    Clearly, this is not an expected behaviour. The fact that the
    above example worked with ``dtype=float`` is considered
    accidental.

    F2PY provides ``intent(inplace)`` attribute that would modify
    the attributes of an input array so that any changes made by
    Fortran routine will be effective also in input argument. For example,
    if one specifies ``intent(inplace) a`` (see below, how), then
    the example above would read:

      >>> a = numpy.ones(8,'i')
      >>> fib1.fib(a)
      >>> print a
      [  0.   1.   1.   2.   3.   5.   8.  13.]

    However, the recommended way to get changes made by Fortran
    subroutine back to python is to use ``intent(out)`` attribute. It
    is more efficient and a cleaner solution.

  * The usage of ``fib1.fib`` in Python is very similar to using
    ``FIB`` in Fortran. However, using *in situ* output arguments in
    Python indicates a poor style as there is no safety mechanism
    in Python with respect to wrong argument types. When using Fortran
    or C, compilers naturally discover any type mismatches during
    compile time but in Python the types must be checked in
    runtime. So, using *in situ* output arguments in Python may cause
    difficult to find bugs, not to mention that the codes will be less
    readable when all required type checks are implemented.

  Though the demonstrated way of wrapping Fortran routines to Python
  is very straightforward, it has several drawbacks (see the comments
  above).  These drawbacks are due to the fact that there is no way
  that F2PY can determine what is the actual intention of one or the
  other argument, is it input or output argument, or both, or
  something else. So, F2PY conservatively assumes that all arguments
  are input arguments by default.

  However, there are ways (see below) how to "teach" F2PY about the
  true intentions (among other things) of function arguments; and then
  F2PY is able to generate more Pythonic (more explicit, easier to
  use, and less error prone) wrappers to Fortran functions.

The smart way
==============

Let's apply the steps of wrapping Fortran functions to Python one by
one.

* First, we create a signature file from ``fib1.f`` by running

  ::

    f2py fib1.f -m fib2 -h fib1.pyf

  The signature file is saved to ``fib1.pyf`` (see ``-h`` flag) and
  its contents is shown below.

  .. include:: fib1.pyf
     :literal:

* Next, we'll teach F2PY that the argument ``n`` is a input argument
  (use ``intent(in)`` attribute) and that the result, i.e. the
  contents of ``a`` after calling Fortran function ``FIB``, should be
  returned to Python (use ``intent(out)`` attribute). In addition, an
  array ``a`` should be created dynamically using the size given by
  the input argument ``n`` (use ``depend(n)`` attribute to indicate
  dependence relation).

  The content of a modified version of ``fib1.pyf`` (saved as
  ``fib2.pyf``) is as follows:

  .. include:: fib2.pyf
     :literal:

* And finally, we build the extension module by running

  ::

    f2py -c fib2.pyf fib1.f

In Python::

  >>> import fib2
  >>> print fib2.fib.__doc__
  fib - Function signature:
    a = fib(n)
  Required arguments:
    n : input int
  Return objects:
    a : rank-1 array('d') with bounds (n)

  >>> print fib2.fib(8)
  [  0.   1.   1.   2.   3.   5.   8.  13.]

.. note::

  * Clearly, the signature of ``fib2.fib`` now corresponds to the
    intention of Fortran subroutine ``FIB`` more closely: given the
    number ``n``, ``fib2.fib`` returns the first ``n`` Fibonacci numbers
    as a Numpy array. Also, the new Python signature ``fib2.fib``
    rules out any surprises that we experienced with ``fib1.fib``.

  * Note that by default using single ``intent(out)`` also implies
    ``intent(hide)``. Argument that has ``intent(hide)`` attribute
    specified, will not be listed in the argument list of a wrapper
    function.

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. Namely, F2PY specific attributes can be inserted directly to
Fortran source codes using the so-called F2PY directive. A F2PY
directive defines special comment lines (starting with ``Cf2py``, for
example) which are ignored by Fortran compilers but F2PY interprets
them as normal lines.

Here is shown a `modified version of the example Fortran code`__, saved
as ``fib3.f``:

.. include:: fib3.f
   :literal:

__ fib3.f

Building the extension module can be now carried out in one command::

  f2py -c -m fib3 fib3.f

Notice that the resulting wrapper to ``FIB`` is as "smart" as in
previous case::

  >>> import fib3
  >>> print fib3.fib.__doc__
  fib - Function signature:
    a = fib(n)
  Required arguments:
    n : input int
  Return objects:
    a : rank-1 array('d') with bounds (n)

  >>> print fib3.fib(8)
  [  0.   1.   1.   2.   3.   5.   8.  13.]