私はほぼ1か月間、非常に奇妙なバグと戦ってきました。皆さんにお尋ねすることは私の最後の希望です。フーリエ(または逆数)空間でImplicit Euler(IE)スキームを使用して、2d Cahn–Hilliard方程式 を統合するプログラムをCで作成しました。
「帽子」とは、フーリエ空間にいることを意味します。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に設定します。次に、波ベクトルqx
およびqy
を計算し、それらを使用して、フーリエ空間で方程式の線形演算子を計算します。これは、コードのLinft
です。 hの-4次導関数のみを線形項と見なすため、線形項の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])
行と以下の行を参照しています。
コードは次のとおりです。
_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
を計算する2つの方法は同等であるため、なぜこれが起こるのですか?
このすべてを読んで私に助けを与えてくれて時間を割いてくれる人に感謝します!
編集:物事をさらに奇妙にするために、このバグは1Dの同じシステムでは発生しないことを指摘する必要があります。 1Dでは、Nonlft
を計算する両方の方法が安定しています。
編集:クラッシュする直前に関数h(x、y)に起こることの短いアニメーションを追加します。また、FFTWライブラリに基づく高速フーリエ変換関数を使用するMATLABのコードをすばやく書き直しましたが、バグは発生していません...謎が深まりました。
解決しました!!問題は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
を複雑な関数と見なす必要があります(実際のはずですが)。
基本的に、愚かな丸め誤差のため、実関数のFTのIFT(私の場合は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];
この「混合」合計を行う問題を回避しました。
うーん...そんな安心!賞金を自分に割り当てることができたらいいのに! :P
編集:fftw_plan_dft_2d
関数を使用する前に、fftw_plan_dft_r2c_2d
およびfftw_plan_dft_c2r_2d
(real-to-complexおよびcomplex-to-real)を使用していたことを追加します。同じバグを見ていました。ただし、fftw_plan_dft_2d
関数はIFTからの残りの虚数部を自動的に「切り落とす」ため、c2r
に切り替えなければ解決できなかったと思われます。 これが事実であり、私が何かを逃していない場合、ユーザーがこのような問題に遭遇するのを防ぐために、これをFFTWウェブサイトのどこかに書く必要があると思います「r2c
およびc2r
変換のようなものは、擬似スペクトルメソッドの実装には適していません」。
編集: another SO question アドレスexactly同じ問題が見つかりました。