<style>
.markdown-body table{
display: unset;
}
</style>
# 終端速度
> 作者:王一哲
> 第1版:2018年3月20日
> 第2版:2024年3月21日
<br />
這篇文章本來應該是前一篇文章〈[自由落下](https://hackmd.io/s/S1e8LxzGQ)〉最後一部分的內容,但由於這個程式的寫法比較不一樣,需要解釋的東西較多,所以獨立出來另外寫一篇。這個程式的目標:當小球從高空落下時,同時受到重力及空氣阻力的作用,試著找出小球的運動過程及終端速度,同時將得到的資料存成文字檔。
<br />
## 物理原理
假設空氣阻力
$$f = -bv$$
小球落下時所受合力(向下為正)
$$F = mg - bv = ma$$
小球剛開始運動時
$$ v = 0 ~~~~~ f = 0 ~~~~~ a = g $$
當小球速度 $v$ 增加時、$f$ 增加、$a$ 減小。當小球所受合力為零時,小球不會再加速,此時的速度稱為終端速度 (terminal velocity),通常代號為 $v_t$,由上式可知
$$
v_t = \frac{mg}{b}
$$
以下圖片是以小球質量 $m = 0.1 ~\mathrm{kg}$、重力加速度 $g = 9.8 ~\mathrm{m/s^2}$、空氣阻力係數 $b = 0.1 ~\mathrm{kg/s}$ 模擬得到的結果,$v_t = 9.799001457836292 ~\mathrm{m/s}$,$理論值 = 9.8 ~\mathrm{m/s}$。
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="60%" width="60%" src="https://imgur.com/jZW99jv.png">
<div style="text-align:center">小球終端速度,b = 0.1, y-t 圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="60%" width="60%" src="https://imgur.com/XE0pzKw.png">
<div style="text-align:center">小球終端速度,b = 0.1, v-t 圖</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="60%" width="60%" src="https://imgur.com/6GkhYff.png">
<div style="text-align:center">小球終端速度,b = 0.1, a-t 圖</div>
<br />
## 程式 4-4:終端速度 ([取得程式碼](https://github.com/YiZheWangTw/VPythonTutorial/blob/master/04.%E8%87%AA%E7%94%B1%E8%90%BD%E4%B8%8B/4-4_airdrag_vt.py))
```python=
"""
VPython教學: 4-4.自由落下, 有空氣阻力, 求終端速度
Ver. 1: 2018/3/8
Ver. 2: 2019/2/2
Ver. 3: 2019/9/6
Ver. 4: 2024/3/21 修改 while 迴圈的停止條件,修改參數減少程式運作時間
作者: 王一哲
"""
from vpython import *
"""
1. 參數設定, 設定變數及初始值
"""
r = 1 # 小球半徑
m = 0.1 # 小球質量
h = 0 # 小球離地高度
g = 9.8 # 重力加速度 9.8 m/s^2
b = 0.1 # 空氣阻力 f=-bv
c1, c2 = color.red, color.green
t = 0 # 時間
dt = 0.001 # 時間間隔
"""
2. 畫面設定
"""
scene = canvas(title="Terminal Velocity", width=400, height=400, x=0, y=0, center=vec(0, h/2, 0), background=vec(0, 0.6, 0.6))
# b1 with air drag, b2 without air drag
b1 = sphere(pos=vec(2*r, h, 0), radius=r, color=c1, v=vec(0, 0, 0), a=vec(0, -g, 0))
b2 = sphere(pos=vec(-2*r, h, 0), radius=r, color=c2, v=vec(0, 0, 0), a=vec(0, -g, 0))
gd = graph(title="<i>y</i> - <i>t</i> plot", width=600, height=450, x=0, y=400,
xtitle="<i>t</i> (s)", ytitle="<i>y</i> (m)", fast=False)
gd2 = graph(title="<i>v</i> - <i>t</i> plot", width=600, height=450, x=0, y=700,
xtitle="<i>t</i> (s)", ytitle="<i>v</i> (m/s)", fast=False)
gd3 = graph(title="<i>a</i> - <i>t</i> plot", width=600, height=450, x=0, y=1000,
xtitle="<i>t</i> (s)", ytitle="<i>a</i> (m/s<sup>2</sup>)", fast=False)
yt1 = gcurve(graph=gd, color=c1)
yt2 = gcurve(graph=gd, color=c2)
vt1 = gcurve(graph=gd2, color=c1)
vt2 = gcurve(graph=gd2, color=c2)
at1 = gcurve(graph=gd3, color=c1)
at2 = gcurve(graph=gd3, color=c2)
# 開啟檔案 data.csv, 屬性為寫入, 先寫入欄位的標題
file = open("data.csv", "w", encoding="UTF-8")
file.write("t(s), y1(m), y2(m), v1(m/s), v2(m/s), a1(m/s^2), a2(m/s^2)\n")
tp = 0
# 設定計算終端速度用的變數
eps = 1E-6
v1 = 0
v2 = -1E6
"""
3. 物體運動部分, 小球速度變化大於 eps 時繼續運作
"""
while abs(v2 - v1) > eps:
rate(1000)
# 更新小球受力、加速度、速度、位置,畫 y-t 及 v-t 圖
f = -b*b1.v
b1.a = vec(0, -g, 0) + f/m
b1.v += b1.a*dt
b1.pos += b1.v*dt
b2.v += b2.a*dt
b2.pos += b2.v*dt
yt1.plot(pos=(t, b1.pos.y))
vt1.plot(pos=(t, b1.v.y))
at1.plot(pos=(t, b1.a.y))
yt2.plot(pos=(t, b2.pos.y))
vt2.plot(pos=(t, b2.v.y))
at2.plot(pos=(t, b2.a.y))
# 每隔 0.1 秒將資料轉成字串後寫入檔案
tc = t
if tc == 0 or tc - tp >= 0.1:
file.write(str(t) + "," + str(b1.pos.y) + "," + str(b2.pos.y) + \
"," + str(b1.v.y) + "," + str(b2.v.y) + "," + str(b1.a.y) + \
"," + str(b2.a.y) + "\n")
tp = tc
# 更新 v1 為 v2 目前的值,更新 v2 為 b1.v.y
v1, v2 = v2, b1.v.y
# 更新時間
t += dt
print("t =", t, "vt =", v2)
file.close() # 關閉檔案
```
<br />
以下只說明這個程式與程式 4-3 的不同之處。
1. 為了使用 計算小球的加速度,需要定義小球質量 m。為了計算空氣阻力 f = -bv ,需要定義係數 b。在第60、61行
```python
f = -b*b1.v
b1.a = vec(0, -g, 0) + f/m
```
計算空氣阻力 f 及加速度 b1.a。
2. 第38~43行,由於 y、v、a 的數值差異很大,為了使 v 和 a 的曲線不會被壓扁,將 y-t、v-t、a-t 分開作圖。
3. 為了將資料存成文字檔,需要增加以下的程式碼,分別位於第46 ~ 48、73 ~ 78行。第46行,先用 open 開啟檔名為 data.csv 的文字檔,**w** 代表寫入,若檔案不存在會新增檔案並寫入資料,若檔案已存在會覆寫檔案內容,最後的 **encoding = "UTF-8"** 是指定文字編碼格式為 UTF-8 (8-bit Unicode Transformation Format)。
```python
file = open("data.csv", "w", encoding="UTF-8")
```
第47行,用 **file.write** 將字串 "t(s), y1(m), y2(m), v1(m/s), v2(m/s), a1(m/s^2), a2(m/s^2)\n" 寫入檔案,最後的 **\n 代表換行**。
```python
file.write("t(s), y1(m), y2(m), v1(m/s), v2(m/s), a1(m/s^2), a2(m/s^2)\n")
```
第85行,最後一定要用 **file.close() 關閉檔案**,否則程式會執行錯誤。我們在之後的課程〈[使用 For 迴圈計算水平抛射資料](https://hackmd.io/@yizhewang/SJoo2fGfQ)〉會介紹另一個寫法,使用 with 開啟檔案,使用這個寫法就不需要關閉檔案。
```python
file.close() # 關閉檔案
```
4. 如果將所有的資料都寫入文字檔會使檔案太大,大約每隔 0.1 秒記錄一次資料應該就夠詳細了,因此在第48行定義了變數 tp、初始值為0,在第73行定義了變數 tc,將此時的 t 數值指定給 tc。接著第74 ~ 77行,當 tc 等於 0 或 tc - tp >= 0.1(經過 0.1 秒)時,用 **str** 將格式為浮點數的變數 t、b1.pos.y、……等數值轉成字串,再用 **+** 接成一個較長的字串,最後用 file.write 寫入檔案。第78行,將 tc 的數值指定給 tp。
```python
tc = t
if tc == 0 or tc - tp >= 0.1:
file.write(str(t) + "," + str(b1.pos.y) + "," + str(b2.pos.y) + \
"," + str(b1.v.y) + "," + str(b2.v.y) + "," + str(b1.a.y) + \
"," + str(b2.a.y) + "\n")
tp = tc
```
5. 如果只想要趕快算出終端速度,並不想看小球落下過程的動畫,可以刪除 while 迴圈當中的 rate(1000) ,不限制每秒執行幾次,程式能跑多快就跑多快。如果覺得刪除 rate(1000) 之後程式運算速度還是太慢,可以再刪除與繪製函數圖形有關的程式碼。
6. 為了計算終端速度,在第51 ~ 53行定義變數 eps = 1E-6、v1 = 0、v2 = -1E6。第57行,while 迴圈運作的條件為
```python
abs(v2 - v1) > eps
```
也就是速度變化仍大於我們所設定的精準度時,代表小球尚未達到終端速度,需要繼續運算。接下來在第80行,更新 v1 為 v2 目前的值,更新 v2 為 b1.v.y。
```python
v1, v2 = v2, b1.v.y
```
7. 轉存成文字檔的資料格式如下([取得檔案](https://raw.githubusercontent.com/YiZheWangTw/VPythonTutorial/master/04.%E8%87%AA%E7%94%B1%E8%90%BD%E4%B8%8B/data.csv)):
```csv=
t(s), y1(m), y2(m), v1(m/s), v2(m/s), a1(m/s^2), a2(m/s^2)
0,-9.800000000000001e-06,-9.800000000000001e-06,-0.009800000000000001,-0.009800000000000001,-9.8,-9.8
0.10000000000000007,-0.048837982593961625,-0.0504798,-0.9419039213273656,-0.9898000000000009,-8.86696304171435,-9.8
0.20000000000000015,-0.18632103459137758,-0.19894980000000026,-1.7852642296382608,-1.969800000000004,-8.02275852889063,-9.8
0.3000000000000002,-0.40401831622843054,-0.44541979999999975,-2.548330013785355,-2.9497999999999855,-7.258928915129776,-9.8
0.4000000000000003,-0.6942928029302021,-0.7898897999999974,-3.23874594301281,-3.9297999999999664,-6.567821878866057,-9.8
0.5000000000000003,-1.050234574833993,-1.2323597999999951,-3.8634288540200266,-4.909799999999988,-5.942513659639614,-9.8
0.6000000000000004,-1.4655915907108468,-1.7728297999999951,-4.428637046335492,-5.8898000000000135,-5.376739693357867,-9.8
0.7000000000000005,-1.9347070527533745,-2.411299799999998,-4.940032980226851,-6.869800000000039,-4.864831851624774,-9.8
0.8000000000000006,-2.452462734727637,-3.147769800000003,-5.402740005277641,-7.849800000000064,-4.40166165637874,-9.8
0.9000000000000007,-3.0142277057300206,-3.982239800000011,-5.821393687957935,-8.82980000000009,-3.9825889009430098,-9.8
...
```
可以將資料匯入 LibreOffice Calc、MicroSoft Excel 之類的試算表軟體進行後續處理。
## 結語
在這個程式當中我們學到了將資料存成文字檔的方法,匯出的資料可以再利用其它的軟體處理並作圖。雖然在 Python 當中有一個很有名的繪圖套件 matplotlib,有興趣的同學可以參考〈[Matplotlib 繪圖技巧:讀取數據及線性擬合](https://hackmd.io/s/ByN63TEC7)〉、〈[Matplotlib 繪圖技巧:在同一張圖上繪製兩個函數、兩張圖並列](https://hackmd.io/s/Bkd0EMyCm)〉。
<br />
## 2022年9月29日補充:使用 NumPy 以及 Matplotlib 繪圖
如果想要使用 Python 讀取文字檔的資料並繪圖,可以使用 NumPy 以及 Matplotlib,程式碼如下。
<br />
```python=
import numpy as np
import matplotlib.pyplot as plt
# 從 data.csv 讀取資料
t, y1, y2, v1, v2, a1, a2 = np.loadtxt('data.csv', skiprows=1, delimiter=',', unpack=True)
# 繪圖
plt.figure(figsize=(8, 5), dpi=100) # 設定圖片尺寸
plt.xlabel(r'$t ~\mathrm{(s)}$', fontsize=16) # 設定坐標軸標籤
plt.ylabel(r'$v ~\mathrm{(m/s)}$', fontsize=16)
plt.xticks(fontsize=12) # 設定坐標軸數字格式
plt.yticks(fontsize=12)
plt.grid(color='0.5', linestyle='--', linewidth=1) # 設定格線顏色、種類、寬度
# 繪圖並設定線條顏色、寬度、圖例
line1, = plt.plot(t, v1, color='red', linewidth=3, label=r'$v_1$')
line2, = plt.plot(t, v2, color='blue', linewidth=3, label=r'$v_2$')
plt.legend(handles=[line1, line2], loc='lower left', fontsize=16)
plt.savefig('v-t_plot.svg') # 儲存圖片
plt.savefig('v-t_plot.png')
plt.show() # 顯示圖片
```
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="60%" width="60%" src="https://imgur.com/bQeNGnE.png">
<div style="text-align:center">使用 Matplotlib 繪製的 v-t 圖</div>
<br /><br />
假設有以下的實驗數據,若推測 y 與 x 成反比,可以使用以下的程式碼作圖並計算最接近直線的斜率及截距。
<div style="text-align:center">
| x | 0.2 | 0.4 | 0.6 | 0.8 | 1.0 |
| - | --- | --- | --- | --- | --- |
| y | 10.1| 4.9 | 3.3 | 2.6 | 1.9 |
</div><br /><br />
```python=
import numpy as np
import matplotlib.pyplot as plt
x = np.asarray([0.2, 0.4, 0.6, 0.8, 1.0])
y = np.asarray([10.1, 4.9, 3.3, 2.6, 1.9])
z = 1/x
# 產生計算最接近直線需要用到的陣列 A ,計算直線的斜率 m 和截距 c
A = np.vstack([z, np.ones(len(z))]).T
m, c = np.linalg.lstsq(A, y, rcond = -1)[0]
print(m, c)
plt.figure(figsize=(8, 6), dpi=100) # 設定圖片尺寸
plt.xlabel(r'$1/x$', fontsize=16) # 設定坐標軸標籤
plt.ylabel(r'$y$', fontsize=16)
plt.xticks(fontsize=12) # 設定坐標軸數字格式
plt.yticks(fontsize=12)
plt.grid(color='0.5', linestyle='--', linewidth=1) # 設定格線顏色、種類、寬度
# 繪圖並設定線條顏色、寬度、圖例
data, = plt.plot(z, y, color='blue', marker='o', markersize=10, linestyle='', label="Data")
fitline, = plt.plot(z, m*z + c, color='orange', linewidth=2, label="Fitted Line")
plt.legend(handles=[data, fitline], loc='upper left', fontsize=16)
plt.savefig('fit_plot.svg') # 儲存圖片
plt.savefig('fit_plot.png')
plt.show() # 顯示圖片
```
<br />
最接近直線的斜率、縱軸截距分別為 $slope = 2.028088701161563$、$intercept = -0.07080253431890154$,以下是繪圖成果。
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/aQKfx36.png">
<div style="text-align:center">使用 Matplotlib 繪製資料點及最接近直線</div>
<br /><br />
## VPython官方說明書
1. **canvas**: http://www.glowscript.org/docs/VPythonDocs/canvas.html
2. **sphere**: http://www.glowscript.org/docs/VPythonDocs/sphere.html
3. **graph**: http://www.glowscript.org/docs/VPythonDocs/graph.html
---
###### tags: `VPython`