Je me bats avec un bug très bizarre depuis près d'un mois. Vous demander est mon dernier espoir. J'ai écrit un programme en C qui intègre le 2d équation de Cahn – Hilliard en utilisant le schéma d'Euler implicite (IE) dans l'espace de Fourier (ou réciproque):
Où les "chapeaux" signifient que nous sommes dans l'espace de Fourier: h_q (t_n + 1) et h_q (t_n) sont les FT de h (x, y) aux temps t_n et t_ (n + 1), N [h_q] est l'opérateur non linéaire appliqué à h_q, dans l'espace de Fourier, et L_q est linéaire, toujours dans l'espace de Fourier. Je ne veux pas trop entrer dans les détails de la méthode numérique que j'utilise, car je suis sûr que le problème ne vient pas de là (j'ai essayé d'utiliser d'autres schémas).
Mon code est en fait assez simple. Voici le début, où fondamentalement je déclare des variables, alloue de la mémoire et crée les plans pour les 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);
Après ce préambule, j'initialise ma fonction h (x, y) avec un bruit aléatoire uniforme, et j'en prends aussi le FT. J'ai mis la partie imaginaire de h (x, y), qui est dh[i*Ny+j][1]
Dans le code, à 0, car c'est une fonction réelle. Puis je calcule les vecteurs d'onde qx
et qy
, et avec eux, je calcule l'opérateur linéaire de mon équation dans l'espace de Fourier, qui est Linft
dans le code. Je considère uniquement la dérivée - quatrième de h comme le terme linéaire, de sorte que le FT du terme linéaire est simplement -q ^ 4 ... mais encore une fois, je ne veux pas entrer dans les détails de ma méthode d'intégration. La question n'est pas là-dessus.
// 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];
}
}
Puis, enfin, vient la boucle temporelle. Essentiellement, ce que je fais est le suivant:
De temps en temps, j'enregistre les données dans un fichier et j'imprime certaines informations sur le terminal. En particulier, j'imprime la valeur la plus élevée du FT du terme non linéaire. Je vérifie également si h (x, y) divergent à l'infini (cela ne devrait pas arriver!),
Calculez h ^ 3 dans l'espace direct (c'est simplement dh[i*Ny+j][0]*dh[i*Ny+j][0]*dh[i*Ny+j][0]
). Encore une fois, la partie imaginaire est mise à 0,
Prenez le FT de h ^ 3,
Obtenez le terme non linéaire complet dans l'espace réciproque (c'est-à-dire N [h_q] dans l'algorithme IE écrit ci-dessus) en calculant -q ^ 2 * (FT [h ^ 3] - FT [h] Dans le code, je me réfère aux lignes Nonlft[i*Ny+j][0] = -Q2[i*Ny+j]*(Nonlft[i*Ny+j][0] -dhft[i*Ny+j][0])
et celle ci-dessous, pour la partie imaginaire. Je le fais parce que:
Voici le 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);
}
}
}
Dernière partie du code: vider la mémoire et détruire les 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;
}
Si j'exécute ce code, j'obtiens la sortie suivante:
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!
Le code se bloque avant d'atteindre la fin et nous pouvons voir que le terme non linéaire est divergent.
Maintenant, ce qui n'a pas de sens pour moi, c'est que si je change les lignes dans lesquelles je calcule le FT du terme non linéaire de la manière suivante:
// 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];
}
}
Ce qui signifie que j'utilise cette définition:
au lieu de celui-ci:
Ensuite, le code est parfaitement stable et aucune divergence ne se produit! Même pour des milliards de pas de temps! Pourquoi cela se produit-il, puisque les deux façons de calculer Nonlft
devraient être équivalentes?
Merci beaucoup à tous ceux qui prendront le temps de lire tout cela et de m'aider!
EDIT: Pour rendre les choses encore plus étranges, je dois souligner que ce bug ne se produit PAS pour le même système dans 1D. Dans 1D, les deux méthodes de calcul de Nonlft
sont stables.
EDIT: J'ajoute une courte animation de ce qui arrive à la fonction h (x, y) juste avant de planter. Aussi: j'ai rapidement réécrit le code dans MATLAB, qui utilise des fonctions de transformation de Fourier rapide basées sur la bibliothèque FFTW, et le bug ne se produit PAS ... le mystère s'approfondit.
Je l'ai résolu !! Le problème était le calcul du terme 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;
Cela doit être changé pour:
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];
En d'autres termes: je dois considérer dh
comme une fonction complexe (même si elle doit être réelle).
Fondamentalement, à cause d'erreurs d'arrondi stupides, l'IFT du FT d'une fonction réelle (dans mon cas dh
), n'est PAS purement réel , mais aura une très petite partie imaginaire. En définissant Nonl[i*Ny+j][1] = 0.0
J'ignorais complètement cette partie imaginaire. Le problème était donc que je sommais récursivement FT (dh
), dhft
, et un objet obtenu à l'aide de l'IFT (FT (dh
)), c'est Nonlft
, mais en ignorant les parties imaginaires résiduelles!
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]);
De toute évidence, calculer Nonlft
comme dh
^ 3 -dh
puis faire
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];
Évité le problème de faire cette somme "mixte".
Ouf ... un tel soulagement! J'aimerais pouvoir m'attribuer la prime! : P
EDIT: Je voudrais ajouter cela, avant d'utiliser le fftw_plan_dft_2d
fonctions, j'utilisais fftw_plan_dft_r2c_2d
et fftw_plan_dft_c2r_2d
(du réel au complexe et du complexe au réel), et je voyais le même bogue. Cependant, je suppose que je n'aurais pas pu le résoudre si je ne suis pas passé à fftw_plan_dft_2d
, puisque le c2r
la fonction "coupe" automatiquement la partie imaginaire résiduelle provenant de l'IFT. Si tel est le cas et que je ne manque rien, je pense que cela devrait être écrit quelque part sur le site Web de la FFTW, pour empêcher les utilisateurs de rencontrer des problèmes comme celui-ci. Quelque chose comme "r2c
et c2r
les transformations ne sont pas bonnes pour implémenter des méthodes pseudospectrales ".
EDIT: J'ai trouvé n autre SO question qui résout exactement le même problème.