数値シミュレーション -オイラー法-

数値シミュレーション -オイラー法-

数値シミュレーション解説3

Published: 8/15/2018

Updated: 12/22/2025

7

更新履歴
  • 2025/12/22: プログラムを C# 14.0 に修正
こんにちは、Rafkaです。
前回から間が書いてしまいましたが、今回は常微分方程式の数値解法の1つで、最も単純なオイラー法の導出と実装を行っていきます。
実装をすると言っても、前回の時に実装は済ませてしまっているので、導出した後に改めてソースを見直して、アルゴリズムとの照らし合わせをする感じになりそうです。

数値解法の共通の考え方

第 1 回の時に、 微分方程式の数値解法は、 未知関数 x(t)\boldsymbol{x}(t) の変化量を定義している 右辺の式 f(x(t),t)\boldsymbol{f}\bigl( \boldsymbol{x}(t), t \bigr) の値を 数値的に積分することと書きました。 もっと単純に書くと、 右辺に定義される式から計算できる微小時間 Δt\Delta t あたりの 変化量 Δx(t)\Delta \boldsymbol{x}(t) を計算し、 その値を用いて x(t)\boldsymbol{x}(t) の値を更新していきます。
x(t+Δt)=x(t)+Δx(t)\boldsymbol{x}(t + \Delta t) = \boldsymbol{x}(t) + \Delta \boldsymbol{x}(t)
微分方程式は限りなく小さい微小時間についての定義であることや、 上に示した更新式からも分かる通り、 Δt\Delta t をなるべく小さくすることで、 より精緻なシミュレーションを行うことができますが、 そもそもコンピュータを用いて計算を行うので、 取れる小さい値には限度があることや、 細かい Δt\Delta t を用いると計算ステップ数が増大してコストが大きくなってしまう問題があります。 ですので、計算誤差と計算コストのバランスを考えて Δt\Delta t の値を設定する必要があるのですが、 計算誤差は Δx(t)\Delta \boldsymbol{x}(t) の計算方法を工夫することである程度改善することができます。 その計算方法の種類に、今回紹介するオイラー法やルンゲクッタ法などが存在します。

オイラー法の導出

いくつかあるのですが、他の方法よりもいくらか直感的に理解できる方法から解説したいと思います。

直感的な導出

微分方程式の最初の式に手を加えていきます。
dx(t)dt=f(x(t),t)\frac{d\boldsymbol{x}(t)}{dt} = \boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr)
左辺の微分の定義は以下の式です。
dx(t)dt=limΔt0x(t+Δt)x(t)Δt\frac{d\boldsymbol{x}(t)}{dt} = \lim_{\Delta t \to 0} \frac{\boldsymbol{x}(t + \Delta t) - \boldsymbol{x}(t)} {\Delta t}
limΔ0\lim_{\Delta \to 0} を取り除いてしまって、 前の式に代入してしまいましょう。 すると、以下の式になります。
x(t+Δt)x(t)Δt=f(x(t),t)\frac{\boldsymbol{x}(t + \Delta t) - \boldsymbol{x}(t)} {\Delta t} = \boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr)
ここから式変形して先に示した更新式に近い形にしていきます。
x(t+Δt)x(t)=f(x(t),t)Δtx(t+Δt)=x(t)+f(x(t),t)Δt\begin{split} \boldsymbol{x}(t + \Delta t) - \boldsymbol{x}(t) &= \boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr)\Delta t \\ \boldsymbol{x}(t + \Delta t) &= \boldsymbol{x}(t) + \boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr)\Delta t \end{split}
これで更新式の 変化量を Δx(t)=f(x(t),t)Δt\Delta \boldsymbol{x}(t) = \boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr)\Delta t と すれば良いことがわかりました。
以上で更新式の導出自体はできるのですが、誤差の議論とかは全くできないので、一応テイラー展開を用いた導出も書いておきます。

テイラー展開を用いた導出

次の時間ステップの値 x(t+Δt)\boldsymbol{x}(t + \Delta t)tt の周りでテイラー展開します。
x(t+Δt)=x(t)+dx(t)dtΔt+O(Δt2)\boldsymbol{x}(t + \Delta t) = \boldsymbol{x}(t) + \frac{d\boldsymbol{x}(t)}{dt}\Delta t + O(\Delta t^2)
最初に示した微分方程式の定義より、 dx(t)/dt=f(x(t),t)d\boldsymbol{x}(t) / dt = \boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr) ですのでこれを代入します。
x(t+Δt)=x(t)+f(x(t),t)Δt+O(Δt2)\boldsymbol{x}(t + \Delta t) = \boldsymbol{x}(t) + \boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr)\Delta t + O(\Delta t^2)
これで先程示した式と同じ式が導出できました。 O(Δt2)O(\Delta t^2) から、オイラー法の式の範囲では 2 次以上の精度の誤差が無視されていることが分かり、 オイラー法が計算精度 1 次の手法であることも分かります。

実装

以上でオイラー法のアルゴリズムの導出が終わったので、実装の方に移ろうと思います。
ちなみに今回示したオイラー法は、厳密には陽的オイラー法と呼ばれるものです。陰的オイラー法と呼ばれるものもあるので、興味が湧いた方はぜひ調べてみてください。
さて、前回の記事で示したように、オイラー法は以下のように実装しました。
void Euler(ModelFunction f, Span<double> x, double t, double dt) {
  var n = x.Length;
  Span<double> dx = stackalloc double[n];
 
  f(x, t, dx);
  ScaleAdd(x, dx, dt, x);
}
5 行目の f(x, t, dx)f(x(t),t)\boldsymbol{f} \bigl( \boldsymbol{x}(t), t \bigr) に相当し、 6 行目の処理で、その値に Δt\Delta t を掛けて、 前の時間ステップの値 x に足し込むことで、 x(t)\boldsymbol{x}(t) の更新を行っています。

まとめ

今回は微分方程式の数値解法の1つとして、オイラー法の紹介を行いました。
次回、次次回ではオイラー法よりも誤差を小さくできるホイン法とルンゲクッタ法の紹介を行います。 全て紹介し終わった後、斜方投射を題材に、各手法でどの程度誤差が小さくなるのかを示します。

Rafka
Rafka

Software Engineer

最近はドラクエ 1 & 2 をプレイしてます。楽しい!

関連記事