Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

scipy の solve_ivp の精度が不自然に悪い (rk45)

解決したいこと

常微分方程式を用意し、 scipysolve_ivp と 自作の 4次ルンゲクッタコードを比較したのですが、 solve_ivp の精度が極端に悪いです。この原因について分かる方いたら教えていただけますでしょうか。

発生している問題

2次元空間で初速度 (10,0), 向心加速度の大きさ10 で等速円運動のシミュレーションを行いました。
理論的には、これは半径10の円を描くはずです。

solve_ivp (method="RK45") による結果を青、
自作コード (RK4) による結果を赤でプロットしたところ、次の画像のようになりました。
image.png

rk4は4次精度、rk45は5次精度なので、rk45のほうがより半径10の円に近づくはずかと思うのですが、実際には rk4 のほうがはるかに良い結果を得ております。

該当するソースコード

シミュレーションのコード
from scipy.integrate import solve_ivp
import math
import matplotlib.pyplot as plt
import numpy as np
def get_a(t, r, v):
    # 進行方向に垂直な方向に10だけ加速し続ける
    x,y = 0,1
    
    進行方向 = math.atan2(v[y], v[x])
    加速度の方向 = 進行方向 + math.pi / 2
    加速度の大きさ = 10

    a_x = 加速度の大きさ * math.cos(加速度の方向)
    a_y = 加速度の大きさ * math.sin(加速度の方向)

    return np.array([a_x, a_y])

def get_v_and_a(t, r_and_v):
    # r_and_v == np.array([r_x, r_y, v_x, v_y])
    # v_and_a == np.array([v_x, v_y, a_x, a_y])
    r = r_and_v[0:2]
    v = r_and_v[2:4]
    a = get_a(t, r, v)

    v_and_a = np.concat([v,a])
    return v_and_a

# シミュレーションの設定
初期位置 = [0, 0]
初速度 = [10, 0]
終了時刻 = 300
時刻幅 = 0.01


# シミュレーションの実行 (scipy.integrate.solve_ivp rk45)
初期値 = np.array(初期位置 + 初速度)
時点たち = np.arange(0, 終了時刻+時刻幅, 時刻幅)
結果 = solve_ivp(
    fun = get_v_and_a,
    t_span = [0, 終了時刻],
    y0 = 初期値,
    method = "RK45",
    t_eval = 時点たち
)

scipy_ts = 結果.t
scipy_xs = 結果.y[0]
scipy_ys = 結果.y[1]

# シミュレーションの実行 (自作コード rk4)
my_ts, my_xs, my_ys = [], [], []
時刻 = 0.
ステップ = 0
r = np.array(初期位置, dtype="float64")
v = np.array(初速度, dtype="float64")
delta_t = 時刻幅
while 時刻 <= 終了時刻:
    my_ts += [時刻]
    my_xs += [r[0]]
    my_ys += [r[1]]
    
    t = 時刻 + delta_t    
    r_and_v = np.concat([r,v])

    k0 = get_v_and_a(t - delta_t,       r_and_v                     )
    k1 = get_v_and_a(t - 1/2 * delta_t, r_and_v + 1/2 * k0 * delta_t)
    k2 = get_v_and_a(t - 1/2 * delta_t, r_and_v + 1/2 * k1 * delta_t)
    k3 = get_v_and_a(t                , r_and_v +       k2 * delta_t)

    ステップ += 1
    時刻 = ステップ * delta_t
    r_and_v += (1/6 * k0 + 1/3 * k1 + 1/3 * k2 + 1/6 * k3)*delta_t
    r = r_and_v[0:2]
    v = r_and_v[2:4]

# 結果のグラフ
# 1. 用意
 = max([abs(x) for x in scipy_xs] + [abs(y) for y in scipy_ys])*2.5
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111)
plt.xlim(-/2, /2)
plt.ylim(-/2, /2)
plt.xlabel("X")
plt.ylabel("Y")
ax.set_aspect("equal", adjustable="box")

# 2. 軌跡
ax.plot(scipy_xs, scipy_ys, lw = 0.1, color = "blue")
ax.plot(my_xs, my_ys, lw = 0.1, color = "red")

# 3. 時刻
for i, t in enumerate(scipy_ts):
    if t % 5 == 0:
        ax.text(
            scipy_xs[i], scipy_ys[i], str(round(t)), fontsize=8,
            ha = "center", va = "center", color = "blue"
        )
    if t % 1 == 0 and (t <= 5 or 295 <= t): 
        ax.text(
            my_xs[i], my_ys[i], str(round(t)), fontsize=8,
            ha = "center", va = "center", color = "red"
        )



# 4. 表示
leg = ax.legend(["scipy.integrate.solve_ivp rk45", "homemade code rk4"])
leg.get_lines()[0].set_linewidth(1)
leg.get_lines()[1].set_linewidth(1)
ax.grid(True)
plt.show()
自作コードについて

$$\begin{array}{l}
\overrightarrow{{\rm r\_and\_v}(t)}=\overrightarrow{{\rm r\_and\_v}(t-\Delta t)}
+ \left(
\frac{1}{6} \overrightarrow{k_0} +
\frac{1}{3} \overrightarrow{k_1} +
\frac{1}{3} \overrightarrow{k_2} +
\frac{1}{6} \overrightarrow{k_3}
\right)\Delta t\\
\overrightarrow{k_0} = \overrightarrow{{\rm get\_v\_and\_a}\left(
t-\Delta t,
\overrightarrow{{\rm r\_and\_v}(t-\Delta t)}
\right)}\\
\overrightarrow{k_1} = \overrightarrow{{\rm get\_v\_and\_a}\left(
t-\frac{1}{2}\Delta t,
\overrightarrow{{\rm r\_and\_v}(t-\Delta t) + \frac{1}{2}\overrightarrow{k_0}\Delta t}
\right)}\\
\overrightarrow{k_2} = \overrightarrow{{\rm get\_v\_and\_a}\left(
t-\frac{1}{2}\Delta t,
\overrightarrow{{\rm r\_and\_v}(t-\Delta t) + \frac{1}{2}\overrightarrow{k_1}\Delta t}
\right)}\\
\overrightarrow{k_3} = \overrightarrow{{\rm get\_v\_and\_a}\left(
t,
\overrightarrow{{\rm r\_and\_v}(t-\Delta t) + \overrightarrow{k_2}\Delta t}
\right)}\\
\end{array}$$

のように更新式を定義しています。ただし$\overrightarrow{{\rm r\_and\_v}(t)}$ は、時刻 $t$ における位置と速度をまとめた4次元ベクトルであり、 $\overrightarrow{{\rm get\_v\_and\_a}\left(
t,
\overrightarrow{{\rm r\_and\_v}(t)}
\right)}$ は、$\frac{\rm d}{{\rm d}t}\overrightarrow{{\rm r\_and\_v}(t)}$ であり、位置と速度をまとめた4次元ベクトルを、速度と加速度をまとめた4次元ベクトルに変換する関数です。

(自作コードについて 終わり)

自分で試したこと

公式ドキュメント を読んでみました。

0

1Answer

scipy_ivpr_tola_tol オプションにて許容誤差を指定すると、飛躍的に精度が向上するようでした。

0Like

Your answer might help someone💌