3. 案例3:PINN求解一维Burgers方程并分析激波捕捉

问题设定与方程无量纲化

一维含粘性 Burgers 方程是检验 PINN 激波捕捉能力的经典基准:

u_t + u·u_x = ν·u_xx,   x ∈ [-1, 1],  t ∈ [0, 1]
u(x, 0) = -sin(πx)
u(-1, t) = u(1, t) = 0

粘性系数 ν 控制陡峭程度:ν 越小,t≈0.5 附近梯度越陡,趋近于激波;ν 越大,解越平滑。本例取 ν = 0.01/π ≈ 0.00318 与 ν = 0.1/π ≈ 0.0318 两组做对照,前者是 PINN 文献中的标准算例,后者用于观察平滑化后的误差分布差异。

PINN 的损失由三部分组成:物理残差损失 L_r(在配点上计算 PDE 残差)、初值损失 L_0、边界损失 L_b。关键难点在于:均匀随机配点在激波附近密度不足,残差难以被压低,因此需要引入基于残差的自适应配点或在陡峭区加密的采样策略。

网络结构与训练实现

采用 4 层隐藏层、每层 64 个神经元的 tanh MLP,输入 (x, t) 归一化到 [-1, 1],输出 u。使用一阶自动微分计算 u_t、u_x,二阶自动微分计算 u_xx。优化分两阶段:先用 Adam(学习率 1e-3,10000 步)粗收敛,再用 L-BFGS 精调。

import torch
import torch.nn as nn

class PINN(nn.Module):
    def __init__(self, width=64, depth=4):
        super().__init__()
        layers = [nn.Linear(2, width), nn.Tanh()]
        for _ in range(depth - 1):
            layers += [nn.Linear(width, width), nn.Tanh()]
        layers += [nn.Linear(width, 1)]
        self.net = nn.Sequential(*layers)

    def forward(self, x, t):
        return self.net(torch.cat([x, t], dim=1))

def pde_residual(model, x, t, nu):
    x.requires_grad_(True); t.requires_grad_(True)
    u = model(x, t)
    u_t = torch.autograd.grad(u, t, torch.ones_like(u), create_graph=True)[0]
    u_x = torch.autograd.grad(u, x, torch.ones_like(u), create_graph=True)[0]
    u_xx = torch.autograd.grad(u_x, x, torch.ones_like(u_x), create_graph=True)[0]
    return u_t + u * u_x - nu * u_xx

def sample_uniform(n, device):
    x = torch.rand(n, 1, device=device) * 2 - 1
    t = torch.rand(n, 1, device=device)
    return x, t

def sample_shock_refined(n, device, x_c=0.0, sigma=0.15):
    # 在 x≈0 的陡峭区按高斯密度加密,其余均匀
    n_shock = n // 2
    x_s = torch.randn(n_shock, 1, device=device) * sigma + x_c
    x_s = x_s.clamp(-1, 1)
    x_u, t_u = sample_uniform(n - n_shock, device)
    x = torch.cat([x_s, x_u], dim=0)
    t = torch.cat([torch.rand(n_shock, 1, device=device), t_u], dim=0)
    return x, t

训练循环中,物理损失用均方残差,初值与边界损失用 MSE。下面给出单步损失计算的核心片段:

def total_loss(model, nu, n_col, n_ic, n_bc, device, refined=False):
    if refined:
        xc, tc = sample_shock_refined(n_col, device)
    else:
        xc, tc = sample_uniform(n_col, device)
    r = pde_residual(model, xc, tc, nu)
    loss_r = (r ** 2).mean()

    x_ic = torch.rand(n_ic, 1, device=device) * 2 - 1
    t_ic = torch.zeros_like(x_ic)
    u_ic = -torch.sin(torch.pi * x_ic)
    loss_0 = ((model(x_ic, t_ic) - u_ic) ** 2).mean()

    t_bc = torch.rand(n_bc, 1, device=device)
    xL = -torch.ones_like(t_bc); xR = torch.ones_like(t_bc)
    loss_b = ((model(xL, t_bc)) ** 2).mean() + ((model(xR, t_bc)) ** 2).mean()

    return loss_r + 100 * loss_0 + 100 * loss_b

初值与边界损失权重取 100,是因为它们的量级远小于残差项,需要放大才能有效约束。若权重过小,网络会优先压低残差而忽略边界,导致解在边界附近漂移。

激波区误差分布与配点策略对比

训练完成后,在规则网格(x 方向 256 点、t 方向 100 点)上评估预测解,并与高分辨率数值解(如 Chebfun 或有限差分细网格)对比。重点观察 t ∈ [0.4, 0.7]、x ∈ [-0.3, 0.3] 的陡峭梯度区域。下图展示了两种配点策略下误差在空间上的分布差异:

均匀配点 vs 激波区加密配点:误差分布对比 均匀随机配点 x 误差 误差尖峰 配点均匀分布,激波区密度不足 激波区加密配点 x 误差 误差降低 x≈0 附近配点加密,残差被有效压低 判读要点: ① 均匀配点时误差峰值集中在 x≈0 的陡峭区;② 加密后峰值显著下降,但需注意过度加密可能牺牲平滑区精度; ③ ν 越小,激波越陡,加密的必要性越强;ν 较大时两种策略差异缩小。

从图中可以读出两个关键结论:其一,误差峰值位置与解的陡峭区高度重合,说明 PINN 的困难不在于全局拟合,而在于局部高频结构的捕捉;其二,加密配点能显著压低峰值误差,但若加密范围过窄,平滑区可能因配点减少而精度下降,因此需要平衡。

粘性系数对误差分布的影响

固定配点策略(均匀 10000 点 + 激波区加密 5000 点),分别训练 ν = 0.01/π 与 ν = 0.1/π 两组模型,在相同测试网格上统计误差。下表给出示例性的相对 L2 误差与激波区最大误差(数值为教学示例,实际结果随随机种子与训练步数波动):

粘性系数 ν配点策略全域相对 L2 误差激波区最大绝对误差
0.01/π ≈ 0.00318均匀约 8.5e-2约 1.2e-1
0.01/π ≈ 0.00318激波区加密约 3.2e-2约 4.5e-2
0.1/π ≈ 0.0318均匀约 2.1e-2约 3.0e-2
0.1/π ≈ 0.0318激波区加密约 1.5e-2约 1.8e-2

规律很清晰:ν 越小,解的梯度越陡,均匀配点的误差越大,加密带来的收益也越明显;ν 增大后解本身更平滑,两种策略的差距收窄。这说明配点策略的选择应与问题的陡峭程度匹配,而不是一味加密。

实操要点与验证方法

  • 残差监控:训练过程中分别记录 L_r、L_0、L_b,若 L_b 长期不降,说明边界权重偏小或网络容量不足。
  • 自适应加密:可在训练若干步后,按残差大小对配点做重采样(RAD 类方法),把新配点集中到残差高的区域,比固定高斯加密更通用。
  • 验证基准:用有限差分细网格或 Chebfun 生成参考解,在 t=0.5 的剖面上对比预测曲线,重点看激波位置与幅值是否偏移。
  • 随机性控制:固定随机种子,至少跑 3 次取均值,避免单次训练结果误导结论。
若发现激波位置整体偏移而非仅幅值误差,通常不是配点问题,而是初值/边界约束不足或网络对高频分量表达能力有限,可尝试加深网络或引入 Fourier 特征嵌入。
ν 很小时(如 0.001/π),纯 PINN 可能难以稳定收敛到陡峭解,此时应优先考虑自适应配点、课程学习(先大 ν 后小 ν)或与数值格式混合的策略,不要盲目增加训练步数。