【问题标题】:Is there a multiple integrator in Python providing both variable integration limits (like scipy) and high precision (like mpmath)?Python 中是否有多个积分器同时提供变量积分限制(如 scipy)和高精度(如 mpmath)?
【发布时间】:2020-08-31 08:01:19
【问题描述】:

我可以使用 scipy quad 和 nquad 进行涉及可变积分限制的四重积分。问题是,当无法达到所要求的容差时,使用的默认精度会引发错误。使用 mpmath 积分器,我可以通过设置 mp.dps = 任意来定义任意精度,但是我看不到限制是否以及如何像使用 nquad 一样变得可变。 Mpmath 还在 quadgl 中使用 Gauss-Legendre 方法提供了非常快速的执行,这是非常可取的,因为我的函数是平滑的,但是使用 scipy 完成四个集成需要大量时间。请帮忙。 以下只是一个没有达到我目标的简单功能:

from datetime import datetime
import scipy
from scipy.special import jn, jn_zeros
import numpy as np
import matplotlib.pyplot as plt
from mpmath import *
from mpmath import mp
from numpy import *
from scipy.optimize import *

# Set the precision
mp.dps = 15#; mp.pretty = True

# Setup shortcuts, so we can just write exp() instead of mp.exp(), etc.
F = mp.mpf
exp = mp.exp
sin = mp.sin
cos = mp.cos
asin = mp.asin
acos = mp.acos
sqrt = mp.sqrt
pi = mp.pi
tan = mp.tan

start = datetime.now()
print(start)

#optionsy={'limit':100, 'epsabs':1.49e-1, 'epsrel':1.49e-01}
#optionsx={'limit':100, 'epsabs':1.49e-1, 'epsrel':1.49e-01}

def f(x,y,z):
    return 2*sqrt(1-x**2) + y**2.0 + z

def rangex(y,z):
    return [-1,1]

def rangey(z):
    return [1,2]

def rangez():
    return [2,3]


def result():
    return quadgl(f, rangex, rangey, rangez)

"""
#The below works:

def result():
    return quadgl(f, [-1,1], [1,2], [2,3])
"""

print(result())

end = datetime.now()
print(end-start)

【问题讨论】:

  • 你不应该递归地分割积分吗?从mpmath.org/doc/current/calculus/integration.html,查看文本和示例“对于非矩形区域,可以递归调用 quad()。”
  • 谢谢,是的,我可以使用 mpmath 递归地进行最多三个具有可变限制和任意精度的积分。然后我可以使用 scipy 进行第四个积分(最内层),但是默认精度的误差会传播到外部三个积分。
  • 好吧,看来您已经知道如何处理任意限制了。你可以提出你自己的答案。我的建议是用你的新代码问另一个问题,并具体说明精度损失。不要试图一次问几个问题。我去看看
  • @Severin 再次感谢。但是,我没有成功插入“第四个”最里面的 scipy 积分,但没有错误。即使这也不会比使用四重 scipy 集成有所改进。此外,mpmath 即使使用简单的函数超过三重积分也需要很长时间。我在输出端的实际物理参数预计相对而言变化小于 e-12。我认为内部积分应该提供比这更好的精度。在极限情况下,这需要很多天,除非我大幅降低相对容差。真正的问题。
  • 不知道能不能用mpmath设置epsabs和epsrel。理想情况下,我们需要这个选项以及提高 scipy 和/或 mpmath 的精度。执行时间是最后一个障碍。

标签: python numpy scipy mpmath


【解决方案1】:

好吧,让我回答一下,很难将代码放入 cmets

MP 数学的简单优化就是遵循简单的规则:

  1. y2.0 非常昂贵(log、exp、...),替换为 y*y
  2. y2 还是很贵,换成 y*y
  3. 乘法比求和贵很多,将 x*y + y**2.0 替换为 (x+y)*y
  4. 除法比乘法更昂贵,将 y/4 替换为 0.25*y

代码,Win 10 x64,Python 3.8

def f3():
    def f2(x):
        def f1(x,y):
            def f(x,y,z):
                return 1.0 + (x+y)*y + 3.0*z
            return mpmath.quadgl(f, [-1.0, 1], [1.2*x, 1.0], [0.25*y, x*x])
        return mpmath.quadgl(f1, [-1, 1.0], [1.2*x, 1.0])
    return mpmath.quadgl(f2, [-1.0, 1.0])

在我的电脑上从 12.9 秒到 10.6 秒,大约 20% 的折扣

【讨论】:

  • 认为不适合使用完整代码与社区互动。很高兴在下面这样做。我在其他计算机上使用:“基于 x64 的 PC,MS Windows 7 Home Premium,处理器:Intel(R) Core(TM) 3612QM CPU @2.10 GHz 高达 3.0 GHz,具有涡轮增压,4 核,8 个逻辑处理器,8 GB内存”。尝试运行我总结了术语的并行多核实例。不确定在减去术语时这是否有帮助,不确定拆分代码如何影响精度和最终结果。非常感谢您的帮助。
  • 当我使用整数而不是小数表达式(比如 2 而不是 2.0)时,我遇到了很大的错误,尤其是在指数中。
  • @gerryD 我去看看我能做什么
【解决方案2】:

下面是一个简单的例子,说明我如何只用 mpmath 进行三重集成。这并没有解决四个集成的高精度问题。无论如何,执行时间是一个更大的问题。欢迎任何帮助。

from datetime import datetime
import scipy
import numpy as np
from mpmath import *
from mpmath import mp
from numpy import *

# Set the precision
mp.dps = 20#; mp.pretty = True

# Setup shortcuts, so we can just write exp() instead of mp.exp(), etc.
F = mp.mpf
exp = mp.exp
sin = mp.sin
cos = mp.cos
asin = mp.asin
acos = mp.acos
sqrt = mp.sqrt
pi = mp.pi
tan = mp.tan

start = datetime.now()
print('start: ',start)

def f3():
    def f2(x):
        def f1(x,y):
            def f(x,y,z):
                return 1.0 + x*y + y**2.0 + 3.0*z
            return quadgl(f, [-1.0, 1], [1.2*x, 1.0], [y/4, x**2.0])
        return quadgl(f1, [-1, 1.0], [1.2*x, 1.0])
    return quadgl(f2, [-1.0, 1.0])

print('result =', f3())

end = datetime.now()
print('duration in mins:',end-start)

#start:  2020-08-19 17:05:06.984375
#result = 5.0122222222222221749
#duration: 0:01:35.275956

此外,即使使用最简单的函数,尝试将一个(第一个)scipy 积分和一个三重 mpmath 积分器组合起来似乎也不会产生超过 24 小时的任何输出。下面的代码有什么问题?

from datetime import datetime
import scipy
import numpy as np
from mpmath import *
from mpmath import mp
from numpy import *

from scipy import integrate

# Set the precision
mp.dps = 15#; mp.pretty = True

# Setup shortcuts, so we can just write exp() instead of mp.exp(), etc.
F = mp.mpf
exp = mp.exp
sin = mp.sin
cos = mp.cos
asin = mp.asin
acos = mp.acos
sqrt = mp.sqrt
pi = mp.pi
tan = mp.tan

start = datetime.now()
print('start: ',start)

#Function to be integrated
def f(x,y,z,w):
    return 1.0 + x + y + z + w 
    
#Scipy integration:FIRST INTEGRAL
def f0(x,y,z):
    return integrate.quad(f, -20, 10, args=(x,y,z), epsabs=1.49e-12, epsrel=1.4e-8)[0]


#Mpmath integrator of function f0(x,y,z): THREE OUTER INTEGRALS
def f3():
    def f2(x):
        def f1(x,y):
            return quadgl(f0, [-1.0, 1], [-2, x], [-10, y])
        return quadgl(f1, [-1, 1.0], [-2, x])
    return quadgl(f2, [-1.0, 1.0])

print('result =', f3())

end = datetime.now()
print('duration:', end-start)

以下是提出原始问题的完整代码。其中包含使用scipy进行四种集成:


# Imports
from datetime import datetime
import scipy.integrate as si
import scipy
from scipy.special import jn, jn_zeros
from scipy.integrate import quad
from scipy.integrate import nquad
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import fixed_quad
from scipy.integrate import quadrature
from mpmath import mp

from numpy import *
from scipy.optimize import *

# Set the precision
mp.dps = 30

# Setup shortcuts, so we can just write exp() instead of mp.exp(), etc.
F = mp.mpf
exp = mp.exp
sin = mp.sin
cos = mp.cos
asin = mp.asin
acos = mp.acos
sqrt = mp.sqrt
pi = mp.pi
tan = mp.tan

start = datetime.now()
print(start)

R1 = F(6.37100000000000e6)
k1 = F(8.56677817058932e-8)
R2 = F(1.0)
k2 = F(5.45789437248245e-01)
r = F(12742000.0)

#Replace computed initial constants with values presuming is is faster, like below:
#a2 = R2/r
#print(a2) 
a2 = F(0.0000000784806152880238581070475592529)

def u1(phi2):
    return r*cos(phi2)-r*sqrt(a2**2.0-(sin(phi2))**2.0)
def u2(phi2):
    return r*cos(phi2)+r*sqrt(a2**2.0-(sin(phi2))**2.0)

def om(u,phi2):
    return u-r*cos(phi2)
def mp2(phi2):
    return r*sin(phi2)

def a1(u):
    return R1/u

optionsx={'limit':100, 'epsabs':1.49e-14, 'epsrel':1.49e-11}
optionsy={'limit':100, 'epsabs':1.49e-14, 'epsrel':1.49e-10}

#---- in direction u
def a1b1_u(x,y,u):
    return 2.0*u*sqrt(a1(u)**2.0-(sin(y))**2.0)

def oa2_u(x,y,u,phi2):
    return (mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*cos(y) 
                    - sqrt((mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*(cos(y)))**2.0 
                           + R2**2.0-om(u,phi2)**2.0-mp2(phi2)**2.0))

def ob2_u(x,y,u,phi2):
    return (mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*cos(y) 
                    + sqrt((mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*(cos(y)))**2.0 
                           + R2**2.0-om(u,phi2)**2.0-mp2(phi2)**2.0))

def func1_u(x,y,u,phi2):
    return (-exp(-k1*a1b1_u(x,y,u)-k2*ob2_u(x,y,u,phi2))+exp(+k2*oa2_u(x,y,u,phi2)))*sin(y)*cos(y)
 
#--------joint_coaxial integration: u1
def fg_u1(u,phi2):
    return nquad(func1_u, [[-pi, pi], [0, asin(a1(u))]], args=(u,phi2), opts=[optionsx,optionsy])[0]

#Constants to be used for normalization at the end or in the interim inegrals if this helps adjust values for speed of execution
piA1 = pi*(R1**2.0-1.0/(2.0*k1**2.0)+exp(-2.0*k1*R1)*(2.0*k1*R1+1.0)/(2.0*k1**2.0))
piA2 = pi*(R2**2.0-1.0/(2.0*k2**2.0)+exp(-2.0*k2*R2)*(2.0*k2*R2+1.0)/(2.0*k2**2.0))

#----THIRD integral of u1
def third_u1(u,phi2):
    return fg_u1(u,phi2)*u**2.0
def third_u1_I(phi2):
    return quad(third_u1, u1(phi2), u2(phi2), args = (phi2), epsabs=1.49e-20, epsrel=1.49e-09)[0]
    
#----FOURTH integral of u1
def fourth_u1(phi2):
    return third_u1_I(phi2)*sin(phi2)*cos(phi2)
def force_u1():
    return quad(fourth_u1, 0.0, asin(a2), args = (), epsabs=1.49e-20, epsrel=1.49e-08)[0]


force_u1 = force_u1()*r**2.0*2.0*pi*k2/piA1/piA2

print('r = ', r, 'force_u1 =', force_u1)

end = datetime.now()
print(end)

args = {
            'p':r,
            'q':force_u1,
            'r':start,
            's':end
        }   

#to txt file
f=open('Sphere-test-force-u-joint.txt', 'a')

f.write('\n{p},{q},{r},{s}'.format(**args))
#f.flush()
f.close()

我有兴趣将 epsrel 设置得足够低,具体取决于具体情况。 epsabs 通常是先验未知的,因此我知道我应该将其设置得很低以避免它占用输出,在这种情况下它会引入计算工件。当我降低它时,会发出错误警告,指出舍入误差很大,并且可能会低估总误差以达到所需的容差。

【讨论】:

  • 撞了。您能否添加完整表达式(所有 4 个积分)的代码来计算?
【解决方案3】:

虽然问题不在于速度,但后者与在查询精度和公差之前执行四重积分密切相关。为了测试速度,我设置(增加)所有四个 epsrel=1e-02,这将原始代码的时间减少到 2:14(小时)。然后我简化了每个 Severin 的权力并实现了一些 memoization。这些将时间累积减少到 1:29(小时)。此处提供了已编辑的代码行:

from memoization import cached

@cached(ttl=10)
def u1(phi2):
    return r*cos(phi2)-r*sqrt(a2*a2-sin(phi2)*sin(phi2))
@cached(ttl=10)
def u2(phi2):
    return r*cos(phi2)+r*sqrt(a2*a2-sin(phi2)*sin(phi2))
@cached(ttl=10)
def om(u,phi2):
    return u-r*cos(phi2)
@cached(ttl=10)
def mp2(phi2):
    return r*sin(phi2)
@cached(ttl=10)
def a1(u):
    return R1/u

optionsx={'limit':100, 'epsabs':1.49e-14, 'epsrel':1.49e-02}
optionsy={'limit':100, 'epsabs':1.49e-14, 'epsrel':1.49e-02}

def a1b1_u(x,y,u):
    return 2.0*u*sqrt(a1(u)*a1(u)-sin(y)*sin(y))

def oa2_u(x,y,u,phi2):
    return (mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*cos(y) 
                    - sqrt((mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*(cos(y)))**2.0 
                           + 1.0-om(u,phi2)*om(u,phi2)-mp2(phi2)*mp2(phi2)))

def ob2_u(x,y,u,phi2):
    return (mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*cos(y) 
                    + sqrt((mp2(phi2)*sin(y)*cos(x)+om(u,phi2)*(cos(y)))**2.0 
                           + 1.0-om(u,phi2)*om(u,phi2)-mp2(phi2)*mp2(phi2)))

def third_u1(u,phi2):
    return fg_u1(u,phi2)*u*u

def third_u1_I(phi2):
    return quad(third_u1, u1(phi2), u2(phi2), args = (phi2), epsabs=1.49e-20, epsrel=1.49e-02)[0]
    
def force_u1():
    return quad(fourth_u1, 0.0, asin(a2), args = (), epsabs=1.49e-20, epsrel=1.49e-02)[0]

但是,输出是由于引入的容差不足而导致的伪影。我可以逐步将 epsrel 设置为较低的值,并查看结果是否在可用的 scipy 精度下实时收敛到实际值。希望这能更好地说明原始问题。

【讨论】:

  • 重要提示:在逐步增加 epsrel 的后续行动中,我已经重现了“IntegrationWarning:检测到舍入错误的发生,这会阻止达到要求的容差。错误可能被低估了”。这发生在 epsrel=1.49e-03, 1.49e-06, 1.49e-03, 1.49e-05 从最里面到最外面的积分,而事先设置 epsrel=1.49e-03, 1.49e-05, 1.49e- 03、1.49e-05 17:45(小时)后正常完成。失败的运行是在一夜之间开始的,并且没有记录发生时间。 @Severin
  • 注意:重复失败的测试并在大约 50 分钟后引发了 IntegrationWarning。看起来 Scipy 进展不错,但它的默认精度需要用户控制。有可能这样做吗? (每个原始问题)
最近更新 更多