3.4 數值解與根的定位#
本節目標#
- 學會用
nsolve()求方程的數值解,理解其背後是牛頓法(第 X 章預告)。 - 認識「無解析解」的方程(如 \(e^x = 3x\) ),理解為什麼必須走向數值方法。
- 會用
plot目視判斷根的個數與位置,作為nsolve的初始值依據。 - 能在
solve與nsolve之間做正確選擇。 - 實驗:解 \(\cos x = x\) 、探討根的個數與函數形狀的關係。
數學概念:精確解與數值解#
前面兩節的 solve() 給的是精確解(符號解)。但許多方程根本沒有根式或初等函數的封閉形式解:
- \(e^x = 3x\) :涉及超越函數與多項式混合,一般無法用初等運算表示根。
- \(\cos x = x\) :同理,根只能近似。
- \(x^5 - x - 1 = 0\) :五次多項式無根式解(Abel–Ruffini 定理)。
數值解(numerical solution)不追求「漂亮的式子」,而是求一個足夠接近真根的小數,例如 \(x \approx 0.567143\) 。常用的方法之一是牛頓法(Newton's method):
\[ x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} \]
從一個猜測值 \(x_0\) 出發,反覆用「切線與 \(x\) 軸的交點」更新猜測,通常收斂得非常快(二次收斂)。詳細推導與誤差分析見第 X 章;本節先學會把 nsolve 當工具用。
nsolve 基本用法#
from sympy import symbols, nsolve, cos, exp, pi
x = symbols('x')
# 例 1:cos(x) = x(著名方程,根稱為 Dottie number)
print(nsolve(cos(x) - x, x, 0)) # 0.739085133215161
print(nsolve(cos(x) - x, x, 1)) # 0.739085133215161(不同初始值,同一根)
# 例 2:指定精度(第 3 個參數是位數)
print(nsolve(cos(x) - x, x, 0, prec=30))
# 0.739085133215160641655312087674
# 例 3:多個根時,初始值決定收斂到哪一個
# x^2 - 5 = 0 的根是 ±sqrt(5) ≈ ±2.236
print(nsolve(x**2 - 5, x, 1)) # 2.23606797749979 → +√5
print(nsolve(x**2 - 5, x, -1)) # -2.23606797749979 → -√5
# 例 4:也可用 Eq
print(nsolve(cos(x) - x, 0)) # 不寫變數也可(單變數時)
要點:nsolve(expr, x, x0) 的 x0 是初始猜測值,它決定了牛頓法迭代走向哪個根。給錯初始值可能收斂到別的根、甚至不收斂——所以先「定位」根很重要,這正是 plot 的用武之地。
無解析解的方程: \(e^x = 3x\)#
from sympy import symbols, solve, solveset, nsolve, plot, exp, S, N, Poly
x = symbols('x', real=True)
# 1. 先試符號解:solve 舉白旗或給 LambertW 形式
print(solve(exp(x) - 3*x, x))
# [3*LambertW(-1/3)] ← LambertW 是特殊函數,不算「初等」解
# 一般教學情境會說「此方程無初等解析解」
# 2. 圖形法:f(x) = e^x - 3x 的零點
p = plot(exp(x) - 3*x, (x, -1, 2), line_color='blue', show=False)
p.show() # 觀察:曲線與 x 軸交於兩處?一處在 x≈0.6,一處在 x≈1.5
# 3. 用 nsolve 分別求出兩個根
print(nsolve(exp(x) - 3*x, x, 0.5)) # 0.619061286735945
print(nsolve(exp(x) - 3*x, x, 1.5)) # 1.51213455165784
# 4. 驗證:代回原方程,殘差接近 0
r = nsolve(exp(x) - 3*x, x, 1.5)
print(exp(r) - 3*r) # 約 0(浮點殘差 ~1e-16 級)
觀察重點:solve 給的 LambertW(-1/3) 是一個精確解,但圖形顯示有兩個實根——符號方法找到了一個,另一個(較小的根)沒有以初等形式現身。這說明:「有符號解」與「找到全部解」是兩回事;圖形是找根個數最可靠的工具。
用 plot 目視找根個數#
找根的標準流程:
- 定義 \(f(x)\) ,把方程化成 \(f(x) = 0\) 。
- 畫 \(y = f(x)\) ,看曲線與 \(x\) 軸交幾次、大約在哪。
- 對每個交點,取附近的初始值呼叫
nsolve。
from sympy import symbols, plot, nsolve, sin, cos, exp
x = symbols('x', real=True)
# 另一個例子:x^3 - 6x^2 + 11x - 6 = (x-1)(x-2)(x-3)
p = plot(x**3 - 6*x**2 + 11*x - 6, (x, 0, 4), show=False)
p.show() # 三個交點 x ≈ 1, 2, 3
for guess in [0.9, 2.1, 3.1]:
print(nsolve(x**3 - 6*x**2 + 11*x - 6, x, guess))
# 1.0, 2.0, 3.0
# 技巧:也可以把方程改寫成「兩邊各畫一條曲線看交點」
# cos(x) = x ⟺ y = cos(x) 與 y = x 的交點
p2 = plot(cos(x), x, (x, -2, 2), show=False)
p2.show() # 只有一個交點 → 只有一個根
也可以兩條線一起畫:plot(f1, f2, (x, a, b)),交點的 \(x\) 座標就是原方程的根。這招對「一邊是震盪函數」的方程特別好用。
solve vs nsolve 的選擇#
| 面向 | solve()(符號解) | nsolve()(數值解) |
|---|---|---|
| 結果形式 | 精確式子( \(\sqrt{2}\) 、分數) | 小數近似 |
| 能否得到全部根 | 多項式通常可以 | 一次只能給一個(靠初始值) |
| 超越方程 | 常失敗或給特殊函數 | 幾乎總能給出近似根 |
| 適合場景 | 需要後續符號運算、證明、化簡 | 只要數值、驗證、畫圖資料 |
| 速度 | 可能極慢甚至停不下來 | 快 |
選擇原則:
- 多項式、簡單代數方程:先
solve(),拿不到再nsolve()(或對Poly.all_roots()取N())。 - 超越方程、只要數值:直接
plot定位 +nsolve。 - 需要全部實根:
plot看個數,再逐個nsolve;或solveset/Poly.all_roots()。
from sympy import symbols, solve, nsolve, N, Poly, plot
x = symbols('x')
# 五次方程:solve 給不出根式解,但 Poly.all_roots() 可以(以代數數表示)
print(solve(x**5 - x - 1, x)) # CRootOf(...) 形式或空
roots = Poly(x**5 - x - 1, x).all_roots()
print(N(roots[0], 8)) # 1.1673040 ← 唯一實根
動手實驗 1: \(\cos x = x\) 的解#
實驗目標:完整走一遍「畫圖定位 → nsolve 求根 → 驗證」流程。
from sympy import symbols, cos, nsolve, plot, N
x = symbols('x', real=True)
# 步驟 1:畫 y = cos(x) 與 y = x,確認交點個數與大致位置
p = plot(cos(x), x, (x, -3, 3), show=False)
p.show() # 唯一交點,在 x ≈ 0.7 附近
# 步驟 2:nsolve 求根(試幾個不同的初始值)
for x0 in [-1, 0, 0.5, 1, 2]:
print(x0, '→', N(nsolve(cos(x) - x, x, x0), 15))
# 全部收斂到 0.739085133215161(Dottie number)
# 步驟 3:驗證殘差
r = nsolve(cos(x) - x, x, 0.5, prec=50)
print(cos(r) - r) # ≈ 0(高精度下幾乎為零)
# 步驟 4:牛頓法手動重現(預告第 X 章)——迭代 5 次看看收斂速度
f = cos(x) - x
fp = f.diff(x)
xn = 1.0 # 初始值
for i in range(6):
fxn = float(f.subs(x, xn))
fpxn = float(fp.subs(x, xn))
xn = xn - fxn / fpxn # x_{n+1} = x_n - f/f'
print(f"第 {i+1} 次迭代: x = {xn:.15f}")
# 觀察:有效位數幾乎每次翻倍 → 這就是「二次收斂」
動手實驗 2:根的個數與函數形狀的關係#
實驗目標:系統性掃描幾個函數,歸納「形狀 ⟹ 根個數」的規律。
from sympy import symbols, plot, nsolve, exp, sin, cos, Poly, N
x = symbols('x', real=True)
cases = {
'x^2 - 2 (開口向上的拋物線)': x**2 - 2, # 2 根
'x^2 + 2 (拋物線整體在軸上方)': x**2 + 2, # 0 根
'(x-1)^2 (與軸相切)': (x - 1)**2, # 1 個重根
'x^3 - 3x (S 形,穿越三段)': x**3 - 3*x, # 3 根
'exp(x) - 3*x (先降後升的 U 形)': exp(x) - 3*x, # 2 根
'sin(x) - x/5 (震盪對上緩斜直線)': sin(x) - x/5, # 多根
}
for name, f in cases.items():
plot(f, (x, -6, 6), show=False).show()
print(name)
# 以 sin(x) - x/5 為例:找出 [-6, 6] 內全部的根
f = sin(x) - x/5
guesses = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
found = []
for g in guesses:
r = nsolve(f, x, g)
if all(abs(float(r) - fr) > 1e-6 for fr in found):
found.append(float(r))
print(sorted(found))
# [-2.876…, -2.536…, 0.0, 2.536…, 2.876…] 附近 → 5 個根
# (準確個數請以圖形為準:|sin x| 的坡度被 x/5 追上幾次,就有幾個根)
歸納出的規律:
- 變號才可能有根:若 \(f\) 在區間上連續且 \(f(a) f(b) < 0\) ,則 \((a,b)\) 內必有根(勘根定理 / 中間值定理)。
- 極值個數限制根個數: \(n\) 次多項式至多 \(n-1\) 個極值、至多 \(n\) 個實根;單峰(U 形)函數至多 2 個根。
- 切於 \(x\) 軸 = 重根: \((x-1)^2\) 只「碰」不「穿」,是偶數重根的幾何特徵。
- 震盪函數對緩慢變化的項:震盪幾次就可能交幾次——根的個數由「振幅被直線追上的次數」決定。
進一步挑戰:
- 把實驗 2 的係數 \(1/5\) 改成 \(1/2\) 、 \(1\) 、 \(2\) ,觀察 \(\sin x = kx\) 根個數的變化,找出「從多根變成單根」的臨界 \(k\) 。
- 對 \(e^x = 3x\) ,證明(用符號微分求極值)當 \(e^x = 3x\) 的判別條件 \(x = \ln 3\) 處函數值正負不同時,根的個數會從 2 變 0;把 3 換成參數 \(c\) ,求出臨界 \(c\) 值。
- 用
nsolve解 \(\tan x = x\) 在 \((0, \pi/2)\) 內的根;再用圖形解釋為什麼這個區間內只有一個根( \(\tan x > x\) 恆成立?驗證看看)。
小結與練習#
小結
nsolve(expr, x, x0)用牛頓法求數值根,初始值 \(x_0\) 決定收斂到哪個根;prec可控制精度。- 許多方程( \(e^x = 3x\) 、 \(\cos x = x\) 、五次多項式)沒有初等解析解,數值方法是唯一務實選擇。
- 找根流程:
plot目視定位 → 每個交點附近給nsolve初始值 → 代回驗證殘差。 - 根的個數由函數形狀決定:勘根定理、極值個數、重根相切、震盪對緩變項,是四個核心直覺。
練習
- 用
nsolve解 \(e^{-x} = x\) (答案即 \(\Omega\) 常數 \(\approx 0.5671\) ),並用牛頓法手動迭代 5 次重現收斂過程。 - 先用
plot判斷 \(x^4 - 4x^3 + 2x^2 + 4x + 1 = 0\) 在 \([-1, 4]\) 內有幾個實根,再用nsolve全部求出,最後用Poly(...).all_roots()驗證個數。 - 解 \(\sin x = 0.1\) 在 \([0, 2\pi]\) 內的所有根:先用圖形數出個數,再逐個
nsolve。 - 方程 \(x^3 - x = c\) 有三個相異實根的條件是什麼?(提示:研究 \(f(x) = x^3 - x\) 的極大極小值,令 \(c\) 介於其間;用
nsolve對臨界的 \(c\) 驗證。) - 比較
solve(exp(x) - 3*x, x)與兩個nsolve根的數值:LambertW那個精確解對應哪一個數值根?另一個根為什麼solve沒給?