跳轉到

2.4 等式與假設系統#

本節目標#

  • 理解 SymPy 中 = 只是賦值、== 是結構比較,真正的「數學等式」要用 Eq
  • 學會用 solve() 解 Eq 形式的方程
  • 掌握假設系統:Symbol('x', positive=True) 宣告符號性質
  • 學會 ask() 詢問性質、refine() 利用假設化簡
  • 體會假設如何讓 simplify() 變聰明( \(\sqrt{x^2} = x\) 需要 \(x \ge 0\) )

從數學開始:等式與符號的性質#

數學中寫 \(x^2 = 4\) 是一個「命題」——可以為真或為假。但在 Python 裡:

  • x = 4 是賦值:把 4 綁到變數名 x
  • x == 4 是結構比較:回傳 Python 的 True/False
  • SymPy 表達式之間的 == 比較的是語法樹,不是數學相等:
from sympy import Symbol

x = Symbol('x')
print((x + 1)**2 == x**2 + 2*x + 1)   # False!語法樹不同

即使兩式數學上恆等,== 也回傳 False。所以 SymPy 提供 Eq(a, b) 來表達數學等式 \(a = b\) 。

另一個重要觀念:符號是有「身分證」的。數學上我們說「 \(x > 0\) 」來限定符號的範圍,SymPy 的假設系統就是在 Symbol 物件上記錄這些性質。

Eq:表達數學等式#

from sympy import Eq, symbols, sin

x = symbols('x')
eq = Eq(x**2, 4)
print(eq)          # Eq(x**2, 4)
print(eq.lhs)      # x**2(左邊)
print(eq.rhs)      # 4(右邊)

Eq 是一個 SymPy 物件,可以當作 solve()、dsolve() 的輸入:

from sympy import solve

print(solve(Eq(x**2, 4), x))       # [-2, 2]
print(solve(Eq(sin(x), S(1)/2), x))
# [pi/6, 5*pi/6]

不傳 Eq 時,solve(expr, x) 預設解 expr = 0,兩種寫法等價:

print(solve(x**2 - 4, x))          # [-2, 2]

假設系統:Symbol 的身分證#

宣告符號時可以附加假設:

from sympy import Symbol, sqrt

x = Symbol('x')                      # 無假設:x 可以是任何複數
p = Symbol('x', positive=True)       # x > 0
n = Symbol('n', integer=True)        # n 是整數
r = Symbol('r', real=True)           # r 是實數

假設立刻影響化簡行為。經典例子: \(\sqrt{x^2}\) 。

\[ \sqrt{x^2} = |x| = \begin{cases} x, & x \ge 0 \\ -x, & x < 0 \end{cases} \]

所以在「不知正負」時不能化簡;在「 \(x \ge 0\) 」時才能寫成 \(x\) :

print(sqrt(x**2))    # sqrt(x**2),不動
print(sqrt(p**2))    # x(因為 p 是正的!)

這不是 SymPy 挑剔,而是數學上的嚴謹:若 \(x = -3\) ,則 \(\sqrt{x^2} = 3 \ne -3\) 。

ask():詢問符號的性質#

ask() 對假設系統提問,回傳 True、False 或 None(無法判斷):

from sympy import ask, Q

x = Symbol('x', positive=True)
y = Symbol('y')

print(ask(Q.positive(x)))        # True
print(ask(Q.positive(y)))        # None(不知道)
print(ask(Q.integer(x)))         # False
print(ask(Q.positive(x + 1)))    # True(可由 x>0 推出 x+1>0)
print(ask(Q.even(2*x)))          # None?—— 若 x 是整數則是

搭配假設能做邏輯推理:

n = Symbol('n', integer=True)
print(ask(Q.even(2*n)))          # True(2n 必為偶數)
print(ask(Q.odd(n**2)))          # None(奇偶未知)
k = Symbol('k', odd=True)
print(ask(Q.even(k**2 + 1)))     # True(奇平方+1 必為偶)

refine():利用假設化簡表達式#

refine() 依假設把表達式化成更精確的形式:

from sympy import refine, Abs, sqrt

r = Symbol('r', real=True)
print(refine(Abs(r)**2, Q.positive(r)))   # r**2
print(refine(sqrt(r**2), Q.positive(r)))  # r

沒有足夠假設時,refine() 保持原樣——這正是保守而正確的行為。

假設讓 simplify() 變聰明#

比較同一個表達式在不同假設下的化簡結果:

from sympy import simplify, sqrt, log, exp, Symbol

xa = Symbol('x')                          # 無假設
xp = Symbol('x', positive=True)           # x > 0

print(simplify(sqrt(xa**2)))              # sqrt(x**2),不動
print(simplify(sqrt(xp**2)))              # x

print(simplify(log(exp(xa))))             # exp(x)(複數域上有歧義,保守)
print(simplify(log(exp(xp))))             # x

背後機制:simplify() 及各專用函數在化簡前會查詢假設系統,若化簡步驟「在給定假設下恆成立」才執行。這讓同一行程式碼能表現出數學上嚴謹的多種行為。

常用的假設關鍵字:positive、negative、nonnegative、real、integer、even、odd、rational、nonzero。

動手實驗:不同假設下 \(\sqrt{x^2 y^2}\) 的化簡結果#

from sympy import Symbol, sqrt, simplify, refine, Q

# 情況 1:x, y 都無假設
x = Symbol('x')
y = Symbol('y')
print(simplify(sqrt(x**2 * y**2)))
# sqrt(x**2*y**2)(不動)

# 情況 2:只有 x 為正
xp = Symbol('x', positive=True)
print(simplify(sqrt(xp**2 * y**2)))
# y*sqrt(x**2)?—— 觀察 y 仍未化簡

# 情況 3:x, y 都為正
yp = Symbol('y', positive=True)
print(simplify(sqrt(xp**2 * yp**2)))
# x*y

# 情況 4:x, y 都是實數(但不知正負)
xr = Symbol('x', real=True)
yr = Symbol('y', real=True)
print(simplify(sqrt(xr**2 * yr**2)))
# 不動:實數但可能為負,sqrt(x^2)=|x| 而非 x

# 用 refine 與 ask 進一步探究
print(refine(sqrt(xr**2), Q.positive(xr)))   # x
print(ask(Q.positive(sqrt(xr**2)), Q.nonnegative(xr)))  # True

觀察重點:

  1. 兩變數都正時,才能完全化成 \(x \cdot y\)
  2. 只有一個正時,另一個保持保守
  3. 「實數」假設不足以化成 \(xy\) ,但足以保證 \(\sqrt{x^2y^2} \ge 0\)

實驗延伸:

  1. 把 y 宣告為 negative=True, \(\sqrt{x^2 y^2}\) 會化成什麼?(提示:應為 \(-xy\) ?還是不動?思考後再執行驗證)
  2. 用 ask 驗證:若 \(x\) 為正整數,則 \(x^2 + 1\) 是否必為正、是否可能為偶數?
  3. 對 \(\log(x^2)\) 分別在「無假設」「正數」「非零實數」假設下做 expand,比較結果。
  4. 建構一個例子:某化簡在無假設時錯誤地給出結果、加了假設後才正確——試著用 \(\sqrt{x^2}\) 的數值代入 \(x=-3\) 說明為什麼保守是必要的。

小結與練習#

本節學會了:

  • = 是賦值、== 比較語法樹、數學等式用 Eq;solve() 可直接吃 Eq
  • 假設系統:宣告 Symbol(..., positive=True) 等性質,讓符號帶數學身分
  • ask(Q.positive(...), ...) 詢問性質,refine() 依假設化簡
  • 假設讓 simplify() 嚴謹地變聰明: \(\sqrt{x^2} = x\) 只在 \(x \ge 0\) 時成立

練習:

  1. 用 Eq 與 solve 解 \(x^3 - 3x + 1 = 0\) ,觀察根的形式(會出現三角函數表示)。
  2. 宣告 n = Symbol('n', integer=True, positive=True),用 ask 驗證 \(n(n+1)\) 恆為偶數。
  3. 分別在 x 為正、負、實數三種假設下化簡 \(\sqrt{x^2} \cdot \sqrt{y^2}\) ,整理成表。
  4. 用 refine 化簡 \(\cos^2 x + \sin^2 x\) (提示:搭配 Q.real(x)),觀察與 simplify 的差異。