微分方程式の数値解法

微分方程式の数値解法
微分方程式の例
毎秒 a [m3/s] の水を注入
a
dx(t)
a
dt
体積 x(t) [m3]
解
高さx(t) [m]
x(t)  x0  at
x
底面積 1 m2
微少時間 Dt の間に注入
される量
aDt
Dx  aDt
傾き a
x0
0
t
微分方程式の例(2)
dx(t)
 bx(t)
dt
bx(t)
流出速度が x(t) に
比例するとする
微少時間 Dt の間に流出
する量 bx(t)Dt
解
x(t)  x0 ebt
x
x0
Dx  bx(t)Dt
0
t
微分方程式の例(3)
a
dx(t)
 a  bx(t)
dt
bx(t)
流入・流出が
両方ある場合
おそらくこんな解になるだろう
x
a
b
x0
0
t
厳密に解ける
dx(t)
 a  bx(t)
dt
厳密解:
(a, b は正の定数)
dx(t)
a

 b  x(t)  
右辺を

dt
b
と変形
a
y(t)  x(t) 
と置くと
b
これは直ぐ解けて
よって
dy(t)
 by(t)
dt
a  bt

bt
y(t)  y(0)e   x0   e

b
a  bt a

x(t)   x0   e 

b
b
オイラー法
たとえば
dx(t)
 a  bx(t) という微分方程式を
dt
t  0 で x  x0 という初期条件で解きたいとする。
プログラムで全ての時刻 t での x(t) を求めるのは無理
飛び飛びの時刻で x(t) を求める
x
x0
0
Dt 2Dt 3Dt
t
オイラー法(2)
dx(t)
 f x(t) の初期値問題を解く
1階の常微分方程式
dt
dx(t)
1 d2x
2
テイラー展開 x(t  Dt)  x(t) 
Dt 
(Dt)
L
2
dt
2! dt
Dt が十分小さければ無視出来るだろう
dx(t)
x(t  Dt)  x(t) 
Dt  x(t)  f x(t)Dt
dt
今の時刻 t での x(t)から、
次の時刻 t  Dt
での
x(t が求まる
 Dt)

時刻 t での速度 f x(t)
のまま時刻
tまで等速運動
 Dt
オイラー法(3)
現在時刻の x(t)が正確にわかっているとして…
x(t) のグラフ
x
dx(t)
x(t) 
Dt
dt
x(t)
dx(t )
接線の傾き
dt
x(t  Dt)
Dt
t
t  Dt
dx(t)
Dt
dt
t
オイラー法で解いてみる
オイラー法
今の例では
よって
x(t  Dt)  x(t)  f x(t)Dt
dx(t)
 a  bx(t) つまり f (x)  a  bx
dt
x(t  Dt)  x(t)  (a  bx(t))Dt
a=1, b=1, x(0)=0 の場合を解いてみる。
オイラー法
厳密解
x(t  Dt)  x(t)  (1 x(t))Dt
x(t)  1  et
#include <stdio.h>
#include <math.h>
int main()
{
double x, t, dt, tmax;
dt = 0.1; tmax = 10.0;
t = 0.0; x = 0.0;
/* 初期値 */
printf("%f\t%f\t%f\n",t,x,1-exp(-t));
while(t < tmax) {
x += (1.0-x)*dt;
/* 次の時刻の x */
t += dt;
printf("%f\t%f\t%f\n",t,x,1-exp(-t));
}
return 0;
}
結果をgnuplot で表示してみる
リダイレクトにより結果を x.txt に保存:
$ ./a.out > x.txt
$ less x.txt
0.000000
0.000000
0.200000
0.200000
..
..
時刻 t
オイラー法
によるx(t)
0.000000
0.181269
..
正確なx(t)
less を終了するには
q を押す
gnuplot でグラフ化してみる:
$ gnuplot
gnuplot> plot 'x.txt' using 1:2, 'x.txt' using 1:3
gnuplot> quit
$
演習
dt を変化させるとt=2.0 での誤差がどのように変
化するかを調べる。
誤差
?
dt
差分
差分
dx(t )
微分係数
を
dt
飛び飛びの時刻での x(t) から近似的に求める
前進差分
dx(t) x(t  Dt)  x(t)

dt
Dt
微分方程式
dx(t)
 f x(t)
dt
x(t  Dt)  x(t)  f (x(t))Dt
オイラー法
x(t  Dt)  x(t)
 f x(t)
Dt
前進差分
テイラー展開
2
dx(t)
1d x
2
x(t  Dt)  x(t) 
Dt 
(Dt)
L
2
dt
2! dt
2
dx(t)
1d x
2
Dt  x(t  Dt)  x(t) 
(Dt)
L
2
dt
2! dt
dx(t) x(t  Dt)  x(t) 1 d x


Dt  L
2
dt
Dt
2! dt
O(Dt) 高々 Dtに比例して
2
dx(t) x(t  Dt)  x(t)

dt
Dt
ゼロに近づく量
各種の差分
前進差分
dx(t) x(t  Dt)  x(t)

 O(Dt)
dt
Dt
後退差分
dx(t) x(t)  x(t  Dt)

 O(Dt)
dt
Dt
中心差分
dx(t) x(t  Dt)  x(t  Dt)

 O (Dt)2
dt
2Dt


必ず精度が高くなるわけではない
中心差分
2
3
dx(t)
1d x
1d x
2
3
x(t  Dt)  x(t) 
Dt 
(Dt)

(Dt)
L
2
3
dt
2! dt
3! dt
3
dx(t)
1 d2x
1
d
x
2
3
x(t  Dt)  x(t) 
Dt 
(Dt)

(Dt)
L
2
3
dt
2! dt
3! dt
上式から下式を引くと
3
dx(t)
2d x
3
x(t  Dt)  x(t  Dt)  2
Dt 
(Dt)
L
3
dt
3! dt
dx(t) x(t  Dt)  x(t  Dt)

 O (Dt)2
dt
2Dt


微分係数とグラフの傾き
x(t)  x(t  Dt)
後退差分
Dt
x
x(t  Dt)  x(t)
前進差分
Dt
中心差分
x(t  Dt)  x(t  Dt)
2Dt
x(t) のグラフ
dx(t )
接線の傾き
dt
x(t  Dt)
x(t)
x(t  Dt)
t  Dt
t
t  Dt
t
運動方程式を解く
運動方程式
d 2 x(t)
m
 F(t)
2
dt
質点の位置 x(t) が解れば質点に働く力 F(t) が解る場合を考える
例えば、バネに繋がれた質点の場合
0
自然の長さ
x(t)
x(t)
F(t)  kx(t)
x
オイラー法で解く
dx(t)
速度 v(t) 
を導入して1階の連立常微分方程式にする:
dt
dx(t)
 v(t)
dt
dv(t) 1
 F(t)
dt
m
前進差分
x(t  Dt)  x(t)
 v(t)
Dt
v(t  Dt)  v(t) 1
 F(t)
Dt
m
現在の x(t), v(t), F(t) から
x(t  Dt)  x(t)  v(t)Dt
次の時刻の x(t  Dt), v(t  Dt)
1
v(t  Dt)  v(t)  F(t)Dt
m
が計算できる
厳密解
d 2 x(t)
 kx(t)
運動方程式 m
2
dt
両辺を m で割って
一般解
d 2 x(t)
2



x(t)
2
dt
但し

x(t)  C1 cos  t  C2 sin  t
初期条件を
t = 0 で x = x0 , v = 0
x(t)  x0 cos  t
とすると
k
m
#include <stdio.h>
#include <math.h>
int main()
{
double k, m, w, x, v, t, dt, tmax, F;
k = 1.0; m = 1.0; w = sqrt(k/m); /* パラメタ */
tmax = 10.0; dt = 0.02;
/* 計算条件 */
t = 0.0; x = 1.0; v = 0.0;
/* 初期条件 */
while( t < tmax ) {
printf("%f¥t%f¥t%f¥n", t, x, cos(w*t));
F = -k*x;
/* 質点に働く力 */
x += v*dt;
/* 次の時刻の x */
v += F/m*dt;
/* 次の時刻の v */
t += dt;
}
return 0;
}
実行してみる
リダイレクトにより結果を x.txt に保存:
$ cc oscillator.c -lm
$ ./a.out > x.txt
$ less x.txt
0.000000
1.000000
0.020000
1.000000
..
..
時刻 t
オイラー法
によるx(t)
1.000000
0.999800
..
less を終了するには
q を押す
正確なx(t)
gnuplot でグラフ化してみる:
$ gnuplot
gnuplot> plot 'x.txt' using 1:2, 'x.txt' using 1:3
gnuplot> quit
$
ベルレ法
dx(t)
1 d 2 x(t)
2
x(t  Dt)  x(t) 
Dt 
(Dt)

2
dt
2! dt
dx(t)
1 d 2 x(t)
2
x(t  Dt)  x(t) 
Dt 
(Dt)

2
dt
2! dt
1 d 3 x(t)
3
(Dt)
L
3
3! dt
1 d 3 x(t)
3
(Dt)
L
3
3! dt
この2式を加えると
d 2 x(t)
2
x(t  Dt)  x(t  Dt)  2x(t) 
(Dt)
L
2
dt
よって
d 2 x(t) x(t  Dt)  2x(t)  x(t  Dt)

2
2
dt
Dt 
F(t)
x(t  Dt)  2x(t)  x(t  Dt) 
(Dt)2
m
次の時刻
現在
一つ前
の時刻
現在
2階微分に対
する差分
ベルレ法
ベルレ法(2):別の見方
dx(t)
v(t) 
dt
であるから、中心差分により
d 2 x(t) dv(t) v(t  Dt / 2)  v(t  Dt / 2)


2
dt
dt
Dt
再度中心差分を用いると
x(t  Dt)  x(t)
v(t  Dt / 2) 
Dt
x(t)  x(t  Dt)
v(t  Dt / 2) 
Dt
よって
Dt
t
2
t  Dt
Dt
t
2
t
t  Dt
Dt
d 2 x(t) 1  x(t  Dt)  x(t) x(t)  x(t  Dt) 
 


2
dt
Dt 
Dt
Dt
x(t  Dt)  2x(t)  x(t  Dt)
前ページと同じ式

2
Dt 
ベルレ法(3)
F(t)
x(t  Dt)  2x(t)  x(t  Dt) 
(Dt)2
m
例えば
F(t)  kx(t)
●初期条件として x(0) に加え x(-Dt) が必要
例えば x(Dt)  x(0)  v(0)Dt
1 F(0)
2
Dt 
あるいは x(Dt)  x(0)  v(0)Dt 
2 m
●速度が必要な場合、 x(t  Dt) を求めた後
x(t  Dt)  x(t  Dt)
v(t) 
2Dt
で現在の速度を求める。
次の時刻での位置が解って初めて現在の速度が解る
(この不便を解消する方法もあるが、やや煩雑なので ここ
では単純なベルレ法を使う)
#include <stdio.h>
#include <math.h>
int main()
{
double k, m, w, xold, xnow, xnew, t, dt, tmax, F, E;
k = 1.0; m = 1.0; w = sqrt(k/m);
tmax = 10.0; dt = 0.02;
t = 0.0; xnow = 1.0;
xold = xnow;
/* t=0で速度v=0だから、xoldの予測値=xnow */
while( t < tmax ) {
F = -k*xnow;
xnew = 2*xnow - xold + F/m*dt*dt; /* 次の時刻の x */
vnow = (xnew - xold)/(2*dt);
/* 現在の速度 */
E = 0.5*(m*vnow*vnow + k*xnow*xnow); /* エネルギー */
printf("%f¥t%f¥t%f\t%f¥n",t, xnow, cos(w*t), E);
t += dt;
xold = xnow;
xnow = xnew;
}
}
エネルギー保存則
力学的エネルギー
1
1
2
E  mv(t)  kx(t)2
2
2
運動エネルギー
ポテンシャルエネルギー
E は時間に依らない一定値になるはず
x(t)  x0 cos  t の場合
v(t)   x0 sin t
1
1 2
2 2
2
E  m x0 sin  t  kx0 cos2  t
2
2
1 2
1 2
2
2
 kx0 sin  t  cos  t  kx0
2
2

k
m
演習(今回はこれだけ)
オイラー法でエネルギー保存則がどれくらい良く
満たされているかについて
•dt を変化させて誤差がどう変化するか
E
オイラー
?
log誤差
?
t
log dt
演習
エネルギー保存則がどれくらい良く満たされて
いるかについて
•オイラー法とベルレ法で比較
•dt を変化させて誤差がどう変化するか
E
オイラー
?
ベルレ
log誤差
?
t
log dt
放物運動
d2x
m 2 0
dt
d2y
m 2  mg
dt
初期条件
x(t  0)  0,
y(t  0)  0
厳密解
x(t)  vx 0t
1 2
y(t)  vy0 t  gt
2
y
v0
q
0
x
vx (t  0)  vx0  v0 cosq ,
vy (t  0)  vy0  v0 sinq
オイラー法で解く
x(t  Dt)  x(t)  vx (t)Dt
vx (t  Dt)  vx (t)
y(t  Dt)  y(t)  vy (t)Dt
vy (t  Dt)  vy (t)  gDt
#include <stdio.h>
#include <math.h>
int main()
{
double x, vx, y, vy, t, dt;
double m, g, v0, theta, vx0, vy0, deg;
deg = 4.0*atan(1.0)/180.0;
/* 度をラジアンに変換 */
g = 9.8; m = 0.1;
/* 質量と重力加速度 */
v0 = 10.0; theta = 30.0*deg; /* 初速と投げる方向 */
vx0 = v0*cos(theta); vy0 = v0*sin(theta);
dt = 0.01;
t = 0.0; x = 0.0; y = 0.0;
vx = vx0; vy = vy0;
(次ページに続く)
tan

4
1
tan 1 1 

4
/* 初期条件 */
/* 初期条件 */
while(y >= 0.0) {
printf("%f¥t%f¥t%f¥t%f¥t%f¥n",
t, x, y, vx0*t, vy0*t - 0.5*g*t*t);
x += vx*dt;
vx += 0;
y += vy*dt;
vy += -g*dt;
t += dt;
}
return 0;
}
地面に落ちてくるまで計算する
空気抵抗
速度 v で空気中を運動する物体に働く抵抗力 F は、およそ
1
F   CD air Svv
2
となることが知られている。
(速度と逆向きで、速度の2乗に比例する)
但し  air =空気の密度=約 1 kg/m3
C D =抵抗係数(物体の形状に依存)
S =物体の断面積
v  v  vx2  vy2
密度 ball 、半径 a の球の場合 C D  0.5 程度 であり
S  a
2
m  ball
air 1
k  0.2
ball a
4 3
a
3
とおくと
F
air 1
 0.2
vv
m
ball a
F
 kvv
m
空気抵抗
 
 
抵抗力 F の x,y 成分を Fx, Fy とすると、運動方程式は
d2x
m 2  Fx
dt
d2y
m 2  mg  Fy
dt
両辺を m で
割って
d2x
 kvvx
2
dt
d2y
 g  kvvy
2
dt
オイラー法を使うとすると、例えば y は
dy(t)
y(t  Dt)  y(t)
左辺を
 vy (t)
 vy (t)
dt
Dt
前進差分
dvy (t)
vy (t  Dt)  vy (t)
で近似
 g  kv(t)vy (t)
 g  kv(t)vy (t)
dt
Dt
よって

y(t  Dt)  y(t)  vy (t)Dt


vy (t  Dt)  vy (t)  g  kv(t)vy (t) Dt
但し
v(t)  vx (t)2  vy (t)2
#include <stdio.h>
#include <math.h>
int main()
{
double x, vx, y, vy, t, dt;
double g, v0, theta, vx0, vy0, deg;
double k, v;
deg = 4.0*atan(1.0)/180.0;
/* 度をラジアンに変換 */
g = 9.8;
/* 重力加速度 */
k = 0.2/(0.01*1000);
/* 0.2/(半径*密度) */
v0 = 10.0; theta = 30.0*deg; /* 初速と投げる方向 */
vx0 = v0*cos(theta); vy0 = v0*sin(theta);
dt = 0.01;
t = 0.0; x = 0.0; y = 0.0;
vx = vx0; vy = vy0;
(次ページに続く)
/* 初期条件 */
/* 初期条件 */
while(y >= 0.0) {
printf("%f¥t%f¥t%f¥t%f¥t%f¥n",
t, x, y, vx0*t, vy0*t - 0.5*g*t*t);
v = sqrt(vx*vx + vy*vy); /* 現在の速さ */
x += vx*dt;
vx += -k*v*vx*dt;
y += vy*dt;
vy += -g*dt - k*v*vy*dt;
t += dt;
}
return 0;
}
dt は十分小さくすること