Webページでは数式を表すためにLaTeX表記が使えるMathJaxを利用しています。 WebブラウザにはSafari/Chrome/Firefoxを使って下さい(IEでは表示できないようです。)

SciPyで微分方程式を解く

モジュール SciPyの数値積分には微分方程式の数値解法 scipy.integrate.odeint が含まれている。 FORTRANライブラリのodepackにあるlsodaを使っているので高速な計算が可能である。 『硬い方程式』(stiff equation)にも対応しており汎用性が高い。 詳しい使い方はSciPyのドキュメントscipy.integrate.odeintを参照。

問題とする微分方程式系は$n$変数の1階微分方程式と$n$個の初期値の組 とする。 scipy.integrate.odeintの基本的な使い方は次のようである。

import scipy.integrate.odeint

orbit = scipy.integrate.odeint(vectorfield, y0, t, args)

引数 vectorfield は微分方程式の右辺を定めるベクトル場リスト(以下の例)、u0 は初期値のリスト(array)、tは解くべき時間点のarrayリスト(最初の時間点での変数値が初期値 u0)、args はvectorfield に渡す引数のタプルである。 返り値 orbit はarrayリストで、解かれる時間点に応じた軌道変数値のarrayリスト [u0, u1, u2,...] である(各要素もまた変数の数の長さからなるarrayである)。

具体例

ベクトル場関数の与え方

Lorenz系(Deterministic Nonperiodic Flow J. Atmos. Sci., 20(11963), 130–141)では、odeintで呼ばれるベクトル場は次のように書くことができる。 p, b, r はベクトル場のパラメータ(ここでは定数)である。

    def lorenz(u, t, p, b, r):
        x = u[0]
        y = u[1]
        z = u[2]
        dxdt = -p * x + p * y
        dydt = -x * z + r * x - y
        dzdt = x * y - b * z
        return([dxdt, dydt, dzdt])

初期値の設定

odeintに渡す初期値は、$x_0 = 0.1, y_0=0.1, z_0=0.1$とすると次のように与える。

u0 = [0.1, 0.1, 0.1]

解くべき時間点

初期値 u0 の時間点を 0.0 としたとき、差分刻み 0.01 で 時刻 40 までの計算のために odeint に渡す時間点 times は次のように与える。
dt = 0.01
T = 40.0
times = np.arange(0.0, T, dt)

プログラム例

以上をまとめて、Lorenz系をSciPyを使って微分方程式の数値解を求めて、軌道要素を3次元プロットするプログラム scipy_diff.py を次のように書く。 odeintで返される軌道要素は多数行$\times 3$のarrayであり、Numpyを使って効率化を図るで取り上げたように、 各列全ての要素リストはそれぞれ orbit[:, 0], orbit[:, 1], orbit[:, 2] でスライスできることに注意しよう(コメントアウトされた xorbit, yorbit, zorbitを改めて抜き出す計算は必要はない)。
# -*- coding: utf-8 -*-
# numerila solution using scipy.integrate
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D

def lorenz(u, t, p, b, r):
    x = u[0]
    y = u[1]
    z = u[2]
    dxdt = -p * x + p * y
    dydt = -x * z + r * x - y
    dzdt = x * y - b * z
    return([dxdt, dydt, dzdt])

# parameters
p =10
b = 8.0 / 3.0
r = 28
# initial values
u0 = [0.1, 0.1, 0.1]
# setting
dt = 0.01
T = 40.0
times = np.arange(0.0, T, dt)
# numerical solution using scipy.integrate
args = (p, b, r)
orbit = odeint(lorenz, x0, times, args)

#xorbit = []
#yorbit = []
#zorbit = []
#for x, y, z in orbit:
#    xorbit.append(x)
#    yorbit.append(y)
#    zorbit.append(z)

#plot3D using matplotlib
fig = plt.figure()
ax = fig.gca(projection='3d')
#ax.plot(xorbit, yorbit, zorbit)
ax.plot(orbit[:, 0], orbit[:, 1], orbit[:, 2])
ax.set_xlabel('x(t)')
ax.set_ylabel('y(t)')
ax.set_zlabel('z(t)')
plt.show()