克兰克—尼科尔森格式
克兰克—尼科尔森格式(Crank–Nicolson scheme)将空间离散算子在新旧两个时刻的作用取平均,是热方程等演化问题常用的二阶隐式时间格式。它对线性扩散问题具有无条件能量稳定性,但高频误差在大步长下可能换号振荡;无条件稳定并不等于无条件非负或任意步长都准确。
只有一个内部网格点的热传导
考虑 上的热方程 ,边界温度为零,。令网格间距 ,只有中点一个未知温度 。二阶中心空间差分给出 取 ,新旧时刻平均导数得到 所以前两步为 。用来比较时间误差的精确对象是空间离散系统的解 ;它仍与连续热方程有空间离散误差,不能混作一个误差来源。
一般离散与二阶精度
在均匀网格的内部节点定义 。格式为 令 ,逐行写成 零边界值直接省去;非零或随时间变化的边界还应加入两时刻的相应项。每一步需解一个三对角线性系统。
对光滑半离散真解 ,区间积分使用梯形求积:。这说明单步状态缺陷为三阶,稳定后固定时间的全局时间误差为二阶;中心空间差分在四阶空间导数有界等条件下给出二阶空间误差。非光滑初值、初边值不相容或缺少所需正则性时,不能直接沿用同一误差常数和阶数。
该时间离散也是亚当斯—莫尔顿法的梯形公式应用到空间离散方程。数值 PDE 教材常以热方程比较它与显式、后向 Euler 方法。[1]
无条件能量稳定性的证明
取离散内积 ,并将边界零值补齐。设中点状态 。把格式与 作内积,左边利用差平方得到 将二阶差分展开,移位相邻求和并用端点为零,得离散分部求和恒等式 右边非正,故任意正时间步长下 。证明针对这个对称扩散算子和边界条件;非线性、反应项、非对称离散或近似线性求解器需要另作分析。
高频振荡与步长选择
若离散特征模式满足 、,其放大因子为 模始终小于一,但当 时为负; 时趋于 ,很快的真实衰减模式在数值上只缓慢交替。
上文模型若改用 ,首步就是 ,而真实解为正。这是完整的有解变式,也给出误用“稳定”的反例。对初始不连续或尖峰数据,可先用几个较小的后向 Euler 子步消减高频,再使用二阶梯形推进;具体起步与误差容限仍须核查。
三对角实现
下面对零 Dirichlet 边界求解一次时间步。输入内部节点值、扩散率、空间步长和时间步长,返回新列表,不修改输入。Thomas 消元在本问题的严格对角占优正定系统中没有零主元。每步时间与空间均为 ;固定参数时可预先分解,避免重复消元。
import math
def crank_nicolson_heat(interior, kappa, dx, dt):
u = list(map(float, interior))
kappa, dx, dt = map(float, (kappa, dx, dt))
if not u or not all(math.isfinite(v) for v in u + [kappa, dx, dt]):
raise ValueError("nonempty finite data required")
if kappa <= 0 or dx <= 0 or dt <= 0:
raise ValueError("positive parameters required")
r = kappa * dt / (2 * dx * dx)
if not math.isfinite(r) or not math.isfinite(1+2*r):
raise OverflowError("coefficient outside floating-point range")
n = len(u)
old = [0.0] + u + [0.0]
rhs = [r*old[i] + (1-2*r)*old[i+1] + r*old[i+2] for i in range(n)]
diag = [1+2*r] * n
for i in range(1, n):
multiplier = -r / diag[i-1]
diag[i] += multiplier*r
rhs[i] -= multiplier*rhs[i-1]
result = [0.0] * n
result[-1] = rhs[-1] / diag[-1]
for i in range(n-2, -1, -1):
result[i] = (rhs[i] + r*result[i+1]) / diag[i]
if not all(math.isfinite(v) for v in result):
raise OverflowError("nonfinite linear solve")
return result
u = crank_nicolson_heat([1], 1, 0.5, 0.1)
print(u) # [0.4285714285714286]
print(crank_nicolson_heat(u, 1, 0.5, 0.1)) # [0.1836734693877551]
print(crank_nicolson_heat([1], 1, 0.5, 1)) # [-0.6]参考资料
- ↑ Tobin A. Driscoll, Richard J. Braun. Fundamentals of Numerical Computation: Stiffness,扩散的空间离散与时间积分。