使用积分时计算错误

Wrong calculation when using integrals

对于上下文:我有一些用 Mathcad 做的旧方程,我想用 sympy 重写,所以我确实得到了我应该得到的结果。

但是,考虑到公式:

uu, vv, rr = sp.symbols("u v r")
h2 = sp.Integral(2*sp.exp( (sp.I*uu*rr**2) /2 )*sp.besselj(0,vv*rr)*rr,(rr,0,1))

如果我将其中一个变量(uu 或 vv)代入 0,则计算正确。

f=sp.lambdify((uu),(sp.Abs(h2.subs(vv,0).doit()))**2)
g=sp.lambdify((vv),(sp.Abs(h2.subs(uu,0).doit()))**2)

但是,如果我想使用这两个变量制作 3D 图形,结果是不正确的。

f2 = sp.lambdify((uu,vv),(sp.Abs(h2.doit()))**2)

f2(5,0) 应该得到 0.57,对于 f(5),但它没有

所以我用一些循环解决了这个问题,而不是同时对两个变量进行 lambdifying :

z_list = []
for j in yy:
    f3=sp.lambdify([uu],sp.re((sp.Abs(h2.subs(vv,j).doit())**2)))
    for i in xx:
        z_list.append(f3(i))

这需要更长的时间来计算,但结果很好。 关于积分或 lambdify 或两者的组合,我是否遗漏了什么?

此外,我需要计算的下一个公式是前面 h2 的积分:

I6 = sp.Integral(vv*sp.Abs(h2)**4,(vv,0,10))

从 11-01 编辑

感谢 Oscar Benjamin 的回答,计算中的问题来自 lambdify 的返回值,returns 只有实部。

我测试了三种情况:

f3=sp.lambdify((uu),h2.subs(vv,0).doit())
f4=sp.lambdify((uu),h2.subs(vv,0).evalf())
f5=sp.lambdify((uu),h2.subs(vv,0))

只有 f3 returns 具有实部和虚部的值(这解释了我之前遇到的其他计算问题)。

(0.23938885764158258+0.7204574462187735j)
0.23938885764158258
0.23938885764158258

可以用其他模块调用 lambdify 来防止这种情况发生吗?

doit 积分函数预期用于以符号方式计算积分:

In [47]: f = Integral(exp(-x**2), (x, 0, oo))

In [48]: f
Out[48]: 
∞        
⌠        
⎮    2   
⎮  -x    
⎮ ℯ    dx
⌡        
0        

In [49]: f.doit()
Out[49]: 
√π
──
2

In [50]: _.evalf()
Out[50]: 0.886226925452758

当它起作用时它很好,因为一个不涉及整数符号的显式表达式可以更有效地进行数值计算。您可以使用 evalf 进行多精度计算的评估,或者您可以使用 lambdify 并让 numpy/scipy 等在机器精度浮点计算中更快地进行计算。

然而,并不是所有的积分都可以用符号计算,所以 doit 有时会 return 相同的积分:

In [53]: h2 = Integral(2*r*exp(I*r**2*u/2)*besselj(0, r*v), (r, 0, 1))

In [54]: h2
Out[54]: 
1                               
⌠                               
⎮         2                     
⎮      ⅈ⋅r ⋅u                   
⎮      ──────                   
⎮        2                      
⎮ 2⋅r⋅ℯ      ⋅besselj(0, r⋅v) dr
⌡                               
0                               

In [55]: h2.doit() # slow
Out[55]: 
  1                             
  ⌠                             
  ⎮       2                     
  ⎮    ⅈ⋅r ⋅u                   
  ⎮    ──────                   
  ⎮      2                      
2⋅⎮ r⋅ℯ      ⋅besselj(0, r⋅v) dr
  ⌡                             
  0  

这里我们看到积分没有求值。有时,如果积分具有积分变量以外的符号,我们可以通过代入值来获得符号结果。如果将 uv 设置为零,就会发生这种情况:

In [59]: h2.subs({u:0, v:5}).doit()
Out[59]: 
2⋅besselj(1, 5)
───────────────
       5       

In [60]: h2.subs({u:5, v:0}).doit()
Out[60]: 
       5⋅ⅈ      
       ───      
        2       
  2⋅ⅈ⋅ℯ      2⋅ⅈ
- ──────── + ───
     5        5 

因为被积函数变得更简单,所以可以用符号来计算积分。但是,在这种情况下,如果 uv 都不为零,则无法对积分进行符号计算:

In [61]: h2.subs({uu:2,vv:3}).doit()
Out[61]: 
  1                           
  ⌠                           
  ⎮       2                   
  ⎮    ⅈ⋅r                    
2⋅⎮ r⋅ℯ    ⋅besselj(0, 3⋅r) dr
  ⌡                           
  0 

我希望这个特定的积分没有直接的解析公式。我检查了 Wolfram Alpha,它也没有找到符号结果(只有数字结果):

https://www.wolframalpha.com/input/?i=Integral%282*r*exp%28I*r**2%29*besselj%280%2C+3*r%29%2C+%28r%2C+0%2C+1%29%29

不可能从积分中找到解析结果是符号学的一个基本事实:不存在可识别的数学函数来表达大多数可能的积分结果。即使在许多情况下,函数确实存在,但并不存在可以找到它们的算法。

所以我们接受符号集成是不可能的,这意味着调用 doit 没有意义,这是迄今为止您使用的代码中最慢的操作。 doit 的目的是获得一个符号结果,然后我们可以更有效地使用它来处理 uv 的许多不同值,但是您却为每个调用 doit uv 的可能值,但每次都没有 return 有用的东西。

如果无法进行符号积分,则需要进行数值积分。对于数值积分,我们首先需要替换 uv 的值,然后我们可以使用数值积分算法。由于我们没有积分的符号结果,因此对于每个可能的参数值,数值积分算法需要 运行 一次。在 SymPy 中有两种直接的方法可以做到这一点:.evalflambdify(请注意 N(obj)obj.evalf()obj.n() 相同)。

使用 .evalf 速度较慢,因为它使用多精度算法,但这也使它更准确。 returned 结果中的每个数字都可以被合理地认为是正确的(除非有错误...),如果需要,您还可以计算更多数字:

In [64]: %time h2.subs({uu:2,vv:3}).evalf()
CPU times: user 1.35 s, sys: 0 ns, total: 1.35 s
Wall time: 1.35 s
Out[64]: 0.236305280388988 + 0.0146270775320418⋅ⅈ

In [65]: %time h2.subs({uu:2,vv:3}).evalf(50)
CPU times: user 3.64 s, sys: 8 ms, total: 3.65 s
Wall time: 3.65 s
Out[65]: 
0.23630528038898808262356997039156552180149071451802 + 0.014627077532041813385103750863443465733130
09416169⋅ⅈ

这比调用 doit 快,但与顶级标准机器精度数字代码相比仍然慢。虽然它非常准确。

为了加快评估速度,您可以使用 lambdify:

In [66]: f2 = lambdify((uu, vv), h2)

In [67]: %time f2(2, 3)
/home/oscar/current/sympy/sympy.git/38venv/lib/python3.8/site-packages/scipy/integrate/quadpack.py:463: ComplexWarning: Casting complex values to real discards the imaginary part
  return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 438 µs
Out[67]: 0.2363052803889881

这现在 return 非常快并且给出了准确的结果,除了它只是 return 的实部。我猜 scipy 例程只处理实数积分。我们可以分开做实部和虚部,例如虚部是:

In [91]: %time f2(2, 3)
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 387 µs
Out[91]: 0.01462707753204179

在 lambdify 表达式时使用模块“mpmath”return 完整的复数,而不仅仅是它的实部。

f3 = sp.lambdify([uu,vv],h2,modules='mpmath')
f3(5,0)
mpc(real='0.2393888576415826', imag='0.7204574462187735')