简介:基于PyTorch的物理信息神经网络,正在为电网故障模拟开辟数字孪生新维度。一个21页PDF以“PyTorch物理信息神经网络在电网故障模拟中的实时推演”为主题,面向具备一定深度学习基础、希望将PINNs应用于电力系统的研究人员与PyTorch学习者;文档不仅介绍数字孪生背景与PINNs基础,还完整覆盖了电网故障模型构建、实时推演算法设计、超参数调优以及实验对比分析,并用专门章节展示实际应用案例与行业前景,整体结构完整、条理清晰。资源为单文件PDF,大小1.81MB,支持目录跳转与大纲快速定位,适合在阅读器中按章节精读。文档内容包含输入输出设计、物理约束融入、损失函数定义、优化器选择、并行计算与模型压缩等关键实现要点,能帮助读者快速理解从理论到实战的完整流程。目前已有53人学习,适合用作课程设计、项目预研或技术调研的参考资料。
1. 数字孪生电网的实时推演,卡在仿真速度与物理可信之间
一条单相接地报警弹在调度大屏上,功率曲线随即抖动。数字孪生平台需要几秒内回答"故障在哪、往哪儿发展、电压怎么衰减",但PSCAD/EMTP这类电磁暂态仿真跑一次要几分钟甚至更久。电网故障模拟的实时推演,本质上是卡在仿真速度与物理可信之间。
PyTorch物理信息神经网络(PINN)给出的第三条路是:把控制方程写进损失函数,让网络既拟合观测数据,又必须满足telegrapher方程、摇摆方程等物理约束。离线把模型训练到位,在线推演只是一次前向传播,快;同时因为方程约束焊死在损失里,故障参数超出历史样本分布时,输出也不会像纯数据代理模型那样偏离物理规律太多。这正是数字孪生的新维度。
下文的路线是:先讲清PINN的损失函数结构和电网故障方程选型,再用一段可运行的PyTorch最小实现走通残差计算与调参,接着讨论TorchScript导出、GPU批推理和场景缓存,最后给出验证基准和三个必调参数。适合正在做电网数字孪生、故障诊断代理模型或调度推演平台的工程与研究人员。
2. 物理信息神经网络原理:PDE残差、故障间断与方程选型
2.1 PINN的损失骨架:数据项与PDE残差共同约束网络
PINN用全连接网络u_θ(x,t)近似偏微分方程的解,损失是四部分加权和:
L = ωd·L_data + ωp·L_PDE + ωic·L_IC + ωbc·L_BC
其中L_PDE是内部配点上的控制方程残差均方。以有损telegrapher方程为例:
L_PDE = (1/N_p)·Σ | V_xx - LC·V_tt - (RC+GL)·V_t - RG·V |²
所有偏导都由PyTorch自动微分完成,不需要手写差分格式;唯一要注意的是对残差求梯度时开启create_graph=True,二阶物理项才能继续回传梯度。
数据项与物理项的权重关系决定模型倾向。故障样本本身稀缺,L_data往往只能覆盖少数测点,而L_PDE在整个时空域铺开,因此PINN在缺少大量故障实测数据时依然能给出物理一致的波形,这是它比纯数据代理模型更适合电网故障模拟的根本原因。权重设置的经验是:先固定ωd=1,单独算一次L_PDE的量级,让ωp与它同量级或略大;数据噪声明显时,ωp可以提到数据项的5到10倍,让物理约束做平滑。
2.2 电网故障模拟的方程选型:行波、摇摆与对称分量
2.2.1 输电线路故障行波:带损耗的telegrapher方程
故障瞬间在故障点产生入射波和反射波,行波沿线路传播,这是故障定位和故障选相的主要物理依据。有损线路的telegrapher方程是两个一阶PDE:
∂V/∂x = -L·∂I/∂t - R·I ∂I/∂x = -C·∂V/∂t - G·V
消去电流后,电压单独满足二阶PDE,波速v=1/√(LC)。PINN输出V(x,t)时直接对该二阶方程算残差;如果同时输出V和I两个分量,则分别满足两个一阶方程,输出维度变成2。前者实现简单,后者物理分解更清楚,故障点处的电流边界更容易表达,我一般在线路较短、故障定位精度要求高的场景选双输出。
2.2.2 发电机暂态:摇摆方程的PINN化写法
故障切除后的功角摆动用摇摆方程描述:
2H/ωs·d²δ/dt² = Pm - Pe - D·(ω - ωs)
它是一组关于t的常微分方程,PINN输入退化为只剩t,残差就是方程左右相减。故障对系统的影响体现在Pe表达式里:故障期间输电功率突降,Pe变小,功角开始加速。如果数字孪生要推演暂态稳定,建议把摇摆方程与网络拓扑约束一起训练,不要单独训一个功角曲线的黑箱回归。
2.2.3 三个模型的对照与选型原则
| 模型 | 方程形式 | 时间尺度 | PINN变量 | 典型用途 |
|---|---|---|---|---|
| telegrapher行波 | 两个一阶PDE | 微秒~毫秒 | V(x,t)、I(x,t) | 故障定位、故障选相 |
| 摇摆方程 | 二阶ODE | 秒级 | δ(t)、ω(t) | 暂态稳定、切机策略 |
| 对称分量网络 | 代数+PDE联立 | 毫秒~秒 | 各序电压电流 | 不对称故障暂态分析 |
选型原则一句话:要做什么样的推演,就选对应时间尺度的方程。行波层负责快速定位与故障性质识别,机电暂态层负责扰动后系统轨迹,一套完整的电网故障模拟模块通常把两层串起来,PINN在各层做代理模型,两层之间只需要交换边界时刻的电压电流。
2.3 故障时刻是强间断:时间分段与因果加权
故障的本质是拓扑跳变,t_f时刻电压存在突变。标准PINN用tanh激活,网络天然偏好光滑解,直接训练会把电压跃变学成斜坡。处理强间断有三个常用手段。
时间分段训练:在[0,t_f]和[t_f,T]上各训练一个子网络,衔接处加连续性约束L_match=|V_left-V_right|+|∂V_left/∂t-∂V_right/∂t|,让两个子网络在故障时刻取值与斜率对齐。分段之后每个子网络面对的平滑性更好,训练难度会明显下降。
因果加权:把时间轴切成K个窗口,残差按因果权重叠加,避免网络用未来信息提前抹平突变:
w_k = exp(-ε·Σ_{j<k} L_PDE(t_j))
实现上先算每个窗口的平均残差,再生成权重:
def causal_weight(loss_per_window, epsilon=0.1): # loss_per_window: 形状 (K,),每个时间窗口的PDE残差均值 weights = torch.ones_like(loss_per_window) for k in range(1, loss_per_window.shape[0]): weights[k] = torch.exp(-epsilon * loss_per_window[:k].sum()) return weightsε的经验范围是0.01到1:太小等于没做因果约束,太大会让后续窗口残差趋零、后期细节被丢弃。第三个手段是把故障位置x_f、过渡电阻R_f、故障类型编码成网络输入,让一个模型覆盖多种故障假设——这是数字孪生批量推演的前提,第四章的批推理会依赖这个设计。
3. 用PyTorch搭建故障模拟PINN:最小实现、损失组装与调参
3.1 网络结构与自动微分:torch.autograd.grad求物理残差
在PyTorch环境里,一个能跑的最小PINN只需要torch和numpy。网络定义如下:
import torch import torch.nn as nn class FaultPINN(nn.Module): def __init__(self, in_dim=2, hidden=64, depth=4, out_dim=1): super().__init__() layers = [nn.Linear(in_dim, hidden), nn.Tanh()] for _ in range(depth - 1): layers += [nn.Linear(hidden, hidden), nn.Tanh()] layers.append(nn.Linear(hidden, out_dim)) self.net = nn.Sequential(*layers) def forward(self, x, t): return self.net(torch.cat([x, t], dim=-1))输入是空间坐标x和时间t,输出是该点的电压V(x,t)。训练前把x除以线路长度、t除以观察窗口,统一归一化到[0,1];电压同样除以额定幅值基准。不做归一化时,kV量级的电压与米级坐标混在一起,初始梯度被坐标尺度带偏,前期训练极不稳定,这是PINN实践里最早踩到也最容易被忽视的坑。
残差计算依赖自动微分:
def telegrapher_residual(model, x, t, R, L, G, C): x = x.clone().requires_grad_(True) t = t.clone().requires_grad_(True) V = model(x, t) def grad(u, inp): return torch.autograd.grad( u, inp, grad_outputs=torch.ones_like(u), create_graph=True)[0] V_t = grad(V, t) V_x = grad(V, x) V_tt = grad(V_t, t) V_xx = grad(V_x, x) # 有损telegrapher电压方程: V_xx = LC*V_tt + (RC+GL)*V_t + RG*V residual = V_xx - L * C * V_tt - (R * C + G * L) * V_t - R * G * V return residual这里create_graph=True是必须的:V_tt和V_xx还要继续对网络参数求梯度,没有它高阶导数会断链。grad_outputs用ones_like保持回传梯度形状正确。函数返回每个配点上的残差张量,损失里取平方和即可。
一个收敛加速技巧:当初始电压分布V0(x)非零时,让网络只学扰动项g_θ(x,t),最终输出V(x,t)=V0(x)+g_θ(x,t)。这样初值条件被自动满足,网络把容量集中在故障产生的偏移上,比直接学完整电压场快不少。
3.2 损失函数组装:初值、边界与故障样点
配点采样分三类。内部配点在(0,l)×(0,T)里均匀随机采样,故障点(x_f,t_f)邻域按3到5倍密度加密;边界配点取x=0和x=l两条边;初始配点取t=0一条边。用torch.rand生成坐标后拼成(N,2)张量,一次性喂进残差函数。
def pinn_loss(model, pde_pts, ic_pts, bc_pts, R, L, G, C): # 内部PDE残差 res = telegrapher_residual(model, pde_pts[:, 0:1], pde_pts[:, 1:2], R, L, G, C) loss_pde = torch.mean(res ** 2) # 初始条件: t=0 处 V=V0(x) V_ic = model(ic_pts[:, 0:1], ic_pts[:, 1:2]) V0 = initial_voltage(ic_pts[:, 0:1]) loss_ic = torch.mean((V_ic - V0) ** 2) # 边界条件: 源端波形 source(t),末端按负载方程更新 V_bc = model(bc_pts[:, 0:1], bc_pts[:, 1:2]) Vb = boundary_voltage(bc_pts[:, 1:2]) loss_bc = torch.mean((V_bc - Vb) ** 2) total = loss_pde + 10.0 * loss_ic + 10.0 * loss_bc return total, {"pde": loss_pde, "ic": loss_ic, "bc": loss_bc}initial_voltage和boundary_voltage是按线路工况自定义的函数,返回归一化电压值;源端通常给阶跃或正弦电压源波形,末端按空载或负载阻抗方程更新。IC和BC的权重取10,是因为初边值样本数量远少于内部配点,不加权的话这两项会被成百上千倍的PDE残差项直接淹没。
故障场景如何进入损失:把x_f、R_f作为额外输入拼到网络输入,训练时每个batch随机抽一组故障参数;或者按2.3节做时间分段,分段边界加L_match。两条路都验证过:故障参数进输入适合批量假设推演,分段训练适合单个故障形态的精细还原,按孪生平台需要的推演粒度取舍。
3.3 训练参数与收敛检查:Adam热身、L-BFGS收尾
训练分两段:先Adam跑2000步把各损失项压到同一量级,再切L-BFGS做全批量精调。L-BFGS对二阶信息利用更充分,PINN收尾阶段用它通常能再降一个数量级的残差。
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) for step in range(2000): optimizer.zero_grad() total, loss_dict = pinn_loss(model, pde_pts, ic_pts, bc_pts, R, L, G, C) total.backward() optimizer.step() if step % 200 == 0: print(step, {k: float(v) for k, v in loss_dict.items()}) optimizer = torch.optim.LBFGS(model.parameters(), lr=0.1, max_iter=20) def closure(): optimizer.zero_grad() total, _ = pinn_loss(model, pde_pts, ic_pts, bc_pts, R, L, G, C) total.backward() return total optimizer.step(closure)超参数参考:
| 超参数 | 建议值 | 备注 |
|---|---|---|
| 隐藏层/宽度 | 4层×64 | 行波高频场景加到128 |
| 内部配点数 | 10000~50000 | 故障点邻域加密3~5倍 |
| Adam学习率 | 1e-3 | 前2000步,可配StepLR |
| L-BFGS迭代 | 100~500次 | 全批量,关闭mini-batch |
| ω_ic、ω_bc | 10 | 与配点密度匹配 |
| 激活函数 | Tanh / Sin | 残差平台期换Sin |
收敛检查别只看总损失。把loss_dict里每个分量单独打印:pde项持续下降说明物理约束在起作用;ic或bc项比pde大两个量级以上,说明初边值没学住,先调它们的权重。验证时随机生成一组未见配点算L_PDE,训练集上低、验证集偏高就是过拟合,加大配点数量比加大网络更有效。
4. 从离线训练到实时推演:模型导出、推理加速与批处理
4.1 代理模型范式:故障假设枚举在数字孪生中的位置
数字孪生的故障推演流程是:接收SCADA/PMU状态估计 → 生成故障假设集合(故障位置、过渡电阻、故障类型的组合)→ 对每个假设做暂态推演 → 把结果叠到数字孪生可视化平台上比对。传统数值仿真在这一步是串行for循环,每个假设一次完整时域积分;PINN离线训练完成后,在线只有前向传播,多个假设合并成张量一次算完。
这个"离线重训练、在线一次前向"的范式,是电网故障模拟实时推演能成立的关键。推演模块在运行时只承担矩阵乘和激活计算,CPU亲和、GPU吞吐、内存占用都可预估,调度系统敢给它一个确定的时间预算。
需要清醒的一点:代理模型不等价于数值仿真。训练分布之外的外推行为不可信,工程上要主动限制推演范围——故障位置只在巡检划分的区段内枚举,过渡电阻只在历史统计区间内抽样,超出区间就回退到传统仿真器,而不是让PINN硬给答案。这条边界写进平台设计文档,能避免上线后大量"对不上"的投诉。
4.2 TorchScript导出与毫秒级推理延迟测量
PyTorch模型工程化最直接是TorchScript:trace一次存成独立文件,推理端不依赖训练代码和自定义类定义。
model.eval() dummy_x = torch.randn(1, 1) dummy_t = torch.randn(1, 1) traced = torch.jit.trace(model, (dummy_x, dummy_t)) traced.save("fault_pinn.pt")加载与推理延迟粗测:
import time model = torch.jit.load("fault_pinn.pt") x = torch.linspace(0, 1, 256).unsqueeze(1) # 256个空间点 t = torch.full_like(x, 0.5) # 推演t=0.5s时刻的电压分布 start = time.perf_counter() with torch.inference_mode(): v = model(x, t) print(f"single-step inference: {(time.perf_counter() - start) * 1000:.2f} ms")torch.jit.trace对静态控制流够用;如果网络里有数据相关的if分支,改用torch.jit.script或ONNX Runtime。ONNX导出的优势是推理端可用onnxruntime-gpu,不绑定PyTorch版本,和数字孪生平台里的Java/C++组件更好集成。
| 加速方案 | 典型延迟量级 | 适用场景 | 注意 |
|---|---|---|---|
| TorchScript CPU | 毫秒~十毫秒 | 原型、小规模假设集 | 无额外依赖 |
| ONNX Runtime GPU | 亚毫秒~毫秒 | 生产批量推理 | 算子需兼容ONNX opset |
| TensorRT FP16 | 亚毫秒 | 大规模假设枚举 | 需校准,做精度回归 |
| GPU batch推理 | 单次延迟不增加 | 128路以上假设并行 | 受显存与batch size限制 |
4.3 多故障场景GPU批推理与结果缓存
假设集合转成batch:每个假设共用x网格,t为查询时刻,batch维度对应假设序号。
n_hypo = 128 x_grid = torch.linspace(0, 1, 256).repeat(n_hypo, 1, 1) # (128, 256, 1) t_eval = torch.full((n_hypo, 256, 1), 0.5) # (128, 256, 1) with torch.inference_mode(): v_all = model(x_grid, t_eval) # (128, 256, 1)当推演要求随时间滚动输出时,把t_eval换成时间序列,一次前向就能拿到整段波形。这个特性对实时推演很有价值:数值求解器受CFL条件限制必须逐步推进,PINN则是任意t直接求值,时间分辨率只由采样决定,没有数值稳定性约束,这也是物理信息神经网络适合做推演代理的深层原因。
场景缓存针对高频请求设计:故障推演往往集中在少数几类故障和区段上。用functools.lru_cache按(故障类型, 区段, 过渡电阻分位)做键,缓存关键母线电压波形,可以有效摊薄GPU负载。
from functools import lru_cache @lru_cache(maxsize=256) def simulate_scenario(fault_type, segment_id, r_f): # 返回该场景关键母线电压波形(序列化字节) ...缓存键必须带上拓扑版本号——线路检修、合环操作会改变系统运行方式,不带版本号的缓存会给出已失效的推演结果。这个细节在联调阶段不显眼,但恰恰是数字孪生平台"看起来实时、实际在吃老数据"的主要根源。
5. 电网故障PINN的验证基准与三个必调参数
5.1 用FDTD差分求解器做误差基准
PINN训练完先别急着接进数字孪生平台,第一步是跟数值解对齐。对单条线路,用FDTD leapfrog格式写参考解最快。
import numpy as np # 单线无损telegrapher方程的leapfrog FDTD参考解 dx = 10.0 # 空间步长(m) dt = 0.8 * dx / v # CFL = 0.8,v为波速 Nx = int(line_length / dx) + 1 V = np.zeros(Nx) # 电压节点 I = np.zeros(Nx - 1) # 电流支路(交错网格) for n in range(int(T_total / dt)): V[1:-1] -= (dt / (C * dx)) * (I[1:] - I[:-1]) I[:] -= (dt / (L * dx)) * (V[1:] - V[:-1]) V[0] = source_waveform(n * dt)这只是参考骨架:实际对齐有损线路时要在差分更新里补R和G项,故障点处接故障电阻支路的边界条件。误差指标用NRMSE=‖V_pinn-V_fdtd‖₂/‖V_fdtd‖₂,并且把t_f前后分开统计——故障时刻附近的误差通常会比平稳段高一到两个数量级,分开统计才能看出时间分段和因果加权到底起没起作用。
提示:FDTD参考解本身也有离散误差,空间步长要取到PINN网格密度的10倍以上,否则是用一个有偏的基准去评判模型。
5.2 三个必调参数与失败样式
第一个是因果窗口数K与ε。K取20到50,ε从0.1起步:故障时刻之后波形提前衰减,说明因果约束太弱;波形过于平直则说明ε太大,后期残差权重被压没。
第二个是故障边界配点密度。x_f和t_f邻域的配点密度取全局的3到5倍,用拒绝采样或在故障点周围叠加高斯扰动生成。故障附近的残差是整个训练里最难压的部分,配点不加密,模型会把有限容量浪费在好学的平滑区段上。
第三个是激活函数与Fourier特征。行波叠加了高频分量,tanh容易陷入谱偏差,表现为pde残差卡在平台不降。这时换成sin激活,或直接在网络入口加Fourier特征层:
class FourierFeature(nn.Module): def __init__(self, in_dim=2, freq=32, scale=10.0): super().__init__() B = torch.randn(in_dim, freq) * scale self.register_buffer("B", B) # 随机固定,不参与训练 def forward(self, x, t): u = torch.cat([x, t], dim=-1) return torch.cat([torch.sin(u @ self.B), torch.cos(u @ self.B)], dim=-1)Fourier特征把低频输入映射到多组频率的正余弦上,网络更容易表达高频瞬态;B矩阵固定不训练,只做特征工程,不会增加过拟合风险。
5.2.1 失败现象与处理对照
| 现象 | 原因 | 处理 |
|---|---|---|
| 总损失下降但波形整体偏小 | 数据项权重过大,PDE项被淹没 | 降低ωd或提高ωp到同量级 |
| 故障时刻被学成光滑斜坡 | tanh谱偏差 | 时间分段+因果加权或Fourier特征 |
| 训练后期loss振荡 | Adam未收敛就切L-BFGS | 先让Adam步长下loss稳定再切换 |
| 在线误差持续偏大 | 系统拓扑或工况漂移 | 用最近PMU窗口数据做在线微调 |
5.2.2 与PMU数据融合的残差监控
部署后最实用的监控指标是PMU实测与PINN推演的残差。连续3个PMU采样点的误差超过额定电压的5%,说明当前场景已滑出训练分布。常见做法是保留最近30分钟PMU窗口数据,做一次短周期在线微调:只更新数据项损失,冻结物理项权重,让模型快速跟住运行方式变化;等下一次离线重训练窗口到来时,再把微调参数固化回主模型。
本文还有配套的精品资源,点击获取