大学生の雑記

色々なものを書いています 旧:旧帝大生の雑記ブログ

【Python】二重ばね振り子をシミュレーションする

二重ばね振り子の運動方程式オイラーラグランジュ方程式から導出し、それを基に数値計算を行います。

まずラグランジアンを計算します。図を用意するのが面倒だったので、二重振り子の場合と同様の変数設定をしていると考えてください。ただし質点はばねでつながれていますので l(t)は時間変化します。

運動エネルギーT


\begin{align}
T&=\frac{1}{2}m_1(\dot{x_1}^2+\dot{y_1}^2)+\frac{1}{2}m_2(\dot{x_2}^2+\dot{y_2}^2)\\
&=\frac{1}{2}[m_1[(\dot{l_1}\cos\theta_1-l_1\dot{\theta_1}\sin\theta_1)^2+(\dot{l_1}\sin\theta_1+l_1\dot{\theta_1}\cos\theta_1)^2] \\&+
m_2[(-l_1\dot{\theta_1}\sin\theta_1 - l_2\dot{\theta_2}\sin\theta_2 + \dot{l1}\cos\theta_1 + \dot{l_2}\cos\theta_2)^2 +
(l_1\dot{\theta_1}\cos\theta_1 + l_2\dot{\theta_2}\cos\theta_2 + \dot{l1}\sin\theta_1 + \dot{l_2}\sin\theta_2)^2]]\\
&=\frac{\dot{\theta}_1^{2} l_{1}^{2} m_{1}}{2} + \frac{\dot{\theta}_1^{2} l_{1}^{2} m_{2}}{2} + \dot{\theta}_1 \dot{\theta}_2 l_{1} l_{2} m_{2} \cos{\left(\theta_{1} - \theta_{2} \right)} - \dot{\theta}_1 \dot{l}_2 l_{1} m_{2} \sin{\left(\theta_{1} - \theta_{2} \right)} + \frac{\dot{\theta}_2^{2} l_{2}^{2} m_{2}}{2} + \dot{\theta}_2 \dot{l}_1 l_{2} m_{2} \sin{\left(\theta_{1} - \theta_{2} \right)} + \frac{\dot{l}_1^{2} m_{1}}{2} + \frac{\dot{l}_1^{2} m_{2}}{2} + \dot{l}_1 \dot{l}_2 m_{2} \cos{\left(\theta_{1} - \theta_{2} \right)} + \frac{\dot{l}_2^{2} m_{2}}{2}
\end{align}

ポテンシャルエネルギーUは、


\begin{align}
U=-gl_{1} m_{1} \cos{\left(\theta_{1} \right)} - g m_{2} \left(l_{1} \cos{\left(\theta_{1} \right)} + l_{2} \cos{\left(\theta_{2} \right)}\right) + \frac{k_{1} \left(L_{1} - l_{1}\right)^{2}}{2} + \frac{k_{2} \left(L_{2} - l_{2}\right)^{2}}{2}
\end{align}
からラグランジアンL = T-Uが求まります。

これをオイラーラグランジュ方程式に代入して、


\begin{align}
\ddot{\theta_1}&=\frac{L_{2} k_{2} \sin{\left(\theta_{1} - \theta_{2} \right)} - 2 \dot{\theta_1} \dot{l_1} m_{1} - g m_{1} \sin{\theta_{1}} - k_{2} l_{2} \sin{\left(\theta_{1} - \theta_{2} \right)}}{l_{1} m_{1}}\\
\ddot{\theta_2}&=\frac{- L_{1} k_{1} \sin{\left(\theta_{1} - \theta_{2} \right)} - 2 \dot{\theta_2} \dot{l_2} m_{1} + k_{1} l_{1} \sin{\left(\theta_{1} - \theta_{2} \right)}}{l_{2} m_{1}}\\
\ddot{l_1}&=\frac{L_{1} k_{1} - L_{2} k_{2} \cos{\left(\theta_{1} - \theta_{2} \right)} + \dot{\theta_1}^{2} l_{1} m_{1} + g m_{1} \cos{\theta_{1}} - k_{1} l_{1} + k_{2} l_{2} \cos{\left(\theta_{1} - \theta_{2} \right)}}{m_{1}}\\
\ddot{l_2}&=- \frac{L_{1} k_{1} \cos{\left(\theta_{1} - \theta_{2} \right)}}{m_{1}} + \frac{L_{2} k_{2}}{m_{2}} + \frac{L_{2} k_{2}}{m_{1}} + \dot{\theta_2}^{2} l_{2} + \frac{k_{1} l_{1} \cos{\left(\theta_{1} - \theta_{2} \right)}}{m_{1}} - \frac{k_{2} l_{2}}{m_{2}} - \frac{k_{2} l_{2}}{m_{1}}
\end{align}
が得られます。

コードを簡単に書く方法がわからなかったので、今回はscipyのodeintを使用して手動で実装しました。
コードは以下の通りです。

from numpy import sin, cos
from scipy.integrate import solve_ivp
from matplotlib.animation import FuncAnimation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.style as mplstyle
import math

g = 1 #重力加速度
m1 = 0.5 #質点1の質量
m2 = 0.5 #質点2の質量
k1 = 100 #ばね1のばね定数
k2 = 100 #ばね2のばね定数
L1 = 1 #ばね1の自然長
L2 = 1 #ばね2の自然長


# 運動方程式
def derivs(t, state):
    #state[0]=theta1, [1]=omega1, [2]=theta2, [3]=omega2, [4]=l1, [5]=v1, [6]=l2, [7]=v2
    #dydx[0]=theta1, {1}=\dot{omega1}, [2]=theta2, [3]=\dot{omega2}, [4]=l1, [5]=\dot{v1}, [6]=l2, [7]=\dot{v2}

    dydx = np.zeros_like(state)
    dydx[0] = state[1] #theta1=omega1

    delta = state[0] - state[2] #delta = theta1-theta2
    
    dydx[1] = (L2*k2*sin(delta) - 2 *m1* state[1] * state[5] - m1*g*sin(state[0]) - k2*state[6] * sin(delta))/(m1*state[4])
               
    dydx[2] = state[3] #theta2=omega2

    dydx[3] = (-L1*k1*sin(delta)-2*m1*state[3]*state[7]+k1*state[4]*sin(delta))/(m1*state[6])
    
    dydx[4] = state[5]
    
    dydx[5] = (L1*k1-L2*k2*cos(delta)+m1*state[1]*state[1]*state[4]+m1*g*cos(state[0])-k1*state[4]+k2*state[6]*cos(delta))/m1
    
    dydx[6] = state[7]
    
    dydx[7] = -(L1*k1/m1)*cos(delta)+L2*k2*(1/m1+1/m2)+state[3]*state[3]*state[6]+(k1*state[4]*cos(delta))/m1 - k2*state[6]*(1/m1+1/m2)

    return dydx


t_span = [0,20]
dt = 0.01
t = np.arange(t_span[0], t_span[1], dt)

# 初期条件
th1 = 90.0
w1 = 0.0
th2 = 90.0
w2 = 0.0
l1 = 180/math.pi * 0.9
v1 = 0
l2 = 180/math.pi * 0.9
v2 = 0
state = np.radians([th1, w1, th2, w2, l1, v1, l2, v2])


sol = solve_ivp(derivs, t_span, state, t_eval=t)
y = sol.y



a1=1
a2=1
def gen():
    for tt, th1, th2, l1, l2 in zip(t,y[0,:], y[2,:], y[4,:], y[6,:]):
        x1 = l1*sin(th1)
        y1 = -l1*cos(th1)
        x2 = l2*sin(th2) + x1
        y2 = -l2*cos(th2) + y1
        yield tt, x1, y1, x2, y2

fig, ax = plt.subplots()
ax.set_xlim(-(a1+a2), a1+a2)
ax.set_ylim(-(a1+a2), a1+a2)
ax.set_aspect('equal')
ax.grid()

locus, = ax.plot([], [], 'r-', linewidth=2)
line, = ax.plot([], [], 'o-', linewidth=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)

xlocus, ylocus = [], []
def animate(data):
    t, x1, y1, x2, y2 = data
    xlocus.append(x2)
    ylocus.append(y2)

    locus.set_data(xlocus, ylocus)
    line.set_data([0, x1, x2], [0, y1, y2])
    time_text.set_text(time_template % (t))

ani = FuncAnimation(fig, animate, gen, interval=1, repeat=True)

plt.show()

実行すると次のような運動が見られます。

【Python】三体問題をシミュレーションする

結果

コード
太陽、地球、月と書いてありますが上の結果は太陽でも地球でも月でもありません。
初期条件と書かれたstateの部分には太陽、地球、月のx, y座標およびx, y方向の初期速度が入ります。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import math

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

# 三体問題の微分方程式
def three_body_equations(t, state):
    G = 6.674e-11  # 重力定数
    m_sun = 2.01e28  # 太陽の質量 
    m_earth = 1.4e28#17  # 地球の質量 
    m_moon = 2e28  # 月の質量 

    x_sun, y_sun, vx_sun, vy_sun, x_earth, y_earth, vx_earth, vy_earth, x_moon, y_moon, vx_moon, vy_moon = state

    r_se = np.sqrt((x_earth - x_sun)**2 + (y_earth - y_sun)**2)
    r_sm = np.sqrt((x_moon - x_sun)**2 + (y_moon - y_sun)**2)
    r_me = np.sqrt((x_moon - x_earth)**2 + (y_moon - y_earth)**2)

    ax_sun = G * m_earth * (x_earth - x_sun) / r_se**3 + G * m_moon * (x_moon - x_sun) / r_sm**3
    ay_sun = G * m_earth * (y_earth - y_sun) / r_se**3 + G * m_moon * (y_moon - y_sun) / r_sm**3

    ax_earth = G * m_sun * (x_sun - x_earth) / r_se**3 + G * m_moon * (x_moon - x_earth) / r_me**3
    ay_earth = G * m_sun * (y_sun - y_earth) / r_se**3 + G * m_moon * (y_moon - y_earth) / r_me**3

    ax_moon = G * m_sun * (x_sun - x_moon) / r_sm**3 + G * m_earth * (x_earth - x_moon) / r_me**3
    ay_moon = G * m_sun * (y_sun - y_moon) / r_sm**3 + G * m_earth * (y_earth - y_moon) / r_me**3

    return np.array([vx_sun, vy_sun, ax_sun, ay_sun, vx_earth, vy_earth, ax_earth, ay_earth, vx_moon, vy_moon, ax_moon, ay_moon])

# アニメーションの初期化
def init():
    sun.set_data([], [])
    earth.set_data([], [])
    moon.set_data([], [])
    sun_traj.set_data([], [])
    earth_traj.set_data([], [])
    moon_traj.set_data([], [])
    return sun, earth, moon, sun_traj, earth_traj, moon_traj

# アニメーションの更新
def update(frame):
    global state
    state = runge_kutta(dt, lambda y: three_body_equations(0, y), state)

    sun.set_data(state[0], state[1])
    earth.set_data(state[4], state[5])
    moon.set_data(state[8], state[9])

    sun_traj.set_data(np.append(sun_traj.get_xdata(), state[0]),
                      np.append(sun_traj.get_ydata(), state[1]))

    earth_traj.set_data(np.append(earth_traj.get_xdata(), state[4]),
                        np.append(earth_traj.get_ydata(), state[5]))

    moon_traj.set_data(np.append(moon_traj.get_xdata(), state[8]),
                       np.append(moon_traj.get_ydata(), state[9]))

    return sun, earth, moon, sun_traj, earth_traj, moon_traj

# 初期条件
state = np.array([-1.5e11, 1.0e11, 1.0e3, 0.0e3, 0.0e11, 0.0e10, 0, -0.0e3, 1.5e11, -1.0e11, -1.0e3, -0.0e4])


# シミュレーションの設定
dt = 60 * 60 * 24 *2 
num_frames = 400*2  # シミュレーションのフレーム数

# グラフの初期化
fig, ax = plt.subplots()
l=2
ax.set_xlim(-l*10**11, l*10**11)
ax.set_ylim(-l*10**11, l*10**11)


sun, = ax.plot([], [], 'o', color='red', markersize=2)
earth, = ax.plot([], [], 'o', color='blue', markersize=2)
moon, = ax.plot([], [], 'o', color='green', markersize=2)

# 軌跡の初期化
sun_traj, = ax.plot([], [], '-', color='red', linewidth=1)
earth_traj, = ax.plot([], [], '-', color='blue', linewidth=1)
moon_traj, = ax.plot([], [], '-', color='green', linewidth=1)

# アニメーションの作成
interval_ms = 2  # アニメーションの描画速度(ミリ秒)
ani = FuncAnimation(fig, update, frames=num_frames, init_func=init, blit=True, interval=interval_ms)


plt.gca().set_aspect('equal', adjustable='box')
# アニメーションの表示
#plt.show()

Processingでセルオートマトン➃:オリジナルの交通モデルを作る

ビハム・ミドルトン・レヴィン交通モデルを参考にしてProcessingでオリジナルの対面歩行者モデルを作成しました。

  • コード
  • シミュレーション結果
    • q=0.2付近
    • 0.2 < q < 0.7のとき
    • q>0.7のとき
  • 渋滞での現象
    • 渋滞の移動
    • 渋滞内を横断するセル
続きを読む

人工生命を育てるシミュレーションゲーム『OpenPraparat』の魅力を紹介!

  • 【概要】
  • 【特徴】
  • 【プレイ感想】
  • 【その他】
    • pcスペックの目安
  • 【実装方法】

【概要】

「OpenPraparat」は、PCで苔を育てる人さん
https://twitter.com/alife_praparat?s=21&t=3UYPD6NTmLw6wziAvETsOA
が開発した、人工生命をシミュレーションするゲームです。


初期状態は単細胞生物であり、単細胞たちは自身の遺伝子に従って行動します。

プレイヤーは突然変異を設定したり、環境設定を変更することができます。

新たな形状の生命を発見することを目指す、生命の生存戦略や特徴を研究する、複数種類の種を同時に繁栄させて種の有利不利を観察する、など多彩な楽しみ方があります。

突然変異により発生したカラフルな生物たち
続きを読む