右を見てください。同じ数式(a の平方根を2で割った分数)が2つ並んで見えていますか? そうでない場合にはあなたが今使っているブラウザは数式表示のための規格 MathML に対応していません。 現時点で対応しているのは, Firefox, Google Chrome, Safari, Opera ですので,どれかをインストールして使ってください。 $ \frac{\sqrt{a}}{2} $

微分から差分へ: Euler 法


微分から差分へ

状態の変化や物体の運動のような「変化」は,一般に微分方程式を用いて 記述され,それを解くことで将来の予測が可能になります. 次はよくある微分方程式の形です.

(1) 微分方程式: \[ \frac{d x}{d t} = f(x,t) \]

この式の意味は,対象としている量 $ x $ の値の 変化が, 現在の時刻とその量の現在の値とで決まる関数の値によって決まるということです. ちょっと分りにくい表現ですが,実例が後で出てきますので,とりあえずさらっと読んでください.

このような 微分方程式が, 解析的に,つまり式の変形などの数式の操作によって解くことができるならば ,数学的に完全な答を導くことが出来るのですが,通常そうはいきません. せっかく微分方程式を立ててみても,解けないというケースが 非常に多いのです.

そこで,本来なら無限に短い瞬間($d t$)ごとの変化($dx$) で記述される微分を短い有限の時間のきざみ($\delta t$)ごとの変化($\delta x$)に置き換えてしまい,これを コンピュータを用いて繰り返し計算で計算することで, 実用上差し支えない精度の結果を得ようというやり方がとられています. このように有限の時間の刻みを用いるやり方は,微分に対して差分と呼ばれます.

(2) 差分方程式: \[ \frac{\delta x}{\delta t} = f(x,t) \]
ここで左辺は分子と分母が有限な値をもつ分数そのものであり, 微分におけるような極限操作の概念はもはや含まれていません. すなわち,
\[\delta x = f(x,t)\times \delta t \]
と気楽に変形して掛け算の形にしてしまって,変化の大きさを求めても 構わないというわけです(これはEuler法の話で,実際にはもう少し複雑になります). 差分を使った計算では,微分を扱うときのような厳密性や正確さは失われてしまい ます. しかし, 微分を差分として扱って微分方程式を 近似的に解くことは,解析的な方法に比べてはるかに容易であり,さまざまなシミュレーションの 解法の基本的な手法となっています.

ところで,自然界でおきる現象の中には,世代ごとに形質が少しずつ変化していく 生物の進化のように,段階的な変化が積み重なっていくようなケースも あります.進化生態学では,生物個体の数が対象になるので, その意味でも不連続な取り扱いが適切だという側面もあります. したがって, この場合は本質的に差分が使えるという考え方もできます.

しかし,世代ごとの形質の変化は微小であり, 一方で生物個体の数が多いためにその変化が連続である とみなせることから,この種の問題も基本的には微分の問題として 取り扱われています.シミュレーションではそれをまた差分化して解くわけです. 回りくどいやり方ですが,このようなやり方で問題を抽象化,一般化 できるわけです(最近では個々の生物個体をじかに仮想的な個体として コンピュータの上に再現して,それらに自立的な行動をさせる, マルチエージェント手法も盛んになっていますが,これはいわば原点に 戻った考え方です.ある意味分かりやすいのですが,解明に抽象性が欠けてしまいがちで,本質を とらえにくい欠点も持っています).

Euler 法: 素朴な計算法

絶滅のシミュレーション

ある生物種の個体数を $x$ としましょう. 時間経過を$t$とし, 単位時間ごとに$x$のうち $k$ の割合は減少してしまうものとします. これは時間刻みを世代としたときに, 人口が一定の割合で減少することを意味しています. このとき人口の変化のようすは 次の微分方程式で表されることになります.

(3) \[ \frac{d x}{d t} = -kx \]

この微分方程式は解析的にも簡単に解けて,次のような解を持ちます.

(4) \[ x = x_0 e^{-kt} \]

ここで $t_0$ は時刻 $t=0$ の時の $x$ の値です.

式(3)を数値的に解くことを考えましょう. 微分は変化率を無限に短い時間の極限値で考えるのですが,コンピュータは 有限の時間しか扱えないので,「十分に短い」時間の刻みを考え, それ を$\delta t$とします. その間の $x$ の増加量を $\delta x$ と置くと, 式(3)は次のように近似できます.

(5) \[ \frac{\delta x}{\delta t} = -kx \]

この左辺はもはや単なる分数ですから,次のように変形しても構いません.

(5) \[ \delta x = -kx \delta t \]

式(5)は,有限の短い時間 $\delta t$ の間の $x$ の変化量 $\delta x$ を 掛け算の形で 求める式になっています.したがって 具体的に必要な値を定めてやればその間の変化を算出できます. その計算を何回も繰り返してやれば,長時間にわたる変化を計算することが できるわけです.

それでは実際に計算を行う手順を考えます.

  1. $t$ に 0 を代入する.
  2. 最初の人口を決めて $x$ に代入する.
  3. 温度変化 $-k \delta t$ を $x$ に加え, $t$ に $\delta t$ を加える.これで $\delta t$ 後の人口が求められる.
  4. 上の手続きを十分な回数繰り返す.

あっけないほど簡単ですね.このようなやり方を Euler 法といいます.ちなみに Leonhard Euler(オイラー,1703-1783)は歴史上最大の数学者の一人で,数え切れないほどの定理や式を世に出したので, Euler の式とか Euler の定理というのも山のようにあります.彼の著作集は現在でも 出版事業が進められており,現在までに74巻が完結し,14巻が出版途中,それでも まだ残されているものの出版を準備中だそうです.すごいですねえ.→出版計画

プログラムの例

上の例をプログラムで記述してみましょう.Ruby で書いたソースは次のようになります.なお,このプログラムには小さな問題点があり,その解説を下に書いてあります。


#!/usr/local/bin/ruby
# euler1.rb   :  人口が減少する変化を追う
x_start  =  10000.0    # 最初の人口
t_start  =   0.0    # 最初の時刻
t_end    = 100.0    # 最後の時刻
dt      =   0.1    # 時間の刻み(分)
k       =   0.05    # 減衰の速さを決める定数

t = t_start
x = x_start
nstep = ((t_end - t_start)/dt).to_i
for i in 0 .. nstep
  printf("%8.6f %8.6f\n",t,x)
  t +=  dt
  x +=  -k * x * dt
end

さて,これを走らせて得られた結果を図示すると次のようになります.
Figure

図は GNUPLOT で描かせて,PNG フォーマットで出力させたものです.

解析解との比較

ここで扱っている微分方程式

\[ \frac{d x}{d t} = k (x - x_e) \]
は解析的に解くことが出来て,その解は次のようになります.
\[ x = (x_0 - x_e) e^{-k t} + x_e\]
この解が妥当なものであることは, $t = 0$ と置いたときと $t \rightarrow \infty $ としたときの$x$の値から納得できますから,自分で 考えてください.

さて,この解析的な解も上の数値解と一緒にグラフにプ ロットしてみたのが下の図です.2つのグラフがよく重なっていることから,Euler 法がかなり満足すべき解を 与えていることが分ります.
Figure


練習問題

  1. 上記の例について, 具体的な物理量を次のように与える。なお,定数 $C$ は次のように与えられる。すなわち, お湯の温度を $ x$ ,室温を $ x_e$ としたとき,毎秒 $ C (x - x_e)$ だけの熱量が外に流れ出す(負だったら 内部に流れ込む)。
    お湯の量: 1000.0 g, $C $ : 5.0 J/(℃$\cdot$s),室温 20 ℃, 追跡する時間: 1000 秒
    注: 1 J の熱を与えられると, 1 g の水の温度は 0.239℃ だけ上昇する。
    この場合について,上と同じように冷却問題を解きなさい。 ただし,今度は初期温度を 0.0℃,20℃,40℃,60℃,80℃として,それぞれの 初期温度についての解を求めてグラフに表示しなさい。
  2. 室温の水が入っているポットがある。時刻 $ t = 0.0 $ でポットが加熱され始め, 時刻 $t = t_m$ (= 300 秒) になったらヒーターのスイッチが切れて加熱が終了する。 その後は自然に冷却してふたたび室温に近づく。
    この過程を Euler 法で解き,結果を表示しなさい。なお, 水の沸騰については考えなくてよい。つまりお湯の温度が100℃を超えてもかまわ ない。またポットの熱容量は無視する。 ヒーターの発熱量は 500.0 J/sとし,その他のパラメータは上記の 1. と同じとする。
  3. 微分方程式 $ \frac{dx}{dt} =\sin(t)$ をEuler法を用いて,次の条件で解きなさい。 時刻 0 での $x$ の値: 0.0, 追跡する時間: $0.0 \le t\le 10.0 $ なお,この問題に対応する簡単な物理的な状況はない(ように思う)。

t の正確な計算法は?
上のプログラムでは,時間 t を計算するのに, ループが回るたびにdt を加算していくというやり方(t += dt) をとりました。 しかしこのやり方には問題があります。なぜなら,数を非常に 多数回加算することは,誤差の蓄積につながるからです。x の 値については,アルゴリズムの性質上,加算で求めるしかないのですが。 t については,むしろ 次のようにして求めるのが, 高い精度の計算のために適切なのです。

t = t_start + i * dt
このことを確かめるには,次のテストプログラムを走らせてみると よいでしょう。
dt = 0.01
t  = 0.0
nstep = 1000000
for i in 0 .. nstep
  t += dt
end
printf("%20.18f %20.18f\n",t,dt*nstep)

練習問題解答

プログラムの書き方は各人各様で,正しいプログラムというのは何通りも あります.以下のプログラムはそのひとつの例として 提示したものです.ただし,合理的な書き方はこうあるべきであるという 指針にもなっているはずですので,参考にしてください.

なお,グラフについては,正しいプログラムであればほぼ同一の値が 出るはずです.もしグラフにちがいがあったら間違いと思ってよいでしょう( たまには模範解答の方がちがっていたりしますが).

  1. ここでは2種類のプログラムを参考として提示しています.
    1. ソース: potheating1a.rb, 温度をそのつど手で書き換えながら計算させる手仕事バージョン
    2. ソース: potheating1b.rb, 温度を書き換えながら別々のファイルに出力する全自動バージョン
    いずれも結果は次のとおり.
  2. ソース:potheating2.rb
    結果は下の図のようになるはずです。

  3. ソース: sin-euler.rb
    結果は 下の通り.

    これは$ \int\sin(t) = -\cos(t) + C $ であることを示しています.
元に戻る

小波秀雄, 2005/10/19