あめつちひらく

物理が好きです。数値シミュレーション始めました。資格の勉強とか。

二重振り子のシミュレーション


こんにちは。おととしです。
前回、二重振り子の運動方程式の計算が終わったので、次にプログラムをしていきます。

ラグランジアン使ってみた - あめつちひらく

プログラミングは少しだけやったことがあって、学生の頃にFortranとMTALABを用いていました。ただ、どちらもたくさんの人に使われている印象はないので、今回はPythonでやっていこうと思います。とはいっても、そもそもプログラミングは趣味程度しかやったことがないので、参考程度にご覧いただくと幸いです。

プログラムの流れとしては、
1.定数と変数の宣言
2.初期状態
3.θの更新式の定義
4.数値シミュレーションの更新式の定義(ルンゲ・クッタ法)
5.計算
6.プロット
というのを考えています。前回やったのは、3のθの更新式の定義だけです。

まず、定数と変数の宣言を行います。

import matplotlib.pyplot as plt
import matplotlib.animation as anm
import numpy as np

# Const Definition
g=9.8		# m/s2
m1=0.1		# kg
m2=0.1		# kg
l1=0.55		# m
l2=0.45		# m
mu=(m1+m2)/m2

dt=0.01
itr=1000 	# Iteration Times

theta1=[0]*itr
theta2=[0]*itr
V1=[0]*itr
V2=[0]*itr

X1=[0]*itr
Y1=[0]*itr
X2=[0]*itr
Y2=[0]*itr

gは重力加速度、m1とm2は2つのおもりのそれぞれの質量、l1とl2は2つの振り子のそれぞれの紐の長さです。
muは次の計算のためにここで定義しておきます。
dtは時間の区切りで、細かいほどシミュレーションした時の動きが滑らかになります。
itrは計算の終点で、多いほど長い時間のシミュレーションを行えますが、計算にも時間がかかります。
theta1からY2までは計算結果を保持するための「箱」を用意しています。

次に、初期状態を定義しておきます。

# Initial State
theta1[0]=np.pi/180*120
theta2[0]=np.pi/180*120
V1[0]=0
V2[0]=0
X1[0]=l1*np.sin(theta1[0])
Y1[0]=l1*np.cos(theta1[0])
X2[0]=X1[0]+l2*np.sin(theta2[0])
Y2[0]=Y1[0]+l2*np.cos(theta2[0])
t=0

二重振り子は最初の重りの位置でどのような挙動をするか変化します。今回は角度を120°にしています。
V1とV2はθの時間微分を表しています。
X1からY2までは、計算して得られるθの値を直交座標に変換しています。

次に、θの更新式の定義を行います。

# Function Definition
def F_V1(g, mu, l1, l2, theta1, theta2, V1, V2):
	D_theta=theta1-theta2
	F_V=(g*(np.sin(theta2)*np.cos(D_theta)-mu*np.sin(theta1))\
	-(l1*V1*V1*np.cos(D_theta)+l2*V2*V2)*np.sin(D_theta))\
	/(l1*(mu-(np.cos(D_theta))**2))
	return F_V

def F_V2(g, mu, l1, l2, theta1, theta2, V1, V2):
	D_theta=theta1-theta2
	F_V=(g*mu*(np.sin(theta1)*np.cos(D_theta)-np.sin(theta2))\
	+(mu*l1*V1*V1+l2*V2*V2*np.cos(D_theta))*np.sin(D_theta))\
	/(l2*(mu-(np.cos(D_theta))**2))
	return F_V

ここは、前回の記事で求めた式を間違いのないように書いたものとなります。

次に、今回のプログラムの肝となる、ルンゲ・クッタ法の記述となります。

# RK Definition
def RK4(g, mu, l1, l2, theta1, theta2, V1, V2, dt, func1, func2):
	k1_x1=V1
	k1_x2=V2
	k1_v1=func1(g, mu, l1, l2, theta1, theta2, V1, V2)
	k1_v2=func2(g, mu, l1, l2, theta1, theta2, V1, V2)

	k2_x1=V1+dt*k1_v1/2
	k2_x2=V2+dt*k1_v2/2
	k2_v1=func1(g, mu, l1, l2, theta1+dt*k1_x1/2, theta2+dt*k1_x2/2, V1+dt*k1_v1/2, V2+dt*k1_v2/2)
	k2_v2=func2(g, mu, l1, l2, theta1+dt*k1_x1/2, theta2+dt*k1_x2/2, V1+dt*k1_v1/2, V2+dt*k1_v2/2)

	k3_x1=V1+dt*k2_v1/2
	k3_x2=V2+dt*k2_v2/2
	k3_v1=func1(g, mu, l1, l2, theta1+dt*k2_x1/2, theta2+dt*k2_x2/2, V1+dt*k2_v1/2, V2+dt*k2_v2/2)
	k3_v2=func2(g, mu, l1, l2, theta1+dt*k2_x1/2, theta2+dt*k2_x2/2, V1+dt*k2_v1/2, V2+dt*k2_v2/2)

	k4_x1=V1+dt*k3_v1
	k4_x2=V2+dt*k3_v2
	k4_v1=func1(g, mu, l1, l2, theta1+dt*k3_x1, theta2+dt*k3_x2, V1+dt*k3_v1, V2+dt*k3_v2)
	k4_v2=func2(g, mu, l1, l2, theta1+dt*k3_x1, theta2+dt*k3_x2, V1+dt*k3_v1, V2+dt*k3_v2)

	theta1+=(k1_x1+2*k2_x1+2*k3_x1+k4_x1)*dt/6
	theta2+=(k1_x2+2*k2_x2+2*k3_x2+k4_x2)*dt/6
	V1+=(k1_v1+2*k2_v1+2*k3_v1+k4_v1)*dt/6
	V2+=(k1_v2+2*k2_v2+2*k3_v2+k4_v2)*dt/6
	return theta1, theta2, V1, V2

数値シミュレーションでは、ルンゲ・クッタ法と呼ばれる方法がよく用いられているようです。運動方程式のように、微分方程式の形で表されているものの、解析解を求めるのが難しい場合に用いられます。漸化式のようになっていて、前のステップの値を用いて運動の傾きを近似し、次の位置を近似的に求めています。私も方法として用いているだけで、原理や精度については完璧には理解していません。

次に、実際に計算を行います。

# Calculation
for i in range(itr-1):
	theta1[i+1], theta2[i+1], V1[i+1], V2[i+1]=RK4(g, mu, l1, l2, theta1[i], theta2[i], V1[i], V2[i], dt, F_V1, F_V2)

	# Convert to XY
	X1[i+1]=l1*np.sin(theta1[i+1])
	Y1[i+1]=l1*np.cos(theta1[i+1])

	X2[i+1]=X1[i+1]+l2*np.sin(theta2[i+1])
	Y2[i+1]=Y1[i+1]+l2*np.cos(theta2[i+1])

ここまでに関数を定義していたので、計算の記述はシンプルになります。

最後に、プロットしていきます。

# Final Plot
fig=plt.figure(figsize=(6,6))
def update(i, dt, X1, Y1, X2, Y2):
	if i !=0:
		plt.cla()

	if i<150:
		len=i
	else:
		len=150
	T_x1=[0]*len
	T_y1=[0]*len
	T_x2=[0]*len
	T_y2=[0]*len

	for k in range(len):
		T_x1[k]=X1[i-len+k]
		T_y1[k]=Y1[i-len+k]
		T_x2[k]=X2[i-len+k]
		T_y2[k]=Y2[i-len+k]
	
	t=i*dt
	plt.plot([0, X1[i], X2[i]],[0, Y1[i], Y2[i]], color='k')	# String
	plt.plot(0,0,marker='.', markersize=10, color='k')		# Origin
	plt.plot(T_x1,T_y1, color='r')					# m1 Trace
	plt.plot(T_x2,T_y2, color='b')					# m2 Trace
	plt.plot(X1[i],Y1[i], marker='.', markersize=20, color='r')	# m1
	plt.plot(X2[i],Y2[i], marker='.', markersize=20, color='b')	# m2
	plt.xlim(-1.1,1.1)
	plt.ylim(1.2,-1.0)
	plt.title('t= %.1f s' %t)

# Animation
def animation_play(itr, dt, X1, Y1, X2, Y2):
	params={
		'fig':fig,
		'func':update,
		'fargs':(dt, X1, Y1, X2, Y2),
		'interval':100,
		'frames':itr,
		'repeat':False,
	}
	ani=anm.FuncAnimation(**params)
	plt.show()

animation_play(itr, dt, X1, Y1, X2, Y2)

アニメーションまでできるというので、調べてやってみました。また、質点の軌跡を描いていた例があって、キレイだなと思ったのでマネしてみました。

ここまでやってみて、この程度のシミュレーションであれば、思ったより簡単にできたという印象です。ただ、世の中に存在する物理現象は二重振り子だけではありません。それらを網羅していてはどれだけリソースがあっても足りないと感じました。いちいち運動方程式を立てて計算するのではなく、もっと違うアプローチがありそうだと考えています。今度はそっちの方法でいろいろシミュレーションできたらいいな、と思います。

コード全文:

import matplotlib.pyplot as plt
import matplotlib.animation as anm
import numpy as np

# Const Definition
g=9.8		# m/s2
m1=0.1		# kg
m2=0.1		# kg
l1=0.55		# m
l2=0.45		# m
mu=(m1+m2)/m2

dt=0.01
itr=1000 	# Iteration Times

theta1=[0]*itr
theta2=[0]*itr
V1=[0]*itr
V2=[0]*itr

X1=[0]*itr
Y1=[0]*itr
X2=[0]*itr
Y2=[0]*itr

# Initial State
theta1[0]=np.pi/180*120
theta2[0]=np.pi/180*120
V1[0]=0
V2[0]=0
X1[0]=l1*np.sin(theta1[0])
Y1[0]=l1*np.cos(theta1[0])
X2[0]=X1[0]+l2*np.sin(theta2[0])
Y2[0]=Y1[0]+l2*np.cos(theta2[0])
t=0

# Function Definition
def F_V1(g, mu, l1, l2, theta1, theta2, V1, V2):
	D_theta=theta1-theta2
	F_V=(g*(np.sin(theta2)*np.cos(D_theta)-mu*np.sin(theta1))\
	-(l1*V1*V1*np.cos(D_theta)+l2*V2*V2)*np.sin(D_theta))\
	/(l1*(mu-(np.cos(D_theta))**2))
	return F_V

def F_V2(g, mu, l1, l2, theta1, theta2, V1, V2):
	D_theta=theta1-theta2
	F_V=(g*mu*(np.sin(theta1)*np.cos(D_theta)-np.sin(theta2))\
	+(mu*l1*V1*V1+l2*V2*V2*np.cos(D_theta))*np.sin(D_theta))\
	/(l2*(mu-(np.cos(D_theta))**2))
	return F_V

# RK Definition
def RK4(g, mu, l1, l2, theta1, theta2, V1, V2, dt, func1, func2):
	k1_x1=V1
	k1_x2=V2
	k1_v1=func1(g, mu, l1, l2, theta1, theta2, V1, V2)
	k1_v2=func2(g, mu, l1, l2, theta1, theta2, V1, V2)

	k2_x1=V1+dt*k1_v1/2
	k2_x2=V2+dt*k1_v2/2
	k2_v1=func1(g, mu, l1, l2, theta1+dt*k1_x1/2, theta2+dt*k1_x2/2, V1+dt*k1_v1/2, V2+dt*k1_v2/2)
	k2_v2=func2(g, mu, l1, l2, theta1+dt*k1_x1/2, theta2+dt*k1_x2/2, V1+dt*k1_v1/2, V2+dt*k1_v2/2)

	k3_x1=V1+dt*k2_v1/2
	k3_x2=V2+dt*k2_v2/2
	k3_v1=func1(g, mu, l1, l2, theta1+dt*k2_x1/2, theta2+dt*k2_x2/2, V1+dt*k2_v1/2, V2+dt*k2_v2/2)
	k3_v2=func2(g, mu, l1, l2, theta1+dt*k2_x1/2, theta2+dt*k2_x2/2, V1+dt*k2_v1/2, V2+dt*k2_v2/2)

	k4_x1=V1+dt*k3_v1
	k4_x2=V2+dt*k3_v2
	k4_v1=func1(g, mu, l1, l2, theta1+dt*k3_x1, theta2+dt*k3_x2, V1+dt*k3_v1, V2+dt*k3_v2)
	k4_v2=func2(g, mu, l1, l2, theta1+dt*k3_x1, theta2+dt*k3_x2, V1+dt*k3_v1, V2+dt*k3_v2)

	theta1+=(k1_x1+2*k2_x1+2*k3_x1+k4_x1)*dt/6
	theta2+=(k1_x2+2*k2_x2+2*k3_x2+k4_x2)*dt/6
	V1+=(k1_v1+2*k2_v1+2*k3_v1+k4_v1)*dt/6
	V2+=(k1_v2+2*k2_v2+2*k3_v2+k4_v2)*dt/6
	return theta1, theta2, V1, V2

# Calculation
for i in range(itr-1):
	theta1[i+1], theta2[i+1], V1[i+1], V2[i+1]=RK4(g, mu, l1, l2, theta1[i], theta2[i], V1[i], V2[i], dt, F_V1, F_V2)

	# Convert to XY
	X1[i+1]=l1*np.sin(theta1[i+1])
	Y1[i+1]=l1*np.cos(theta1[i+1])

	X2[i+1]=X1[i+1]+l2*np.sin(theta2[i+1])
	Y2[i+1]=Y1[i+1]+l2*np.cos(theta2[i+1])

# Final Plot
fig=plt.figure(figsize=(6,6))
def update(i, dt, X1, Y1, X2, Y2):
	if i !=0:
		plt.cla()

	if i<150:
		len=i
	else:
		len=150
	T_x1=[0]*len
	T_y1=[0]*len
	T_x2=[0]*len
	T_y2=[0]*len

	for k in range(len):
		T_x1[k]=X1[i-len+k]
		T_y1[k]=Y1[i-len+k]
		T_x2[k]=X2[i-len+k]
		T_y2[k]=Y2[i-len+k]
	
	t=i*dt
	plt.plot([0, X1[i], X2[i]],[0, Y1[i], Y2[i]], color='k')	# String
	plt.plot(0,0,marker='.', markersize=10, color='k')		# Origin
	plt.plot(T_x1,T_y1, color='r')					# m1 Trace
	plt.plot(T_x2,T_y2, color='b')					# m2 Trace
	plt.plot(X1[i],Y1[i], marker='.', markersize=20, color='r')	# m1
	plt.plot(X2[i],Y2[i], marker='.', markersize=20, color='b')	# m2
	plt.xlim(-1.1,1.1)
	plt.ylim(1.2,-1.0)
	plt.title('t= %.1f s' %t)

# Animation
def animation_play(itr, dt, X1, Y1, X2, Y2):
	params={
		'fig':fig,
		'func':update,
		'fargs':(dt, X1, Y1, X2, Y2),
		'interval':100,
		'frames':itr,
		'repeat':False,
	}
	ani=anm.FuncAnimation(**params)
	plt.show()

animation_play(itr, dt, X1, Y1, X2, Y2)

参考:
ルンゲ=クッタ法 - Wikipedia
2重8足振り子 - YouTube