Опыт применения теоремы о свертке с использованием scipy.fft
Теорема о свёртке утверждает, что преобразование Фурье от свёртки двух функций является произведением их Фурье образов:
Как следствие, свёртку двух функций можно вычислить путём обратного преобразования Фурье:
Когда свёртку не удаётся получить аналитически, использование теоремы позволяет вместо численного интегрирования воспользоваться алгоритмом быстрого преобразования Фурье и существенно ускорить процесс вычислений. В ходе решения возникшей у меня задачи мне не удалось найти в интернете достаточно подробного описания алгоритма действий, поэтому я решил изложить его здесь, во-первых, как памятку для себя и во-вторых, на случай если это окажется полезным кому-то ещё. Не претендуя на общность изложения, ниже я приведу примеры найденных решений для функций от одной и от двух переменных.
Функция от одной переменной
Допустим, нам нужно вычислить свёртку функций
причём имеется в виду, что нас интересует только действительная часть области значений функции
где
Дискретным преобразованием Фурье (ДПФ) называется метод вычислений, в котором и функция и ее преобразование заменяются дискретными аналогами. Быстрое преобразование Фурье (БПФ) относится к способу эффективного вычисления ДПФ с использованием симметрии в вычисляемых условиях. Симметрия наивысшая, когда число узлов
Выберем область определения
В шкале частот нашему диапазону шкалы
Если функция, которую мы ищем, вещественна (не имеет мнимой части), положительные частоты в последовательности являются сопряженными величинами отрицательных частот, за счёт этого можно примерно в два раза сократить время вычислений. Для получения укороченной (без отрицательных частот) последовательности используется функция scipy.fft.rfftfreq. Для выполнения прямого и обратного преобразований Фурье для вещественного сигнала предназначены функции scipy.fft.rfft и scipy.fft.irfft соответственно. Функция scipy.fft.fftshift располагает компоненту с нулевой частотой в середине частотного спектра, а scipy.fft.ifftshift осуществляет обратное преобразование. Ниже приведён код для вычисления свёртки:
import numpy as np
from scipy.special import j0 as BesselJ0
import scipy.fft as FFT
from matplotlib import pyplot as plt
π = np.pi
σ = 0.01
R = 1.5 # интервал, в котором вычисляется свёртка
Nx = 4096 # число разбиений по оси x
Dx = 2*R/Nx # шаг дискретизации
x = np.linspace(0.5*Dx-R, R-0.5*Dx, num = Nx)
u = FFT.rfftfreq(Nx//16, d = 16*Dx) # цифровой ФНЧ
J0 = BesselJ0(2*π*u)*np.exp(-2*(π*σ*u)**2)
y = FFT.ifftshift(FFT.irfft(J0, n=Nx, norm='forward'))
plt.subplot(211)
plt.plot(u, J0, 'r-', label='спектр частот')
plt.xlabel('u, [1/см]')
plt.grid(True); plt.legend()
plt.subplot(212)
plt.plot(x, y, 'r-', label='результат свёртки')
plt.xlabel('x, [см]')
plt.grid(True); plt.legend()
plt.show()
Результат представлен на картинке. Как видно из спектра частот, амплитуда гармоник экспоненциально падает с ростом частоты (из-за
Функция от двух переменных
Перейдем к рассмотрению двумерного случая, основываясь на статье Natalie Baddour. Фурье преобразование функции
где
где для краткости используются обозначения
Если функция не зависит от угла, т. е.
Используя интегральное определение функции Бесселя нулевого порядка
наше Фурье преобразование можно записать так:
где
Для примера вычислим свёртку двух функций:
одна из которых задана в полярных координатах, а другая - в декартовых. Фурье-образ функции
где
import numpy as np
from scipy import fft as FFT
import matplotlib.pyplot as plt
π = np.pi
σx = 0.05
σy = 0.02
R = 1.5
N = 1024
D = 2*R/N
s = np.linspace(0.5*D-R, R-0.5*D, num = N)
f = FFT.fftshift(FFT.fftfreq(N, d = D))
x,y = np.meshgrid(s,s)
u,v = np.meshgrid(f,f)
J = 2*np.sinc(2*(u**2 + v**2)**0.5)*np.exp(-2*π*π*((σx*u)**2 + (σy*v)**2))
z = np.abs(FFT.ifftshift(FFT.irfft2(J, s=[N,N], workers=-1, norm='forward')))
plt.figure(figsize=(16, 9))
levels = np.linspace(J.min(), J.max(), 16)
plt.subplot(121)
plt.contourf(u, v, np.real(J), levels)
plt.colorbar(); plt.title('Фурье образ')
plt.xlim(-5,5); plt.xlabel('u, [1/см]')
plt.ylim(-5,5); plt.ylabel('v, [1/см]')
plt.subplot(122)
levels = np.linspace(z.min(), z.max(), 16)
plt.contourf(x, y, z, levels)
plt.colorbar(); plt.title('Результат свёртки')
plt.xlabel('x, [см]')
plt.ylabel('y, [см]')
plt.show()
Опция workers=-1
в функции irfft2 позволяет параллельно использовать все имеющиеся в системе процессорные ядра. Результат вычислений представлен на картинке:
Заключение
Вот вроде бы и всё о чём я хотел написать. Буду рад критике, вопросам и предложениям. Всех с наступающим 2022!