简介:面向核工程与机器学习交叉领域的学习者与实践者,这份压缩包聚焦物理信息神经网络(PINN)在中子学中的应用,围绕有效增殖因子计算、多维中子扩散方程求解及逆问题建模等核心方向,整理了一套可直接运行的Python实验代码。压缩包共39个文件,主体为28个.py程序,覆盖不同维度、不同边界条件及并行搜索变体;另有xml工程配置文件、dat训练过程数据以及说明文档,便于追踪实验轨迹与复现结果。包体仅269KB,轻量紧凑,适合快速部署。目前已有46人学习下载,常被用于毕业设计、课程设计或期末大作业的参考实现。通过阅读代码和运行记录,学习者既能理解物理定律嵌入神经网络的实现逻辑,也能对比硬边界条件与软约束对求解精度的影响;同时,从一维到多维、从正问题到反问题的脚本设计,为开展后续研究提供了完整的技术基线。
1. 机器学习开始啃中子学:这个方向解决什么问题、适合谁
做反应堆物理或屏蔽计算的人,手里那套流程通常很稳重:几何建模、网格划分、蒙特卡罗或确定论求解,专业软件跑上一晚,出来一堆通量和剂量结果。机器学习这几年开始入侵这个领域,PINN把中子学相关的偏微分方程直接编进神经网络损失函数,一维平板扩散问题几百行代码就能和解析解对齐到几个百分点,几何改来改去也不用重新画网格。这篇文章就把这个方向从头捋一遍:PINN到底解决传统中子学计算的哪个痛点、最小可复现模型怎么搭、参数怎么调、坑在哪里。适合做核设计仿真的工程师,也适合不满足于 MNIST 手写数字、想找物理落地场景的机器学习入门者。
2. 从中子扩散方程到 PINN 损失函数:把物理约束编译进神经网络
2.1 两套方程:输运方程与扩散近似的比较
中子学关注的是中子在介质里的数量守恒,最严格的描述是中子输运方程。空间 3 维、能量按能群离散、角度 2 维,加上时间自变量,轻轻松松到 7 个以上。直接数值求解这个方程的代价极高,所以工程上绝大多数场景先用扩散近似退一步。稳态单群扩散方程写出来就是:
-d/dx ( D dφ/dx ) + Σa φ = S
其中 D 是扩散系数,Σa 是宏观吸收截面,S 是源项。这个方程形式简单,但材料系数 D 和 Σa 随空间位置和能群剧烈变化。一道屏蔽墙里可能叠着水、铁、硼聚乙烯好几层,通量剖面在材料边界处产生陡峭梯度;实际堆芯模型又是多组件、多能群,几十个能群叠在一起,方程数量成倍增长。这就是为什么中子学计算至今还是核工程设计里的主要耗时环节。
这里还有一个中子学特有的细节:真空边界条件并不是几何边界上通量为 0,而是外推边界上通量为 0。外推距离近似等于 2D,几何边界处通量实际是一个小正数。入门算例里很多人直接按几何边界 φ=0 处理,能跑通,但和参考解对比时就会差几个百分点,这个坑后面避坑章节会专门展开。
PINN 这几年在中子学方向能冒出来,是因为它和传统数值方法有个本质差别:传统做法先把解离散到网格点上,再在离散空间里解代数方程;PINN 则把一个连续函数(网络输出)直接代入偏微分方程,用自动微分求导数,把“解方程”换成“最小化残差”。两者思路完全不同,所以老方法遇到的网格依赖、统计涨落、离散方向选择问题,到了 PINN 这里换了一组新问题。
2.2 传统方法的三个瓶颈和 PINN 的对应关系
有限差分和有限元方法最怕两件事:网格生成成本和加密成本。几何复杂还好说,强非均匀材料交替出现时,网格要细化到能抓住通量陡峭梯度,网格量一上去,线性求解器的内存和时间立刻爆炸。PINN 这边没有网格,采样点可以随时加密,改几何只改边界采样位置,网格重画的成本基本消失。
蒙特卡罗方法的问题在统计涨落,误差按样本数的二分之一次方收敛,想多一位精度就要多一百倍计算量。PINN 的误差受训练迭代和网络容量控制,维度升高时没有蒙特卡罗那么明显的方差爆炸,但代价转移到损失函数各项的权重调节上。确定论 SN 方法对离散方向阶数和能群数目的选择高度敏感,阶数不够通量就振荡;PINN 没有离散方向,用的是连续角度输入,代价变成训练不稳定。
顺带说一句,这个方向不是贝叶斯网络那种概率图模型,也不是黑盒分类器,它属于物理约束神经网络那一类。核心思想是靠物理方程本身做正则化,所以训练数据少、甚至没有测量数据也能学得起来。如果你之前只接触过图像分类或时序预测,这里最大的观念转变是:网络的监督信号不再来自标签,而是来自方程残差。
2.3 损失函数三项:残差、边界、数据
PINN 的核心是把方程变成损失项。第一项是残差项,把网络预测的 φ 代入扩散方程左减右:
res = -D * d²φ/dx² + Σa φ - S
希望它在定义域内处处为 0。第二项是边界条件残差,真空边界要求 φ=0,那就在 x=0 和 x=H 两个点上计算 φ² 并求和。第三项是可选的测量数据项,布置若干探测器测到通量值,网络输出与测量值之差也进损失。总损失写成:
L = λ_res * mean(res²) + λ_bc * (φ(0)² + φ(H)²) + λ_data * mean((φ - φ_meas)²)
λ 是一组权重系数,后面整篇讨论的重点几乎都落在 λ 怎么设。把连续方程变成三项损失,按“从连续方程到损失函数”的机器学习应用流程落地,操作顺序就四步:一、写出你关心方程的具体形式;二、搭建网络输出物理量;三、用自动微分求方程残差;四、加权累加三个损失项开始训练。
残差项为什么可以直接对空间点做平方,而不是像有限元那样先做弱形式积分?原因是自动微分。PINN 对 x 求一阶导、二阶导用的是解析链式法则,没有差分模板,也就没有截断误差。这一点决定了 PINN 和传统方法的精度来源完全不同,也是它能在很少的数据下把方程约束得很死的原因。到这里原理层面的内容就完整了:连续方程、三项损失、自动微分求导,下一章直接给最小可跑的代码,先把一维平板扩散问题在 PyTorch 里跑通。
3. 用 PyTorch 跑通一维中子扩散的最小 PINN:代码与参数说明
3.1 问题设定与解析解
从最简单的一维均匀平板开始。一块厚度 H=100 cm 的平板,材料均匀,单群扩散参数 D=1.0 cm,Σa=0.05 /cm,固定源 S=1.0 /(cm³·s),两侧真空边界。这个算例有解析解,适合和 PINN 结果对照。
前面提过真空边界实际上是在外推边界处通量降为 0,外推距离 d=2D≈2.0 cm。为了入门代码简单,这里直接在 0 和 H 处强制 φ=0,这叫几何边界近似,工程上误差可接受;避坑章节会说外推距离没处理会带来什么后果。解析解公式:
φ(x) = (S/Σa) * [1 - cosh(κ(x - H/2)) / cosh(κ H/2)]
其中 κ = sqrt(Σa / D)。中心点通量约 S/Σa = 20,边界处趋近 0,剖面呈钟形。
3.2 最小可复现代码
import torch import torch.nn as nn # ---------- 物理参数与几何参数 ---------- D = 1.0 # 扩散系数 cm Sigma_a = 0.05 # 宏观吸收截面 /cm S = 1.0 # 固定源强 /(cm^3 * s) H = 100.0 # 平板厚度 cm kappa = (Sigma_a / D) ** 0.5 # ---------- 网络:输出就是中子通量 phi ---------- class PINN(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Linear(1, 64), nn.Tanh(), nn.Linear(64, 64), nn.Tanh(), nn.Linear(64, 64), nn.Tanh(), nn.Linear(64, 1) # 最后一层不带激活,phi 可正可负 ) def forward(self, x): return self.net(x) net = PINN() optimizer = torch.optim.Adam(net.parameters(), lr=1e-3) # ---------- 采样:内部 256 个点,边界 2 个点 ---------- x_int = torch.linspace(0, H, 256).reshape(-1, 1) x_int.requires_grad_(True) # 让 x 参与自动微分 x_bc = torch.tensor([[0.0], [H]]) # 真空边界 # ---------- 解析解,用于训练后对比 ---------- def phi_ref(x): return (S / Sigma_a) * (1 - torch.cosh(kappa * (x - H / 2)) / torch.cosh(kappa * H / 2)) # ---------- 训练循环 ---------- for step in range(5000): phi = net(x_int) # 自动微分求二阶导:phi -> dphi -> d2phi dphi = torch.autograd.grad(phi, x_int, grad_outputs=torch.ones_like(phi), create_graph=True)[0] d2phi = torch.autograd.grad(dphi, x_int, grad_outputs=torch.ones_like(dphi), create_graph=True)[0] # 残差项:-D * phi'' + Sigma_a * phi - S 处处趋近 0 res = -D * d2phi + Sigma_a * phi - S loss_res = torch.mean(res ** 2) # 边界项:真空边界通量为 0 phi_bc = net(x_bc) loss_bc = torch.mean(phi_bc ** 2) # 加权求和,边界权重要给大 loss = loss_res + 50.0 * loss_bc optimizer.zero_grad() loss.backward() optimizer.step() if step % 500 == 0: print(f"step {step:4d} loss {loss.item():.3e} res {loss_res.item():.3e} bc {loss_bc.item():.3e}") # ---------- 对比解析解 ---------- with torch.no_grad(): x_plot = torch.linspace(0, H, 101).reshape(-1, 1) phi_net = net(x_plot) phi_exact = phi_ref(x_plot) rel_err = torch.mean(torch.abs(phi_net - phi_exact) / phi_exact) print(f"相对误差均值: {rel_err.item():.3%}")逻辑说明:网络输入是位置 x,输出是通量 φ。4 层、每层 64 个神经元、tanh 激活,最后一层线性输出,因为通量在网络前向传播时不做非负约束,正负都由训练决定。自动微分两次得到二阶导,这一步替代了传统数值方法里的差分模板,没有截断误差,但注意必须保留 create_graph=True,否则第二次求导时计算图已经断了。残差项期望每一点方程左右两边之差为 0;边界项期望两端为 0;权重 50 是经验值,意义下一章详述。相对误差在训练几千步后通常能到几个百分点;没到的话先看 loss 曲线,大概率是边界权重太小或网络宽度不够。
这段代码里还有两个小细节。一是 x_int 用了 requires_grad_,而不是在输入上直接 requires_grad=True,因为后续要对这个具体张量求二阶导;二是第二个 autograd.grad 的 grad_outputs 用的是 ones_like(dphi),保持形状一致,漏了这个维度会直接报错。训练时每 500 步打印三个量:总 loss、残差 loss、边界 loss,观察它们的量级关系,和解析解对比时建议在边界附近少看逐点误差,多剖面整体看,因为边界附近精确解趋近 0,相对误差会被分母放大。
3.3 训练配置与结果检查
上面代码默认了三个关键配置。学习率 1e-3 配合 Adam,在大多数 PINN 入门算例里都算稳;学习率再大会出现 loss 锯齿,再小训练变慢,5000 步不够收敛。内部采样 256 个点,实际用少量点先看趋势再加密是常见做法,不用一上来就几千个点。激活函数选了 tanh 而不是 ReLU,原因很直接:ReLU 的二阶导是 0,而残差项里面有明显的二阶导,网络输出对 x 的二阶导为 0 时,扩散残差项就只剩 Σa φ - S,相当于把整个方程压成了代数形式,结果必然不对。这一点踩的人最多。
检查结果的顺序也有讲究:先看积分量,比如总通量 ∫φ dx;再看剖面形状;最后才看逐点误差。总通量差 5% 以内说明物理上大体对了;剖面两端是否下压、中心是否隆起,能看出边界条件和源项有没有配平。训练停止时机不要僵硬地用固定步数,可以按 loss 平台判断:连续 500 步总 loss 下降小于 5%,就记录当前权重,把学习率减半再续跑 2000 步,通常还能从平台往下走一段。这一步在反复调参时很省时间。
4. 五个必调参数与参考解对比:让 PINN 从中子学可复现到可用
这些参数看着像玄学,其实都是机器学习实战里常见的旋钮,只是 PINN 场景里每个旋钮都有了物理含义。下面按调参顺序讲,每个参数都给出推荐范围和检查方法。
4.1 边界条件权重:默认给 1 会翻车
第 3 章代码里边界权重给了 50,这个数字不是拍脑袋。残差项是 256 个点的均值,边界项只有 2 个值的均值,数量级天然不对等。残差刚开始通常在 1e-1 到 1e0 量级,边界损失在 1e-2 量级,如果两项权重都是 1,梯度被残差项独占,网络优先去拟合内部方程,边界约束学得很慢,结果就是边界处通量翘起来,总通量偏高。血泪经验:先把边界权重放在 50 到 200 之间,预跑 500 步,看边界 loss 是否压到残差 loss 的 1/10 以下;没压下来就再把 λ_bc 翻倍。权重不是玄学,本质是在方程和边界之间做帕累托折中。工程上原则很朴素:边界先对,再谈内部。
4.2 激活函数与网络宽度:二阶导对网络的要求
这一项在 3.3 提过,这里展开。tanh、sigmoid 这类光滑激活函数二阶导连续,适合扩散方程;ReLU 系列的二阶导几乎处处为 0,直接废掉残差项里的 -D d²φ/dx²。宽度上我一般从 64 起步,一维问题足够;如果发现 loss 怎么调都在 1e-4 以上降不下去,把宽度加到 128,同时训练步数加倍,看看是不是网络容量限制。也可以试试 Swish 或 sine 激活,sine 对高频振荡场景表现好,对应到中子学里就是多层屏蔽墙中的通量起伏问题。但工程上先别急着换,记住“激活函数选择”这个旋钮存在即可,默认 tanh 能覆盖大部分扩散类问题。
4.3 采样点分布:均匀随机与边界加密
刚开始用 linspace 均匀采样没问题,但真实模型往往是不均匀的:材料边界附近通量梯度大,需要加密。常见做法是分区采样,每个材料区内部均匀采,边界两侧额外各加 20 到 50 个点,再配合随机采样打散结构化偏差。随机采样用 Latin Hypercube 比纯随机更均匀,PyTorch 里可以直接用 torch.rand 加区间偏移实现。采样密度影响的是残差项对空间各处的权重,点越密,该区域约束越强,这正好用来“告诉”网络哪里重要。注意内部点和边界点的数量不要悬殊太大,否则边界约束会稀释得厉害。
4.4 材料突变界面:单一网络跨两种材料的处理
如果把 D 和 Σa 写成 x 的分段函数直接喂给一个网络,网络会尝试用连续函数去拟合通量,结果界面处梯度被磨平。通量本身在界面上连续,但它的一阶导数在界面上不连续,这是扩散方程在材料界面处的物理要求:净流连续。常见做法两种。一是单一网络但把 D(x) 和 Σa(x) 作为输入特征同时喂进去,让网络学会“看到截面组合就切换行为”;二是更稳的区域分解,两种材料各有一个网络,界面处加两个约束:通量连续、流连续(-D dφ/dx 连续)。我一般先用第二种,可靠且排错容易,缺点是网络数量翻倍。单一网络跨界面时还有一个软化技巧:把界面附近的截面写成线性过渡,避免自动微分在分段函数处输出奇异值。
4.5 无源临界问题与噪声数据:两个特殊的参数场景
前面都是有固定源 S 的情况。临界问题 S=0,方程齐次,零解天然满足方程,网络很容易全部学成 0。处理办法是加归一化约束,比如固定全堆通量积分等于某个参考值,或者把裂变源项写成含 keff 的特征值问题,把 keff 也变成可训练参数,这就进入了特征值 PINN 的领域。噪声数据则是另一个方向:实验测量值噪声大,数据项权重 λ_data 别给太高,工程上按测量误差的倒数来定权重才是合理做法,误差大的测点自动获得低权重,这和贝叶斯思想是相通的。
下面这张参数速查表是我现在做一维扩散问题时的起点,按这个范围先跑起来,再根据 loss 曲线微调。
| 参数 | 推荐范围 | 检查方式 |
|---|---|---|
| 边界权重 λ_bc | 50~200 | 边界 loss 小于残差 loss 的 1/10 |
| 数据权重 λ_data | 1/σ²(σ 为测量噪声) | 数据拟合误差与噪声量级一致 |
| 激活函数 | tanh 起步,sine 备选 | 二阶导是否连续 |
| 网络宽度 | 64~128 | loss 平台是否因容量不足 |
| 内部采样密度 | 256~1024 | 加密后结果变化小于 1% |
这章五个旋钮调完,一维扩散问题基本就到“可用”状态了。但工程上真正费时间的不是调参,而是排错,下一章列几条最常见的翻车现场。
5. 中子学 PINN 常见翻车清单:现象、原因与排查路径
这套清单是从实际训练中攒出来的,每一条都经历过“看起来很对,细看全错”的阶段。排查时按现象对号入座即可,不用从头到尾重跑。
5.1 残差降了但解整体偏离参考值
现象:训练中 loss 稳步下降,边界项也压得很低,但画出的通量剖面和解析解整体错位,要么整体偏高,要么形状对但幅值不对。
原因:残差=0 可以同时满足很多函数,方程只在“网络能表达的函数空间”内被约束。当边界条件太弱,网络把 φ 整体抬高一个常数,残差里 -S 的部分被吸收项补偿掉一部分,方程看似满足,数值却偏了。
解决:边界权重拉到 100 以上重新训练;还不行就检查是不是把 S、Σa 写错单位。一维模型的量纲必须一致,扩散系数单位是 cm,吸收截面单位是 /cm,源项单位是 /(cm³·s),任何一项差一个量级,剖面形状都会变形。
5.2 边界通量翘起来,不是零
现象:x=0 和 x=H 处通量不是 0,出现明显上翘或下垂。
原因:几何边界近似和外推边界不一致。真空边界真正成立的位置在几何边界外推距离处,直接强制几何边界为 0 属于额外约束。网络为了同时满足“几何边界为 0”和“内部方程残差最小”,会在边界附近付出额外的形变代价。
解决:入门算例直接在几何边界强约束没问题,但对比参考解时把外推距离 d=2D 考虑进去,把求解区间扩到 [-d, H+d],边界点放在外推边界上,内部点仍按几何区间采。这样网络在几何边界附近有了自由空间,剖面会自然形成物理上正确的形状。
5.3 训练中途 loss 发散到 nan
现象:几百步还好,一两千步 loss 跳变到 nan,重启后同样位置复发。
原因:自动微分嵌套产生的高阶梯度在计算图里反复传播,梯度爆炸;tanh 在饱和区导数趋近 0,梯度回传消失,网络参数又容易大范围跳动。
解决:加梯度裁剪,optimizer.step() 前执行 torch.nn.utils.clip_grad_norm_(net.parameters(), 1.0);学习率降到 3e-4;输入 x 先归一化到 [0,1] 或 [-1,1],避免大数值输入让网络初值进入饱和区。第三条往往是最有效的。
5.4 与蒙特卡罗结果永远差 3 个百分点
现象:PINN 算的总通量和 MCNP 对不上,差 3% 左右,换网格、加采样点都压不下去。
原因:物理近似不一致。扩散近似本身只在各向异性散射较弱时成立,MCNP 则是连续能量、连续角度,两者对同一模型天然存在模型差异;再叠加外推边界简化,差 3% 很正常。这不是 PINN 的实现 bug。
解决:对比前先讲清楚基准。用一维屏蔽基准题,两组解都取通量积分量对比;先跟同精度的有限差分对齐,确认 PINN 实现无误,再跟蒙特卡罗比。记住 PINN 不是用来替代 MCNP 的,它的价值在快速扫参和反问题,这个定位决定了对比标准不能定得太苛刻。
5.5 训练时间太长,但减少步数结果又要崩
现象:5000 步不够,10000 步太久,摸不准停止时机。
原因:PINN 的收敛不是单调下降,存在先快后慢的平台期。平台期里网络有时在细微调整边界层,有时是卡在局部极小,从 loss 表面看不出区别。
解决:按 loss 平台而不是按步数判断。连续 500 步总 loss 变化小于 1% 就停,记录权重;续跑时把学习率减半再跑一次,通常能从平台再往下走一段。我一般会提前算好解析解或参考解,每 500 步算一次相对误差,误差不再降就收工,这套习惯能省下大量等待时间。
5.6 监控三条曲线:loss、边界损失和参考误差
调参时我习惯同时画三条曲线:总 loss、边界损失、参考解相对误差。总 loss 下降不代表参考误差下降,边界损失压低不代表内部剖面正确,只有三条曲线同时进入平台期,才算训练真正完成。画不了参考解的项目,可以用有限差分解替代。这三条线是排查所有上面问题的基础——它们在训练过程中各自负责反映方程、边界、整体解三个视角,少任何一条都容易误判。
6. 进阶方向:反演截面、多群扩散与验证习惯
6.1 从扩散到输运:角度维度的代价
输运方程比扩散方程多一个角度变量,中子飞行方向用方向余弦 μ 表示,输入从一维变成二维,网络要同时输入 x 和 μ。一维平面几何带各向同性散射的输运方程可以试,结果会和 SN 方法对得上;高维输运就非常吃训练成本。常见的过渡路线是先把角度离散成少数几个方向,训练一个“半输运”PINN,看看在这个简化层级上和确定论结果的差距,再逐步加密角度。这条路线每一步都有参考解可比对,翻车时容易定位是空间部分还是角度部分出了问题。
6.2 反问题:把截面当作网络参数一起训练
机器学习应用流程里最有吸引力的环节其实是反问题。探测器给一组测量通量,把 Σa、D 设为可训练参数,损失里加数据项,训练完直接得到截面的估计。最小做法是把截面参数化的对数形式:
Sigma_a_log = torch.nn.Parameter(torch.tensor(-3.0)) # 初始 exp(-3) 约等于 0.05 # 训练时用 torch.exp(Sigma_a_log) 得到正截面,参与残差计算 # 损失里再加数据项:loss_data = torch.mean((phi_pred - phi_meas) ** 2)设置成 log 形式是为了保证截面始终为正,避免训练过程中优化器把截面推到负数,这是反问题实践里的基本技巧。噪声测量点的权重按 1/σ² 设置,σ 是测量噪声标准差。这个方向做出来后,价值比正向求解大得多,因为传统方法做反问题是每改一组截面就要重新解一次方程,PINN 只需要一次训练。
6.3 我现在的验证习惯
我吃完亏才养成的验证顺序是三层对齐,缺一层不上楼。
| 验证层级 | 对齐对象 | 看什么指标 |
|---|---|---|
| 第一层 | 解析解 | 剖面形状、总通量误差 |
| 第二层 | 有限差分密集网格 | 逐点误差、边界行为 |
| 第三层 | 蒙特卡罗基准 | 积分量、能群总泄漏 |
第一层对不上,多半是边界权重和采样配比问题;第二层对不上,大概率是材料界面处理和外推边界没做对;第三层对不上,通常就是模型本身近似差异,可以接受。每次改完物理参数,至少把第一层重新过一遍,确认没有因为参数变化引入新的数值问题。这套习惯已经替我拦住过好几轮低效调试。如果你刚接触这个方向,建议第一周不要碰输运方程,先把这个一维扩散算例做到和解析解稳稳对齐,再考虑往外推。希望帮到你。
本文还有配套的精品资源,点击获取