1. 一维热传导方程PINN求解

任务目标

使用物理信息神经网络(PINN)求解一维热传导方程,在给定初始条件与Dirichlet边界条件下,预测温度场 \( u(x,t) \) 随空间与时间的变化。本案例采用方程:

\[ \frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2}, \quad x \in [0, L], \ t \in [0, T] \]

其中热扩散系数 \(\alpha = 1\),空间域 \(L = 1\),时间域 \(T = 1\)。初始条件 \( u(x,0) = \sin(\pi x) \),边界条件 \( u(0,t) = u(1,t) = 0 \)。该问题存在解析解 \( u(x,t) = e^{-\pi^2 t} \sin(\pi x) \),可用于定量验证PINN预测精度。

环境依赖

需要 Python 3.9+,并安装以下库:

pip install torch numpy matplotlib

PINN实现

核心思路:构建一个全连接神经网络 \( u_\theta(x,t) \),通过自动微分计算损失函数。总损失由三部分构成:

  • PDE残差损失:在内部配点处强制满足热传导方程;
  • 初始条件损失:在 \( t=0 \) 处匹配初始温度分布;
  • 边界条件损失:在 \( x=0 \) 和 \( x=1 \) 处保持温度为0。

通过梯度下降最小化总损失,训练后的网络即可预测任意时刻的温度分布。

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt

torch.manual_seed(42)

# 物理参数
alpha = 1.0
L = 1.0
T_max = 1.0

# 神经网络模型
class PINN(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(2, 50),
            nn.Tanh(),
            nn.Linear(50, 50),
            nn.Tanh(),
            nn.Linear(50, 50),
            nn.Tanh(),
            nn.Linear(50, 1)
        )
    
    def forward(self, x, t):
        u = self.net(torch.cat([x, t], dim=1))
        return u

# 自动微分求导数
def pde_residual(model, x, t):
    x.requires_grad_(True)
    t.requires_grad_(True)
    u = model(x, t)
    u_t = torch.autograd.grad(u, t, grad_outputs=torch.ones_like(u), create_graph=True)[0]
    u_x = torch.autograd.grad(u, x, grad_outputs=torch.ones_like(u), create_graph=True)[0]
    u_xx = torch.autograd.grad(u_x, x, grad_outputs=torch.ones_like(u), create_graph=True)[0]
    return u_t - alpha * u_xx

# 生成训练数据
N_f = 10000  # 内部配点数
N_b = 200    # 边界点数
N_i = 200    # 初始点数

# 内部配点(拉丁超立方采样)
x_f = torch.rand(N_f, 1) * L
t_f = torch.rand(N_f, 1) * T_max

# 边界点:x=0 和 x=1
t_b = torch.rand(N_b, 1) * T_max
x_b0 = torch.zeros(N_b, 1)
x_b1 = torch.ones(N_b, 1) * L

# 初始点:t=0
x_i = torch.rand(N_i, 1) * L
t_i = torch.zeros(N_i, 1)

# 训练
model = PINN()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_history = []

for epoch in range(3000):
    optimizer.zero_grad()
    
    # PDE损失
    f = pde_residual(model, x_f, t_f)
    loss_pde = torch.mean(f**2)
    
    # 边界损失
    u_b0 = model(x_b0, t_b)
    u_b1 = model(x_b1, t_b)
    loss_bc = torch.mean(u_b0**2) + torch.mean(u_b1**2)
    
    # 初始损失
    u_i = model(x_i, t_i)
    u_exact_i = torch.sin(np.pi * x_i)
    loss_ic = torch.mean((u_i - u_exact_i)**2)
    
    # 总损失
    loss = loss_pde + loss_bc + loss_ic
    loss.backward()
    optimizer.step()
    
    loss_history.append(loss.item())
    if epoch % 500 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item():.6e}')

# 验证:与解析解对比
x_test = torch.linspace(0, L, 100).reshape(-1, 1)
t_test = torch.tensor([[0.25], [0.5], [1.0]])

plt.figure(figsize=(10, 4))
for i, t_val in enumerate(t_test):
    t_plot = torch.ones_like(x_test) * t_val
    u_pred = model(x_test, t_plot).detach().numpy()
    u_exact = np.exp(-np.pi**2 * t_val.item()) * np.sin(np.pi * x_test.numpy())
    plt.subplot(1, 3, i+1)
    plt.plot(x_test.numpy(), u_exact, 'k-', label='Exact')
    plt.plot(x_test.numpy(), u_pred, 'r--', label='PINN')
    plt.title(f't={t_val.item()}')
    plt.xlabel('x')
    plt.legend()

plt.tight_layout()
plt.savefig('pinn_1d_heat.png', dpi=150)
plt.show()

# 计算L2相对误差
t_final = torch.ones_like(x_test)
u_pred_final = model(x_test, t_final).detach().numpy()
u_exact_final = np.exp(-np.pi**2) * np.sin(np.pi * x_test.numpy())
l2_error = np.linalg.norm(u_pred_final - u_exact_final) / np.linalg.norm(u_exact_final)
print(f'L2 relative error at t=1: {l2_error:.6f}')

运行方式

将上述代码保存为 pinn_heat1d.py,在终端执行:

python pinn_heat1d.py

训练约3000轮(CPU上约2~3分钟),程序会输出每500轮的损失值,并生成 pinn_1d_heat.png 对比图。

验证与判定标准

  • 损失收敛:训练结束时总损失应低于 \(10^{-4}\),且PDE残差损失持续下降;
  • 可视化对比:图中红色虚线(PINN预测)应与黑色实线(解析解)基本重合,尤其在 \( t=0.5 \) 和 \( t=1.0 \) 时刻;
  • 定量指标:终端输出的L2相对误差应小于 \(5\%\),表明PINN成功预测了温度衰减过程。

若误差偏大,可增加训练轮数至5000或调整网络宽度(如改为80个神经元)。

常见问题

  • 训练不收敛:检查学习率是否过大(建议1e-3),或尝试更换激活函数为 Sin;
  • 边界处震荡:适当增加边界配点数量,或对边界损失施加更高权重;
  • 长时间预测退化:当 \( t \) 较大时温度趋近于0,可对损失函数按时间加权,或分段训练。