Python 实现二维数值积分的计算速度更快的方法是什么?

Python 实现二维数值积分的计算速度更快的方法是什么?,python,numpy,scipy,integration,numerical-integration,Python,Numpy,Scipy,Integration,Numerical Integration,我对做二维数值积分感兴趣。现在我使用的是scipy.integrate.dblquad,但速度非常慢。请参阅下面的代码。我需要的是用完全不同的参数来计算这个积分100次。因此,我希望使处理过程尽可能快速高效。代码是: import numpy as np from scipy import integrate from scipy.special import erf from scipy.special import j0 import time q = np.linspace(0.03,

我对做二维数值积分感兴趣。现在我使用的是
scipy.integrate.dblquad
,但速度非常慢。请参阅下面的代码。我需要的是用完全不同的参数来计算这个积分100次。因此,我希望使处理过程尽可能快速高效。代码是:

import numpy as np
from scipy import integrate
from scipy.special import erf
from scipy.special import j0
import time

q = np.linspace(0.03, 1.0, 1000)

start = time.time()

def f(q, z, t):
    return t * 0.5 * (erf((t - z) / 3) - 1) * j0(q * t) * (1 / (np.sqrt(2 * np.pi) * 2)) * np.exp(
        -0.5 * ((z - 40) / 2) ** 2)


y = np.empty([len(q)])
for n in range(len(q)):
    y[n] = integrate.dblquad(lambda t, z: f(q[n], z, t), 0, 50, lambda z: 10, lambda z: 60)[0]

end = time.time()
print(end - start)

花费的时间是

212.96751403808594

这太过分了。请提出一个更好的方法来实现我想做的事情。在来这里之前,我试着做了一些搜索,但没有找到任何解决方案。我读过
quadpy
可以更好更快地完成这项工作,但我不知道如何实现。请帮忙

通常,通过矩阵运算进行求和要比使用scipy.integrate.quad(或dblquad)快得多。您可以重写f(q,z,t)以获取q,z和t向量,并使用np.tensordot返回f值的三维数组,然后将面积元素(dtdz)与函数值相乘,并使用np.sum求和。如果面积元素不是常量,则必须创建面积元素数组,并使用np.einsum将积分限制考虑在内。在汇总之前,可以使用掩码数组将积分限制之外的函数值掩码。请注意,np.einsum忽略了掩码,因此如果使用einsum,可以使用np.where将积分限制之外的函数值设置为零。示例(具有恒定面积元素和简单积分限制):

这在我的2012 macbook pro(2.5GHz i5)上花费了18.5秒,dA=0.04。这样做还可以让您轻松地在精度和效率之间进行选择,并将dA设置为一个在您了解函数行为时有意义的值

然而,值得注意的是,如果你想要更多的点,你必须分割你的积分,否则你会冒着内存最大化的风险(1000 x 1000 x 1000)双倍需要8GB的ram。因此,如果您正在以高优先级进行非常大的集成,那么在运行之前快速检查所需的内存是值得的。

您可以使用Numba或低级别的可调用内存 几乎就是你的例子

我只是将函数直接传递给scipy.integrate.dblquad,而不是使用lambdas生成函数的方法

import numpy as np
from scipy import integrate
from scipy.special import erf
from scipy.special import j0
import time

q = np.linspace(0.03, 1.0, 1000)

start = time.time()

def f(t, z, q):
    return t * 0.5 * (erf((t - z) / 3) - 1) * j0(q * t) * (1 / (np.sqrt(2 * np.pi) * 2)) * np.exp(
        -0.5 * ((z - 40) / 2) ** 2)

def lower_inner(z):
    return 10.

def upper_inner(z):
    return 60.


y = np.empty(len(q))
for n in range(len(q)):
    y[n] = integrate.dblquad(f, 0, 50, lower_inner, upper_inner,args=(q[n],))[0]

end = time.time()
print(end - start)
#143.73969149589539
这已经快了一点点(143比151s),但唯一的用途是有一个简单的例子来优化

只需使用Numba编译函数

要让它运行,您需要额外的和。numba scipy的目的是提供来自
scipy.special
的包装函数

import numpy as np
from scipy import integrate
from scipy.special import erf
from scipy.special import j0
import time
import numba as nb

q = np.linspace(0.03, 1.0, 1000)

start = time.time()

#error_model="numpy" -> Don't check for division by zero
@nb.njit(error_model="numpy",fastmath=True)
def f(t, z, q):
    return t * 0.5 * (erf((t - z) / 3) - 1) * j0(q * t) * (1 / (np.sqrt(2 * np.pi) * 2)) * np.exp(
        -0.5 * ((z - 40) / 2) ** 2)

def lower_inner(z):
    return 10.

def upper_inner(z):
    return 60.


y = np.empty(len(q))
for n in range(len(q)):
    y[n] = integrate.dblquad(f, 0, 50, lower_inner, upper_inner,args=(q[n],))[0]

end = time.time()
print(end - start)
#8.636585235595703
使用低级别可调用

scipy.integrate
函数还提供了传递C回调函数而不是Python函数的可能性。这些函数可以用C、Cython或Numba编写,我在本例中使用这些函数。主要优点是,函数调用时不需要Python解释器交互

一个优秀的@Jacques Gaudin展示了一个简单的方法,包括附加的参数

import numpy as np
from scipy import integrate
from scipy.special import erf
from scipy.special import j0
import time
import numba as nb
from numba import cfunc
from numba.types import intc, CPointer, float64
from scipy import LowLevelCallable

q = np.linspace(0.03, 1.0, 1000)

start = time.time()

def jit_integrand_function(integrand_function):
    jitted_function = nb.njit(integrand_function, nopython=True)

    #error_model="numpy" -> Don't check for division by zero
    @cfunc(float64(intc, CPointer(float64)),error_model="numpy",fastmath=True)
    def wrapped(n, xx):
        ar = nb.carray(xx, n)
        return jitted_function(ar[0], ar[1], ar[2])
    return LowLevelCallable(wrapped.ctypes)

@jit_integrand_function
def f(t, z, q):
    return t * 0.5 * (erf((t - z) / 3) - 1) * j0(q * t) * (1 / (np.sqrt(2 * np.pi) * 2)) * np.exp(
        -0.5 * ((z - 40) / 2) ** 2)

def lower_inner(z):
    return 10.

def upper_inner(z):
    return 60.


y = np.empty(len(q))
for n in range(len(q)):
    y[n] = integrate.dblquad(f, 0, 50, lower_inner, upper_inner,args=(q[n],))[0]

end = time.time()
print(end - start)
#3.2645838260650635

看起来您的代码目前运行正常,您正在寻求改进。一般来说,这些问题对本网站来说过于固执己见,但你可能会在网站上找到更好的运气。记住阅读,因为他们比这个网站更严格。@DavidBuck非常感谢你的建议。如果你觉得这样,我会把它贴在CodeReview上。我把它贴在这里是因为我希望在改进代码的同时得到建议。如果别人也这么想,我就把它取下来。干杯:)@David,你是否积极参与CodeReview并准备在那里回答这个问题?如果没有,请不要推荐它,尤其是对于
numpy
问题。您已经在中询问了
quadpy
。通常,在SO中,询问其他软件包的建议来解决问题是不受欢迎的。你需要承认并建立在上一个问题中得到的帮助的基础上。并尝试应用您从以前的CR帖子中获得的反馈。
import numpy as np
from scipy import integrate
from scipy.special import erf
from scipy.special import j0
import time
import numba as nb
from numba import cfunc
from numba.types import intc, CPointer, float64
from scipy import LowLevelCallable

q = np.linspace(0.03, 1.0, 1000)

start = time.time()

def jit_integrand_function(integrand_function):
    jitted_function = nb.njit(integrand_function, nopython=True)

    #error_model="numpy" -> Don't check for division by zero
    @cfunc(float64(intc, CPointer(float64)),error_model="numpy",fastmath=True)
    def wrapped(n, xx):
        ar = nb.carray(xx, n)
        return jitted_function(ar[0], ar[1], ar[2])
    return LowLevelCallable(wrapped.ctypes)

@jit_integrand_function
def f(t, z, q):
    return t * 0.5 * (erf((t - z) / 3) - 1) * j0(q * t) * (1 / (np.sqrt(2 * np.pi) * 2)) * np.exp(
        -0.5 * ((z - 40) / 2) ** 2)

def lower_inner(z):
    return 10.

def upper_inner(z):
    return 60.


y = np.empty(len(q))
for n in range(len(q)):
    y[n] = integrate.dblquad(f, 0, 50, lower_inner, upper_inner,args=(q[n],))[0]

end = time.time()
print(end - start)
#3.2645838260650635