f2py, Python function that returns an array (vector-valued function)

末鹿安然 提交于 2019-12-17 20:49:24

问题


In the following Python I have five functions contained in the array returned by func which I have to integrate. The code calls an external Fortran module generated using f2py:

import numpy as np
from numpy import cos, sin , exp
from trapzdv import trapzdv
def func(x):
    return np.array([x**2, x**3, cos(x), sin(x), exp(x)])

if __name__ == '__main__':
    xs = np.linspace(0.,20.,100)
    ans =  trapzdv(func,xs,5)
    print 'from Fortran:', ans
    print 'exact:', np.array([20**3/3., 20**4/4., sin(20.), -cos(20.), exp(20.)])

The Fortran routine is:

      subroutine trapzdv(f,xs,nf,nxs,result)
          integer :: I
          double precision :: x1,x2
          integer, intent(in) :: nf, nxs
          double precision, dimension(nf) :: fx1,fx2
          double precision, intent(in), dimension(nxs) :: xs
          double precision, intent(out), dimension(nf) :: result
          external :: f 
          result = 0.0
          do I = 2,nxs
            x1 = xs(I-1)
            x2 = xs(I)
            fx1 = f(x1)
            fx2 = f(x2)
            result = result + (fx1+fx2)*(x2-x1)/2
          enddo
          return
      end 

The problem is that Fortran is only integrating the first function in func(x). See the print result:

from Fortran: [ 2666.80270721  2666.80270721  2666.80270721  2666.80270721  2666.80270721]
exact: [  2.66666667e+03   4.00000000e+04   9.12945251e-01  -4.08082062e-01 4.85165195e+08]

One way to workarond that is to modify func(x) to return the value of a given position in the array of functions:

def func(x,i):
    return np.array([x**2, x**3, cos(x), sin(x), exp(x)])[i-1]

And then change the Fortran routine to call the function with two parameters:

      subroutine trapzdv(f,xs,nf,nxs,result)
          integer :: I
          double precision :: x1,x2,fx1,fx2
          integer, intent(in) :: nf, nxs
          double precision, intent(in), dimension(nxs) :: xs
          double precision, intent(out), dimension(nf) :: result
          external :: f 
          result = 0.0
          do I = 2,nxs
            x1 = xs(I-1)
            x2 = xs(I)
            do J = 1,nf
                fx1 = f(x1,J)
                fx2 = f(x2,J)
                result(J) = result(J) + (fx1+fx2)*(x2-x1)/2
            enddo
          enddo
          return
      end 

Which works:

from Fortran: [  2.66680271e+03   4.00040812e+04   9.09838195e-01   5.89903440e-01 4.86814128e+08]
exact: [  2.66666667e+03   4.00000000e+04   9.12945251e-01  -4.08082062e-01 4.85165195e+08]

But here func is called 5 times more than necessary (in the real case func has above 300 functions, so it will be called 300 times more than necessary).

  • Does anyone know a better solution to make Fortran recognizes all the array returned by func(x)? In other words, make Fortran build fx1 = f(x1) as an array with 5 elements corresponding to the functions in func(x).

OBS: I am compiling using f2py -c --compiler=mingw32 -m trapzdv trapzdv.f90


回答1:


Unfortunately, you cannot return the array from the python function into Fortran. You would need a subroutine for that (meaning it is called with the call statement), and this is something that f2py does not let you do.

In Fortran 90 you can create functions that return arrays, but again this is not something that f2py can do, especially since your function is not a Fortran function.

Your only option is to use your looping workaround, or a redesign of how you want python and Fortran to interact.




回答2:


Despite this answer does not solve the question, it is a workaround doing the same in Cython. Here the trapezoidal rule and a polynomial integrator are implemented for a vector-valued function. The code below I put in a integratev.pyx:

import numpy as np
from numpy.linalg import inv
cimport numpy as np
FLOAT = np.float32
ctypedef np.float_t FLOAT_t

def trapzv(f, np.ndarray xs, int nf):
    cdef int nxs = xs.shape[0]
    cdef np.ndarray ans = np.zeros(nf, dtype=FLOAT)
    cdef double x1, x2
    for i in range(1,nxs):
        x1 = xs[i-1]
        x2 = xs[i]
        ans += (f(x2)+f(x1))*(x2-x1)/2.
    return ans

def poly(f, np.ndarray xs, int nf, int order=2):
    cdef int nxs = xs.shape[0]
    cdef np.ndarray ans = np.zeros(nf, dtype=FLOAT)
    cdef np.ndarray xis = np.zeros(order+1, dtype=FLOAT)
    cdef np.ndarray ais
    if nxs % (order+1) != 0:
        raise ValueError("poly: The size of xs must be a multiple of 'order+1'")
    for i in range(order,nxs,order):
        xis = xs[i-order:i+1]
        X = np.concatenate([(xis**i)[:,None] for i in range(order+1)], axis=1)
        ais = np.dot( inv(X), f(xis).transpose() )
        for k in range(1,order+2):
            ans += ais[k-1,:]/k * (xis[-1]**k - xis[0]**k)
    return ans

The following test was used:

import numpy as np
from numpy import cos, sin , exp
import pyximport; pyximport.install()
import integratev
from subprocess import Popen
def func(x):
    return np.array([x**2, x**3, cos(x), sin(x), exp(x)])

if __name__ == '__main__':
    xs = np.linspace(0.,20.,33)
    print 'exact:', np.array([20**3/3., 20**4/4., sin(20.), -cos(20.)+1, exp(20.)-1])
    ans =  integratev.trapzv(func,xs,5)
    print 'trapzv:', ans
    ans =  integratev.poly(func,xs,5,2)
    print 'poly:', ans

Giving:

exact: [  2.66666667e+03   4.00000000e+04   9.12945251e-01   5.91917938e-01 4.85165194e+08]
trapzv: [  2.66796875e+03   4.00390625e+04   8.83031547e-01   5.72522998e-01 5.00856448e+08]
poly: [  2.66666675e+03   4.00000000e+04   9.13748980e-01   5.92435718e-01 4.85562144e+08]

The poly can be of any order, which will probably give better results for most of the cases...



来源:https://stackoverflow.com/questions/17474225/f2py-python-function-that-returns-an-array-vector-valued-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!