3.2 方程組與 linsolve#
本節目標#
- 會用消去法手算二元一次方程組,並用
solve()驗證。 - 學會
linsolve()的兩種用法(運算式列表與矩陣形式),為第 6 章高斯消去法鋪路。 - 能解非線性方程組,理解解可能是多個點。
- 認識解的結構:
FiniteSet、無限多解時的參數表示、無解時的EmptySet。 - 實驗:三條直線何時共點,用行列式與
linsolve判斷。
數學概念:方程組與解的幾何意義#
二元一次方程組
的每個方程對應平面上的一條直線,解的個數有三種情形:
- 唯一解:兩直線相交於一點(行列式 \(a_1 b_2 - a_2 b_1 \neq 0\) )。
- 無解:兩直線平行(係數成比例但常數項不成比例)。
- 無限多解:兩直線重合。
三元以上推廣到平面/超平面,這正是線性代數(第 6 章)的主題。
手算(消去法)vs solve#
例:解
手算:兩式相加消去 \(y\) ,得 \(3x = 9\) ,故 \(x = 3\) ,代回得 \(y = 1\) 。
from sympy import symbols, solve, Eq
x, y = symbols('x y')
sol = solve([Eq(2*x + y, 7), Eq(x - y, 2)], [x, y])
print(sol) # {x: 3, y: 1}
print(sol[x], sol[y]) # 3 1
也可以不用 Eq,直接給「左邊 − 右邊」的運算式(隱含 = 0):
print(solve([2*x + y - 7, x - y - 2], [x, y])) # {x: 3, y: 1}
消去法的精神就是「線性組合」;SymPy 內部做的是更通用的消去演算法(高斯消去),不論幾個變數都一樣。
linsolve:專為線性方程組而生#
solve() 是萬用工具,而 linsolve() 專門處理線性方程組,有兩種介面:
from sympy import symbols, linsolve, Matrix, eq
x, y, z = symbols('x y z')
# 用法一:運算式列表(= 0 形式)
sol1 = linsolve([2*x + y - 7, x - y - 2], (x, y))
print(sol1) # {(3, 1)}
# 用法二:矩陣形式 A*x = b
A = Matrix([[2, 1], [1, -1]])
b = Matrix([7, 2])
sol2 = linsolve((A, b), (x, y))
print(sol2) # {(3, 1)}
# 矩陣形式的意義:解 = A 的反矩陣乘 b(若 A 可逆)
print(A.det()) # -3 ≠ 0,唯一解
print(A.inv() * b) # Matrix([[3], [1]])
矩陣形式 linsolve((A, b), vars) 就是「把線性代數的語言直接搬進來」:A 是係數矩陣、b 是常數向量。第 6 章會進一步用 A.LUsolve(b)、A.gauss_jordan_solve() 等方法,那裡的高斯消去就是今日手算消去法的系統化版本。
無限多解:參數解#
當方程組的自由度大於零,解必須用參數表示:
from sympy import symbols, linsolve, Nonlinsolve # noqa
from sympy import nonlinsolve
x, y, z = symbols('x y z')
# 例:x + y + z = 6, 2x - y + z = 3(三變數兩方程 → 一個自由參數)
sol = linsolve([x + y + z - 6, 2*x - y + z - 3], (x, y, z))
print(sol)
# {(tau0, 1 - tau0/2 + 3*tau0/2 - ..., ...)} 之類的參數形式
# 整理後:x = t, y = 1 + t, z = 5 - 2t(t 為任意實數)
讀法:tau0 是 SymPy 自動引入的自由參數(等價於手算的 \(t\) )。解集合是一條直線——這是「解的結構」的幾何圖像:線性方程組的解集是點、直線或平面(第 6 章的行空間觀點)。
非線性方程組#
from sympy import symbols, solve, nonlinsolve
x, y = symbols('x y')
# 例 1:直線與拋物線 → 通常 0、1 或 2 個交點
print(solve([y - x - 1, y - x**2 - 1], [x, y]))
# {(-1, 0), (0, 1)} ← 兩個交點
# 例 2:圓與直線 x^2 + y^2 = 25, x + y = 7
sol = nonlinsolve([x**2 + y**2 - 25, x + y - 7], [x, y])
print(sol)
# {(3, 4), (4, 3)} ← 3-4-5 直角三角形的聯想
# 例 3:兩個圓
print(nonlinsolve([(x - 1)**2 + y**2 - 4, x**2 + (y - 1)**2 - 4], [x, y]))
# 對稱的兩個交點
非線性方程組的解的個數取決於曲線的交點數,可能有多組——solve/nonlinsolve 會把所有(精確)解回傳。
解的結構:FiniteSet、EmptySet 與集合觀#
從 3.1 節的 solveset 一路到本節,SymPy 的解都是集合:
from sympy import symbols, linsolve, S, EmptySet, FiniteSet
x, y = symbols('x y')
# linsolve 回傳 FiniteSet(或含參數的集合)
sol = linsolve([x + y - 3], (x, y))
print(type(sol)) # <class 'sympy.sets.sets.FiniteSet'> 的子類(條件集合)
print(sol) # {(3 - y, y)} ← 一條直線
# 無解
print(linsolve([x + y - 1, x + y - 2], (x, y))) # EmptySet
print(linsolve([x + y - 1, x + y - 2], (x, y)) == S.EmptySet) # True
# 有解時可迭代、取元素
s = linsolve([2*x + y - 7, x - y - 2], (x, y))
for pt in s:
print(pt) # (3, 1)
print(pt[0], pt[1]) # 3 1
對照表:
| 情形 | 表示法 |
|---|---|
| 唯一解 | FiniteSet((3, 1)) |
| 有限多解 | FiniteSet((3, 4), (4, 3)) |
| 無限多解 | 含 tau0 參數的集合(ImageSet / ConditionSet) |
| 無解 | EmptySet |
把「解」視為集合,是現代數學的標準做法;solve() 的字典形式只是 FiniteSet 的便利包裝。
動手實驗:三條直線何時共點#
實驗目標:給定三條直線,判斷它們是否共點(三線交於同一點)。
數學原理:三線 \(a_i x + b_i y = c_i\) 共點 \(\iff\) 前兩線的交點滿足第三式 \(\iff\) 三式組成的線性方程組有解。
from sympy import symbols, linsolve, Matrix, Eq, solve, Rational
x, y = symbols('x y')
def concurrence(lines, tol_note=""):
"""lines: [(a, b, c)] 代表 a*x + b*y = c"""
A = Matrix([[a, b] for a, b, c in lines])
bvec = Matrix([c for a, b, c in lines])
sol = linsolve((A, bvec), (x, y))
return sol
# 案例 1:三線共點(交於 (1, 1))
L1, L2, L3 = (1, 1, 2), (2, -1, 1), (1, -2, -1)
print(concurrence([L1, L2, L3])) # {(1, 1)} ← 共點!
# 案例 2:兩兩相交但不共點(三角形)
L4, L5, L6 = (1, 0, 0), (0, 1, 0), (1, 1, 3)
print(concurrence([L4, L5, L6])) # EmptySet
# 案例 3:三線平行(都無交點或重合)
L7, L8, L9 = (1, 1, 2), (1, 1, 3), (1, 1, 4)
print(concurrence([L7, L8, L9])) # EmptySet
# 案例 4:兩線重合、第三條穿過 → 仍是共點(一整條線的解)
L10, L11, L12 = (1, 1, 2), (2, 2, 4), (1, -1, 0)
print(concurrence([L10, L11, L12]))
# {(1 - tau0/1, tau0)} 之類:解是一條線,因為有兩式其實是同一條線
實驗觀察:
- 共點 \(\iff\) 係數矩陣與增廣矩陣的秩相等(第 6 章的語言: \(\operatorname{rank}(A) = \operatorname{rank}([A|b]) = 2\) )。
- 若三線兩兩不同且共點,交點唯一;若其中有兩線重合,解集是一條線。
進一步挑戰:
- 把案例 1 的第三條線常數項 \(c\) 換成符號 \(c\) ,解出「使三線共點」的 \(c\) 值。(提示:
solve對 \(c\) 解。) - 對任意三線,寫一個函數回傳
'unique'/'line'/'none'三種分類。 - 用行列式驗證:三線 \(a_i x + b_i y = c_i\) 共點的一個充分必要條件是
用 Matrix(...).det() 對上面四個案例各驗一次。
小結與練習#
小結
solve([eq1, eq2], [x, y])解方程組最通用;linsolve()專攻線性,且支援矩陣形式。- 線性方程組的解集是集合:唯一解
FiniteSet、無限多解含參數、無解EmptySet。 - 非線性方程組用
nonlinsolve(),解的個數由曲線交點數決定。 - 三線共點 \(\iff\) 線性方程組有解 \(\iff\) 對應的 \(3\times 3\) 行列式為 0——線性代數觀點的預告。
練習
- 用消去法手算 \(\begin{cases}3x + 2y = 12 \\ x - y = 1\end{cases}\) ,再用
solve()與linsolve()(含矩陣形式)各驗一次。 - 解 \(\begin{cases}x + y + z = 6 \\ x + 2y + 3z = 14 \\ x + 4y + 9z = 36\end{cases}\) (這是通過點 \((1,2,3)\) 型的設計,答案應很整齊)。
- 求圓 \(x^2 + y^2 = 2\) 與直線 \(y = x\) 的交點。
- 構造一組「三線兩兩相交但不共點」的係數,並用 \(3\times 3\) 行列式驗證不為 0。
- 讓第三條直線的係數含參數 \(k\) : \((1, 1, 2), (2, -1, 1), (k, 1, 3)\) ,求 \(k\) 使三線共點。