細川・林・石田・長谷部 2019...
Post on 24-May-2020
0 Views
Preview:
TRANSCRIPT
プログラミング演習II細川・林・石田・長谷部 2019 2Q
拡散方程式
液体を混ぜる
C v濃度 速度
濃度Cの時間変化を支配する方程式
濃度は位置と時間の関数
Cの移流と拡散を表現する方程式(advection-diffusion equation)
@C
@t+ v ·rC = ↵r2C
拡散係数(diffusion coefficient)
1次元
2次元
C(x, t)
@C
@t+ u
@C
@x= ↵
@2C
@x2
@C
@t+ u
@C
@x+ v
@C
@y= ↵
✓@2C
@x2+
@2C
@y2
◆
1次元拡散方程式
まずは単純な問題から
時間だけでなく位置の関数 をどう扱うか?C(x, t)
計算対象とする領域(computational domain)
x
x = 0 x = 1
C(x, t)
分布は隣接領域に周期的とする (periodic boundary condition)
@C
@t= ↵
@2C
@x2
離散化連続関数のすべての位置における値を取り扱う(保存し、計算する)のは不可能
xx = 0 x = 1
C(x, t)
i = 0 1 2 3 4 5 6 7 8 9x9x8x7x6x5x4x3x2x1x0
限られた場所のCの値のみを保存・計算する
(Cを離散的な値として取り扱う)Cn(xi) Cn
i
C0C1 C2 C3
C4
C5C6 C7
C8C9
位置に通し番号をつける
�x �x �x
const int NX=10; double* C = new double [NX]; delete [] C;
(C++の配列確保・削除)
double C[NX];
でも可。この場合delete無し
(newしたら必ずdelete)
中心差分
5 6 7
C5
C6C7
�x �x
x
@C
@x
����n
i+1/2
!Cn
i+1 � Cni
�x
@C
@x
����n
i�1/2
!Cn
i � Cni�1
�x
@2C
@x2
����n
i
! 1
�x
"@C
@x
����n
i+1/2
� @C
@x
����n
i�1/2
#
中心差分
5 6 7
C5
C6C7
�x �x
x
@C
@x
����n
i+1/2
!Cn
i+1 � Cni
�x
@C
@x
����n
i�1/2
!Cn
i � Cni�1
�x
@2C
@x2
����n
i
!Cn
i+1 � 2Cni + Cn
i�1
�x2
一次精度陽解法 + 中心差分
Cn+1i � Cn
i
�t= ↵
Cni+1 � 2Cn
i + Cni�1
�x2
Cn+1i = Cn
i +↵�t
�x2
�Cn
i+1 � 2Cni + Cn
i�1
�
Cn+1i = Cn
i + d�Cn
i+1 � 2Cni + Cn
i�1
�
d =↵�t
�x2拡散数(diffusion number)
@C
@t= ↵
@2C
@x2
拡散数と数値安定性(概説)
拡散数を変形すると、 d =↵�t
�x2=
↵/�x
�x/�t
数値計算の1ステップである点から隣の点にCの情報が伝達される、と捉えると、 はCの離散情報の伝播速度であり、流束は
�x/�t(�x/�t)C
一方、 から分かるように、 が物理的な流束であり、そのオーダは
r · ↵rC(↵/�x)C
これらの比が拡散数、とみても良い。 物理的な変化よりも離散情報の伝播の方が速くなければ正しく計算できないことは容易に想像できる。つまり、拡散数は1より十分小さくとるべき。 実際、理論的に が安定条件となる。
d <1
2
�↵@C/@x
計算条件の設定
計算領域と分割数の設定
格子点幅が決まる
拡散数を安定条件の範囲で指定
時間刻み幅が決まる dt =d�x2
↵
const int NX=10; const double dx = 1.0/(double)(NX); const double alpha = 1.0; const double dnx = 0.10; const double dt = dnx/(alpha*(1.0/dx/dx));
x 2 [0, 1] Nx = 10
�x = 1/Nx
< 1/2
【新規プロジェクト:03DiffusionEquation】
配列の確保・初期条件設定
double* x = new double [NX]; for(int i=0; i<NX; i++) x[i]=((double)i+0.5)*dx; double* C = new double [NX]; double* Cn = new double [NX]; double* tmp; for(int i=0; i<NX; i++){ C[i]=(cos(2.0*M_PI*x[i])+1.0)*0.5; }
Cは濃度の値保存用の配列。
Cnは差分式でアップデートした値を一旦保存しておく用の配列。
tmpはCnの値をCに付け替えるために利用するポインタ。
C(x, 0) = (cos(2⇡x) + 1)/2
差分式の計算
while(step<stepEnd){ for(int i=0; i<NX; i++){ double cCenter = C[i]; int iw = i - 1; int ie = i + 1; double diffX = dnx*(C[ie] - 2.0*cCenter + C[iw]); Cn[i] = cCenter + diffX; } tmp=C; C=Cn; Cn=tmp;
step++; }
Cn+1i = Cn
i + d�Cn
i+1 � 2Cni + Cn
i�1
�
境界条件の設定:考え方
今回は周期的条件を適用するので、 は 。
Cn+1i = Cn
i + d�Cn
i+1 � 2Cni + Cn
i�1
�
において、 のとき は計算領域外で、プログラムでC[-1]を参照するとエラーが出るか、検討ハズレの値を拾ってくる。
i = 0 Ci�1 = C�1
i = �1 i = Nx � 1
はi = Nx i = 0
x
1 2 3 4 5 6 7 8 9
C0C1 C2 C3
C4
C5C6 C7
C8C9
0
(= Nx)(= �1)
(= Nx � 1)
境界条件の設定:実装
while(step<stepEnd){ for(int i=0; i<NX; i++){ double cCenter = C[i]; int iw = i - 1; int ie = i + 1; if(i == 0 ) iw = NX-1; if(i == NX-1) ie = 0; double diffX = dnx*(C[ie] - 2.0*cCenter + C[iw]); Cn[i] = cCenter + diffX; } tmp=C; C=Cn; Cn=tmp;
step++; }
gnuplotを使ったアニメーション
if(step%1==0){ gnuplot->PlotSymbol("plot (exp(-4*pi*pi*" + std::to_string(alpha*dt*(double)step) + ")*cos(2*pi*x)+1)*0.5,", x, C, NX, step); gnuplot->Flush(); }
理論解をプロット
データをシンボルでプロットプロットの更新
C(x, t) = e�4⇡2↵t(cos(2⇡x) + 1)/2
何step毎に出力するか(適当に調整)
プログラム全景#include <iostream> #include <cmath> #include "GnuplotInterface.h"
int main(int argc, const char * argv[]) { GnuplotInterface* gnuplot = new GnuplotInterface(); gnuplot->SetAxisLabel("x", "x"); gnuplot->SetAxisLabel("y", "C"); gnuplot->SetGraphRange("x", 0.0, 1.0); gnuplot->SetGraphRange("y", 0.0, 1.0); const int NX=10; const double dx = 1.0/(double)(NX); const double alpha = 1.0; const double dnx = 0.10; const double dt = dnx/(alpha*(1.0/dx/dx)); std::cout << "dt: " << dt << std::endl; const double Tend = 0.2; const int stepEnd=(int)(Tend/dt) + 1; double* x = new double [NX]; for(int i=0; i<NX; i++) x[i]=((double)i+0.5)*dx; double* C = new double [NX]; double* Cn = new double [NX]; double* tmp; for(int i=0; i<NX; i++) C[i]=(cos(2.0*M_PI*x[i])+1.0)*0.5; int step=0; gnuplot->PlotSymbol("plot (exp(-4*pi*pi*" + std::to_string(alpha*dt*(double)step) + ")*cos(2*pi*x)+1)*0.5,", x, C, NX, step); gnuplot->Flush(); while(step<stepEnd){ for(int i=0; i<NX; i++){ double cCenter = C[i]; int iw = i - 1; int ie = i + 1; if(i == 0 ) iw = NX-1; if(i == NX-1) ie = 0; double diffX = dnx*(C[ie] - 2.0*cCenter + C[iw]); Cn[i] = cCenter + diffX; } tmp=C; C=Cn; Cn=tmp; step++; if(step%1==0){ gnuplot->PlotSymbol("plot (exp(-4*pi*pi*" + std::to_string(alpha*dt*(double)step) + ")*cos(2*pi*x)+1)*0.5,", x, C, NX, step); gnuplot->Flush(); } } delete [] x; delete [] C; delete [] Cn; delete gnuplot; return 0; }
【課題】中心差分の精度
@2C
@x2
����n
i
!Cn
i+1 � 2Cni + Cn
i�1
�x2
【課題】テイラー展開を用いて中心差分の精度を調べる!
ヒント:C
ni+1 = C
ni +
1
1!
@C
@x
����n
i
�x+1
2!
@2C
@x2
����n
i
�x2 +
1
3!
@3C
@x3
����n
i
�x3 +O(�x
4)
【課題】精度の数値的検証
@2C
@x2
����n
i
!Cn
i+1 � 2Cni + Cn
i�1
�x2
【課題】適当な関数に対して中心差分を適用し、格子幅の減少に伴う誤差の減少率を調べる。その結果が前頁の理論的検討と合っていることを確認する!
例: とすると 。C(x) = eax
を中心として における値を与え、差分を計算。x = 1 x±�x
を小さくしていき、理論値と比較する。�x
d2C(x)/dx2 = a2eax
【課題】精度の数値的検証
2次元拡散方程式
i = 0
j = 0
1 2 3 4
1
2
3
4
Cn+1ij = Cn
ij +↵�t
�x2(Cn
i+1j � 2Cnij + Cn
i�1j) +↵�t
�y2(Cn
ij+1 � 2Cnij + Cn
ij�1)
Cij Ci+1jCi�1j
Cij�1
Cij+1
d = ↵�t
✓1
�x2+
1
�y2
◆
const int NX=10; const int NY=10; const double dx = 1.0/(double)(NX); const double dy = 1.0/(double)(NY); const double alpha = 1.0; const double dnmax = 0.10; const double dt = dnmax/(alpha*(1.0/dx/dx + 1.0/dy/dy)); const double dnx = alpha*dt/dx/dx; const double dny = alpha*dt/dy/dy; double* x = new double [NX]; double* y = new double [NY]; for(int i=0; i<NX; i++) x[i]=((double)i+0.5)*dx; for(int j=0; j<NY; j++) y[j]=((double)j+0.5)*dy; double* C = new double [NX*NY]; double* Cn = new double [NX*NY]; double* tmp; for(int j=0; j<NY; j++){ for(int i=0; i<NX; i++){ int m = NX*j + i; C[m]=sin(M_PI*x[i])*sin(M_PI*(y[j])); } }
1次元配列で二次元データを扱う
C(x, y, 0) = sin⇡x sin⇡y初期条件の例
m = Nx ⇥ j + i 配列インデックス
2次元拡散方程式:結果の出力gnuplotを使った二次元プロット GnuplotInterface* gnuplot = new GnuplotInterface(); gnuplot->SetAxisLabel("x", "x"); gnuplot->SetAxisLabel("y", "y"); gnuplot->Injection("set cbrange [0:1]\n"); gnuplot->Injection("set size square\n");
...
if(step%1==0){ gnuplot->PlotMap("splot", x, y, C, NX, NY, step); gnuplot->Flush(); }
GnuplotInterface* gnuplot = new GnuplotInterface(); gnuplot->SetAxisLabel("x", "x"); gnuplot->SetAxisLabel("y", "y"); ...
if(step%1==0){ gnuplot->PlotMesh("splot", x, y, C, NX, NY, step); gnuplot->Flush(); }
1次元拡散方程式:陰解法
d =↵�t
�x2
@C
@t= ↵
@2C
@x2
Cn+1i � Cn
i
�t= ↵
Cn+1i+1 � 2Cn+1
i + Cn+1i�1
�x2
�dCn+1i�1 + (1 + 2d)Cn+1
i � dCn+1i+1 = Cn
i
3つとも未知数のため、単純な代入計算では解は求まらない
陰解法:連立方程式
�dCn+1Nx�1 + (1 + 2d)Cn+1
0 � dCn+11 = Cn
0
�dCn+10 + (1 + 2d)Cn+1
1 � dCn+12 = Cn
1
�dCn+11 + (1 + 2d)Cn+1
2 � dCn+13 = Cn
2
�dCn+1Nx�2 + (1 + 2d)Cn+1
Nx�1 � dCn+10 = Cn
Nx�1
...
各格子点について差分式を書き下すと次のようになる。
Nx個の方程式と未知数からなる連立方程式なので、線形代数の方法(ガウスの消去法)を適用すれば解ける。 ただし、その手の方法は、このように係数行列の要素がほとんど0の場合には非常に非効率。 反復法と呼ばれる方法を使う
連立方程式の反復解法:例方程式・未知数が3つだけの小規模な問題を考える。
解はわからないが、適当な推定値を与える。たとえば、
C(0)0 = C(0)
1 = C(0)2 = 0
上付添字(0)は最初(0回目)に推定された値、という意味。
i = 0 :
i = 1 :
i = 2 :
ここから、ある計算を繰り返す(反復(iterate)する)ことで、推定値を正しい答え(解)に近づけていく!
5C0 + 2C1 + (0)C2 = 4
C0 + 6C1 + 3C2 = 2
(0)C0 + C1 + 2C2 = 4
連立方程式の反復解法:例
隣接点i-1, i+1の現在の推定値(0)を代入して、中心の点iの次の推定値(1)を求める!
i = 0 :
i = 1 :
i = 2 :
5C0 + 2C1 = 4 ! 5C(1)0 = 4� 2C(0)
1
(0)C0 + C1 + 2C2 = 4 ! 2C(1)2 = 4� C(0)
1
新しい推定値(1)を使って、同じ計算をもう一度行えば次の推定値(2)が求まる。一般にk+1番目の推定値は、
C(k+1)2 = (4� C(k)
1 )/2
C(k+1)0 = (4� 2C(k)
1 )/5
C0 + 6C1 + 3C2 = 2 ! 6C(1)1 = 2� (C(0)
0 + 3C(0)2 )
C(k+1)1 = [2� (C(k)
0 + 3C(k)2 )]/6
【課題】連立方程式の反復解法:小規模問題
C(k+1)2 = (4� C(k)
1 )/2
C(k+1)0 = (4� 2C(k)
1 )/5
反復計算を繰り返すと次第に正しい答えに近づく(と期待している)が、「倍精度で表現できる範囲で完全に正しい答え」まで繰り返す必要はない(し、大きい問題では非現実的)。そこで、自分が必要としている精度の解が得られたら反復を打ち切る(計算終了)。
1回反復する際に、未知数の変化量を計算し、各未知数の変化量のうち最大のものを調べる。その値が自分で設定した数値よりも小さければ、反復を終了すればよい。
【課題】この問題を反復法で解く!
C(k+1)1 = [2� (C(k)
0 + 3C(k)2 )]/6
#include <iostream> #include <cmath>
int main(int argc, const char * argv[]) { int NX = 3; double* C = new double [NX]; double* Cn = new double [NX]; double* Aw = new double [NX]; double* Ap = new double [NX]; double* Ae = new double [NX]; double* S = new double [NX]; Aw[0] = 0.0; Ap[0] = 5.0; Ae[0] = 2.0; Aw[1] = 1.0; Ap[1] = 6.0; Ae[1] = 3.0; Aw[2] = 1.0; Ap[2] = 2.0; Ae[2] = 0.0; S[0] = 4.0; S[1] = 2.0; S[2] = 4.0; C[0] = 0.0; C[1] = 0.0; C[2] = 0.0; double tolerance = 1.0e-6; double dcmax = 1.0e30; int iteration = 0; while(dcmax > tolerance){ for(int i=0; i<NX; i++){ double Cw = 0.0; double Ce = 0.0; if(i != 0) Cw = C[i-1]; if(i != NX-1) Ce = C[i+1]; Cn[i] = (S[i] - (Aw[i]*Cw + Ae[i]*Ce))/Ap[i]; } dcmax = 0.0; for(int i=0; i<NX; i++){ double delta = fabs(C[i] - Cn[i]); if(delta > dcmax) dcmax = delta; C[i] = Cn[i]; } iteration++; std::cout << "iteration: " << iteration << std::endl; for(int i=0; i<NX; i++) std::cout << C[i] << std::endl; } delete [] C; delete [] Cn; delete [] Aw; delete [] Ap; delete [] Ae; delete [] S; return 0; }
【課題】連立方程式の反復解法:小規模問題
【課題】1次元拡散方程式:陰解法
【課題】1次元拡散方程式を陰解法で解く!
*この解法をヤコビ法(Jacobi method)という
top related