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

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

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

数値積分 scipy.integrate.quad()

振幅 $\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}

これを Tp1(theta0) と定義する。積分部分は数値積分 scipy.integrate.quad() を使う。

Python で必要なパッケージを import します。

In [1]:
# NumPy も使います
import numpy as np

# Matplotlib でグラフを描きます
import matplotlib.pyplot as plt

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

平方根や三角関数などは NumPy の関数 np.sqrt()np.cos()np.sin() などを使います。scipy.integrate.quad() については授業で簡単な説明をしています。

In [2]:
# 数値積分 scipy.integrate.quad() を import
from scipy.integrate import quad
In [3]:
# 被積分関数の定義
def f(x, theta0):
    return 1/np.sqrt(np.cos(x)-np.cos(theta0))

$\theta = \theta_0$ で被積分関数の分母はゼロになってしまうので,何かエラーでも起こるのかと思いきや,scipy.integrate.quad() は大変優秀で何事もなかったかのように数値積分してしまいます!

ためしに $\theta_0 = 60^{\circ}$ の場合の積分値を数値積分 scipy.integrate.quad() で求めてみます。

In [4]:
# 振幅を°で 
th = 60
theta0 = np.radians(th)
quad(f, 0, theta0, args=theta0)
Out[4]:
(2.384011014479094, 1.478306366209381e-10)

quad() は (積分の近似, 見積もられた近似の絶対誤差) を返します。

In [5]:
# 周期 Tp1(theta0) の定義
def Tp1(th):
    """度で与えられた振幅 th から
       規格化された単振り子の周期を求める"""
    # 度で与えられた th からラジアンへ変換
    theta0 = np.radians(th)
    tmp = quad(f, 0, theta0, args=theta0)
    # 積分値のみを tmp[0] としてとる
    return np.sqrt(2)/np.pi * tmp[0] 
In [6]:
help(Tp1)
Help on function Tp1 in module __main__:

Tp1(th)
    度で与えられた振幅 th から
    規格化された単振り子の周期を求める

In [7]:
# 振幅と周期のグラフを描くために x, y に追加していく
x = []
y = []

# 参考として,ほぼ単振動となるであろう θ_0 = 1°の場合
th = 1
x.append(th)
y.append(Tp1(th))
# リストに append したばかりの最後の値を表示 
print('θ_0 = %2d° のとき  ' % x[-1], end = '')
print('周期 T = %10.8f' % y[-1])

# θ_0 を 10° から 90° まで 10° ごとにかえて計算
for th in range(10, 91, 10):
    x.append(th)
    y.append(Tp1(th))
    print('θ_0 = %2d° のとき  ' % x[-1], end = '')
    print('周期 T = %10.8f' % y[-1])
θ_0 =  1° のとき  周期 T = 1.00001904
θ_0 = 10° のとき  周期 T = 1.00190719
θ_0 = 20° のとき  周期 T = 1.00766903
θ_0 = 30° のとき  周期 T = 1.01740880
θ_0 = 40° のとき  周期 T = 1.03134052
θ_0 = 50° のとき  周期 T = 1.04978296
θ_0 = 60° のとき  周期 T = 1.07318201
θ_0 = 70° のとき  周期 T = 1.10214491
θ_0 = 80° のとき  周期 T = 1.13749256
θ_0 = 90° のとき  周期 T = 1.18034060
In [8]:
# 横軸に振幅 x, 縦軸に周期 y 
plt.scatter(x, y, s=10)
plt.grid(linestyle='dotted')
plt.xlabel("振幅 θ (°)")
plt.xlim(0, 91)
plt.xticks(list(range(10,91,10)))
plt.ylim(0.95, 1.2)
plt.ylabel("規格化された周期 T")
plt.title("単振り子の振幅と周期");

第1種完全楕円積分 elliptic_kc(m)

ありがたいことに,SciPy では第1種完全楕円積分が使える。 マニュアルは以下を参照:

$$\mbox{ellipk(m)} = K(m) \equiv \int_0^{\frac{\pi}{2}}\frac{1}{\sqrt{1 – m \sin^2 x}} dx$$

第1種完全楕円積分 scipy.special.ellipk(m) を使って,振幅 $\theta_0$ の単振り子の周期 $T_p$ を以下のように定義する。($m = k^2$ に注意。)

In [9]:
from scipy.special import ellipk

def Tp(th):
    theta0 = np.radians(th)
    m = np.sin(theta0/2)**2
    return(2/np.pi*ellipk(m))
In [10]:
# 振幅と周期のグラフを描くために x1, y1 に追加していく
x1 = []
y1 = []

th = 1
x1.append(th)
y1.append(Tp(th))
print('θ_0 = %2d° のとき  ' % x1[-1], end = '')
print('周期 T = %14.12f' % y1[-1])

for th in range(2, 91):
    x1.append(th)
    y1.append(Tp(th))
    # 10°ごとに表示
    if th % 10 == 0:
        print('θ_0 = %2d° のとき  ' % x1[-1], end = '') 
        print('周期 T = %14.12f' % y1[-1])
θ_0 =  1° のとき  周期 T = 1.000019038921
θ_0 = 10° のとき  周期 T = 1.001907188143
θ_0 = 20° のとき  周期 T = 1.007669025792
θ_0 = 30° のとき  周期 T = 1.017408797596
θ_0 = 40° のとき  周期 T = 1.031340519130
θ_0 = 50° のとき  周期 T = 1.049782960623
θ_0 = 60° のとき  周期 T = 1.073182007149
θ_0 = 70° のとき  周期 T = 1.102144909639
θ_0 = 80° のとき  周期 T = 1.137492559924
θ_0 = 90° のとき  周期 T = 1.180340599016
In [11]:
# 横軸に振幅 x1, 縦軸に周期 y1 
# 1°ごとのデータがあるので,plot で線に
plt.plot(x1, y1)
plt.grid(linestyle='dotted')
plt.xlabel("振幅 θ (°)")
plt.xlim(0, 90)
plt.ylim(0.95, 1.2)
plt.ylabel("規格化された周期 T")
plt.title("単振り子の振幅と周期");
plt.savefig('pfuriE02A.svg');