跳轉到

3.2 方程組與 linsolve#

本節目標#

  • 會用消去法手算二元一次方程組,並用 solve() 驗證。
  • 學會 linsolve() 的兩種用法(運算式列表與矩陣形式),為第 6 章高斯消去法鋪路。
  • 能解非線性方程組,理解解可能是多個點。
  • 認識解的結構:FiniteSet、無限多解時的參數表示、無解時的 EmptySet。
  • 實驗:三條直線何時共點,用行列式與 linsolve 判斷。

數學概念:方程組與解的幾何意義#

二元一次方程組

\[ \begin{cases} a_1 x + b_1 y = c_1 \\ a_2 x + b_2 y = c_2 \end{cases} \]

的每個方程對應平面上的一條直線,解的個數有三種情形:

  1. 唯一解:兩直線相交於一點(行列式 \(a_1 b_2 - a_2 b_1 \neq 0\) )。
  2. 無解:兩直線平行(係數成比例但常數項不成比例)。
  3. 無限多解:兩直線重合。

三元以上推廣到平面/超平面,這正是線性代數(第 6 章)的主題。

手算(消去法)vs solve#

例:解

\[ \begin{cases} 2x + y = 7 \\ x - y = 2 \end{cases} \]

手算:兩式相加消去 \(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)} 之類:解是一條線,因為有兩式其實是同一條線

實驗觀察:

  1. 共點 \(\iff\) 係數矩陣與增廣矩陣的秩相等(第 6 章的語言: \(\operatorname{rank}(A) = \operatorname{rank}([A|b]) = 2\) )。
  2. 若三線兩兩不同且共點,交點唯一;若其中有兩線重合,解集是一條線。

進一步挑戰:

  1. 把案例 1 的第三條線常數項 \(c\) 換成符號 \(c\) ,解出「使三線共點」的 \(c\) 值。(提示:solve 對 \(c\) 解。)
  2. 對任意三線,寫一個函數回傳 'unique' / 'line' / 'none' 三種分類。
  3. 用行列式驗證:三線 \(a_i x + b_i y = c_i\) 共點的一個充分必要條件是
\[ \det\begin{pmatrix} a_1 & b_1 & c_1 \\ a_2 & b_2 & c_2 \\ a_3 & b_3 & c_3 \end{pmatrix} = 0 \]

用 Matrix(...).det() 對上面四個案例各驗一次。

小結與練習#

小結

  • solve([eq1, eq2], [x, y]) 解方程組最通用;linsolve() 專攻線性,且支援矩陣形式。
  • 線性方程組的解集是集合:唯一解 FiniteSet、無限多解含參數、無解 EmptySet。
  • 非線性方程組用 nonlinsolve(),解的個數由曲線交點數決定。
  • 三線共點 \(\iff\) 線性方程組有解 \(\iff\) 對應的 \(3\times 3\) 行列式為 0——線性代數觀點的預告。

練習

  1. 用消去法手算 \(\begin{cases}3x + 2y = 12 \\ x - y = 1\end{cases}\) ,再用 solve() 與 linsolve()(含矩陣形式)各驗一次。
  2. 解 \(\begin{cases}x + y + z = 6 \\ x + 2y + 3z = 14 \\ x + 4y + 9z = 36\end{cases}\) (這是通過點 \((1,2,3)\) 型的設計,答案應很整齊)。
  3. 求圓 \(x^2 + y^2 = 2\) 與直線 \(y = x\) 的交點。
  4. 構造一組「三線兩兩相交但不共點」的係數,並用 \(3\times 3\) 行列式驗證不為 0。
  5. 讓第三條直線的係數含參數 \(k\) : \((1, 1, 2), (2, -1, 1), (k, 1, 3)\) ,求 \(k\) 使三線共點。