# AFEM — 自适应网格细化的 GNN + PPO 强化学习 ## 项目架构 ``` afem/ ├── src/ # 应用层 │ ├── config.yaml # 配置文件 │ ├── main.py # 入口:解析命令行 → train / test / viz │ ├── network.py # GNN + Actor-Critic 完整网络定义 │ ├── ppo.py # RolloutBuffer + PPOTrainer │ ├── utils.py # 读配置、保存/加载 checkpoint │ └── visualize.py # viz 模式:加载模型 → 推理 → 存 PNG │ ├── environment/ # 仿真环境层 │ ├── mesh_refinement.py # ★ 核心:网格细化 RL 环境 │ │ # - GNN 图观测构建(节点 + 边特征) │ │ # - continuous_sizing_field (score-based + budget) 细化策略 │ │ # - spatial 奖励 + step0 penalty 降权 │ ├── helmholtz.py # Helmholtz FEM 求解器 + 残差误差估计 │ ├── fem_problem.py # FEM 问题封装 + PDE 循环缓冲区 │ ├── fem_util.py # 三角形面积、中点、随机采样、尺寸场函数 │ ├── domain.py # 计算域:meshpy 三角剖分 │ ├── utils.py # 数组拼接、随机索引采样 │ └── visualization.py # plotly 网格渲染(RL 环境用) │ ├── checkpoints/ # 模型保存 ├── result/ # 可视化输出 └── README.md ``` --- ## 项目简介 ### 物理场景 二维 Helmholtz 电磁散射: ``` ∇²u_scat + k²·ε_r·u_scat = k²·(1-ε_r)·u_inc ``` - **入射波**: 沿 -x 方向的平面波 `u_inc = exp(i·k·x)` - **散射体**: 圆形介质柱(ε_r 随机采样),位置和半径可配 - **边界条件**: SBC (Sommerfeld) `∂u/∂n = i·k·u` - **域**: 可配矩形域,初始网格密度自适应 + domain area 线性缩放:`N_init = N_base × (k/k_ref)^k_exponent × domain_area`。k_ref 和 k_exponent 均可通过 helmholtz config 配置(默认 k_exponent=2.0, k_ref=6.0),保证不同域尺寸下每单位面积单元数一致 - 可配 exponent:^2 = P1 Helmholtz 理论最优 (污染误差 ∝ k²)。建议 N_base 配合 exponent 调整,使 N_init 约为 COMSOL 目标 (λ/10√ε_r) 的 30-50%,为 RL agent 留出充分细化空间 - **介质区前渐近区边缘约束**: 介质内 λ_d = 2π/(k√ε_r) 更短,强制迭代细化至 h ≤ λ_d/N(默认 N=2.0,helmholtz.pre_asymptotic_N 可配)。约 2 点/波长,赋予初始网格基本相位解析能力但不过度消耗物理预算,为 RL agent 留出充分的选择性细化空间 - **后验误差**: 残差型 indicator(Ainsworth & Oden 风格),含单元内部残差 + 梯度跳变 + SBC 边界残差 ### 强化学习建模 | 概念 | 对应实体 | |------|---------| | **智能体** | 每个三角形网格单元 | | **状态** | GNN 节点特征(几何 + PDE 残差 + 振幅 + 相位方向 + 物理参数,节点 13 维 + 边 1 维) + 13 维全局统计向量 | | **动作** | 1 维连续标量 δ_i → `score_i = log(η_i + ε) + c·tanh(δ_i)` 降序 top-k 选择(η baseline + bounded Actor correction) | | **奖励** | 纯局部 r_local = log(η_old) − log(l2(η_child)),clip [0, 2.0],减去动作惩罚(step0 降权);未细化单元 r=0 | | **终止** | 达到最大步数、超过最大单元数、或 sel=0(无单元可选) | --- ## 网络架构 双流 GNN 架构(Actor / Critic 共享基座,各自独立头): ``` 图观测 → MessagePassingBase → Actor MLP → δ_i (连续动作) ├─ nn.Linear(嵌入) → Critic MLP → V(s) (标量) ├─ MessagePassingStack(2 层消息传递 + MultiPoolGVN 全局广播) │ ├─ MessagePassingStep × 2 │ │ ├─ EdgeModule: MLP([src | dst | edge_attr]) │ │ └─ NodeModule: MLP([node | scatter(入边)]) │ │ 内残差 + LayerNorm │ └─ MultiPoolGVN: 多策略池化 + 13 维全局统计 → 注意力门控广播 │ Stage A: g_global = MLP(concat(g_mean, g_eta, global_stats)) │ Stage B: α_v = σ(W_att[h_v || g_global]) │ h_v ← h_v + scale · α_v ⊙ W_V · g_global └─ 输出: 节点隐向量 h_i Actor 输入: concat(h_i, g_global, rel_logeta, rel_area, is_top_eta, budget_stats) [2D+6] Critic 输入: concat(h_i, g_global) [2D] ``` ### MultiPoolGVN — 多池化全局虚拟节点 替代原始单一 η 加权 GVN,用多种池化策略聚合节点嵌入,拼接全局统计后生成 `g_global`: | 池化模式 | 公式 | 说明 | |----------|------|------| | `mean` | `g_mean = Σ h_v / N` | 均匀平均 | | `eta_softmax` | `g_eta = Σ (η_v / Ση) · h_v` | η 加权 softmax,高误差节点主导 | | `top_eta` | `g_top = mean(h_v : log η_v > μ + σ)` | top-η 节点均值(log 空间 >1σ) | 配置项 `gvn_pooling: [mean, eta_softmax]`,可选加 `top_eta`。 ### Global Stats — 13 维图级统计 每个图观测附带 13 维全局统计向量(`graph.global_stats`),用于 GVN 和 Actor/Critic 的条件输入: | 索引 | 名称 | 说明 | |------|------|------| | 0 | `remaining_ratio` | (N_budget − N_current) / N_budget | | 1 | `step_ratio` | current_step / max_steps | | 2 | `elem_ratio` | N_current / N_budget | | 3 | `logeta_mean` | log(η) 均值 | | 4 | `logeta_std` | log(η) 标准差 | | 5 | `logeta_max` | log(η) 最大值 | | 6 | `logeta_p90` | log(η) P90 | | 7 | `logeta_p75` | log(η) P75 | | 8 | `top10_eta_energy` | top 10% η² 能量占比 | | 9 | `eligible_ratio` | 面积安全阈值以上元素占比 | | 10 | `inside_eta_energy` | 散射体内 η² 能量占比 | | 11 | `outside_eta_energy` | 散射体外 η² 能量占比 | | 12 | `interface_eta_energy` | 界面附近 η² 能量占比 | ### 全局条件化 Actor/Critic 当 `use_global_conditioned_correction: true` 时,Actor 和 Critic 的输入额外拼接全局上下文: - **Actor**: `concat(h_i, g_global, rel_logeta, rel_area, is_top_eta, budget_stats)` → 维度 `2D + 6` - `rel_logeta`: `(log η_i − μ) / σ`,per-graph 标准化 - `rel_area`: `log(area_i / mean_area)`,per-graph 相对面积 - `is_top_eta`: `rel_logeta > 1.0` 的 0/1 标记 - `budget_stats`: `[remaining_ratio, step_ratio, elem_ratio]` - **Critic**: `concat(h_i, g_global)` → 维度 `2D` ### 超参数 | 超参数 | 值 | |--------|-----| | latent_dim | 64 | | 消息传递层数 | 2 | | 残差连接 | inner | | 归一化 | inner LayerNorm | | 边 dropout | 0.1 | | Actor MLP | 2 层 tanh | | Critic MLP | 2 层 tanh | | Optimizer | Adam, lr=3e-4, lr_decay=1.0 | | **动作分布** | `DiagGaussianDistribution`(连续 Box 动作空间),`log_std` 可学习,clamp 在 [-2.5, -1.0] | | **log_std 策略** | 初始化 -2.0(std≈0.135),每步 optimizer.step() 后 clamp 到 [-2.5, -1.0](std ∈ [0.082, 0.368]),熵系数 0.01 | | **correction_scale** | 0.3 — Actor 修正幅值 c·tanh(δ) ∈ [−0.3, +0.3] | | **correction_reg_coef** | 0.03 — correction 正则化系数,L_corr = coef × mean(correction²) | | **step0_penalty_scale** | 0.3 — 第一步 element penalty 降权系数 | ### 动作分布策略说明 环境定义的是 `_action_space`(下划线前缀),网络初始化时必须用 `environment._action_space` 而非 `environment.action_space`(后者默认为 None,会错误回退到 `CategoricalDistribution(1)`,导致 policy gradient 恒为零)。 `continuous_sizing_field`(score-based)的 scoring 公式: - `score_i = log(η_i + ε) + c·tanh(δ_i)`,其中 c=`correction_scale` - Actor 输出 δ_i,经 tanh 限幅,只能微调 log(η) 基准排序,不能覆盖物理先验 - 选 top-k 按 score 降序(越大越优先) - `initial_log_std=-2.0`(std≈0.135),clamp 在 [-2.5, -1.0](std ∈ [0.082, 0.368]) - `entropy_coefficient=0.01` --- ## 输入特征 ### 节点特征(13 维) | 维度 | 来源 | 名称 | 说明 | |------|------|------|------| | 1 | cfg | `volume` | 无量纲单元面积:volume / λ² | | 3 | cfg | `internal_residual` / `gradient_jump` / `sbc_residual` | PDE 残差三分量(真空波数 k 归一化,经 log₁₀ 压缩):
`(h_K/k)·√V·|r|` / `√(½Σ h_e·\|jump\|²/k)` / `(h_bnd/k)·\|SBC\|` | | 1 | cfg | `element_penalty` | 单元惩罚系数 λ | | 1 | cfg | `timestep` | 当前 rollout 步数 | | 1 | cfg | `k_local_sqrt_vol` | k × √(ε_r) × √(V)(局域波数 × 特征长度) | | 1 | cfg | `is_sbc_boundary` | 是否与 SBC 吸收边界相邻 (0/1) | | 1 | cfg | `dist_to_interface` | 到介质圆柱边界的带符号距离,无量纲化后经 sign·ln(1+|d|) 压缩:`sign(d)·ln(1+|(dist-radius)/λ|)` — 近场近似线性保留分辨力,远场对数压缩避免 OOD,与残差 log₁₀ 风格一致 | | 1 | fix | `epsilon_r` | 单元中点相对介电常数(圆柱内 = εᵣ,外 = 1.0) | | 1 | fix | `total_solution_magnitude` | 散射场振幅 \|u_scat\|(per-element 均值) | | 1 | fix | `cos_phase` | Re(u) / (\|u\| + 1e-8),相位方向余弦,∈ [−1, 1],无分支切割 | | 1 | fix | `sin_phase` | Im(u) / (\|u\| + 1e-8),相位方向正弦,与 cos 联合编码相位 | > - **cfg**: 由 `element_features` 配置控制 > - **fix**: 始终启用(Helmholtz 振幅 + 相位方向,硬编码) > > GNN 输入用 `_compute_residual_components`(真空波数 k 归一化,log₁₀ 压缩)。Reward 用逐单元 η_K(`_eta_indicator`),与 GNN 特征公式一致但不经 log 压缩。SBC 边界条件保留 `k_local`。 ### 边特征(1 维) | 维度 | 名称 | 说明 | |------|------|------| | 1 | `phase_distance` | 相邻单元中点相位距离 = d × √(k_local_src·k_local_dst) / 2π — 介质内短波长自然放大,赋予 GNN k 不变性 | --- ## 调用逻辑 ``` main.py --mode train/test/viz │ ├─→ utils.load_config() # 读 YAML ├─→ environment.MeshRefinement # 创建 RL 环境 │ └─→ FEMProblemCircularQueue # 管理 N 个随机 PDE 实例 │ └─→ HelmholtzProblem # FEM 求解 + 残差误差 ├─→ network.create_model() # 创建 ActorCritic │ └─ [train] → ppo.PPOTrainer.fit_iteration() 循环 ├─ collect_rollouts() # 256 步 rollout │ └─ buffer.compute_returns_and_advantage() │ └─ 单路 GAE # 逐 agent 时序差分(scatter_add 处理网格细化) │ └─ Return / value 归一化 └─ train_step() # 多 epoch PPO 更新 ├─ policy_loss() # Clipped PPO ├─ value_loss() # Clipped value loss ├─ entropy_loss() # 熵正则 └─ correction_reg() # Correction 正则化 L_corr ``` ### 环境内部调用 ``` MeshRefinement.reset() └─→ FEMProblemWrapper.reset() └─→ initial_mesh (meshpy → 介质内 前渐近区边缘迭代细化) MeshRefinement.step(action) ├─→ score = log(η) + c·tanh(δ) 排序 + 物理预算约束 → top-k 细化单元 ├─→ FEMProblemWrapper.refine_mesh() # scikit-fem refine ├─→ calculate_solution_and_get_error() │ ├─→ HelmholtzProblem.calculate_solution() # FEM 求解 │ └─→ _compute_residual_indicator() # 残差误差 ├─→ _get_reward_by_type() # spatial 奖励 + step0 penalty 降权 └─→ last_observation # 构建 Data(x, edge_index, edge_attr, eta, area, global_stats) ``` ### 训练 ```bash CUDA_VISIBLE_DEVICES=7 python src/main.py --mode train --config src/config.yaml ``` 首次迭代需收集 256 步 rollout(含 FEM 求解),后续打印: ``` it | loss ev agents avg_r sum_r corr_m corr_s r_le_sc δ<0 elig sel rem_r corr_reg corr_l2 corr_a p_sc avg_p avg_rl step_id time ``` | 字段 | 含义 | 健康范围 | |------|------|---------| | `corr_m` | `c·tanh(δ)` 均值 | 接近 0,Actor 修正无系统性偏差 | | `corr_s` | `c·tanh(δ)` 标准差 | 应稳定在 0.03–0.08,不应持续涨到 0.15 | | `r_le_sc` | Pearson r(log_η, score) | 接近 1.0 → Actor 修正小;<0.9 → Actor 在主动修正 | | `δ<0` | Actor 输出负值的比例(纯诊断) | — | | `elig` | 通过双过滤器的候选占比 | — | | `sel` | 实际选中的细化单元数 | 每步最多 N_current // 4 | | `rem_r` | remaining / N_budget | — | | `corr_reg` | correction 正则化损失 L_corr | 监控 correction drift | | `corr_l2` | mean(correction²) | 监控 correction 幅值增长 | | `corr_a` | mean(\|correction\|) | 监控 correction 绝对值 | | `p_sc` | penalty_scale | step0=0.3,后续=1.0 | | `avg_p` | 平均 element penalty | step0 应明显小于后续 | | `avg_r_local` | 平均 r_local(penalty 前) | — | | `step_id` | 当前步数 | — | ### 测试 ```bash python src/main.py --mode test --checkpoint checkpoints/model_final.pt --k-test 6.0 python src/main.py --mode test --checkpoint checkpoints/model_final.pt \ --k-test 6.0 --center 0.3,0.6 --radius 0.15 ``` 输出: ``` Step 0: reward=--- aw_rel=79.28% max_err=2.2133 elements=1078 budget=... Step 1: reward=+2.345 aw_rel=30.10% max_err=0.7096 elements=2020 sel=269 ... ``` 每步打印 `reward aw_rel max_err elements sel`,第 0 步额外显示 `N_budget`。 ### 可视化 ```bash python src/main.py --mode viz --checkpoint checkpoints/model_final.pt --k-test 30.0 ``` 输出: `result/visualization.png`(总览)+ `result/visualization_steps/step*.png`(逐步对比)。 --- ## 后验误差估计 ### 残差 indicator 公式(无量纲化) 引入局部波数 $k_{local} = k\sqrt{\max(\varepsilon_r, 1.0)}$,消除纯几何尺度 $h$ 带来的特征偏差, 使误差指示子反映"相位分辨率残差"而非"网格粗疏程度"。 对 P1 三角单元 K,三项残差分量为: $$r_{\text{int}} = \frac{h_K}{k} \sqrt{V_K} \cdot \left| k^2\varepsilon_r u + k^2(\varepsilon_r-1)u_{inc} \right|_K \tag{1}$$ $$r_{\text{jump}} = \sqrt{\frac{1}{2}\sum_{e\in\partial K} \frac{h_e}{k} \cdot \left| [[\nabla u \cdot n]] \right|^2_e} \tag{2}$$ $$r_{\text{sbc}} = \frac{h_{bnd}}{k} \cdot \left| \frac{\partial u}{\partial n} - ik_{local}u \right| \tag{3}$$ **逐单元误差指示子**: $$\eta_K = \sqrt{r_{\text{int}}^2 + r_{\text{jump}}^2 + r_{\text{sbc}}^2}$$ 量纲分析($k \sim [L]^{-1}$,$h_e \sim [L]$,$|\text{jump}|^2 \sim [L]^{-2}$): 三项均严格无量纲:$h_e/k \cdot |\text{jump}|^2 \sim [L]^2 \cdot [L]^{-2} = 1$。 SBC 边界条件仍用 $k_{local}$(物理正确),仅归一化因子改用 $k$。 介质内残差不再被 $\sqrt{\varepsilon_r}$ 压低,Agent 获得正确的介质内/外优先级信号。 `η_K` 的计算(`_compute_residual_indicator`)与 GNN 输入特征(`_compute_residual_components`)公式完全一致,特征仅多一层 log₁₀ 压缩。关键验证点: - 内部残差:P1 元 ∇²u_h ≡ 0,仅含反应项 `k²ε_r·u + k²(ε_r-1)·u_inc`,真空波数 k 归一化 - 梯度跳变:`(h_e/k)·|jump|²`,½ 分配给相邻左右单元;$h_e$ 保留边积分路径,细化后自然衰减 - SBC 项归一化用 k,物理条件保留 k_local:`(h_bnd²/k²)·|∂u/∂n − i·k_local·u|²` ### 连续尺寸场策略(score-based + 物理预算约束 + 动作掩码) Actor 输出标量 δ_i → `score_i = log(η_i + ε) + c·tanh(δ_i)`,在预算和上限内选 top-k: ``` ε = max(0.01·median(η), 1e-12) // 动态 eps,防止 log(0) corr_i = c · tanh(δ_i) c = correction_scale // Actor 修正幅值 ∈ [−c, +c] score_i = log(η_i + ε) + corr_i // 降序 top-k A_budget_i = ½(λ_local_i / 6)² // 每局部波长方向 ~6 尺度点(仅用于 N_budget) N_budget = max(N_phys, ⌈5·N_init⌉) // rho_min=5.0 remaining = N_budget − N_current V_min_safeguard = 1e-10 × domain_area eligible: area > V_min_safeguard AND η_K ∈ Reverse Dörfler 保留集 (ε_noise=0.01, ≥20% floor) num = min(|eligible|, N_current//4, remaining//3) selected = top-k by score descending → 1-to-4 切分 ``` - Actor 通过 bounded correction 微调排序,不能覆盖物理先验 log(η) - Reverse Dörfler 动作掩码剔除噪声尾部,≥20% floor 确保 Agent 始终有选择空间 - sel=0 提前终止:agent 选中 0 个单元时 episode 自动结束 - k_exponent=2.0:P1 Helmholtz 理论最优初始网格缩放 ### 奖励计算 纯局部改善 reward,无调制、无 bonus: ``` r_local_i = log(η_old_i + ε) − log(l2_η_children_i + ε) l2_η_children_i = √(Σ_{j∈C(i)} η_new_j²) reward_i = clip(r_local_i, 0, rmax) − penalty_scale · λ · (n_child_i − 1) rmax = 2.0 λ = 0.02 (element_penalty.value) penalty_scale = step0_penalty_scale if current_step == 0 (默认 0.3) = 1.0 otherwise ``` | 组件 | 聚合 | 作用 | |------|------|------| | 局部项 `log(η_old / √(Σ η_child²))` | scatter_add,仅 refined parents | L₂ 保证 r_local ≥ 0;clip 到 [0, 2.0] | | 动作惩罚 `λ=0.02` | per-refined-parent | 轻微抑制网格膨胀(1-to-4 扣 0.06) | | **step0 降权** | step 0 时 penalty × 0.3 | 防止第一步"真实误差改善但 reward 给负反馈" | | 因果隔离 `r=0` | unrefined parents | 未细化元素干净零基准 | **step0 penalty 降权动机**:诊断发现 step 0 经常出现 reward < 0 但真实 aw_rel 改善的情况,说明 element penalty 淹没了真实的物理改善信号。降权后 step 0 的 reward 与 Δaw_rel 符号一致性提高。 --- ## PPO 关键细节 - **单路 GAE**: r_local 自身已闭合因果(细化单元的局部误差改善),不需要势函数塑形。用 `scatter_add` 将细化后的子单元值聚合回父单元,单路 GAE 即可 - **奖励归一化**: rollout 内 reward 做 z-score 归一化(std < 1e-8 则跳过) - **Value clipping**: 默认 clip_range=0.2 - **梯度裁剪**: max_grad_norm=0.5 - **log_std clamp**: 每步 `optimizer.step()` 后将 `log_std` clamp 到 `[-2.5, -1.0]`,σ ∈ [0.082, 0.37]
初始化 `-2.0` (σ≈0.135),放宽下限防止策略过早确定化 - **熵正则**: `entropy_coefficient=0.01` - **epochs_per_iteration**: 3 ### Correction 正则化 为防止 Actor 学会利用"大 correction"刷局部 residual reward(correction drift / reward hacking),在 PPO loss 中加入 correction 正则项: ``` correction_i = correction_scale · tanh(action_i) L_corr = correction_reg_coef · mean(correction²) loss = policy_loss + value_coef · value_loss + entropy_coef · entropy_loss + L_corr ``` - `correction_reg_coef` 默认 0.03,设为 0 可禁用 - correction 在 PPO 训练时从 stored actions 重新计算(与环境中的公式一致) - 目标:corr_std 不再从 ~0.03 持续涨到 ~0.15;r_le_sc 保持更接近 η baseline;validation aw_rel 不随训练后期变差 - 训练 reward 可能因正则化而下降,这是正常现象;成功标准是测试误差更稳定 ### 训练诊断字段 | 字段 | 来源 | 说明 | |------|------|------| | `corr_reg` | train_step | L_corr = coef × mean(corr²),监控 correction drift | | `corr_l2` | train_step | mean(correction²),correction 幅值 | | `corr_abs` | train_step | mean(\|correction\|),correction 绝对值 | | `penalty_scale` | environment | step0=0.3,后续=1.0 | | `avg_penalty` | environment | 平均 element penalty(refined parents) | | `avg_r_local` | environment | 平均 r_local(penalty 前,refined parents) | | `step_id` | environment | 当前 timestep | --- ## 配置参考 ```yaml algorithm: batch_size: 32 discount_factor: 1.0 ppo: clip_range: 0.2 entropy_coefficient: 0.01 correction_reg_coef: 0.03 # correction 正则化系数 epochs_per_iteration: 3 gae_lambda: 0.95 initial_log_std: -2.0 max_grad_norm: 0.5 num_rollout_steps: 256 value_function_coefficient: 0.5 use_gpu: true environment: mesh_refinement: correction_scale: 0.3 # c in score = log(η) + c·tanh(δ) step0_penalty_scale: 0.3 # step 0 element penalty 降权 num_timesteps: 4 refinement_strategy: continuous_sizing_field reward_type: spatial element_penalty: value: 0.02 maximum_elements: 50000 element_limit_penalty: 10000 # ... (FEM / Helmholtz / 特征配置见 config.yaml) network: latent_dimension: 64 use_global_conditioned_correction: true # Actor/Critic 拼接 g_global + 局部相对特征 use_global_stats: true # 启用 MultiPoolGVN + 13 维全局统计 gvn_pooling: [mean, eta_softmax] # 池化策略(可选加 top_eta) correction_centering: true # correction 在 eligible 集内中心化 base: edge_dropout: 0.1 scatter_reduce: mean stack: num_steps: 2 mlp: activation_function: leakyrelu num_layers: 2 actor: mlp: activation_function: tanh num_layers: 2 critic: mlp: activation_function: tanh num_layers: 2 training: learning_rate: 0.0003 lr_decay: 1.0 ``` ### 实验对照建议 | 实验 | `correction_reg_coef` | `step0_penalty_scale` | 目的 | |------|----------------------|----------------------|------| | A (baseline) | 0.0 | 1.0 | 无正则、无降权 | | B (corr reg only) | 0.03 | 1.0 | 验证 correction 正则效果(优先) | | C (both) | 0.03 | 0.3 | 正则 + step0 降权 | 成功标准(非训练 reward 高低): - `corr_std` 不再持续涨到 ~0.15 - `r_le_sc` 保持更接近 η baseline - top-k overlap 不随训练后期明显下降 - validation `aw_rel / max_err` 更稳定 --- ## Correction GNN 训练数据 Correction GNN 用于二分类预测:给定当前网格,哪些单元需要加密(teacher_mark=1)。 训练数据由 `outlook/src/gen.py` 生成。 ### 参数采样 每个样本随机采样物理参数: | 参数 | 分布 | 说明 | |------|------|------| | `k` | Uniform(3, 15) | 波数 | | `eps_r` | Uniform(2, 8) | 介质相对介电常数 | | `cx` | Uniform(0.2, 0.8) | 散射体中心 x | | `cy` | Uniform(0.2, 0.8) | 散射体中心 y | | `radius` | Uniform(0.05, 0.25) | 散射体半径 | ### 初始网格 采用物理自适应初始网格(`make_initial_mesh`),元素尺寸由局域波长决定: - **介质外**: h ≤ λ₀ / q, λ₀ = 2π / k - **介质内/散射体附近**: h ≤ λ_eff / q, λ_eff = 2π / (k √ε_r) - **q = 2**(每波长 2 个单元) 网格在 [0,1]×[0,1] 域上通过张量积生成,x/y 方向各自根据散射体位置做分级加密: - 远离散射体:粗网格(h = λ₀/q) - 散射体附近(含过渡区):细网格(h = λ_eff/q) ### AMR 循环与标签生成 对每个参数样本,运行残差驱动 AMR 循环,每步保存快照: ``` for step in range(max_steps): 1. FEM 求解 → u_scat 2. 计算残差指示子 η(teacher 信号) 3. 计算 physics_score = h / λ_eff(物理 baseline) 4. teacher_mark = top-fraction(η, mark_fraction) # 二值标签 physics_mark = top-fraction(physics_score, mark_fraction) # 物理 baseline correction_label = teacher_mark - physics_mark # {-1, 0, +1} 5. 提取 16 维节点特征 + 边索引 6. 保存 .npz → 残差指示子 top-k 加密 → 下一步 ``` - **mark_fraction**: 默认 0.03(每步标记 top 3% 的单元为正样本) - **top-fraction**: 按 score 降序取 top `n × fraction` 个单元,标记为 1 - **teacher_mark**: 以 η(残差指示子)为 score,代表"最优加密目标" - **physics_mark**: 以 h/λ_eff 为 score,代表"纯物理 baseline" - **correction_label**: teacher 与 physics 的差集,+1 = teacher 独有(GNN 应补充),-1 = physics 独有(GNN 应抑制) ### 数据文件格式 每个样本每步保存为 `sample{id}_step{step}.npz`,包含: | 字段 | 形状 | 说明 | |------|------|------| | `features` | (n_elem, 16) | 15 维几何/PDE 特征 + 1 维 physics_score | | `edge_index` | (2, n_edges) | 双向边 + 自环 | | `physics_score` | (n_elem,) | h / λ_eff | | `teacher_eta` | (n_elem,) | 残差指示子 η | | `teacher_mark` | (n_elem,) | 二值标签 (0/1) | | `physics_mark` | (n_elem,) | 物理 baseline 标签 (0/1) | | `correction_label` | (n_elem,) | 差集标签 (-1/0/+1) | | `k, eps_r, cx, cy, radius` | scalar | 物理参数 | | `elements` | scalar | 当前单元数 | | `step` | scalar | AMR 步数 | --- ## One-Shot Density Prediction One-shot final mesh density prediction experiments are documented in [`outlook/README.md`](outlook/README.md).