HOWTO · Python
Python で ode45()関数を模倣する
この記事では、Python での ode45()関数の実装について説明します。もともと、関数は MatLab で定義されています。ただし、この記事では、Python で scipy モジュールを使用した ode45()関数の実装について説明しています。
このページの内容
MATLAB の ode45() 関数は、適応型ルンゲ=クッタ法で常微分方程式(ODE)の初期値問題を解きます。Python には文字どおり ode45() という名前の関数はありませんが、SciPy の solve_ivp() が同じワークフローをカバーします。方程式を記述し、ソルバーに時間区間と初期状態を渡し、返されたオブジェクトから解を読み取ります。
MATLAB の ode45() には SciPy の solve_ivp() を使う
scipy.integrate の solve_ivp() 関数は、ode45() に最も近い実用的な代替です。デフォルト手法の RK45 は、自動刻み幅制御を備えた次数 5(4) の陽的ルンゲ=クッタ公式であり、MATLAB で ode45() が使うのと同じアルゴリズム系です。そのため、解の変化が緩やかな区間では大きなステップを、変化が速い区間では小さなステップを取ります。
ただし、そのまま置き換えられるわけではありません。引数名が異なり、結果も t と y の配列のペアではなく 1 つのオブジェクトとして返されます。それでも、導関数・時間区間・初期状態という全体のパターンは両者の間で直接対応します。SciPy がまだ利用できない場合は、pip install scipy でインストールしてください。
ODE を定義して solve_ivp() を呼び出す
基本的な呼び出しは solve_ivp(fun, t_span, y0, method="RK45", t_eval=None, args=None, ...) です。主な入力は次のとおりです。
fun: 現在の時刻tと状態yに対する導関数dy/dtを返す呼び出し可能オブジェクトfun(t, y)。単一の方程式では 1 つの値を返し、連立方程式では状態成分ごとの導関数を含む配列を返します。t_span: 積分区間の開始と終了を表す(t_start, t_end)のペア。y0:t_startにおける状態。その長さが方程式系の本数を決めます。method(省略可): 積分スキーム。デフォルトは'RK45'です。args(省略可):fun(t, y, *args)のように関数へ渡される追加の値。rtolとatol(省略可): 適応刻み幅を制御する相対許容誤差と絶対許容誤差。
次の例は、初期値 y(0) = 1 から区間 [0, 2] で dy/dt = 2y を解きます。この呼び出しは結果オブジェクトを返し、その t 属性と y 属性を表示すると、ソルバーが選んだ時点と各時点での状態がわかります。次のコードと出力に表示されるラベルや文字列は英語のままです:
from scipy.integrate import solve_ivp
def growth(t, y):
return 2 * y
result = solve_ivp(growth, (0, 2), [1])
print("t:", result.t)
print("y:", result.y[0])
t: [0. 0.07580098 0.56590702 1.18757739 1.85907272 2. ]
y: [ 1. 1.16369695 3.10133569 10.75272756 41.18553629 54.59497095]
報告される時刻の間隔が不均一なのは、solve_ivp() が一定の増分で進むのではなく、各ステップを解の局所的な振る舞いに適応させるためです。
ソルバーの結果を読み取る
solve_ivp() は scipy.integrate.OdeResult オブジェクトを返します。最もよく使う属性は次のとおりです。
t: 報告された時点を格納する NumPy 配列。t_evalを指定しない場合、これらはソルバー内部の適応ステップです。y: 形状(n, len(t))の 2 次元 NumPy 配列。行iには報告された各時刻での状態の成分iが入るため、y[:, k]はt[k]における状態です。単一の方程式では、y[0]が解曲線です。sol:result.sol(t)として呼び出し、区間内の任意の時刻で解を評価できる連続的なOdeSolution補間オブジェクト。dense_output=Trueを渡した場合にのみ設定され、それ以外ではNoneです。successとmessage: 積分が完了したかどうかを示すブール値と、どのように終了したかを説明する人間が読めるメッセージ。status: 区間が完了した場合は0、終了イベントが発生した場合は1、ソルバーが進展できなかった場合は-1。
失敗した実行でも部分的な配列が返されることがあるため、y を使う前に success(または status)を確認する習慣を付けるとよいでしょう。
t_eval で出力時刻を指定する
適応ステップの時刻は、報告したい時刻と一致するとは限りません。ソート済みの時刻配列を t_eval に渡すと、solve_ivp() は内部ステップではなく、指定した時刻ちょうどの解を返します。次のコードと出力に表示されるラベルや文字列は英語のままです:
from scipy.integrate import solve_ivp
def growth(t, y):
return 2 * y
times = [0.5, 1.0, 1.5, 2.0]
result = solve_ivp(growth, (0, 2), [1], t_eval=times)
print("t:", result.t)
print("y:", result.y[0])
t: [0.5 1. 1.5 2. ]
y: [ 2.71823524 7.38683161 20.07910026 54.59497095]
これで t には要求した時刻だけが含まれ、y にはそれぞれの時刻に補間された状態が格納されます。t_eval の値はすべて t_span の内側になければなりません。積分区間の外にある時刻を要求すると、その値は t_span 内になければならないというエラーで呼び出しが停止します。端点は許可されているため、t_eval には t_start と t_end の両方を含められます。