2.2 有理式與化簡#
本節目標#
- 認識 SymPy 中的有理函數(分式)表示法
- 熟練
cancel()約分與together()通分 - 學會
apart()部分分式分解,為積分鋪路 - 了解
simplify()的定位:何時用通用化簡、何時用專用函數 - 用
nsimplify()把浮點數轉回漂亮的分數
從數學開始:有理函數#
有理函數是兩個多項式的比值:
\[ r(x) = \frac{p(x)}{q(x)} \]
處理有理式的三大基本操作:
- 約分:把 \(\dfrac{x^2-1}{x+1}\) 化為 \(x-1\) (消去公因式)
- 通分:把 \(\dfrac{1}{x} + \dfrac{1}{x+1}\) 合併為 \(\dfrac{2x+1}{x(x+1)}\)
- 部分分式分解:把 \(\dfrac{1}{x(x+1)}\) 拆成 \(\dfrac{1}{x} - \dfrac{1}{x+1}\)
部分分式分解在微積分中是積分 \(\int \frac{1}{x(x+1)}\,dx\) 的標準前處理,SymPy 的 integrate() 內部就依賴這個技術。
在 SymPy 中建立有理式,記得用 Rational 或 S() 處理數字,避免 Python 的浮點除法:
from sympy import symbols, Rational, S
x = symbols('x')
r1 = (x**2 - 1) / (x + 1)
r2 = 1/x + 1/(x + 1)
print(r1) # (x**2 - 1)/(x + 1)
print(r2) # 1/x + 1/(x + 1)
cancel():約分#
cancel() 把有理式化成「分子分母互質」的標準型(p/q 形式):
from sympy import cancel
print(cancel((x**2 - 1) / (x + 1)))
# x - 1
print(cancel((x**3 + 2*x**2 + x) / (x**2 + x)))
# x + 1
print(cancel((x**2 + 1) / (x**2 - 1)))
# (x**2 + 1)/((x - 1)*(x + 1)) —— 無公因式,保持原樣
注意:直接做除法 p / q 時 SymPy 會自動做一些局部化簡,但複雜情況下公因式可能殘留,此時 cancel() 就派上用場:
expr = (x**10 - 1) / (x - 1)
print(cancel(expr))
# x**9 + x**8 + ... + x + 1
together():通分#
together() 把多個分式合併成單一分式:
from sympy import together
print(together(1/x + 1/(x + 1)))
# (2*x + 1)/(x*(x + 1))
print(together(1/x + 1/y + 1/(x*y)))
# (x + y + 1)/(x*y)
together() 與 apart() 大致互為逆操作(見後面實驗)。
apart():部分分式分解#
apart() 把有理式拆成部分分式:
from sympy import apart
print(apart(1 / (x*(x + 1))))
# 1/x - 1/(x + 1)
print(apart(x**2 / (x**2 - 1)))
# 1 + 1/(2*(x - 1)) - 1/(2*(x + 1))
print(apart(1 / (x**2 + 2*x + 1)))
# 1/(x + 1)**2 —— 重根也沒問題
為微積分鋪路#
部分分式是積分前處理的標準手法。先手動 apart,再積分:
from sympy import integrate, Symbol
t = Symbol('t')
f = 1 / (t*(t - 3))
print(apart(f))
# -1/(3*t) + 1/(3*(t - 3))
print(integrate(f, t))
# log(t - 3)/3 - log(t)/3
apart() 也能對其他變數分解:
y = symbols('y')
print(apart((y + 1) / (y**3 + y), y))
# (y + 1)/(y*(y**2 + 1)) —— 若在實域不可約則保持
simplify() 的定位:何時用它,何時用專用函數#
simplify() 是「萬用」化簡器:它會嘗試多種策略(約分、合併、三角化簡、指數律……)並回傳結果最短的版本。
from sympy import simplify
print(simplify((x**2 - 1) / (x + 1))) # x - 1
print(simplify(sin(x)**2 + cos(x)**2)) # 1(下一節詳談)
但通用化的代價是:
- 結果不可預測:同一個輸入可能得到多種「等價但形式不同」的答案
- 速度慢:要嘗試多種演算法
- 可能過度化簡:把你想保留的結構(例如 \(\frac{1}{x} + \frac{1}{x+1}\) 的項)合併掉
實務準則:
| 需求 | 用什麼 |
|---|---|
| 有理式約分 | cancel() |
| 合併分式 | together() |
| 部分分式 | apart() |
| 展開 | expand() |
| 因式分解 | factor() |
| 不確定、要「看起來最乾淨」 | simplify() |
from sympy import expand
expr = (x + 1)**3 / (x + 1)
print(simplify(expr)) # x**2 + 2*x + 1
print(cancel(expr)) # x**2 + 2*x + 1(更快、更可控)
nsimplify():把小數變分數#
從數值計算或外部資料進來的浮點數,常需要轉回精確的有理數或常數表示:
from sympy import nsimplify, pi, exp
print(nsimplify(0.5)) # 1/2
print(nsimplify(0.333333)) # 1/3
print(nsimplify(3.1415926535)) # pi
print(nsimplify(2.718281828)) # E
nsimplify() 甚至能從數值「猜」出公式:
print(nsimplify(4.9348022005, [pi])) # pi**2/2
print(nsimplify(1.0 + 1.7320508, [pi, exp(1)]))
在互動探索中,nsimplify 是連接「數值觀察」與「符號證明」的橋樑:先用浮點數看出規律,再用符號運算證明。
動手實驗:把 \(\frac{1}{x(x+1)}\) apart 回去驗證#
目標:驗證 apart() 與 together() 互為逆操作,並觀察 simplify() 在這個問題上的行為。
from sympy import symbols, apart, together, simplify, cancel, expand
x = symbols('x')
f = 1 / (x*(x + 1))
# 步驟 1:部分分式分解
pf = apart(f)
print(pf) # 1/x - 1/(x + 1)
# 步驟 2:通分合併回去
g = together(pf)
print(g) # (2*x + 1 ... )
print(g) # 觀察合併結果
# 步驟 3:驗證恆等(兩種方法)
print(simplify(g - f)) # 0
print(expand((g - f).as_numer_denom()[0])) # 0(分子為 0)
輸出:
1/x - 1/(x + 1)
(2*x + 1)/(x*(x + 1))
0
0
實驗延伸:
- 換成 \(\frac{1}{x(x+1)(x+2)}\) ,做
apart()後together()回去,驗證恆等。 - 對 \(\frac{x^3}{x^2 - 1}\) 做
apart(),觀察多項式部分(假分式會拆出 \(x\) 項)。 - 試試
apart(1/(x**3 + 1))——在三階不可約因子上會發生什麼?加上full=True參數再試一次,比較結果。 - 用
nsimplify猜猜 \(1.4676908375\) 與 \(\pi\) 的關係(提示: \(\frac{\pi}{2}\) 附近)。
小結與練習#
本節學會了:
cancel()約分、together()通分、apart()部分分式分解- 驗證兩個有理式恆等:
simplify(g - f)或檢查分子為零 simplify()的定位:通用但慢且不可控;優先用專用函數nsimplify()把浮點數還原為精確數學常數
練習:
- 對 \(\dfrac{2x+3}{(x-1)(x+2)}\) 做
apart(),並手算驗證係數。 - 驗證:
together(apart(1/(x**2 - 1)))經cancel()後回到原式。 - 用
apart()與integrate()求 \(\int \frac{3}{x^2 + 3x + 2}\,dx\) 。 - 用
nsimplify()找出 \(0.6931471805\) 、 \(1.4426950409\) 各對應什麼常數(提示: \(\ln 2\) 與 \(\log_2 e\) )。