XLPack for Python および XLPack for Matplotlib の使用例 (3) アニメーション (アレンストーフ軌道)
質量が \(\mu\) と \(\mu’ (= 1 – \mu)\) の2つの天体が平面内回転運動をしており, その2つに比べて質量が無視できる第3の物体が同一平面内を運動するものとします. その方程式は次のようになります (参考文献: E. Hairer, 他「常微分方程式の数値解法 I」(2007) シュプリンガー・ジャパン).\[
\begin{align}
& y_1” = y_1 + 2y_2′ – \mu'(y_1 + \mu)/D_1 – \mu(y_1 – \mu’)/D_2 \\
& y_2” = y_2 – 2y_1′ – \mu’y_2/D_1 – \mu y_2/D_2 \\
& ただし, \\
& D_1 = ((y_1 + \mu)^2 + y_2^2)^{3/2} \\
& D_2 = ((y_1 – \mu’)^2 + y_2^2)^{3/2} \\
& \mu = 0.012277471, \mu’ = 1 – \mu \\
\end{align}
\] 次の初期値を用いると, \(t = 17.0652165601579625588917206249\) で周期解となることが知られています.
\[
\begin{align}
& y_1(0) = 0.994, y_1′(0) = 0 \\
& y_2(0) = 0, y_2′(0) = -2.00158510637908252240537862224 \\
\end{align}
\] これはアレンストーフ軌道とよばれています.
ここでは, 1周期分の数値解を求めアニメーションで表示することにします.
1. 全体の構成
メインプログラムは以下のとおりです.
Const Tend = 17.065216560158
Sub Main()
Dim Xt() As Double, Yt() As Double
Dim Dt As Double, Nt As Long, Info As Long
Dt = 0.1
Nt = Int(Tend / Dt) + 1
ReDim Xt(Nt), Yt(Nt)
Call ODE(Tend, Dt, Nt, Xt(), Yt(), Info)
Call Animate(Nt, Xt(), Yt(), Dt)
End Sub
サブルーチン ODE() は微分方程式の解を求めます. 得られた解はサブルーチン Animate() により表示されます. Animate() では XLPack for Python または XLPack for Matplotlib を使用してアニメーションで表示を行います.
実行結果は次のようになります.
2. 微分方程式の解
この微分方程式は 2 階常微分方程式なので, 1 階の連立常微分方程式の形に変数変換して 4 元の方程式として解きます.
Const Xinit = 0.994
Sub F(N As Long, T As Double, Y() As Double, Yp() As Double)
Const Mu = 0.012277471, Mup = 1 - Mu
Dim D1 As Double, D2 As Double
Yp(0) = Y(2)
Yp(1) = Y(3)
D1 = ((Y(0) + Mu) ^ 2 + Y(1) ^ 2) ^ (3# / 2#)
D2 = ((Y(0) - Mup) ^ 2 + Y(1) ^ 2) ^ (3# / 2#)
Yp(2) = Y(0) + 2 * Y(3) - Mup * (Y(0) + Mu) / D1 - Mu * (Y(0) - Mup) / D2
Yp(3) = Y(1) - 2 * Y(2) - Mup * Y(1) / D1 - Mu * Y(1) / D2
End Sub
Sub ODE(Tend As Double, Dt As Double, Nt As Long, Xt() As Double, Yt() As Double, Info As Long)
Const N = 4, Mode = 3, Tol = 0.0000000001 '1.0e-10
Dim Y(N - 1) As Double, T As Double, Tout As Double
Dim RTol(0) As Double, ATol(0) As Double, I As Integer
RTol(0) = Tol
ATol(0) = Tol
T = 0
Y(0) = Xinit: Y(1) = 0: Y(2) = 0: Y(3) = -2.00158510637908
Info = 0
Xt(0) = Y(0)
Yt(0) = Y(1)
For I = 0 To Nt - 1
Tout = (I + 1) * Dt
If Tout > Tend Then Tout = Tend
Call Derkfa(N, AddressOf F, T, Y(), Tout, Tend, RTol(), ATol(), Mode, Info)
If Info < 0 Or Info > 10 Then Exit Sub
Xt(I + 1) = Y(0)
Yt(I + 1) = Y(1)
Next
End Sub
得られた解は, Xt(), Yt() に保存します.
3. 解のアニメーション表示
得られた解を 表示する Python のコードを XLPack for Python を使って直接記述することができます.
配列データはそのまま渡すことができないので, いったんワークシートに書き出してから Python 側で xlc_get() を使って読み込んでいます.
Sub Animate(Nt As Long, Xt() As Double, Yt() As Double, Dt As Double)
Const Col = 2, Row = 6
Dim I As Long
For I = 0 To Nt
Cells(Row + I, Col) = Xt(I)
Cells(Row + I, Col + 1) = Yt(I)
Next
'-- Python codes
PY "import matplotlib.pyplot as plt"
PY "import matplotlib.animation as animation"
PY "from XLPackPy import *"
PY "fig = plt.figure()"
PY "ax = fig.gca()"
PY "ax.grid()"
PY "ax.plot(0, 0, '-o', color = 'orange')"
PY "ax.plot(1, 0, '-o', color = 'yellow')"
PY "ax.set_xlim(-1.5, 1.5)"
PY "ax.set_ylim(-1.5, 1.5)"
PY "xt = xlc_get(5, 1," + Str(5 + Nt) + ")"
PY "yt = xlc_get(5, 2," + Str(5 + Nt) + ")"
PY "xtrace = []; ytrace = []; artist = []"
PY "for i in range(len(xt)):" + vbNewLine + _
" a_x = [xt[i]]; a_y = [yt[i]]" + vbNewLine + _
" a, = ax.plot(a_x, a_y, 'o-', color = 'b')" + vbNewLine + _
" xtrace.append(xt[i]); ytrace.append(yt[i])" + vbNewLine + _
" trace, = ax.plot(xtrace, ytrace, ':c', lw=1)" + vbNewLine + _
" artist.append((a, trace))"
PY "ani = animation.ArtistAnimation(fig, artist, interval = 100, blit = True, repeat = False)"
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, Xt() As Double, Yt() As Double, Dt As Double)
Dim Ex(0) As Double, Ey(0) As Double, Mx(0) As Double, My(0) As Double
Dim X(0) As Double, Y(0) As Double, I As Long
Dim Fig As Figure, Ax As Axs, Artist() As PyObject
Dim Emarker As PyObject, Mmarker As PyObject
Dim Anim As ArtistAnimation
Ex(0) = 0: Ey(0) = 0
Mx(0) = Xinit: My(0) = 0
ReDim Artist(Nt, 1)
Set Fig = Plt.Figure()
Set Ax = Fig.Gca()
Call Ax.Grid
Call Ax.Set_xlim(-1.5, 1.5)
Call Ax.Set_ylim(-1.5, 1.5)
Set Emarker = Ax.Plot(1, Ex(), Ey(), "", "color=orange,marker=o")
Set Mmarker = Ax.Plot(1, Mx(), My(), "", "color=yellow,marker=o")
For I = 0 To Nt
X(0) = Xt(I): Y(0) = Yt(I)
Set Artist(I, 0) = Ax.Plot(1, X(), Y(), "", "color=b,marker=o")
Set Artist(I, 1) = Ax.Plot(I + 1, Xt(), Yt(), ":c", "lw=1")
Next
Dim Ani As PyObject
Set Anim = Animation.ArtistAnimation(Fig, Nt + 1, 2, Artist(), "interval =" + Str(Dt * 1000) + ", repeat = False")
Call Plt.Show
End Sub


