Return to Python でコンピュータ演習(旧版アーカイブ)

Matplotlib でアニメーション

補足:授業でやってみて気づいたのですが,以下のようにして作成した mp4 ファイルは,Windows の Edge では見ることができないようです。同じ Windows でも Firefox なら見れますし,Mac の Edge でも大丈夫です。(Mac の Firefox で準備しているので,気づかなかった。)なので,Windows で以下の実習をする場合は,互換性維持のため,

ani.save("anim01.mp4")

ではなく,

ani.save("anim01.gif")

などとして保存する必要があるだろう。以下を参照:

ライブラリの import

必要なライブラリを import します。Matplotlib の FuncAnimation() を使ってアニメーションを作成してみます。

In [1]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# 以下はグラフを SVG で Notebook にインライン表示させる設定
%config InlineBackend.figure_formats = ['svg']
# グラフのサイズ。お好みで。
plt.rcParams["figure.figsize"] = (6.4, 4.8)

Matplotlib でグラフを描くおさらい

Matplotlib でグラフ作成」を参照。

基本形:

plt.plot([x0, x1, ..., xn], [y0, y1, ..., yn]);

等間隔の数値配列の作成

座標値のリストを [0, 0.2, 0.4, ...] と手で一つ一つ入力するのは大変なので,以下のように等間隔の数値配列を作成する関数を使う。

np.arange() は間隔を指定
In [2]:
# 0以上1未満,間隔0.2ごとの数値要素を作成
x = np.arange(0, 1, 0.2)
print(x)
print(x**2)
[0.  0.2 0.4 0.6 0.8]
[0.   0.04 0.16 0.36 0.64]
np.linspace() は要素数を指定
In [3]:
# 0から1までを5等分し,端点を含めて6個の数値要素を作成
x = np.linspace(0, 1, 6)
print(x)
# NumPy の関数を使う
print(np.sin(x))
[0.  0.2 0.4 0.6 0.8 1. ]
[0.         0.19866933 0.38941834 0.56464247 0.71735609 0.84147098]

斜方投射の軌道をパラパラアニメにする

運動方程式

水平方向を $x$, 鉛直上向きを $y$ として,運動方程式は重力加速度の大きさを $g$ として

\begin{eqnarray}
\frac{d^2 x}{dt^2} &=& 0 \\
\frac{d^2 y}{dt^2} &=& -g
\end{eqnarray}

解は

\begin{eqnarray}
x(t) &=& x_0 + v_{0x} t \\
y(t) &=& y_0 + v_{0y} t – \frac{1}{2} g t^2
\end{eqnarray}

初期条件を $x_0 = 0, y_0 = 0, v_{0x} = v_0 \cos\theta, v_{0y} = v_0 \sin\theta$ とすると

\begin{eqnarray}
x(t) &=& v_{0} \cos\theta \cdot t \\
y(t) &=& v_{0} \sin\theta \cdot t – \frac{1}{2} g t^2
\end{eqnarray}

無次元化

この系に特徴的な時間 $\displaystyle \tau \equiv \frac{v_0}{g}$ および長さ $\displaystyle \ell \equiv v_0 \tau = \frac{v_0^2}{g}$ で解を無次元化しておく。

\begin{eqnarray}
\bar{t} &\equiv& \frac{t}{\tau} \\
\bar{x} &\equiv& \frac{x}{\ell} = \cos\theta\cdot \bar{t} \\
\bar{y}&\equiv& \frac{y}{\ell} = \sin\theta\cdot \bar{t} – \frac{1}{2} \bar{t}^2 \\
\bar{v}_x &\equiv& \frac{d\bar{x}}{d\bar{t}} = \cos\theta \\
\bar{v}_y &\equiv& \frac{d\bar{y}}{d\bar{t}} = \sin\theta – \bar{t}
\end{eqnarray}

以後しばらくは,無次元化された量であることを忘れないことにして,簡単のために $\bar{\ }$ を省略する。

\begin{eqnarray}
{x} &=& \cos\theta\cdot {t} \tag{1}\\
{y} &=&  \sin\theta\cdot {t} – \frac{1}{2} {t}^2 \tag{2}\\
{v}_x &=& \cos\theta \tag{3}\\
{v}_y &=& \sin\theta – {t}  \tag{4}
\end{eqnarray}

In [4]:
# NumPy の関数を使う。
# xp, yp の "p" の意味は,point とか projectile とか
def xp(t, theta):
    return np.cos(theta) * t
def yp(t, theta):
    return np.sin(theta) * t - t**2/2
def vx(t, theta):
    return np.cos(theta)
def vy(t, theta):
    return np.sin(theta) - t

滞空時間と到達距離

$t = 0$ で投射して,ふたたび地面 $y = 0$ に落ちるまでの滞空時間 $T$ は $(2)$ 式から

$$ 0 = \sin\theta \cdot t – \frac{1}{2} t^2 $$

より,
$$T = 2 \sin \theta $$

ちなみに,この時間での水平到達距離 $L$ は $(1)$ 式から

\begin{eqnarray}
L &=& \cos\theta \cdot T \\
&=& 2 \sin\theta \cos\theta = \sin 2\theta
\end{eqnarray}

In [5]:
def T(theta):
    return 2*np.sin(theta)
def L(theta):
    return np.sin(2*theta)

軌道全体のグラフを描く

$\theta = 45^{\circ}$ の場合に,まずは軌道全体をグラフにしてみます。

In [6]:
# 45°をラジアンになおす。
th0 = np.radians(45)

# グラフの縦横のアスペクト比を equal に。
plt.axes().set_aspect('equal')

# 横軸縦軸の表示範囲
plt.xlim(-0.1, 1.1)
plt.ylim(-0.1, 0.5)

# グリッドを表示
plt.grid()

# t の範囲を0.01ごとに分割。
t = np.arange(0, T(th0), 0.01)

plt.plot(xp(t, th0), yp(t, th0), color="blue");

途中までのグラフ(白抜点つき)

In [7]:
# 45°をラジアンになおす。
th0 = np.radians(45)

# グラフの縦横のアスペクト比を equal に。
plt.axes().set_aspect('equal')

# 横軸縦軸の表示範囲
plt.xlim(-0.1, 1.1)
plt.ylim(-0.1, 0.5)

# グリッドを表示
plt.grid()

# 軌道の途中までを描く例。
# tend までの範囲を0.01ごとに。
frames = 5
i = 1
tend = T(th0)*i/frames
t = np.arange(0, tend, 0.01)

# tend まで曲線を描く
plt.plot(xp(t, th0), yp(t, th0), color="blue")

# tend のところに○を描く
plt.plot(xp(tend, th0), yp(tend, th0), 
         marker='o',
         markeredgecolor='blue',
         markerfacecolor='none');

FuncAnimation() でアニメーション作成

$t$ の範囲 $[0, T(\theta_0)]$ を frames 個に分割し,パラパラアニメを作成する例。

In [8]:
# 小綺麗な動画にするために解像度を設定
fig = plt.figure(dpi=288)

# グラフの縦横のアスペクト比を equal に。
plt.axes().set_aspect('equal')

# 45°をラジアンになおす。
th0 = np.radians(45)

# 途中 T(th0)*i/frames までの曲線と白抜きの円を描くように
# func を定義
def func(i):
    # 前の frame を消す
    plt.cla()
    # 横軸縦軸の表示範囲
    plt.xlim(-0.1, 1.1)
    plt.ylim(-0.1, 0.5)
    # グリッドを表示
    plt.grid()
    
    # tend を少しずつ変化させてパラパラアニメに。
    tend = T(th0)*i/frames
    t = np.arange(0, tend, 0.01)
    # tend まで曲線を描く
    plt.plot(xp(t, th0), yp(t, th0), color="blue")
    # tend のところに○を描く
    plt.plot(xp(tend, th0), yp(tend, th0), 
             marker='o',
             markeredgecolor='blue',
             markerfacecolor='none')

# 変数名 frames は固定。
# 軌道全体を frames 個に分割してパラパラアニメに。
# frames 数を増やし,interval を短くすると滑らかに。
frames = 5
ani = FuncAnimation(fig, func,  
        # interval は frame 間の時間をミリ秒単位で。
        interval = 1000, 
        # 端点も含めて frames+1 個のコマ数にしてみた。
        frames = range(frames+1))

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
ani.save("anim01.mp4")

# jupyterhub のホームの mp4 ファイルをクリックして確認。

速度ベクトルを描く

In [9]:
# 45°をラジアンになおす。
th0 = np.radians(45)

# グラフの縦横のアスペクト比を equal に。
plt.axes().set_aspect('equal')

# 横軸縦軸の表示範囲
plt.xlim(-0.1, 1.1)
plt.ylim(-0.1, 0.5)

# グリッドを表示
plt.grid()

# tend での速度ベクトルを描く。
frames = 10
i = 2
tend = T(th0)*i/frames

# vx
plt.quiver(xp(tend,th0), yp(tend,th0), 
           vx(tend,th0), 0, label="速度の x 成分",
           color='blue', 
           # 以下の3点セットを書かないと自動スケーリングされる
           angles='xy', scale_units='xy', scale=7)
# vy
plt.quiver(xp(tend,th0), yp(tend,th0), 
           0, vy(tend,th0), label="速度の y 成分",
           color='red', 
           # 以下の3点セットを書かないと自動スケーリングされる
           angles='xy', scale_units='xy', scale=7)
# v
plt.quiver(xp(tend,th0), yp(tend,th0), 
           vx(tend,th0), vy(tend,th0), label="速度ベクトル",
           color='black', 
           # 以下の3点セットを書かないと自動スケーリングされる
           angles='xy', scale_units='xy', scale=7)

# tend まで曲線を描く
t = np.arange(0, tend, 0.01)
plt.plot(xp(t, th0), yp(t, th0), color="violet")

# tend のところに○を描く
plt.plot(xp(tend, th0), yp(tend, th0), 
             marker='o',
             markeredgecolor='violet',
             markerfacecolor='none');

FuncAnimation() でアニメーション作成
In [10]:
# 小綺麗な動画にするために解像度を設定
fig = plt.figure(dpi=288)

# グラフの縦横のアスペクト比を equal に。
plt.axes().set_aspect('equal')

# 途中 T(th0)*i/frames までの曲線と○を描くように
# func を定義
def func(i):
    # 前の frame を消す
    plt.cla()
    # 横軸縦軸の表示範囲
    plt.xlim(-0.1, 1.1)
    plt.ylim(-0.1, 0.5)
    # グリッドを表示
    plt.grid()

    # tend を少しずつ変化させてパラパラアニメに。
    tend = T(th0)*i/frames
    # vx
    plt.quiver(xp(tend,th0), yp(tend,th0), 
               vx(tend,th0), 0, label="速度の x 成分",
               color='blue', 
               # 以下の3点セットを書かないと自動スケーリングされる
               angles='xy', scale_units='xy', scale=7)
    # vy
    plt.quiver(xp(tend,th0), yp(tend,th0), 
               0, vy(tend,th0), label="速度の y 成分",
               color='red', 
               # 以下の3点セットを書かないと自動スケーリングされる
               angles='xy', scale_units='xy', scale=7)
    # v
    plt.quiver(xp(tend,th0), yp(tend,th0), 
               vx(tend,th0), vy(tend,th0), label="速度ベクトル",
               color='black', 
               # 以下の3点セットを書かないと自動スケーリングされる
               angles='xy', scale_units='xy', scale=7)
    # tend まで曲線を描く
    t = np.arange(0, tend, 0.01)
    plt.plot(xp(t, th0), yp(t, th0), color="violet")
    # tend のところに○を描く
    plt.plot(xp(tend, th0), yp(tend, th0), 
                 marker='o',
                 markeredgecolor='violet',
                 markerfacecolor='none');

# 変数名 frames は固定。
# 軌道全体を frames 個に分割してパラパラアニメに。
# frames 数を増やし,interval を短くすると滑らかに。
frames = 10
ani = FuncAnimation(fig, func,  
        # interval は frame 間の時間をミリ秒単位で。
        interval = 1000, 
        # 端点も含めて frames+1 個のコマ数にしてみた。
        frames = range(frames+1))

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
ani.save("anim02.mp4")

# jupyterhub のホームの mp4 ファイルをクリックして確認。

参考:ffmpeg で動画を連結する

上記で作成した2つの動画ファイル anim01.mp4anim02.mp4 は解像度(figsizedpi)もフレームレート(interval)も同じなので,ffmpeg で簡単に連結できます。

In [11]:
# まずは連結したい動画のリストを作る
dat = ["file anim01.mp4", "file anim02.mp4"]
np.savetxt('input.txt', dat, fmt='%s') # 文字列として書き込む
In [12]:
# 念のため,input.txt の内容を確認
!cat input.txt
file anim01.mp4
file anim02.mp4
In [13]:
# すでに(古い)output12.mp4 がある場合は削除
!rm -f outfile12.mp4
# input.txt の内容を読み込んで動画ファイルを連結し,
# outfile12.mp4 として作成
!ffmpeg -hide_banner -loglevel error -f concat -i input.txt -c copy outfile12.mp4

# jupyterhub のホームの outfile12.mp4 ファイルをクリックして確認。

円運動をパラパラアニメにする

円運動の媒介変数表示

半径 $1$,周期 $T$(したがって角振動数が $\omega \equiv \frac{2\pi}{T}$)の円運動の $x$ 座標,$y$ 座標は

\begin{eqnarray}
x &=& \cos \frac{2\pi}{T} t = \cos \omega t \\
y &=& \sin \frac{2\pi}{T} t = \sin \omega t
\end{eqnarray}

周期 $T$ を $N$ 等分し,
\begin{eqnarray}
\Delta t &\equiv& \frac{T}{N} \\
t_i &=& \Delta t \times i, \quad (i = 0, 1, \dots, N)
\end{eqnarray}

つまり
$$\omega t_i = \frac{2\pi}{N} \times i, \quad (i = 0, 1, \dots, N)$$
として,等しい時間間隔 $\displaystyle \Delta t$ ごとの位置 $x(\omega t_i), \ y(\omega t_i)$ をグラフにする。

In [14]:
# 分割数 frames はグローバル変数

def omegat(i):
    global frames
    return 2*np.pi/frames * i
    
# 円周上の i 番目の x 座標
def enx(omegaT):
    return np.cos(omegaT)

# 円周上の i 番目の y 座標
def eny(omegaT):
    return np.sin(omegaT)

まずは途中までの軌道を描く

In [15]:
# 分割数
frames = 36
# 
fig = plt.figure(figsize=[5,5])
ax = fig.add_subplot()

# 外枠と目盛を非表示に
ax.axis('off')

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

# 横軸縦軸の表示範囲
plt.xlim(-1.2, 1.2)
plt.ylim(-1.2, 1.2)

i = 6
# i 番目までの軌道
omt = np.arange(0, omegat(i), 0.02)
plt.plot(enx(omt), eny(omt))

# i 番目の位置に赤丸
plt.plot(enx(omegat(i)), eny(omegat(i)), "or")

# x軸 y軸
plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6);

FuncAnimation() でアニメーション作成
In [16]:
# 
fig = plt.figure(figsize=[5,5], dpi=288)
ax = fig.add_subplot()

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

def func(i):
    # 前の frame を消す
    plt.cla()

    # 外枠と目盛を非表示に
    ax.axis('off')

    # 横軸縦軸の表示範囲
    plt.xlim(-1.2, 1.2)
    plt.ylim(-1.2, 1.2)

    # i 番目までの軌道
    omt = np.arange(0, omegat(i), 0.02)
    plt.plot(enx(omt), eny(omt))

    # i 番目の位置に赤丸
    plt.plot(enx(omegat(i)), eny(omegat(i)), "or")

    # x軸 y軸
    plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
    plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6);

# 変数名 frames は固定。
# 軌道全体を frames 個に分割してパラパラアニメに。
# frames 数を増やし,interval を短くすると滑らかに。
frames = 36

ani = FuncAnimation(fig, func,  
        # interval は frame 間の時間をミリ秒単位で。
        interval = 200, 
        # 最後の端点も含めて frames+1 個のコマ数にしてみた。
        frames = range(frames+1))

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
ani.save("anim03.mp4")

# jupyterhub のホームの mp4 ファイルをクリックして確認。

円軌道全体に一定時間ごとの位置を描く

In [17]:
# 
fig = plt.figure(figsize=[5,5])
ax = fig.add_subplot()

# 外枠と目盛を非表示に
ax.axis('off')

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

# 横軸縦軸の表示範囲
plt.xlim(-1.2, 1.2)
plt.ylim(-1.2, 1.2)

# 円軌道は全体
omt = np.arange(0, 2*np.pi, 0.02)
plt.plot(enx(omt), eny(omt))

# 分割数
frames = 36
i = 6
# 一定時間ごとの位置をi 番目まで描く
omt = np.linspace(0, omegat(i), i + 1)
plt.plot(enx(omt), eny(omt), "or")

# x軸 y軸
plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6);

FuncAnimation() でアニメーション作成
In [18]:
# 
fig = plt.figure(figsize=[5,5], dpi=288)
ax = fig.add_subplot()

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

def func(i):
    # 前の frame を消す
    plt.cla()

    # 外枠と目盛を非表示に
    ax.axis('off')

    # 横軸縦軸の表示範囲
    plt.xlim(-1.2, 1.2)
    plt.ylim(-1.2, 1.2)

    # 円軌道は全体
    omt = np.arange(0, 2*np.pi, 0.02)
    plt.plot(enx(omt), eny(omt))

    # 一定時間ごとの位置を i 番目まで描く
    omt = np.linspace(0, omegat(i), i + 1)
    plt.plot(enx(omt), eny(omt), "or")

    # x軸 y軸
    plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
    plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6)

# 変数名 frames は固定。
# 軌道全体を frames 個に分割してパラパラアニメに。
# frames 数を増やし,interval を短くすると滑らかに。
frames = 36

ani = FuncAnimation(fig, func,  
        # interval は frame 間の時間をミリ秒単位で。
        interval = 200, 
        # 最後の端点も含めず frames 個のコマ数にしてみた。
        frames = range(frames))

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
ani.save("anim04.mp4")

# jupyterhub のホームの mp4 ファイルをクリックして確認。

Δt の間に掃く扇形を描く

In [19]:
# 
fig = plt.figure(figsize=[5,5])
ax = fig.add_subplot()

# 外枠と目盛を非表示に
ax.axis('off')

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

# 横軸縦軸の表示範囲
plt.xlim(-1.2, 1.2)
plt.ylim(-1.2, 1.2)

# 円軌道は全体
omt = np.arange(0, 2*np.pi, 0.02)
plt.plot(enx(omt), eny(omt), 'tab:blue')

# 分割数
frames = 36
i = 6
# 原点と i 番目を結ぶ直線
plt.plot([0, enx(omegat(i))], [0, eny(omegat(i))], 'tab:blue')
# 原点と i+1 番目を結ぶ直線
plt.plot([0, enx(omegat(i+1))], [0, eny(omegat(i+1))], 'tab:blue')

# 一定時間ごとの位置を全 frames 個
omt = np.linspace(0, omegat(frames), frames+1)
plt.plot(enx(omt), eny(omt), "or")

# x軸 y軸
plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6);

FuncAnimation() でアニメーション作成
In [20]:
# 
fig = plt.figure(figsize=[5,5], dpi=288)
ax = fig.add_subplot()

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

def func(i):
    # 前の frame を消す
    plt.cla()

    # 外枠と目盛を非表示に
    ax.axis('off')

    # 横軸縦軸の表示範囲
    plt.xlim(-1.2, 1.2)
    plt.ylim(-1.2, 1.2)

    # 円軌道は全体
    omt = np.arange(0, 2*np.pi, 0.02)
    plt.plot(enx(omt), eny(omt), 'tab:blue')

    # 原点と i 番目を結ぶ直線
    plt.plot([0, enx(omegat(i))], [0, eny(omegat(i))], 'tab:blue')
    # 原点と i+1 番目を結ぶ直線
    plt.plot([0, enx(omegat(i+1))], [0, eny(omegat(i+1))], 'tab:blue')

    # 一定時間ごとの位置を全 frames 個
    omt = np.linspace(0, omegat(frames), frames+1)
    plt.plot(enx(omt), eny(omt), "or")

    # x軸 y軸
    plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
    plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6)
    
# 変数名 frames は固定。
# 軌道全体を frames 個に分割してパラパラアニメに。
# frames 数を増やし,interval を短くすると滑らかに。
frames = 36

ani = FuncAnimation(fig, func,  
        # interval は frame 間の時間をミリ秒単位で。
        interval = 200, 
        # 最後の端点も含めず frames 個のコマ数にしてみた。
        frames = range(frames))

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
ani.save("anim05.mp4")

# jupyterhub のホームの mp4 ファイルをクリックして確認。

Δt の間に掃く扇形を塗りつぶす

In [21]:
# 
fig = plt.figure(figsize=[5,5])
ax = fig.add_subplot()

# 外枠と目盛を非表示に
ax.axis('off')

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

# 横軸縦軸の表示範囲
plt.xlim(-1.2, 1.2)
plt.ylim(-1.2, 1.2)

# 円軌道は全体
omt = np.arange(0, 2*np.pi, 0.02)
plt.plot(enx(omt), eny(omt), 'tab:blue')

# 分割数
frames = 36
i = 6
# i ~ i+1 の扇形を塗りつぶす
omt = np.linspace(omegat(i), omegat(i+1), 50)
# list にしてから結合
Xougi = [0] + enx(omt).tolist() 
Yougi = [0] + eny(omt).tolist() 
# 扇形の内部を塗りつぶす
plt.fill(Xougi, Yougi, fc = "yellow")

# 原点と i 番目を結ぶ直線
plt.plot([0, enx(omegat(i))], [0, eny(omegat(i))], 'tab:blue')
# 原点と i+1 番目を結ぶ直線
plt.plot([0, enx(omegat(i+1))], [0, eny(omegat(i+1))], 'tab:blue')

# 一定時間ごとの位置を全 frames 個
omt = np.linspace(0, omegat(frames), frames+1)
plt.plot(enx(omt), eny(omt), "or")

# x軸 y軸
plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6);

FuncAnimation() でアニメーション作成
In [22]:
# 
fig = plt.figure(figsize=[5,5], dpi=288)
ax = fig.add_subplot()

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')

def func(i):
    # 前の frame を消す
    plt.cla()

    # 外枠と目盛を非表示に
    ax.axis('off')

    # グラフの縦横のアスペクト比を equal に
    ax.set_aspect('equal')

    # 横軸縦軸の表示範囲
    plt.xlim(-1.2, 1.2)
    plt.ylim(-1.2, 1.2)

    # 円軌道は全体
    omt = np.arange(0, 2*np.pi, 0.02)
    plt.plot(enx(omt), eny(omt), 'tab:blue')

    # i ~ i+1 の扇形を塗りつぶす
    omt = np.linspace(omegat(i), omegat(i+1), 50)
    # list にしてから結合
    Xougi = [0] + enx(omt).tolist() + [0]
    Yougi = [0] + eny(omt).tolist() + [0] 
    # 扇形の内部を塗りつぶす
    plt.fill(Xougi, Yougi, fc = "yellow")

    # 原点と i 番目を結ぶ直線
    plt.plot([0, enx(omegat(i))], [0, eny(omegat(i))], 'tab:blue')
    # 原点と i+1 番目を結ぶ直線
    plt.plot([0, enx(omegat(i+1))], [0, eny(omegat(i+1))], 'tab:blue')

    # 一定時間ごとの位置を全 frames 個
    omt = np.linspace(0, omegat(frames), frames+1)
    plt.plot(enx(omt), eny(omt), "or")

    # x軸 y軸
    plt.axhline(0, color='black', dashes=(3, 3), linewidth=0.6)
    plt.axvline(0, color='black', dashes=(3, 3), linewidth=0.6)
    
# 変数名 frames は固定。
# 軌道全体を frames 個に分割してパラパラアニメに。
# frames 数を増やし,interval を短くすると滑らかに。
frames = 36

ani = FuncAnimation(fig, func,  
        # interval は frame 間の時間をミリ秒単位で。
        interval = 200, 
        # 最後の端点も含めず frames 個のコマ数にしてみた。
        frames = range(frames))

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
ani.save("anim06.mp4")

# jupyterhub のホームの mp4 ファイルをクリックして確認。

参考:ffmpeg で動画を連結する

上記で作成した2つの動画ファイル anim03.mp4anim04.mp4anim06.mp4 は解像度(figsizedpi)もフレームレート(interval)も同じなので,ffmpeg で簡単に連結できます。

In [23]:
# まずは連結したい動画のリストを作る
dat = ["file anim03.mp4", 
       "file anim04.mp4", 
       "file anim06.mp4"]
np.savetxt('input.txt', dat, fmt='%s') # 文字列として書き込む
In [24]:
# 念のため,input.txt の内容を確認
!cat input.txt
file anim03.mp4
file anim04.mp4
file anim06.mp4
In [25]:
# すでに(古い)output345.mp4 がある場合は削除
!rm -f outfile346.mp4
# input.txt の内容を読み込んで動画ファイルを連結し,
# outfile346.mp4 として作成
!ffmpeg -hide_banner -loglevel error -f concat -i input.txt -c copy outfile346.mp4

# jupyterhub のホームの outfile346.mp4 ファイルをクリックして確認。

最終的には,楕円軌道の場合に以下のような動画を作ることを目標に。