数値計算③ SciPyライブラリ【Pythonと物理#7】

Python

※Amazonのアソシエイトとして、当メディア(Nラボ備忘録)は適格販売により収入を得ています。

今回のターゲット:SciPyライブラリ

 前回まででオイラー法とルンゲ-クッタ法を勉強してみました。

お気づきの方が多いかと思いますが、【Pythonと物理】シリーズではすでに優れた数値計算を使っていました。

例えば、下記の記事ではオイラー法やルンゲ-クッタ法を実装するのではなく、SciPyライブラリ使って数値計算をしていました。

そこで、今回は自分で実装したオイラー法、ルンゲ-クッタ法、SciPyライブラリ、それぞれを比べてみたいと思います。

勉強の際に参考にさせていただいた書籍もご紹介させていただきます。具体的なPythonのコードも載っており、大変勉強になる1冊です。

SciPyライブラリによる数値計算

SciPyライブラリによる常微分方程式の数値計算結果は、オイラー法やルンゲ-クッタ法よりも高精度な計算ができるようです。

具体的にどのような計算をしているのか、私にはわかりませんでしたが、予測子-修正子法なるものを使っているようです。

いずれは理解したいですが、今は使えることを重視します。

SciPyライブラリで数値計算をしてみる

それでは(すでに使ったことはありますが改めて)SciPyライブラリを使って数値計算をしてみます。今回はSciPyライブラリのscipy.integrate.odientを使います。

計算する常微分方程式は前回、前々回と同様に以下の式にします。

$$
\cfrac{dy}{dt} = y
$$

これは普通に厳密解 $y$ を求めることもできるので、求めてみると以下のようになります。

$$
y = y_{0}\exp\left(t\right)
$$

ただし、 $y_{0}$ は定数です。

Pythonで計算する

コードは下のようにしました。詳細はいつも通りコメントアウトで書いています。
厳密解、オイラー法、ルンゲ-クッタ法、SciPyライブラリを1つのグラフで比較するコードにしています。

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint 

#数値計算したい微分方程式の定義(オイラー法、ルンゲクッタ法用)
def func(y):
    return y
#数値計算したい微分方程式の定義(scipy.integrate.odeint用)
def func_scipy(y, t):
    return y

#ルンゲ-クッタ,法
def RungeKutta_method(f, y, h):
    k1 = f(y)
    k2 = f(y + k1*h/2)
    k3 = f(y + k2*h/2)
    k4 = f(y + k3*h/2)
    return y + (k1 + 2*k2 + 2*k3 + k4)*h/6

#オイラー法
def euler_method(f, y, h):
    return y + f(y) * h

#常微分方程式を計算
def ode_solve_RKM(f, y0, T, N):
    t = np.linspace(0, T, N)
    h = t[1] - t[0]
    y = [y0, ]
    for i in range(N - 1):
        y.append(RungeKutta_method(f, y[-1], h))
    return t, np.array(y)
    
def ode_solve_EM(f, y0, T, N):
    t = np.linspace(0, T, N)
    h = t[1] - t[0]
    y = [y0, ]
    for i in range(N - 1):
        y.append(euler_method(f, y[-1], h))
    return t, np.array(y)
    
#初期条件などを設定して、上で定義した関数たちを呼び出す
y0 = 0.01
T = 10
N =101
t_scipy = np.linspace(0, T, N) #本来ならtとt_scipyで分けて定義するべきではないと思います

t, y_RKM = ode_solve_RKM(func, y0, T, N) #ルンゲ-クッタ法
t, y_EM = ode_solve_EM(func, y0, T, N) #オイラー法
y_exact = y0 * np.exp(t) #math.exp()だと配列ではないので計算不可
y_scipy = odeint(func_scipy, y0, t_scipy)


#以下は可視化のためにグラフを書くコード
fig, ax = plt.subplots(dpi=240) #解像度上げないと分かりづらかったので、240dpiまで上げています
ax.plot(t_scipy, y_scipy, color='green', label='Scipy Library')
ax.plot(t, y_RKM, color='orange', label='Runge-Kutta Method')
ax.plot(t, y_EM, color='crimson', label='Euler Method')
ax.plot(t, y_exact, "--", color='cornflowerblue', label='Exact')
    
ax.set_xlabel('t')
ax.set_ylabel('y')
ax.legend()
    
ax.tick_params(direction='in', axis='x')
ax.tick_params(direction='in', axis='y')

#以下の範囲設定は適当です
ax.set_xlim(0, 5)
ax.set_ylim(0, 1)

plt.show()

今回はscipy.integrate.odeintを使用していますが、どうやら若干古い関数らしいです。より新しい関数として、scipy.integrate.solve_ivp があります。特に理由がなければ、scipy.integrate.solve_ivp を使用する方が好ましいようです。
ただし、後述するグラフを見ていただければわかりますが、scipy.integrate.odeintでも十分に高い精度があると思います。少なくとも今回の微分方程式を計算する分には十分です。

実行結果

実行結果は下のグラフのようになると思います。SciPyライブラリのscipy.integrate.odeintで計算した結果はほとんど厳密解(Exact)と一致していることがわかります。

うーん、すごい…

最後に

 今回は改めてSciPyライブラリを使ってみました。自分でオイラー法やルンゲ-クッタ法を実装してみた後にscipy.integrate.odeintを使うと、その精度の高さがよくわかります。

物理屋さんにとってはSciPyライブラリをとりあえず使えれば良いかなという気もしますが、こうやって実際に比較してみるのも面白いですね。

今回はこの辺で終わりにします。次回は偏微分方程式かな…

他の記事もぜひご覧ください!

コメント

タイトルとURLをコピーしました