简体   繁体   English

为什么(A + B)的FFT与FFT(A)+ FFT(B)不同?

[英]Why is FFT of (A+B) different from FFT(A) + FFT(B)?

I have been fighting with a very weird bug for almost a month. 我差不多一个月一直在和一个非常奇怪的虫子打架。 Asking you guys is my last hope. 问你们这是我最后的希望。 I wrote a program in C that integrates the 2d Cahn–Hilliard equation using the Implicit Euler (IE) scheme in Fourier (or reciprocal) space: 我在C中编写了一个程序,它使用傅里叶(或倒数)空间中的隐式欧拉(IE)方案集成了2d Cahn-Hilliard方程

IE方法

Where the "hats" mean that we are in Fourier space: h_q(t_n+1) and h_q(t_n) are the FTs of h(x,y) at times t_n and t_(n+1), N[h_q] is the nonlinear operator applied to h_q, in Fourier space, and L_q is the linear one, again in Fourier space. “帽子”意味着我们处于傅里叶空间:h_q(t_n + 1)和h_q(t_n)是时间t_n和t_(n + 1)的h(x,y)的FT,N [h_q]是非线性算子应用于傅立叶空间中的h_q,而L_q是线性的,同样在傅立叶空间中。 I don't want to go too much into the details of the numerical method I am using, since I am sure that the problem is not coming from there (I tried using other schemes). 我不想过多介绍我使用的数值方法的细节,因为我确信问题不是来自那里(我尝试使用其他方案)。

My code is actually quite simple. 我的代码实际上非常简单。 Here is the beginning, where basically I declare variables, allocate memory and create the plans for the FFTW routines. 这是开始,基本上我声明变量,分配内存并为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);

After this preamble, I initialise my function h(x,y) with a uniform random noise, and I also take the FT of it. 在这个序言之后,我用一个均匀的随机噪声初始化我的函数h(x,y),并且我也采用它的FT。 I set the imaginary part of h(x,y), which is dh[i*Ny+j][1] in the code, to 0, since it is a real function. 我将h(x,y)的虚部(即代码中的dh[i*Ny+j][1] )设置为0,因为它是一个实数函数。 Then I calculate the wavevectors qx and qy , and with them, I compute the linear operator of my equation in Fourier space, which is Linft in the code. 然后我计算波矢量qxqy ,并用它们,我计算傅立叶空间中方程的线性算子,这是代码中的Linft I consider only the - fourth derivative of h as the linear term, so that the FT of the linear term is simply -q^4... but again, I don't want to go into the details of my integration method. 我只考虑h的四阶导数作为线性项,因此线性项的FT只是-q ^ 4 ......但是,我不想再深入讨论我的积分方法的细节。 The question is not about it. 问题不在于此。

// 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];
  }
}

Then, finally, it comes the time loop. 然后,最后,它来了时间循环。 Essentially, what I do is the following: 基本上,我所做的是以下内容:

  • Every once in a while, I save the data to a file and print some information on the terminal. 每隔一段时间,我就会将数据保存到文件中并在终端上打印一些信息。 In particular, I print the highest value of the FT of the Nonlinear term. 特别是,我打印非线性项的FT的最高值。 I also check if h(x,y) is diverging to infinity (it shouldn't happen!), 我还检查h(x,y)是否发散到无穷大(它不应该发生!),

  • Calculate h^3 in direct space (that is simply dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0] ). 计算直接空间中的h ^ 3(即简单地dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0] )。 Again, the imaginary part is set to 0, 同样,虚部设置为0,

  • Take the FT of h^3, 拿h ^ 3的FT,

  • Obtain the complete Nonlinear term in reciprocal space (that is N[h_q] in the IE algorithm written above) by computing -q^2*(FT[h^3] - FT[h]). 通过计算-q ^ 2 *(FT [h ^ 3] -FT [h])获得倒数空间中的完整非线性项(即上面描述的IE算法中的N [h_q])。 In the code, I am referring to the lines Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0]) and the one below, for the imaginary part. 在代码中,我指的是Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0])对于虚部, Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0])和下面的那个。 I do this because: 我这样做是因为:

在此输入图像描述

  • Advance in time using the IE method, transform back in direct space, and then normalise. 使用IE方法提前,在直接空间中转换回来,然后进行标准化。

Here is the code: 这是代码:

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);
  }
}

}

Last part of the code: empty the memory and destroy FFTW plans. 代码的最后一部分:清空内存并破坏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;

}

If I run this code, I obtain the following output: 如果我运行此代码,我将获得以下输出:

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!

The code crashes before reaching the end and we can see that the Nonlinear term is diverging. 代码在到达结束之前崩溃,我们可以看到非线性项是分歧的。

Now, the thing that doesn't make sense to me is that if I change the lines in which I calculate the FT of the Nonlinear term in the following way: 现在,对我来说没有意义的是,如果我通过以下方式更改我计算非线性项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];
  }
}

Which means that I am using this definition: 这意味着我正在使用这个定义:

在此输入图像描述

instead of this one: 而不是这一个:

在此输入图像描述

Then the code is perfectly stable and no divergence happens! 然后代码非常稳定,不会发生分歧! Even for billions of time steps! 即使是数十亿的时间步! Why does this happen, since the two ways of calculating Nonlft should be equivalent? 为什么会发生这种情况,因为计算Nonlft的两种方法应该是等价的?

Thank you very much to anyone who will take the time to read all of this and give me some help! 非常感谢任何花时间阅读所有这些并给我一些帮助的人!

EDIT: To make things even more weird, I should point out that this bug does NOT happen for the same system in 1D. 编辑:为了让事情变得更加奇怪,我应该指出,这个错误不会发生在1D的同一系统中。 In 1D both methods of calculating Nonlft are stable. 在1D中,两种计算Nonlft方法都是稳定的。

EDIT: I add a short animation of what happens to the function h(x,y) just before crashing. 编辑:我添加一个简短的动画,说明在崩溃之前函数h(x,y)发生了什么。 Also: I quickly re-wrote the code in MATLAB, which uses Fast Fourier Transform functions based on the FFTW library, and the bug is NOT happening... the mystery deepens. 另外:我很快在MATLAB中重新编写了代码,它使用基于FFTW库的快速傅立叶变换函数,并且没有发生错误......神秘感加深了。 在此输入图像描述

I solved it!! 我解决了!! The problem was the calculation of the Nonl term: 问题是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;

That needs to be changed to: 这需要改为:

  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];

In other words: I need to consider dh as a complex function (even though it should be real). 换句话说:我需要将dh视为一个复杂的函数(即使它应该是真实的)。

Basically, because of stupid rounding errors, the IFT of the FT of a real function (in my case dh ), is NOT purely real , but will have a very small imaginary part. 基本上,由于愚蠢的舍入误差, 实数函数 (在我的情况下为dh的FT的IFT 不是纯粹的真实 ,而是具有非常小的虚部。 By setting Nonl[i*Ny+j][1] = 0.0 I was completely ignoring this imaginary part. 通过设置Nonl[i*Ny+j][1] = 0.0我完全忽略了这个想象的部分。 The issue, then, was that I was recursively summing FT( dh ), dhft , and an object obtained using the IFT(FT( dh )), this is Nonlft , but ignoring the residual imaginary parts! 那么,问题在于我递归地求和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]);

Obviously, calculating Nonlft as dh ^3 -dh and then doing 显然,计算Nonlftdh ^ 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];

Avoided the problem of doing this "mixed" sum. 避免做这个“混合”总和的问题。

Phew... such a relief! P ......这样的解脱! I wish I could assign the bounty to myself! 我希望我可以给自己分配赏金! :P :P

EDIT: I'd like to add that, before using the fftw_plan_dft_2d functions, I was using fftw_plan_dft_r2c_2d and fftw_plan_dft_c2r_2d (real-to-complex and complex-to-real), and I was seeing the same bug. 编辑:我想补充一点,在使用fftw_plan_dft_2d函数之前,我使用的是fftw_plan_dft_r2c_2dfftw_plan_dft_c2r_2d (真实到复杂和复杂到实际),我看到了同样的错误。 However, I suppose that I couldn't have solved it if I didn't switch to fftw_plan_dft_2d , since the c2r function automatically "chops off" the residual imaginary part coming from the IFT. 但是,如果我没有切换到fftw_plan_dft_2d ,我想我无法解决它,因为c2r函数会自动“切断”来自IFT的剩余虚部。 If this is the case and I'm not missing something, I think that this should be written somewhere on the FFTW website, to prevent users from running into problems like this. 如果是这种情况并且我没有遗漏某些东西,我认为这应该写在FFTW网站的某个地方,以防止用户遇到这样的问题。 Something like " r2c and c2r transforms are not good to implement pseudospectral methods". 像“ r2cc2r变换”这样的东西并不适合实现伪谱方法。

EDIT: I found another SO question that addresses exactly the same problem. 编辑:我发现另一个SO问题解决了完全相同的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM