afem/README.md

556 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.0helmholtz.pre_asymptotic_N 可配)。约 2 点/波长,赋予初始网格基本相位解析能力但不过度消耗物理预算,为 RL agent 留出充分的选择性细化空间
- **后验误差**: 残差型 indicatorAinsworth & 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) (标量)
├─ MessagePassingStack2 层消息传递 + 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.0std≈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.135clamp 在 [-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₁₀ 压缩):<br>`(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(δ)` 均值 | 接近 0Actor 修正无系统性偏差 |
| `corr_s` | `c·tanh(δ)` 标准差 | 应稳定在 0.030.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_localpenalty | |
| `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.0P1 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 0clip [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]<br>
初始化 `-2.0` (σ≈0.135),放宽下限防止策略过早确定化
- **熵正则**: `entropy_coefficient=0.01`
- **epochs_per_iteration**: 3
### Correction 正则化
为防止 Actor 学会利用"大 correction"刷局部 residual rewardcorrection 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.15r_le_sc 保持更接近 η baselinevalidation 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 penaltyrefined parents |
| `avg_r_local` | environment | 平均 r_localpenalty 前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).