Return to 単振り子のエネルギー保存則から周期を求める準備

SymPy で単振り子のエネルギー保存則から周期を求める

単振り子の運動方程式から得られるエネルギー保存則から,Python の SymPyを使って(かつ SciPy と NumPy は使わずに)数値積分によって周期を求める。導出については以下を参照:

ライブラリの import

In [1]:
from sympy.abc import *
from sympy import *

# SymPy Plotting Backends (SPB) でグラフを描く
from spb import *

# ax 用
import matplotlib.pyplot as plt

# グラフを SVG で Notebook にインライン表示
%config InlineBackend.figure_formats = ['svg']

SymPy の N() で数値積分する

振幅 $\theta_0$ の単振り子の周期 $T_p$ は

\begin{eqnarray}
T_p(\theta_0) &=&
\frac{2}{\pi} \int_{0}^{\theta_0} \frac{1}{\sqrt{2(\cos\theta-\cos\theta_0)}}d\theta \\
&=&
\frac{\sqrt{2}}{\pi} \int_{0}^{\theta_0} \frac{1}{\sqrt{\cos\theta-\cos\theta_0}}d\theta
\end{eqnarray}

これを Tp(th0) と定義する。

ためしに,$\theta_0 = 80^{\circ}$ として計算してみます。

In [2]:
# ラジアンに変換
theta0 = rad(80)

# Tp の計算
sqrt(2)/pi*integrate(1/sqrt(cos(theta)-cos(theta0)), (theta, 0, theta0))
Out[2]:
$\displaystyle \frac{\sqrt{2} \int\limits_{0}^{\frac{4 \pi}{9}} \frac{1}{\sqrt{\cos{\left(\theta \right)} – \cos{\left(\frac{4 \pi}{9} \right)}}}\, d\theta}{\pi}$

解析的に積分できない場合,SymPy は式をそのまま返します。

これを数値的に求めるには,以下のように N() で囲みます。

Jupyter Notebook では,以下のようにセルの1行目に %%time を入れると,計算にかかった時間を表示します。

In [3]:
%%time

theta0 = rad(80)

N(sqrt(2)/pi * 
  integrate(1/sqrt(cos(theta)-cos(theta0)), (theta, 0, theta0)))
# けっこう時間がかかる
CPU times: user 4.42 s, sys: 9.76 ms, total: 4.43 s
Wall time: 4.43 s
Out[3]:
$\displaystyle 1.13749255992392$

SymPy の N()Tp1(th0)

初めから N() で数値積分したを返す関数を Tp1(th0) として定義します。

In [4]:
def Tp1(th0):
    """度で与えられた振幅 th0 から
       規格化された単振り子の規格化された周期を求める"""
    # 度で与えられた th0 からラジアンへ変換
    theta0 = rad(th0)
    ans = N(sqrt(2)/pi * 
            integrate(1/sqrt(cos(theta)-cos(theta0)), (theta, 0, theta0)))
    return ans
In [5]:
%%time

Tp1(80)
CPU times: user 4.27 s, sys: 28.3 ms, total: 4.3 s
Wall time: 4.3 s
Out[5]:
$\displaystyle 1.13749255992392$

SymPy は解析的な微分や積分ができるので便利ですが,数値積分の場合だと妙に時間がかかります。

SymPy の積分そのものが遅くてつかいものにならないというわけではありません,念のため。積分範囲(端点)で被積分関数が発散するようなケースを N() で数値計算する場合,いろいろ考えて陰であの手この手を試しているためかと推察します。のちに示すように,この積分は適当な変数変換によって,積分範囲内で被積分関数が発散しない形にできます。その場合には,N() もそんなに時間がかからずに答えを出してくれますよ。

置換積分で被積分関数が発散しないようにする

このへんにまとめているように,変数変換によって
\begin{eqnarray}
\int_0^{\theta_0} \frac{1}{\sqrt{2(\cos\theta-\cos\theta_0)}} d\theta
&=&
\int_0^{\pi/2} \frac{dt}{\sqrt{1 – k^2 \sin^2 t}}, \\ \quad k &\equiv& \sin\frac{\theta_0}{2}
\end{eqnarray}

と書ける。この形にすると,積分範囲内で被積分関数が発散することもない。

SymPy の N()Tp2(th0)

被積分関数が発散しないように変数変換を行ったあとに,数値積分する関数を Tp2(th0) として定義する。

In [6]:
def Tp2(th0):
    theta0 = N(rad(th0))
    m = sin(theta0/2)**2
    ans = 2/N(pi) * N(integrate(1/sqrt(1-m*sin(t)**2), (t, 0, pi/2)))
    return ans
In [7]:
%%time

Tp2(80)
CPU times: user 932 ms, sys: 4.02 ms, total: 936 ms
Wall time: 935 ms
Out[7]:
$\displaystyle 1.13749255992392$

第1種完全楕円積分を使う

このへんにまとめているように,
第1種完全楕円積分 $K(k)$ を使うと,振幅 $\theta_0$ の単振り子の周期 $T_p$ は

\begin{eqnarray}
T_p(\theta_0) &=&
\frac{2}{\pi} \int_{0}^{\theta_0} \frac{1}{\sqrt{2(\cos\theta-\cos\theta_0)}}d\theta \\
&=& \frac{2}{\pi} K(k) \\
&=& \frac{2}{\pi}\int_0^{\pi/2} \frac{dt}{\sqrt{1-k^2 \sin^2 t}}, \quad k \equiv \sin\frac{\theta_0}{2}
\end{eqnarray}

SymPy の elliptic_k()Tp3(th0)

ありがたいことに,SymPy では第1種完全楕円積分 elliptic_k() が使える。

elliptic_k(m) $\displaystyle \equiv \int_0^{\pi/2} \frac{dt}{\sqrt{1-m \sin^2 t}}$

第1種完全楕円積分 elliptic_k() を使って周期を返す関数を Tp3(th0) として定義する。

In [8]:
def Tp3(th0):
    theta0 = N(rad(th0))
    m = sin(theta0/2)**2
    ans = 2/N(pi) * elliptic_k(m)
    return ans
In [9]:
%%time

Tp3(80)
CPU times: user 425 µs, sys: 48 µs, total: 473 µs
Wall time: 479 µs
Out[9]:
$\displaystyle 1.13749255992392$

Tp3(th0) のグラフ

In [10]:
# 振幅
th0s = [1] + [10*i for i in range(1,10)]
In [11]:
# 周期
Tp3s = []

# 各振幅ごとの周期
for th0 in th0s:
    Tp3s += [Tp3(th0)]
    print('θ_0 = %2d° のとき,' % th0, end = '')
    print('周期 T = %.15f' % Tp3s[-1])
θ_0 =  1° のとき,周期 T = 1.000019038921006
θ_0 = 10° のとき,周期 T = 1.001907188143217
θ_0 = 20° のとき,周期 T = 1.007669025791545
θ_0 = 30° のとき,周期 T = 1.017408797595956
θ_0 = 40° のとき,周期 T = 1.031340519130037
θ_0 = 50° のとき,周期 T = 1.049782960623032
θ_0 = 60° のとき,周期 T = 1.073182007149365
θ_0 = 70° のとき,周期 T = 1.102144909639270
θ_0 = 80° のとき,周期 T = 1.137492559923922
θ_0 = 90° のとき,周期 T = 1.180340599016096
In [12]:
# SymPy Plotting Backends でグラフ
# 細かな設定はバックエンドの matplotlib ax で

# おまじない。これで ax が使える。
fig, ax = plt.subplots()

# x の目盛を 10 刻みに
ax.set_xticks([10*i for i in range(1, 10)])
# グリッドを点線に
ax.grid(ls = 'dotted')

graphics(
         list_2d(th0s, Tp3s, scatter = True, 
                 rendering_kw={"ms":3}), 
         xlabel = "振幅 θ (°)", 
         ylabel = "規格化された周期 T", 
         title = "単振り子の振幅と周期", 
         xlim = (0, 91), 
         ylim = (0.98, 1.2), 
         grid = False, # 一旦 False にして...
         ax = ax       # ax の設定を反映
);

まとめ

SymPy の N(integrate()) で数値積分した場合と第一種完全楕円積分 elliptic_k(m) を使った場合の周期を求めた。N() を使った場合,かなり時間がかかることがわかった。

しかし,計算時間には大差があるものの,数値計算の結果は同じ値を返すことも以下のことからわかる。

In [13]:
th0s = [1] + [10*i for i in range(1,10)]
In [14]:
%%time

Tp1s = []
for th0 in th0s:
    Tp1s += [Tp1(th0)]
CPU times: user 40.3 s, sys: 64.8 ms, total: 40.4 s
Wall time: 40.4 s
In [15]:
%%time

Tp2s = []
for th0 in th0s:
    Tp2s += [Tp2(th0)]
CPU times: user 18.9 s, sys: 140 ms, total: 19 s
Wall time: 19 s
In [16]:
%%time

Tp3s = []
for th0 in th0s:
    Tp3s += [Tp3(th0)]
CPU times: user 2.62 ms, sys: 0 ns, total: 2.62 ms
Wall time: 2.63 ms
In [17]:
for i in range(len(th0s)):
    print('Tp1(%2d)=%.15f' % (th0s[i], Tp1s[i]))
    print('Tp2(%2d)=%.15f' % (th0s[i], Tp2s[i]))
    print('Tp3(%2d)=%.15f' % (th0s[i], Tp3s[i]))
    print('')
Tp1( 1)=1.000019038921006
Tp2( 1)=1.000019038921006
Tp3( 1)=1.000019038921006

Tp1(10)=1.001907188143217
Tp2(10)=1.001907188143217
Tp3(10)=1.001907188143217

Tp1(20)=1.007669025791545
Tp2(20)=1.007669025791545
Tp3(20)=1.007669025791545

Tp1(30)=1.017408797595956
Tp2(30)=1.017408797595956
Tp3(30)=1.017408797595956

Tp1(40)=1.031340519130037
Tp2(40)=1.031340519130037
Tp3(40)=1.031340519130037

Tp1(50)=1.049782960623032
Tp2(50)=1.049782960623032
Tp3(50)=1.049782960623032

Tp1(60)=1.073182007149364
Tp2(60)=1.073182007149365
Tp3(60)=1.073182007149365

Tp1(70)=1.102144909639270
Tp2(70)=1.102144909639270
Tp3(70)=1.102144909639270

Tp1(80)=1.137492559923922
Tp2(80)=1.137492559923922
Tp3(80)=1.137492559923922

Tp1(90)=1.180340599016096
Tp2(90)=1.180340599016096
Tp3(90)=1.180340599016096