Python で数値解析するためのライブラリである SciPy
の使用例を示します。グラフは Matplotlib
の pyplot.plot()
を使って描きます。
# NumPy も使います
import numpy as np
# Matplotlib でグラフを描きます
import matplotlib.pyplot as plt
# 以下はグラフを SVG で Notebook にインライン表示させる設定
%config InlineBackend.figure_formats = ['svg']
SciPy による数値積分
scipy.integrate.quad()
SciPy には数値積分してくれる関数 scipy.integrate.quad()
が用意されています。それを使ってみましょう。結果には積分値と推定誤差が出力されます。
$\displaystyle \int_0^{\pi} \sin(\sin x)\, dx$ の計算をします。
数値計算の際は,三角関数等は NumPy の関数を使い,円周率 $\pi$ も NumPy の np.pi
を使います。
from scipy.integrate import quad
# quad を使うときは変数名 x 決め打ち
def sinsin(x):
return np.sin(np.sin(x))
# quad で数値積分
quadans = quad(sinsin, 0, np.pi)
print(quadans)
# 積分値のみを出力するには...
print("積分値は", quadans[0])
# 別のやり方
ans, err = quadans
print("積分値は", ans)
print("推定誤差は", err)
SciPy による求根(方程式の数値解)
scipy.optimize.root_scalar()
例としてベッセル関数 $J_0(x)$ のゼロ点を求めてみます。SciPy
におけるベッセル関数 $J_0(x)$ は scipy.special.jv(0,x)
です。
まずは $J_0(x) = 0$ となる $x$ のあたりをつけるためにグラフを描いてみます。
from scipy.special import jv
def J0(x):
return jv(0,x)
# pyplot.plot でグラフを描く
x = np.linspace(0, 10, 101)
plt.plot(x, J0(x))
# pyplot.plot で x 軸を描く
plt.plot(x, 0*x, color='black')
plt.xlabel('x')
plt.ylabel('J0(x)')
plt.grid()
plt.xlim(0, 10)
plt.title('ベッセル関数 J0(x)');
from scipy.optimize import root_scalar
# x が 2 ~ 3 の間で j0(x) = 0 の根を探す
ans = root_scalar(J0, bracket = [2, 3])
print(ans)
# 根のみを出力するには...
print(ans.root)
# ほんとに根になってるか確認。ゼロに近ければよい。
J0(ans.root)
# どうしてももっと精度が欲しいというのであれば...
# xtol に,小さい値をいれてみる
ans = root_scalar(J0, bracket = [2, 3], xtol=1e-14)
ans.root
# ほんとに根になってるか確認。ゼロに近ければよい。
J0(_)
SciPy による1階常微分方程式の数値解法
SciPy には常微分方程式を数値的に解いてくれる関数 scipy.integrate.solve_ivp()
が用意されています。それを使ってみましょう。
scipy.integrate.solve_ivp()
例として,簡単なロジスティック方程式
$$ \frac{dx}{dt} = f(x) = (1-x)\,x$$
を,初期条件 $t_0 = 0$ で $x_0 = x(t_0) = 0.1$,$t = t_0 = 0$ から $t_1 = 10$ まで解きます。
solve_ivp()
を使う場合は変数名が決め打ちで t, y
です。連立常微分方程式の場合は y
がリストになります。
from scipy.integrate import solve_ivp
# scipy.integrate.solve_ivp() は
# dy/dt = f(t, y) の形を解く
# solve_ivp を使う際は変数名 t, y は決め打ち
# solve.ivp() に渡す関数の引数の順番に注意。t が先
# 連立でないときは,1次元リストとして返す
def f(t, y):
x = y[0]
return [(1-x)*x]
# t の範囲
tini = 0
tend = 10
# 計算する点は t の範囲を N 等分して N+1 点
N = 20
t_list = np.linspace(tini, tend, N+1)
# y の初期値
y_ini = [0.1]
answer = solve_ivp(f, [tini, tend], y_ini, t_eval = t_list,
rtol = 1.e-12,
atol = 1.e-14)
print(answer)
# グラフにするために,t の値と y の値を取り出す
# t は answer.t, y は answer.y[0] として取り出す
# scatter では点のサイズは s=10 などと設定
plt.scatter(answer.t, answer.y[0], s=10)
plt.grid()
plt.xlabel("$t$")
plt.ylabel("$x(t)$")
plt.title("ロジスティック方程式の数値解");
参考までに,解析解は
$$x(t) = \frac{\exp(t)}{9 + \exp(t)}$$
です。(解析解がわかっているときには,わざわざ数値計算する必要はないのですが,練習ということで。)
# 解析解
def x(t):
return np.exp(t)/(9+np.exp(t))
# 解析解のラインも描く
t = np.linspace(tini, tend, 201)
plt.plot(t, x(t), color='red', linewidth=1, label='解析解')
plt.scatter(answer.t, answer.y[0], s=10, label='数値解')
plt.grid()
plt.xlabel("$t$")
plt.ylabel("$x(t)$")
plt.title("ロジスティック方程式の数値解")
plt.legend();
SciPy による2階常微分方程式の数値解法
scipy.integrate.solve_ivp()
SciPy には常微分方程式を数値的に解いてくれる関数 scipy.integrate.solve_ivp()
が用意されています。この函数は,1階常微分方程式だけでなく,連立1階常微分方程式も,従って高階の常微分方程式も解いてくれるので,それを使ってみましょう。
例として,簡単な減衰+強制振動の方程式
$$ \frac{d^2 x}{dt^2} = -x – a \frac{dx}{dt} + b \cos(t) $$
を,
- $a , b $ にいくつか値を入れて
- 初期条件 $t_0 = 0$ で $x_0 = x(t_0) = 3, v_0 = v(t_0) = 0$,
- $t = t_0 = 0$ から $t_1 = 20$ までを解きます。
2階常微分方程式は,
\begin{eqnarray}
\frac{dx}{dt} &=& F_1(t,x,v) = v \\
\frac{dv}{dt} &=& F_2(t,x,v) = -x – a v + b \cos t
\end{eqnarray}
として連立1階微分方程式の形にして解きます。
from scipy.integrate import solve_ivp
# scipy.integrate.solve_ivp() は
# dy/dt = F(t, y) の形を解く
def F(t, y, a, b):
# solve.ivp() に渡す関数の引数の順番に注意。t が先
x = y[0]
v = y[1]
F1 = v
F2 = -x - a*v + b*np.cos(t)
return [F1, F2]
tini = 0
tend = 20
t_list = np.linspace(tini, tend, 201)
y_ini = [3, 0]
a = 0.5; b = 0.2
answer1 = solve_ivp(F, [tini, tend], y_ini, t_eval = t_list,
args=(a, b),
rtol = 1.e-12,
atol = 1.e-14)
print(answer1)
# 数値解 x は .y[0] のようにして取り出す
# 数式を label に設定する際は $ で囲んで LaTeX 式に
# a, b の値を凡例に
key = '$a=%3.1f, b=%3.1f$' % (a, b)
plt.plot(answer1.t, answer1.y[0], label=key)
plt.grid()
plt.xlabel("$t$")
plt.ylabel("$x(t)$")
plt.legend()
plt.title("減衰+強制振動")
plt.xlim(0, 20);
問題
減衰+強制振動の方程式を以下の場合に解き,グラフにする。
まず $a$ の影響を調べるため,
- $a = 0, b = 0$
- $a = 0.2, b = 0$
- $a = 0.5, b = 0$
- $a = 0.8, b = 0$
次に,$b$ の影響を調べるため,
- $a = 0, b = 0$
- $a = 0, b = 0.2$
- $a = 0, b = 0.5$
- $a = 0, b = 0.8$