簡體   English   中英

為什么(A + B)的FFT與FFT(A)+ FFT(B)不同?

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

我差不多一個月一直在和一個非常奇怪的蟲子打架。 問你們這是我最后的希望。 我在C中編寫了一個程序,它使用傅里葉(或倒數)空間中的隱式歐拉(IE)方案集成了2d Cahn-Hilliard方程

IE方法

“帽子”意味着我們處於傅里葉空間: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是線性的,同樣在傅立葉空間中。 我不想過多介紹我使用的數值方法的細節,因為我確信問題不是來自那里(我嘗試使用其他方案)。

我的代碼實際上非常簡單。 這是開始,基本上我聲明變量,分配內存並為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,因為它是一個實數函數。 然后我計算波矢量qxqy ,並用它們,我計算傅立葉空間中方程的線性算子,這是代碼中的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,

  • 拿h ^ 3的FT,

  • 通過計算-q ^ 2 *(FT [h ^ 3] -FT [h])獲得倒數空間中的完整非線性項(即上面描述的IE算法中的N [h_q])。 在代碼中,我指的是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])和下面的那個。 我這樣做是因為:

在此輸入圖像描述

  • 使用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的兩種方法應該是等價的?

非常感謝任何花時間閱讀所有這些並給我一些幫助的人!

編輯:為了讓事情變得更加奇怪,我應該指出,這個錯誤不會發生在1D的同一系統中。 在1D中,兩種計算Nonlft方法都是穩定的。

編輯:我添加一個簡短的動畫,說明在崩潰之前函數h(x,y)發生了什么。 另外:我很快在MATLAB中重新編寫了代碼,它使用基於FFTW庫的快速傅立葉變換函數,並且沒有發生錯誤......神秘感加深了。 在此輸入圖像描述

我解決了!! 問題是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視為一個復雜的函數(即使它應該是真實的)。

基本上,由於愚蠢的舍入誤差, 實數函數 (在我的情況下為dh的FT的IFT 不是純粹的真實 ,而是具有非常小的虛部。 通過設置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]);

顯然,計算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];

避免做這個“混合”總和的問題。

P ......這樣的解脫! 我希望我可以給自己分配賞金! :P

編輯:我想補充一點,在使用fftw_plan_dft_2d函數之前,我使用的是fftw_plan_dft_r2c_2dfftw_plan_dft_c2r_2d (真實到復雜和復雜到實際),我看到了同樣的錯誤。 但是,如果我沒有切換到fftw_plan_dft_2d ,我想我無法解決它,因為c2r函數會自動“切斷”來自IFT的剩余虛部。 如果是這種情況並且我沒有遺漏某些東西,我認為這應該寫在FFTW網站的某個地方,以防止用戶遇到這樣的問題。 像“ r2cc2r變換”這樣的東西並不適合實現偽譜方法。

編輯:我發現另一個SO問題解決了完全相同的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM