4. 二维泊松方程九点差分格式精度提升实践

问题设定与制造解

在单位正方形 Ω=[0,1]² 上求解二维泊松方程 −Δu=f,取制造解 u(x,y)=sin(πx)sin(πy),则右端项 f=2π²sin(πx)sin(πy),边界上 u=0。该解在边界附近曲率变化剧烈,正好用来检验高阶格式对边界附近精度的改善。

为便于比较,两种格式使用相同网格:N×N 内部节点,h=1/(N+1),节点坐标 xi=ih,yj=jh。误差用离散最大范数 E∞=max|uij−u(xi,yj)| 与离散 L² 范数衡量,收敛阶用相邻网格误差比的对数估计:p≈log(Eh/Eh/2)/log2。

五点格式与九点紧致格式的构造

五点格式直接对 −(uxx+uyy) 作中心二阶差分:

(-Δ_h^5 u)_ij = (4u_ij - u_{i-1,j} - u_{i+1,j} - u_{i,j-1} - u_{i,j+1}) / h²

其截断误差为 O(h²)。九点紧致格式利用 uxx、uyy 的四阶紧致算子组合,并借助方程本身消去高阶导数项。记 δx²uij=ui+1,j−2uij+ui−1,j,δy² 同理,则九点格式可写为:

(1 + h²/12 · (δ_x² + δ_y²)/h²) 作用在 (-Δ_h^5 u) 上 = f_ij + h²/12 · (δ_x² + δ_y²)f_ij / h²

展开后每个内部节点涉及 3×3 共九个点(中心、四邻、四对角),故称九点格式。其截断误差为 O(h⁴),但边界附近需要特殊处理:由于格式模板在距边界一层的节点上会引用域外点,实践中对最靠近边界的节点仍用五点格式,内部节点用九点格式,这种"边界降阶"是紧致格式的典型工程处理方式。

五点格式与九点紧致格式的模板与边界处理 五点格式(O(h²)) −1 −1 4 −1 −1 仅用四邻点,边界无需特殊处理 九点紧致格式(O(h⁴)) 1 1 1 1 4 4 4 4 −20 含四对角点,边界一层需降阶为五点 边界附近降阶处理 u=0 边界 五点(边界层) 九点(内部) 相同网格下:五点格式误差 O(h²),九点紧致格式内部 O(h⁴),边界层降阶后整体仍优于五点

可运行的实现代码

下面给出完整的 Python 实现,包含制造解、右端项、两种格式的系数矩阵组装、直接求解(小网格用稠密求解便于验证)以及误差与收敛阶计算。代码自洽,可直接运行。

import numpy as np

def exact(x, y):
    return np.sin(np.pi * x) * np.sin(np.pi * y)

def rhs(x, y):
    return 2.0 * np.pi**2 * np.sin(np.pi * x) * np.sin(np.pi * y)

def build_grid(N):
    h = 1.0 / (N + 1)
    x = np.linspace(h, 1 - h, N)
    y = np.linspace(h, 1 - h, N)
    X, Y = np.meshgrid(x, y, indexing='ij')
    return h, X, Y

def solve_five_point(N):
    h, X, Y = build_grid(N)
    n = N * N
    A = np.zeros((n, n))
    b = np.zeros(n)
    def idx(i, j):
        return i * N + j
    for i in range(N):
        for j in range(N):
            k = idx(i, j)
            A[k, k] = 4.0 / h**2
            b[k] = rhs(X[i, j], Y[i, j])
            if i > 0:   A[k, idx(i-1, j)] = -1.0 / h**2
            if i < N-1: A[k, idx(i+1, j)] = -1.0 / h**2
            if j > 0:   A[k, idx(i, j-1)] = -1.0 / h**2
            if j < N-1: A[k, idx(i, j+1)] = -1.0 / h**2
    u = np.linalg.solve(A, b).reshape(N, N)
    return h, X, Y, u

def solve_nine_point(N):
    h, X, Y = build_grid(N)
    n = N * N
    A = np.zeros((n, n))
    b = np.zeros(n)
    def idx(i, j):
        return i * N + j
    # 九点格式系数(乘以 h² 归一化后):
    # 中心 -20/6,四邻 4/6,四对角 1/6,右端为 h²*(f + h²/12 * Δf)
    # 这里采用等价形式:对 -Δu=f 两边作用 (1 + h²/12 Δ) 得
    # (-Δ_h^5 u) + h²/12 * (-Δ_h^5)(δ_x²+δ_y²)/h² u = f + h²/12 Δf
    # 展开后中心 -20/6,四邻 4/6,四对角 1/6(均除以 h²)
    c0, c1, c2 = -20.0/6.0, 4.0/6.0, 1.0/6.0
    for i in range(N):
        for j in range(N):
            k = idx(i, j)
            # 边界层(距边界一层)用五点格式
            boundary_layer = (i == 0 or i == N-1 or j == 0 or j == N-1)
            if boundary_layer:
                A[k, k] = 4.0 / h**2
                b[k] = rhs(X[i, j], Y[i, j])
                if i > 0:   A[k, idx(i-1, j)] = -1.0 / h**2
                if i < N-1: A[k, idx(i+1, j)] = -1.0 / h**2
                if j > 0:   A[k, idx(i, j-1)] = -1.0 / h**2
                if j < N-1: A[k, idx(i, j+1)] = -1.0 / h**2
            else:
                A[k, k] = c0 / h**2
                for di, dj, c in [(-1,0,c1),(1,0,c1),(0,-1,c1),(0,1,c1),
                                  (-1,-1,c2),(-1,1,c2),(1,-1,c2),(1,1,c2)]:
                    A[k, idx(i+di, j+dj)] = c / h**2
                # 右端:f + h²/12 * Δf,Δf 用中心差分
                f0 = rhs(X[i, j], Y[i, j])
                fxx = (rhs(X[i+1,j],Y[i+1,j]) - 2*f0 + rhs(X[i-1,j],Y[i-1,j])) / h**2
                fyy = (rhs(X[i,j+1],Y[i,j+1]) - 2*f0 + rhs(X[i,j-1],Y[i,j-1])) / h**2
                b[k] = f0 + h**2/12.0 * (fxx + fyy)
    u = np.linalg.solve(A, b).reshape(N, N)
    return h, X, Y, u

def error_norm(u, X, Y):
    ue = exact(X, Y)
    return np.max(np.abs(u - ue)), np.sqrt(np.mean((u - ue)**2))

if __name__ == "__main__":
    print(f"{'N':>4} {'五点E_inf':>12} {'五点p':>8} {'九点E_inf':>12} {'九点p':>8}")
    prev5 = prev9 = None
    for N in [8, 16, 32, 64]:
        h5, X5, Y5, u5 = solve_five_point(N)
        h9, X9, Y9, u9 = solve_nine_point(N)
        e5, _ = error_norm(u5, X5, Y5)
        e9, _ = error_norm(u9, X9, Y9)
        p5 = np.log2(prev5/e5) if prev5 else float('nan')
        p9 = np.log2(prev9/e9) if prev9 else float('nan')
        print(f"{N:>4} {e5:>12.3e} {p5:>8.2f} {e9:>12.3e} {p9:>8.2f}")
        prev5, prev9 = e5, e9

结果判读与精度分析

运行上述代码(示例网格 N=8,16,32,64)可观察到以下典型现象,这些是由格式截断误差直接推出的预期结果,而非实测数据:

  • 五点格式:E∞ 随 h 减小按约 2 阶下降,相邻网格误差比接近 4,估计收敛阶 p≈2。
  • 九点紧致格式:内部节点误差按约 4 阶下降,但边界层降阶为五点后,整体收敛阶通常介于 2 与 4 之间;在 N 较大时,边界层误差占比下降,整体阶数趋近 3~4。这正是"边界附近精度"成为紧致格式工程应用关键的原因。
  • 相同网格对比:在 N=32 时,九点格式的 E∞ 通常比五点格式小 1~2 个数量级(取决于边界层处理方式),且误差分布更集中于边界附近。
若希望整体达到接近四阶,可对边界层节点采用单侧高阶差分或引入虚拟节点(ghost point)配合边界条件消元,而不是简单降阶为五点。这是紧致格式实际部署时的常见改进方向。

边界附近精度的可视化判读

将误差按节点位置绘制热力图可直观看到:五点格式的误差在边界附近与内部量级相近,而九点格式的误差在内部极小、在边界一层明显抬升。若把边界层也升级为高阶处理,误差分布会趋于均匀。判断格式是否"真正高阶"的关键,不是看内部最大误差,而是看误差随 h 的整体衰减斜率是否与理论阶一致。

注意:九点格式的系数推导依赖 f 的光滑性(需要计算 Δf)。若右端项不光滑或仅有离散值,需改用不显含 Δf 的紧致格式变体,否则右端项差分会引入额外误差,抵消高阶收益。