為什麼「一個固定步長」是個錯的問題
到現在你已能漂亮地積分一個初值問題:挑一個步長 h,用 RK4 從 x_n 走到 x_{n+1},整體誤差便以 O(h^4) 下降。但稍早那些篇章裡,每一步都用同一個 h 走完整段旅程——而這幾乎從不是你要的。想像一顆彗星掠過太陽:好幾個月裡它幾乎走在一條直線上漂移,接著在短短幾小時內繞著近日點急甩而過。一個小到足以解析那記急甩的步長,若處處都用,就會在平靜處浪費掉數百萬個毫無意義的步。
所以對的問題不是「步長多大?」,而是「我能容忍每步多少誤差,又是哪個步長能在此時此地剛好給出那麼多?」這就是 自適應步長控制:一個好的積分器會時時估計每一步會犯下的局部截斷誤差,並在行進中拉長或縮短 h,讓誤差貼著使用者設定的容忍值。平靜段大步走,狂暴段細步爬,誤差大致均勻地灑滿整條路徑——這就是目標。
估計那看不見的誤差
難處在這裡:要控制誤差,你得先估計它,但你又從不知道真正的解能拿來比對。訣竅是用不同階數的方法把同一步走兩次,再從兩者之間的落差讀出誤差。最乾淨的版本是嵌入式龍格-庫塔對,例如著名的 Runge-Kutta-Fehlberg,或 MATLAB 的 ode45 背後那個 Dormand-Prince 對。同一組級(stage)求值,配上一組權重給出比方說一個五階估計,再配上第二組權重給出一個四階估計——一批工作換來兩個答案。
兩個答案之間的差,由較低階方法的誤差主導,因此它是一個廉價而誠實的估計,告訴你這一步錯得多離譜——就叫它 err。嵌入的妙處在於兩條公式共用它們的級求值:你幾乎是免費地拿到誤差估計,而不是用另一套完整的方法把這一步重做一遍。(回想一下,對 f 的每級斜率求值,是任何 龍格-庫塔方法的主要成本;重複利用它們,正是自適應變得划算的關鍵。)
一旦你有了 err 和容忍值 tol,基本的微積分就告訴你如何挑下一個步長。若方法的誤差按 h^(p+1) 縮放,那麼要把 err 壓到 tol,就把 h 乘上 (tol/err) 的 1/(p+1) 次方。下面是個常見、略偏保守的配方。若 err 已落在 tol 之下,你就保留這一步、並把 h 放大供下次用;若 err 超標,你就拒絕這一步、縮小 h、重做它——絕不把一個過大的誤差往前帶。
One adaptive step (embedded pair of orders p and p+1):
take step h -> y_high (order p+1), y_low (order p)
err = || y_high - y_low || # cheap error estimate
ratio = ( tol / err ) ^ ( 1 / (p+1) )
h_new = SAFETY * h * ratio # SAFETY ~ 0.9
if err <= tol: accept y_high, advance, use h_new next
else: reject step, set h = h_new, redo多步法:記住,別重算
龍格-庫塔方法是單步的:要從 x_n 前進,它只看 y_n,並靠著在步內部的好幾個臨時點上對斜率 f 求值來換得高階——那些點隨後就被丟掉。線性多步法做的是相反的交易。它把單步的健忘變成長期的記憶:要算 y_{n+1},它重複利用先前各步早已算過、存下的斜率 f(y_n)、f(y_{n-1})、f(y_{n-2})、...。舊工作不被丟棄,而是被回收。
經典的一族是 亞當斯(Adams)方法。想像過去那幾個存下的斜率;用一個多項式穿過它們,然後在下一個區間上積分那個多項式,以預測 y 如何往前移動——這正是數值求積那一級的「先替換再積分」一招,套用在斜率上。亞當斯-巴什福斯(Adams-Bashforth)只用過去、早已知道的斜率,所以是顯式的:一條公式、不必求解,而且關鍵在於——無論階數多高,每步只多一次 f 求值。這就是它的招牌優勢——RK4 每步花四次斜率求值;一個四階的亞當斯-巴什福斯本質上只花一次。
當然,天下沒有白吃的午餐。多步法在開步前需要好幾個過去的點,所以它無法只憑單一個初始條件自行啟動——你得先用一個單步法(常是 RK4)替前幾步「灌引水」。它也討厭改變 h:存下的斜率是按舊步長間隔排列的,所以一次乾淨的步長變更,意味著要重新推導係數或重新內插歷史,這也是為什麼自適應多步法的程式碼,比自適應 RK 要更繁瑣。這筆交易是:用每步更少的函數呼叫,換來一台較顛簸、被歷史綁住的機器。
預測-修正:先猜,再精修
如果你改成讓多項式穿過過去那些斜率以及 x_{n+1} 處的新斜率,你就得到一個亞當斯-莫頓(Adams-Moulton)方法——但現在未知的 y_{n+1} 出現在方程式的兩邊,因為 f(y_{n+1}) 需要的,正是你正在求解的那個值。這方法是隱式的:你不能直接代入硬算,每步都得解一個關於 y_{n+1} 的方程式。對同樣的階數,隱式方法更準確、也穩定得多,這正是它們對下一篇那些剛性問題至關重要的原因——但解那個方程式是要付代價的。
優雅的折衷是 預測-修正法。先由廉價的顯式亞當斯-巴什福斯預測出一個暫定的 y_{n+1}。接著把那個猜測代入隱式亞當斯-莫頓公式的右側來修正它——把那道困難的隱式方程式,變成一次容易的顯式求值。你可以只施一次修正,或依喜好迭代個一兩次。附帶的好處是:預測值與修正值之間的落差,本身就是一個免費的局部誤差估計,隨時可以像嵌入式 RK 對那樣,拿來驅動自適應步長控制。
BDF,以及顯式方法撞上的那堵牆
亞當斯方法擬合一個多項式到過去的斜率上、再積分它。還有第二個同樣重要的多步族,它擬合一個多項式到過去的解值 y_n、y_{n-1}、... 上,並強迫它在 x_{n+1} 處的導數對上 f:這些就是反向差分公式,即 BDF。它們依其構造就是隱式的,而且是一類惡名昭彰的問題的首選方法——剛性問題,其中解的某些分量以兇猛的速度衰減,而另一些卻在爬行。
在這裡,光靠自適應是不夠的,而這正是整個這一級誠實的關鍵句。在一條剛性方程式上,一個顯式方法——RK4、亞當斯-巴什福斯,無論你的步長控制器多聰明——都被迫採取荒謬地微小的步,不是為了準確度,而是純粹為了數值上的穩定性:只要踏出一步太大,算出的解就會爆成一堆與真正、平滑衰減的答案毫不相干的振盪。自適應控制器盡責地把 h 砸到極小以防爆炸,於是你的運算慢得像在爬,而真正的解卻幾乎一動不動地坐在那裡。
隱式方法——尤以 BDF 為最——能逃出這個陷阱,因為它們的穩定區域極為廣闊,所以即使在剛性問題上,它們也能採取由準確度決定大小的大步。這就是為什麼正式的 ODE 求解器都配兩具引擎:一具顯式自適應 RK(如 ode45)對付普通問題,一具隱式 BDF 程式碼(如 ode15s)對付剛性問題。隱式為什麼會贏、「絕對穩定區域」究竟是什麼意思、以及如何在剛性毀掉你的運算之前就認出它——這正是本級下一篇、也是最後一篇的故事。