tiny_simulations.double_pendulum のソースコード

"""
二重振り子のシミュレーションとアニメーションを描画するモジュール。

詳細説明:
このモジュールは、重力下で運動する二重振り子の運動方程式をSciPyを用いて数値的に解き、
その結果をMatplotlibを使用してアニメーションとして表示します。

関連リンク:
:doc:`double_pendulum_usage`
"""

import argparse

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from scipy.integrate import odeint


# 運動方程式の設定
[ドキュメント] def derivs(state, t, g, L1, L2, m1, m2): """ 二重振り子の運動方程式を計算する関数。 詳細説明: 現在の状態(各振り子の角度と角速度)から、ラグランジュ力学に基づく運動方程式を用いて 角度と角速度の時間微分を計算します。 :param state: list, 現在の状態 [theta1, theta2, theta1_dot, theta2_dot] :param t: float, 現在の時間 :returns: list, 状態の時間微分値 [dtheta1_dt, dtheta2_dt, dtheta1_dot_dt, dtheta2_dot_dt] """ dtheta1_dt = state[2] dtheta2_dt = state[3] delta = state[1] - state[0] denominator1 = (m1 + m2) * L1 - m2 * L1 * np.cos(delta) * np.cos(delta) denominator2 = (L2 / L1) * denominator1 dtheta1_dot_dt = ((m2 * L1 * state[2] ** 2 * np.sin(delta) * np.cos(delta) + m2 * g * np.sin(state[1]) * np.cos(delta) + m2 * L2 * state[3] ** 2 * np.sin(delta) - (m1 + m2) * g * np.sin(state[0])) / denominator1) dtheta2_dot_dt = ((-m2 * L2 * state[3] ** 2 * np.sin(delta) * np.cos(delta) + (m1 + m2) * g * np.sin(state[0]) * np.cos(delta) - (m1 + m2) * L1 * state[2] ** 2 * np.sin(delta) - (m1 + m2) * g * np.sin(state[1])) / denominator2) return [dtheta1_dt, dtheta2_dt, dtheta1_dot_dt, dtheta2_dot_dt]
[ドキュメント] def parse_args(): parser = argparse.ArgumentParser( description="二重振り子のシミュレーションとアニメーションを描画します。" ) # 定数の設定 parser.add_argument("--g", type=float, default=9.81, help="重力加速度 (m/s^2)") parser.add_argument("--L1", type=float, default=1.0, help="上部の振り子の長さ (m)") parser.add_argument("--L2", type=float, default=1.0, help="下部の振り子の長さ (m)") parser.add_argument("--m1", type=float, default=1.0, help="上部の振り子の質量 (kg)") parser.add_argument("--m2", type=float, default=1.0, help="下部の振り子の質量 (kg)") # 初期条件 parser.add_argument("--theta1", type=float, default=np.pi / 2, help="上部の振り子の初期角度 (ラジアン)") parser.add_argument("--theta2", type=float, default=np.pi / 2, help="下部の振り子の初期角度 (ラジアン)") parser.add_argument("--theta1_dot", type=float, default=0.0, help="上部の振り子の初期角速度 (rad/s)") parser.add_argument("--theta2_dot", type=float, default=0.0, help="下部の振り子の初期角速度 (rad/s)") parser.add_argument("--tmax", type=float, default=120.0, help="シミュレーション時間の最大値") parser.add_argument("--tstep", type=float, default=0.05, help="時間刻み") parser.add_argument("--interval", type=int, default=10, help="アニメーションのフレーム間隔 (ms)") return parser.parse_args()
[ドキュメント] def main(): args = parse_args() # 初期状態と時間配列の設定 state = [args.theta1, args.theta2, args.theta1_dot, args.theta2_dot] t = np.arange(0.0, args.tmax, args.tstep) # 運動方程式を数値的に解く y = odeint( derivs, state, t, args=(args.g, args.L1, args.L2, args.m1, args.m2), ) # シミュレーション結果の表示 x1 = args.L1 * np.sin(y[:, 0]) y1 = -args.L1 * np.cos(y[:, 0]) x2 = args.L2 * np.sin(y[:, 1]) + x1 y2 = -args.L2 * np.cos(y[:, 1]) + y1 xmax = args.L1 + args.L2 ymax = args.L1 + args.L2 fig = plt.figure() ax = fig.add_subplot( 111, autoscale_on=False, xlim=(-xmax, xmax), ylim=(-ymax, ymax), ) ax.set_aspect('equal') ax.grid() line, = ax.plot([], [], 'o-', lw=2) time_template = 'time = %.1fs' time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes) def init(): """ アニメーションの初期化関数。 詳細説明: プロットされる線オブジェクトと時間表示テキストをクリアし、アニメーションの描画準備を行います。 :returns: tuple, 初期化された線オブジェクトとテキストオブジェクト (line, time_text) """ line.set_data([], []) time_text.set_text('') return line, time_text def animate(i): """ アニメーションの各フレームを描画する関数。 詳細説明: インデックスに基づいて二重振り子の各質点の位置座標を更新し、現在の時間をテキストとして表示します。 :param i: int, フレームのインデックス :returns: tuple, 更新された線オブジェクトとテキストオブジェクト (line, time_text) """ thisx = [0, x1[i], x2[i]] thisy = [0, y1[i], y2[i]] line.set_data(thisx, thisy) time_text.set_text(time_template % t[i]) return line, time_text ani = animation.FuncAnimation( fig, animate, np.arange(1, len(y)), interval=args.interval, blit=True, init_func=init, ) plt.show()
if __name__ == "__main__": main()