问题
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 buildfx1 = f(x1)
as an array with 5 elements corresponding to the functions infunc(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