Почему FFT (A + B) отличается от FFT (A) + FFT (B)?
Я сражался с очень странной ошибкой почти месяц. Спросить вас, ребята, это моя последняя надежда. Я написал программу на C, которая интегрирует уравнение 2d Cahn-Hilliard, используя схему Implicit Euler (IE) в пространстве Фурье (или обратном):
Где "шляпы" означают, что мы находимся в пространстве Фурье: h_q (t_n + 1) и h_q (t_n) - это FTs h (x, y) в моменты времени t_n и t_ (n + 1), N [h_q] нелинейный оператор, примененный к h_q, в пространстве Фурье, а L_q - линейный, снова в пространстве Фурье. Я не хочу вдаваться в подробности численного метода, который я использую, так как я уверен, что проблема не идет оттуда (я пробовал использовать другие схемы).
Мой код на самом деле довольно прост. Вот начало, где в основном я объявляю переменные, выделяю память и создаю планы для подпрограмм FFTW.
# include <stdlib.h>
# include <stdio.h>
# include <time.h>
# include <math.h>
# include <fftw3.h>
# define pi M_PI
int main(){
// define lattice size and spacing
int Nx = 150; // n of points on x
int Ny = 150; // n of points on y
double dx = 0.5; // bin size on x and y
// define simulation time and time step
long int Nt = 1000; // n of time steps
double dt = 0.5; // time step size
// number of frames to plot (at denominator)
long int nframes = Nt/100;
// define the noise
double rn, drift = 0.05; // punctual drift of h(x)
srand(666); // seed the RNG
// other variables
int i, j, nt; // variables for space and time loops
// declare FFTW3 routine
fftw_plan FT_h_hft; // routine to perform fourier transform
fftw_plan FT_Nonl_Nonlft;
fftw_plan IFT_hft_h; // routine to perform inverse fourier transform
// declare and allocate memory for real variables
double *Linft = fftw_alloc_real(Nx*Ny);
double *Q2 = fftw_alloc_real(Nx*Ny);
double *qx = fftw_alloc_real(Nx);
double *qy = fftw_alloc_real(Ny);
// declare and allocate memory for complex variables
fftw_complex *dh = fftw_alloc_complex(Nx*Ny);
fftw_complex *dhft = fftw_alloc_complex(Nx*Ny);
fftw_complex *Nonl = fftw_alloc_complex(Nx*Ny);
fftw_complex *Nonlft = fftw_alloc_complex(Nx*Ny);
// create the FFTW plans
FT_h_hft = fftw_plan_dft_2d ( Nx, Ny, dh, dhft, FFTW_FORWARD, FFTW_ESTIMATE );
FT_Nonl_Nonlft = fftw_plan_dft_2d ( Nx, Ny, Nonl, Nonlft, FFTW_FORWARD, FFTW_ESTIMATE );
IFT_hft_h = fftw_plan_dft_2d ( Nx, Ny, dhft, dh, FFTW_BACKWARD, FFTW_ESTIMATE );
// open file to store the data
char acstr[160];
FILE *fp;
sprintf(acstr, "CH2d_IE_dt%.2f_dx%.3f_Nt%ld_Nx%d_Ny%d_#f%.ld.dat",dt,dx,Nt,Nx,Ny,Nt/nframes);
После этой преамбулы я инициализирую свою функцию h (x, y) с равномерным случайным шумом, а также возьму FT. Я устанавливаю мнимую часть h (x, y), которая является dh[i*Ny+j][1]
в коде, до 0, так как она является вещественной функцией. Затем я вычисляю Linft
qx
и qy
, и с ними вычисляю линейный оператор моего уравнения в пространстве Фурье, который является Linft
в коде. Я рассматриваю только четвертую производную от h как линейный член, так что FT линейного члена просто -q ^ 4... но опять же, я не хочу вдаваться в подробности моего метода интеграции. Вопрос не в этом.
// generate h(x,y) at initial time
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
rn = (double) rand()/RAND_MAX; // extract a random number between 0 and 1
dh[i*Ny+j][0] = drift-2.0*drift*rn; // shift of +-drift
dh[i*Ny+j][1] = 0.0;
}
}
// execute plan for the first time
fftw_execute (FT_h_hft);
// calculate wavenumbers
for (i = 0; i < Nx; i++) { qx[i] = 2.0*i*pi/(Nx*dx); }
for (i = 0; i < Ny; i++) { qy[i] = 2.0*i*pi/(Ny*dx); }
for (i = 1; i < Nx/2; i++) { qx[Nx-i] = -qx[i]; }
for (i = 1; i < Ny/2; i++) { qy[Ny-i] = -qy[i]; }
// calculate the FT of the linear operator
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
Q2[i*Ny+j] = qx[i]*qx[i] + qy[j]*qy[j];
Linft[i*Ny+j] = -Q2[i*Ny+j]*Q2[i*Ny+j];
}
}
Затем, наконец, наступает цикл времени. По существу, я делаю следующее:
-
Время от времени я сохраняю данные в файле и печатаю некоторую информацию на терминале. В частности, я печатаю наивысшее значение FT нелинейного члена. Я также проверяю, если h (x, y) расходится к бесконечности (этого не должно быть!),
-
Вычислим h ^ 3 в прямом пространстве (это просто dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0]
). Опять же, мнимая часть установлена в 0,
-
Возьмем FT h ^ 3,
-
Получите полный нелинейный член в обратном пространстве (то есть N [h_q] в алгоритме IE, написанном выше), вычислив -q ^ 2 * (FT [h ^ 3] - FT [h]). В коде я имею в виду строки Nonlft[i*Ny+j][0] = -q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0])
и ниже, для мнимой части. Я делаю это, потому что:
- Прогресс во времени с использованием метода IE, преобразование обратно в прямом пространстве, а затем нормализация.
Вот код:
for(nt = 0; nt < Nt; nt++) {
if((nt % nframes)== 0) {
printf("%.0f %%\n",((double)nt/(double)Nt)*100);
printf("Nonlft %.15f \n",Nonlft[(Nx/2)*(Ny/2)][0]);
// write data to file
fp = fopen(acstr,"a");
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
fprintf(fp, "%4d %4d %.6f\n", i, j, dh[i*Ny+j][0]);
}
}
fclose(fp);
}
// check if h is going to infinity
if (isnan(dh[1][0])!=0) {
printf("crashed!\n");
return 0;
}
// calculate nonlinear term h^3 in direct space
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
Nonl[i*Ny+j][0] = dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0];
Nonl[i*Ny+j][1] = 0.0;
}
}
// Fourier transform of nonlinear term
fftw_execute (FT_Nonl_Nonlft);
// second derivative in Fourier space is just multiplication by -q^2
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0]);
Nonlft[i*Ny+j][1] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][1] -dhft[i*Ny+j][1]);
}
}
// Implicit Euler scheme in Fourier space
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
dhft[i*Ny+j][0] = (dhft[i*Ny+j][0] + dt*Nonlft[i*Ny+j][0])/(1.0 - dt*Linft[i*Ny+j]);
dhft[i*Ny+j][1] = (dhft[i*Ny+j][1] + dt*Nonlft[i*Ny+j][1])/(1.0 - dt*Linft[i*Ny+j]);
}
}
// transform h back in direct space
fftw_execute (IFT_hft_h);
// normalize
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
dh[i*Ny+j][0] = dh[i*Ny+j][0] / (double) (Nx*Ny);
dh[i*Ny+j][1] = dh[i*Ny+j][1] / (double) (Nx*Ny);
}
}
}
Последняя часть кода: пустая память и уничтожение планов FFTW.
// terminate the FFTW3 plan and free memory
fftw_destroy_plan (FT_h_hft);
fftw_destroy_plan (FT_Nonl_Nonlft);
fftw_destroy_plan (IFT_hft_h);
fftw_cleanup();
fftw_free(dh);
fftw_free(Nonl);
fftw_free(qx);
fftw_free(qy);
fftw_free(Q2);
fftw_free(Linft);
fftw_free(dhft);
fftw_free(Nonlft);
return 0;
}
Если я запустил этот код, я получаю следующий вывод:
0 %
Nonlft 0.0000000000000000000
1 %
Nonlft -0.0000000000001353512
2 %
Nonlft -0.0000000000000115539
3 %
Nonlft 0.0000000001376379599
...
69 %
Nonlft -12.1987455309071730625
70 %
Nonlft -70.1631962517720353389
71 %
Nonlft -252.4941743351609204637
72 %
Nonlft 347.5067875825179726235
73 %
Nonlft 109.3351142318568633982
74 %
Nonlft 39933.1054502610786585137
crashed!
Кодекс падает до достижения цели, и мы видим, что нелинейный член расходится.
Теперь то, что не имеет для меня смысла, заключается в том, что если я изменю линии, в которых я вычисляю FT нелинейного термина следующим образом:
// calculate nonlinear term h^3 -h in direct space
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
Nonl[i*Ny+j][0] = dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0] -dh[i*Ny+j][0];
Nonl[i*Ny+j][1] = 0.0;
}
}
// Fourier transform of nonlinear term
fftw_execute (FT_Nonl_Nonlft);
// second derivative in Fourier space is just multiplication by -q^2
for ( i = 0; i < Nx; i++ ) {
for ( j = 0; j < Ny; j++ ) {
Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]* Nonlft[i*Ny+j][0];
Nonlft[i*Ny+j][1] = -Q2[i*Ny+j]* Nonlft[i*Ny+j][1];
}
}
Это означает, что я использую это определение:
вместо этого:
Тогда код абсолютно стабилен и никаких расхождений не происходит! Даже в течение миллиардов шагов! Почему это происходит, поскольку два способа расчета Nonlft
должны быть эквивалентными?
Большое вам спасибо всем, кто найдет время, чтобы прочитать все это и оказать мне помощь!
EDIT: Чтобы сделать вещи еще более странными, я должен отметить, что эта ошибка НЕ происходит для одной и той же системы в 1D. В 1D оба метода расчета Nonlft
устойчивы.
EDIT: Я добавляю короткую анимацию того, что происходит с функцией h (x, y) непосредственно перед сбоем. Кроме того: я быстро переписал код в MATLAB, который использует функции Fast Fourier Transform, основанные на библиотеке FFTW, и ошибка НЕ происходит... тайна углубляется.
Ответы
Ответ 1
Я решил это !! Проблема заключалась в расчете термина Nonl
:
Nonl[i*Ny+j][0] = dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0];
Nonl[i*Ny+j][1] = 0.0;
Это должно быть изменено на:
Nonl[i*Ny+j][0] = dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0] -3.0*dh[i*Ny+j][0]*dh[i*Ny+j][1]*dh[i*Ny+j][1];
Nonl[i*Ny+j][1] = -dh[i*Ny+j][1]*dh[i*Ny+j][1]*dh[i*Ny+j][1] +3.0*dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][1];
Другими словами: мне нужно рассматривать dh
как сложную функцию (даже если она должна быть реальной).
В основном, из-за глупых ошибок округления, IFT FT реальной функции (в моем случае dh
) НЕ является чисто вещественной, но будет иметь очень маленькую мнимую часть. Установив Nonl[i*Ny+j][1] = 0.0
я полностью игнорировал эту мнимую часть. Тогда проблема заключалась в том, что я рекурсивно суммировал FT (dh
), dhft
и объект, полученный с помощью IFT (FT (dh
)), это Nonlft
, но игнорировал остаточные мнимые части!
Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0]);
Nonlft[i*Ny+j][1] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][1] -dhft[i*Ny+j][1]);
Очевидно, что вычисление Nonlft
как dh
^ 3 -dh
и затем выполнение
Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]* Nonlft[i*Ny+j][0];
Nonlft[i*Ny+j][1] = -Q2[i*Ny+j]* Nonlft[i*Ny+j][1];
Избежал проблемы с выполнением этой "смешанной" суммы.
Фуф... такое облегчение! Я хотел бы назначить награду для себя! :П
РЕДАКТИРОВАТЬ: Я хотел бы добавить, что перед использованием функций fftw_plan_dft_2d
я использовал fftw_plan_dft_r2c_2d
и fftw_plan_dft_c2r_2d
(от реального к сложному и от сложного к реальному), и я видел ту же ошибку. Тем не менее, я полагаю, что я не смог бы решить эту проблему, если бы не переключился на fftw_plan_dft_2d
, поскольку функция c2r
автоматически " c2r
" остаточную мнимую часть, поступающую от IFT. Если это так, и я что-то не пропустил, я думаю, что это должно быть написано где-то на веб-сайте FFTW, чтобы пользователи не сталкивались с подобными проблемами. Что-то вроде "преобразования r2c
и c2r
не c2r
для реализации псевдоспектральных методов".
РЕДАКТИРОВАТЬ: я нашел еще один вопрос, который решает точно такую же проблему.
Ответ 2
В общем случае (Аддитивное обратное свойство) объясняет, почему это не может быть правдой; т.е. из-за знака чисел. Дайте тот же знак и номер для работы, тогда да, A + B - это то же самое, что и (A) + (B), что само по себе имеет значение БПФ и логика в нем.
Приведя пример, где a = -1, b = 2 и fft от Lamda, мы можем утверждать, что lamda (-1) + (2) = 1, а также что Lamda (-1 + 2) = 1.
Кажется, условия ваших уравнений верны, но код, который реализует нормализацию, может иметь одну или две ошибки, я бы проверил эту логику...
Linft[i*Ny+j] = -Q2[i*Ny+j]*Q2[i*Ny+j];
dh[i*Ny+j][0] = dh[i*Ny+j][0] / (double) (Nx*Ny);
dh[i*Ny+j][1] = dh[i*Ny+j][1] / (double) (Nx*Ny);
Строки, которые вы приводите в своем вопросе:
Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0]);
назначается до того, как будут выполнены все эти предыдущие вычисления, и должны соответствовать тем же терминам, что и последующие части уравнения для математики, которую вы написали, чтобы соответствовать логике в программе. Следовательно, почему я считаю, что вы нормализуетесь...
Для других ссылок, которые описывают аддитивное обратное свойство, смотрите комментарии ниже.
Что касается того, почему программа аварийно завершает работу, похоже, что она связана с типом данных, которые вы пытаетесь преобразовать в double из различных пространств... например, существуют определенные диапазоны для типов в программировании, которые могут отличаться от наших математических концепций в зависимости от типа и точности. используется в расчете.
В дополнение к вышеупомянутому вы также присваиваете той же переменной, из которой вы читаете, но используемые указатели присваиваются другой функцией. Эти определения не публикуются и на основе вашего кода я предполагаю, что вы получаете доступ к правильно распределенным Pointeres в правильных диапазонах.
Трудно сказать, где именно находится ошибка в вашей программе, не указав ее значения из-за вышеупомянутого, однако я бы посоветовал вам использовать флаги компиляции, чтобы предоставить вам такую информацию, которая может оказаться полезной...
Это снова зависит от вашего компилятора:
Смотрите также:
https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html
Как только у вас есть информация о сбое, это должно быть тривиально, чтобы решить проблему...
Если вы просто выведите значение своих переменных до и после уравнения, вам будет намного легче определить, что происходит, почему и в большинстве случаев, что необходимо сделать для решения проблемы.