\section{Calling wrapper functions from Python} \label{sec:notes} \subsection{Scalar arguments} \label{sec:scalars} In general, for scalar argument you can pass in in addition to ordinary Python scalars (like integers, floats, complex values) also arbitrary sequence objects (lists, arrays, strings) --- then the first element of a sequence is passed in to the Fortran routine. It is recommended that you always pass in scalars of required type. This ensures the correctness as no type-casting is needed. However, no exception is raised if type-casting would produce inaccurate or incorrect results! For example, in place of an expected complex value you can give an integer, or vice-versa (in the latter case only a rounded real part of the complex value will be used). If the argument is \texttt{intent(inout)} then Fortran routine can change the value ``in place'' only if you pass in a sequence object, for instance, rank-0 array. Also make sure that the type of an array is of correct type. Otherwise type-casting will be performed and you may get inaccurate or incorrect results. The following example illustrates this \begin{verbatim} >>> a = array(0) >>> calculate_pi(a) >>> print a 3 \end{verbatim} If you pass in an ordinary Python scalar in place of \texttt{intent(inout)} variable, it will be used as an input argument since Python scalars cannot not be changed ``in place'' (all Python scalars are immutable objects). \subsection{String arguments} \label{sec:strings} You can pass in strings of arbitrary length. If the length is greater than required, only a required part of the string is used. If the length is smaller than required, additional memory is allocated and fulfilled with `\texttt{\bs0}'s. Because Python strings are immutable, \texttt{intent(inout)} argument expects an array version of a string --- an array of chars: \texttt{array("")}. Otherwise, the change ``in place'' has no effect. \subsection{Array arguments} \label{sec:arrays} If the size of an array is relatively large, it is \emph{highly recommended} that you pass in arrays of required type. Otherwise, type-casting will be performed which includes the creation of new arrays and their copying. If the argument is also \texttt{intent(inout)}, the wasted time is doubled. So, pass in arrays of required type! On the other hand, there are situations where it is perfectly all right to ignore this recommendation: if the size of an array is relatively small or the actual time spent in Fortran routine takes much longer than copying an array. Anyway, if you want to optimize your Python code, start using arrays of required types. Another source of performance hit is when you use non-contiguous arrays. The performance hit will be exactly the same as when using incorrect array types. This is because a contiguous copy is created to be passed in to the Fortran routine. \fpy provides a feature such that the ranks of array arguments need not to match --- only the correct total size matters. For example, if the wrapper function expects a rank-1 array \texttt{array([...])}, then it is correct to pass in rank-2 (or higher) arrays \texttt{array([[...],...,[...]])} assuming that the sizes will match. This is especially useful when the arrays should contain only one element (size is 1). Then you can pass in arrays \texttt{array(0)}, \texttt{array([0])}, \texttt{array([[0]])}, etc and all cases are handled correctly. In this case it is correct to pass in a Python scalar in place of an array (but then ``change in place'' is ignored, of course). \subsubsection{Multidimensional arrays} If you are using rank-2 or higher rank arrays, you must always remember that indexing in Fortran starts from the lowest dimension while in Python (and in C) the indexing starts from the highest dimension (though some compilers have switches to change this). As a result, if you pass in a 2-dimensional array then the Fortran routine sees it as the transposed version of the array (in multi-dimensional case the indexes are reversed). You must take this matter into account also when modifying the signature file and interpreting the generated Python signatures: \begin{itemize} \item First, when initializing an array using \texttt{init\_expr}, the index vector \texttt{\_i[]} changes accordingly to Fortran convention. \item Second, the result of CPP-macro \texttt{shape(,0)} corresponds to the last dimension of the Fortran array, etc. \end{itemize} Let me illustrate this with the following example:\\ \begin{verbatim} ! Fortran file: arr.f subroutine arr(l,m,n,a) integer l,m,n real*8 a(l,m,n) ... end \end{verbatim} \fpy will generate the following signature file:\\ \begin{verbatim} !%f90 ! Signature file: arr.f90 python module arr ! in interface ! in :arr subroutine arr(l,m,n,a) ! in :arr:arr.f integer optional,check(shape(a,2)==l),depend(a) :: l=shape(a,2) integer optional,check(shape(a,1)==m),depend(a) :: m=shape(a,1) integer optional,check(shape(a,0)==n),depend(a) :: n=shape(a,0) real*8 dimension(l,m,n) :: a end subroutine arr end interface end python module arr \end{verbatim} and the following wrapper function will be produced \begin{verbatim} None = arr(a,l=shape(a,2),m=shape(a,1),n=shape(a,0)) \end{verbatim} In general, I would suggest not to specify the given optional variables \texttt{l,m,n} when calling the wrapper function --- let the interface find the values of the variables \texttt{l,m,n}. But there are occasions when you need to specify the dimensions in Python. So, in Python a proper way to create an array from the given dimensions is \begin{verbatim} >>> a = zeros(n,m,l,'d') \end{verbatim} (note that the dimensions are reversed and correct type is specified), and then a complete call to \texttt{arr} is \begin{verbatim} >>> arr(a,l,m,n) \end{verbatim} From the performance point of view, always be consistent with Fortran indexing convention, that is, use transposed arrays. But if you do the following \begin{verbatim} >>> a = transpose(zeros(l,m,n,'d')) >>> arr(a) \end{verbatim} then you will get a performance hit! The reason is that here the transposition is not actually performed. Instead, the array \texttt{a} will be non-contiguous which means that before calling a Fortran routine, internally a contiguous array is created which includes memory allocation and copying. In addition, if the argument array is also \texttt{intent(inout)}, the results are copied back to the initial array which doubles the performance hit! So, to improve the performance: always pass in arrays that are contiguous. \subsubsection{Work arrays} Often Fortran routines use the so-called work arrays. The corresponding arguments can be declared as optional arguments, but be sure that all dimensions are specified (bounded) and defined before the initialization (dependence relations). On the other hand, if you call the Fortran routine many times then you don't want to allocate/deallocate the memory of the work arrays on every call. In this case it is recommended that you create temporary arrays with proper sizes in Python and use them as work arrays. But be careful when specifying the required type and be sure that the temporary arrays are contiguous. Otherwise the performance hit would be even harder than the hit when not using the temporary arrays from Python! \subsection{Call-back arguments} \label{sec:cbargs} \fpy builds a very flexible call-back mechanisms for call-back arguments. If the wrapper function expects a call-back function \texttt{fun} with the following Python signature to be passed in \begin{verbatim} def fun(a_1,...,a_n): ... return x_1,...,x_k \end{verbatim} but the user passes in a function \texttt{gun} with the signature \begin{verbatim} def gun(b_1,...,b_m): ... return y_1,...,y_l \end{verbatim} and the following extra arguments (specified as additional optional argument for the wrapper function): \begin{verbatim} fun_extra_args = (e_1,...,e_p) \end{verbatim} then the actual call-back is constructed accordingly to the following rules: \begin{itemize} \item if \texttt{p==0} then \texttt{gun(a\_1,...,a\_q)}, where \texttt{q=min(m,n)}; \item if \texttt{n+p<=m} then \texttt{gun(a\_1,...,a\_n,e\_1,...,e\_p)}; \item if \texttt{p<=mm} then \texttt{gun(e\_1,...,e\_m)}; \item if \texttt{n+p} is less than the number of required arguments of the function \texttt{gun}, an exception is raised. \end{itemize} A call-back function \texttt{gun} may return any number of objects as a tuple: if \texttt{kl}, then only objects \texttt{x\_1,...,x\_l} are set. \subsection{Obtaining information on wrapper functions} \label{sec:info} From the previous sections we learned that it is useful for the performance to pass in arguments of expected type, if possible. To know what are the expected types, \fpy generates a complete documentation strings for all wrapper functions. You can read them from Python by printing out \texttt{\_\_doc\_\_} attributes of the wrapper functions. For the example in Sec.~\ref{sec:intro}: \begin{verbatim} >>> print foobar.foo.__doc__ Function signature: foo(a) Required arguments: a : in/output rank-0 array(int,'i') >>> print foobar.bar.__doc__ Function signature: bar = bar(a,b) Required arguments: a : input int b : input int Return objects: bar : int \end{verbatim} In addition, \fpy generates a LaTeX document (\texttt{module.tex}) containing a bit more information on the wrapper functions. See for example Appendix that contains a result of the documentation generation for the example module \texttt{foobar}. Here the file \texttt{foobar-smart.f90} (modified version of \texttt{foobar.f90}) is used --- it contains \texttt{note()} attributes for specifying some additional information. \subsection{Wrappers for common blocks} \label{sec:wrapcomblock} [See examples \texttt{test-site/e/runme*}] What follows is obsolute for \fpy version higher that 2.264. \fpy generates wrapper functions for common blocks. For every common block with a name \texttt{} a function \texttt{get\_()} is constructed that takes no arguments and returns a dictionary. The dictionary represents maps between the names of common block fields and the arrays containing the common block fields (multi-dimensional arrays are transposed). So, in order to access to the common block fields, you must first obtain the references \begin{verbatim} commonblock = get_() \end{verbatim} and then the fields are available through the arrays \texttt{commonblock[""]}. To change the values of common block fields, you can use for scalars \begin{verbatim} commonblock[""][0] = \end{verbatim} and for arrays \begin{verbatim} commonblock[""][:] = \end{verbatim} for example. For more information on the particular common block wrapping, see \texttt{get\_.\_\_doc\_\_}. \subsection{Wrappers for F90/95 module data and routines} \label{sec:wrapf90modules} [See example \texttt{test-site/mod/runme\_mod}] \subsection{Examples} \label{sec:examples} Examples on various aspects of wrapping Fortran routines to Python can be found in directories \texttt{test-site/d/} and \texttt{test-site/e/}: study the shell scripts \texttt{runme\_*}. See also files in \texttt{doc/ex1/}. %%% Local Variables: %%% mode: latex %%% TeX-master: "f2py2e" %%% End: