为什么我需要"fftshift" DFTs的乘积来恢复卷积乘积
Why do I need to "fftshift" the product of DFTs to recover the convolution product
我需要使用卷积定理计算卷积积。但是,我不明白为什么我需要在傅立叶逆变换上应用 fftshift
以获得正确的结果。否则,结果将被交换(好吧,我知道这就是 fftshift 的用途,但我不明白为什么我从逆 fft 获得交换结果)。这是一个最小的示例,其中包含两个快速减少的函数,因此我不会添加填充来打扰。根据 scipy.signal.convolve
:
检查结果
import numpy as np
import scipy.signal as sig
Nx = 400
xp = np.arange(Nx) - Nx/2.
Lg = 20
Lb = 25
ff = np.exp(-(xp/Lg)**2) * xp/Lg # function (two bumps of opposite signs)
gg = np.zeros(Nx) # convolution kernel (just a box)
gg[abs(xp)<Lb] = 1
conv_pure = sig.convolve(ff, gg, mode="same") # that is the correct one
tff = np.fft.rfft(ff) # DFT of the function
tfg = np.fft.rfft(gg) # DFT of the kernel
conv_dfts = np.fft.irfft(tff*tfg).real # should be the convolution product
conv_dftshift = np.fft.fftshift(conv_dfts)
And here is how it looks like
那么,为什么 conv_dfts
交换了?
为了正确定义 scipy.signal.convolve
和 mode='full'
或 mode='same'
中的计算,第一个参数中的数据(有效地)用零扩展。另一方面,您的 FFT 计算执行循环卷积,这对应于使用数据的周期性扩展。要查看此差异的后果,请考虑如何计算结果的第一个点。
(记住通常的 "sliding window" 卷积视图很有帮助,例如 http://mathworld.wolfram.com/Convolution.html or https://en.wikipedia.org/wiki/Convolution#Visual_explanation 所示。在您的情况下,滑动 window 是 gg
.)
对于 scipy.signal.convolve
和 mode='same'
,您可以通过将 gg
的右半部分与 ff
的左端对齐来可视化第一个点的计算,并且将这两个信号的元素乘积相加。 ff
在它的左端非常小,所以这个计算非常接近于 0。卷积的后续点保持为零,直到滑动 window 开始遇到更大的 ff
值。所以结果的"interesting"部分在卷积的中间。
对于 FFT 计算的第一个点,假设 gg
的右端与 ff
的左端对齐。再次取元素乘积的总和。这里有两个很大的不同。首先,gg
不像 scipy.signal.convolve
中的 mode='same'
那样移动一半长度。其次,gg
乘以的值不全为零——它们是 ff
的周期性扩展,所以在这个 "sliding window" 可视化中,我们有矩形 window直接对准双脉冲的中心(在周期性扩展中)。由于 gg
的对称性和 ff
的反对称性,第一个值为 0。随着 gg
向右滑动,对称性被打破,正脉冲主导计算,并且非平凡值被计算。一旦window通过双脉冲,卷积的值就会变得很小。当矩形脉冲遇到双脉冲的另一侧时,它们在卷积结束附近再次变得非常大。
为了使您的 FFT 计算与 scipy.signal.convolve
计算匹配,您可以调整 gg
中矩形脉冲的相位。例如(假设 Nx
是偶数)。例如,如果您添加此行
gg2 = np.roll(gg, -(Nx//2 - 1))
并在 tfg
的计算中使用 gg2
代替 gg
:
tfg = np.fft.rfft(gg2) # DFT of the kernel
然后 conv_dfts
和 conv_pure
同意。您还可以通过其他方式调整事物以使结果与您预期的一致。这个答案的重点是解释为什么你计算的结果不同。
我需要使用卷积定理计算卷积积。但是,我不明白为什么我需要在傅立叶逆变换上应用 fftshift
以获得正确的结果。否则,结果将被交换(好吧,我知道这就是 fftshift 的用途,但我不明白为什么我从逆 fft 获得交换结果)。这是一个最小的示例,其中包含两个快速减少的函数,因此我不会添加填充来打扰。根据 scipy.signal.convolve
:
import numpy as np
import scipy.signal as sig
Nx = 400
xp = np.arange(Nx) - Nx/2.
Lg = 20
Lb = 25
ff = np.exp(-(xp/Lg)**2) * xp/Lg # function (two bumps of opposite signs)
gg = np.zeros(Nx) # convolution kernel (just a box)
gg[abs(xp)<Lb] = 1
conv_pure = sig.convolve(ff, gg, mode="same") # that is the correct one
tff = np.fft.rfft(ff) # DFT of the function
tfg = np.fft.rfft(gg) # DFT of the kernel
conv_dfts = np.fft.irfft(tff*tfg).real # should be the convolution product
conv_dftshift = np.fft.fftshift(conv_dfts)
And here is how it looks like
那么,为什么 conv_dfts
交换了?
为了正确定义 scipy.signal.convolve
和 mode='full'
或 mode='same'
中的计算,第一个参数中的数据(有效地)用零扩展。另一方面,您的 FFT 计算执行循环卷积,这对应于使用数据的周期性扩展。要查看此差异的后果,请考虑如何计算结果的第一个点。
(记住通常的 "sliding window" 卷积视图很有帮助,例如 http://mathworld.wolfram.com/Convolution.html or https://en.wikipedia.org/wiki/Convolution#Visual_explanation 所示。在您的情况下,滑动 window 是 gg
.)
对于 scipy.signal.convolve
和 mode='same'
,您可以通过将 gg
的右半部分与 ff
的左端对齐来可视化第一个点的计算,并且将这两个信号的元素乘积相加。 ff
在它的左端非常小,所以这个计算非常接近于 0。卷积的后续点保持为零,直到滑动 window 开始遇到更大的 ff
值。所以结果的"interesting"部分在卷积的中间。
对于 FFT 计算的第一个点,假设 gg
的右端与 ff
的左端对齐。再次取元素乘积的总和。这里有两个很大的不同。首先,gg
不像 scipy.signal.convolve
中的 mode='same'
那样移动一半长度。其次,gg
乘以的值不全为零——它们是 ff
的周期性扩展,所以在这个 "sliding window" 可视化中,我们有矩形 window直接对准双脉冲的中心(在周期性扩展中)。由于 gg
的对称性和 ff
的反对称性,第一个值为 0。随着 gg
向右滑动,对称性被打破,正脉冲主导计算,并且非平凡值被计算。一旦window通过双脉冲,卷积的值就会变得很小。当矩形脉冲遇到双脉冲的另一侧时,它们在卷积结束附近再次变得非常大。
为了使您的 FFT 计算与 scipy.signal.convolve
计算匹配,您可以调整 gg
中矩形脉冲的相位。例如(假设 Nx
是偶数)。例如,如果您添加此行
gg2 = np.roll(gg, -(Nx//2 - 1))
并在 tfg
的计算中使用 gg2
代替 gg
:
tfg = np.fft.rfft(gg2) # DFT of the kernel
然后 conv_dfts
和 conv_pure
同意。您还可以通过其他方式调整事物以使结果与您预期的一致。这个答案的重点是解释为什么你计算的结果不同。