第 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 生態的一部分,易於與數值計算、繪圖、資料分析整合。
  • 開源、跨平台,適合教學與自學。
In [1]:
import sympy as sp
sp.init_printing()

x, y = sp.symbols('x y')
expr = (x**2 - 1)/(x - 1)
sp.simplify(expr)
Out[1]:
$\displaystyle x + 1$

這個例子中,SymPy 幫我們做了代數式的「約分」, 卻仍然保留 x ≠ 1 這個隱含條件(因為原式在 x=1 不定義)。

重點: CAS 會做「形式運算」,但對定義域與上下文的理解,需要使用者自己把關。

16.2 CAS 的優點與限制¶

優點:¶

  • 能處理複雜、不易手算的推導與化簡。
  • 可以快速嘗試多種形式的等價變形,幫助發現模式。
  • 容易做參數掃描與數值實驗(例如改變初始條件,看看解怎麼變)。
  • 降低「計算失誤」的風險,讓你把注意力集中在「建模與解讀」。

限制與風險:¶

  • 若不了解背後數學,很容易「盲信結果」而看不出不合理之處。
  • CAS 可能輸出多個解、含參數的解,需要人來判斷哪些才符合問題情境。
  • 某些表達式在符號上「太難解」,CAS 也可能只給近似數值或保留原式。
  • 計算資源有限,大型問題或高維度符號推導可能非常慢或無法完成。

因此,理想的使用方式 是:

讓 CAS 做「熟練的計算助理」,而不是「代替你思考的老師」。

16.3 一般解題流程:人機分工¶

面對一個數學應用題時,可以嘗試以下流程:

  1. 閱讀與理解題目(人)

    • 搞清楚問題要問什麼、有哪些未知數、有哪些條件。
    • 先用自然語言描述問題。
  2. 建立數學模型(人)

    • 決定用哪種數學工具:代數方程?微分方程?機率模型?
    • 寫下相對應的方程式或關係式。
  3. 轉為 SymPy 形式(人 + CAS)

    • 定義符號、函數。
    • 用 SymPy 表達出上一步的數學式。
  4. 利用 CAS 計算與化簡(CAS)

    • solve, simplify, diff, integrate, limit 等。
    • 若符號解太複雜,可改用數值近似。
  5. 解讀與檢查結果(人)

    • 解是否合理?有無違反條件(如負長度、除以 0)?
    • 是否所有解都要?需不需要排除某些解?
    • 嘗試帶回原題目檢查。

16.4 範例:用 SymPy 重新檢查代數與微積分答案¶

例 1:代數方程檢查¶

題目:解方程 $x^3 - 3x^2 + 2x = 0$。

手算:

$x(x^2 - 3x + 2) = x(x-1)(x-2)$,解得 $x=0,1,2$。

用 SymPy 檢查:

In [2]:
x = sp.symbols('x')
expr = x**3 - 3*x**2 + 2*x
solutions = sp.solve(sp.Eq(expr, 0), x)
solutions
Out[2]:
$\displaystyle \left[ 0, \ 1, \ 2\right]$

你可以把這些解代回原式,確認真的都使方程成立:

In [3]:
[sp.simplify(expr.subs(x, s)) for s in solutions]
Out[3]:
$\displaystyle \left[ 0, \ 0, \ 0\right]$

例 2:微分計算檢查¶

題目:求 $f(x) = x e^x$ 的導數。

手算用乘法法則: $f'(x) = e^x + x e^x = (1+x)e^x$。

用 SymPy:

In [4]:
x = sp.symbols('x')
f = x*sp.exp(x)
f_prime = sp.diff(f, x)
sp.simplify(f_prime)
Out[4]:
$\displaystyle \left(x + 1\right) e^{x}$

藉由 SymPy,我們可以把「計算是否做對」這件事交給電腦, 自己專心在理解「為什麼要這樣微分」。

16.5 範例:方程建模與解聯立方程¶

問題情境:¶

某兩種飲料 A、B 混合成一杯 500 mL 的飲料,

  • A 飲料含糖濃度為 8%。
  • B 飲料含糖濃度為 3%。
  • 混合後的飲料含糖濃度為 6%。

問:A 與 B 各用了多少 mL?

建模:¶

  • 設 A 用量 $x$ mL,B 用量 $y$ mL。
  • 體積守恆:$x + y = 500$。
  • 糖量守恆:$0.08x + 0.03y = 0.06\times 500$。

用 SymPy 解聯立方程:

In [5]:
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
Out[5]:
$\displaystyle \left\{ x : 300.0, \ y : 200.0\right\}$

結果(通常是有理數或浮點數)。

你可以將解帶回原方程,確認體積與糖量條件都滿足。

這個流程可以用在許多「守恆量」問題:

  • 潔淨水與鹽水混合。
  • 資金或人口的流入流出平衡。
  • 物理中的動量守恆等。

16.6 數值求解與符號求解:nsolve vs solve¶

  • solve 嘗試給出符號解(Exact symbolic solution)。
  • nsolve 給出數值解,通常需要初始猜測值。

例:解方程 $\sin x = x/2$。

這個方程不容易寫成「閉合形式解」,我們可以用數值方法。

In [6]:
x = sp.symbols('x')
f = sp.sin(x) - x/2

# 使用 nsolve 需要提供初始猜測值,例如 x=1
root_approx = sp.nsolve(f, 1)
root_approx
Out[6]:
$\displaystyle 1.89549426703398$

你可以畫圖或代入檢查:

In [7]:
sp.N(sp.sin(root_approx) - root_approx/2, 10)
Out[7]:
$\displaystyle 0$

一般策略:

  • 若問題的結構相對簡單,先試 solve。
  • 若表達式複雜或 solve 給不出 Closed-form 解,改用 nsolve 搭配初始猜測。
  • 可先用 sp.plot 畫圖,了解根的大致位置,再決定初始值。

16.7 結合符號與數值:lambdify 與繪圖¶

SymPy 中的符號表達式可以轉換成「純數值」的 Python 函數, 方便用於數值計算或繪圖,這可以透過 sp.lambdify 達成。

例:研究函數 $f(x) = x^3 - 3x$ 的圖形與特性。

In [8]:
x = sp.symbols('x')
f_expr = x**3 - 3*x
f_prime_expr = sp.diff(f_expr, x)
f_expr, f_prime_expr
Out[8]:
$\displaystyle \left( x^{3} - 3 x, \ 3 x^{2} - 3\right)$

將符號函數轉為數值函數,並用 matplotlib 畫圖:

In [9]:
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()
No description has been provided for this image

這樣的流程可以應用在:

  • 先用 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 幫忙推導。

In [10]:
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
Out[10]:
$\displaystyle \left( t v_{0} \cos{\left(\theta \right)}, \ - \frac{g t^{2}}{2} + t v_{0} \sin{\left(\theta \right)}\right)$

最大高度¶

對 $y(t)$ 微分並令導數為 0,求出頂點時間:

In [11]:
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
Out[11]:
$\displaystyle \left( - g t + v_{0} \sin{\left(\theta \right)}, \ \frac{v_{0} \sin{\left(\theta \right)}}{g}, \ \frac{v_{0}^{2} \sin^{2}{\left(\theta \right)}}{2 g}\right)$

這樣就得到最大高度的公式。

水平射程¶

求落地時間:令 $y(t)=0$,求 $t>0$ 的解,再代回 $x(t)$。

In [12]:
t_solutions = sp.solve(sp.Eq(y_t, 0), t)
t_solutions
Out[12]:
$\displaystyle \left[ \frac{2 v_{0} \sin{\left(\theta \right)}}{g}\right]$

在這兩個解中,$t=0$ 是起始時刻,另一個正解是落地時間:

In [13]:
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
Out[13]:
$\displaystyle \left( \frac{2 v_{0} \sin{\left(\theta \right)}}{g}, \ \frac{v_{0}^{2} \sin{\left(2 \theta \right)}}{g}\right)$

透過 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 寫出來並做優化:

In [14]:
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
Out[14]:
$\displaystyle \left( 1000 - 20 p, \ 12000 - 200 p, \ p \left(1000 - 20 p\right), \ - 20 p^{2} + 1200 p - 12000\right)$

求使利潤最大的價格(求導數為 0):

In [15]:
dPi_dp = sp.diff(Pi, p)
critical_p = sp.solve(sp.Eq(dPi_dp, 0), p)
critical_p
Out[15]:
$\displaystyle \left[ 30\right]$

計算此時的需求量與利潤:

In [16]:
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)
Out[16]:
$\displaystyle \left( 400, \ 6000\right)$

接著你可以畫出 $\Pi(p)$ 的圖形,觀察在不同售價下利潤如何變化:

In [17]:
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()
No description has been provided for this image

這個例子展示了:

  • 從文字敘述 → 數學模型 → SymPy 符號式。
  • 用微分找極值點,再用圖形輔助理解。

16.10 啟發性例子三:數列與極限的探索¶

考慮數列:

$$a_n = \left(1 + \frac{1}{n}\right)^n.$$

這是常見的「自然常數 e 的極限定義」之一,我們可以用 SymPy 探索:

  • 計算一些 $a_n$ 的數值。
  • 觀察其極限是否接近 $e$。
In [18]:
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
Out[18]:
$\displaystyle \left( \left(1 + \frac{1}{n}\right)^{n}, \ e\right)$

也可以自己寫一小段數值迴圈,看 $a_n$ 收斂的情況:

In [19]:
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
Out[19]:
$\displaystyle 2.71828182845905$

SymPy 幫我們做了極限運算;Python 的數值實驗則給了我們「接近的感覺」。

這種「符號 + 數值 + 直觀」的組合,是 CAS 很適合扮演的角色。

16.11 使用 CAS 的學習策略與注意事項¶

建議的使用方式¶

  1. 先自己想,再丟給 CAS

    • 嘗試用紙筆或心算想出大致方向或部分步驟。
    • 將 CAS 視為「驗算工具」與「實驗平台」。
  2. 把 CAS 當作探索工具

    • 改變參數值,看看解的行為如何變化。
    • 用圖形、數值表輔助理解定理與公式。
  3. 學會解讀錯誤訊息與奇怪的輸出

    • 有時解出來是複數或含參數的解,要回到題目情境判斷合理性。
    • 若出現 "no explicit solution found",考慮改用數值方法或改寫模型。
  4. 寫出乾淨的 Notebook

    • 把文字說明、數學式與程式碼混合在同一份 Notebook,形成可閱讀的「解題過程」。
    • 這樣的文件比只寫計算結果更有價值。

避免的陷阱¶

  • 不要只記得鍵入指令,卻忘了理解背後的數學。
  • 不要完全相信 CAS:結果有時可能不符合問題設定(例如定義域限制)。
  • 不要把 CAS 當作「作弊工具」,而是當作「協作夥伴」。

16.12 本章小結¶

本章你學到了:

  • 電腦代數系統(CAS)的概念與 SymPy 在 Python 生態中的角色。
  • 一般解題流程中的人機分工:理解題目與建模仍由人主導。
  • 用 SymPy 檢查代數與微積分計算、解聯立方程、做符號與數值混合分析。
  • 使用 solve, nsolve, lambdify, plot 等工具建構從模型到圖形的完整流程。
  • 透過物理拋射、成本收益、數列極限等例子,體會 CAS 如何輔助不同領域。
  • 使用 CAS 的學習策略與注意事項,避免「盲信」或「過度依賴」。

未來無論你是否走向理工、財經、社會科學或其他領域, 善用這類工具都能大幅降低計算負擔,讓你更專注在概念與創意。

16.13 練習題¶

請在本 Notebook 中新增儲存格,試著完成以下練習。重點不只是得到答案, 也請用文字(Markdown)簡短說明你如何建模、如何解讀結果。

  1. 解題+檢查:二次方程
    題目:解方程 $2x^2 - 3x - 2 = 0$。
    (a) 用紙筆或心算寫出解的公式。
    (b) 用 SymPy 的 solve 計算解。
    (c) 將解代回原方程,用 SymPy 檢查是否為 0。
    (d) 用文字說明:你覺得在這個問題中,CAS 扮演了什麼角色?

  2. 聯立方程的應用:票價問題
    某活動售票有兩種票:學生票與全票。已知:

    • 總共賣出 120 張票,收入 10200 元。
    • 全票一張 100 元,學生票一張 80 元。
      (a) 設未知數並寫出聯立方程。
      (b) 用 SymPy 求出各賣出多少張。
      (c) 用文字說明:如果你手算不小心算錯,SymPy 如何幫你發現?
  3. 數值解與圖形:解 $\cos x = x$
    (a) 寫出方程 $\cos x = x$,用 nsolve 找到靠近 0.5 的解。
    (b) 用 sp.plot 或 matplotlib 畫出 $y=\cos x$ 與 $y=x$,標出交點位置。
    (c) 用文字說明:為什麼這類方程不容易用代數方法解?

  4. 拋物線拋射參數實驗
    使用第 16.8 節的拋射運動模型:
    (a) 固定 $v_0=10$ m/s, $g=9.8$,讓 $\theta$ 從 10° 變到 80°,每 5° 計算一次水平射程。
    (b) 找出射程最大的角度(從數值上觀察)。
    (c) 用圖形顯示射程隨角度變化,並與課本中「45° 射程最大」的說法對照。
    (提示:np.linspace + lambdify。)

  5. 成本與利潤模型的延伸
    在第 16.9 節的模型中,假設成本函數改為 $C(q) = 3000 + 8q + 0.01q^2$。
    (a) 重新寫出新的利潤函數 $\Pi(p)$。
    (b) 使用 diff 與 solve 找出最適價格。
    (c) 比較新舊模型下的最適價格與最大利潤,討論成本結構改變對定價策略的影響。

  6. 數列與極限探索
    (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 的極限比較,寫下你對「收斂」的直觀理解。

  7. 加分題:做一份「CAS 解題報告」
    從高中或大學一年級的課本中選一題你覺得有趣或具挑戰性的題目:
    (a) 在 Notebook 中完整寫出題目。
    (b) 先用紙筆或 Markdown 說明你的解題想法。
    (c) 用 SymPy 實作關鍵步驟(如求導、解方程、化簡等)。
    (d) 用圖形、數值實驗等方式幫助解釋結果。
    (e) 最後寫一小段反思:這次使用 CAS 的經驗,有沒有幫助你更理解這個題目?為什麼?

回首頁