微分方程式の数値解法
微分方程式の例
毎秒 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 ebt
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 et
#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 は十分小さくすること
© Copyright 2026 ExpyDoc