Используя SciPy для интеграции функции, возвращающей матрицу или массив
У меня есть символический массив, который может быть выражен как:
from sympy import lambdify, Matrix
g_sympy = Matrix([[ 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]])
g = lambdify( (x), g_sympy )
Итак, для каждого x
получается другая матрица:
g(1.) # matrix([[ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.],
# [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
g(2.) # matrix([[ 2.00e+00, 4.00e+00, 6.00e+00, 8.00e+00, 1.00e+01, 1.20e+01, 1.40e+01, 1.60e+01, 1.80e+01, 2.00e+01],
# [ 4.00e+00, 8.00e+00, 1.60e+01, 3.20e+01, 6.40e+01, 1.28e+02, 2.56e+02, 5.12e+02, 1.02e+03, 2.05e+03]])
и т.д.
Мне нужно численно интегрировать g
поверх x
, скажем from 0. to 100.
(в реальном случае интеграл не имеет точного решения), и в моем текущем подходе я должен lambdify
каждый элемент в g
и интегрировать это индивидуально. Я использую quad
для элементарного интегрирования, например:
ans = np.zeros( g_sympy.shape )
for (i,j), func_sympy in ndenumerate(g_sympy):
func = lambdify( (x), func_sympy)
ans[i,j] = quad( func, 0., 100. )
Здесь есть две проблемы: 1) lambdify используется много раз и 2) для цикла; и я считаю, что первое из них является узким местом, потому что матрица g_sympy
имеет не более 10000 терминов (что не очень важно для цикла for).
Как показано выше, lambdify
позволяет оценить всю матрицу, поэтому я подумал: "Есть ли способ интегрировать всю матрицу?"
scipy.integrate.quadrature
имеет параметр vec_func
, который дал мне надежду. Я ожидал чего-то вроде:
g_int = quadrature( g, x1, x2 )
чтобы получить полностью интегрированную матрицу, но при этом матрица ValueError:
должна быть двумерной
EDIT: Я пытаюсь сделать по-видимому, в Matlab, используя quadv
и уже обсуждался для SciPy
Здесь представлен реальный случай .
Чтобы запустить его, вам понадобится:
- NumPy
- SciPy
- Matplotlib
- SymPy
Просто запустите: "python curved_beam_mrs.py"
.
Вы увидите, что процедура выполняется медленно, главным образом из-за интеграции, указанной TODO
в файле curved_beam.py
.
Это будет намного медленнее, если вы удалите комментарий, указанный после TODO
в файле curved_beam_mrs.py
.
Матрица интегрируемых функций показана в файле print.txt
.
Спасибо!
Ответы
Ответ 1
Первый аргумент либо quad
, либо quadrature
должен быть вызываемым. Аргумент vec_func
quadrature
относится к тому, является ли аргумент этого вызываемого является (возможно, многомерным) вектором. Технически вы можете vectorize
самой quad
:
>>> 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]))
Но это просто эквивалентно явному циклу над элементами a
. В частности, это не даст вам каких-либо выигрышей в производительности, если это вам нужно. Итак, в целом, вопрос в том, почему и что именно вы пытаетесь достичь здесь.
Ответ 2
В реальном случае интеграл не имеет точного решения, вы имеете в виду особенности? Не могли бы вы быть более точными на нем, а также на размер матрицы, которую вы хотите интегрировать. Я должен признать, что sympy ужасно медленна, когда дело доходит до некоторых вещей (не уверен, что интеграция является частью этого, но я предпочитаю держаться подальше от sympy и придерживаться решения numpy). Вы хотите получить более элегантное решение, выполнив его с помощью матрицы или более быстрой?
-note: видимо я не могу добавить комментарий к вашему сообщению, чтобы спросить об этом, поэтому мне пришлось опубликовать это как ответ, может быть, это потому, что у меня нет достаточной репутации или так? -
edit: что-то вроде этого?
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))
заметив, что вы хотите интегрировать такие вещи, как sum (a * sin (bx + c) ^ n * cos (dx + e) ^ m), для разных коэффициентов для a, b, c, d, e, m, n, я предлагаю сделать все это аналитически. (для этого должна быть какая-то формула, поскольку вы можете просто переписать грех на сложные экспоненты
Еще одна вещь, которую я заметил при проверке этих функций немного лучше, заключается в том, что sin (a * x + pi/2) и sin (a * x + pi) и тому подобное могут быть переписаны в cos или sin способом который удаляет pi/2 или pi.
Также я вижу, что просто посмотрев на первый элемент в вашей матрице функций:
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
что также упрощает вычисления. Если у вас были формулы таким образом, который не включал массивный txtfile или так, удостоверьтесь, что самая общая формула вам нужна для интеграции, но я думаю, что это что-то вроде * sin ^ n (bx + c) * cos ^ m (dx + e), причем m и n равны 0 1 или 2, и эти вещи могут быть упрощены во что-то, что может быть аналитически интегрировано. Поэтому, если вы обнаружите самую общую аналитическую функцию, которую вы получили, вы можете легко сделать что-то вроде
f=lambda x: [[s1(x),s2(x)],[s3(x),s4(x)]]
res=f(x2)-f(x1)
где s1 (x) и т.д. - это просто аналитически интегрированные версии ваших функций?
(на самом деле не планируете перебирать весь ваш код, чтобы увидеть, что делает все остальное, но просто ли он интегрирует эти функции в txt файл от a до b или что-то в этом роде? или есть где-то что-то подобное квадрат каждой функции или что-то еще, что может испортить возможность сделать это аналитически?)
это должно упростить ваши интегралы, я думаю?
первый интеграл и: второй
hmm, эта вторая ссылка не работает, но вы получаете идею от первого, я думаю,
так как вам не нужны аналитические решения:
улучшение остается в избавлении от 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
приводит к:
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,
вывод: используйте numpy, ditch sympy (мой медленный метод numpy на самом деле быстрее в этом случае, потому что в этом примере я только пробовал его на одной функции sin, а не на ndarray из них, но точка канавки sympy по-прежнему остается при сравнении времени версии numpy более быстрого метода с одной из версий Sympy более быстрого метода)
Ответ 3
Векторизация трапециевидных и симпсонных интегральных правил. Trapezoidal просто копирует и вставляет из другого проекта, который использует пространство logspace вместо linspace, чтобы он мог использовать неравномерные сетки.
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)))
Изучение ловушкильных и симпсонов управляет кумулятивными относительными ошибками:
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. Обратите внимание, что оба трапециевидных правила используют 200 точек, в то время как симпсоны приурочены только к 100 на основе вышеуказанной конвергенции. Извините, у меня нет симпы:
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.'
Результаты:
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.
Что-то, что нужно указать в тайминги, - это ответ жены должен быть гораздо точнее. Я считаю, что все правильно, пожалуйста, сообщите мне, нужны ли изменения.
Если вы предоставляете функции и диапазон, которые вы будете использовать, я, вероятно, могу взломать что-то лучше для вашей системы. Кроме того, вы были бы заинтересованы в использовании дополнительных ядер/узлов?
Ответ 4
Я мог бы найти интересный способ сделать это за счет определения разных символов для матрицы 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)
с результатами:
[[ 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]]
и используя магию ipython %timeit vec_quad(G,0,100)
Я получил
1000 loops, best of 3: 527 µs per loop
Я думаю, что этот подход несколько более чист, несмотря на жонглирование символами.
Ответ 5
quadpy (мой проект) делает векторизованную квадратуру. Это
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)
дает
[[[ 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]]]
Тайминги:
%timeit quadpy.line_segment.integrate(f, [0.0, 100.0], gl)
10000 loops, best of 3: 65.1 µs per loop