Wolfram Alpha和scipy.integrate.quad给出了相同积分的不同答案

时间:2015-08-30 23:38:16

标签: python math numpy scipy wolframalpha

考虑以下功能:

import numpy as np
from scipy.special import erf

def my_func(x):
    return np.exp(x ** 2) * (1 + erf(x))

当我使用-14 -4函数评估此函数从scipyquad的积分时,得到以下结果:

In [3]: from scipy import integrate

In [4]: integrate.quad(my_func, -14, -4)
/usr/local/lib/python2.7/dist-packages/scipy/integrate/quadpack.py:289: UserWarning: The maximum number     of subdivisions (50) has been achieved.
  If increasing the limit yields no improvement it is advised to analyze 
  the integrand in order to determine the difficulties.  If the position of a 
  local difficulty can be determined (singularity, discontinuity) one will 
  probably gain from splitting up the interval and calling the integrator 
  on the subranges.  Perhaps a special-purpose integrator should be used.
  warnings.warn(msg)
Out[4]: (0.21896647054443383, 0.00014334175850538866)

即约0.22

然而,当我将此积分提交给Wolfram Alpha时,我得到了一个非常不同的结果:

-5.29326 X 10 ^ 69.

这笔交易是什么?我猜测这与警告scipy给我的警告有关。在python中评估此积分的最佳方法是什么?

注意:增加limit会更改警告但保持scipy结果不变:

In [5]: integrate.quad(my_func, -14, -4, limit=10000)
/usr/local/lib/python2.7/dist-packages/scipy/integrate/quadpack.py:289: UserWarning: The occurrence of roundoff error is detected, which prevents 
  the requested tolerance from being achieved.  The error may be 
  underestimated.
  warnings.warn(msg)
Out[5]: (0.21894780966717864, 1.989164129832358e-05)

1 个答案:

答案 0 :(得分:8)

TL; DR:被积函数相当于erfcx(-x)scipy.special.erfcx erfcx的实现负责数值问题:

In [10]: from scipy.integrate import quad

In [11]: from scipy.special import erfcx

In [12]: quad(lambda x: erfcx(-x), -14, -4)
Out[12]: (0.6990732491815446, 1.4463494884581349e-13)

In [13]: quad(lambda x: erfcx(-x), -150, -50)
Out[13]: (0.6197754761443759, 4.165648376274775e-14)

您可以通过更改积分参数和限制的符号来避免lambda表达式:

In [14]: quad(erfcx, 4, 14)
Out[14]: (0.6990732491815446, 1.4463494884581349e-13)

问题是对1 + erf(x)的负值x进行数值评估。当x减少时,erf(x)接近-1。当您添加1时,您会得到catastrophic loss of precision,而对于足够负x(具体为x< -5.87),1 + erf(x)在数字上为0。

请注意,Wolfram Alpha的默认行为会遇到同样的问题。我不得不点击“更多数字”两次才能得到合理的答案。

修复是重新构建你的功能。您可以将1+erf(x)表达为2*ndtr(x*sqrt(2)),其中ndtr是正常的累积分布函数,可从scipy.special.ndtr获得(例如,请参阅https://en.wikipedia.org/wiki/Error_function)。这是您的函数的替代版本,以及将其与scipy.integrate.quad集成的结果:

In [133]: def func2(x):
   .....:     return np.exp(x**2) * 2 * ndtr(x * np.sqrt(2))
   .....: 

In [134]: my_func(-5)
Out[134]: 0.1107029852258767

In [135]: func2(-5)
Out[135]: 0.11070463773306743

In [136]: integrate.quad(func2, -14, -4)
Out[136]: (0.6990732491815298, 1.4469372263470424e-13)

点击“更多数字”两次后,Wolfram Alpha的答案是0.6990732491815446...

当您使用数字稳定版本时,这就是函数的图形:

Plot of the integrand

为避免大幅度参数的上溢或下溢,您可以在日志空间中进行部分计算:

from scipy.special import log_ndtr

def func3(x):
    t = x**2 + np.log(2) + log_ndtr(x * np.sqrt(2))
    y = np.exp(t)
    return y

E.g。

In [20]: quad(func3, -150, -50)
Out[20]: (0.6197754761435517, 4.6850379059597266e-14)

(看起来@ali_m在新问题中打败了我:Tricking numpy/python into representing very large and very small numbers。)

最后,正如Simon Byrne在Tricking numpy/python into representing very large and very small numbers的回答中指出的那样,要整合的函数可以表示为erfcx(-x),其中erfcx是缩放的互补误差函数。它以scipy.special.erfcx提供。

例如,

In [10]: from scipy.integrate import quad

In [11]: from scipy.special import erfcx

In [12]: quad(lambda x: erfcx(-x), -14, -4)
Out[12]: (0.6990732491815446, 1.4463494884581349e-13)

In [13]: quad(lambda x: erfcx(-x), -150, -50)
Out[13]: (0.6197754761443759, 4.165648376274775e-14)