跳轉到

2.1 多項式運算#

本節目標#

  • 理解 SymPy 中符號多項式與 Python 數值運算的差異
  • 熟練使用 expand()、factor() 進行展開與因式分解
  • 學會 collect() 依變數整理多項式
  • 認識 Poly 類,掌握 degree()、coeffs() 等多項式檢視工具
  • 透過 \((x+1)^{10}\) 的展開觀察二項式係數

從數學開始:什麼是多項式運算#

多項式是形如

\[ p(x) = a_n x^n + a_{n-1} x^{n-1} + \cdots + a_1 x + a_0 \]

的表達式。多項式的加減乘都封閉於多項式集合內,但除法會產生有理式(下一節的主題)。

兩個重要的方向性操作:

  • 展開: \((x+1)^2 \to x^2 + 2x + 1\) ,把乘積攤開成標準型(升冪或降冪排列)
  • 因式分解: \(x^2 + 2x + 1 \to (x+1)^2\) ,把標準型還原為乘積

這兩個操作互為逆操作(在可分解的範圍內),是代數運算的基本功。

基本運算:加、減、乘#

SymPy 中用 Symbol 宣告符號後,多項式就像普通的變數一樣做運算:

from sympy import Symbol, expand

x = Symbol('x')
p = x**2 + 3*x + 2
q = x - 5

print(p + q)        # x**2 + 4*x - 3
print(p - q)        # x**2 + 2*x + 7
print(p * q)        # x**3 - 2*x**2 - 13*x - 10

注意:乘出來的結果 SymPy 未必自動展開。上面 p * q 直接印出已展開的樣子,是因為這個例子足夠簡單;遇到 \((x+1)^{10}\) 這類表達式,就需要明確呼叫 expand()。

expr = (x + 1) * (x + 2) * (x + 3)
print(expr)         # (x + 3)*(x + 1)*(x + 2)  —— 保持未展開
print(expand(expr)) # x**3 + 6*x**2 + 11*x + 6

expand():展開#

expand() 會把所有乘積與冪次攤開:

from sympy import symbols, expand

x, y = symbols('x y')

print(expand((x + y)**3))
# x**3 + 3*x**2*y + 3*x*y**2 + y**3

print(expand((x - 2)*(x**2 + 2*x + 4)))
# x**3 - 8

一個經典的驗證:平方差公式與和的平方公式。

from sympy import simplify

lhs1 = expand((x + y)**2) - (x**2 + 2*x*y + y**2)
lhs2 = expand((x + y)*(x - y)) - (x**2 - y**2)
print(lhs1)  # 0
print(lhs2)  # 0

結果為 0 就代表兩邊相等——這是後面章節「驗證恆等式」的基本手法。

factor():因式分解#

factor() 是 expand() 的反向操作:

from sympy import factor

print(factor(x**3 - x))        # x*(x - 1)*(x + 1)
print(factor(x**2 + 2*x + 1))  # (x + 1)**2
print(factor(x**4 - 16))       # (x - 2)*(x + 2)*(x**2 + 4)

整數係數 vs 有理係數#

factor() 預設在有理數域 \(\mathbb{Q}\) 上分解。有些多項式在整數係數下無法分解,但允許有理係數後就可以:

from sympy import Rational

# 整數係數下:x**2 + x + 1 在有理數域不可約
print(factor(x**2 + x + 1))    # x**2 + x + 1(不動)

# 但配合擴充域可以分解(此處僅示意)
from sympy import sqrt
print(factor(x**2 - 2))        # x**2 - 2(根是 ±√2,非有理數)
print(factor(x**2 - 2, extension=sqrt(2)))
# (x - sqrt(2))*(x + sqrt(2))

有理係數的多項式也能正常處理:

p = Rational(1, 2)*x**2 - Rational(3, 2)*x + 1
print(factor(p))   # (x - 2)*(x - 1)/2

collect():依變數整理#

當表達式含多個符號,想把它看成「對 \(x\) 的多項式」整理時,用 collect():

from sympy import collect

a, b = symbols('a b')
expr = a*x**2 + b*x**2 + a*x - b*x + a + b
print(collect(expr, x))
# x**2*(a + b) + x*(a - b) + a + b

collect() 常用在推導過程中,把某個變數的係數結構看清楚。搭配 .coeff() 可以取出特定次方的係數:

collected = collect(expr, x)
print(collected.coeff(x, 2))  # a + b
print(collected.coeff(x, 0))  # a + b

Poly 類:把多項式當物件#

Poly 類把表達式正式轉成「多項式物件」,提供一系列查詢方法:

from sympy import Poly

p = Poly(x**3 - 2*x + 1, x)

print(p.degree())     # 3(最高次方)
print(p.coeffs())     # [1, -2, 1](所有非零係數,由高到低)
print(p.monoms())     # [(3,), (1,), (0,)](對應的次方)
print(p.all_coeffs()) # [1, 0, -2, 1](含零係數的完整係數列)

Poly 很適合用來寫「多項式演算法」,例如判斷是否為多項式、取首項係數:

print(p.is_multivariate())  # False
print(p.LC())               # 1(領導係數)

範例: \((x+1)^{10}\) 與二項式係數#

\((x+1)^n\) 展開後的係數就是二項式係數 \(\binom{n}{k}\) 。用 SymPy 驗證:

from sympy import symbols, expand

x = symbols('x')
p = expand((x + 1)**10)
print(p)
# x**10 + 10*x**9 + 45*x**8 + 120*x**7 + 210*x**6 + 252*x**5
# + 210*x**4 + 120*x**3 + 45*x**2 + 10*x + 1

係數列 \(1, 10, 45, 120, 210, 252, \ldots\) 正是巴斯卡三角形第 10 列。用 Poly 抽出係數與 binomial 對照:

from sympy import Poly, binomial

coeffs = Poly(p, x).all_coeffs()   # 由高次到低次
n = 10
check = [binomial(n, k) for k in range(n, -1, -1)]
print(coeffs == check)             # True

也可以直接驗證一般公式:

k = symbols('k', integer=True)
from sympy import summation

total = summation(binomial(10, k), (k, 0, 10))
print(total)   # 1024 = 2**10,即 (1+1)**10

動手實驗:探討 \((x-1)(x-2)\cdots(x-n)\)#

連乘 \((x-1)(x-2)\cdots(x-n)\) 稱為「降階階乘多項式」。展開後的係數有什麼規律?

from sympy import symbols, expand, Poly, prod

x = symbols('x')

def falling_poly(n):
    return expand(prod(x - k for k in range(1, n + 1)))

for n in range(1, 7):
    p = Poly(falling_poly(n), x)
    print(n, p.all_coeffs())

觀察輸出:

1 [1, -1]
2 [1, -3, 2]
3 [1, -6, 11, -6]
4 [1, -10, 35, -50, 24]
5 [1, -15, 85, -225, 274, -120]
6 [1, -21, 175, -735, 1624, -1764, 720]

可以發現:

  1. 常數項是 \((-1)^n n!\) (因為把 \(x=0\) 代入, \((-1)(-2)\cdots(-n) = (-1)^n n!\) )
  2. 係數正負交錯
  3. 這些係數正是第一類 Stirling 數(帶符號版本)

進一步驗證常數項規律:

from sympy import factorial

n = symbols('n', integer=True, positive=True)
for m in range(1, 8):
    p = Poly(falling_poly(m), x)
    assert p.all_coeffs()[-1] == (-1)**m * factorial(m)
print("常數項規律驗證成功")

實驗延伸:

  • 把 x - k 換成 x + k,係數規律有何改變?
  • 比較 \((x-1)(x-2)\cdots(x-n)\) 與 \(\prod (x-k)\) 在 \(x = n+1\) 的值,你有什麼發現?(提示:階乘)

小結與練習#

本節學會了:

  • expand() 展開、factor() 因式分解(含整數與有理係數的差異)
  • collect() 依變數整理、.coeff() 取係數
  • Poly 類的 degree()、coeffs()、all_coeffs() 等方法
  • 用係數對照驗證數學猜想(二項式係數、Stirling 數)

練習:

  1. 展開 \((x+y+z)^3\) ,並用 collect() 分別對 \(x\) 、 \(y\) 整理,觀察結果差異。
  2. 因式分解 \(x^4 - 5x^2 + 4\) ,並驗證 expand(factor(expr)) == expr。
  3. 對 \((x+2)^{15}\) 展開,驗證所有係數之和等於 \(3^{15}\) 。(提示:係數和 = 代入 \(x=1\) )
  4. 用 Poly 寫一個函數,判斷兩個多項式是否恆等(不靠 simplify,只靠係數比較)。