scipy.signal.convolve中的黎曼和伪影
简短总结: 我该如何快速计算两个数组的有限卷积?
问题描述
我想要得到两个函数 f(x) 和 g(x) 的有限卷积,定义如下:
为此,我对这些函数进行了离散采样,并将它们转化为长度为 steps
的数组:
xarray = [x * i / steps for i in range(steps)]
farray = [f(x) for x in xarray]
garray = [g(x) for x in xarray]
然后我尝试使用 scipy.signal.convolve
函数来计算卷积。这个函数的结果和算法 conv
的结果是一样的,具体算法可以在这里找到。不过,结果和解析解相比差别很大。通过修改算法 conv
来使用梯形法则,可以得到想要的结果。
为了说明这一点,我设定了:
f(x) = exp(-x)
g(x) = 2 * exp(-2 * x)
结果是:
这里 Riemann
代表简单的黎曼和,trapezoidal
是使用梯形法则修改过的黎曼算法,scipy.signal.convolve
是 scipy 的函数,而 analytical
是解析卷积。
现在设定 g(x) = x^2 * exp(-x)
,结果变为:
这里的 'ratio' 是从 scipy 得到的值与解析值的比率。以上结果表明,单纯通过重新归一化积分是无法解决这个问题的。
问题
是否可以在保持 scipy 速度的同时,保留梯形法则的更好结果,还是说我必须编写 C 扩展才能达到想要的结果?
示例
只需复制并粘贴下面的代码,就可以看到我遇到的问题。通过增加 steps
变量,可以使两个结果更接近。我认为问题出在右侧黎曼和的伪影上,因为当积分增加时会被高估,而在减少时又会接近解析解。
编辑: 我现在已经包含了原始算法2作为比较,它给出的结果和 scipy.signal.convolve
函数是一样的。
import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
import math
def convolveoriginal(x, y):
'''
The original algorithm from http://www.physics.rutgers.edu/~masud/computing/WPark_recipes_in_python.html.
'''
P, Q, N = len(x), len(y), len(x) + len(y) - 1
z = []
for k in range(N):
t, lower, upper = 0, max(0, k - (Q - 1)), min(P - 1, k)
for i in range(lower, upper + 1):
t = t + x[i] * y[k - i]
z.append(t)
return np.array(z) #Modified to include conversion to numpy array
def convolve(y1, y2, dx = None):
'''
Compute the finite convolution of two signals of equal length.
@param y1: First signal.
@param y2: Second signal.
@param dx: [optional] Integration step width.
@note: Based on the algorithm at http://www.physics.rutgers.edu/~masud/computing/WPark_recipes_in_python.html.
'''
P = len(y1) #Determine the length of the signal
z = [] #Create a list of convolution values
for k in range(P):
t = 0
lower = max(0, k - (P - 1))
upper = min(P - 1, k)
for i in range(lower, upper):
t += (y1[i] * y2[k - i] + y1[i + 1] * y2[k - (i + 1)]) / 2
z.append(t)
z = np.array(z) #Convert to a numpy array
if dx != None: #Is a step width specified?
z *= dx
return z
steps = 50 #Number of integration steps
maxtime = 5 #Maximum time
dt = float(maxtime) / steps #Obtain the width of a time step
time = [dt * i for i in range (steps)] #Create an array of times
exp1 = [math.exp(-t) for t in time] #Create an array of function values
exp2 = [2 * math.exp(-2 * t) for t in time]
#Calculate the analytical expression
analytical = [2 * math.exp(-2 * t) * (-1 + math.exp(t)) for t in time]
#Calculate the trapezoidal convolution
trapezoidal = convolve(exp1, exp2, dt)
#Calculate the scipy convolution
sci = signal.convolve(exp1, exp2, mode = 'full')
#Slice the first half to obtain the causal convolution and multiply by dt
#to account for the step width
sci = sci[0:steps] * dt
#Calculate the convolution using the original Riemann sum algorithm
riemann = convolveoriginal(exp1, exp2)
riemann = riemann[0:steps] * dt
#Plot
plt.plot(time, analytical, label = 'analytical')
plt.plot(time, trapezoidal, 'o', label = 'trapezoidal')
plt.plot(time, riemann, 'o', label = 'Riemann')
plt.plot(time, sci, '.', label = 'scipy.signal.convolve')
plt.legend()
plt.show()
感谢你的时间!
2 个回答
或者,对于那些更喜欢用numpy而不是C语言的人来说。虽然它的速度比C语言的实现要慢,但代码只需要几行。
>>> t = np.linspace(0, maxtime-dt, 50)
>>> fx = np.exp(-np.array(t))
>>> gx = 2*np.exp(-2*np.array(t))
>>> analytical = 2 * np.exp(-2 * t) * (-1 + np.exp(t))
在这种情况下,这看起来像是梯形法(不过我没有检查具体的数学计算)。
>>> s2a = signal.convolve(fx[1:], gx, 'full')*dt
>>> s2b = signal.convolve(fx, gx[1:], 'full')*dt
>>> s = (s2a+s2b)/2
>>> s[:10]
array([ 0.17235682, 0.29706872, 0.38433313, 0.44235042, 0.47770012,
0.49564748, 0.50039326, 0.49527721, 0.48294359, 0.46547582])
>>> analytical[:10]
array([ 0. , 0.17221333, 0.29682141, 0.38401317, 0.44198216,
0.47730244, 0.49523485, 0.49997668, 0.49486489, 0.48254154])
最大的绝对误差:
>>> np.max(np.abs(s[:len(analytical)-1] - analytical[1:]))
0.00041657780840698155
>>> np.argmax(np.abs(s[:len(analytical)-1] - analytical[1:]))
6
简短回答: 用C语言写!
详细回答
我参考了关于 numpy数组 的手册,把梯形卷积的方法用C语言重写了一遍。要使用这段C代码,需要三个文件 (https://gist.github.com/1626919):
- C代码文件(performancemodule.c)。
- 用于构建代码并使其可以从Python调用的设置文件(performancemodulesetup.py)。
- 使用C扩展的Python文件(performancetest.py)。
下载后,代码应该可以运行,步骤如下:
- 调整
performancemodule.c
中的包含路径。 运行以下命令:
python performancemodulesetup.py build
python performancetest.py
你可能需要把库文件 performancemodule.so
或 performancemodule.dll
复制到和 performancetest.py
同一个目录下。
结果和性能
结果之间非常一致,如下所示:
C语言方法的性能甚至比scipy的卷积方法还要好。运行10,000次卷积,数组长度为50时需要:
convolve (seconds, microseconds) 81 349969
scipy.signal.convolve (seconds, microseconds) 1 962599
convolve in C (seconds, microseconds) 0 87024
因此,C语言实现的速度大约是Python实现的1000倍,比scipy实现快了20倍多(当然,scipy实现更灵活)。
编辑: 这并没有完全解决原来的问题,但对我来说已经足够了。