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,可对损失函数按时间加权,或分段训练。