Return to Python でグラフ作成

Matplotlib で作成したグラフをアニメーションに:ax 編

Matplotlib でグラフ作成:ax 編」で解説した方法,つまり ax.*** のみで作成したグラフを,matplotlib.animation.FuncAnimation() でパラパラアニメにする。

Matplotlib でグラフ作成:plt 編」で解説した方法,つまり plt.*** のみで作成したグラフを使ってアニメ化する方法は,以下のページにまとめています。

ライブラリの import と初期設定

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

In [1]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

from matplotlib.animation import FuncAnimation

# 以下はグラフを SVG で Notebook にインライン表示させる設定
%config InlineBackend.figure_formats = ['svg']

# mathtext font の設定
plt.rcParams['mathtext.fontset'] = 'cm'

# デフォルトの figsize の設定変更は,
# 一度何か描いてからにするとよいようだ。
plt.rcParams["figure.figsize"] = [6.4, 4.8]

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

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

基本形:

# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

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

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

運動方程式

水平方向を $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 [2]:
# 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_f$ は $(2)$ 式から

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

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

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

\begin{eqnarray}
L_f &=& \cos\theta \cdot T_f \\
&=& 2 \sin\theta \cos\theta = \sin 2\theta
\end{eqnarray}

In [3]:
# f は flight or fall の f
def Tf(theta):
    return 2*np.sin(theta)
def Lf(theta):
    return np.sin(2*theta)

軌道全体のグラフを描く

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

In [4]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# アスペクト比を equal に。
ax.set_aspect('equal')
# 表示範囲
ax.axis([-0.1, 1.1, -0.1, 0.5])
# 横軸目盛
ax.xaxis.set_major_locator(MultipleLocator(0.1))
# 縦軸目盛
ax.yaxis.set_major_locator(MultipleLocator(0.1))
# グリッド
ax.grid(c='lightgray', ls='--', lw=0.5, which='both')

# 45°をラジアンになおす。
thdeg = 45
th = np.radians(thdeg)
# t の範囲
t = np.linspace(0, Tf(th))

ax.plot(xp(t, th), yp(t, th), label='θ = %2d°' % thdeg)
ax.legend();
In [5]:
plt.rcParams["figure.figsize"]
Out[5]:
[6.0, 4.0]

確かに,最初に plt.rcParams["figure.figsize"] = [6.4, 4.8] としたのに,グラフを描いた後に確認すると [6.0, 4.0] になっている。

デフォルトの figsize の設定変更は,一度何か描いてからにするとよいようだ。

In [6]:
# デフォルトの figsize の設定変更は,
# 一度何か描いてからにするとよいようだ。
plt.rcParams["figure.figsize"] = [6.4, 4.8]

念のため,もう一度同じグラフを描いた後,figsize を確認してみます。

In [7]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# アスペクト比を equal に。
ax.set_aspect('equal')
# 表示範囲
ax.axis([-0.1, 1.1, -0.1, 0.5])
# 横軸目盛
ax.xaxis.set_major_locator(MultipleLocator(0.1))
# 縦軸目盛
ax.yaxis.set_major_locator(MultipleLocator(0.1))
# グリッド
ax.grid(c='lightgray', ls='--', lw=0.5, which='both')

# 45°をラジアンになおす。
thdeg = 45
th = np.radians(thdeg)
# t の範囲
t = np.linspace(0, Tf(th))

ax.plot(xp(t, th), yp(t, th), label='θ = %2d°' % thdeg)
ax.legend();
In [8]:
plt.rcParams["figure.figsize"]
Out[8]:
[6.4, 4.8]

○練習:$\theta$ を変えて斜方投射

打ち出し角度 $\theta$ を変えて,いくつか軌道を描け。 例えば,$\theta = 20^{\circ}, 45^{\circ}, 85^{\circ}$ の場合。

せっかく Python の for 文で繰り返しを覚えたので,

thdegs = [20, 45, 85]
for thdeg in thdegs:
...

などのようにして描いてみること。

In [9]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 表示範囲
ax.axis([-0.1, 1.1, -0.1, 0.7])
# 横軸目盛
ax.xaxis.set_major_locator(MultipleLocator(0.1))
# 縦軸目盛
ax.yaxis.set_major_locator(MultipleLocator(0.1))
# グリッド
ax.grid(c='lightgray', ls='--', lw=0.5, which='both')
# x軸 y軸
ax.axhline(0, c='k', ls='--', lw=0.6)
ax.axvline(0, c='k', ls='--', lw=0.6)

thdegs = [20, 45, 85]
for thdeg in thdegs:
    th = np.radians(thdeg)
    # t の範囲
    t = np.linspace(0, Tf(th))
    ax.plot(xp(t, th), yp(t, th), label = 'θ = %2d°'% thdeg)

ax.legend();

○練習:$\theta$ を変えた軌道の同時刻での位置

打ち出し角度 $\theta$ を変えると滞空時間 $T_f(\theta)$ も変わるし,それぞれの軌道上での位置も異なる。

一定時間間隔 $dt$ ごとの,打ち出し角度 $\theta$ を変えた軌道上での位置を示すグラフを描け。

例えば,$dt$ を $\theta = 45^{\circ}$ の際の滞空時間 $T_f(45^{\circ})$ の 1/frames としてみる。

$$dt = \frac{T_f(45^{\circ})}{\tt frames}$$

In [10]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 表示範囲
ax.axis([-0.1, 1.1, -0.1, 0.7])
# 横軸目盛
ax.xaxis.set_major_locator(MultipleLocator(0.1))
# 縦軸目盛
ax.yaxis.set_major_locator(MultipleLocator(0.1))
# グリッド
ax.grid(c='lightgray', ls='--', lw=0.5, which='both')
# x軸 y軸
ax.axhline(0, c='k', ls='--', lw=0.6)
ax.axvline(0, c='k', ls='--', lw=0.6)

frames = 10
dt = Tf(np.radians(45))/frames
thdegs = [20, 45, 85]
ths = np.radians(thdegs)
for i in range(0, frames+1):
    X = xp(dt*i, ths)
    Y = yp(dt*i, ths)
    ax.scatter(X,Y, label='', zorder=2) # グリッドの上に描く

上のグラフから,$\theta$ が大きい場合は滞空時間が長く,$\theta$ が小さい場合は滞空時間が短いことがわかります。

しかし上のグラフをよく見ると,$\theta = 20^{\circ}$ の場合は地面にめり込んでもなお,運動しているように見えます。

地面にめり込まないように,$y$ 座標が負の場合の点はグラフに描かないようにし,以下のようなグラフを作成してください。

In [11]:


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

$t$ の範囲 $[0, T_f(\theta)]$ を frames 個に分割し,i 番目の時刻
$$ t_i = T_f \times \frac{\tt i}{\tt frames}$$ までの軌道と,$t = t_i$ での位置を丸で表す。

In [12]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# 軌道の途中までを描く例。
# 全体を 10 分割し,2 番目までの軌道を描く
# frames: 分割数,コマ数
frames = 10
# i: 何番目のコマかを指定
i = 2

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 表示範囲
ax.axis([-0.1, 1.1, -0.1, 0.5])
# 横軸目盛
ax.xaxis.set_major_locator(MultipleLocator(0.1))
# 縦軸目盛
ax.yaxis.set_major_locator(MultipleLocator(0.1))
# グリッド
ax.grid(c='lightgray', ls='--', lw=0.5, which='both')

# θ = 45° の場合
thdeg = 45
th = np.radians(thdeg)
# i 番目の時刻
ti = Tf(th)*i/frames

# tの範囲
t = np.linspace(0, ti)

# ti まで曲線を描く
ax.plot(xp(t, th), yp(t, th), color="blue")

# ti のところに○を描く
ax.plot(xp(ti, th), yp(ti, th), 
        marker='o',
        markeredgecolor='blue',
        markerfacecolor='none');
FuncAnimation() でアニメーション作成

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

上の i0 から frames の値まで増やしていけば,それぞれの時刻までの軌道のグラフが出来上がるから,それをパラパラアニメにすればよい。

In [13]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# i で決まる ti までの曲線と白抜きの円を描くように
# func を定義
def func(i):
    # 前の frame を消す
    ax.cla()

    # グラフの縦横のアスペクト比を equal に。
    ax.set_aspect('equal')
    # 表示範囲
    ax.axis([-0.1, 1.1, -0.1, 0.5])
    # 横軸目盛
    ax.xaxis.set_major_locator(MultipleLocator(0.1))
    # 縦軸目盛
    ax.yaxis.set_major_locator(MultipleLocator(0.1))
    # グリッド
    ax.grid(c='lightgray', ls='--', lw=0.5, which='both')

    thdeg = 45
    th = np.radians(thdeg)
    # i 番目の時刻
    ti = Tf(th)*i/frames

    # tの範囲
    t = np.linspace(0, ti)

    # ti まで曲線を描く
    ax.plot(xp(t, th), yp(t, th), color="blue")

    # ti のところに○を描く
    ax.plot(xp(ti, th), yp(ti, th), 
            marker='o',
            markeredgecolor='blue',
            markerfacecolor='none')

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

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
# 小綺麗な動画にするために解像度 dpi を設定
ani.save("anim01.mp4", dpi=288)

# 念のため,gif ファイルとしても保存。
ani.save("anim01.gif", dpi=144)

速度ベクトルを描く

$t$ の範囲 $[0, T_f(\theta)]$ を frames 個に分割し,i 番目の時刻
$\displaystyle t_i = T_f \times \frac{\tt i}{\tt frames}$ までの軌道と,$t = t_i$ の位置を丸で表し,その時刻における速度ベクトル(全体と,$x$ 成分,$y$ 成分)を描く。

In [14]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 表示範囲
ax.axis([-0.1, 1.1, -0.1, 0.5])
# 横軸目盛
ax.xaxis.set_major_locator(MultipleLocator(0.1))
# 縦軸目盛
ax.yaxis.set_major_locator(MultipleLocator(0.1))
# グリッド
ax.grid(c='lightgray', ls='--', lw=0.5, which='both')

# 軌道の途中までを描く例。
# 全体を 10 分割し,2 番目までの軌道を描く
# frames: 分割数,コマ数
frames = 10
# i: 何番目のコマかを指定
i = 2

# θ = 45° の場合
thdeg = 45
th = np.radians(thdeg)
# i 番目の時刻
ti = Tf(th)*i/frames

# tの範囲
t = np.linspace(0, ti)

# ti まで曲線を描く
ax.plot(xp(t, th), yp(t, th), color="blue")

# ti のところに○を描く
ax.plot(xp(ti, th), yp(ti, th), 
        marker='o',
        markeredgecolor='blue',
        markerfacecolor='none')

# 以下を新たに追加
# vx
ax.quiver(xp(ti, th), yp(ti, th), 
          vx(ti, th), 0,          
          label="速度の x 成分",
          color='green', 
          # 以下の3点セットを書かないと自動スケーリングされる
          angles='xy', scale_units='xy', scale=7)
# vy
ax.quiver(xp(ti, th), yp(ti, th), 
          0,          vy(ti, th), 
          label="速度の y 成分",
          color='red', 
          # 以下の3点セットを書かないと自動スケーリングされる
          angles='xy', scale_units='xy', scale=7)
# v
ax.quiver(xp(ti, th), yp(ti, th), 
          vx(ti, th), vy(ti, th), 
          label="速度ベクトル",
          color='blue', 
          # 以下の3点セットを書かないと自動スケーリングされる
          angles='xy', scale_units='xy', scale=7)
ax.legend();
FuncAnimation() でアニメーション作成
In [15]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots()

# i で決まる tend までのベクトルと曲線と○を描くように
# func を定義
def func(i):
    # 前の frame を消す
    ax.cla()

    # グラフの縦横のアスペクト比を equal に。
    ax.set_aspect('equal')
    # 表示範囲
    ax.axis([-0.1, 1.1, -0.1, 0.5])
    # 横軸目盛
    ax.xaxis.set_major_locator(MultipleLocator(0.1))
    # 縦軸目盛
    ax.yaxis.set_major_locator(MultipleLocator(0.1))
    # グリッド
    ax.grid(c='lightgray', ls='--', lw=0.5, which='both')

    thdeg = 45
    th = np.radians(thdeg)
    # i 番目の時刻
    ti = Tf(th)*i/frames

    # tの範囲
    t = np.linspace(0, ti)

    # ti まで曲線を描く
    ax.plot(xp(t, th), yp(t, th), color="blue")

    # ti のところに○を描く
    ax.plot(xp(ti, th), yp(ti, th), 
            marker='o',
            markeredgecolor='blue',
            markerfacecolor='none')

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

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

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
# 小綺麗な動画にするために解像度 dpi を設定
ani.save("anim02.mp4", dpi=288)

# 念のため,gif ファイルとしても保存。
ani.save("anim02.gif", dpi=144)

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

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

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

!rm input.txt
# jupyterhub のホームの outfile12.mp4 ファイルをクリックして確認。
In [19]:
# 念のため gif に変換
!rm -f outfile12.gif
!ffmpeg -hide_banner -loglevel error -i outfile12.mp4 -vf scale=720:-1 outfile12.gif

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

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

円運動の媒介変数表示

半径 $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 [20]:
# 分割数 frames はグローバル変数

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

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

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

In [21]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

# 分割数
frames = 36
# 
i = 6

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 外枠と目盛を非表示に
ax.axis('off')
# 表示範囲
ax.axis([-1.2, 1.2, -1.2, 1.2])

# i 番目までの軌道
omt = np.arange(0, omegat(i), 0.02)
ax.plot(xen(omt), yen(omt))

# i 番目の位置に赤丸
ax.plot(xen(omegat(i)), yen(omegat(i)), "or")

# x軸 y軸
ax.axhline(0, c='k', ls='--', lw=0.6)
ax.axvline(0, c='k', ls='--', lw=0.6);
FuncAnimation() でアニメーション作成
In [22]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

def func(i):
    # 前の frame を消す
    ax.cla()
    
    # グラフの縦横のアスペクト比を equal に。
    ax.set_aspect('equal')
    # 外枠と目盛を非表示に
    ax.axis('off')
    # 表示範囲
    ax.axis([-1.2, 1.2, -1.2, 1.2])
    
    # i 番目までの軌道
    omt = np.arange(0, omegat(i), 0.02)
    ax.plot(xen(omt), yen(omt))

    # i 番目の位置に赤丸
    ax.plot(xen(omegat(i)), yen(omegat(i)), "or")

    # x軸 y軸
    ax.axhline(0, c='k', ls='--', lw=0.6)
    ax.axvline(0, c='k', ls='--', lw=0.6)

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

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

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
# 小綺麗な動画にするために解像度 dpi を設定
ani.save("anim03.mp4", dpi=288)

# 念のため,gif ファイルとしても保存。
ani.save("anim03.gif", dpi=144)

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

In [23]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

# 分割数
frames = 36
i = 6

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 外枠と目盛を非表示に
ax.axis('off')
# 表示範囲
ax.axis([-1.2, 1.2, -1.2, 1.2])

# 円軌道は全体
omt = np.arange(0, 2*np.pi, 0.02)
ax.plot(xen(omt), yen(omt))

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

# x軸 y軸
ax.axhline(0, c='k', ls='--', lw=0.6)
ax.axvline(0, c='k', ls='--', lw=0.6);

FuncAnimation() でアニメーション作成
In [24]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

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

    # グラフの縦横のアスペクト比を equal に。
    ax.set_aspect('equal')
    # 外枠と目盛を非表示に
    ax.axis('off')
    # 表示範囲
    ax.axis([-1.2, 1.2, -1.2, 1.2])

    # 円軌道は全体
    omt = np.arange(0, 2*np.pi, 0.02)
    ax.plot(xen(omt), yen(omt))

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

    # x軸 y軸
    ax.axhline(0, c='k', ls='--', lw=0.6)
    ax.axvline(0, c='k', ls='--', lw=0.6)

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

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

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
# 小綺麗な動画にするために解像度 dpi を設定
ani.save("anim04.mp4", dpi=288)

# 念のため,gif ファイルとしても保存。
ani.save("anim04.gif", dpi=144)

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

In [25]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

# 分割数
frames = 36
i = 6

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 外枠と目盛を非表示に
ax.axis('off')
# 表示範囲
ax.axis([-1.2, 1.2, -1.2, 1.2])

# 円軌道は全体
omt = np.arange(0, 2*np.pi, 0.02)
ax.plot(xen(omt), yen(omt), 
        c = 'tab:blue')

# 原点と i 番目を結ぶ直線
ax.plot([0, xen(omegat(i))], [0, yen(omegat(i))], 
        c = 'tab:blue')
# 原点と i+1 番目を結ぶ直線
ax.plot([0, xen(omegat(i+1))], [0, yen(omegat(i+1))], 
        c = 'tab:blue')

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

# x軸 y軸
ax.axhline(0, c='k', ls='--', lw=0.6)
ax.axvline(0, c='k', ls='--', lw=0.6);

FuncAnimation() でアニメーション作成
In [26]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

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

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

    # グラフの縦横のアスペクト比を equal に。
    ax.set_aspect('equal')
    # 外枠と目盛を非表示に
    ax.axis('off')
    # 表示範囲
    ax.axis([-1.2, 1.2, -1.2, 1.2])

    # 円軌道は全体
    omt = np.arange(0, 2*np.pi, 0.02)
    ax.plot(xen(omt), yen(omt), 
            c = 'tab:blue')

    # 原点と i 番目を結ぶ直線
    ax.plot([0, xen(omegat(i))], [0, yen(omegat(i))], 
            c = 'tab:blue')
    # 原点と i+1 番目を結ぶ直線
    ax.plot([0, xen(omegat(i+1))], [0, yen(omegat(i+1))], 
            c = 'tab:blue')

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

    # x軸 y軸
    ax.axhline(0, c='k', ls='--', lw=0.6)
    ax.axvline(0, c='k', ls='--', lw=0.6);    

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

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

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
# 小綺麗な動画にするために解像度 dpi を設定
ani.save("anim05.mp4", dpi=288)

# 念のため,gif ファイルとしても保存。
ani.save("anim05.gif", dpi=144)

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

In [27]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

# 分割数
frames = 36
i = 6

# グラフの縦横のアスペクト比を equal に。
ax.set_aspect('equal')
# 外枠と目盛を非表示に
ax.axis('off')
# 表示範囲
ax.axis([-1.2, 1.2, -1.2, 1.2])

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

# i ~ i+1 の扇形を塗りつぶす
omt = np.linspace(omegat(i), omegat(i+1))
# list にしてから結合
Xougi = [0] + xen(omt).tolist() + [0]
Yougi = [0] + yen(omt).tolist() + [0]
# 扇形の内部を塗りつぶす
ax.fill(Xougi, Yougi, fc = "yellow", ec = 'tab:blue')

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

# x軸 y軸
ax.axhline(0, c='k', ls='--', lw=0.6)
ax.axvline(0, c='k', ls='--', lw=0.6);

FuncAnimation() でアニメーション作成
In [28]:
# ax を使う際の最初のおまじない
fig, ax = plt.subplots(figsize=[6,6])

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

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

    # グラフの縦横のアスペクト比を equal に。
    ax.set_aspect('equal')
    # 外枠と目盛を非表示に
    ax.axis('off')
    # 表示範囲
    ax.axis([-1.2, 1.2, -1.2, 1.2])

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

    # i ~ i+1 の扇形を塗りつぶす
    omt = np.linspace(omegat(i), omegat(i+1))
    # list にしてから結合
    Xougi = [0] + xen(omt).tolist() + [0]
    Yougi = [0] + yen(omt).tolist() + [0]
    # 扇形の内部を塗りつぶす
    ax.fill(Xougi, Yougi, fc = "yellow", ec = 'tab:blue')

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

    # x軸 y軸
    ax.axhline(0, c='k', ls='--', lw=0.6)
    ax.axvline(0, c='k', ls='--', lw=0.6)

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

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

# 動画を jupyterhub のホームに mp4 ファイルとして保存。
# 小綺麗な動画にするために解像度 dpi を設定
ani.save("anim06.mp4", dpi=288)

# 念のため,gif ファイルとしても保存。
ani.save("anim06.gif", dpi=144)

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

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

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

!rm input.txt
# jupyterhub のホームの outfile346.mp4 ファイルをクリックして確認。
In [32]:
# 念のため gif に変換
!rm -f outfile346.gif
!ffmpeg -hide_banner -loglevel error -i outfile346.mp4 -vf scale=720:-1 outfile346.gif

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

最終目標その1:ケプラー軌道

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

最終目標その2:近日点移動

さらには,一般相対論的効果によって近日点移動が起こることを,以下のような動画で示したいなぁ。

参考:作成した動画ファイルの消去

以上のコードを実行すると,いくつかの mp4 ファイルやアニメーション gif ファイルができますが,これを一括して消去するには,以下のようなコマンドを実行します。

rm -f *.mp4 *.gif

以下のセルで 2 行目の先頭の # を消去して実行します。

In [33]:
# 作成した mp4 ファイルや gif ファイルを消去するには
# !rm -f *.mp4 *.gif