Warum unterscheidet sich FFT von (A+B) von FFT(A) + FFT(B)?

Lesezeit: 11 Minuten

Benutzeravatar von Tropilio
Tropilio

Ich kämpfe seit fast einem Monat mit einem sehr seltsamen Fehler. Euch zu fragen ist meine letzte Hoffnung. Ich habe ein Programm in C geschrieben, das die 2d integriert Cahn-Hilliard-Gleichung unter Verwendung des impliziten Euler-Schemas (IE) im Fourier- (oder reziproken) Raum:

IE-Methode

Wobei die “Hüte” bedeuten, dass wir uns im Fourier-Raum befinden: h_q(t_n+1) und h_q(t_n) sind die FTs von h(x,y) zu den Zeiten t_n und t_(n+1), N[h_q] ist der nichtlineare Operator, der auf h_q im Fourier-Raum angewendet wird, und L_q ist der lineare Operator, wiederum im Fourier-Raum. Ich möchte nicht zu sehr auf die Details der von mir verwendeten numerischen Methode eingehen, da ich sicher bin, dass das Problem nicht von dort kommt (ich habe versucht, andere Schemata zu verwenden).

Mein Code ist eigentlich ganz einfach. Hier ist der Anfang, wo ich im Wesentlichen Variablen deklariere, Speicher alloziiere und die Pläne für die FFTW-Routinen erstelle.

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

Nach dieser Präambel initialisiere ich meine Funktion h(x,y) mit einem gleichmäßigen Zufallsrauschen und nehme auch die FT davon. Ich setze den Imaginärteil von h(x,y), also dh[i*Ny+j][1] im Code auf 0, da es sich um eine echte Funktion handelt. Dann berechne ich die Wellenvektoren qx und qyund mit ihnen berechne ich den linearen Operator meiner Gleichung im Fourier-Raum, der ist Linft im Code. Ich betrachte nur die -vierte Ableitung von h als linearen Term, sodass die FT des linearen Terms einfach -q^4 ist … aber ich möchte auch hier nicht auf die Details meiner Integrationsmethode eingehen. Die Frage bezieht sich nicht darauf.

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

Dann kommt endlich die Zeitschleife. Im Wesentlichen mache ich folgendes:

  • Hin und wieder speichere ich die Daten in einer Datei und drucke einige Informationen auf dem Terminal aus. Insbesondere drucke ich den höchsten Wert der FT des nichtlinearen Terms. Ich überprüfe auch, ob h(x,y) gegen unendlich divergiert (das sollte nicht passieren!),

  • Berechnen Sie h^3 im direkten Raum (das ist einfach dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0]). Auch hier wird der Imaginärteil auf 0 gesetzt,

  • Nimm die FT von h^3,

  • Ermitteln Sie den vollständigen nichtlinearen Term im reziproken Raum (d. h. N[h_q] im oben beschriebenen IE-Algorithmus) durch Berechnen von -q^2*(FT[h^3] -FT[h]). Im Code beziehe ich mich auf die Zeilen Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0]) und die untere für den Imaginärteil. Ich mache das weil:

Geben Sie hier die Bildbeschreibung ein

  • Mit der IE-Methode in der Zeit vorrücken, in den direkten Raum zurücktransformieren und dann normalisieren.

Hier ist der 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);
  }
}

}

Letzter Teil des Codes: Speicher leeren und FFTW-Pläne zerstören.

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

}

Wenn ich diesen Code ausführe, erhalte ich die folgende Ausgabe:

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!

Der Code stürzt ab, bevor er das Ende erreicht, und wir können sehen, dass der nichtlineare Term divergiert.

Was für mich keinen Sinn ergibt, ist, dass ich die Zeilen, in denen ich die FT des nichtlinearen Terms berechne, folgendermaßen ändere:

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

Das bedeutet, dass ich diese Definition verwende:

Geben Sie hier die Bildbeschreibung ein

statt dieser hier:

Geben Sie hier die Bildbeschreibung ein

Dann ist der Code absolut stabil und es kommt zu keiner Divergenz! Sogar für Milliarden von Zeitschritten! Warum geschieht dies, da die zwei Arten der Berechnung Nonlft sollte gleichwertig sein?

Vielen Dank an alle, die sich die Zeit nehmen, das alles zu lesen und mir etwas zu helfen!

BEARBEITEN: Um die Dinge noch seltsamer zu machen, sollte ich darauf hinweisen, dass dieser Fehler NICHT für dasselbe System in 1D auftritt. In 1D beide Berechnungsmethoden Nonlft sind stabil.

BEARBEITEN: Ich füge eine kurze Animation hinzu, die zeigt, was mit der Funktion h(x,y) kurz vor dem Absturz passiert. Außerdem: Ich habe den Code in MATLAB schnell neu geschrieben, das Fast-Fourier-Transformationsfunktionen basierend auf der FFTW-Bibliothek verwendet, und der Fehler tritt NICHT auf … das Rätsel vertieft sich.
Geben Sie hier die Bildbeschreibung ein

  • Kommentare sind nicht für längere Diskussionen gedacht; diese Konversation wurde in den Chat verschoben.

    – Benutzer229044

    7. Dezember 2018 um 16:42 Uhr

Benutzeravatar von Tropilio
Tropilio

Ich habe es gelöst!! Das Problem war die Berechnung der Nonl Begriff:

  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;

Das muss geändert werden in:

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

Mit anderen Worten: Ich muss überlegen dh als komplexe Funktion (obwohl sie real sein sollte).

Grundsätzlich wegen dummer Rundungsfehler, die IFT der FT einer reellen Funktion (in meinem Fall dh), ist NICHT rein real, hat aber einen sehr kleinen Imaginärteil. Indem man es einstellt Nonl[i*Ny+j][1] = 0.0 Ich habe diesen imaginären Teil völlig ignoriert. Das Problem war also, dass ich rekursiv FT(dh), dhftund ein Objekt, das mit IFT(FT(dh)), das ist Nonlftaber unter Vernachlässigung der restlichen Imaginärteile!

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

Na klar kalkulieren Nonlft wie dh^3 -dh und dann tun

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

Das Problem dieser “gemischten” Summe wurde vermieden.

Puh… was für eine Erleichterung! Ich wünschte, ich könnte das Kopfgeld mir selbst zuweisen! 😛

BEARBEITEN: Ich möchte das hinzufügen, bevor ich die verwende fftw_plan_dft_2d Funktionen, die ich verwendet habe fftw_plan_dft_r2c_2d und fftw_plan_dft_c2r_2d (reell-zu-komplex und komplex-zu-real), und ich sah denselben Fehler. Ich nehme jedoch an, dass ich es nicht hätte lösen können, wenn ich nicht zu gewechselt hätte fftw_plan_dft_2dseit der c2r Funktion “schneidet” automatisch den restlichen Imaginärteil ab, der von der IFT kommt. Wenn dies der Fall ist und ich nichts vermisse, denke ich, dass dies irgendwo auf der FFTW-Website geschrieben werden sollte, um zu verhindern, dass Benutzer auf solche Probleme stoßen. Etwas wie “r2c und c2r Transformationen sind nicht gut, um pseudospektrale Methoden zu implementieren”.

EDIT: Ich habe eine andere SO-Frage gefunden, die adressiert exakt Dasselbe Problem.

  • Ich hatte irgendwie gehofft, es gäbe eine komplizierte Erklärung für Ihr Problem, aber ich bin froh, dass Sie es zumindest herausgefunden haben.

    – BurnsBA

    10. Dezember 2018 um 13:15 Uhr

  • Gut! Von den 3 möglichen Ursachen war es also ein falscher Algorithmus. Je weniger wahrscheinlich für Sie. L’importante è arrivarci…

    – Frankie_C

    10. Dezember 2018 um 15:04 Uhr

  • Ihr Compiler tut das (fast) sicher schon, aber warum, der Lesbarkeit halber, nicht Sie Schreiben all dieser Schleifen als for ( k = 0; k < Nx * Ny; ++k ) { whatever[k][0] = …; whatever[k][1] = …;}?

    – Bob__

    10. Dezember 2018 um 15:36 Uhr

  • @Bob__ Hmm, guter Punkt! Ich schätze, ich mache das, weil ich im Allgemeinen erste Ableitungen nehmen möchte, und das würde bedeuten, Operationen mit zu machen qx[i] und qy[j]. Aber ja, Sie haben absolut Recht, in diesem speziellen Beispiel könnte ich das Schreiben so vereinfachen, wie Sie es vorschlagen.

    – Tropilio

    10. Dezember 2018 um 15:53 ​​Uhr

  • Wenn ich deine Erklärung gelesen habe 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. mein spidey sinn kitzelte. Hätten Sie es nicht schon selbst herausgefunden, wäre das meine erste Vermutung, der ich nachgehen würde. Für diejenigen, die mitlesen: Immer wenn Sie auf ein numerisches Problem mit seltsamen Instabilitäten stoßen, die verschwinden, wenn Sie die Terme unter Verwendung einer Formidentität neu anordnen, dann ist die wahrscheinlichste Ursache, dass die Identität nicht erhalten bleibt, durch “Leaking”. Wenn es zu einem Zwang kommt, müssen Sie sicherstellen, dass Sie sich wieder normalisieren.

    – Datenwolf

    11. Dezember 2018 um 9:50 Uhr


1407720cookie-checkWarum unterscheidet sich FFT von (A+B) von FFT(A) + FFT(B)?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy