跳轉到

01.4 專案:螺線族探索#

本節目標#

  • 完成本章第一個完整專案:以螺旋線 \(\gamma(t) = (a\cos t,\ a\sin t,\ bt)\) 為對象,從定義、計算、驗證到視覺化走一遍完整流程。
  • 用 SymPy 符號推導並驗證經典結果:
\[ \kappa = \frac{a}{a^2+b^2}, \qquad \tau = \frac{b}{a^2+b^2} \]
  • 用 matplotlib 的 3D 繪圖可視化螺旋線,並觀察改變 \(a\) (半徑)與 \(b\) (螺距)對形狀與曲率的影響。
  • 探索螺線族的極限情形:當 \(b\to 0\) 退化為平面圓、當 \(a\to 0\) 退化為直線;並對「一般/圓柱螺線」的曲率極限情形做反思。

回顧:螺旋線是什麼#

圓柱螺旋線是「繞著圓柱面等速爬升」的軌跡,公式為

\[ \gamma(t) = (a\cos t,\ a\sin t,\ bt), \quad a>0,\ b\neq 0,\ t\in\mathbb{R} \]
  • \(a\) :圓柱半徑。
  • \(b\) :每轉一弧度爬升的高度,螺距 \(=2\pi b\) 。
  • \(b>0\) 右旋、 \(b<0\) 左旋、 \(b=0\) 退化為平面圓。

它是一條以等速率行進的曲線(01.1 節我們算過 \(\|\gamma'\|=\sqrt{a^2+b^2}\) 為常數),也是自然界最普遍的空間曲線:DNA 雙股、螺旋樓梯、彈簧、斜坡上的盤山公路。

專案 Step 1:符號推導曲率與撓率#

import sympy as sp

t = sp.symbols('t', real=True)
a, b = sp.symbols('a b', positive=True)

gamma = sp.Matrix([a*sp.cos(t), a*sp.sin(t), b*t])

v = gamma.diff(t)          # γ'
w = v.diff(t)              # γ''
j = w.diff(t)              # γ'''

speed2 = sp.simplify(v.dot(v))                     # ||γ'||^2 = a^2+b^2
cross_w = v.cross(w)
cross2  = sp.simplify(cross_w.dot(cross_w))        # ||γ'×γ''||^2

kappa = sp.simplify(sp.sqrt(cross2) / speed2**sp.Rational(3, 2))
det3  = sp.simplify(v.dot(w.cross(j)))
torsion = sp.simplify(det3 / cross2)

print("κ =", kappa)       # sqrt(a**2/(a**2 + b**2)**2) → a/(a**2 + b**2)
print("τ =", torsion)     # a**2*b/(a**2*(a**2 + b**2)) → b/(a**2 + b**2)

kappa = sp.simplify(kappa.rewrite(sp.sqrt))   # 若有根號殘留,再化簡一次
print("κ 化簡 =", sp.simplify(kappa))          # a/(a**2 + b**2)
print("τ 化簡 =", torsion)                     # b/(a**2 + b**2)

預期輸出:

κ = a/(a**2 + b**2)
τ = b/(a**2 + b**2)

數學驗證: \(v = (-a\sin t, a\cos t, b)\) , \(\|v\|^2 = a^2+b^2\) 為常數; \(w = (-a\cos t, -a\sin t, 0)\) ; \(v\times w = (ab\sin t, -ab\cos t, a^2)\) ,其範數平方為 \(a^2(b^2+a^2)\) ,故

\[ \kappa = \frac{a\sqrt{a^2+b^2}}{(a^2+b^2)^{3/2}} = \frac{a}{a^2+b^2}, \qquad \tau = \frac{\det(v,w,j)}{\|v\times w\|^2} = \frac{a^2 b}{a^2(a^2+b^2)} = \frac{b}{a^2+b^2} \]

兩者都是常數——螺旋線每個點的彎曲與扭轉程度都相同,這是它被稱為「最均勻的空間曲線」的原因。

專案 Step 2:封装成可重用的模組#

def frenet_analysis(g, t):
    v, w, j = g.diff(t), g.diff(t, 2), g.diff(t, 3)
    v_dot_v = sp.simplify(v.dot(v))
    cross = v.cross(w)
    cross2 = sp.simplify(cross.dot(cross))
    kappa = sp.simplify(sp.sqrt(cross2) / v_dot_v**sp.Rational(3, 2))
    tau = sp.simplify(v.dot(w.cross(j)) / cross2)
    return kappa, tau

# 驗證數值:a=2, b=1
k_val, tau_val = frenet_analysis(gamma, t)
print("a=2, b=1 → κ =", k_val.subs({a: 2, b: 1}))   # 2/5
print("a=2, b=1 → τ =", tau_val.subs({a: 2, b: 1})) # 1/5
print(float(k_val.subs({a: 2, b: 1})))              # 0.4

專案 Step 3:3D 視覺化#

import numpy as np
import matplotlib.pyplot as plt

def plot_helix(a_val, b_val, T=np.pi*8, n=1000, ax=None, color='steelblue'):
    tt = np.linspace(0, T, n)
    x, y, z = a_val*np.cos(tt), a_val*np.sin(tt), b_val*tt
    if ax is None:
        fig = plt.figure(figsize=(7, 7))
        ax = fig.add_subplot(projection='3d')
    ax.plot(x, y, z, color=color, lw=1.5)
    ax.set_xlabel('x'); ax.set_ylabel('y'); ax.set_zlabel('z')
    ax.set_title(f'helix a={a_val}, b={b_val}, κ={a_val/(a_val**2+b_val**2):.3f}')
    return ax

plot_helix(1, 0.3)
plt.show()

你會看到一條沿 z 軸纏繞的彈簧。標題同時顯示理論曲率值,把公式和形狀綁在一起。

專案 Step 4:改變 \(a\) 、 \(b\) 的效應#

fig = plt.figure(figsize=(15, 5))
params = [(1.0, 0.3), (2.0, 0.3), (1.0, 1.0), (1.0, 0.05)]
for i, (av, bv) in enumerate(params, 1):
    ax = fig.add_subplot(1, 4, i, projection='3d')
    plot_helix(av, bv, ax=ax, color=plt.cm.viridis(i/4))
    print(f"a={av}, b={bv}: κ={av/(av**2+bv**2):.3f}, τ={bv/(av**2+bv**2):.3f}")
plt.tight_layout(); plt.show()

觀察規律(用公式預測,再用圖驗證):

  • **加大 \(a\) **(半徑變粗):曲率 \(\kappa = a/(a^2+b^2)\) 先增後減,在 \(a=b\) 時達最大值 \(1/(2b)\) ——圓柱不是越粗彎得越急。
  • **加大 \(b\) **(螺距變長): \(\kappa\) 單調減小而 \(\tau\) 單調先增後減( \(\tau\) 在 \(b=a\) 處最大)。曲線越「斜」,彎越平緩、扭越劇烈。
  • \(\kappa^2+\tau^2 = \dfrac{a^2+b^2}{(a^2+b^2)^2} = \dfrac{1}{a^2+b^2} = \dfrac{1}{\|\gamma'\|^2}\) ,即速率越大,空間總彎度越小。

用 SymPy 一次驗證這些極值:

kappa_expr = a/(a**2 + b**2)
tau_expr   = b/(a**2 + b**2)
# 對 b 微分找極值
print(sp.solve(sp.diff(tau_expr, b), b))   # [a] —— τ 在 b=a 時最大
# κ + τ 的平方和
print(sp.simplify(kappa_expr**2 + tau_expr**2))   # 1/(a**2+b**2)

專案 Step 5:極限情形的探索與反思#

退化為平面圓( \(b\to 0\) )

print(sp.limit(kappa_expr, b, 0))    # 1/a —— 正是半徑 a 的圓的曲率
print(sp.limit(tau_expr, b, 0))      # 0    —— 平面曲線無撓率

當 \(b\to 0\) ,螺旋線壓扁成半徑 \(a\) 的平面圓:曲率回到 \(1/a\) ,撓率歸零。所有數學都平滑退回二維世界——這是個好的一致性檢查。

退化為直線( \(a\to 0\) )

print(sp.limit(kappa_expr, a, 0))    # 0    —— 直線曲率為零
print(sp.limit(tau_expr, a, 0))      # 0    —— 直線也無撓率

當 \(a\to 0\) ,螺旋縮成一條沿 z 軸的直線 \((0,0,bt)\) ,曲率與撓率都消失。直線是空間中「最快抵達」的曲線,卻沒有任何幾何戲劇性。

反思 1: \(\kappa\) 與 \(\tau\) 是什麼的坐標?

對螺旋線有 \(\tau/\kappa = b/a\) 恆定。Lancret 定理告訴我們:撓率與曲率之比為常數的曲線恰是一般螺旋線(繞某固定軸的螺旋線)。也就是說,比值 \(\tau/\kappa\) 像指紋一樣刻畫了「螺旋性」。你可以把 (κ, τ) 想成曲線空間的「極坐標」——任何常數對應一條螺旋線,變動的函數對應更狂野的曲線。

反思 2:圓柱螺線 vs. 一般(廣義)螺線

  • 圓柱螺線:曲率、撓率都為常數,是我們本節的主角。
  • 一般(廣義/Lancret)螺線:只要 \(\tau/\kappa\) 為常數即可,例如畫在圓錐面上的對數螺線投影(concho-spiral)。這類曲線對應的物理是「以固定傾角沿曲面爬升」。
# 廣義螺旋線的例子:圓錐螺線
cone = sp.Matrix([t*sp.cos(t), t*sp.sin(t), t])
kc, tc = frenet_analysis(cone, t)
ratio = sp.simplify(tc/kc)
print("τ/κ =", ratio)   # 應為常數 → 支持其為廣義螺旋線

如果輸出是常數,就驗證了 Lancret 定理在這個例子上的適用。

反思 3:為什麼螺旋線無所不在?

在許多約束下(等速率、貼著圓柱面、能量最省),最自然的解就是螺旋線。曲率與撓率是常數,意味著受力模式恆定——這也許就是 DNA、彈簧、海螺殼都選擇螺線形式的幾何原因。當你改 \(a, b\) 時,其實是在一維的「形狀調音」上滑動,而 (κ, τ) 就是那兩個旋鈕。

動手實驗#

實驗 1:數值驗證 \((\kappa,\tau)\) 表

import sympy as sp
t = sp.symbols('t', real=True)
a, b = sp.symbols('a b', positive=True)
gamma = sp.Matrix([a*sp.cos(t), a*sp.sin(t), b*t])
v, w, j = gamma.diff(t), gamma.diff(t, 2), gamma.diff(t, 3)
cross = v.cross(w)
kappa = sp.simplify(sp.sqrt(cross.dot(cross)) / sp.sqrt(v.dot(v))**3)
tau   = sp.simplify(v.dot(w.cross(j)) / cross.dot(cross))
expr = sp.lambdify((a, b), (kappa, tau), 'numpy')
theo = lambda av, bv: (av/(av**2+bv**2), bv/(av**2+bv**2))
for av, bv in [(1, 1), (2, 1), (1, 3), (3, 3)]:
    print(f"(a,b)=({av},{bv}): num={expr(av,bv)}, theo={theo(av,bv)}, "
          f"match={expr(av,bv) == theo(av,bv)}")

實驗 2:用 sp.plot3d_parametric_line 快速可視化

sp.plot3d_parametric_line(a*sp.cos(t), a*sp.sin(t), b*t,
    (t, 0, 8*sp.pi), params={a: (1, 0, 3), b: (1, -3, 3)},
    use_cm=True)

拖動滑桿即可動態觀察 \(a, b\) 效應,比多張靜態圖更直觀。

實驗 3: \(\kappa\) 最大時的形狀

固定 \(b=1\) ,掃 \(a\in(0, 3]\) ,畫 \(\kappa(a)\) 曲線。你會看到單峰、峰值在 \(a=1\) (即 \(a=b\) )。畫 \(a=1,b=1\) 的 3D 圖——這個「最彎的螺旋線」長什麼樣?你有什麼直覺性猜想嗎?

實驗 4(挑戰):重建曲線

給 \(\kappa = a/(a^2+b^2)\) 、 \(\tau = b/(a^2+b^2)\) 常數對,用 Frenet–Serret ODE(01.2 節)數值求解(scipy.integrate.solve_ivp + 標準正交矩陣演化),重建出的曲線是否為螺旋線(允許剛體旋轉/平移差別)?這是微分幾何基本定理(曲線版)的動手驗證。

小結與練習#

本節重點

  1. 螺旋線 \(\gamma(t)=(a\cos t,a\sin t,bt)\) 的曲率與撓率都是常數: \(\kappa = a/(a^2+b^2)\) 、 \(\tau = b/(a^2+b^2)\) 。
  2. \((\kappa,\tau)\) 是形狀的兩個「旋鈕」: \(b\to 0\) 退化為平面圓( \(\kappa\to 1/a, \tau\to 0\) ), \(a\to 0\) 退化為直線(兩者皆 0)。
  3. \(\tau/\kappa = b/a\) 恆定;由 Lancret 定理,這正是「螺旋性」的代數特徵。
  4. 完整工作流:SymPy 符號推導 → 公式驗證 → lambdify 數值 → matplotlib 3D 視覺化 → 極限與反思。

練習

  1. 證明: \(\kappa^2+\tau^2 = 1/(a^2+b^2)\) ,並解釋它與「速率」 \(\sqrt{a^2+b^2}\) 的關係(提示:Frenet 公式三式平方相加)。
  2. 對數螺旋線的圓柱投影:考慮 \(\gamma(t) = (e^{ct}\cos t, e^{ct}\sin t, bt)\) ,計算 \(\kappa, \tau\) ,觀察它們是否仍為常數;若不是, \(c=0\) 時呢?
  3. 求使螺旋線「曲率與撓率相等」( \(\kappa=\tau\) )的 \(a, b\) 關係,並畫出此情形的 3D 圖。
  4. 用 SymPy 證明螺旋線上任意兩點間的弦長與弧長之比在兩點靠近時趨近 1,並對比直線與一般曲線的行為。
  5. 反思寫作:選一個自然界中的螺旋物件(DNA、貝殼、藤蔓),根據它的形狀估算 \(a, b\) 及對應的 \(\kappa, \tau\) ,並討論「為什麼自然界選了這組參數」。這是開放題,重點是把幾何量連結到物理/生物約束。