第 16 章 電腦代數系統的應用策略(以 SymPy 為例)¶
本章重點:
- 認識「電腦代數系統」(Computer Algebra System, CAS)與 SymPy 在其中的角色。
- 了解 CAS 的優點與限制:哪些事適合交給電腦做?哪些事仍需人來思考?
- 建構一套「用 SymPy 解題」的基本流程與策略。
- 練習用 SymPy 協助處理代數、微積分、方程、數列、機率、幾何等問題。
- 將 SymPy 與數值計算、繪圖結合,做參數探索與模型實驗。
- 思考「如何使用 CAS 來學好數學」,而不是「讓 CAS 代替你學數學」。
本章比較偏向「方法論」與「工具使用哲學」,適合在通識課最後一章作為總結與延伸。
16.1 什麼是電腦代數系統?¶
電腦代數系統(Computer Algebra System, CAS) 是一類可以做「符號運算」的軟體, 例如:
- 代數式簡化、展開、因式分解。
- 解方程、解聯立方程。
- 微分與積分(符號而非近似數值)。
- 矩陣與線性代數運算。
- 特定的特殊函數、極限、級數展開⋯⋯
常見的 CAS 有:Mathematica、Maple、wxMaxima、SageMath、SymPy 等。
在這本書中,我們主要使用 SymPy:
- 它是 Python 生態的一部分,易於與數值計算、繪圖、資料分析整合。
- 開源、跨平台,適合教學與自學。
import sympy as sp
sp.init_printing()
x, y = sp.symbols('x y')
expr = (x**2 - 1)/(x - 1)
sp.simplify(expr)
這個例子中,SymPy 幫我們做了代數式的「約分」,
卻仍然保留 x ≠ 1 這個隱含條件(因為原式在 x=1 不定義)。
重點: CAS 會做「形式運算」,但對定義域與上下文的理解,需要使用者自己把關。
16.2 CAS 的優點與限制¶
優點:¶
- 能處理複雜、不易手算的推導與化簡。
- 可以快速嘗試多種形式的等價變形,幫助發現模式。
- 容易做參數掃描與數值實驗(例如改變初始條件,看看解怎麼變)。
- 降低「計算失誤」的風險,讓你把注意力集中在「建模與解讀」。
限制與風險:¶
- 若不了解背後數學,很容易「盲信結果」而看不出不合理之處。
- CAS 可能輸出多個解、含參數的解,需要人來判斷哪些才符合問題情境。
- 某些表達式在符號上「太難解」,CAS 也可能只給近似數值或保留原式。
- 計算資源有限,大型問題或高維度符號推導可能非常慢或無法完成。
因此,理想的使用方式 是:
讓 CAS 做「熟練的計算助理」,而不是「代替你思考的老師」。
16.3 一般解題流程:人機分工¶
面對一個數學應用題時,可以嘗試以下流程:
閱讀與理解題目(人)
- 搞清楚問題要問什麼、有哪些未知數、有哪些條件。
- 先用自然語言描述問題。
建立數學模型(人)
- 決定用哪種數學工具:代數方程?微分方程?機率模型?
- 寫下相對應的方程式或關係式。
轉為 SymPy 形式(人 + CAS)
- 定義符號、函數。
- 用 SymPy 表達出上一步的數學式。
利用 CAS 計算與化簡(CAS)
solve,simplify,diff,integrate,limit等。- 若符號解太複雜,可改用數值近似。
解讀與檢查結果(人)
- 解是否合理?有無違反條件(如負長度、除以 0)?
- 是否所有解都要?需不需要排除某些解?
- 嘗試帶回原題目檢查。
x = sp.symbols('x')
expr = x**3 - 3*x**2 + 2*x
solutions = sp.solve(sp.Eq(expr, 0), x)
solutions
你可以把這些解代回原式,確認真的都使方程成立:
[sp.simplify(expr.subs(x, s)) for s in solutions]
x = sp.symbols('x')
f = x*sp.exp(x)
f_prime = sp.diff(f, x)
sp.simplify(f_prime)
藉由 SymPy,我們可以把「計算是否做對」這件事交給電腦, 自己專心在理解「為什麼要這樣微分」。
x, y = sp.symbols('x y', real=True)
eq1 = sp.Eq(x + y, 500)
eq2 = sp.Eq(0.08*x + 0.03*y, 0.06*500)
solution = sp.solve((eq1, eq2), (x, y))
solution
結果(通常是有理數或浮點數)。
你可以將解帶回原方程,確認體積與糖量條件都滿足。
這個流程可以用在許多「守恆量」問題:
- 潔淨水與鹽水混合。
- 資金或人口的流入流出平衡。
- 物理中的動量守恆等。
16.6 數值求解與符號求解:nsolve vs solve¶
solve嘗試給出符號解(Exact symbolic solution)。nsolve給出數值解,通常需要初始猜測值。
例:解方程 $\sin x = x/2$。
這個方程不容易寫成「閉合形式解」,我們可以用數值方法。
x = sp.symbols('x')
f = sp.sin(x) - x/2
# 使用 nsolve 需要提供初始猜測值,例如 x=1
root_approx = sp.nsolve(f, 1)
root_approx
你可以畫圖或代入檢查:
sp.N(sp.sin(root_approx) - root_approx/2, 10)
一般策略:
- 若問題的結構相對簡單,先試
solve。 - 若表達式複雜或
solve給不出 Closed-form 解,改用nsolve搭配初始猜測。 - 可先用
sp.plot畫圖,了解根的大致位置,再決定初始值。
16.7 結合符號與數值:lambdify 與繪圖¶
SymPy 中的符號表達式可以轉換成「純數值」的 Python 函數,
方便用於數值計算或繪圖,這可以透過 sp.lambdify 達成。
例:研究函數 $f(x) = x^3 - 3x$ 的圖形與特性。
x = sp.symbols('x')
f_expr = x**3 - 3*x
f_prime_expr = sp.diff(f_expr, x)
f_expr, f_prime_expr
將符號函數轉為數值函數,並用 matplotlib 畫圖:
import numpy as np
import matplotlib.pyplot as plt
f_num = sp.lambdify(x, f_expr, 'numpy')
xs = np.linspace(-3, 3, 400)
ys = f_num(xs)
plt.plot(xs, ys)
plt.axhline(0, color='black')
plt.axvline(0, color='black')
plt.title('f(x) = x^3 - 3x')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid(True)
plt.show()
這樣的流程可以應用在:
- 先用 SymPy 做符號微分、解極值點。
- 再用數值方式畫圖、跑參數,觀察函數行為。
16.8 啟發性例子一:拋物線拋射運動(物理)¶
考慮一個物體以初速度 $v_0$、仰角 $\theta$(相對水平)拋出, 忽略空氣阻力,重力加速度為 $g$。
標準模型(原點為拋出點):
$$ \begin{aligned} x(t) &= v_0 \cos\theta \cdot t,\\ y(t) &= v_0 \sin\theta \cdot t - \frac{1}{2}gt^2. \end{aligned} $$
想問:
- 最大高度是多少?在什麼時間達到?
- 水平射程(落地時的水平距離)是多少?
我們可以用 SymPy 幫忙推導。
t, v0, g, theta = sp.symbols('t v0 g theta', positive=True)
x_t = v0*sp.cos(theta)*t
y_t = v0*sp.sin(theta)*t - g*t**2/2
x_t, y_t
最大高度¶
對 $y(t)$ 微分並令導數為 0,求出頂點時間:
dy_dt = sp.diff(y_t, t)
t_peak = sp.solve(sp.Eq(dy_dt, 0), t)[0]
y_peak = sp.simplify(y_t.subs(t, t_peak))
dy_dt, t_peak, y_peak
t_solutions = sp.solve(sp.Eq(y_t, 0), t)
t_solutions
在這兩個解中,$t=0$ 是起始時刻,另一個正解是落地時間:
t_flight = sp.simplify([sol for sol in t_solutions if sol != 0][0])
range_expr = sp.simplify(x_t.subs(t, t_flight))
t_flight, range_expr
透過 SymPy,你可以:
- 檢查課本上的拋物線拋射公式推導。
- 用數值代入(例如 $v_0=10$ m/s, $g=9.8$ m/s², $\theta=45°$)比較理論與模擬。
16.9 啟發性例子二:數學建模小實驗(成本與收益)¶
假設某產品的:
- 價格為 $p$(每單位)。
- 需求量約為 $D(p) = 1000 - 20p$(單位:件)。
- 總成本約為 $C(q) = 2000 + 10q$,其中 $q$ 為產量(件)。
在簡化模型下,假設產量 = 銷量 = $D(p)$。
則:
- 收入:$R(p) = p \cdot D(p)$。
- 利潤:$\Pi(p) = R(p) - C(D(p))$。
我們想找「使利潤最大的價格」。
用 SymPy 寫出來並做優化:
p = sp.symbols('p', real=True)
D = 1000 - 20*p
C = 2000 + 10*D
R = p*D
Pi = sp.simplify(R - C)
D, C, R, Pi
求使利潤最大的價格(求導數為 0):
dPi_dp = sp.diff(Pi, p)
critical_p = sp.solve(sp.Eq(dPi_dp, 0), p)
critical_p
計算此時的需求量與利潤:
p_star = critical_p[0]
D_star = D.subs(p, p_star)
Pi_star = Pi.subs(p, p_star)
sp.simplify(D_star), sp.simplify(Pi_star)
接著你可以畫出 $\Pi(p)$ 的圖形,觀察在不同售價下利潤如何變化:
import numpy as np
import matplotlib.pyplot as plt
Pi_num = sp.lambdify(p, Pi, 'numpy')
ps = np.linspace(0, 60, 200)
Pis = Pi_num(ps)
plt.plot(ps, Pis)
plt.axvline(float(p_star), color='red', linestyle='--', label='optimal p')
plt.xlabel('Price p')
plt.ylabel('Profit Pi(p)')
plt.legend()
plt.grid(True)
plt.title('Profit vs Price')
plt.show()
這個例子展示了:
- 從文字敘述 → 數學模型 → SymPy 符號式。
- 用微分找極值點,再用圖形輔助理解。
16.10 啟發性例子三:數列與極限的探索¶
考慮數列:
$$a_n = \left(1 + \frac{1}{n}\right)^n.$$
這是常見的「自然常數 e 的極限定義」之一,我們可以用 SymPy 探索:
- 計算一些 $a_n$ 的數值。
- 觀察其極限是否接近 $e$。
n = sp.symbols('n', positive=True, integer=True)
a_n = (1 + 1/n)**n
limit_expr = sp.limit(a_n, n, sp.oo)
a_n, limit_expr
也可以自己寫一小段數值迴圈,看 $a_n$ 收斂的情況:
import math
for N in [1, 2, 5, 10, 50, 100, 500, 1000]:
val = (1 + 1/N)**N
print(N, val)
math.e
1 2.0 2 2.25 5 2.4883199999999994 10 2.5937424601000023 50 2.691588029073608 100 2.7048138294215285 500 2.715568520651728 1000 2.7169239322355936
SymPy 幫我們做了極限運算;Python 的數值實驗則給了我們「接近的感覺」。
這種「符號 + 數值 + 直觀」的組合,是 CAS 很適合扮演的角色。
16.11 使用 CAS 的學習策略與注意事項¶
建議的使用方式¶
先自己想,再丟給 CAS
- 嘗試用紙筆或心算想出大致方向或部分步驟。
- 將 CAS 視為「驗算工具」與「實驗平台」。
把 CAS 當作探索工具
- 改變參數值,看看解的行為如何變化。
- 用圖形、數值表輔助理解定理與公式。
學會解讀錯誤訊息與奇怪的輸出
- 有時解出來是複數或含參數的解,要回到題目情境判斷合理性。
- 若出現 "no explicit solution found",考慮改用數值方法或改寫模型。
寫出乾淨的 Notebook
- 把文字說明、數學式與程式碼混合在同一份 Notebook,形成可閱讀的「解題過程」。
- 這樣的文件比只寫計算結果更有價值。
避免的陷阱¶
- 不要只記得鍵入指令,卻忘了理解背後的數學。
- 不要完全相信 CAS:結果有時可能不符合問題設定(例如定義域限制)。
- 不要把 CAS 當作「作弊工具」,而是當作「協作夥伴」。
16.12 本章小結¶
本章你學到了:
- 電腦代數系統(CAS)的概念與 SymPy 在 Python 生態中的角色。
- 一般解題流程中的人機分工:理解題目與建模仍由人主導。
- 用 SymPy 檢查代數與微積分計算、解聯立方程、做符號與數值混合分析。
- 使用
solve,nsolve,lambdify,plot等工具建構從模型到圖形的完整流程。 - 透過物理拋射、成本收益、數列極限等例子,體會 CAS 如何輔助不同領域。
- 使用 CAS 的學習策略與注意事項,避免「盲信」或「過度依賴」。
未來無論你是否走向理工、財經、社會科學或其他領域, 善用這類工具都能大幅降低計算負擔,讓你更專注在概念與創意。
16.13 練習題¶
請在本 Notebook 中新增儲存格,試著完成以下練習。重點不只是得到答案, 也請用文字(Markdown)簡短說明你如何建模、如何解讀結果。
解題+檢查:二次方程
題目:解方程 $2x^2 - 3x - 2 = 0$。
(a) 用紙筆或心算寫出解的公式。
(b) 用 SymPy 的solve計算解。
(c) 將解代回原方程,用 SymPy 檢查是否為 0。
(d) 用文字說明:你覺得在這個問題中,CAS 扮演了什麼角色?聯立方程的應用:票價問題
某活動售票有兩種票:學生票與全票。已知:- 總共賣出 120 張票,收入 10200 元。
- 全票一張 100 元,學生票一張 80 元。
(a) 設未知數並寫出聯立方程。
(b) 用 SymPy 求出各賣出多少張。
(c) 用文字說明:如果你手算不小心算錯,SymPy 如何幫你發現?
數值解與圖形:解 $\cos x = x$
(a) 寫出方程 $\cos x = x$,用nsolve找到靠近 0.5 的解。
(b) 用sp.plot或 matplotlib 畫出 $y=\cos x$ 與 $y=x$,標出交點位置。
(c) 用文字說明:為什麼這類方程不容易用代數方法解?拋物線拋射參數實驗
使用第 16.8 節的拋射運動模型:
(a) 固定 $v_0=10$ m/s, $g=9.8$,讓 $\theta$ 從 10° 變到 80°,每 5° 計算一次水平射程。
(b) 找出射程最大的角度(從數值上觀察)。
(c) 用圖形顯示射程隨角度變化,並與課本中「45° 射程最大」的說法對照。
(提示:np.linspace+lambdify。)成本與利潤模型的延伸
在第 16.9 節的模型中,假設成本函數改為 $C(q) = 3000 + 8q + 0.01q^2$。
(a) 重新寫出新的利潤函數 $\Pi(p)$。
(b) 使用diff與solve找出最適價格。
(c) 比較新舊模型下的最適價格與最大利潤,討論成本結構改變對定價策略的影響。數列與極限探索
(a) 定義數列 $a_n = (1 + 2/n)^n$,用 SymPy 求 $\lim_{n\to\infty} a_n$。
(b) 寫一段 Python 程式計算 $n=1,2,5,10,50,100,500,1000$ 時的數值。
(c) 將結果與 SymPy 的極限比較,寫下你對「收斂」的直觀理解。加分題:做一份「CAS 解題報告」
從高中或大學一年級的課本中選一題你覺得有趣或具挑戰性的題目:
(a) 在 Notebook 中完整寫出題目。
(b) 先用紙筆或 Markdown 說明你的解題想法。
(c) 用 SymPy 實作關鍵步驟(如求導、解方程、化簡等)。
(d) 用圖形、數值實驗等方式幫助解釋結果。
(e) 最後寫一小段反思:這次使用 CAS 的經驗,有沒有幫助你更理解這個題目?為什麼?