プログラミング演習II細川・林・石田・長谷部 2019 2Q
本演習の内容と目的
以下の内容を講義・演習する • 質点の運動に関するシミュレーション • 拡散方程式の数値解法 • 移流方程式の数値解法 • 有限要素法
目的 • 物理現象を数値計算するための基礎知識を理解し • 数値計算のためのプログラム開発能力を修得する
講義日程
A(火曜)班
6/11 質点運動
6/18 質点運動
6/25 拡散方程式
7/2 拡散方程式
7/9 移流方程式
7/16 移流方程式
7/23 有限要素法
B(水曜)班
6/12 質点運動
6/19 質点運動
6/24 拡散方程式
7/3 拡散方程式
7/10 移流方程式
7/17 移流方程式
7/24 有限要素法
質点の運動
重力による質点の落下
dv(t)
dt= g
x
x = 0
g
Z t2
t1
dv(t)
dtdt =
Z t2
t1
gdt
v(t2) = v(t1) + g(t2 � t1)
時間経過後の質点の速度を知りたいなら、運動方程式を積分すればよい
t
vv(t)
t1 t2
g(t2 � t1)
空気抵抗がある場合
v(t2) = v(t1) + g(t2 � t1)
を、未来の速度 = 今の速度 + 加速度x経過時間 と捉えよう
t
v
t1 t2
VT
空気抵抗により加速度が時間的に変化(速度に依存)する問題にも使える?
g
dv
dt= g � �
mv(t)
ϒ:空気抵抗の係数終端速度
v(t) =mg
�
⇣1� e�
�m t
⌘
少し先の未来を知り、繰り返す
t
v
v(t)
t1 t2
VT
経過時間を細切れにして、少し先の未来の速度を求め、それを繰り返してずっと先の未来を知ることはできるのではないか?
g
�t�t�t
v(t+�t) = v(t) +hg � �
mv(t)
i�t
時間刻み幅(time step size)
同じ計算を何度もする。。。コンピュータを使う!
差分式(differential equation)
プログラミングの前に。。。:無次元化
式を無次元化しておく。
v(t+�t) = v(t) +hg � �
mv(t)
i�t
とおく
(以下ではチルダは省略)
�
mgv(t+�t) =
�
mgv(t) +
1� �
mgv(t)
���t
m
v =�v
mgf�t =
��t
m
v(t+ f�t) = v(t) +�1� v(t)
� f�t
t =�t
m
プログラミングの前に。。。:時間刻み幅の設定
無次元化した厳密解(exact solution)(チルダ省略)
t
v(t) = 1� e�t
v
1� e�t = 0.63t = 1
t = 1
1� e�t = 0.63
のとき
は時定数よりも小さくないと、 速度の変化を再現できないと考えられる
時定数 (time constant)
⌧ = 1
�t �t = 0.1を試す
Xcode (1)
コレ
新規プロジェクトの作成
Xcode (2)
mac OS > Command Line Tool
コレ
注:XcodeのVer.によって多少見え方が違います
Xcode (3)
Language: C++
Product name: 01FallingParticle
Location: デスクトップに講義用フォルダを作成して入れる ファイル名・ディレクトリ名は半角英数が無難!!!!
Xcode (4)
実行・書出ファイルが同じディレクトリに出るようにする
XcodeメニューのPreferences…からLocationsタブ選択、Derived DataをRelativeにする
コレ
コレと
とりあえずmain.cppをBuild&Run(1)この中のBuild
Succeededと出ればOK(2)Run
(3)下のConsoleからHelloが来ればOK
#include <iostream>
int main(int argc, const char * argv[]) { // insert code here... std::cout << "Hello, World!\n";
return 0; }
main.cppの編集
不要なので消す
#include <iostream>
int main(int argc, const char * argv[]) { double v0 = 0.0; double dt = 0.1;
double v = v0; return 0; }
代わりにこれを入れる
�t = 0.1v0 = 0 v(0) = v0
#include <iostream>
int main(int argc, const char * argv[]) { double v0 = 0.0; double dt = 0.1;
double v = v0; std::cout << "v: " << v << std::endl;
return 0; }
main.cpp: 値の確認
これを入れる
Build, Runし、Consoleに”v: 0.0”がでればOK
標準出力
Cではprintf関数を使いますが、C++ではstd::coutを使います。
std::coutに<<で続けて出力したい文字列や変数を書いていくだけで簡単。
std::endlは改行命令です(end of line)
stdはstandardの略です。とりあえず気にしない。
#include <iostream> int main(int argc, const char * argv[]) {
double v0 = 0.0; double dt = 0.1;
double v = v0; std::cout << "v: " << v << std::endl; v = v + (1.0 - v)*dt; std::cout << "v: " << v << std::endl;
return 0; }
数値計算を始める!
早速、未来の速度を知る手続きを始める
これを入れる
Build, Runし、vの変化を確認せよ!
#include <iostream> int main(int argc, const char * argv[]) {
double v0 = 0.0; double dt = 0.1;
double v = v0; int stepEnd = 100; int step = 0; std::cout << "v: " << v << std::endl; while(step < stepEnd){ v += (1.0 - v)*dt; step++; std::cout << "v(" << dt*(double)step << ") = " << v << std::endl;
} return 0; }
もっと先まで計算する
変更箇所
Build, Runし、vの変化を確認せよ!
(注:これは1行です)
指定した回数だけ計算させる
step = step + 1v += ! v = v +
t = �t⇥ step
gnuplot用データ出力まずはスペース区切りで値のみ出力するように変更
#include <iostream>
int main(int argc, const char * argv[]) { double v0 = 0.0; double dt = 0.1;
double v = v0; int stepEnd = 100; int step = 0;
std::cout << dt*(double)step << " " << v << std::endl; while(step < stepEnd){
v += (1.0 - v)*dt; step++;
std::cout << dt*(double)step << " " << v << std::endl; }
return 0; }
gnuplot用データ出力:Terminalで実行
Terminalを起動
cd (change directory)で実行ファイルの場所まで移動
(1)cd と打ったあと、
(2)ファインダーから”Debug”
フォルダをdrag & drop
(3)Enterして、pwdで現ディレクトリを確認
(……/Debug/になっているはず)
gnuplot用データ出力:Terminalで実行
Terminal内で実行する
$./01FallingParticle
Terminal内で実行し、結果をファイルに出力する
$./01FallingParticle > data.txt
ファイルができていることと内容を確認する
【参考】UnixコマンドTerminalで使うコマンド【この講義で使うもの】
pwd:現ディレクトリのフルパスを表示(print working directory)
cd: ディレクトリの移動(change directory)
ls: 現作業ディレクトリ内のファイル一覧を表示
相対パス
./ 現作業ディレクトリの意味
../ 現作業ディレクトリのひとつ上のディレクトリを指す
例えば、 cd ../ はひとつ上のディレクトリに移動 【他の基本コマンド】
cp: ファイルコピー(cp data.txt data_copy.txtとか)
mv: ファイルの移動、ファイル名変更(mv data.txt ../)とか
rm: ファイル消去
gnuplot
Terminalからgnuplotを起動(ディレクトリを変えずに)
$gnuplot
gnuplotが起動したらデータファイルから描画
>set xlabel “t” >set ylabel “v” >plot “data.txt” title ‘v’
gnuplot:理論解との比較(定性的)
理論解を重ねてプロット
>plot “data.txt” title ‘v’, 1-exp(-x)
期待通り!(でもちょっとずれてる?)
gnuplot:結果の保存
PDFで描画結果を保存できます。File > Save as
誤差の評価 ・ここからまたXcodeで
数学ライブラリの読み込み(exp使用のため)
画面出力の関数化と誤差評価
t = 1までに変更Output関数で出力
e(t) =
����v(t)� vex(t)
vex(t)
����
#include <iostream> #include <cmath>
double Output(double t, double v, bool display){ double vExact = 1.0 - exp(-t); double error = (v - vExact)/vExact; if(vExact == 0.0) error = 0.0; if(display == true) std::cout << t << ": " << v << ", " << vExact << ", " << error << std::endl; return fabs(error); };
int main(int argc, const char * argv[]) { double v0 = 0.0; double dt = 0.1; double v = v0; int stepEnd = 10; int step = 0; Output(dt*(double)step, v, true); while(step < stepEnd){ v += (1.0 - v)*dt; step++; Output(dt*(double)step, v, true); } return 0; }
gnuplotを計算中に呼び出す
計算結果をファイルに入れてからgnuplotで描画するのは手間
pipeという機能を使って、計算しながらgnuplotを使う
計算を実行している端末(のプロセス)から
gnuplotを起動するための端末(子プロセス)を作るイメージ
GnuplotInterfaceの利用
GnuplotInterface.h & .cpp:pipe関連の命令を記述したプログラム
www2.kobe-u.ac.jp/~hayashiダウンロード
にファイルを移動(main.cppと同じところ)
/01FallingParticle/01FallingParticle/
ファイルを2つとも ここにdrag & drop
利用するための手続き in main.cpp
#include <iostream> #include <cmath> #include “GnuplotInterface.h"
double Output(double t, double v, bool display){...}
int main(int argc, const char * argv[]) { GnuplotInterface* gnuplot = new GnuplotInterface(); double v0 = 0.0; double dt = 0.1; double v = v0; int stepEnd = 10; int step = 0; Output(dt*(double)step, v, true); while(step < stepEnd){ v += (1.0 - v)*dt; step++; Output(dt*(double)step, v, true); } delete gnuplot; return 0; }
読み込み
利用開始の手続き
利用終了の手続き
(gnuplotに情報を流す仲介役)
同プロジェクト内にあるheaderファイルを読む場合 #include “” (<>ではない)
結果の描画
軸ラベルの設定
‘-’はデータ待ち受け開始の意味
データを投入
eはデータ待ち受け終了
#include <iostream> #include <cmath> #include "GnuplotInterface.h"
double Output(double t, double v, bool display){...};
int main(int argc, const char * argv[]) { GnuplotInterface* gnuplot = new GnuplotInterface(); gnuplot->SetAxisLabel("x", "t"); gnuplot->SetAxisLabel("y", "v"); double v0 = 0.0; double dt = 0.1; double v = v0; int stepEnd = 10; int step = 0; Output(dt*(double)step, v, true); gnuplot->Injection("plot '-' title 'predicted', 1-exp(-x) \n"); gnuplot->Injection(dt*(double)step, v); while(step < stepEnd){ v += (1.0 - v)*dt; step++; Output(dt*(double)step, v, true); gnuplot->Injection(dt*(double)step, v); } gnuplot->Injection("e\n"); delete gnuplot; return 0; }
注:’はPDFからのcopy&pasteだと化ける可能性あります
+ キーshift 7
【課題】(1)誤差の分析I・(2)差分式の再検討
を小さくすれば、予測の精度が上がるのではないだろうか?�t• dt = 0.1 • dt = 0.01 • dt = 0.001
dt と stepEndを変更して、
の場合のt = 1における誤差を比較・誤差とdtの関係を検討する!
(1)誤差の分析
横軸に dt 、縦軸に t=1における誤差 をとり、gnuplotで描画すること。
(2)差分式の再検討
ここは としてもよいのではないか?v(t+�t)
その差分式を用いて計算し、dt = 0.1での誤差を前回と比較する。右辺の速度を に変え、この速度で解き直し、差分式を得る。
【新規プロジェクト:01FallingParticleImplicit】
x1, y1 x2, y2 x3, y3
v(t+�t)
結果を のようにテキストファイルに貼り、Terminalで起動したgnuplotで読み込む(plot “data.txt”)。set logscale xで軸を対数にする。(yも)
v(t+�t) = v(t) + (1� v(t))�t
【課題】(1)誤差の分析I
(1)誤差の分析
�t1
1/10
1/10
【課題】(2)差分式の再検討
(2)差分式の再検討【新規プロジェクト:01FallingParticleImplicit】
#include <iostream> #include <cmath> #include "GnuplotInterface.h"
void Output(double t, double v, bool display){...};
int main(int argc, const char * argv[]) { ... while(step < stepEnd){ v = (v + dt)/(1.0 + dt); step++; Output(dt*(double)step, v, true); gnuplot->Injection(dt*(double)step, v); } ... }
1行変えるだけ!
v(t+�t) =v(t) +�t
1 +�t
【課題】(2)差分式の再検討
(2)差分式の再検討
v(t+�t) = v(t) + (1� v(t))�t
v(t+�t) =v(t) +�t
1 +�t
誤差の検討
誤差が に比例していることが分かった。その原因は?�t1
テイラー展開により、
v(t+�t) = v(t) + (1� v(t))�t差分式 に代入
元の微分方程式にはない項。主要項は に比例 差分式はこの余計な項がある方程式を解いている!
�t1
この方法は一次精度(1st-order accuracy)である。
v(t+�t) = v(t) +1
1!
dv
dt
����t
�t+1
2!
d2v
dt2
����t
�t2 +O(�t
3)
v(t) +1
1!
dv
dt
����t
�t+1
2!
d2v
dt2
����t
�t2 +O(�t
3) = v(t) + (1� v(t))�t
dv
dt
����t
= 1� v(t) +
� 1
2!
d2v
dt2
����t
�t1 +O(�t
2)
�
【課題】(3)誤差の分析II
差分式
についても同様に誤差のオーダを調べる。
v(t+�t) =v(t) +�t
1 +�t
時間の表記法
t = n�t
これまでのプログラムでもすでに見たように、時間は
と書けるので、時間を表すには何回目の計算時点か(つまりn)を示せば十分。そこでこれ以降は
vn+1 = v(t+�t)
vn = v(t)
にように上付添字で時間を表すことにする(n乗ではないので注意)。
差分式の分類
一般に、 に対して、df
dt= g(f)
と右辺を既知の値で評価するものを陽解法(explicit scheme)という。
一方、未知の値を使うものは陰解法(implicit scheme)という。
【課題】前の2つの方法を陽解法か陰解法に分類する。
fn+1 � fn
�t= g(fn±k, fn) (k > 0)
fn+1 � fn
�t= g(fn�k, fn) (k > 0)
二次精度の方法
陽解法
の誤差はともに に比例していたが、誤差の主要項の符号は逆
両者を組み合わせることでうまく誤差を低減できないか?
vn+1 = vn + (1� vn)�t
vn+1 = vn + (1� vn+1)�t
陰解法
�t1
Crank-Nicolson法 fn+1 � fn
�t=
g(fn+1) + g(fn)
2
Crank-Nicolson法
vn+1 � vn
�t=
1
2
⇥(1� vn+1) + (1� vn)
⇤
vn+1 =vn +
�1� 1
2vn��t
1 + �t2
#include <iostream> #include <cmath> #include "GnuplotInterface.h"
void Output(double t, double v, bool display){...};
int main(int argc, const char * argv[]) { ... while(step < stepEnd){ v = (v + (1.0 - v*0.5)*dt)/(1.0 + dt*0.5); step++; Output(dt*(double)step, v, true); gnuplot->Injection(dt*(double)step, v); } ... }
【課題】Crank-Nicolson法の精度
かなり改善されている!
【課題】Crank-Nicolson法が二次精度であることをテイラー展開で示す!
Adams-Bashforth法・Runge-Kutta法
Crank-Nicolson法は未来の値を使うので陰解法。 二次精度の陽解法は?
fn+1 � fn
�t=
3g(fn)� g(fn�1)
2
f (1) � fn
�t/2= g(fn)
fn+1 � fn
�t= g(f (1))
Adams-Bashforth法
Runge-Kutta法(多段階法)
注:最初のステップは古い値が分からないので、次のRunge-Kuttaなどを使う
1回目の計算で中間段階の速度を求める もう一度計算し、速度を更新する
【課題】二次精度陽解法の精度検証
【課題】Adams-Bashforth法を質点の落下問題に適用し、差分法を得る。
【課題】Adams-Bashforth法を用いて質点の速度変化を計算し、二次精度であることを検証する。
【課題】Runge-Kutta法を質点の落下問題に適用し、差分法を得る。
【課題】Runge-Kutta法を用いて質点の速度変化を計算し、二次精度であることを検証する。
(進んだ問題:テイラー展開を用いて両者が二次精度であることを示す)
【課題】二次精度陽解法の精度検証
【課題】二次精度陽解法の精度検証
�t1
�t2
【課題】数値安定性(Numerical stability)
は時定数よりも小さくないと、 速度の変化を再現できないと考えられる�t �t = 0.1
を試す
これまで、
としてきたが、時定数と同程度、さらには時定数よりも大きい時間刻み幅を使うと何がおきるだろう?
各手法で時間刻み幅を大きくしたとき何が起きるか確かめる
gnuplotで複数の計算結果を同時プロットしたい人へ(1/4)
例えば、陽解法の計算結果(時間と速度のデータ)と、陰解法の計算結果を同時にプロットしたい場合、
• 陽解法で計算し、結果をファイル”explicit.txt”に出力する。
• 陰解法で計算し、結果をファイル”implicit.txt”に出力する。 • 結果のファイルを同じ場所に置いておく。 • Terminalを開き、結果のファイルがある場所に移動する。
• gnuplotをTerminalで起動する。
• plot ‘explicit.txt’, ‘implicit’, 1-exp(-x) でグラフを描画する。
ファイルへの出力は、1回目に習ったTerminalで’ > ’を使って実行する方法で出来る。プログラムで出力する場合は次頁を参照。
gnuplotで複数の計算結果を同時プロットしたい人へ(2/4)
std::ofstreamを使うと、C言語のfprintfみたいにファイルに書き込める。
int main(int argc, const char * argv[]) { std::ofstream output("data.txt"); ... output << dt*(double)step << " “ << v << " “ << std::endl; while(step < stepEnd){ ... step++;
output << dt*(double)step << " “ << v << " “ << std::endl; } ... return 0; }
ファイルを扱うための変数名(自分で決めていい)作成するファイル名(自分で決めていい)
std::coutのかわりに、自分で決めたofstreamの変数名を書く
(Terminalで’>’実行せずに、Xcodeでファイル出力まで完結できます)
gnuplotで複数の計算結果を同時プロットしたい人へ(3/4)
以下、gnuplotまで含めてXcodeで完結したい人向け。
gnuplotでこの3列のデータから2つのプロットを作るには、
プログラムで陽解法と陰解法の結果をひとつのファイルにまとめて出力。(「時刻、陽解法結果、陰解法結果」を縦に並べたテキストファイル)
plot 'data.txt' using 1:2 title 'explicit', '' using 1:3 title 'implicit'
1,2列目のデータを使うという意味 前(‘data.txt’)のデータを使うという意味
一回の計算で陽解法と陰解法の計算結果を取り扱うので、vを配列で定義しておくと便利(Crank-Nicolsonとかを同じ方法で追加できる)。C言語と同じように配列を利用できるが、std::vector<型名>も便利。 std::vector<double> v {v0, v0}; output << dt*(double)step << " "; for(int i=0; i<v.size(); i++) output << v[i] << " "; output << std::endl;
double型ベクターの宣言・初期化例ベクターvの値をoutputに出力
gnuplotで複数の計算結果を同時プロットしたい人へ(3/4)
...
double Output(double t, double v, bool display){...};
int main(int argc, const char * argv[]) { std::ofstream output("data.txt"); GnuplotInterface* gnuplot = new GnuplotInterface(); gnuplot->SetAxisLabel("x", “t"); gnuplot->SetAxisLabel("y", "v"); double v0 = 0.0; double dt = 0.1; std::vector<double> v {v0, v0}; int stepEnd = 10; int step = 0; output << dt*(double)step << " "; for(int i=0; i<v.size(); i++) output << v[i] << " "; output << std::endl; while(step < stepEnd){ for(int i=0; i<v.size(); i++){ switch (i) { case 0: v[i] += (1.0 - v[i])*dt; break; case 1: v[i] = (v[i] + dt)/(1.0 + dt); break; default: break; } } step++; output << dt*(double)step << " "; for(int i=0; i<v.size(); i++) output << v[i] << " "; output << std::endl; } gnuplot->Injection("set key left top\n"); gnuplot->Injection("plot 'data.txt' using 1:2 title 'explicit', '' using 1:3 title 'implicit', 1-exp(-x)\n"); for(int i=0; i<v.size(); i++) std::cout << Output(dt*(double)step, v[i], false) << ", "; delete gnuplot; return 0; }
switchで陽解法と陰解法を分岐
ofstreamでファイル出力準備
std::vectorで配列準備
ファイル出力
ファイル出力
gnuplot
2階線形常微分方程式:振動問題
バネに繋がれた質点とみなせる物体の振動問題を考える
md2x
dt2= �kxx自然長での位置
F = �kxフックの法則 (k > 0)
物体の位置 を計算するには の時間に関する2階微分に対する差分を考える?これまでに学んだ1階微分の差分解法でどうすれば?
x x
連立1階線形常微分方程式として捉える
dx
dt= v
このように書いてみると、未知数 の連立1階常微分方程式。 1階なので、それぞれの式には既に修得した方法が使える!
x, v
dv
dt= �!2x ! =
rk
m
一次精度の陽解法を適用
dx
dt= v
dv
dt= �!2x
xn+1 � xn
�t= vn xn+1 = xn + vn�t
vn+1 � vn
�t= �!2xn vn+1 = vn � !2xn�t
プログラム開発のポイント • 変数はxとvの2つ必要 • 1ステップの計算で2つの変数をアップデート
問題設定と時間刻み幅の検討x(0) = x0 = 1
v(0) = v0 = 0
! = 1
x(t) = cos(!t)
v(t) = �! sin(!t)
(この際単位は気にしないでいきましょう。。)
T
周期 の に対してせめて10ステップはとろうT/4
�t =T
4⇥ 1
10
T/4
T = 2⇡/!
一次精度の陽解法によるプログラムを開発#include <iostream> #include <cmath> #include "GnuplotInterface.h"
int main(int argc, const char * argv[]) { GnuplotInterface* gnuplot = new GnuplotInterface(); gnuplot->SetAxisLabel("x", "t"); gnuplot->SetAxisLabel("y", "x"); double x0 = 1.0; double v0 = 0.0; double omega = 1.0; double omega2= omega*omega; double x = x0; double v = v0; double T = 2.0*M_PI/omega; double dt = (T/4.0)*0.1; gnuplot->SetGraphRange("x", 0.0, T); int stepEnd = (int)(T/dt) + 1; int step = 0; gnuplot->Injection("set key left bottom\n"); gnuplot->Injection("plot '-' title 'predicted x', cos(x), 0 \n"); gnuplot->Injection(dt*(double)step, x); while(step < stepEnd){ double xtmp = x + v*dt; double vtmp = v - omega2*x*dt; x = xtmp; v = vtmp; step++; gnuplot->Injection(dt*(double)step, x); } gnuplot->Injection("e\n"); delete gnuplot; return 0; }
x0 v0!
!2
�t =T
4⇥ 1
10
xn+1 = xn + vn�t
vn+1 = vn � !2xn�t
【新規プロジェクト:02OscillatingBody】
T = 2⇡/!
【課題】一次精度の陽解法による結果(x)と課題
【課題】周期の数倍程度計算すると結果はどうか、確認する雰囲気はでているけれども、だんだん振幅が大きくなっているような?
【課題】dtを1/10に小さくして1周期分の計算をしてみるとどうか、確認する
一次精度の陽解法による結果(v)
速度が時間経過とともに大きくなっている(エネルギが保存されていない)
【課題】二次精度のRunge-Kutta法を適用
dx
dt= v
dv
dt= �!2x
【課題】Runge-Kutta法のプログラムを作成し計算する!
x(1) � xn
�t/2= vn
v(1) � vn
�t/2= �!2xn vn+1 � vn
�t= �!2x(1)
xn+1 � xn
�t= v(1)
Step 1 Step 2
二次精度Runge-Kutta法による計算結果
かなり改善されている!
教訓:一次精度・二次精度のどちらにすべきかは精度・安定性・開発時間・計算時間を天秤にかけて適切に判断する必要あり。
【自由課題】四次精度のRunge-Kutta法を適用
f (1) = fn +�t
2gn
f (2) = fn +�t
2g(1)
f (3) = fn +�tg(2)
fn+1 = fn +�t
6(gn + 2g(1) + 2g(2) + g(3))
20周期計算でもこれだけ合います。
手間がかかるだけあって。。
【課題】一次精度の陰解法を適用
dx
dt= v
dv
dt= �!2x
xn+1 � xn
�t= vn+1
vn+1 � vn
�t= �!2xn+1
お互いの未知量が入っている。。。どうする?!
【課題】陰解法の計算式を検討する!
【課題】陰解法のプログラムを作成し計算する!
一次精度の陰解法による結果(x)