XLPack for Python および XLPack for Matplotlib の使用例 (2) アニメーション (単振り子)
下図のように, 長さ \(l\) の糸 (重さは無視できるものとします) で吊り下げられた重さ \(m\) の重りが左右に振れているものとします. 糸が垂直な状態からの角度を \(\theta\) で表します. これを単振り子といいます.
この運動は, 時間を \(t\) として次の微分方程式で表すことができます. なお, \(g\) は重力加速度 \((9.81 m/s^2)\) です.
\(d^2\theta/dt^2 = -(mg/l)sin\theta\)
ここでは簡単のために \(m = 1\) とし, 初期条件を \(\theta = \pi/4, d\theta/dt = 0\) として \(t = 0 \sim 10\) における数値解を求め, アニメーションで表示することにします.
1. 全体の構成
メインプログラムは以下のとおりです.
Sub Main()
Dim N As Long, T As Double, Y() As Double, Yp() As Double
Dim Nt As Long, Tend As Double
Dim X1() As Double, Y1() As Double
Dim Info As Long
N = 1
ReDim Y(N - 1), Yp(N - 1)
'-- Initial values
T = 0
Y(0) = Atn(1) ' Pi() / 4
Yp(0) = 0
'-- Solve ODE
Tend = 10
Nt = 200
ReDim X1(Nt - 1), Y1(Nt - 1)
Call ODE_2(N, T, Y(), Yp(), Nt, Tend, X1(), Y1(), Info)
If Info <> 0 Then Exit Sub
'-- Display animation
Call Animate(Nt, X1(), Y1(), Tend / Nt)
End Sub
サブルーチン ODE_2() は微分方程式の解を求めます. 得られた解はサブルーチン Animate() により表示されます. Animate() では XLPack for Python または XLPack for Matplotlib を使用してアニメーションで表示を行います.
実行結果は次のようになります.
2. 微分方程式の解
単振り子の微分方程式は 2 階常微分方程式なので, 通常は次のように 1 階の連立常微分方程式の形にして解きます.
\(d\theta_1/dt = \theta_2\)
\(d\theta_2/dt = -(mg/l)sin\theta_1\)
しかし, この場合は 1 階の項に依存しない特別な形をしているため XLPack ライブラリのサブルーチン Dopn43() (ルンゲ・クッタ・ニュストレム法) を使ってそのままの形で解くことができます.
Const L = 1, M = 1, G = 9.81
Const Mode = 3
Sub F_2(N As Long, T As Double, Y() As Double, Ypp() As Double)
Ypp(0) = -(M * G / L) * Sin(Y(0))
End Sub
Sub ODE_2(N As Long, T As Double, Y() As Double, Yp() As Double, Nt As Long, Tend As Double, X1() As Double, Y1() As Double, Info As Long)
Dim RTol(0) As Double, ATol(0) As Double
Dim Dt As Double, Tout As Double, I As Long
RTol(0) = 0.0000000001 '1e-10
ATol(0) = RTol(0)
Dt = Tend / Nt
Info = 0
For I = 0 To Nt - 1
Tout = (I + 1) * Dt
Call Dopn43(N, AddressOf F_2, T, Y(), Yp(), Tout, Tend, RTol(), ATol(), Mode, Info)
If Info < 0 Or Info > 10 Then
MsgBox ("Error in Dopn43: Info =" + Str(Info))
Exit Sub
End If
X1(I) = L * Sin(Y(0))
Y1(I) = -L * Cos(Y(0))
Next
End Sub
得られた解は, XY 座標に変換して X1(), Y1() に返します.
3. 解のアニメーション表示
得られた解を 表示する Python のコードを XLPack for Python を使って直接記述することができます.
配列データはそのまま渡すことができないので, いったんワークシートに書き出してから Python 側で xlc_get() を使って読み込んでいます.
Sub Animate(Nt As Long, X1() As Double, Y1() As Double, Dt As Double)
Dim I As Long
For I = 0 To Nt - 1
Cells(6 + I, 2) = X1(I)
Cells(6 + I, 3) = Y1(I)
Next
'-- Python codes
PY "import matplotlib.pyplot as plt"
PY "import matplotlib.animation as animation"
PY "from XLPackPy import *"
PY "nt =" + Str(Nt)
PY "dt =" + Str(Dt)
PY "x1 = xlc_get(5, 1, 4 + nt)"
PY "y1 = xlc_get(5, 2, 4 + nt)"
PY "fig = plt.figure()"
PY "ax = fig.gca()"
PY "ax.set_xlim(-1.0, 1.0)"
PY "ax.set_ylim(-1.5, 0.5)"
PY "ax.set_aspect('equal')"
PY "ax.grid()"
PY "ax.set_title('Animation (Pendulum)')"
PY "xt = []"
PY "yt = []"
PY "artist = []"
PY _
"for i in range(nt):" + vbNewLine + _
" pend, = ax.plot([0, x1[i]], [0, y1[i]], 'o-', color = 'b', lw=2)" + vbNewLine + _
" xt.append(x1[i])" + vbNewLine + _
" yt.append(y1[i])" + vbNewLine + _
" trace, = ax.plot(xt, yt, ':c', lw=1)" + vbNewLine + _
" artist.append((pend, trace))"
PY "ani = animation.ArtistAnimation(fig, artist, interval = dt * 1000)"
PY "plt.show()"
End Sub
XLPack for Matplotlib を使うと同じ処理を全て VBA で記述することができます. 内部的には Python を呼び出して処理します.
Dim Plt As New PyPlot
Dim Animation As New Animation
Sub Animate(Nt As Long, X1() As Double, Y1() As Double, Dt As Double)
Dim Fig As Figure, Ax As Axs, Artist() As PyObject, Anim As Animation
Dim X(1) As Double, Y(1) As Double
Dim I As Long
ReDim Artist(Nt - 1, 1)
Set Fig = Plt.Figure()
Set Ax = Fig.Gca()
Call Ax.Set_xlim(-1, 1)
Call Ax.Set_ylim(-1.5, 0.5)
Call Ax.Set_aspect(1)
Call Ax.Grid
Call Ax.Set_title("Animation (Pendulum)")
X(0) = 0: Y(0) = 0
For I = 0 To Nt - 1
X(1) = X1(I): Y(1) = Y1(I)
Set Artist(I, 0) = Ax.Plot(2, X(), Y(), "o-", "color=b,lw=2")
Set Artist(I, 1) = Ax.Plot(I + 1, X1(), Y1(), ":c", "lw=1")
Next
Set Anim = Animation.ArtistAnimation(Fig, Nt, 2, Artist(), "interval =" + Str(Dt * 1000))
Call Plt.Show
End Sub


