亚当斯—莫尔顿法
亚当斯—莫尔顿法(Adams–Moulton method)是一族包含新时刻导数的隐式线性多步法。它与亚当斯—巴什福思法都来自导数插值的积分;区别在于插值节点包含尚未算出的时刻,因此新状态通常要通过代数方程求出。隐式性可以改善稳定性,却同时引入方程求解的成本和收敛条件。
把区间两端的斜率都算进去
考虑无量纲初值问题 。在时间区间 上,精确增量等于导数的积分。若用连接两端导数的直线替代积分曲线,就得到 其中 。这就是梯形法,也是二阶 Adams–Moulton 格式。本文把“二阶”用于精度;有些资料按历史步数编号,同一个格式会称为一阶步数的 Adams–Moulton 法,不能只比较编号而不比较公式。Adams 插值积分的构造见 Driscoll 与 Braun 的教材。[1]
这个式子不是把未知量写在右边就停止计算。对当前衰减方程,代入右端可直接解出 取 ,每步乘以 ,因此 精确值是 、。误差不是零,但全过程只解了一次线性关系,没有另加一个外推的起步值。
高阶格式的权重来自插值
若再使用历史导数 ,在相对坐标 的节点 上作二次插值,三个基函数分别是 它们从零到一的积分为 ,所以三阶格式为 这说明权重与节点有关,并不是从梯形法任意调整系数得到。三阶格式还需要起步历史。本文后面的代码与稳定性证明针对二阶梯形格式,不能直接移用于这一三阶格式。
精度与一步误差
沿精确解记 ,假设其二阶导数连续。端点线性插值的余项为 若 ,积分后单步状态缺陷不超过 这个结论用的是精确的两端值。实际计算还须考虑旧误差与隐式方程的求解误差。
若 对状态有利普希茨常数 ,且 ,两份格式相减并取绝对值,得到 在固定终止时间内反复使用这一式子,乘积因子至多按时间的指数增长,缺陷累计为 。这给出充分光滑、隐式方程准确求解时的全局二阶精度。条件 是此处误差估计与简单不动点求解的充分条件;它不是梯形法在所有问题上的必要稳定条件。
变式 可以手算验证。两端导数的平均为 ,每步积分恰好等于 ,所以数值解精确为 。这同时检查了右端时间不能仍写成旧时刻。
左半平面稳定与强衰减的差别
对测试方程 ,放大因子为 若 ,则 因此分母非零,而且 。这证明梯形法是 A 稳定的:线性衰减测试问题不会因为步长过大而发生指数放大。
但当 沿负实轴趋于负无穷时,,并不趋于零。极快衰减的真实分量可能变成缓慢换号的离散分量。例如 时,,一步后仍保留近乎原来的大小。A 稳定并不等于没有振荡或能迅速消除刚性瞬态;这一区分在刚性微分方程中尤其重要。
解隐式方程与一次校正
对一般非线性右端,把新状态记为 ,求根问题是 可用牛顿法,其分母为 。Newton 迭代需要导数、合适初猜及非零分母;一般情形不能保证任意步长、任意初猜都收敛。
如果只先做一次显式 Euler 预测,再把这个预测值代入梯形右端校正一次,所得方法并不等于解出了隐式方程。在线性测试问题上,一次预测校正给出 ,恰是显式二阶格式的放大因子。取 ,它为五;真正的隐式梯形因子却为 。要使用隐式格式的稳定性结论,必须确实把对应方程解到足够精度。生产求解器如何把 Adams 方法与非线性迭代结合,可参见 CVODE 的说明。[2]
带残差检查的标量一步
代码输入标量右端 、其状态导数 、当前时间与状态、正步长;返回一个新状态,不改写输入。迭代上限防止无休止求解,残差阈值用于验收离散方程;它不自动等于真实解的误差。每步执行 次 Newton 迭代时,右端及导数成本为 ,工作空间为常数。
import math
def trapezoid_step(f, df, t, y, h, tol=1e-12, max_iter=30):
if not all(math.isfinite(v) for v in (t, y, h, tol)):
raise ValueError("finite inputs required")
if h <= 0 or tol <= 0 or not isinstance(max_iter, int) or max_iter < 1:
raise ValueError("positive h, tolerance and iteration limit required")
old = float(f(t, y))
z = y + h * old
for _ in range(max_iter):
new = float(f(t + h, z))
residual = z - y - h * (old + new) / 2
if not all(math.isfinite(v) for v in (z, new, residual)):
raise ArithmeticError("nonfinite iteration")
if abs(residual) <= tol * (1 + abs(z)):
return z
jacobian = 1 - h * float(df(t + h, z)) / 2
if not math.isfinite(jacobian) or abs(jacobian) < 1e-14:
raise ArithmeticError("singular or ill-scaled Newton derivative")
z -= residual / jacobian
raise RuntimeError("Newton iteration did not reach the residual tolerance")
y = 1.0
for n in range(2):
y = trapezoid_step(lambda t, y: -y, lambda t, y: -1, n * 0.2, y, 0.2)
print(round(y, 9)) # 0.818181818, then 0.669421488非线性方程可能有多个根;收敛到哪个根需要结合连续分支、初猜和步长判断。算法的高阶插值公式、非线性求解误差和真实初值问题的误差是三个层次,应分别控制。
参考资料
- ↑ Tobin A. Driscoll, Richard J. Braun. Fundamentals of Numerical Computation: Multistep methods,隐式 Adams 格式及起步、精度与稳定性。
- ↑ SUNDIALS Development Team. CVODE: Mathematical Considerations,Adams 方法与非线性系统求解。