using SciPy to integrate a function that returns a matrix or array

邮差的信 提交于 2019-11-29 03:38:49

The first argument to either quad or quadrature must be a callable. The vec_func argument of the quadrature refers to whether the argument of this callable is a (possibly multidimensional) vector. Technically, you can vectorize the quad itself:

>>> from math import sin, cos, pi
>>> from scipy.integrate import quad
>>> from numpy import vectorize
>>> a = [sin, cos]
>>> vectorize(quad)(a, 0, pi)
(array([  2.00000000e+00,   4.92255263e-17]), array([  2.22044605e-14,   2.21022394e-14]))

But that's just equivalent to explicit looping over the elements of a. Specifically, it'll not give you any performance gains, if that's what you're after. So, all in all, the question is why and what exactly you are trying to achieve here.

Daniel

Vectorizing trapezoidal and simpson's integral rules. Trapezoidal is just copy and pasted from another project that used logspace instead of linspace so that it can utilize non-uniform grids.

def trap(func,a,b,num):
    xlinear=np.linspace(a,b,num)
    slicevol=np.diff(xlinear)
    output=integrand(xlinear)
    output=output[:,:-1]+output[:,1:]
    return np.dot(output,slicevol)/2

def simpson(func,a,b,num):
    a=float(a)
    b=float(b)
    h=(b-a)/num

    output=4*np.sum(integrand(a+h*np.arange(1,num,2)),axis=1)
    output+=2*np.sum(integrand(a+h*np.arange(2,num-1,2)),axis=1)
    output+=np.sum(integrand(b),axis=1)
    output+=np.sum(integrand(a),axis=1)
    return output*h/3

def integrand(rlin):
    first=np.arange(1,11)[:,None]
    second=np.arange(2,12)[:,None]
    return np.vstack((rlin*first,np.power(rlin,second)))

Examine trapazoidal and simpsons rule cumulative relative errors:

b=float(100)
first=np.arange(1,11)*(b**2)/2
second=np.power(b,np.arange(3,13))/np.arange(3,13)
exact=np.vstack((first,second))

for x in range(3):
    num=x*100+100
    tr=trap(integrand,0,b,num).reshape(2,-1)
    si=simpson(integrand,0,b,num).reshape(2,-1)
    rel_trap=np.sum(abs((tr-exact)/exact))*100
    rel_simp=np.sum(abs((si-exact)/exact))*100
    print 'Number of points:',num,'Trap Rel',round(rel_trap,6),'Simp Rel',round(rel_simp,6)

Number of points: 100 Trap Rel 0.4846   Simp Rel 0.000171
Number of points: 200 Trap Rel 0.119944 Simp Rel 1.1e-05
Number of points: 300 Trap Rel 0.053131 Simp Rel 2e-06

Timeit. Note that both trapezoidal rules use 200 points while simpsons is timed at only 100 based on the above convergence. Sorry I dont have sympy:

s="""
import numpy as np
from scipy.integrate import trapz

def integrand(rlin):
    first=np.arange(1,11)[:,None]
    second=np.arange(2,12)[:,None]
    return np.vstack((rlin*first,np.power(rlin,second)))

def trap(func,a,b,num):
    xlinear=np.linspace(a,b,num)
    slicevol=np.diff(xlinear)
    output=integrand(xlinear)
    output=output[:,:-1]+output[:,1:]
    return np.dot(output,slicevol)/2

def simpson(func,a,b,num):
    a=float(a)
    b=float(b)
    h=(b-a)/num

    output=4*np.sum(integrand(a+h*np.arange(1,num,2)),axis=1)
    output+=2*np.sum(integrand(a+h*np.arange(2,num-1,2)),axis=1)
    output+=np.sum(integrand(b),axis=1)
    output+=np.sum(integrand(a),axis=1)
    return output*h/3

def simpson2(func,a,b,num):
    a=float(a)
    b=float(b)
    h=(b-a)/num
    p1=a+h*np.arange(1,num,2)
    p2=a+h*np.arange(2,num-1,2)
    points=np.hstack((p1,p2,a,b))
    mult=np.hstack((np.repeat(4,p1.shape[0]),np.repeat(2,p2.shape[0]),1,1))
    return np.dot(integrand(points),mult)*h/3

def x2(x):
    return x**2
def x3(x):
    return x**3
def x4(x):
    return x**4
def x5(x):
    return x**5
def x5(x):
    return x**5
def x6(x):
    return x**6
def x7(x):
    return x**7
def x8(x):
    return x**8
def x9(x):
    return x**9
def x10(x):
    return x**10
def x11(x):
    return x**11
def xt5(x):
    return 5*x
"""

zhenya="""
a=[xt5,xt5,xt5,xt5,xt5,xt5,xt5,xt5,xt5,xt5,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11]
vectorize(quad)(a, 0, 100)
"""

usethedeathstar="""
g=lambda x: np.array([[x,2*x,3*x,4*x,5*x,6*x,7*x,8*x,9*x,10*x],[x**2,x**3,x**4,x**5,x**6,x**7,x**8,x**9,x**10,x**11]])
xv=np.linspace(0,100,200)
trapz(g(xv))
"""

vectrap="""
trap(integrand,0,100,200)
"""

vecsimp="""
simpson(integrand,0,100,100)
"""

vecsimp2="""
simpson2(integrand,0,100,100)
"""

print 'zhenya took',timeit.timeit(zhenya,setup=s,number=100),'seconds.'
print 'usethedeathstar took',timeit.timeit(usethedeathstar,setup=s,number=100),'seconds.'
print 'vectrap took',timeit.timeit(vectrap,setup=s,number=100),'seconds.'
print 'vecsimp took',timeit.timeit(vecsimp,setup=s,number=100),'seconds.'
print 'vecsimp2 took',timeit.timeit(vecsimp2,setup=s,number=100),'seconds.'

Results:

zhenya took 0.0500509738922 seconds.
usethedeathstar took 0.109386920929 seconds.
vectrap took 0.041011095047 seconds.
vecsimp took 0.0376999378204 seconds.
vecsimp2 took 0.0311458110809 seconds.

Something to point out in the timings is zhenya's answer should be much more accurate. I believe everything is correct, please let me know if changes are required.

If you provide the functions and range that you will be using I can probably whip up something better for your system. Also would you be interested in utilizing additional cores/nodes?

usethedeathstar

In the real case the integral does not have an exact solution, do you mean singularities? Could you be more precise on it, as well as on the size of the matrix that you wish to integrate. I have to admit that sympy is dreadfully slow when it comes to some things (not sure if integration is part of it, but i prefer to stay away from sympy and stick to numpy solution). Do you want to get a more elegant solution, by doing it with a matrix or a faster one?

-note: apparently i cant add comment to your post to ask for this stuff, so i had to post this as answer, maybe this is because i dont have enough reputation or so?-

edit: something like this?

    import numpy
    from scipy.integrate import trapz
    g=lambda x: numpy.array([[x,2*x,3*x],[x**2,x**3,x**4]])
    xv=numpy.linspace(0,100,200)
    print trapz(g(xv))

having seen that you want to integrate stuff like sum(a*sin(bx+c)^n*cos(dx+e)^m), for different coefficients for the a,b,c,d,e,m,n, i suggest doing all of those analytically. (should have some formula for that since you can just rewrite sin to complex exponentials

Another thing i noted when checking those functions a bit better, is that sin(a*x+pi/2) and sin(a*x+pi) and stuff like that can be rewritten to cos or sin in a way that removes the pi/2 or pi. Also what i see is that just by looking at the first element in your matrix of functions:

a*sin(bx+c)^2+d*cos(bx+c)^2 = a*(sin^2+cos^2)+(d-a)*cos(bx+c)^2 = a+(d-a)*cos(bx+c)^2 

which also simplifies the calculations. If you had the formulas in a way which didnt involve a massive txtfile or so, id check what the most general formula is that you need to integrate, but i guess its something like a*sin^n(bx+c)*cos^m(dx+e), with m and n being 0 1 or 2, and those things can be simplified into something which can be analytically integrated. So if you find out the most general analytical function you got, you can easily make something like

f=lambda x: [[s1(x),s2(x)],[s3(x),s4(x)]]
res=f(x2)-f(x1)

where s1(x) etc are just the analytically integrated versions of your functions?

(not really planning on going through your entire code to see what all the rest does, but is it just integrating those functions in the txt file from a to b or something like that? or is there somewhere something like that you take the square of each function or whatever thing that might mess up the possibility of doing it analytically?)

this should simplify your integrals i guess?

first integral and: second one

hmm, that second link doesnt work, but you get the idea from the first one i guess

edit, since you do not want analytical solutions: the improvement remains in getting rid of sympy:

from sympy import sin as SIN
from numpy import sin as SIN2
from scipy.integrate import trapz
import time
import numpy as np

def integrand(rlin):
    first=np.arange(1,11)[:,None]
    second=np.arange(2,12)[:,None]
    return np.vstack((rlin*first,np.power(rlin,second)))

def simpson2(func,a,b,num):
    a=float(a)
    b=float(b)
    h=(b-a)/num
    p1=a+h*np.arange(1,num,2)
    p2=a+h*np.arange(2,num-1,2)
    points=np.hstack((p1,p2,a,b))
    mult=np.hstack((np.repeat(4,p1.shape[0]),np.repeat(2,p2.shape[0]),1,1))
    return np.dot(integrand(points),mult)*h/3


A=np.linspace(0,100.,200)

B=lambda x: SIN(x)
C=lambda x: SIN2(x)

t0=time.time()
D=simpson2(B,0,100.,200)
print time.time()-t0
t1=time.time()
E=trapz(C(A))
print time.time()-t1

    t2=time.time()
    F=simpson2(C,0,100.,200)
    print time.time()-t2

results in:

0.000764131546021 sec for the faster method, but when using sympy

7.58171081543e-05 sec for my slower method, but which uses numpy

0.000519037246704 sec for the faster method, when using numpy, 

conclusion: use numpy, ditch sympy, (my slower numpy method is actually faster in this case, because in this example i only tried it on one sin-function, instead of on a ndarray of them, but the point of ditching sympy still remains when comparing the time of the numpy version of the faster method to the one of the sympy version of the faster method)

I might have found some interesting way of doing this, at the expense of defining different symbols for the matrix g_symp:

import numpy as np
from scipy.integrate import quad
import sympy as sy

@np.vectorize
def vec_lambdify(var, expr, *args, **kw):
    return sy.lambdify(var, expr, *args, **kw)

@np.vectorize
def vec_quad(f, a, b, *args, **kw):
    return quad(f, a, b, *args, **kw)[0]

Y = sy.symbols("y1:11")
x = sy.symbols("x")
mul_x = [y.subs(y,x*(i+1)) for (i,y) in enumerate(Y)]
pow_x = [y.subs(y,x**(i+1)) for (i,y) in enumerate(Y)]

g_sympy = np.array(mul_x + pow_x).reshape((2,10))
X = x*np.ones_like(g_sympy)
G = vec_lambdify(X, g_sympy)
I = vec_quad(G, 0, 100)
print(I)

with results:

[[  5.00000000e+03   1.00000000e+04   1.50000000e+04   2.00000000e+04
    2.50000000e+04   3.00000000e+04   3.50000000e+04   4.00000000e+04
    4.50000000e+04   5.00000000e+04]
[  5.00000000e+03   3.33333333e+05   2.50000000e+07   2.00000000e+09
   1.66666667e+11   1.42857143e+13   1.25000000e+15   1.11111111e+17
   1.00000000e+19   9.09090909e+20]]

and using the ipython magic%timeit vec_quad(G,0,100) I got

1000 loops, best of 3: 527 µs per loop

I think this approach is somewhat more clean, despite the juggling with symbols.

quadpy (a project of mine) does vectorized quadrature. This

import numpy
import quadpy


def f(x):
    return [
        [x,  2*x,  3*x,  4*x,  5*x,  6*x,  7*x,  8*x,   9*x,  10*x],
        [x**2, x**3, x**4, x**5, x**6, x**7, x**8, x**9, x**10, x**11]
        ]


sol = quadpy.line_segment.integrate(
        f,
        numpy.array([[0.0], [100.0]]),
        quadpy.line_segment.GaussLegendre(6)
        )

print(sol)

gives

[[[  5.00000000e+03]
[  1.00000000e+04]
[  1.50000000e+04]
[  2.00000000e+04]
[  2.50000000e+04]
[  3.00000000e+04]
[  3.50000000e+04]
[  4.00000000e+04]
[  4.50000000e+04]
[  5.00000000e+04]]

[[  3.33333333e+05]
[  2.50000000e+07]
[  2.00000000e+09]
[  1.66666667e+11]
[  1.42857143e+13]
[  1.25000000e+15]
[  1.11111111e+17]
[  1.00000000e+19]
[  9.09090909e+20]
[  8.33333333e+22]]]

Timings:

%timeit quadpy.line_segment.integrate(f, [0.0, 100.0], gl)
10000 loops, best of 3: 65.1 µs per loop
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!