# 單擺
> 作者:王一哲
> 日期:2018/4/5
<br />
將小球用一條理想的繩子懸掛在天花板底下,若繩子與鉛垂線的夾角(擺角)為 $\theta_0$,小球由靜止釋放。當擺角為 $\theta$ 時,小球受到重力及繩子張力的作用,相對於懸掛點產生的力矩為
$$
\vec \tau = \vec r \times \vec F ~\Rightarrow~ \tau = rF \sin \theta
$$
若定義轉動慣量 (moment of inertia)
$$
I = mr^2
$$
由於力矩 $\tau$ 與角加速度 $\alpha$ 方向相反,兩者的關係為
$$
\tau = -I \alpha ~\Rightarrow~ rF \sin \theta = -mr^2 \frac{d^2 \theta}{dt^2} ~\Rightarrow~ \frac{F}{mr} \sin \theta = -\frac{d^2 \theta}{dt^2}
$$
若 $r = L$,$F = mg$,當 $\theta < 5^{\circ}$ 時, $\sin \theta \approx \theta$,上式可以改為
$$
\frac{g}{L} \theta = -\frac{d^2 \theta}{dt^2}
$$
可以解出
$$
\theta (t) = \theta_0 \cos \left(\sqrt{\frac{g}{L}} t \right) = \theta_0 \cos ( \omega t)
$$
上式中 $\omega$ 為單擺擺動的角頻率,因此週期為
$$
T = \frac{2 \pi}{\omega} = 2 \pi \sqrt{\frac{L}{g}}
$$
<br />
以下共有2個程式:
1. 理想的單擺,改變起始的擺角計算運動過程及週期。 ([GlowScript 網站動畫連結](http://www.glowscript.org/#/user/yizhe/folder/Public/program/9-1pendulum))
2. 考慮空氣阻力的單擺。 ([GlowScript 網站動畫連結](http://www.glowscript.org/#/user/yizhe/folder/Public/program/9-2pendulumdrag))
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://i.imgur.com/m8KHI4z.png">
<div style="text-align:center">單擺畫面截圖</div>
<br />
## 程式 9-1:單擺 ([取得程式碼](https://github.com/YiZheWangTw/VPythonTutorial/blob/master/09.%E5%96%AE%E6%93%BA/9-1_pendulum.py))
```python=
"""
VPython教學: 9-1.單擺
Ver. 1: 2018/2/25
Ver. 2: 2019/9/8
Ver. 3: 2023/4/27 懸掛點改成原點,所有物件向上平移
作者: 王一哲
"""
from vpython import *
"""
1. 參數設定, 設定變數及初始值
"""
m = 1 # 小球質量
size = 0.2 # 小球半徑
L = 5 # 擺長
theta0 = radians(30)# 起始擺角, 用 radians 將單位換成 rad
theta = theta0 # 擺角
g = 9.8 # 重力加速度
T = 2*pi*sqrt(L/g) # 單擺週期理論值, L=5, g=9.8
alpha = 0 # 角加速度, 初始值為 0
omega = 0 # 角速度, 初始值為 0
i = 0 # 小球經過週期次數
t = 0 # 時間
dt = 0.001 # 時間間隔
print("週期理論值 T = {:.12f} s".format(T))
"""
2. 畫面設定
"""
# 產生動畫視窗、天花板、小球、繩子
scene = canvas(title="Pendulum", width=600, height=600, x=0, y=0, background=vec(0, 0.6, 0.6), center=vec(0, -L/2, 0), range=L)
roof = box(pos=vec(0, 0.05, 0), size=vec(L, 0.1, 0.5*L), color=color.blue)
ball = sphere(pos=vec(L*sin(theta0), -L*cos(theta0), 0), radius=size, color=color.red,
make_trail=True, retain=100, v=vec(0, 0, 0))
rope = cylinder(pos=vec(0, 0, 0), axis=ball.pos, radius=0.1*size, color=color.yellow)
# 產生表示速度的箭頭
arrow_v = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.green)
arrow_vx = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.magenta)
arrow_vy = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.orange)
# 繪圖部分
gd = graph(title="plot", width=600, height=450, x=0, y=600, xtitle="<i>t</i> (s)",
ytitle="blue: theta (rad), green: omega (rad/s), red: alpha (rad/s<sup>2</sup>)")
theta_t = gcurve(graph=gd, color=color.blue)
omega_t = gcurve(graph=gd, color=color.green)
alpha_t = gcurve(graph=gd, color=color.red)
"""
3. 物體運動部分, 重覆5個週期
"""
omega_p = omega
while i < 5:
rate(1000)
# 計算小球所受力矩、角加速度、角速度、擺角
r = ball.pos
alpha = cross(r, vec(0, -m*g, 0)).z/(m*L**2)
#alpha = -m*g*ball.pos.x/(m*L**2)
omega += alpha*dt
theta += omega*dt
# 更新小球的位置、速度, 繩子的軸方向及長度
ball.pos = vec(L*sin(theta), -L*cos(theta), 0)
v = L*omega
vx = v*cos(theta)
vy = v*sin(theta)
rope.axis = r
# 更新代表速度的箭頭位置、方向、長度
arrow_v.pos = ball.pos
arrow_vx.pos = ball.pos
arrow_vy.pos = ball.pos
arrow_v.axis = vec(vx, vy, 0)
arrow_vx.axis = vec(vx, 0, 0)
arrow_vy.axis = vec(0, vy, 0)
# 畫出 theta-t, omega-t, alpha-t 圖
theta_t.plot(pos=(t, theta))
omega_t.plot(pos=(t, omega))
alpha_t.plot(pos=(t, alpha))
# 檢驗小球是否經過一個週期
omega_c = omega
if omega_p > 0 and omega_c < 0:
i += 1
print("第{:d}次擺動經過的時間 t = {:.12f} s".format(i, t))
omega_p = omega_c
# 更新時間
t += dt
```
<br />
### 參數設定
在此定義的變數有 m、size、L、theta0、theta、g、T、alpha、omega、i、t、dt,用途都已經寫在該行的註解中。
<br />
### 畫面設定
1. 由於我希望物件可以盡量佔滿整個動畫視窗,因此將天花板的位置向上移動到 y = L/2 + 0.05 處,懸掛點不在原點,計算繩子的長度及方向時要減掉天花板的中心位置。
2. 配合懸掛點的位置,小球位於 y = L/2 - L*cos(theta0) 處。
3. 目前只畫出代表 v、vx、vy 的箭頭,可以仿照這個方法畫出代表 a、ax、ay 的箭頭。
4. 繪圖部分只畫出 $\theta - t$、$\omega - t$、$\alpha - t$ 關係圖。
<br />
### 物體運動
1. 由於我們將小球的懸掛點改到原點,將天花板、小球、繩子都向上平移,因此計算 r 等於小球的位置。
```python
r = ball.pos
```
2. 計算角加速度的方法有兩種,如果要用數學上的外積 (cross product)先計算力矩,由於 $\vec r$ 在 $xy$ 平面上,重力在 $-y$ 軸方向上,力矩會在 $z$ 軸方向上,因此以下的程式碼中,用 cross 計算完力矩之後只取 $z$ 軸分量,再除以轉動慣量即可得到角加速度。
```python
alpha = cross(r, vec(0, -m*g, 0)).z/(m*L**2)
```
第二種寫法,用力乘以力臂計算重力產生的力矩,再計算角加速度
```python
alpha = -m*g*ball.pos.x / (m*L**2)
```
再計算角速度、擺角。
3. 更新小球的位置、速度, 繩子的軸方向及長度。
4. 更新代表速度的箭頭位置、方向、長度。
5. 畫出 $\theta - t$、$\omega - t$、$\alpha - t$ 關係圖。
6. 當小球經過一個週期時,角度速會由逆時鐘方向(正值)變為順時鐘方向(負值),藉此判斷小球是否經過一個週期。
7. 最後記得要更新時間。
<br />
### 模擬結果
當 $\theta < 5^{\circ}$ 時,週期理論值為 4.487989505128 s;當 $\theta = 1^{\circ}$ 時週期為 4.488000000000 s;當 $\theta = 30^{\circ}$ 時週期為 4.566000000000;當 $\theta = 60^{\circ}$ 時週期為 4.816000000000,當 $\theta$ 越大時週期與理論值的差距越大。從 $\theta - t$、$\omega - t$、$\alpha - t$ 關係圖可以看出,三者大約是 cos、-sin、-cos 的關係,與理論計算相符。
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/MNwCCrg.png">
<div style="text-align:center">𝛳 = 1°,𝛳 - t 、𝜔 - t 、𝛼 - t 關係圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/tmIDdjD.png">
<div style="text-align:center">𝛳 = 30°,𝛳 - t 、𝜔 - t 、𝛼 - t 關係圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/hvHee5M.png">
<div style="text-align:center">𝛳 = 60°,𝛳 - t 、𝜔 - t 、𝛼 - t 關係圖</div>
<br />
## 程式 9-2.單擺, 有空氣阻力 ([取得程式碼](https://github.com/YiZheWangTw/VPythonTutorial/blob/master/09.%E5%96%AE%E6%93%BA/9-2_pendulum_drag.py))
```python=
"""
VPython教學: 9-2.單擺, 有空氣阻力
Ver. 1: 2018/2/25
Ver. 2: 2019/9/8
Ver. 3: 2023/4/27 懸掛點改成原點,所有物件向上平移
作者: 王一哲
"""
from vpython import *
"""
1. 參數設定, 設定變數及初始值
"""
m = 1 # 小球質量
size = 0.2 # 小球半徑
L = 5 # 擺長
theta0 = radians(30) # 起始擺角, 用 radians 將單位換成 rad
theta = theta0 # 擺角
g = 9.8 # 重力加速度
b = 0.1 # 空氣阻力 f = -bv
T = 2*pi*sqrt(L/g) # 單擺週期理論值, L = 5, g = 9.8,
alpha = 0 # 角加速度, 初始值為 0
omega = 0 # 角速度, 初始值為 0
i = 0 # 小球經過週期次數
t = 0 # 時間
dt = 0.001 # 時間間隔
print("週期理論值 T = {:.12f} s".format(T))
"""
2. 畫面設定
"""
# 產生動畫視窗、天花板、小球、繩子
scene = canvas(title="Pendulum with Air Drag", width=600, height=600, x=0, y=0, background=vec(0, 0.6, 0.6), center=vec(0, -L/2, 0), range=L)
roof = box(pos=vec(0, 0.05, 0), size=vec(L, 0.1, 0.5*L), color=color.blue)
ball = sphere(pos=vec(L*sin(theta0), -L*cos(theta0), 0), radius=size, color=color.red,
make_trail=True, retain=100, v=vec(0, 0, 0))
rope = cylinder(pos=vec(0, 0, 0), axis=ball.pos, radius=0.1*size, color=color.yellow)
# 產生表示速度的箭頭
arrow_v = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.green)
arrow_vx = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.magenta)
arrow_vy = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.orange)
# 繪圖部分
gd = graph(title="plot", width=600, height=450, x=0, y=600, xtitle="<i>t</i> (s)",
ytitle="blue: theta (rad), green: omega (rad/s), red: alpha (rad/s<sup>2</sup>)")
theta_t = gcurve(graph=gd, color=color.blue)
omega_t = gcurve(graph=gd, color=color.green)
alpha_t = gcurve(graph=gd, color=color.red)
"""
3. 物體運動部分, 重覆5個週期
"""
omega_p = omega
while i < 5:
rate(1000)
# 計算小球所受力矩、角加速度、角速度、角位移
r = ball.pos
F = vec(0, -m*g, 0) - b*ball.v
alpha = cross(r, F).z/(m*L*L)
omega += alpha*dt
theta += omega*dt
# 更新小球的位置、速度, 繩子的軸方向及長度
ball.pos = vec(L*sin(theta), -L*cos(theta), 0)
v = L*omega
vx = v*cos(theta)
vy = v*sin(theta)
ball.v = vec(vx, vy, 0)
rope.axis = r
# 更新代表速度的箭頭位置、方向、長度
arrow_v.pos = ball.pos
arrow_vx.pos = ball.pos
arrow_vy.pos = ball.pos
arrow_v.axis = vec(vx, vy, 0)
arrow_vx.axis = vec(vx, 0, 0)
arrow_vy.axis = vec(0, vy, 0)
# 畫出 theta-t, omega-t, alpha-t 圖
theta_t.plot(pos = (t, theta))
omega_t.plot(pos = (t, omega))
alpha_t.plot(pos = (t, alpha))
# 檢驗小球是否經過一個週期
omega_c = omega
if omega_p > 0 and omega_c < 0:
i += 1
print("第{:d}次擺動經過的時間 t = {:.12f} s".format(i, t))
omega_p = omega_c
# 更新時間
t += dt
```
<br />
### 程式設計部分
程式 9-2 與 9-1 非常類似,不同之處在於
1. 第 18 行,新增變數 b = 0.1。
2. 第 58 行,計算小球所受合力時要加上空氣阻力
```python
F = vec(0, -m*g, 0) - b * ball.v
```
<br />
### 模擬結果
取 $\theta_0 = 30^{\circ}$,由以下4張圖可以看出當 b 越大時,𝛳、𝜔、𝛼 隨時間衰減的幅度較大,符合理論預測。
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/AJzpWYc.png">
<div style="text-align:center">b = 0.1,𝛳 - t 、𝜔 - t 、𝛼 - t 關係圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/pWGYHLG.png">
<div style="text-align:center">b = 0.2,𝛳 - t 、𝜔 - t 、𝛼 - t 關係圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/AbKM3tC.png">
<div style="text-align:center">b = 0.3,𝛳 - t 、𝜔 - t 、𝛼 - t 關係圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/wlDuZNf.png">
<div style="text-align:center">b = 0.4,𝛳 - t 、𝜔 - t 、𝛼 - t 關係圖</div>
<br />
## 程式 9-3.單擺 + 彈簧 ([取得程式碼](https://github.com/YiZheWangTw/VPythonTutorial/blob/master/09.%E5%96%AE%E6%93%BA/9-3_pendulum_spring.py)) ([GlowScript 網站動畫連結](http://www.glowscript.org/#/user/yizhe/folder/Public/program/9-3pendulumspring))
```python=
"""
VPython教學: 9-3.單擺 + 彈簧
Ver. 1: 2018/4/5
Ver. 2: 2019/9/8
作者: 王一哲
"""
from vpython import *
"""
1. 參數設定, 設定變數及初始值
"""
m = 1 # 小球質量
size = 0.2 # 小球半徑
L = 5 # 擺長
theta0 = radians(30) # 起始擺角, 用 radians 將單位換成 rad
theta = theta0 # 擺角
g = 9.8 # 重力加速度
k = 10 # 彈性常數 10 N/m
L0 = 4 # 彈簧原長
T = 2*pi*sqrt(L/g) # 單擺週期理論值
ratio = 0.2 # 箭頭長度與實際值的比例
t = 0 # 時間
dt = 0.001 # 時間間隔
"""
2. 畫面設定
"""
# 產生動畫視窗、天花板、小球、彈簧
scene = canvas(title="Pendulum with Spring", width=600, height=600, x=0, y=0, background=vec(0, 0.6, 0.6))
roof = box(pos = vec(0, L/2 + 0.05, 0), size=vec(L, 0.1, 0.5*L), color=color.blue)
ball = sphere(pos=vec(L*sin(theta0), L/2 - L*cos(theta0), 0), radius=size,
color=color.red, make_trail=True, retain=100, v=vec(0, 0, 0))
spring = helix(pos=vec(0, L/2, 0), axis=ball.pos - vec(0, L/2, 0), radius=0.6*size,
thickness=0.3*size, color=color.yellow)
# 產生表示速度的箭頭
arrow_v = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.green)
arrow_vx = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.magenta)
arrow_vy = arrow(pos=ball.pos, axis=vec(0, 0, 0), shaftwidth=0.3*size, color=color.orange)
# 繪圖部分
gd1 = graph(title="position", width=600, height=450, x=0, y=600, xtitle="<i>t</i>(s)",
ytitle="blue: <i>r</i>(m), red: <i>x</i>(m), green: <i>y</i>(m)")
r_t = gcurve(graph=gd1, color=color.blue)
x_t = gcurve(graph=gd1, color=color.red)
y_t = gcurve(graph=gd1, color=color.green)
gd2 = graph(title="velocity", width=600, height=450, x=0, y=1050, xtitle="<i>t</i>(s)",
ytitle="blue: <i>v</i>(m/s), red: <i>v<sub>x</sub></i>(m/s), green: <i>v<sub>y</sub></i>(m/s)")
v_t = gcurve(graph=gd2, color=color.blue)
vx_t = gcurve(graph=gd2, color=color.red)
vy_t = gcurve(graph=gd2, color=color.green)
gd3 = graph(title="acceleration", width=600, height=450, x=0, y=1500, xtitle="<i>t</i>(s)",
ytitle = "blue: <i>a</i>(m/s<sup>2</sup>), red: <i>a<sub>x</sub></i>(m/s<sup>2</sup>), green: <i>a<sub>y</sub></i>(m/s<sup>2</sup>)")
a_t = gcurve(graph=gd3, color=color.blue)
ax_t = gcurve(graph=gd3, color=color.red)
ay_t = gcurve(graph=gd3, color=color.green)
"""
3. 物體運動部分
"""
r = ball.pos - vec(0, L/2, 0)
while t < 5*T:
rate(1000)
# 計算小球所受合力、加速度、速度、位置, 更新彈簧長度、方向
F = vec(0, -m*g, 0) - k*(r.mag - L0)*r.norm()
ball.a = F/m
ball.v += ball.a*dt
ball.pos += ball.v*dt
r = ball.pos - vec(0, L/2, 0)
spring.axis = r
# 更新代表速度的箭頭位置、方向、長度
arrow_v.pos = ball.pos
arrow_vx.pos = ball.pos
arrow_vy.pos = ball.pos
arrow_v.axis = ball.v * ratio
arrow_vx.axis = vec(ball.v.x, 0, 0) * ratio
arrow_vy.axis = vec(0, ball.v.y, 0) * ratio
# 畫出位置、速度、加速度與時間關係圖
r_t.plot(pos=(t, ball.pos.mag))
x_t.plot(pos=(t, ball.pos.x))
y_t.plot(pos=(t, ball.pos.y))
v_t.plot(pos=(t, ball.v.mag))
vx_t.plot(pos=(t, ball.v.x))
vy_t.plot(pos=(t, ball.v.y))
a_t.plot(pos=(t, ball.a.mag))
ax_t.plot(pos=(t, ball.a.x))
ay_t.plot(pos=(t, ball.a.y))
# 更新時間
t += dt
```
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://i.imgur.com/yXd1iIn.png">
<div style="text-align:center">將繩子改為彈簧,𝛳<sub>0</sub> = 30°,不考慮空氣阻力的運動畫面截圖</div>
<br />
### 程式設計部分
在前一篇文章〈[簡諧運動](https://hackmd.io/s/S1eiJsuGzm)〉畫出了木塊、彈簧系統的運動情形,在程式 9-1 又畫了單擺的運動情形,於是我很好奇將單擺系統中的繩子換成彈簧會發生什麼事,就拿程式 9-1 來修改一下,以下是修改的地方:
1. 第 18、19 行,新增彈性常數 k = 10、彈簧原長 L0 = 4。
2. 為了使代表速度的箭頭不會太長,在第 21 行新增箭頭長度與實際值的比例 ratio = 0.2。
3. 第 33 行,將原來產生的繩子改用 helix 產生彈簧。
4. 第 40 ~ 54 行,繪圖部分共畫 3 張圖,將位置、速度、加速度與時間關係圖分開畫。
5. 第 59 行,在 while 迴圈外先用
```python
r = ball.pos - vec(0, L/2, 0)
```
計算軸的長度及方向。
6. 第 63 ~ 66 行,先用
```python
F = vec(0, -m * g, 0) - k * (r.mag - L0) * r.norm()
```
計算小球所受合力,再用
```python
ball.a = F/m
ball.v += ball.a * dt
ball.pos += ball.v * dt
```
計算小球的加速度、更新速度及位置。
7. 第 74 ~ 75 行,為了避免箭頭太長影響到畫面的比例,在更新箭頭的長度及方向時乘以 ratio,使長度縮短。
8. 第 77 ~ 85 行,改成畫位置、速度、加速度與時間關係圖。
<br />
### 模擬結果
取 𝛳<sub>0</sub> = 30° ,k = 10 N/m,L0 = 4 m,小球由靜止釋放後的位置、速度、加速度與時間關係圖如下,我實在看不出有任何的規律性。有時候小球會超過出發時的高度,這是有可能發生的,因為小球出發時除了有重力位能之外還有小球和彈簧之間的彈性位能,如果小球正好在較比較靠近懸掛點處向上移動,就有可能把大部分的彈性位能換成重力位能。
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/WPi0W9Y.png">
<div style="text-align:center">將繩子改為彈簧,𝛳<sub>0</sub> = 30°,小球的位置 - 時間關係圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/7h6CT8H.png">
<div style="text-align:center">將繩子改為彈簧,𝛳<sub>0</sub> = 30°,小球的速度 - 時間關係圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/NPa3oAk.png">
<div style="text-align:center">將繩子改為彈簧,𝛳<sub>0</sub> = 30°,小球的加速度 - 時間關係圖</div>
<br />
## 結語
即使系統比較複雜,但只要知道如何描述物體的受力情形,就可以用 VPython 把運動過程畫出來。
<br />
## VPython官方說明書
1. **canvas**: http://www.glowscript.org/docs/VPythonDocs/canvas.html
2. **box**: http://www.glowscript.org/docs/VPythonDocs/box.html
3. **sphere**: http://www.glowscript.org/docs/VPythonDocs/sphere.html
4. **cylinder**: http://www.glowscript.org/docs/VPythonDocs/cylinder.html
5. **arrow**: http://www.glowscript.org/docs/VPythonDocs/arrow.html
6. **graph**: http://www.glowscript.org/docs/VPythonDocs/graph.html
7. **helix**: http://www.glowscript.org/docs/VPythonDocs/helix.html
---
###### tags: `VPython`