Python 大数字上的scipy.integrate.quad精度

Python 大数字上的scipy.integrate.quad精度,python,numpy,scipy,calculus,Python,Numpy,Scipy,Calculus,我试图通过scipy.integrate.quad()计算这样一个积分(实际上是指数分布的cdf及其pdf): 结果如下: (1.0, 3.5807346295637055e-11) (0.0, 0.0) (3.881683817604194e-22, 7.717972744764185e-22) (1.0, 1.6059202674761255e-14) 尽管使用np.inf解决了这个问题,但所有使用较大积分上限的尝试都会产生错误的答案 本文讨论了类似的情况 我应该怎么做才能避免在积分其他密

我试图通过
scipy.integrate.quad()
计算这样一个积分(实际上是指数分布的cdf及其pdf):

结果如下:

(1.0, 3.5807346295637055e-11)
(0.0, 0.0)
(3.881683817604194e-22, 7.717972744764185e-22)
(1.0, 1.6059202674761255e-14)
尽管使用
np.inf
解决了这个问题,但所有使用较大积分上限的尝试都会产生错误的答案

本文讨论了类似的情况


我应该怎么做才能避免在积分其他密度函数时出现这样的错误?

我认为这个问题是由于
np.exp(-x)
随着
x
的增加而迅速变小,由于数值精度有限,导致计算结果为零。例如,即使对于
x
小到
x=10**2*
np.exp(-x)
的计算结果为
3.72007597602e-44
,而
x
顺序值
10**3
或以上的结果为
0

我不知道
quad
的具体实现细节,但它可能会在给定的集成范围内对要集成的函数执行某种类型的采样。对于较大的积分上限,
np.exp(-x)
的大多数样本计算为零,因此积分值被低估。(请注意,在这些情况下,
quad
提供的绝对误差与积分值的阶数相同,积分值表示后者不可靠。)

避免此问题的一种方法是将积分上界限制为数值函数变得非常小的值(因此,对积分值的贡献很小)。从您的代码snipet中,
10**4
的值似乎是一个不错的选择,但是,
10**2
的值也会导致对积分的精确计算

另一种避免数值精度问题的方法是使用一个以任意精度算法(如
mpmath
)执行计算的模块。例如,对于
x=10**5
mpmath
计算
exp(-x)
如下(使用本机
mpmath
指数函数)

3.56294956530937e-43430

请注意此值有多小。使用标准硬件数字精度(由
numpy
使用),此值变为
0

mpmath
提供了一个积分函数(
mp.quad
),它可以为积分上限的任意值提供精确的积分估计

import mpmath as mp

print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, mp.inf]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**13]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**8]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**5]))
我们还可以通过将精度提高到,比如说,
50
小数点(从
15
这是标准精度)来获得更精确的估计值

一般来说,获得该精度的成本是增加计算时间


附言:不用说,如果你能够首先用解析的方法计算积分(例如,借助于
Sympy
),你就可以忘记以上所有内容

使用
points
参数告诉算法函数的大致支持位置:

import numpy as np
from scipy.integrate import quad

def g(x):
    return .5 * np.exp(-.5 * x)

print quad(g, a=0., b=10**3, points=[1, 100])
print quad(g, a=0., b=10**6, points=[1, 100])
print quad(g, a=0., b=10**9, points=[1, 100])
print quad(g, a=0., b=10**12, points=[1, 100])

mpmath也不是绝对正确的:
mp.quad(lambda x:.5*mp.exp(-0.5*x),[0,10**20])
->
2.20502636520112e-56
。关键是,如果没有一些“平滑”条件,函数的数值积分是不可能的——函数在积分区间中不能有太尖锐的“尖峰”。当积分间隔非常大时,函数
exp(-x/2)
非常“尖峰”,这会导致问题。@pv。的确,谢谢你的评论。但是,如果您将精度提高到足够的程度,则不存在此类问题。例如,在调用
mp.mp.dps=100
之前,尝试
mp.quad
增加精度只是向上推上限,尝试
10**120
。它还增加了计算的成本,在这种情况下,这是不必要的。问题不在于函数值太小,以至于低于浮点范围,而是函数在缩放到积分间隔时非常尖锐,这误导了积分算法的误差估计。@Stelios与
scipy
兼容,
pandas
和其他流行软件包?来自Mathematica世界,我习惯于先进行符号简化,然后与机器精度集成,然后提高工作精度。我想这里的哲学是相似的。我喜欢同样的思想仍然大致适用:-),它解决了我的问题,就像一个字符,将这些案例的输出与
np.quad(g,a=0,b=100)
的输出进行比较,似乎这种方法本质上将上限设置为100,而不考虑实际用户输入。当然,这对于OP来说可能很好,但事实并非如此。积分器确实会对x>100以外的函数进行采样,但这当然是一个基本事实,即该部分积分的贡献非常小。@pv在阅读了quad docstring之后,我不明白您的建议有何帮助。第1点和第100点不是分数discontinuity@DenisKorzhenkov:它强制积分器在这些点对函数进行采样。否则,对于较大的积分间隔,它将采样点
a+eps*(b-a)
,其中eps是一些较小的数字——但如果
b-a
非常大,它将错过接近x=0的峰值。
import mpmath as mp

print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, mp.inf]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**13]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**8]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**5]))
1.0
0.999999650469474
0.999999999996516
0.999999999999997
mp.mp.dps = 50; 

print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, mp.inf]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**13]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**8]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**5]))
1.0
0.99999999999999999999999999999999999999999829880262
0.99999999999999999999999999999999999999999999997463
0.99999999999999999999999999999999999999999999999998
import numpy as np
from scipy.integrate import quad

def g(x):
    return .5 * np.exp(-.5 * x)

print quad(g, a=0., b=10**3, points=[1, 100])
print quad(g, a=0., b=10**6, points=[1, 100])
print quad(g, a=0., b=10**9, points=[1, 100])
print quad(g, a=0., b=10**12, points=[1, 100])