2重振り子のシミュレーション
前回、多自由度系の運動方程式をルンゲクッタ法でシミュレーションする方法について記載しました。
mori-memo.hateblo.jp
今回は、それを応用してよくある2重振り子のカオス挙動をシミュレーションしてみましょう。
wikipediaにて、『2重振り子』と検索すると2重振り子のアニメーションが出てくるので、今回の目標はそれを作成することとします
ja.wikipedia.org
2重振り子の運動方程式
対象のシステムは下記の通りとします。
このシステムの運動方程式は、下記のような2階の常微分方程式で表すことができます。
式変形
ここで、いろいろと式変形を行い、2階の常微分方程式を1階の常微分方程式の形で表します。
簡単のため、下記の変数を定義しておきます。
をそれぞれと置くと、上記の運動方程式は、下記の1階の連立常微分方程式に変換できる。
これによりを陽解法で解ける形になりました。
以前せっかく作ったので、ルンゲクッタ法を用いてシミュレーションしてみましょう。
Pythonで実装
コード全文は以下です。
import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from matplotlib.animation import PillowWriter from solver import runge_kutta_method import numpy as np def func(omega, theta): '''運動方程式''' # 各種変数定義 C = np.cos(theta[0] - theta[1]) S = np.sin(theta[0] - theta[1]) M = np.sum(m) # マトリクスの生成 v = np.zeros((2)) v[0] = (g * (np.sin(theta[1]) * C - M * np.sin(theta[0]) / m[1]) \ - (l[0] * C * omega[0] ** 2 + l[1] * omega[1] ** 2) * S) \ / (l[0] * (M / m[1] - C ** 2)) v[1] = (g * (M / m[1]) * (C * np.sin(theta[0]) - np.sin(theta[1])) \ + ((M * l[0] * omega[0] ** 2) / m[1] + (l[1] * omega[1] ** 2) * C) * S) \ / (l[1] * (M / m[1] - C ** 2)) return v l = np.array((0.6, 0.5)) # 振り子の長さ m = np.array((0.5, 0.7)) # 振り子の重さ g = 9.8 # 重力加速度 omega = np.array((0, 0)) # 初期の角速度(今回は0) theta = np.array((np.pi / 2, np.pi / 2)) # 初期の角度(今回は90度) t_max = 20 # 終了時間 dt = 0.001 # サンプリング間隔 t = np.arange(0, t_max, dt) # 時間データ # プロット(各自由度の初期間隔を2としてプロットする) x_rk = runge_kutta_method(t, dt, theta, omega, func) # theta fig = plt.figure() ax = fig.add_subplot(1, 1, 1) line2, = ax.plot([], [], color='k') # 軌跡のトレース im, = ax.plot([], [], color='b', marker='o', \ markersize=10, linestyle='None') # 振り子の球の部分 line, = ax.plot([], [], color='b') # 振り子の棒の部分 x_track = l[0] * np.sin(x_rk[:, 0]) + l[1] * np.sin(x_rk[:, 1]) y_track = - l[0] * np.cos(x_rk[:, 0]) - l[1] * np.cos(x_rk[:, 1]) ax.set_xlim(-1.2, 1.2) ax.set_ylim(-2.2, 0.2) ax.set_aspect('equal') # 初期化関数 def init(): # only required for blitting to give a clean slate. im.set_xdata([np.nan, np.nan]) im.set_ydata([np.nan, np.nan]) line.set_xdata([np.nan, np.nan, np.nan]) line.set_ydata([np.nan, np.nan, np.nan]) line2.set_xdata([np.nan]) line2.set_ydata([np.nan]) return line, line2, im, # グラフ更新関数 def update_anim(f): theta1 = x_rk[f, 0] theta2 = x_rk[f, 1] x = [l[0] * np.sin(theta1), l[0] * np.sin(theta1) + l[1] * np.sin(theta2)] y = [- l[0] * np.cos(theta1), - l[0] * np.cos(theta1) - l[1] * np.cos(theta2)] line2.set_data(x_track[:f], y_track[:f]) im.set_data(x, y) line.set_data([0, x[0], x[1]], [0, y[0], y[1]]) return line, line2, im, ani = FuncAnimation( fig, # Figureオブジェクト update_anim, # グラフ更新関数 init_func=init, frames = range(0, 20000, 100), # フレームを設定 interval = 100, # 更新間隔(ms) repeat = True, # 描画を繰り返す blit = True, ) # アニメーションのgif形式での保存 ani.save("anim_test.gif", writer='pillow') plt.show()
実際に出力された結果はこちら。
プログラムの説明
以前の記事でアニメーションを作った際と題材がほぼ同じなので、違うところだけ説明します。
mori-memo.hateblo.jp
ルンゲクッタ法のモジュール化
まず、solver.pyというファイルを作って以下の記事で作ったコードを入れています。ルンゲクッタ法の関数です
mori-memo.hateblo.jp
solver.pyの中身は以下です。
import numpy as np def runge_kutta_method(t, dt, x_0, v_0, func): '''ルンゲクッタ法''' n = len(x_0) x = np.zeros((len(t), n)) v = np.zeros((len(t), n)) for i in range(len(t) - 1): if i == 0: x[i, :] = x_0 v[i, :] = v_0 k1_x = v[i, :] * dt k1_v = func(v[i, :], x[i, :]) * dt k2_x = (v[i, :] + k1_v / 2) * dt k2_v = func(v[i, :] + k1_v / 2, x[i, :] + k1_x / 2) * dt k3_x = (v[i, :] + k2_v / 2) * dt k3_v = func(v[i, :] + k2_v / 2, x[i, :] + k2_x / 2) * dt k4_x = (v[i, :] + k3_v) * dt k4_v = func(v[i, :] + k3_v / 2, x[i, :] + k3_x) * dt x[i + 1, :] = x[i, :] + (k1_x + 2 * k2_x + 2 * k3_x + k4_x) / 6 v[i + 1, :] = v[i, :] + (k1_v + 2 * k2_v + 2 * k3_v + k4_v) / 6 return x
入力には終了時刻、タイムステップ、変位の初期値、速度の初期値、運動方程式の関数をいれます。
出力は各自由度の各タイムステップでの変位が2次元配列で出力されます。
今回は変位ではなく角度ですね。
球の軌跡のプロット
今回は球の軌跡のプロットを新しく導入しました。
ルンゲクッタ法でを求めた後、変位に変換して各タイムステップでの変位を計算しておきます。
x_track = l[0] * np.sin(x_rk[:, 0]) + l[1] * np.sin(x_rk[:, 1]) y_track = - l[0] * np.cos(x_rk[:, 0]) - l[1] * np.cos(x_rk[:, 1])
その後、グラフ更新関数(update_anim)内で現在のタイムステップまでの軌跡をスライスで指定してline2に.set_dataメソッドでデータを渡しています。
def update_anim(f): # 略 line2.set_data(x_track[:f], y_track[:f]) # 略
他は全部これまでやったコードを使いまわしているだけなのですぐ理解できるかと思います。
終わりに
初期位置や、棒の長さ、重さ等パラメータを変えていろいろなカオスを楽しみましょう!