Auto-sync from Server A at 2026-06-09 12:21:00
This commit is contained in:
parent
2c09f91cfa
commit
fa2c670f8d
459
README.md
459
README.md
|
|
@ -16,7 +16,7 @@ afem/
|
||||||
│ ├── mesh_refinement.py # ★ 核心:网格细化 RL 环境
|
│ ├── mesh_refinement.py # ★ 核心:网格细化 RL 环境
|
||||||
│ │ # - GNN 图观测构建(节点 + 边特征)
|
│ │ # - GNN 图观测构建(节点 + 边特征)
|
||||||
│ │ # - continuous_sizing_field (score-based + budget) 细化策略
|
│ │ # - continuous_sizing_field (score-based + budget) 细化策略
|
||||||
│ │ # - spatial 奖励
|
│ │ # - spatial 奖励 + step0 penalty 降权
|
||||||
│ ├── helmholtz.py # Helmholtz FEM 求解器 + 残差误差估计
|
│ ├── helmholtz.py # Helmholtz FEM 求解器 + 残差误差估计
|
||||||
│ ├── fem_problem.py # FEM 问题封装 + PDE 循环缓冲区
|
│ ├── fem_problem.py # FEM 问题封装 + PDE 循环缓冲区
|
||||||
│ ├── fem_util.py # 三角形面积、中点、随机采样、尺寸场函数
|
│ ├── fem_util.py # 三角形面积、中点、随机采样、尺寸场函数
|
||||||
|
|
@ -46,7 +46,7 @@ afem/
|
||||||
- **边界条件**: SBC (Sommerfeld) `∂u/∂n = i·k·u`
|
- **边界条件**: 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),保证不同域尺寸下每单位面积单元数一致
|
- **域**: 可配矩形域,初始网格密度自适应 + 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 留出充分细化空间
|
- 可配 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=1.5,helmholtz.pre_asymptotic_N 可配)。约 1.5 点/波长,刚好跨过渐近区门槛,赋予初始网格基本相位解析能力但不过度消耗物理预算,为 RL agent 留出充分的选择性细化空间
|
- **介质区前渐近区边缘约束**: 介质内 λ_d = 2π/(k√ε_r) 更短,强制迭代细化至 h ≤ λ_d/N(默认 N=2.0,helmholtz.pre_asymptotic_N 可配)。约 2 点/波长,赋予初始网格基本相位解析能力但不过度消耗物理预算,为 RL agent 留出充分的选择性细化空间
|
||||||
- **后验误差**: 残差型 indicator(Ainsworth & Oden 风格),含单元内部残差 + 梯度跳变 + SBC 边界残差
|
- **后验误差**: 残差型 indicator(Ainsworth & Oden 风格),含单元内部残差 + 梯度跳变 + SBC 边界残差
|
||||||
|
|
||||||
### 强化学习建模
|
### 强化学习建模
|
||||||
|
|
@ -54,29 +54,80 @@ afem/
|
||||||
| 概念 | 对应实体 |
|
| 概念 | 对应实体 |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| **智能体** | 每个三角形网格单元 |
|
| **智能体** | 每个三角形网格单元 |
|
||||||
| **状态** | GNN 节点特征(几何 + PDE 残差 + 振幅 + 相位方向 + 物理参数,节点 13 维 + 边 1 维) |
|
| **状态** | GNN 节点特征(几何 + PDE 残差 + 振幅 + 相位方向 + 物理参数,节点 13 维 + 边 1 维) + 13 维全局统计向量 |
|
||||||
| **动作** | 1 维连续标量 x_i → score = -x_i 排序,在物理预算内 top-k 选细化单元(x 越小优先级越高) |
|
| **动作** | 1 维连续标量 δ_i → `score_i = log(η_i + ε) + c·tanh(δ_i)` 降序 top-k 选择(η baseline + bounded Actor correction) |
|
||||||
| **奖励** | 局部子单元 η 的 log-ratio 改善(spatial: sum 聚合 / spatial_max: max 聚合)+ α 衰减全局 η log-ratio shaping |
|
| **奖励** | 纯局部 r_local = log(η_old) − log(l2(η_child)),clip [0, 2.0],减去动作惩罚(step0 降权);未细化单元 r=0 |
|
||||||
| **终止** | 达到最大步数或超过最大单元数 |
|
| **终止** | 达到最大步数、超过最大单元数、或 sel=0(无单元可选) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 网络架构
|
## 网络架构
|
||||||
|
|
||||||
双 GNN 架构(policy / value 各自独立基座):
|
双流 GNN 架构(Actor / Critic 共享基座,各自独立头):
|
||||||
|
|
||||||
```
|
```
|
||||||
图观测 → MessagePassingBase → MLP → 动作分布 / value 标量
|
图观测 → MessagePassingBase → Actor MLP → δ_i (连续动作)
|
||||||
├─ nn.Linear(嵌入)
|
├─ nn.Linear(嵌入) → Critic MLP → V(s) (标量)
|
||||||
├─ MessagePassingStack(2 层消息传递 + GVN 全局广播,inner 残差 + LayerNorm)
|
├─ MessagePassingStack(2 层消息传递 + MultiPoolGVN 全局广播)
|
||||||
│ ├─ MessagePassingStep × N
|
│ ├─ MessagePassingStep × 2
|
||||||
│ │ ├─ EdgeModule: MLP([src | dst | edge_attr])
|
│ │ ├─ EdgeModule: MLP([src | dst | edge_attr])
|
||||||
│ │ └─ NodeModule: MLP([node | scatter(入边)])
|
│ │ └─ NodeModule: MLP([node | scatter(入边)])
|
||||||
│ └─ GlobalVirtualNode (GVN): η_K 加权注意力池化 → 注意力门控广播
|
│ │ 内残差 + LayerNorm
|
||||||
│ h_V = Σ(η_v/Ση)·h_v,α_v = σ(W_att[h_v || h_V]),h_v ← h_v + α_v ⊙ W_V·h_V
|
│ └─ 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 |
|
| latent_dim | 64 |
|
||||||
|
|
@ -86,18 +137,23 @@ afem/
|
||||||
| 边 dropout | 0.1 |
|
| 边 dropout | 0.1 |
|
||||||
| Actor MLP | 2 层 tanh |
|
| Actor MLP | 2 层 tanh |
|
||||||
| Critic MLP | 2 层 tanh |
|
| Critic MLP | 2 层 tanh |
|
||||||
| Optimizer | Adam, lr=3e-4, lr_decay=0.995 |
|
| Optimizer | Adam, lr=3e-4, lr_decay=1.0 |
|
||||||
| **动作分布** | `DiagGaussianDistribution`(连续 Box 动作空间),`log_std` 可学习,clamp 在 [-4.0, -1.0] |
|
| **动作分布** | `DiagGaussianDistribution`(连续 Box 动作空间),`log_std` 可学习,clamp 在 [-2.5, -1.0] |
|
||||||
| **log_std 策略** | 初始化 -2.0(std≈0.135),每步 optimizer.step() 后 clamp 到 [-4.0, -1.0](std ∈ [0.018, 0.368]),熵系数 0.001 |
|
| **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 恒为零)。
|
环境定义的是 `_action_space`(下划线前缀),网络初始化时必须用 `environment._action_space` 而非 `environment.action_space`(后者默认为 None,会错误回退到 `CategoricalDistribution(1)`,导致 policy gradient 恒为零)。
|
||||||
|
|
||||||
`continuous_sizing_field`(score-based)的动作有效范围约 [-3, 3]:
|
`continuous_sizing_field`(score-based)的 scoring 公式:
|
||||||
- score = -x_i,x 越小 ⇒ 优先级越高(纯排序,不设正负门槛)
|
- `score_i = log(η_i + ε) + c·tanh(δ_i)`,其中 c=`correction_scale`
|
||||||
- `initial_log_std=-2.0`(std≈0.135),clamp 在 [-4.0, -1.0](std ∈ [0.018, 0.368])
|
- Actor 输出 δ_i,经 tanh 限幅,只能微调 log(η) 基准排序,不能覆盖物理先验
|
||||||
- 加 `entropy_coefficient=0.001` 提供微弱探索压力,避免 log_std 过早收敛到下限
|
- 选 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`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -152,7 +208,8 @@ main.py --mode train/test/viz
|
||||||
└─ train_step() # 多 epoch PPO 更新
|
└─ train_step() # 多 epoch PPO 更新
|
||||||
├─ policy_loss() # Clipped PPO
|
├─ policy_loss() # Clipped PPO
|
||||||
├─ value_loss() # Clipped value loss
|
├─ value_loss() # Clipped value loss
|
||||||
└─ entropy_loss() # 熵正则
|
├─ entropy_loss() # 熵正则
|
||||||
|
└─ correction_reg() # Correction 正则化 L_corr
|
||||||
```
|
```
|
||||||
|
|
||||||
### 环境内部调用
|
### 环境内部调用
|
||||||
|
|
@ -163,16 +220,15 @@ MeshRefinement.reset()
|
||||||
└─→ initial_mesh (meshpy → 介质内 前渐近区边缘迭代细化)
|
└─→ initial_mesh (meshpy → 介质内 前渐近区边缘迭代细化)
|
||||||
|
|
||||||
MeshRefinement.step(action)
|
MeshRefinement.step(action)
|
||||||
├─→ score = -x 排序 + 物理预算约束 → top-k 细化单元
|
├─→ score = log(η) + c·tanh(δ) 排序 + 物理预算约束 → top-k 细化单元
|
||||||
├─→ FEMProblemWrapper.refine_mesh() # scikit-fem refine
|
├─→ FEMProblemWrapper.refine_mesh() # scikit-fem refine
|
||||||
├─→ calculate_solution_and_get_error()
|
├─→ calculate_solution_and_get_error()
|
||||||
│ ├─→ HelmholtzProblem.calculate_solution() # FEM 求解
|
│ ├─→ HelmholtzProblem.calculate_solution() # FEM 求解
|
||||||
│ └─→ _compute_residual_indicator() # 残差误差
|
│ └─→ _compute_residual_indicator() # 残差误差
|
||||||
├─→ _get_reward_by_type() # spatial 奖励
|
├─→ _get_reward_by_type() # spatial 奖励 + step0 penalty 降权
|
||||||
└─→ last_observation # 构建 Data(x, edge_index, edge_attr)
|
└─→ last_observation # 构建 Data(x, edge_index, edge_attr, eta, area, global_stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### 训练
|
### 训练
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -182,16 +238,25 @@ CUDA_VISIBLE_DEVICES=7 python src/main.py --mode train --config src/config.yaml
|
||||||
首次迭代需收集 256 步 rollout(含 FEM 求解),后续打印:
|
首次迭代需收集 256 步 rollout(含 FEM 求解),后续打印:
|
||||||
|
|
||||||
```
|
```
|
||||||
it | loss ev agents reward x<0 elig sel time
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
| 字段 | 含义 | 健康范围 |
|
| 字段 | 含义 | 健康范围 |
|
||||||
|------|------|---------|
|
|------|------|---------|
|
||||||
| `x<0` | `mean(x_i < 0)`,负值动作比例(纯诊断) | 越负的单元优先级越高 |
|
| `corr_m` | `c·tanh(δ)` 均值 | 接近 0,Actor 修正无系统性偏差 |
|
||||||
| `elig` | 通过双过滤器的候选占比 | 排除数值退化 + 低误差的单元 |
|
| `corr_s` | `c·tanh(δ)` 标准差 | 应稳定在 0.03–0.08,不应持续涨到 0.15 |
|
||||||
| `mask` | 被 Reverse Dörfler 剔除的噪声尾部占比(累积能量 <1% 总误差的底部单元) | 因场景而异,非固定比例 |
|
| `r_le_sc` | Pearson r(log_η, score) | 接近 1.0 → Actor 修正小;<0.9 → Actor 在主动修正 |
|
||||||
|
| `δ<0` | Actor 输出负值的比例(纯诊断) | — |
|
||||||
|
| `elig` | 通过双过滤器的候选占比 | — |
|
||||||
| `sel` | 实际选中的细化单元数 | 每步最多 N_current // 4 |
|
| `sel` | 实际选中的细化单元数 | 每步最多 N_current // 4 |
|
||||||
| `n_budget` | 全局物理预算(每 episode 固定) | k=30 → ~1800 |
|
| `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` | 当前步数 | — |
|
||||||
|
|
||||||
### 测试
|
### 测试
|
||||||
|
|
||||||
|
|
@ -203,12 +268,12 @@ python src/main.py --mode test --checkpoint checkpoints/model_final.pt \
|
||||||
|
|
||||||
输出:
|
输出:
|
||||||
```
|
```
|
||||||
Step 0: reward=--- error=1.0000 elements=174 budget=1885
|
Step 0: reward=--- aw_rel=79.28% max_err=2.2133 elements=1078 budget=...
|
||||||
Step 1: reward=+12.345 error=0.7160 elements=618 x<0=0.45 sel=87
|
Step 1: reward=+2.345 aw_rel=30.10% max_err=0.7096 elements=2020 sel=269
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
每步打印 `reward error elements x<0 sel`,第 0 步额外显示 `N_budget`。
|
每步打印 `reward aw_rel max_err elements sel`,第 0 步额外显示 `N_budget`。
|
||||||
|
|
||||||
### 可视化
|
### 可视化
|
||||||
|
|
||||||
|
|
@ -251,134 +316,52 @@ SBC 边界条件仍用 $k_{local}$(物理正确),仅归一化因子改用
|
||||||
|
|
||||||
### 连续尺寸场策略(score-based + 物理预算约束 + 动作掩码)
|
### 连续尺寸场策略(score-based + 物理预算约束 + 动作掩码)
|
||||||
|
|
||||||
Actor 输出标量 x_i → score = -x_i 直接排序,在预算和上限内选 top-k:
|
Actor 输出标量 δ_i → `score_i = log(η_i + ε) + c·tanh(δ_i)`,在预算和上限内选 top-k:
|
||||||
|
|
||||||
```
|
```
|
||||||
A_budget_i = ½(λ_local_i / 6)² // 每局部波长方向 ~6 尺度点(仅用于 N_budget 计算)
|
ε = max(0.01·median(η), 1e-12) // 动态 eps,防止 log(0)
|
||||||
λ_local_i = 2π / (k · √ε_r_i)
|
corr_i = c · tanh(δ_i) c = correction_scale // Actor 修正幅值 ∈ [−c, +c]
|
||||||
|
score_i = log(η_i + ε) + corr_i // 降序 top-k
|
||||||
N_budget = max(N_phys, ⌈5·N_init⌉) // rho_min=5.0,至少 5× 初始单元数,保证 RL 多步细化空间
|
|
||||||
N_phys = ⌈ Σ |K_i| / A_budget_i ⌉ // 全局物理预算(k=30 真空 ~1800)
|
|
||||||
|
|
||||||
|
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
|
remaining = N_budget − N_current
|
||||||
V_min_safeguard = 1e-10 × domain_area // 纯数值底线(防止 FEM 求解器退化)
|
V_min_safeguard = 1e-10 × domain_area
|
||||||
eligible: area > V_min_safeguard AND η_K ∈ Reverse Dörfler 保留集 // 数值底线 + 能量尾部淘汰 (ε_noise=0.01, ≥20% floor)
|
eligible: area > V_min_safeguard AND η_K ∈ Reverse Dörfler 保留集 (ε_noise=0.01, ≥20% floor)
|
||||||
num = min(|eligible|, N_current//4, remaining//3)
|
num = min(|eligible|, N_current//4, remaining//3)
|
||||||
selected = top-k by score = -x_i → 1-to-4 切分
|
selected = top-k by score descending → 1-to-4 切分
|
||||||
```
|
```
|
||||||
|
|
||||||
- score = -x_i:x 越小 ⇒ 优先级越高(纯排序,不设正负门槛)
|
- Actor 通过 bounded correction 微调排序,不能覆盖物理先验 log(η)
|
||||||
- 不再使用 `0.25·A_budget` 启发式面积地板:RL 应自主学会"细化到多细",而非被人类经验 (12 点/波长) 限制。仅保留数值底线 V_min_safeguard = 1e-10 × domain_area 防止浮点精度问题。
|
- Reverse Dörfler 动作掩码剔除噪声尾部,≥20% floor 确保 Agent 始终有选择空间
|
||||||
- per-step cap 从固定 200 改为自适应 `N_current // 4`,随网格规模缩放但增速更缓,避免大网格时单步消耗过多预算。rho_min 从 3.0 提升到 5.0,赋予更多预算余量。
|
- sel=0 提前终止:agent 选中 0 个单元时 episode 自动结束
|
||||||
- **sel=0 提前终止**:当 agent 选中 0 个单元细化(预算耗尽或 Reverse Dörfler 屏蔽所有候选)时 episode 自动结束,不再浪费 FEM 求解
|
- k_exponent=2.0:P1 Helmholtz 理论最优初始网格缩放
|
||||||
- **k_exponent 可配**:初始网格缩放指数可通过 `helmholtz.k_exponent` 配置(默认 2.0),² 为 P1 Helmholtz 理论最优;对 k=30 的 $N_{init}$ 为 k=6 的 25× 倍
|
|
||||||
- **动作掩码 (Reverse Dörfler)**:按 η_K 升序排列,剔除累积平方误差贡献 < ε_noise·Ση² 的底部单元(数值噪声/已收敛区)。基于能量分布而非密度分位数,在重尾和均匀误差分布下均自适应。保留率不低于 20% 确保 Agent 始终有充分的选择空间
|
|
||||||
|
|
||||||
### 奖励计算
|
### 奖励计算
|
||||||
|
|
||||||
---
|
纯局部改善 reward,无调制、无 bonus:
|
||||||
|
|
||||||
#### 变量
|
|
||||||
|
|
||||||
| 符号 | 含义 |
|
|
||||||
|------|------|
|
|
||||||
| `η_K = √(r_int² + r_jump² + r_sbc²)` | 逐单元误差指示子,`r_*` 定义见式 (1)–(3) |
|
|
||||||
| `C(i)` | 父单元 i 经 1-to-4 切分产生的子单元集合 |
|
|
||||||
| `M_new[j]` | 子单元 j 对应的父单元索引 |
|
|
||||||
| `n_i = |C(i)|` | 父单元 i 的子单元数(1 表示未切分) |
|
|
||||||
| `E_global = √(Σ η_K²) / \|\|u_h\|\|_{L₂(Ω)}` | 全局无量纲误差 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 算法
|
|
||||||
|
|
||||||
**Step 0 — 保存旧状态** (`_set_previous_step`)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
η_old ← 旧逐单元 η_K
|
r_local_i = log(η_old_i + ε) − log(l2_η_children_i + ε)
|
||||||
||u_h_old|| ← 旧解 L₂ 范数 (≈ √(Σ |ū_K|² · area_K))
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
**Step 1 — 网格细化** (`_refine_mesh`)
|
|
||||||
|
|
||||||
```
|
|
||||||
x = action.flatten()
|
|
||||||
score = -x // x 越小 ⇒ 优先级越高
|
|
||||||
|
|
||||||
remaining = N_budget − N_old
|
|
||||||
max_by_budget = max(0, remaining // 3)
|
|
||||||
// 数值底线 + Reverse Dörfler 能量尾部淘汰
|
|
||||||
V_min_safeguard = 1e-10 × domain_area // 纯数值安全底线,防止 FEM 退化
|
|
||||||
η_sq = η_old²; total_energy = Σ η_sq
|
|
||||||
k_dorfler = searchsorted(cumsum(sort_asc(η_sq)), ε_noise·total_energy) // ε_noise=0.01
|
|
||||||
k = min(k_dorfler, N − max(1, N//5)) // ≥20% floor
|
|
||||||
eligible = {i | V_old[i] > V_min_safeguard AND i ∈ sort_asc_idx[k:] }
|
|
||||||
num = min(|eligible|, N_old//3, max_by_budget)
|
|
||||||
elements_to_refine = top-k of eligible by score
|
|
||||||
|
|
||||||
M_new[j] ∈ {0,…,N_old-1} // 子→父映射
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2 — FEM 求解 + 误差估计**
|
|
||||||
|
|
||||||
```
|
|
||||||
η_new ← 新逐单元 η_K
|
|
||||||
||u_h_new|| ← 新解 L₂ 范数
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3 — 因果奖励**(零和预算审查)
|
|
||||||
|
|
||||||
ε_dynamic = max(0.01 × η_P95, 1e-6)
|
|
||||||
|
|
||||||
// Refined parents: r_local + zero-sum bonus − penalty
|
|
||||||
if i ∈ refined_parents:
|
|
||||||
r_i = log(η_old + ε) − log(√(Σ η_child²) + ε) // r_local ≥ 0 (L₂ 聚合)
|
|
||||||
+ 0.3 × (η_old / μ − 1.0) // zero-sum bonus (Σ = 0)
|
|
||||||
− 0.06 // action penalty
|
|
||||||
|
|
||||||
// Unrefined parents: causal isolation
|
|
||||||
else:
|
|
||||||
r_i = 0
|
|
||||||
|
|
||||||
> **零和奖金**:α·(η/μ−1) 全场求和为零。细化高于均值的单元得正奖金,低于均值的倒扣。
|
|
||||||
> 这是 Dörfler 准则的 RL 对偶:Agent 必须选出误差超过全均水平的单元。
|
|
||||||
> **因果隔离**:未细化单元 r ≡ 0。零和奖金本身足够强(介质内 +0.51)、
|
|
||||||
> 不再需要忽视惩罚的推力,排序机制自动淘汰不划算的单元。
|
|
||||||
> **L₂ 聚合**:√(Σ η_child²) ≤ η_parent 天然成立,r_local ≥ 0 永不惩罚细化。
|
|
||||||
|
|
||||||
**Step 4 — 全局误差(仅诊断)**
|
|
||||||
|
|
||||||
global_bonus = α·[log(E_old) − log(E_new)],α = 0.5
|
|
||||||
|
|
||||||
不注入 Actor reward。Helmholtz 污染误差可使 E_new > E_old 在正确细化后发生,
|
|
||||||
注入 global_bonus 导致因果断裂。Actor 仅优化 Step 3 的 per-element reward。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 奖励标度校准(旧尺寸场下测量,score-based 后需重新标定)
|
|
||||||
|
|
||||||
在随机策略下实测各分量量级(1321 个 refined-parent 样本):
|
|
||||||
|
|
||||||
| 分量 | 均值 | 占 r_local 比例 |
|
|
||||||
|------|------|:---:|
|
|
||||||
| `r_local` (仅 refined parents) | +0.364 | — |
|
|
||||||
| `penalty` λ·(n−1), λ=0.02 | +0.045 | 1/8 |
|
|
||||||
| `α·ΔlogE` α=0.2 | +0.069 | 1/5 |
|
|
||||||
| **net** | **+0.387** | |
|
|
||||||
|
|
||||||
满足 `r_local ≫ penalty` 且 `α·ΔlogE ≈ r_local / 5`,局部 credit assignment 不被全局信号淹没。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 设计要点
|
|
||||||
|
|
||||||
| 组件 | 聚合 | 作用 |
|
| 组件 | 聚合 | 作用 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 局部项 `log(η_old / √(Σ η_child²))` | scatter_add,仅 refined parents | L₂ 保证 r_local ≥ 0;int 主导 +0.69 |
|
| 局部项 `log(η_old / √(Σ η_child²))` | scatter_add,仅 refined parents | L₂ 保证 r_local ≥ 0;clip 到 [0, 2.0] |
|
||||||
| 零和奖金 `0.3×(η/μ−1)` | 仅 refined parents | Σ=0;高于 μ 得正奖,低于 μ 倒扣 (Dörfler 准则的 RL 对偶) |
|
| 动作惩罚 `λ=0.02` | per-refined-parent | 轻微抑制网格膨胀(1-to-4 扣 0.06) |
|
||||||
| 动作惩罚 `λ=0.06` | per-refined-parent | 轻微抑制网格膨胀(1-to-4 扣 0.06) |
|
| **step0 降权** | step 0 时 penalty × 0.3 | 防止第一步"真实误差改善但 reward 给负反馈" |
|
||||||
| 因果隔离 `r=0` | unrefined parents | 零和奖金足够强,不需额外推力 |
|
| 因果隔离 `r=0` | unrefined parents | 未细化元素干净零基准 |
|
||||||
| 全局项 `α·ΔlogE` α=0.5 | 仅诊断 | 不注入 Actor,避免污染误差因果断裂 |
|
|
||||||
|
**step0 penalty 降权动机**:诊断发现 step 0 经常出现 reward < 0 但真实 aw_rel 改善的情况,说明 element penalty 淹没了真实的物理改善信号。降权后 step 0 的 reward 与 Δaw_rel 符号一致性提高。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -388,7 +371,185 @@ global_bonus = α·[log(E_old) − log(E_new)],α = 0.5
|
||||||
- **奖励归一化**: rollout 内 reward 做 z-score 归一化(std < 1e-8 则跳过)
|
- **奖励归一化**: rollout 内 reward 做 z-score 归一化(std < 1e-8 则跳过)
|
||||||
- **Value clipping**: 默认 clip_range=0.2
|
- **Value clipping**: 默认 clip_range=0.2
|
||||||
- **梯度裁剪**: max_grad_norm=0.5
|
- **梯度裁剪**: max_grad_norm=0.5
|
||||||
- **log_std clamp**: 每步 `optimizer.step()` 后将 `log_std` clamp 到 `[-3.0, -1.0]`,σ ∈ [0.05, 0.37]<br>
|
- **log_std clamp**: 每步 `optimizer.step()` 后将 `log_std` clamp 到 `[-2.5, -1.0]`,σ ∈ [0.082, 0.37]<br>
|
||||||
初始化 `-2.0` (σ≈0.135),放宽下限防止策略过早确定化
|
初始化 `-2.0` (σ≈0.135),放宽下限防止策略过早确定化
|
||||||
- **熵正则**: `entropy_coefficient=0.005`,施加有意义的探索压力防止 x<0 崩塌
|
- **熵正则**: `entropy_coefficient=0.01`
|
||||||
- **epochs_per_iteration**: 3,减少对同一 rollout 的过拟合
|
- **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).
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,3 +1,4 @@
|
||||||
|
import time
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import gym
|
import gym
|
||||||
|
|
@ -55,7 +56,6 @@ class MeshRefinement(gym.Env):
|
||||||
# graph connectivity, feature and action space #
|
# graph connectivity, feature and action space #
|
||||||
################################################
|
################################################
|
||||||
self._reward_type = environment_config.get("reward_type")
|
self._reward_type = environment_config.get("reward_type")
|
||||||
self._global_reward_alpha = float(environment_config.get("global_reward_alpha", 0.2))
|
|
||||||
_rho_w = environment_config.get("rho_weights", {})
|
_rho_w = environment_config.get("rho_weights", {})
|
||||||
self._w_rho_int = float(_rho_w.get("w_int", 1.0))
|
self._w_rho_int = float(_rho_w.get("w_int", 1.0))
|
||||||
self._w_rho_jump = float(_rho_w.get("w_jump", 1.0))
|
self._w_rho_jump = float(_rho_w.get("w_jump", 1.0))
|
||||||
|
|
@ -223,6 +223,25 @@ class MeshRefinement(gym.Env):
|
||||||
self._diag_selected_count = -1 # 防止跨 episode 残留触发 is_terminal
|
self._diag_selected_count = -1 # 防止跨 episode 残留触发 is_terminal
|
||||||
self._diag_dorfler_tail_ratio = 0.0
|
self._diag_dorfler_tail_ratio = 0.0
|
||||||
self._diag_dorfler_floor_active = False
|
self._diag_dorfler_floor_active = False
|
||||||
|
self._diag_corr_raw_mean = 0.0
|
||||||
|
self._diag_corr_mean = 0.0
|
||||||
|
self._diag_corr_std = 0.0
|
||||||
|
self._diag_corr_abs = 0.0
|
||||||
|
self._diag_neg_ratio = 0.0
|
||||||
|
self._diag_score_eta_corr = 0.0
|
||||||
|
self._diag_max_by_budget = 0
|
||||||
|
self._diag_max_by_growth = 0
|
||||||
|
self._diag_n_budget = 0
|
||||||
|
self._diag_remaining = 0
|
||||||
|
self._diag_n_eligible = 0
|
||||||
|
self._diag_n_next = 0
|
||||||
|
self._diag_corr_rel_eta_corr = 0.0
|
||||||
|
self._diag_corr_inside_mean = 0.0
|
||||||
|
self._diag_corr_outside_mean = 0.0
|
||||||
|
self._diag_corr_top_eta_mean = 0.0
|
||||||
|
self._diag_corr_low_eta_mean = 0.0
|
||||||
|
self._diag_global_top10_eta_energy = 0.0
|
||||||
|
self._diag_remaining_ratio = 0.0
|
||||||
|
|
||||||
# reset internal state that tracks statistics over the episode
|
# reset internal state that tracks statistics over the episode
|
||||||
self._previous_error_per_element = self.error_per_element
|
self._previous_error_per_element = self.error_per_element
|
||||||
|
|
@ -325,9 +344,11 @@ class MeshRefinement(gym.Env):
|
||||||
# solve equation and calculate error per element/element
|
# solve equation and calculate error per element/element
|
||||||
self._previous_error_per_element = self.error_per_element
|
self._previous_error_per_element = self.error_per_element
|
||||||
|
|
||||||
|
t_fem = time.perf_counter()
|
||||||
self._error_estimation_dict = (
|
self._error_estimation_dict = (
|
||||||
self.fem_problem.calculate_solution_and_get_error()
|
self.fem_problem.calculate_solution_and_get_error()
|
||||||
)
|
)
|
||||||
|
self._last_fem_solve_ms = (time.perf_counter() - t_fem) * 1e3
|
||||||
|
|
||||||
# query returns
|
# query returns
|
||||||
observation = self.last_observation
|
observation = self.last_observation
|
||||||
|
|
@ -346,12 +367,30 @@ class MeshRefinement(gym.Env):
|
||||||
"is_truncated": self.is_truncated,
|
"is_truncated": self.is_truncated,
|
||||||
"return": self._cumulative_return,
|
"return": self._cumulative_return,
|
||||||
"neg_action_ratio": getattr(self, "_diag_neg_ratio", 0.0),
|
"neg_action_ratio": getattr(self, "_diag_neg_ratio", 0.0),
|
||||||
|
"corr_raw_mean": getattr(self, "_diag_corr_raw_mean", 0.0),
|
||||||
|
"corr_mean": getattr(self, "_diag_corr_mean", 0.0),
|
||||||
|
"corr_std": getattr(self, "_diag_corr_std", 0.0),
|
||||||
|
"corr_abs": getattr(self, "_diag_corr_abs", 0.0),
|
||||||
|
"score_eta_corr": getattr(self, "_diag_score_eta_corr", 0.0),
|
||||||
"eligible_ratio": getattr(self, "_diag_eligible_ratio", 0.0),
|
"eligible_ratio": getattr(self, "_diag_eligible_ratio", 0.0),
|
||||||
"masked_ratio": getattr(self, "_diag_masked_ratio", 0.0),
|
"masked_ratio": getattr(self, "_diag_masked_ratio", 0.0),
|
||||||
"selected_count": getattr(self, "_diag_selected_count", 0),
|
"selected_count": getattr(self, "_diag_selected_count", 0),
|
||||||
"dorfler_tail_ratio": getattr(self, "_diag_dorfler_tail_ratio", 0.0),
|
"dorfler_tail_ratio": getattr(self, "_diag_dorfler_tail_ratio", 0.0),
|
||||||
"dorfler_floor_active": float(getattr(self, "_diag_dorfler_floor_active", False)),
|
"dorfler_floor_active": float(getattr(self, "_diag_dorfler_floor_active", False)),
|
||||||
"n_budget": self._n_budget,
|
"n_budget": self._n_budget,
|
||||||
|
"remaining": getattr(self, "_diag_remaining", 0),
|
||||||
|
"max_by_budget": getattr(self, "_diag_max_by_budget", 0),
|
||||||
|
"max_by_growth": getattr(self, "_diag_max_by_growth", 0),
|
||||||
|
"n_eligible": getattr(self, "_diag_n_eligible", 0),
|
||||||
|
"n_next": getattr(self, "_diag_n_next", 0),
|
||||||
|
"fem_solve_ms": self._last_fem_solve_ms,
|
||||||
|
"corr_rel_eta_corr": getattr(self, "_diag_corr_rel_eta_corr", 0.0),
|
||||||
|
"corr_inside_mean": getattr(self, "_diag_corr_inside_mean", 0.0),
|
||||||
|
"corr_outside_mean": getattr(self, "_diag_corr_outside_mean", 0.0),
|
||||||
|
"corr_top_eta_mean": getattr(self, "_diag_corr_top_eta_mean", 0.0),
|
||||||
|
"corr_low_eta_mean": getattr(self, "_diag_corr_low_eta_mean", 0.0),
|
||||||
|
"global_top10_eta_energy": getattr(self, "_diag_global_top10_eta_energy", 0.0),
|
||||||
|
"remaining_ratio": getattr(self, "_diag_remaining_ratio", 0.0),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return observation, self._reward, done, info
|
return observation, self._reward, done, info
|
||||||
|
|
@ -527,31 +566,56 @@ class MeshRefinement(gym.Env):
|
||||||
|
|
||||||
if self._refinement_strategy == "continuous_sizing_field":
|
if self._refinement_strategy == "continuous_sizing_field":
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# Score-based 细化选择(由 actor 直接排序,物理预算约束)
|
# Score-based 细化选择:log(η) baseline + bounded Actor correction
|
||||||
#
|
#
|
||||||
# Actor 输出标量 x_i: x_i < 0 → 希望细化; x_i > 0 → 不希望细化
|
# score_i = log(η_i + eps) + c · tanh(δ_i)
|
||||||
# 排序依据 score = -x_i,在预算和上限内选 top-k
|
|
||||||
#
|
#
|
||||||
# 物理预算 N_budget: Σ area_K / A_budget,其中
|
# η_i = current-step residual indicator (physical prior)
|
||||||
# A_budget = ½(λ_local/6)²,对应每局部波长方向 ~6 个尺度点
|
# δ_i = Actor output (continuous scalar per element)
|
||||||
|
# c = correction_scale (0.7) — bounds Actor influence
|
||||||
|
# eps = dynamic: 0.01 · median(η) — prevents log(0)
|
||||||
#
|
#
|
||||||
# 动作掩码 (Reverse Dörfler): 按 η_K 升序排列,剔除累积平方误差
|
# Selection: top-k by score descending (higher score → refine).
|
||||||
# 贡献 < ε_noise·Ση² 的底部单元(数值噪声/已收敛区),保留 ≥20%
|
# Actor can boost or suppress priority by at most ±c in the log-η
|
||||||
# 的单元确保 Agent 始终有充分的选择空间
|
# domain, but cannot override the physical prior.
|
||||||
# ================================================================
|
# ================================================================
|
||||||
x = action.flatten()
|
delta = action.flatten()
|
||||||
|
|
||||||
# ── 训练监控指标(在所有 early return 之前计算)──
|
eta = self._eta_indicator
|
||||||
self._diag_neg_ratio = float(np.mean(x < 0.0))
|
eps_score = max(0.01 * float(np.median(eta)), 1e-12)
|
||||||
|
log_eta = np.log(np.maximum(eta, 1e-30) + eps_score)
|
||||||
|
|
||||||
|
c = float(self._environment_config.get("correction_scale", 0.7))
|
||||||
|
corr_raw = c * np.tanh(delta)
|
||||||
|
|
||||||
remaining = self._n_budget - self._num_elements
|
remaining = self._n_budget - self._num_elements
|
||||||
max_parents_by_budget = max(0, remaining // 3)
|
max_parents_by_budget = max(0, remaining // 6)
|
||||||
|
|
||||||
|
self._diag_max_by_budget = max_parents_by_budget
|
||||||
|
|
||||||
if max_parents_by_budget <= 0:
|
if max_parents_by_budget <= 0:
|
||||||
self._diag_eligible_ratio = 0.0
|
self._diag_eligible_ratio = 0.0
|
||||||
self._diag_selected_count = 0
|
self._diag_selected_count = 0
|
||||||
self._diag_dorfler_tail_ratio = 0.0
|
self._diag_dorfler_tail_ratio = 0.0
|
||||||
self._diag_dorfler_floor_active = False
|
self._diag_dorfler_floor_active = False
|
||||||
|
self._diag_max_by_growth = max(1, self._num_elements // 4)
|
||||||
|
self._diag_n_budget = self._n_budget
|
||||||
|
self._diag_remaining = remaining
|
||||||
|
self._diag_n_eligible = 0
|
||||||
|
self._diag_n_next = self._num_elements
|
||||||
|
self._diag_corr_raw_mean = float(np.mean(corr_raw))
|
||||||
|
self._diag_corr_mean = 0.0
|
||||||
|
self._diag_corr_std = 0.0
|
||||||
|
self._diag_corr_abs = 0.0
|
||||||
|
self._diag_neg_ratio = float(np.mean(delta < 0.0))
|
||||||
|
self._diag_score_eta_corr = 0.0
|
||||||
|
self._diag_corr_rel_eta_corr = 0.0
|
||||||
|
self._diag_corr_inside_mean = 0.0
|
||||||
|
self._diag_corr_outside_mean = 0.0
|
||||||
|
self._diag_corr_top_eta_mean = 0.0
|
||||||
|
self._diag_corr_low_eta_mean = 0.0
|
||||||
|
self._diag_global_top10_eta_energy = 0.0
|
||||||
|
self._diag_remaining_ratio = remaining / max(self._n_budget, 1)
|
||||||
return np.array([], dtype=np.int64)
|
return np.array([], dtype=np.int64)
|
||||||
|
|
||||||
# 动态计算每单元预算面积(仅用于 N_budget 全局资源上限)
|
# 动态计算每单元预算面积(仅用于 N_budget 全局资源上限)
|
||||||
|
|
@ -560,32 +624,23 @@ class MeshRefinement(gym.Env):
|
||||||
lambda_local = 2.0 * np.pi / (k * np.sqrt(np.maximum(eps_r_elem, 1.0)))
|
lambda_local = 2.0 * np.pi / (k * np.sqrt(np.maximum(eps_r_elem, 1.0)))
|
||||||
A_budget = 0.5 * (lambda_local / 6.0) ** 2
|
A_budget = 0.5 * (lambda_local / 6.0) ** 2
|
||||||
|
|
||||||
# 纯数值安全底线:仅防止 scikit-fem 因浮点精度导致的退化/奇异。
|
|
||||||
# 不再用 0.25*A_budget —— RL 应自主学会"多细才够",
|
|
||||||
# 而非被人为启发式 (12 点/波长) 限制。
|
|
||||||
domain_area = float(np.prod(self.fem_problem.plot_boundary[2:] - self.fem_problem.plot_boundary[:2]))
|
domain_area = float(np.prod(self.fem_problem.plot_boundary[2:] - self.fem_problem.plot_boundary[:2]))
|
||||||
V_min_safeguard = 1e-10 * domain_area
|
V_min_safeguard = 1e-10 * domain_area
|
||||||
|
|
||||||
# Filter 1: numerical safeguard only — no physics heuristic
|
# Filter 1: numerical safeguard only
|
||||||
area_eligible = np.where(self.element_volumes > V_min_safeguard)[0]
|
area_eligible = np.where(self.element_volumes > V_min_safeguard)[0]
|
||||||
|
|
||||||
# Filter 2: Reverse Dörfler — eliminate the noise tail, not select the elite.
|
# Filter 2: Reverse Dörfler — eliminate noise tail
|
||||||
# Sort η_K ascending; remove the smallest elements whose cumulative η²
|
|
||||||
# contributes < ε_noise of total error energy. These are numerically
|
|
||||||
# converged or noise — not worth the agent's attention.
|
|
||||||
# A 20% floor on the eligible ratio guarantees the agent meaningful
|
|
||||||
# choices even in heavy-tailed distributions where energy is concentrated.
|
|
||||||
eta_current = self._eta_indicator
|
eta_current = self._eta_indicator
|
||||||
eta_sq = eta_current ** 2
|
eta_sq = eta_current ** 2
|
||||||
total_energy = np.sum(eta_sq)
|
total_energy = np.sum(eta_sq)
|
||||||
|
|
||||||
if total_energy > 0:
|
if total_energy > 0:
|
||||||
idx_asc = np.argsort(eta_current) # ascending
|
idx_asc = np.argsort(eta_current)
|
||||||
cumsum_asc = np.cumsum(eta_sq[idx_asc])
|
cumsum_asc = np.cumsum(eta_sq[idx_asc])
|
||||||
eps_noise = 0.01 # bottom 1% of energy = noise tail
|
eps_noise = 0.01
|
||||||
k_dorfler = int(np.searchsorted(cumsum_asc, eps_noise * total_energy))
|
k_dorfler = int(np.searchsorted(cumsum_asc, eps_noise * total_energy))
|
||||||
self._diag_dorfler_tail_ratio = float(k_dorfler) / max(self._num_elements, 1)
|
self._diag_dorfler_tail_ratio = float(k_dorfler) / max(self._num_elements, 1)
|
||||||
# floor: keep at least 20% of elements for RL agent choice
|
|
||||||
min_keep = max(1, self._num_elements // 5)
|
min_keep = max(1, self._num_elements // 5)
|
||||||
k = min(k_dorfler, self._num_elements - min_keep)
|
k = min(k_dorfler, self._num_elements - min_keep)
|
||||||
self._diag_dorfler_floor_active = k < k_dorfler
|
self._diag_dorfler_floor_active = k < k_dorfler
|
||||||
|
|
@ -597,26 +652,104 @@ class MeshRefinement(gym.Env):
|
||||||
|
|
||||||
eligible = np.intersect1d(area_eligible, error_eligible)
|
eligible = np.intersect1d(area_eligible, error_eligible)
|
||||||
|
|
||||||
|
# ── correction centering (eligible only) ──
|
||||||
|
# Global shift is meaningless for top-k ranking; center within
|
||||||
|
# eligible candidates so the Actor only controls relative priority.
|
||||||
|
self._diag_corr_raw_mean = float(np.mean(corr_raw))
|
||||||
|
if len(eligible) > 0:
|
||||||
|
corr = corr_raw - np.mean(corr_raw[eligible])
|
||||||
|
else:
|
||||||
|
corr = corr_raw - np.mean(corr_raw)
|
||||||
|
|
||||||
|
score = log_eta + corr
|
||||||
|
|
||||||
|
# ── diagnostics ──
|
||||||
|
self._diag_neg_ratio = float(np.mean(delta < 0.0))
|
||||||
|
self._diag_corr_mean = float(np.mean(corr))
|
||||||
|
self._diag_corr_std = float(np.std(corr))
|
||||||
|
self._diag_corr_abs = float(np.mean(np.abs(corr)))
|
||||||
|
# Spearman-like: Pearson r between log_eta and score
|
||||||
|
le = log_eta - log_eta.mean()
|
||||||
|
sc = score - score.mean()
|
||||||
|
denom = np.sqrt(np.sum(le**2) * np.sum(sc**2))
|
||||||
|
self._diag_score_eta_corr = float(np.sum(le * sc) / max(denom, 1e-12))
|
||||||
|
|
||||||
self._diag_eligible_ratio = float(len(eligible)) / max(self._num_elements, 1)
|
self._diag_eligible_ratio = float(len(eligible)) / max(self._num_elements, 1)
|
||||||
self._diag_masked_ratio = (
|
self._diag_masked_ratio = (
|
||||||
1.0 - float(len(eligible)) / max(len(area_eligible), 1)
|
1.0 - float(len(eligible)) / max(len(area_eligible), 1)
|
||||||
if len(area_eligible) > 0 else 0.0
|
if len(area_eligible) > 0 else 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ── GVN global-conditioned correction diagnostics ──
|
||||||
|
# corr-rel_logeta correlation
|
||||||
|
rel_le = log_eta - log_eta.mean()
|
||||||
|
rel_corr = corr - corr.mean()
|
||||||
|
denom_rc = np.sqrt(np.sum(rel_le**2) * np.sum(rel_corr**2))
|
||||||
|
self._diag_corr_rel_eta_corr = float(
|
||||||
|
np.sum(rel_le * rel_corr) / max(denom_rc, 1e-12)
|
||||||
|
)
|
||||||
|
|
||||||
|
# correction by region (inside/outside scatterer)
|
||||||
|
eps_r = self._epsilon_r_elements
|
||||||
|
inside_mask = eps_r > 1.0
|
||||||
|
outside_mask = ~inside_mask
|
||||||
|
self._diag_corr_inside_mean = float(np.mean(corr[inside_mask])) if inside_mask.any() else 0.0
|
||||||
|
self._diag_corr_outside_mean = float(np.mean(corr[outside_mask])) if outside_mask.any() else 0.0
|
||||||
|
|
||||||
|
# correction by eta rank
|
||||||
|
eta = self._eta_indicator
|
||||||
|
k10 = max(1, int(0.1 * self._num_elements))
|
||||||
|
top_idx = np.argsort(eta)[-k10:]
|
||||||
|
low_idx = np.argsort(eta)[:self._num_elements // 2]
|
||||||
|
self._diag_corr_top_eta_mean = float(np.mean(corr[top_idx]))
|
||||||
|
self._diag_corr_low_eta_mean = float(np.mean(corr[low_idx]))
|
||||||
|
|
||||||
|
# global top10 eta energy and remaining ratio
|
||||||
|
eta_sq = eta ** 2
|
||||||
|
total_energy = float(np.sum(eta_sq))
|
||||||
|
self._diag_global_top10_eta_energy = (
|
||||||
|
float(np.sum(eta_sq[top_idx])) / (total_energy + 1e-12)
|
||||||
|
)
|
||||||
|
self._diag_remaining_ratio = remaining / max(self._n_budget, 1)
|
||||||
|
|
||||||
|
max_by_growth = max(1, self._num_elements // 4)
|
||||||
|
self._diag_max_by_growth = max_by_growth
|
||||||
|
self._diag_n_budget = self._n_budget
|
||||||
|
self._diag_remaining = remaining
|
||||||
|
self._diag_n_eligible = len(eligible)
|
||||||
|
|
||||||
num = min(
|
num = min(
|
||||||
len(eligible),
|
len(eligible),
|
||||||
max(1, self._num_elements // 4),
|
max_by_growth,
|
||||||
max_parents_by_budget,
|
max_parents_by_budget,
|
||||||
)
|
)
|
||||||
|
|
||||||
if num <= 0:
|
if num <= 0:
|
||||||
self._diag_selected_count = 0
|
self._diag_selected_count = 0
|
||||||
|
self._diag_n_next = self._num_elements
|
||||||
return np.array([], dtype=np.int64)
|
return np.array([], dtype=np.int64)
|
||||||
|
|
||||||
# x 越小 ⇒ 优先级越高(纯排序,不设正负门槛)
|
# top-k by score descending with physical tie-breaking
|
||||||
score = -x
|
# (avoids spatially arbitrary selection when scores are tied)
|
||||||
selected = eligible[np.argsort(score[eligible])[-num:]]
|
_fp = self.fem_problem.fem_problem
|
||||||
|
_cx = getattr(_fp, "_cx", 0.5)
|
||||||
|
_cy = getattr(_fp, "_cy", 0.5)
|
||||||
|
_radius = getattr(_fp, "_radius", 0.2)
|
||||||
|
_mesh = self.mesh
|
||||||
|
_p = _mesh.p
|
||||||
|
_t = _mesh.t
|
||||||
|
_mx = (_p[0, _t[0]] + _p[0, _t[1]] + _p[0, _t[2]]) / 3.0
|
||||||
|
_my = (_p[1, _t[0]] + _p[1, _t[1]] + _p[1, _t[2]]) / 3.0
|
||||||
|
_dist = np.sqrt((_mx - _cx)**2 + (_my - _cy)**2)
|
||||||
|
_sd = _dist - _radius
|
||||||
|
_inside = (_dist <= _radius).astype(np.float32)
|
||||||
|
_abs_sd = np.abs(_sd[eligible])
|
||||||
|
_inside_elig = _inside[eligible]
|
||||||
|
_tie_key = -_abs_sd + _inside_elig
|
||||||
|
_composite = score[eligible] * 1e6 + _tie_key
|
||||||
|
selected = eligible[np.argsort(_composite)[-num:]]
|
||||||
self._diag_selected_count = len(selected)
|
self._diag_selected_count = len(selected)
|
||||||
|
self._diag_n_next = self._num_elements + len(selected) * 3 # estimate
|
||||||
elements_to_refine = selected
|
elements_to_refine = selected
|
||||||
|
|
||||||
elif self._refinement_strategy in ["absolute", "absolute_discrete"]:
|
elif self._refinement_strategy in ["absolute", "absolute_discrete"]:
|
||||||
|
|
@ -700,6 +833,93 @@ class MeshRefinement(gym.Env):
|
||||||
error_per_dim = np.sqrt(np.sum(error_per_element**2, axis=0))
|
error_per_dim = np.sqrt(np.sum(error_per_element**2, axis=0))
|
||||||
return float(self.project_to_scalar(error_per_dim))
|
return float(self.project_to_scalar(error_per_dim))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_global_stats(self) -> int:
|
||||||
|
"""Number of global statistics attached to each graph observation."""
|
||||||
|
return 13
|
||||||
|
|
||||||
|
def _compute_global_stats(self) -> np.ndarray:
|
||||||
|
"""Compute graph-level global statistics for GVN conditioning.
|
||||||
|
|
||||||
|
Returns: np.ndarray of shape (num_global_stats,) with keys:
|
||||||
|
[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
|
||||||
|
[4] logeta_std
|
||||||
|
[5] logeta_max
|
||||||
|
[6] logeta_p90
|
||||||
|
[7] logeta_p75
|
||||||
|
[8] top10_eta_energy_ratio — top 10% eta^2 energy / total
|
||||||
|
[9] eligible_ratio — elements above area safeguard / total
|
||||||
|
[10] inside_eta_energy — eta energy inside scatterer / total
|
||||||
|
[11] outside_eta_energy — eta energy outside scatterer / total
|
||||||
|
[12] interface_eta_energy — eta energy near interface / total
|
||||||
|
"""
|
||||||
|
eta = self._eta_indicator
|
||||||
|
N = self._num_elements
|
||||||
|
eps = 1e-12
|
||||||
|
|
||||||
|
# log-eta for percentile stats
|
||||||
|
eps_score = max(0.01 * float(np.median(eta)), 1e-12)
|
||||||
|
logeta = np.log(np.maximum(eta, 1e-30) + eps_score)
|
||||||
|
|
||||||
|
# budget stats
|
||||||
|
remaining = max(0, self._n_budget - N)
|
||||||
|
remaining_ratio = remaining / max(self._n_budget, 1)
|
||||||
|
step_ratio = self._timestep / max(self._max_timesteps, 1)
|
||||||
|
elem_ratio = N / max(self._n_budget, 1)
|
||||||
|
|
||||||
|
# logeta stats
|
||||||
|
le_mean = float(np.mean(logeta))
|
||||||
|
le_std = float(np.std(logeta)) + 1e-8
|
||||||
|
le_max = float(np.max(logeta))
|
||||||
|
le_p90 = float(np.percentile(logeta, 90))
|
||||||
|
le_p75 = float(np.percentile(logeta, 75))
|
||||||
|
|
||||||
|
# top 10% eta energy ratio
|
||||||
|
eta_sq = eta ** 2
|
||||||
|
total_energy = float(np.sum(eta_sq))
|
||||||
|
if total_energy > 0:
|
||||||
|
k10 = max(1, int(0.1 * N))
|
||||||
|
top10_idx = np.argsort(eta_sq)[-k10:]
|
||||||
|
top10_ratio = float(np.sum(eta_sq[top10_idx])) / (total_energy + eps)
|
||||||
|
else:
|
||||||
|
top10_ratio = 0.0
|
||||||
|
|
||||||
|
# eligible ratio (area safeguard)
|
||||||
|
domain_area = float(np.prod(
|
||||||
|
self.fem_problem.plot_boundary[2:] - self.fem_problem.plot_boundary[:2]
|
||||||
|
))
|
||||||
|
V_min = 1e-10 * domain_area
|
||||||
|
eligible_ratio = float(np.sum(self.element_volumes > V_min)) / max(N, 1)
|
||||||
|
|
||||||
|
# region eta energy ratios
|
||||||
|
eps_r = self._epsilon_r_elements
|
||||||
|
inside_mask = eps_r > 1.0
|
||||||
|
outside_mask = ~inside_mask
|
||||||
|
|
||||||
|
fp = self.fem_problem.fem_problem
|
||||||
|
cx = getattr(fp, "_cx", 0.5)
|
||||||
|
cy = getattr(fp, "_cy", 0.5)
|
||||||
|
radius = getattr(fp, "_radius", 0.2)
|
||||||
|
midpoints = self._element_midpoints
|
||||||
|
dist_raw = np.sqrt((midpoints[:, 0] - cx)**2 + (midpoints[:, 1] - cy)**2)
|
||||||
|
lam = 2.0 * np.pi / max(self._wave_number, 1e-8)
|
||||||
|
interface_mask = np.abs(dist_raw - radius) < 0.2 * lam
|
||||||
|
|
||||||
|
inside_energy = float(np.sum(eta_sq[inside_mask])) / (total_energy + eps)
|
||||||
|
outside_energy = float(np.sum(eta_sq[outside_mask])) / (total_energy + eps)
|
||||||
|
interface_energy = float(np.sum(eta_sq[interface_mask])) / (total_energy + eps)
|
||||||
|
|
||||||
|
stats = np.array([
|
||||||
|
remaining_ratio, step_ratio, elem_ratio,
|
||||||
|
le_mean, le_std, le_max, le_p90, le_p75,
|
||||||
|
top10_ratio, eligible_ratio,
|
||||||
|
inside_energy, outside_energy, interface_energy,
|
||||||
|
], dtype=np.float32)
|
||||||
|
return stats
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_observation(self) -> Data:
|
def last_observation(self) -> Data:
|
||||||
"""
|
"""
|
||||||
|
|
@ -716,6 +936,10 @@ class MeshRefinement(gym.Env):
|
||||||
|
|
||||||
observation_graph = Data(**graph_dict)
|
observation_graph = Data(**graph_dict)
|
||||||
observation_graph.eta = torch.tensor(self._eta_indicator, dtype=torch.float32)
|
observation_graph.eta = torch.tensor(self._eta_indicator, dtype=torch.float32)
|
||||||
|
observation_graph.area = torch.tensor(self.element_volumes, dtype=torch.float32)
|
||||||
|
observation_graph.global_stats = torch.tensor(
|
||||||
|
self._compute_global_stats(), dtype=torch.float32
|
||||||
|
).unsqueeze(0) # [1, num_global_stats]
|
||||||
|
|
||||||
return observation_graph
|
return observation_graph
|
||||||
|
|
||||||
|
|
@ -932,25 +1156,16 @@ class MeshRefinement(gym.Env):
|
||||||
|
|
||||||
reward_per_agent = self.project_to_scalar(reward_per_agent_and_dim)
|
reward_per_agent = self.project_to_scalar(reward_per_agent_and_dim)
|
||||||
|
|
||||||
# ── Causal isolation + bounded signals ──
|
# ── Pure local improvement reward (no modulation, no bonus) ──
|
||||||
# r_local: clipped to [−1, +1] — prevents pollution-error inversions
|
# r_i = clip(log(η_old) − log(l2(η_child)), 0, rmax)
|
||||||
# (±4.6) from hijacking the Critic's value estimate.
|
# L₂ aggregation guarantees r_local ≥ 0; clip lower bound at 0 as
|
||||||
# r_bonus: 0.5·tanh(η/μ − 1) — linear near μ (preserves Dörfler),
|
# a safety floor against floating-point noise.
|
||||||
# saturates at ±0.5 for extreme η, bounded and safe.
|
rmax = 2.0
|
||||||
# Unrefined parents: r = 0 (causal isolation).
|
|
||||||
unique_old, counts = np.unique(self.agent_mapping, return_counts=True)
|
unique_old, counts = np.unique(self.agent_mapping, return_counts=True)
|
||||||
refined_mask = np.zeros(len(reward_per_agent), dtype=bool)
|
refined_mask = np.zeros(len(reward_per_agent), dtype=bool)
|
||||||
refined_mask[unique_old[counts > 1]] = True
|
refined_mask[unique_old[counts > 1]] = True
|
||||||
|
|
||||||
# Clip r_local to prevent outlier-driven value collapse
|
reward_per_agent = np.clip(reward_per_agent, 0.0, rmax)
|
||||||
reward_per_agent = np.clip(reward_per_agent, -1.0, 1.0)
|
|
||||||
|
|
||||||
# Bounded state bonus: tanh preserves Dörfler near μ, caps at extreme η
|
|
||||||
eta_raw = self._previous_eta_indicator
|
|
||||||
mu_eta = float(np.mean(eta_raw))
|
|
||||||
reward_per_agent[refined_mask] += 0.5 * np.tanh(
|
|
||||||
eta_raw[refined_mask] / (mu_eta + 1e-8) - 1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Unrefined: clean zero (causal isolation)
|
# Unrefined: clean zero (causal isolation)
|
||||||
reward_per_agent[~refined_mask] = 0.0
|
reward_per_agent[~refined_mask] = 0.0
|
||||||
|
|
@ -958,31 +1173,30 @@ class MeshRefinement(gym.Env):
|
||||||
# apply action/element penalty (refined parents only)
|
# apply action/element penalty (refined parents only)
|
||||||
element_penalty = np.zeros(len(reward_per_agent), dtype=reward_per_agent.dtype)
|
element_penalty = np.zeros(len(reward_per_agent), dtype=reward_per_agent.dtype)
|
||||||
element_penalty[unique_old] = self._element_penalty_lambda * (counts - 1)
|
element_penalty[unique_old] = self._element_penalty_lambda * (counts - 1)
|
||||||
|
|
||||||
|
# Step 0 penalty scaling: reduce element penalty on first refinement step
|
||||||
|
# to prevent "reward < 0 but aw_rel improved" feedback inversion.
|
||||||
|
step0_scale = float(self._environment_config.get("step0_penalty_scale", 1.0))
|
||||||
|
penalty_scale = step0_scale if self._timestep == 1 else 1.0
|
||||||
|
element_penalty = element_penalty * penalty_scale
|
||||||
|
|
||||||
element_limit_penalty = (
|
element_limit_penalty = (
|
||||||
(self._element_limit_penalty / self._previous_num_elements)
|
(self._element_limit_penalty / self._previous_num_elements)
|
||||||
if self.reached_element_limits
|
if self.reached_element_limits
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Step 0 diagnostics (r_local = reward before penalty, already clipped)
|
||||||
|
r_local_pre_penalty = reward_per_agent.copy()
|
||||||
|
_step0_penalty_scale = penalty_scale
|
||||||
|
_step0_avg_penalty = float(np.mean(element_penalty[refined_mask])) if refined_mask.any() else 0.0
|
||||||
|
_step0_avg_r_local = float(np.mean(r_local_pre_penalty[refined_mask])) if refined_mask.any() else 0.0
|
||||||
|
_step0_step_id = self._timestep
|
||||||
|
|
||||||
reward_per_agent = (
|
reward_per_agent = (
|
||||||
reward_per_agent - element_penalty - element_limit_penalty
|
reward_per_agent - element_penalty - element_limit_penalty
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Global error change (diagnostic only, NOT injected into Actor reward) ──
|
|
||||||
# Removing global_bonus from per-element reward eliminates the broken causal
|
|
||||||
# chain: Helmholtz pollution error can make E_new > E_old even when the
|
|
||||||
# selected elements were the right choice, punishing agents for physics
|
|
||||||
# they didn't cause. Actor optimises r_local only; Critic captures global
|
|
||||||
# effects through value estimation.
|
|
||||||
l2_old = self._previous_solution_l2_norm
|
|
||||||
l2_new = self._compute_solution_l2_norm()
|
|
||||||
eta_l2_old = float(np.sqrt(np.sum(old_eta ** 2)))
|
|
||||||
eta_l2_new = float(np.sqrt(np.sum(new_eta ** 2)))
|
|
||||||
eps_l2 = 1e-12
|
|
||||||
E_old = eta_l2_old / max(l2_old, eps_l2)
|
|
||||||
E_new = eta_l2_new / max(l2_new, eps_l2)
|
|
||||||
global_bonus = self._global_reward_alpha * float(np.log(E_old + eps_l2) - np.log(E_new + eps_l2))
|
|
||||||
# global_bonus intentionally NOT added to reward_per_agent — see above.
|
|
||||||
|
|
||||||
self._reward_per_agent = reward_per_agent
|
self._reward_per_agent = reward_per_agent
|
||||||
self._cumulative_reward_per_agent = (
|
self._cumulative_reward_per_agent = (
|
||||||
self._cumulative_reward_per_agent[self._previous_agent_mapping]
|
self._cumulative_reward_per_agent[self._previous_agent_mapping]
|
||||||
|
|
@ -991,11 +1205,15 @@ class MeshRefinement(gym.Env):
|
||||||
reward = reward_per_agent
|
reward = reward_per_agent
|
||||||
|
|
||||||
reward_dict["reward"] = reward
|
reward_dict["reward"] = reward
|
||||||
reward_dict["potential_bonus"] = global_bonus
|
|
||||||
reward_dict["penalty"] = -reward
|
reward_dict["penalty"] = -reward
|
||||||
reward_dict["element_limit_penalty"] = element_limit_penalty
|
reward_dict["element_limit_penalty"] = element_limit_penalty
|
||||||
reward_dict["element_penalty"] = element_penalty
|
reward_dict["element_penalty"] = element_penalty
|
||||||
reward_dict["element_penalty_lambda"] = self._element_penalty_lambda
|
reward_dict["element_penalty_lambda"] = self._element_penalty_lambda
|
||||||
|
# Step 0 penalty scaling diagnostics
|
||||||
|
reward_dict["penalty_scale"] = _step0_penalty_scale
|
||||||
|
reward_dict["avg_penalty"] = _step0_avg_penalty
|
||||||
|
reward_dict["avg_r_local"] = _step0_avg_r_local
|
||||||
|
reward_dict["step_id"] = _step0_step_id
|
||||||
return reward, reward_dict
|
return reward, reward_dict
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,469 @@
|
||||||
|
# Outlook: GNN-Guided Adaptive Mesh Refinement for 2D Helmholtz Scattering
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
|
||||||
|
求解 2D 介质圆柱的电磁散射(散射场公式):
|
||||||
|
|
||||||
|
```
|
||||||
|
∇²u + k²·ε_r·u = −k²·(ε_r − 1)·u_inc
|
||||||
|
∂u/∂n − i·k_local·u = 0 (Sommerfeld 辐射 BC)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 入射波:`u_inc = exp(i·k·x)`,参考解:Mie 解析解
|
||||||
|
- 参数空间:k∈[3,15],eps_r∈[2,8],cx/cy∈[0.2,0.8],radius∈[0.05,0.25]
|
||||||
|
- 核心目标:在参数化 Helmholtz 散射问题中,学习一个无在线求解的预算约束网格预测器,用于近似 residual-AMR 的最终加密分布,并在低预算下优于简单物理启发式网格。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 算法全流程
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1. 数据生成 (gen.py)
|
||||||
|
残差驱动 AMR → 每步保存 cell 状态 + 标签
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Step 2. 训练 (train_correction.py)
|
||||||
|
features(15-dim) + physics_score → GNN → sigmoid → 二分类 teacher_mark
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Step 3. 评估
|
||||||
|
3a. 离线指标 (test_correction.py) — top-k overlap / AUC
|
||||||
|
3b. Rollout 评估 (eval_correction.py) — 迭代加密 → FEM → aw_rel
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Step 4. 可视化 (viz_correction.py)
|
||||||
|
amr 模式: GNN 驱动完整 AMR → 网格 + 场 + 误差
|
||||||
|
step 模式: 单步 GNN vs teacher vs physics 标记对比
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 数据生成 (`gen.py`)
|
||||||
|
|
||||||
|
### 3.1 初始网格
|
||||||
|
|
||||||
|
物理自适应初始网格(`build_physics_safe_initial_mesh`):
|
||||||
|
|
||||||
|
- 均匀基底网格 + 迭代局部加密
|
||||||
|
- 介质外:h ≤ λ₀/q,介质内:h ≤ λ_eff/q(q=2,每波长 2 个单元)
|
||||||
|
- 加密准则:`score = max_edge / h_target > 1` 的单元被加密
|
||||||
|
- 二分搜索控制每批加密数量,不超预算
|
||||||
|
|
||||||
|
### 3.2 AMR 循环(每步保存)
|
||||||
|
|
||||||
|
```
|
||||||
|
初始 mesh → FEM solve → 残差估计器 η → teacher_mark (η top-k)
|
||||||
|
├── physics_score → physics_mark (top-k)
|
||||||
|
├── correction_label = teacher_mark − physics_mark
|
||||||
|
└── 保存 .npz → 按 η 选单元加密 → 下一步
|
||||||
|
```
|
||||||
|
|
||||||
|
**残差估计器 η**(`environment/helmholtz.py:_compute_residual_indicator`):
|
||||||
|
- 内部残差:`h_K/k · √V_K · |k²ε_r·u_h + k²(ε_r−1)·u_inc|`
|
||||||
|
- 梯度跳变:`√(½ Σ h_e/k · |[[∇u_h·n]]|²)`
|
||||||
|
- SBC 边界残差:`h_bnd/k · |∂u/∂n − i·k_local·u|`
|
||||||
|
|
||||||
|
**标记策略**:
|
||||||
|
- teacher_mark:按 η 取 top `mark_fraction`(默认 3%)
|
||||||
|
- physics_mark:按 `physics_score` 取 top `mark_fraction`
|
||||||
|
- correction_label = +1(teacher 独有)、0(一致)、−1(physics 独有)
|
||||||
|
|
||||||
|
**安全过滤**:
|
||||||
|
1. 面积过滤:排除面积 ≤ 1e-10 的退化单元
|
||||||
|
2. 反向 Dörfler:排除误差贡献最低 1% 的单元
|
||||||
|
|
||||||
|
### 3.3 输出格式
|
||||||
|
|
||||||
|
```
|
||||||
|
outlook/data_correction/
|
||||||
|
├── params_list.npz # (n_samples, 5) PDE 参数
|
||||||
|
├── sample0000_step000.npz # 逐样本逐步数据
|
||||||
|
└── summary.json
|
||||||
|
```
|
||||||
|
|
||||||
|
每个 step .npz:
|
||||||
|
|
||||||
|
| 字段 | 形状 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `features` | `(n_elem, 15)` | 几何/物理特征 |
|
||||||
|
| `edge_index` | `(2, n_edges)` | 网格图结构 |
|
||||||
|
| `physics_score` | `(n_elem,)` | h/λ_eff |
|
||||||
|
| `teacher_eta` | `(n_elem,)` | 残差估计器 |
|
||||||
|
| `teacher_mark` | `(n_elem,)` | η top-k 标记 (0/1) |
|
||||||
|
| `physics_mark` | `(n_elem,)` | physics top-k 标记 (0/1) |
|
||||||
|
| `correction_label` | `(n_elem,)` | teacher − physics (−1/0/+1) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 特征工程
|
||||||
|
|
||||||
|
### 4.1 15 维基础特征
|
||||||
|
|
||||||
|
| 维 | 特征 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| 0,1 | x, y | 单元中点坐标 |
|
||||||
|
| 2 | area | 单元面积 |
|
||||||
|
| 3 | dist_to_center | 到圆柱中心距离 |
|
||||||
|
| 4 | signed_dist | dist − radius(负=介质内) |
|
||||||
|
| 5 | inside | 是否在圆柱内 (0/1) |
|
||||||
|
| 6 | k | 波数 |
|
||||||
|
| 7 | eps_r | 介电常数 |
|
||||||
|
| 8 | radius | 半径 |
|
||||||
|
| 9,10 | cx, cy | 圆柱中心 |
|
||||||
|
| 11 | k_h | k × √area |
|
||||||
|
| 12 | k_eps_h | k × √eps_r × √area |
|
||||||
|
| 13,14 | sin(k·x), cos(k·x) | 入射波相位 |
|
||||||
|
|
||||||
|
### 4.2 Physics Score(第 16 维输入)
|
||||||
|
|
||||||
|
```python
|
||||||
|
lambda_eff = 2π / (k · √eps_r) # 介质内
|
||||||
|
或 2π / k # 介质外
|
||||||
|
physics_score = max_edge / lambda_eff # >1 = 分辨率不足
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 归一化
|
||||||
|
|
||||||
|
训练集 z-score 归一化,推理复用同一统计量:
|
||||||
|
```python
|
||||||
|
x_norm = (concat(features, physics_score) - mean) / scale
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. GNN 架构
|
||||||
|
|
||||||
|
### 5.1 CorrectionGNN
|
||||||
|
|
||||||
|
基于 DensityGNN 骨干,替换密度回归头为二分类 logit 头:
|
||||||
|
|
||||||
|
```
|
||||||
|
Input: (n_cells, 16) node features
|
||||||
|
│
|
||||||
|
├── node_embedding: Linear(16 → latent_dim)
|
||||||
|
├── edge_embedding: Linear(16 → latent_dim)
|
||||||
|
│
|
||||||
|
├── N × MessagePassingStep:
|
||||||
|
│ ├── EdgeModule: MLP([src | dst | edge_attr]) → latent_dim
|
||||||
|
│ ├── NodeModule: MLP([node | mean(incoming_edges)]) → latent_dim
|
||||||
|
│ └── LayerNorm + Residual
|
||||||
|
│
|
||||||
|
├── GlobalVirtualNode: mean_pool → attention_gate → broadcast
|
||||||
|
│
|
||||||
|
└── head: Linear(latent → hidden) → ReLU → Linear(hidden → 1 logit)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 关键设计
|
||||||
|
|
||||||
|
- 边特征:`edge_attr = |x[src] - x[dst]|`(节点特征差绝对值)
|
||||||
|
- 边丢弃:训练 0.1,推理 0.0
|
||||||
|
- 损失:BCEWithLogitsLoss + per-graph `pos_weight = neg/pos`
|
||||||
|
|
||||||
|
### 5.3 训练配置
|
||||||
|
|
||||||
|
| 参数 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| latent_dim | 64 |
|
||||||
|
| num_mp_steps | 3 |
|
||||||
|
| head_hidden | 64 |
|
||||||
|
| lr | 1e-3 |
|
||||||
|
| optimizer | Adam |
|
||||||
|
| scheduler | ReduceLROnPlateau (patience=10) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 评估体系
|
||||||
|
|
||||||
|
### 6.1 离线指标(test_correction.py)
|
||||||
|
|
||||||
|
- **top-k overlap**:GNN 概率最高的 k 个 cell 与 teacher_mark 的交集 / k
|
||||||
|
- **AUC**:ROC-AUC(GNN vs physics baseline)
|
||||||
|
- **gnn_beats_physics_ratio**:GNN 优于 physics 的样本比例
|
||||||
|
|
||||||
|
### 6.2 Rollout 评估(eval_correction.py)
|
||||||
|
|
||||||
|
从初始 mesh 出发,**无 teacher_eta,无残差标记,无中间 FEM solve**。每步只用分数决定加密单元,最终一次 FEM solve 计算误差。
|
||||||
|
|
||||||
|
三种方法:
|
||||||
|
|
||||||
|
| 方法 | 打分 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `physics` | `physics_score` | 纯物理先验 |
|
||||||
|
| `neural` | `model(features + physics_score)` | 纯 GNN |
|
||||||
|
| `hybrid` | `α·zscore(physics) + β·zscore(neural)` | 混合(默认 α=β=0.5) |
|
||||||
|
|
||||||
|
### 6.3 误差指标
|
||||||
|
|
||||||
|
**aw_rel**(面积加权相对误差):
|
||||||
|
```
|
||||||
|
aw_rel = √( Σ err²_tri · area / Σ ref²_tri · area )
|
||||||
|
```
|
||||||
|
|
||||||
|
**max_err**(最大逐点误差):
|
||||||
|
```
|
||||||
|
max_err = max |Re(u_fem) − Re(u_mie)|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 可视化
|
||||||
|
|
||||||
|
### 7.1 amr 模式
|
||||||
|
|
||||||
|
GNN 驱动完整 AMR,每步 FEM solve,展示网格和场演变。
|
||||||
|
|
||||||
|
输出:
|
||||||
|
- `amr_overview.png` — 所有步骤总览
|
||||||
|
- `amr_steps/step{XX}.png` — 每步 3 面板(FEM 场 / Mie 参考 / 误差)
|
||||||
|
- `ground_truth.png` — 高保真参考解(`--compare` 时)
|
||||||
|
- `compare.png` — Physics vs GNN vs Eta 对比(`--compare` 时)
|
||||||
|
|
||||||
|
### 7.2 step 模式
|
||||||
|
|
||||||
|
重建指定 AMR 步的 mesh,对比 GNN / teacher / physics 标记。
|
||||||
|
|
||||||
|
输出:
|
||||||
|
- `marks_*.png` — 2×2 对比图(teacher / GNN / physics / TP/FP/FN/TN)
|
||||||
|
- `field_gnn_*.png` — GNN 加密后 3 面板图
|
||||||
|
- `field_eta_*.png` — 传统 η 加密后 3 面板图
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 使用方法
|
||||||
|
|
||||||
|
### 8.1 数据生成
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python outlook/src/gen.py \
|
||||||
|
--n-samples 100 \
|
||||||
|
--max-elements 12000 \
|
||||||
|
--mark-fraction 0.03 \
|
||||||
|
--output-dir outlook/data_correction
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 训练
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python outlook/src/train_correction.py \
|
||||||
|
--data-dir outlook/data_correction \
|
||||||
|
--epochs 100 \
|
||||||
|
--batch-size 32 \
|
||||||
|
--lr 1e-3 \
|
||||||
|
--device cuda \
|
||||||
|
--checkpoint-out outlook/ckpt/correction.pt
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:
|
||||||
|
- `correction.pt` — 最终模型
|
||||||
|
- `correction_best.pt` — val_loss 最低 checkpoint
|
||||||
|
- `correction_train_log.json` — 逐 epoch 日志
|
||||||
|
|
||||||
|
### 8.3 离线指标评估
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python outlook/src/test_correction.py \
|
||||||
|
--checkpoint outlook/ckpt/correction.pt \
|
||||||
|
--data-dir outlook/data_correction \
|
||||||
|
--device cuda \
|
||||||
|
--visualize \
|
||||||
|
--output-dir outlook/result/correction/test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Rollout 评估
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python outlook/src/eval_correction.py \
|
||||||
|
--checkpoint outlook/ckpt/correction.pt \
|
||||||
|
--data-dir outlook/data_correction \
|
||||||
|
--target-elements 2000,4000,8000,12000 \
|
||||||
|
--mark-fraction 0.03 \
|
||||||
|
--max-steps 40 \
|
||||||
|
--methods physics,neural,hybrid \
|
||||||
|
--alpha 0.5 --beta 0.5 \
|
||||||
|
--device cuda \
|
||||||
|
--output-dir outlook/result/correction/rollout
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:
|
||||||
|
- `eval_results.json` — 详细结果 + 聚合统计 + 改善比例
|
||||||
|
- `summary.csv` — 每行一个 (method, target) 汇总
|
||||||
|
- `aw_rel_vs_elements.png` / `max_err_vs_elements.png`
|
||||||
|
|
||||||
|
### 8.5 可视化
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 端到端 AMR
|
||||||
|
python outlook/src/viz_correction.py \
|
||||||
|
--checkpoint outlook/ckpt/correction.pt \
|
||||||
|
--data-dir outlook/data_correction \
|
||||||
|
--mode amr --sample-id 0 --max-elements 5000 \
|
||||||
|
--device cuda \
|
||||||
|
--output-dir outlook/result/correction/viz
|
||||||
|
|
||||||
|
# 单步对比
|
||||||
|
python outlook/src/viz_correction.py \
|
||||||
|
--checkpoint outlook/ckpt/correction.pt \
|
||||||
|
--data-dir outlook/data_correction \
|
||||||
|
--mode step --sample-id 0 --step 0 \
|
||||||
|
--device cuda \
|
||||||
|
--output-dir outlook/result/correction/viz
|
||||||
|
|
||||||
|
# OOD 评估(自定义物理参数,需同时指定 k, eps-r, cx, cy, radius)
|
||||||
|
python outlook/src/viz_correction.py \
|
||||||
|
--checkpoint outlook/ckpt/correction.pt \
|
||||||
|
--data-dir outlook/data_correction \
|
||||||
|
--mode amr \
|
||||||
|
--k 30 --eps-r 4 --cx 0.5 --cy 0.5 --radius 0.15 \
|
||||||
|
--max-elements 6000 --compare \
|
||||||
|
--device cuda \
|
||||||
|
--output-dir outlook/result/correction/viz
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
outlook/
|
||||||
|
├── README.md # 本文档
|
||||||
|
├── train.sh # SLURM 训练脚本(可配置 batch_size,默认 1)
|
||||||
|
├── analyze_budget_teacher.py # Budget teacher 数据集分析
|
||||||
|
├── check_correction_data.py # 数据质量校验
|
||||||
|
│
|
||||||
|
├── src/
|
||||||
|
│ ├── gen.py # 数据生成
|
||||||
|
│ ├── train_correction.py # 训练
|
||||||
|
│ ├── test_correction.py # 离线指标评估
|
||||||
|
│ ├── eval_correction.py # Rollout 评估
|
||||||
|
│ ├── viz_correction.py # 可视化
|
||||||
|
│ ├── rollout.py # 统一 AMR rollout(核心循环 + 共享 refinement 工具)
|
||||||
|
│ ├── gnn.py # DensityGNN 模型
|
||||||
|
│ ├── feat.py # 特征提取
|
||||||
|
│ ├── graph.py # mesh → PyG graph(含 build_edge_index_np)
|
||||||
|
│ ├── mesh.py # score → refined mesh
|
||||||
|
│ ├── metrics.py # aw_rel / max_err
|
||||||
|
│ ├── problem.py # PDE 参数 → HelmholtzProblem
|
||||||
|
│ └── amr.py # 残差 AMR teacher
|
||||||
|
│
|
||||||
|
├── ckpt/ # checkpoint
|
||||||
|
├── data_correction/ # 训练数据
|
||||||
|
└── result/ # 评估结果
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 辅助模块
|
||||||
|
|
||||||
|
| 模块 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `rollout.py` | 统一 AMR rollout:`run_rollout_to_budget()` 驱动完整加密循环,支持 physics/neural/hybrid/eta 四种打分方法 |
|
||||||
|
| `feat.py` | 构建 15 维基础特征 + budget_code |
|
||||||
|
| `graph.py` | mesh → PyG Data(边特征 = phase_distance) |
|
||||||
|
| `mesh.py` | score → 迭代 top-k 加密(叶子继承初始单元 score) |
|
||||||
|
| `problem.py` | 参数字典 → HelmholtzProblem 实例 |
|
||||||
|
| `amr.py` | 纯残差驱动 AMR teacher(无网络) |
|
||||||
|
| `metrics.py` | `compute_mie_error`(aw_rel + max_err) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 测试结果
|
||||||
|
|
||||||
|
### 11.1 训练配置
|
||||||
|
|
||||||
|
- 数据集:100 样本,~19 步/样本,1888 个 step 文件
|
||||||
|
- 训练/验证划分:80/20(按 sample_id,seed=42)
|
||||||
|
- 训练图:1513 个,验证图:375 个
|
||||||
|
- 正样本比例:3.0%(teacher_mark top-3%)
|
||||||
|
- 训练 100 epoch,最佳 epoch 57(val_loss=0.5873)
|
||||||
|
|
||||||
|
### 11.2 离线指标(test_correction.py)
|
||||||
|
|
||||||
|
在 20 个验证样本的 375 个 step 图上评估:
|
||||||
|
|
||||||
|
| 指标 | GNN | Physics Baseline |
|
||||||
|
|------|-----|-----------------|
|
||||||
|
| **AUC** | **0.9412** | 0.0000 |
|
||||||
|
| **top-k overlap 均值** | **0.440** | 0.131 |
|
||||||
|
| GNN beats physics | **372/375 (99.2%)** | — |
|
||||||
|
|
||||||
|
> Physics AUC=0.0 是因为 physics_score 在均匀网格上只有 2 个离散值,无法区分排序。GNN 通过学习 η 的空间分布模式,top-k overlap 提升 **3.4 倍**。
|
||||||
|
|
||||||
|
### 11.3 Rollout 评估(eval_correction.py)
|
||||||
|
|
||||||
|
20 个验证样本 × 3 种方法 × 3 个目标预算,每次 rollout 最终做 1 次 FEM solve:
|
||||||
|
|
||||||
|
**aw_rel(面积加权相对误差)**:
|
||||||
|
|
||||||
|
| 方法 | target=2000 | target=4000 | target=8000 |
|
||||||
|
|------|------------|------------|------------|
|
||||||
|
| physics | 19.19% | 14.04% | 13.25% |
|
||||||
|
| neural | 15.96% | 13.72% | 12.98% |
|
||||||
|
| **hybrid** | **15.78%** | **13.57%** | **12.94%** |
|
||||||
|
|
||||||
|
**max_err(最大逐点误差)**:
|
||||||
|
|
||||||
|
| 方法 | target=2000 | target=4000 | target=8000 |
|
||||||
|
|------|------------|------------|------------|
|
||||||
|
| physics | 0.3301 | 0.2514 | 0.2418 |
|
||||||
|
| neural | 0.2741 | 0.2431 | 0.2376 |
|
||||||
|
| **hybrid** | **0.2686** | **0.2430** | **0.2383** |
|
||||||
|
|
||||||
|
**相对 physics-only 的改善**:
|
||||||
|
|
||||||
|
| 方法 | target | aw_rel 改善 | max_err 改善 |
|
||||||
|
|------|--------|-----------|-------------|
|
||||||
|
| hybrid | 2000 | **+9.8%** | **+6.9%** |
|
||||||
|
| neural | 2000 | +9.9% | +6.2% |
|
||||||
|
| hybrid | 4000 | +1.9% | +1.5% |
|
||||||
|
| neural | 4000 | +1.1% | +1.4% |
|
||||||
|
| hybrid | 8000 | +1.9% | +1.3% |
|
||||||
|
| neural | 8000 | +1.8% | +1.5% |
|
||||||
|
|
||||||
|
### 11.4 关键发现
|
||||||
|
|
||||||
|
1. **GNN 显著优于 physics baseline**:离线 top-k overlap 从 0.131 提升到 0.440(3.4×),99.2% 的验证图上 GNN 胜出
|
||||||
|
2. **低预算改善最大**:target=2000 时 aw_rel 改善 ~10%,max_err 改善 ~7%;高预算时改善收窄到 ~2%(因为预算充足时 physics 也够用)
|
||||||
|
3. **hybrid 略优于 neural**:z-score 混合策略在多数场景下比纯 GNN 更稳定
|
||||||
|
4. **GNN 推理效率**:neural 方法比 physics 方法少用 ~12% 的 refinement 步数达到相同预算(8.9 vs 9.4 步 @2000),因为 GNN 的标记更精准
|
||||||
|
|
||||||
|
### 11.5 OOD 评估(k 超出训练范围 [3,15])
|
||||||
|
|
||||||
|
固定 `eps_r=4, cx=cy=0.5, radius=0.15`,在 k=20/30/50/80 上做三方法对比(target=5000/10000/20000):
|
||||||
|
|
||||||
|
**aw_rel (%)**:
|
||||||
|
|
||||||
|
| k | 方法 | target=5000 | target=10000 | target=20000 |
|
||||||
|
|---|------|------------|-------------|-------------|
|
||||||
|
| 20 | physics | 32.17 | 24.55 | 10.61 |
|
||||||
|
| 20 | neural | 32.56 | **17.85** | 10.68 |
|
||||||
|
| 20 | hybrid | **31.73** | 19.57 | 14.03 |
|
||||||
|
| 30 | physics | 33.94 | 15.15 | 10.14 |
|
||||||
|
| 30 | neural | **23.52** | 13.40 | 8.38 |
|
||||||
|
| 30 | hybrid | 27.94 | **13.34** | **7.96** |
|
||||||
|
| 50 | physics | 94.00 | 73.56 | 33.00 |
|
||||||
|
| 50 | neural | **88.16** | **52.92** | **27.99** |
|
||||||
|
| 50 | hybrid | 90.97 | 68.47 | 29.27 |
|
||||||
|
| 80 | physics | 122.06 | 135.78 | 101.64 |
|
||||||
|
| 80 | neural | 139.84 | **127.65** | **92.67** |
|
||||||
|
| 80 | hybrid | 139.84 | 127.68 | 105.94 |
|
||||||
|
|
||||||
|
**相对 physics-only 的改善 (%)**:
|
||||||
|
|
||||||
|
| k | 方法 | target=5000 | target=10000 | target=20000 |
|
||||||
|
|---|------|------------|-------------|-------------|
|
||||||
|
| 20 | neural | −1.2 | **+27.3** | −0.7 |
|
||||||
|
| 20 | hybrid | +1.4 | +20.3 | −32.3 |
|
||||||
|
| 30 | neural | **+30.7** | +11.6 | +17.4 |
|
||||||
|
| 30 | hybrid | +17.7 | +12.0 | +21.5 |
|
||||||
|
| 50 | neural | +6.2 | **+28.1** | **+15.2** |
|
||||||
|
| 50 | hybrid | +3.2 | +6.9 | +11.3 |
|
||||||
|
| 80 | neural | −14.6 | +6.0 | **+8.8** |
|
||||||
|
| 80 | hybrid | −14.6 | +6.0 | −4.2 |
|
||||||
|
|
||||||
|
**OOD 关键发现**:
|
||||||
|
|
||||||
|
1. **neural 在远 OOD(k=50)优势最大**:target=10000 时 aw_rel 从 73.56% 降至 52.92%(改善 +28.1%),hybrid 仅改善 +6.9%
|
||||||
|
2. **hybrid 的 physics 先验在 OOD 时成为拖累**:z-score 混合中 physics_score 的 `max_edge/h_target` 在高 k 下不再准确,导致 hybrid 的标记不如纯 neural
|
||||||
|
3. **neural 的泛化来自几何特征**:GNN 学到的是"界面附近 + 介质内 → 高密度"的空间模式,这一模式在 k 超出训练范围时仍然成立;而 physics_score 依赖 `lambda_eff = 2π/(k√ε_r)` 的具体数值
|
||||||
|
4. **极端 OOD(k=80)两者都差**:误差超过 100%,说明 20000 个单元完全不够分辨 k=80 的短波结构(λ_eff≈0.028,需要 ~70000+ 单元)
|
||||||
|
5. **k=20/30 时 hybrid 反而更好**:接近训练分布时 physics 先验有价值,hybrid 通过混合两种信号获得更稳定的标记
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,203 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Analyze budgeted teacher dataset: per-budget stats, error improvement, saturation.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python outlook/analyze_budget_teacher.py
|
||||||
|
python outlook/analyze_budget_teacher.py --data-dir outlook/data/budget_density_dataset_check
|
||||||
|
python outlook/analyze_budget_teacher.py --sat-threshold 0.05
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def load_dataset(data_dir: Path):
|
||||||
|
"""Load all sample .npz files, return list of dicts."""
|
||||||
|
data_dir = Path(data_dir)
|
||||||
|
params_path = data_dir / "params_list.npz"
|
||||||
|
if params_path.exists():
|
||||||
|
n_samples = len(np.load(params_path)["params"])
|
||||||
|
else:
|
||||||
|
# Fall back to scanning for numbered .npz files
|
||||||
|
n_samples = len(sorted(data_dir.glob("[0-9]*.npz")))
|
||||||
|
|
||||||
|
samples = []
|
||||||
|
for sid in range(n_samples):
|
||||||
|
npz_path = data_dir / f"{sid:04d}.npz"
|
||||||
|
if not npz_path.exists():
|
||||||
|
continue
|
||||||
|
d = np.load(npz_path)
|
||||||
|
samples.append({
|
||||||
|
"sid": sid,
|
||||||
|
"budgets": d["budgets"],
|
||||||
|
"actual_elements": d["actual_elements"],
|
||||||
|
"teacher_aw_rel": d["teacher_aw_rel"],
|
||||||
|
"teacher_max_err": d["teacher_max_err"],
|
||||||
|
"valid_budgets": d["valid_budgets"] if "valid_budgets" in d else np.ones(len(d["budgets"]), dtype=bool),
|
||||||
|
"match_ratios": d["match_ratios"] if "match_ratios" in d else d["actual_elements"] / d["budgets"],
|
||||||
|
"k": float(d["k"]),
|
||||||
|
"eps_r": float(d["eps_r"]),
|
||||||
|
})
|
||||||
|
return samples
|
||||||
|
|
||||||
|
|
||||||
|
def per_budget_summary(samples):
|
||||||
|
"""Aggregate stats grouped by budget value."""
|
||||||
|
groups = {} # budget -> list of sample dicts
|
||||||
|
for s in samples:
|
||||||
|
for bi, b in enumerate(s["budgets"]):
|
||||||
|
b = int(b)
|
||||||
|
groups.setdefault(b, {"aw_rel": [], "max_err": [], "actual": [], "valid": []})
|
||||||
|
groups[b]["aw_rel"].append(s["teacher_aw_rel"][bi])
|
||||||
|
groups[b]["max_err"].append(s["teacher_max_err"][bi])
|
||||||
|
groups[b]["actual"].append(s["actual_elements"][bi])
|
||||||
|
groups[b]["valid"].append(bool(s["valid_budgets"][bi]))
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for b in sorted(groups.keys()):
|
||||||
|
g = groups[b]
|
||||||
|
aw = np.array(g["aw_rel"])
|
||||||
|
me = np.array(g["max_err"])
|
||||||
|
rows.append({
|
||||||
|
"budget": b,
|
||||||
|
"n_samples": len(aw),
|
||||||
|
"n_valid": sum(g["valid"]),
|
||||||
|
"mean_actual": np.mean(g["actual"]),
|
||||||
|
"mean_aw_rel": np.mean(aw),
|
||||||
|
"std_aw_rel": np.std(aw),
|
||||||
|
"mean_max_err": np.mean(me),
|
||||||
|
})
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def compute_improvements(rows):
|
||||||
|
"""Compute Δaw_rel and relative improvement between adjacent budgets."""
|
||||||
|
for i in range(len(rows)):
|
||||||
|
rows[i]["delta_aw_rel"] = None
|
||||||
|
rows[i]["rel_improvement"] = None
|
||||||
|
for i in range(1, len(rows)):
|
||||||
|
prev = rows[i - 1]["mean_aw_rel"]
|
||||||
|
curr = rows[i]["mean_aw_rel"]
|
||||||
|
delta = prev - curr # positive = improvement
|
||||||
|
rows[i]["delta_aw_rel"] = delta
|
||||||
|
rows[i]["rel_improvement"] = delta / prev if prev > 0 else 0.0
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_saturation(samples, threshold=0.02):
|
||||||
|
"""For each sample, find the first budget where further improvement < threshold.
|
||||||
|
|
||||||
|
Saturation budget = first budget b such that
|
||||||
|
(aw_rel[b-1] - aw_rel[b]) / aw_rel[b-1] < threshold
|
||||||
|
i.e. relative improvement from previous budget is below threshold.
|
||||||
|
|
||||||
|
Returns list of (sid, saturation_budget, sat_reason).
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
for s in samples:
|
||||||
|
budgets = s["budgets"]
|
||||||
|
aw = s["teacher_aw_rel"]
|
||||||
|
sat_budget = None
|
||||||
|
for bi in range(1, len(budgets)):
|
||||||
|
if np.isnan(aw[bi - 1]) or np.isnan(aw[bi]):
|
||||||
|
continue
|
||||||
|
if aw[bi - 1] <= 0:
|
||||||
|
continue
|
||||||
|
rel_imp = (aw[bi - 1] - aw[bi]) / aw[bi - 1]
|
||||||
|
if rel_imp < threshold:
|
||||||
|
sat_budget = int(budgets[bi])
|
||||||
|
break
|
||||||
|
# If never saturated, mark as the largest budget
|
||||||
|
if sat_budget is None:
|
||||||
|
sat_budget = int(budgets[-1])
|
||||||
|
results.append({
|
||||||
|
"sid": s["sid"],
|
||||||
|
"saturation_budget": sat_budget,
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def print_table(rows):
|
||||||
|
"""Print the per-budget summary table."""
|
||||||
|
hdr = (f"{'budget':>8s} {'n':>4s} {'valid':>5s} "
|
||||||
|
f"{'mean_act':>9s} {'mean_aw':>9s} {'std_aw':>9s} {'mean_me':>9s} "
|
||||||
|
f"{'Δaw_rel':>9s} {'rel_imp':>9s}")
|
||||||
|
print(hdr)
|
||||||
|
print("-" * len(hdr))
|
||||||
|
for r in rows:
|
||||||
|
delta = f"{r['delta_aw_rel']:.6f}" if r["delta_aw_rel"] is not None else "—"
|
||||||
|
rel = f"{r['rel_improvement']:.4f}" if r["rel_improvement"] is not None else "—"
|
||||||
|
print(f"{r['budget']:8d} {r['n_samples']:4d} {r['n_valid']:5d} "
|
||||||
|
f"{r['mean_actual']:9.1f} {r['mean_aw_rel']:.6f} {r['std_aw_rel']:.6f} "
|
||||||
|
f"{r['mean_max_err']:.6f} {delta:>9s} {rel:>9s}")
|
||||||
|
|
||||||
|
|
||||||
|
def print_saturation(sat_results, threshold):
|
||||||
|
"""Print saturation budget distribution."""
|
||||||
|
budgets = [r["saturation_budget"] for r in sat_results]
|
||||||
|
unique = sorted(set(budgets))
|
||||||
|
print(f"\nSaturation budget distribution (threshold={threshold:.0%} rel. improvement):")
|
||||||
|
print(f" {'sat_budget':>12s} {'count':>6s} {'pct':>6s}")
|
||||||
|
for b in unique:
|
||||||
|
count = budgets.count(b)
|
||||||
|
pct = 100.0 * count / len(budgets)
|
||||||
|
print(f" {b:12d} {count:6d} {pct:5.1f}%")
|
||||||
|
print(f" {'median':>12s} {np.median(budgets):.0f}")
|
||||||
|
print(f" {'mean':>12s} {np.mean(budgets):.1f}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Analyze budgeted teacher dataset")
|
||||||
|
parser.add_argument("--data-dir", type=str, default="outlook/data",
|
||||||
|
help="Path to budget dataset directory (default: outlook/data)")
|
||||||
|
parser.add_argument("--sat-threshold", type=float, default=0.02,
|
||||||
|
help="Relative improvement threshold for saturation (default: 0.02)")
|
||||||
|
parser.add_argument("--sat-threshold-2", type=float, default=0.05,
|
||||||
|
help="Second saturation threshold to report (default: 0.05)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
data_dir = Path(args.data_dir)
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print("Budget Teacher Dataset Analysis")
|
||||||
|
print("=" * 70)
|
||||||
|
print(f" Data dir: {data_dir}")
|
||||||
|
|
||||||
|
samples = load_dataset(data_dir)
|
||||||
|
print(f" Samples: {len(samples)}")
|
||||||
|
|
||||||
|
budgets_all = samples[0]["budgets"]
|
||||||
|
print(f" Budgets: {[int(b) for b in budgets_all]}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 1. Per-budget summary
|
||||||
|
rows = per_budget_summary(samples)
|
||||||
|
rows = compute_improvements(rows)
|
||||||
|
|
||||||
|
print("Per-budget summary:")
|
||||||
|
print_table(rows)
|
||||||
|
|
||||||
|
# 2. Saturation analysis
|
||||||
|
for thresh, label in [(args.sat_threshold, "primary"),
|
||||||
|
(args.sat_threshold_2, "secondary")]:
|
||||||
|
sat = estimate_saturation(samples, threshold=thresh)
|
||||||
|
print_saturation(sat, thresh)
|
||||||
|
|
||||||
|
# 3. Per-sample detail (compact)
|
||||||
|
print(f"\nPer-sample detail:")
|
||||||
|
print(f" {'sid':>4s} {'k':>6s} {'eps_r':>6s} "
|
||||||
|
+ " ".join(f"b{int(b):>5d}" for b in budgets_all))
|
||||||
|
print(f" {'':>4s} {'':>6s} {'':>6s} "
|
||||||
|
+ " ".join(f"{'aw_rel':>5s}" for _ in budgets_all))
|
||||||
|
print(" " + "-" * (20 + 8 * len(budgets_all)))
|
||||||
|
for s in samples:
|
||||||
|
vals = " ".join(f"{v:.4f}" for v in s["teacher_aw_rel"])
|
||||||
|
print(f" {s['sid']:4d} {s['k']:6.2f} {s['eps_r']:6.2f} {vals}")
|
||||||
|
|
||||||
|
print("\nDone.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,457 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
check_correction_data.py — Validate step-wise correction dataset.
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
1. Global statistics (elements, marks, overlap, IoU, correction ratios)
|
||||||
|
2. Per-step trend analysis (correction signal vs AMR step)
|
||||||
|
3. Spatial visualization of 5 random samples
|
||||||
|
4. Field & shape consistency
|
||||||
|
5. Final verdict: PASS / WARNING / FAIL
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python outlook/check_correction_data.py \
|
||||||
|
--data-dir outlook/data_correction \
|
||||||
|
--output-dir outlook/data_correction_check
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Required fields and their expected properties
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
REQUIRED_FIELDS = {
|
||||||
|
"features": {"ndim": 2, "dtype_kind": "f"},
|
||||||
|
"edge_index": {"ndim": 2, "dtype_kind": "i"},
|
||||||
|
"physics_score": {"ndim": 1, "dtype_kind": "f"},
|
||||||
|
"teacher_eta": {"ndim": 1, "dtype_kind": "f"},
|
||||||
|
"teacher_mark": {"ndim": 1, "dtype_kind": "i"},
|
||||||
|
"physics_mark": {"ndim": 1, "dtype_kind": "i"},
|
||||||
|
"correction_label": {"ndim": 1, "dtype_kind": "i"},
|
||||||
|
"elements": {"ndim": 0},
|
||||||
|
"step": {"ndim": 0},
|
||||||
|
"aw_rel_before": {"ndim": 0},
|
||||||
|
"max_err_before": {"ndim": 0},
|
||||||
|
"k": {"ndim": 0},
|
||||||
|
"eps_r": {"ndim": 0},
|
||||||
|
"cx": {"ndim": 0},
|
||||||
|
"cy": {"ndim": 0},
|
||||||
|
"radius": {"ndim": 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Helpers
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def iou_binary(a: np.ndarray, b: np.ndarray) -> float:
|
||||||
|
"""IoU between two binary (0/1) arrays."""
|
||||||
|
a = a.astype(bool)
|
||||||
|
b = b.astype(bool)
|
||||||
|
inter = np.sum(a & b)
|
||||||
|
union = np.sum(a | b)
|
||||||
|
return float(inter) / float(union) if union > 0 else 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def load_all_samples(data_dir: Path):
|
||||||
|
"""Load all sampleXXXX_stepYYY.npz files, return list of dicts."""
|
||||||
|
files = sorted(data_dir.glob("sample*_step*.npz"))
|
||||||
|
samples = []
|
||||||
|
for f in files:
|
||||||
|
try:
|
||||||
|
d = dict(np.load(f, allow_pickle=True))
|
||||||
|
d["_path"] = str(f)
|
||||||
|
d["_name"] = f.name
|
||||||
|
samples.append(d)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" [WARN] Cannot load {f.name}: {e}")
|
||||||
|
return samples
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 1. Field & shape consistency
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def check_fields(samples, issues):
|
||||||
|
"""Check every file has the required fields with correct ndim."""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print(" [1/5] Field & Shape Consistency")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
n_files = len(samples)
|
||||||
|
n_issues_before = len(issues)
|
||||||
|
|
||||||
|
for s in samples:
|
||||||
|
name = s["_name"]
|
||||||
|
for field, spec in REQUIRED_FIELDS.items():
|
||||||
|
if field not in s:
|
||||||
|
issues.append(f"MISSING field '{field}' in {name}")
|
||||||
|
continue
|
||||||
|
arr = s[field]
|
||||||
|
if arr.ndim != spec["ndim"]:
|
||||||
|
issues.append(
|
||||||
|
f"BAD ndim for '{field}' in {name}: "
|
||||||
|
f"expected {spec['ndim']}, got {arr.ndim}"
|
||||||
|
)
|
||||||
|
if "dtype_kind" in spec and arr.dtype.kind != spec["dtype_kind"]:
|
||||||
|
issues.append(
|
||||||
|
f"BAD dtype for '{field}' in {name}: "
|
||||||
|
f"expected kind={spec['dtype_kind']}, got {arr.dtype}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cross-check: features rows == len(physics_score) == elements
|
||||||
|
if "features" in s and "physics_score" in s:
|
||||||
|
nf = s["features"].shape[0]
|
||||||
|
ns = len(s["physics_score"])
|
||||||
|
ne = int(s["elements"])
|
||||||
|
if not (nf == ns == ne):
|
||||||
|
issues.append(
|
||||||
|
f"SIZE MISMATCH in {name}: features={nf}, "
|
||||||
|
f"physics_score={ns}, elements={ne}"
|
||||||
|
)
|
||||||
|
|
||||||
|
n_new = len(issues) - n_issues_before
|
||||||
|
if n_new == 0:
|
||||||
|
print(f" All {n_files} files have consistent fields & shapes.")
|
||||||
|
else:
|
||||||
|
print(f" Found {n_new} field/shape issues.")
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 2. Global statistics
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def compute_global_stats(samples):
|
||||||
|
"""Compute and print global statistics across all files."""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print(" [2/5] Global Statistics")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
elems = []
|
||||||
|
teacher_ratios = []
|
||||||
|
physics_ratios = []
|
||||||
|
overlaps = []
|
||||||
|
overlap_teacher = []
|
||||||
|
overlap_physics = []
|
||||||
|
ious = []
|
||||||
|
pos_ratios = []
|
||||||
|
neg_ratios = []
|
||||||
|
|
||||||
|
for s in samples:
|
||||||
|
n = int(s["elements"])
|
||||||
|
tm = s["teacher_mark"]
|
||||||
|
pm = s["physics_mark"]
|
||||||
|
cl = s["correction_label"]
|
||||||
|
|
||||||
|
elems.append(n)
|
||||||
|
n_tm = int(np.sum(tm))
|
||||||
|
n_pm = int(np.sum(pm))
|
||||||
|
ov = int(np.sum((tm == 1) & (pm == 1)))
|
||||||
|
|
||||||
|
teacher_ratios.append(n_tm / max(n, 1))
|
||||||
|
physics_ratios.append(n_pm / max(n, 1))
|
||||||
|
overlaps.append(ov)
|
||||||
|
overlap_teacher.append(ov / max(n_tm, 1))
|
||||||
|
overlap_physics.append(ov / max(n_pm, 1))
|
||||||
|
ious.append(iou_binary(tm, pm))
|
||||||
|
pos_ratios.append(int(np.sum(cl == 1)) / max(n, 1))
|
||||||
|
neg_ratios.append(int(np.sum(cl == -1)) / max(n, 1))
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
"total_files": len(samples),
|
||||||
|
"elements": elems,
|
||||||
|
"teacher_mark_ratio": teacher_ratios,
|
||||||
|
"physics_mark_ratio": physics_ratios,
|
||||||
|
"overlap_count": overlaps,
|
||||||
|
"overlap/teacher": overlap_teacher,
|
||||||
|
"overlap/physics": overlap_physics,
|
||||||
|
"IoU": ious,
|
||||||
|
"correction_pos_ratio": pos_ratios,
|
||||||
|
"correction_neg_ratio": neg_ratios,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f" {'Metric':<30s} {'mean':>10s} {'min':>10s} {'max':>10s}")
|
||||||
|
print(f" {'-'*30} {'-'*10} {'-'*10} {'-'*10}")
|
||||||
|
for key in [
|
||||||
|
"elements", "teacher_mark_ratio", "physics_mark_ratio",
|
||||||
|
"overlap_count", "overlap/teacher", "overlap/physics",
|
||||||
|
"IoU", "correction_pos_ratio", "correction_neg_ratio",
|
||||||
|
]:
|
||||||
|
arr = np.array(stats[key])
|
||||||
|
print(f" {key:<30s} {arr.mean():10.4f} {arr.min():10.4f} {arr.max():10.4f}")
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 3. Per-step trend analysis
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def check_per_step_trends(samples, issues):
|
||||||
|
"""Group by step, print trend table, flag anomalies."""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print(" [3/5] Per-Step Trend Analysis")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
step_data = defaultdict(lambda: {
|
||||||
|
"elems": [], "tm_ratio": [], "pm_ratio": [],
|
||||||
|
"overlap": [], "iou": [], "pos_ratio": [], "neg_ratio": [],
|
||||||
|
})
|
||||||
|
|
||||||
|
for s in samples:
|
||||||
|
step = int(s["step"])
|
||||||
|
n = int(s["elements"])
|
||||||
|
tm = s["teacher_mark"]
|
||||||
|
pm = s["physics_mark"]
|
||||||
|
cl = s["correction_label"]
|
||||||
|
n_tm = int(np.sum(tm))
|
||||||
|
n_pm = int(np.sum(pm))
|
||||||
|
ov = int(np.sum((tm == 1) & (pm == 1)))
|
||||||
|
|
||||||
|
d = step_data[step]
|
||||||
|
d["elems"].append(n)
|
||||||
|
d["tm_ratio"].append(n_tm / max(n, 1))
|
||||||
|
d["pm_ratio"].append(n_pm / max(n, 1))
|
||||||
|
d["overlap"].append(ov)
|
||||||
|
d["iou"].append(iou_binary(tm, pm))
|
||||||
|
d["pos_ratio"].append(int(np.sum(cl == 1)) / max(n, 1))
|
||||||
|
d["neg_ratio"].append(int(np.sum(cl == -1)) / max(n, 1))
|
||||||
|
|
||||||
|
steps = sorted(step_data.keys())
|
||||||
|
header = f" {'step':>4s} {'n_files':>7s} {'mean_elem':>9s} {'tm_ratio':>9s} {'pm_ratio':>9s} {'IoU':>7s} {'pos%':>7s} {'neg%':>7s}"
|
||||||
|
print(header)
|
||||||
|
print(f" {'-'*4} {'-'*7} {'-'*9} {'-'*9} {'-'*9} {'-'*7} {'-'*7} {'-'*7}")
|
||||||
|
|
||||||
|
for st in steps:
|
||||||
|
d = step_data[st]
|
||||||
|
nf = len(d["elems"])
|
||||||
|
me = np.mean(d["elems"])
|
||||||
|
mt = np.mean(d["tm_ratio"])
|
||||||
|
mp = np.mean(d["pm_ratio"])
|
||||||
|
mi = np.mean(d["iou"])
|
||||||
|
mpos = np.mean(d["pos_ratio"])
|
||||||
|
mneg = np.mean(d["neg_ratio"])
|
||||||
|
print(f" {st:4d} {nf:7d} {me:9.1f} {mt:9.4f} {mp:9.4f} {mi:7.4f} {mpos:7.4f} {mneg:7.4f}")
|
||||||
|
|
||||||
|
# Trend checks
|
||||||
|
if len(steps) >= 3:
|
||||||
|
# Check: correction signal should diminish at late steps (elements grow → marks shrink)
|
||||||
|
late = steps[-1]
|
||||||
|
early = steps[0]
|
||||||
|
late_pos = np.mean(step_data[late]["pos_ratio"])
|
||||||
|
early_pos = np.mean(step_data[early]["pos_ratio"])
|
||||||
|
late_neg = np.mean(step_data[late]["neg_ratio"])
|
||||||
|
early_neg = np.mean(step_data[early]["neg_ratio"])
|
||||||
|
late_iou = np.mean(step_data[late]["iou"])
|
||||||
|
early_iou = np.mean(step_data[early]["iou"])
|
||||||
|
|
||||||
|
print(f"\n Trend summary (step {early} → {late}):")
|
||||||
|
print(f" pos_ratio: {early_pos:.4f} → {late_pos:.4f}")
|
||||||
|
print(f" neg_ratio: {early_neg:.4f} → {late_neg:.4f}")
|
||||||
|
print(f" IoU: {early_iou:.4f} → {late_iou:.4f}")
|
||||||
|
|
||||||
|
# Warning: if late-step IoU is much lower than early, marks diverge
|
||||||
|
if late_iou < early_iou * 0.5:
|
||||||
|
issues.append(
|
||||||
|
f"TREND: IoU drops significantly from step {early} ({early_iou:.3f}) "
|
||||||
|
f"to step {late} ({late_iou:.3f}) — marks diverge at late steps."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warning: if correction signal vanishes (pos+neg → 0) at late steps
|
||||||
|
late_corr = late_pos + late_neg
|
||||||
|
early_corr = early_pos + early_neg
|
||||||
|
if early_corr > 0.01 and late_corr < early_corr * 0.1:
|
||||||
|
issues.append(
|
||||||
|
f"TREND: Correction signal nearly vanishes at step {late} "
|
||||||
|
f"({late_corr:.4f}) vs step {early} ({early_corr:.4f})."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warning: if IoU is very low overall (teacher and physics barely agree)
|
||||||
|
all_ious = [iou for s in steps for iou in step_data[s]["iou"]]
|
||||||
|
overall_iou = np.mean(all_ious)
|
||||||
|
if overall_iou < 0.1:
|
||||||
|
issues.append(
|
||||||
|
f"LOW IoU: mean IoU across all steps is {overall_iou:.4f} — "
|
||||||
|
f"teacher_mark and physics_mark barely overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 4. Spatial visualization
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def plot_spatial_samples(samples, output_dir: Path, n_plot: int = 5):
|
||||||
|
"""Randomly select n_plot files and save 4-panel spatial maps."""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print(" [4/5] Spatial Visualization")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
rng = np.random.RandomState(1234)
|
||||||
|
indices = rng.choice(len(samples), size=min(n_plot, len(samples)), replace=False)
|
||||||
|
selected = [samples[i] for i in sorted(indices)]
|
||||||
|
|
||||||
|
for s in selected:
|
||||||
|
name = s["_name"].replace(".npz", "")
|
||||||
|
features = s["features"]
|
||||||
|
x = features[:, 0]
|
||||||
|
y = features[:, 1]
|
||||||
|
|
||||||
|
physics_score = s["physics_score"]
|
||||||
|
teacher_eta = s["teacher_eta"]
|
||||||
|
teacher_mark = s["teacher_mark"]
|
||||||
|
correction_label = s["correction_label"]
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 4, figsize=(20, 5))
|
||||||
|
fig.suptitle(
|
||||||
|
f"{name} (elem={int(s['elements'])}, k={float(s['k']):.2f}, "
|
||||||
|
f"step={int(s['step'])})",
|
||||||
|
fontsize=12,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Panel 1: physics_score
|
||||||
|
sc0 = axes[0].scatter(x, y, c=physics_score, s=8, cmap="viridis")
|
||||||
|
axes[0].set_title("physics_score")
|
||||||
|
axes[0].set_aspect("equal")
|
||||||
|
plt.colorbar(sc0, ax=axes[0], shrink=0.8)
|
||||||
|
|
||||||
|
# Panel 2: teacher_eta
|
||||||
|
sc1 = axes[1].scatter(x, y, c=teacher_eta, s=8, cmap="magma")
|
||||||
|
axes[1].set_title("teacher_eta")
|
||||||
|
axes[1].set_aspect("equal")
|
||||||
|
plt.colorbar(sc1, ax=axes[1], shrink=0.8)
|
||||||
|
|
||||||
|
# Panel 3: teacher_mark
|
||||||
|
sc2 = axes[2].scatter(x, y, c=teacher_mark, s=8, cmap="Reds", vmin=0, vmax=1)
|
||||||
|
axes[2].set_title("teacher_mark")
|
||||||
|
axes[2].set_aspect("equal")
|
||||||
|
plt.colorbar(sc2, ax=axes[2], shrink=0.8)
|
||||||
|
|
||||||
|
# Panel 4: correction_label
|
||||||
|
sc3 = axes[3].scatter(
|
||||||
|
x, y, c=correction_label, s=8, cmap="coolwarm", vmin=-1, vmax=1
|
||||||
|
)
|
||||||
|
axes[3].set_title("correction_label")
|
||||||
|
axes[3].set_aspect("equal")
|
||||||
|
plt.colorbar(sc3, ax=axes[3], shrink=0.8)
|
||||||
|
|
||||||
|
for ax in axes:
|
||||||
|
ax.set_xlabel("x")
|
||||||
|
ax.set_ylabel("y")
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
out_path = output_dir / f"{name}.png"
|
||||||
|
fig.savefig(out_path, dpi=150, bbox_inches="tight")
|
||||||
|
plt.close(fig)
|
||||||
|
print(f" ✓ Saved: {out_path}")
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 5. Verdict
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def print_verdict(issues):
|
||||||
|
"""Print final PASS / WARNING / FAIL."""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print(" [5/5] Verdict")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
if not issues:
|
||||||
|
print("\n ✅ PASS — Data is consistent and ready for training.")
|
||||||
|
return "PASS"
|
||||||
|
|
||||||
|
# Classify issues
|
||||||
|
errors = [i for i in issues if any(k in i.upper() for k in ["MISSING", "BAD ", "MISMATCH"])]
|
||||||
|
warnings = [i for i in issues if i not in errors]
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print(f"\n ❌ FAIL — {len(errors)} error(s) found:")
|
||||||
|
for e in errors:
|
||||||
|
print(f" • {e}")
|
||||||
|
if warnings:
|
||||||
|
print(f"\n ⚠️ Also {len(warnings)} warning(s):")
|
||||||
|
for w in warnings:
|
||||||
|
print(f" • {w}")
|
||||||
|
return "FAIL"
|
||||||
|
|
||||||
|
print(f"\n ⚠️ WARNING — {len(warnings)} suspicious pattern(s):")
|
||||||
|
for w in warnings:
|
||||||
|
print(f" • {w}")
|
||||||
|
return "WARNING"
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Main
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Check step-wise correction dataset quality"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--data-dir", type=str, required=True,
|
||||||
|
help="Directory containing sampleXXXX_stepYYY.npz files",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-dir", type=str, default="outlook/data_correction_check",
|
||||||
|
help="Directory to save visualization plots",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
data_dir = Path(args.data_dir)
|
||||||
|
output_dir = Path(args.output_dir)
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print(" Correction Data Quality Check")
|
||||||
|
print("=" * 70)
|
||||||
|
print(f" Data dir: {data_dir}")
|
||||||
|
print(f" Output dir: {output_dir}")
|
||||||
|
|
||||||
|
# Load
|
||||||
|
print("\n Loading files...")
|
||||||
|
samples = load_all_samples(data_dir)
|
||||||
|
if not samples:
|
||||||
|
print(" ✗ No sample files found!")
|
||||||
|
sys.exit(1)
|
||||||
|
print(f" Loaded {len(samples)} files.")
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# 1. Field consistency
|
||||||
|
check_fields(samples, issues)
|
||||||
|
|
||||||
|
# 2. Global stats
|
||||||
|
compute_global_stats(samples)
|
||||||
|
|
||||||
|
# 3. Per-step trends
|
||||||
|
check_per_step_trends(samples, issues)
|
||||||
|
|
||||||
|
# 4. Visualization
|
||||||
|
plot_spatial_samples(samples, output_dir)
|
||||||
|
|
||||||
|
# 5. Verdict
|
||||||
|
verdict = print_verdict(issues)
|
||||||
|
|
||||||
|
print()
|
||||||
|
return 0 if verdict == "PASS" else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"data_dir": "outlook/data/budget_density_dataset_debug",
|
||||||
|
"epochs": 2,
|
||||||
|
"batch_size": 2,
|
||||||
|
"lr": 0.001,
|
||||||
|
"density_weight": 1.0,
|
||||||
|
"mono_weight": 0.0,
|
||||||
|
"seed": 42
|
||||||
|
},
|
||||||
|
"per_budget": {
|
||||||
|
"budget_1000": {
|
||||||
|
"mse": 2.209566116333008,
|
||||||
|
"corr": -0.5653519502499024,
|
||||||
|
"top20_overlap": 0.037037037037037035,
|
||||||
|
"n_graphs": 1
|
||||||
|
},
|
||||||
|
"budget_2000": {
|
||||||
|
"mse": 4.281199932098389,
|
||||||
|
"corr": -0.4546216071701198,
|
||||||
|
"top20_overlap": 0.07407407407407407,
|
||||||
|
"n_graphs": 1
|
||||||
|
},
|
||||||
|
"overall": {
|
||||||
|
"mse": 3.2453832626342773,
|
||||||
|
"corr": -0.4747666722392795
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"training_log": [
|
||||||
|
{
|
||||||
|
"epoch": 0,
|
||||||
|
"train_loss": 10.175954818725586,
|
||||||
|
"val_loss": 3.5997772216796875,
|
||||||
|
"val_corr": -0.5083683560193551,
|
||||||
|
"val_top20_overlap": 0.07407407407407407,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 1,
|
||||||
|
"train_loss": 7.313832759857178,
|
||||||
|
"val_loss": 3.2453830242156982,
|
||||||
|
"val_corr": -0.4747666660841676,
|
||||||
|
"val_top20_overlap": 0.05555555555555555,
|
||||||
|
"lr": 0.001
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,902 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"epoch": 0,
|
||||||
|
"train_loss": 1.214073695242405,
|
||||||
|
"val_loss": 1.1141467094421387,
|
||||||
|
"val_auc": 0.7793237914165878,
|
||||||
|
"val_topk_overlap": 0.18174046167047223,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 1,
|
||||||
|
"train_loss": 0.9936319440603256,
|
||||||
|
"val_loss": 0.8714833855628967,
|
||||||
|
"val_auc": 0.868452222365281,
|
||||||
|
"val_topk_overlap": 0.25161985623836425,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 2,
|
||||||
|
"train_loss": 0.8490312211215496,
|
||||||
|
"val_loss": 0.8512038290500641,
|
||||||
|
"val_auc": 0.8743725472266488,
|
||||||
|
"val_topk_overlap": 0.27610961191386213,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 3,
|
||||||
|
"train_loss": 0.789196622868379,
|
||||||
|
"val_loss": 0.8102231820424398,
|
||||||
|
"val_auc": 0.8853419495372643,
|
||||||
|
"val_topk_overlap": 0.27663024403011677,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 4,
|
||||||
|
"train_loss": 0.7635955549776554,
|
||||||
|
"val_loss": 0.7610340118408203,
|
||||||
|
"val_auc": 0.897967248359232,
|
||||||
|
"val_topk_overlap": 0.3101760507386457,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 5,
|
||||||
|
"train_loss": 0.7362100593745708,
|
||||||
|
"val_loss": 0.748906339208285,
|
||||||
|
"val_auc": 0.9006133073599438,
|
||||||
|
"val_topk_overlap": 0.31304398098650305,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 6,
|
||||||
|
"train_loss": 0.7291262559592724,
|
||||||
|
"val_loss": 0.738037109375,
|
||||||
|
"val_auc": 0.9036701915596148,
|
||||||
|
"val_topk_overlap": 0.32063751054106754,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 7,
|
||||||
|
"train_loss": 0.7118012420833111,
|
||||||
|
"val_loss": 0.7175028175115585,
|
||||||
|
"val_auc": 0.9082043418016371,
|
||||||
|
"val_topk_overlap": 0.33353017529134166,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 8,
|
||||||
|
"train_loss": 0.7020367321868738,
|
||||||
|
"val_loss": 0.7400095015764236,
|
||||||
|
"val_auc": 0.9038924270293778,
|
||||||
|
"val_topk_overlap": 0.3313267280645091,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 9,
|
||||||
|
"train_loss": 0.6895044669508934,
|
||||||
|
"val_loss": 0.7122037708759308,
|
||||||
|
"val_auc": 0.9097822096475472,
|
||||||
|
"val_topk_overlap": 0.33196020452732494,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 10,
|
||||||
|
"train_loss": 0.6835353250304858,
|
||||||
|
"val_loss": 0.7104852745930353,
|
||||||
|
"val_auc": 0.9102591502276932,
|
||||||
|
"val_topk_overlap": 0.3377191635459625,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 11,
|
||||||
|
"train_loss": 0.6724687827130159,
|
||||||
|
"val_loss": 0.7091294328371683,
|
||||||
|
"val_auc": 0.9097279573159085,
|
||||||
|
"val_topk_overlap": 0.32852482645677916,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 12,
|
||||||
|
"train_loss": 0.6608287704487642,
|
||||||
|
"val_loss": 0.7088778764009476,
|
||||||
|
"val_auc": 0.9102058348049611,
|
||||||
|
"val_topk_overlap": 0.3395986399411693,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 13,
|
||||||
|
"train_loss": 0.6516422840456167,
|
||||||
|
"val_loss": 0.6851696570714315,
|
||||||
|
"val_auc": 0.9161228078916188,
|
||||||
|
"val_topk_overlap": 0.3576108115171014,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 14,
|
||||||
|
"train_loss": 0.641690748433272,
|
||||||
|
"val_loss": 0.6870623429616293,
|
||||||
|
"val_auc": 0.9166719819324596,
|
||||||
|
"val_topk_overlap": 0.35467355015247576,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 15,
|
||||||
|
"train_loss": 0.6213903104265531,
|
||||||
|
"val_loss": 0.7060682823260626,
|
||||||
|
"val_auc": 0.9133957909689026,
|
||||||
|
"val_topk_overlap": 0.3541738121319473,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 16,
|
||||||
|
"train_loss": 0.6141671799123287,
|
||||||
|
"val_loss": 0.6649165799220403,
|
||||||
|
"val_auc": 0.9223772612302986,
|
||||||
|
"val_topk_overlap": 0.3727640261277016,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 17,
|
||||||
|
"train_loss": 0.6156036208073298,
|
||||||
|
"val_loss": 0.6852359771728516,
|
||||||
|
"val_auc": 0.917546789308805,
|
||||||
|
"val_topk_overlap": 0.3619924910260988,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 18,
|
||||||
|
"train_loss": 0.6078479687372843,
|
||||||
|
"val_loss": 0.6580855945746104,
|
||||||
|
"val_auc": 0.9232592198731486,
|
||||||
|
"val_topk_overlap": 0.36904381643112044,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 19,
|
||||||
|
"train_loss": 0.6046239572266737,
|
||||||
|
"val_loss": 0.6743976672490438,
|
||||||
|
"val_auc": 0.9211166203403452,
|
||||||
|
"val_topk_overlap": 0.3716380846859304,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 20,
|
||||||
|
"train_loss": 0.6046414325634638,
|
||||||
|
"val_loss": 0.6747777163982391,
|
||||||
|
"val_auc": 0.9219827510935892,
|
||||||
|
"val_topk_overlap": 0.36950146318342597,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 21,
|
||||||
|
"train_loss": 0.5860095359385014,
|
||||||
|
"val_loss": 0.6683605263630549,
|
||||||
|
"val_auc": 0.9212946098630218,
|
||||||
|
"val_topk_overlap": 0.36769860427540924,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 22,
|
||||||
|
"train_loss": 0.5924560849865278,
|
||||||
|
"val_loss": 0.6608883539835612,
|
||||||
|
"val_auc": 0.9237303628010585,
|
||||||
|
"val_topk_overlap": 0.38014786877240736,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 23,
|
||||||
|
"train_loss": 0.5699630603194237,
|
||||||
|
"val_loss": 0.6451186140378317,
|
||||||
|
"val_auc": 0.9271866474545916,
|
||||||
|
"val_topk_overlap": 0.3881152462988639,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 24,
|
||||||
|
"train_loss": 0.5860506445169449,
|
||||||
|
"val_loss": 0.6344831412037214,
|
||||||
|
"val_auc": 0.9279389861019536,
|
||||||
|
"val_topk_overlap": 0.37646285728709716,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 25,
|
||||||
|
"train_loss": 0.5696679763495922,
|
||||||
|
"val_loss": 0.651521734893322,
|
||||||
|
"val_auc": 0.926708376460331,
|
||||||
|
"val_topk_overlap": 0.3929882611353039,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 26,
|
||||||
|
"train_loss": 0.5605074142416319,
|
||||||
|
"val_loss": 0.6179426908493042,
|
||||||
|
"val_auc": 0.9313555002762031,
|
||||||
|
"val_topk_overlap": 0.38825309368394906,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 27,
|
||||||
|
"train_loss": 0.5452606951196989,
|
||||||
|
"val_loss": 0.6548436383406321,
|
||||||
|
"val_auc": 0.9257514085577712,
|
||||||
|
"val_topk_overlap": 0.3863040639453161,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 28,
|
||||||
|
"train_loss": 0.5649935603141785,
|
||||||
|
"val_loss": 0.6346206516027451,
|
||||||
|
"val_auc": 0.9282934601660542,
|
||||||
|
"val_topk_overlap": 0.3894207633159005,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 29,
|
||||||
|
"train_loss": 0.5343000267942747,
|
||||||
|
"val_loss": 0.6193932965397835,
|
||||||
|
"val_auc": 0.9335666994117104,
|
||||||
|
"val_topk_overlap": 0.40213981828158085,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 30,
|
||||||
|
"train_loss": 0.5222514060636362,
|
||||||
|
"val_loss": 0.6292086839675903,
|
||||||
|
"val_auc": 0.9315723170567752,
|
||||||
|
"val_topk_overlap": 0.4018058930665952,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 31,
|
||||||
|
"train_loss": 0.5288418171306452,
|
||||||
|
"val_loss": 0.6447375019391378,
|
||||||
|
"val_auc": 0.9296940196560114,
|
||||||
|
"val_topk_overlap": 0.3995336538081403,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 32,
|
||||||
|
"train_loss": 0.5321331315984329,
|
||||||
|
"val_loss": 0.6151211510101954,
|
||||||
|
"val_auc": 0.9339459494864105,
|
||||||
|
"val_topk_overlap": 0.41235340711455215,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 33,
|
||||||
|
"train_loss": 0.530803769826889,
|
||||||
|
"val_loss": 0.6180161188046137,
|
||||||
|
"val_auc": 0.9337274851175723,
|
||||||
|
"val_topk_overlap": 0.40770859907428864,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 34,
|
||||||
|
"train_loss": 0.5169201834748188,
|
||||||
|
"val_loss": 0.6140060871839523,
|
||||||
|
"val_auc": 0.9357011768509869,
|
||||||
|
"val_topk_overlap": 0.41396157069825373,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 35,
|
||||||
|
"train_loss": 0.5327582576622566,
|
||||||
|
"val_loss": 0.5903470193346342,
|
||||||
|
"val_auc": 0.9380921686251513,
|
||||||
|
"val_topk_overlap": 0.41993146997097147,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 36,
|
||||||
|
"train_loss": 0.5328234620392323,
|
||||||
|
"val_loss": 0.619572805861632,
|
||||||
|
"val_auc": 0.9348548623436956,
|
||||||
|
"val_topk_overlap": 0.42379147798654376,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 37,
|
||||||
|
"train_loss": 0.5179037377238274,
|
||||||
|
"val_loss": 0.6465846449136734,
|
||||||
|
"val_auc": 0.9321262023401722,
|
||||||
|
"val_topk_overlap": 0.4210270920253441,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 38,
|
||||||
|
"train_loss": 0.5000059170027574,
|
||||||
|
"val_loss": 0.6163648466269175,
|
||||||
|
"val_auc": 0.9355412748925351,
|
||||||
|
"val_topk_overlap": 0.4168377015768949,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 39,
|
||||||
|
"train_loss": 0.5333476103842258,
|
||||||
|
"val_loss": 0.601667749385039,
|
||||||
|
"val_auc": 0.9388670833680122,
|
||||||
|
"val_topk_overlap": 0.4324906240826149,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 40,
|
||||||
|
"train_loss": 0.49654081215461093,
|
||||||
|
"val_loss": 0.6376668587327003,
|
||||||
|
"val_auc": 0.9323076689591827,
|
||||||
|
"val_topk_overlap": 0.42067692553266867,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 41,
|
||||||
|
"train_loss": 0.49814322777092457,
|
||||||
|
"val_loss": 0.5965311030546824,
|
||||||
|
"val_auc": 0.9379983546564077,
|
||||||
|
"val_topk_overlap": 0.4248828237011408,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 42,
|
||||||
|
"train_loss": 0.4916490235676368,
|
||||||
|
"val_loss": 0.6603853727380434,
|
||||||
|
"val_auc": 0.9284689503947617,
|
||||||
|
"val_topk_overlap": 0.40912795311449807,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 43,
|
||||||
|
"train_loss": 0.5014090954015652,
|
||||||
|
"val_loss": 0.606838454802831,
|
||||||
|
"val_auc": 0.9374487658477999,
|
||||||
|
"val_topk_overlap": 0.42794334160595554,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 44,
|
||||||
|
"train_loss": 0.4867809346566598,
|
||||||
|
"val_loss": 0.5999851673841476,
|
||||||
|
"val_auc": 0.9387288792950925,
|
||||||
|
"val_topk_overlap": 0.43031923154259843,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 45,
|
||||||
|
"train_loss": 0.47708118582765263,
|
||||||
|
"val_loss": 0.5924614369869232,
|
||||||
|
"val_auc": 0.9402622606363618,
|
||||||
|
"val_topk_overlap": 0.4322550191316707,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 46,
|
||||||
|
"train_loss": 0.47802630874017876,
|
||||||
|
"val_loss": 0.6203606501221657,
|
||||||
|
"val_auc": 0.9358948083662981,
|
||||||
|
"val_topk_overlap": 0.4312379379630456,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 47,
|
||||||
|
"train_loss": 0.4643762943645318,
|
||||||
|
"val_loss": 0.6020812268058459,
|
||||||
|
"val_auc": 0.9401552072421557,
|
||||||
|
"val_topk_overlap": 0.4339950425104022,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 48,
|
||||||
|
"train_loss": 0.45041724294424057,
|
||||||
|
"val_loss": 0.6030316427350044,
|
||||||
|
"val_auc": 0.940497758680752,
|
||||||
|
"val_topk_overlap": 0.44206971785040405,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 49,
|
||||||
|
"train_loss": 0.446329349031051,
|
||||||
|
"val_loss": 0.607378251850605,
|
||||||
|
"val_auc": 0.9405052575110606,
|
||||||
|
"val_topk_overlap": 0.44424490947288175,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 50,
|
||||||
|
"train_loss": 0.4445270175735156,
|
||||||
|
"val_loss": 0.6005118936300278,
|
||||||
|
"val_auc": 0.940766237758347,
|
||||||
|
"val_topk_overlap": 0.43260791390184855,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 51,
|
||||||
|
"train_loss": 0.4460391675432523,
|
||||||
|
"val_loss": 0.6029875800013542,
|
||||||
|
"val_auc": 0.9409514039912139,
|
||||||
|
"val_topk_overlap": 0.45019310765449216,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 52,
|
||||||
|
"train_loss": 0.4418986613551776,
|
||||||
|
"val_loss": 0.6034468238552412,
|
||||||
|
"val_auc": 0.9406844266315528,
|
||||||
|
"val_topk_overlap": 0.44199077988820984,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 53,
|
||||||
|
"train_loss": 0.45017045301695663,
|
||||||
|
"val_loss": 0.6176436940828959,
|
||||||
|
"val_auc": 0.9395928665376544,
|
||||||
|
"val_topk_overlap": 0.43845852941961805,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 54,
|
||||||
|
"train_loss": 0.4438604520012935,
|
||||||
|
"val_loss": 0.6102191135287285,
|
||||||
|
"val_auc": 0.9402374042257553,
|
||||||
|
"val_topk_overlap": 0.44187147223059947,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 55,
|
||||||
|
"train_loss": 0.43919234598676365,
|
||||||
|
"val_loss": 0.6168825899561247,
|
||||||
|
"val_auc": 0.9411819435660866,
|
||||||
|
"val_topk_overlap": 0.4501981058770216,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 56,
|
||||||
|
"train_loss": 0.44451989606022835,
|
||||||
|
"val_loss": 0.6126844038565954,
|
||||||
|
"val_auc": 0.9395184630110243,
|
||||||
|
"val_topk_overlap": 0.43740834990699734,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 57,
|
||||||
|
"train_loss": 0.4568589733292659,
|
||||||
|
"val_loss": 0.587329626083374,
|
||||||
|
"val_auc": 0.941194203700295,
|
||||||
|
"val_topk_overlap": 0.4401501924816575,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 58,
|
||||||
|
"train_loss": 0.44505749208231765,
|
||||||
|
"val_loss": 0.6224502697587013,
|
||||||
|
"val_auc": 0.9398294219991805,
|
||||||
|
"val_topk_overlap": 0.44548887871606835,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 59,
|
||||||
|
"train_loss": 0.43547111190855503,
|
||||||
|
"val_loss": 0.5913231869538625,
|
||||||
|
"val_auc": 0.9435686505726166,
|
||||||
|
"val_topk_overlap": 0.45369382113032325,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 60,
|
||||||
|
"train_loss": 0.43305354565382004,
|
||||||
|
"val_loss": 0.6314671859145164,
|
||||||
|
"val_auc": 0.9385988844361027,
|
||||||
|
"val_topk_overlap": 0.4377768264505535,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 61,
|
||||||
|
"train_loss": 0.4373071851829688,
|
||||||
|
"val_loss": 0.6283594146370888,
|
||||||
|
"val_auc": 0.9401304130793475,
|
||||||
|
"val_topk_overlap": 0.4329978451141574,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 62,
|
||||||
|
"train_loss": 0.43299777743717033,
|
||||||
|
"val_loss": 0.6291215494275093,
|
||||||
|
"val_auc": 0.9410761593885777,
|
||||||
|
"val_topk_overlap": 0.44497516710926627,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 63,
|
||||||
|
"train_loss": 0.42864579521119595,
|
||||||
|
"val_loss": 0.6106193959712982,
|
||||||
|
"val_auc": 0.9424943034022305,
|
||||||
|
"val_topk_overlap": 0.4532936274616251,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 64,
|
||||||
|
"train_loss": 0.42673514783382416,
|
||||||
|
"val_loss": 0.6450835888584455,
|
||||||
|
"val_auc": 0.9371212242970672,
|
||||||
|
"val_topk_overlap": 0.43187084953076466,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 65,
|
||||||
|
"train_loss": 0.4351537538071473,
|
||||||
|
"val_loss": 0.6039384330312411,
|
||||||
|
"val_auc": 0.9416574503767061,
|
||||||
|
"val_topk_overlap": 0.4413121605675935,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 66,
|
||||||
|
"train_loss": 0.4369606903443734,
|
||||||
|
"val_loss": 0.6139999578396479,
|
||||||
|
"val_auc": 0.9417945131346139,
|
||||||
|
"val_topk_overlap": 0.44869988908245123,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 67,
|
||||||
|
"train_loss": 0.42625842802226543,
|
||||||
|
"val_loss": 0.614420086145401,
|
||||||
|
"val_auc": 0.9418002442661444,
|
||||||
|
"val_topk_overlap": 0.4466080667540172,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.0005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 68,
|
||||||
|
"train_loss": 0.4289086237549782,
|
||||||
|
"val_loss": 0.6421931609511375,
|
||||||
|
"val_auc": 0.9381999297311365,
|
||||||
|
"val_topk_overlap": 0.4407300883699639,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 69,
|
||||||
|
"train_loss": 0.4162418972700834,
|
||||||
|
"val_loss": 0.6197224507729212,
|
||||||
|
"val_auc": 0.9418537016432265,
|
||||||
|
"val_topk_overlap": 0.4524438704921057,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 70,
|
||||||
|
"train_loss": 0.4120306807259719,
|
||||||
|
"val_loss": 0.6089134141802788,
|
||||||
|
"val_auc": 0.943210214554455,
|
||||||
|
"val_topk_overlap": 0.45175368788257725,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 71,
|
||||||
|
"train_loss": 0.40985787970324356,
|
||||||
|
"val_loss": 0.6260952974359194,
|
||||||
|
"val_auc": 0.9419729299378439,
|
||||||
|
"val_topk_overlap": 0.45117531843183434,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 72,
|
||||||
|
"train_loss": 0.4100499761601289,
|
||||||
|
"val_loss": 0.6254942963520685,
|
||||||
|
"val_auc": 0.9414634241732339,
|
||||||
|
"val_topk_overlap": 0.4515264870513763,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 73,
|
||||||
|
"train_loss": 0.4103688560426235,
|
||||||
|
"val_loss": 0.617545910179615,
|
||||||
|
"val_auc": 0.9422998727410377,
|
||||||
|
"val_topk_overlap": 0.45038961083279627,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 74,
|
||||||
|
"train_loss": 0.40805083389083546,
|
||||||
|
"val_loss": 0.6290716951092085,
|
||||||
|
"val_auc": 0.9419815230403903,
|
||||||
|
"val_topk_overlap": 0.45038819117177503,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 75,
|
||||||
|
"train_loss": 0.40792630054056644,
|
||||||
|
"val_loss": 0.6340545068184534,
|
||||||
|
"val_auc": 0.9403481996467591,
|
||||||
|
"val_topk_overlap": 0.44560532650107126,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 76,
|
||||||
|
"train_loss": 0.40834025479853153,
|
||||||
|
"val_loss": 0.6236351157228152,
|
||||||
|
"val_auc": 0.9428621339939343,
|
||||||
|
"val_topk_overlap": 0.4546709457187485,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 77,
|
||||||
|
"train_loss": 0.4071060884743929,
|
||||||
|
"val_loss": 0.6236031403144201,
|
||||||
|
"val_auc": 0.9420169620152015,
|
||||||
|
"val_topk_overlap": 0.4530651445404334,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 78,
|
||||||
|
"train_loss": 0.4074497365703185,
|
||||||
|
"val_loss": 0.6290433133641878,
|
||||||
|
"val_auc": 0.9421106588716064,
|
||||||
|
"val_topk_overlap": 0.45288347151685654,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.00025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 79,
|
||||||
|
"train_loss": 0.40854040533304214,
|
||||||
|
"val_loss": 0.6204406345884005,
|
||||||
|
"val_auc": 0.9422574500711209,
|
||||||
|
"val_topk_overlap": 0.4520385582247283,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 80,
|
||||||
|
"train_loss": 0.40153354965150356,
|
||||||
|
"val_loss": 0.632063552737236,
|
||||||
|
"val_auc": 0.9424195175064664,
|
||||||
|
"val_topk_overlap": 0.45488917815907826,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 81,
|
||||||
|
"train_loss": 0.4004344331721465,
|
||||||
|
"val_loss": 0.6369497875372568,
|
||||||
|
"val_auc": 0.9416930329987134,
|
||||||
|
"val_topk_overlap": 0.44976943200120656,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 82,
|
||||||
|
"train_loss": 0.39863520363966626,
|
||||||
|
"val_loss": 0.6321395362416903,
|
||||||
|
"val_auc": 0.9425178927376926,
|
||||||
|
"val_topk_overlap": 0.45331703729372624,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 83,
|
||||||
|
"train_loss": 0.40041249555846053,
|
||||||
|
"val_loss": 0.6306998108824095,
|
||||||
|
"val_auc": 0.9420066732924216,
|
||||||
|
"val_topk_overlap": 0.4527129685740117,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 84,
|
||||||
|
"train_loss": 0.3980693680544694,
|
||||||
|
"val_loss": 0.6334150905410448,
|
||||||
|
"val_auc": 0.9417373821686397,
|
||||||
|
"val_topk_overlap": 0.45229146535383963,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 85,
|
||||||
|
"train_loss": 0.39918220477799576,
|
||||||
|
"val_loss": 0.6338345656792322,
|
||||||
|
"val_auc": 0.9418289696568314,
|
||||||
|
"val_topk_overlap": 0.45314173622093856,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 86,
|
||||||
|
"train_loss": 0.3980860977123181,
|
||||||
|
"val_loss": 0.6285603841145834,
|
||||||
|
"val_auc": 0.942747009903466,
|
||||||
|
"val_topk_overlap": 0.4547239031491113,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 87,
|
||||||
|
"train_loss": 0.39842924910287064,
|
||||||
|
"val_loss": 0.6385276938478152,
|
||||||
|
"val_auc": 0.9419261166158088,
|
||||||
|
"val_topk_overlap": 0.45597434103509366,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 88,
|
||||||
|
"train_loss": 0.3973500070472558,
|
||||||
|
"val_loss": 0.655018168191115,
|
||||||
|
"val_auc": 0.9400152699086647,
|
||||||
|
"val_topk_overlap": 0.44998755110062144,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 89,
|
||||||
|
"train_loss": 0.3958093722661336,
|
||||||
|
"val_loss": 0.6387847339113554,
|
||||||
|
"val_auc": 0.9418482854933052,
|
||||||
|
"val_topk_overlap": 0.4550794129001621,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 0.000125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 90,
|
||||||
|
"train_loss": 0.39699620256821316,
|
||||||
|
"val_loss": 0.6364182805021604,
|
||||||
|
"val_auc": 0.9422100800575627,
|
||||||
|
"val_topk_overlap": 0.45455014977480973,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 91,
|
||||||
|
"train_loss": 0.39377099089324474,
|
||||||
|
"val_loss": 0.6385861511031786,
|
||||||
|
"val_auc": 0.9422414241184843,
|
||||||
|
"val_topk_overlap": 0.4553409776868369,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 92,
|
||||||
|
"train_loss": 0.393089081471165,
|
||||||
|
"val_loss": 0.6448530877629916,
|
||||||
|
"val_auc": 0.9416143742782719,
|
||||||
|
"val_topk_overlap": 0.45326054623552575,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 93,
|
||||||
|
"train_loss": 0.3924831257512172,
|
||||||
|
"val_loss": 0.6419519757231077,
|
||||||
|
"val_auc": 0.9417058895247528,
|
||||||
|
"val_topk_overlap": 0.4531113294679451,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 94,
|
||||||
|
"train_loss": 0.3940933719277382,
|
||||||
|
"val_loss": 0.6359312161803246,
|
||||||
|
"val_auc": 0.9424735513283455,
|
||||||
|
"val_topk_overlap": 0.4535435590373251,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 95,
|
||||||
|
"train_loss": 0.3928225214282672,
|
||||||
|
"val_loss": 0.6437094608942667,
|
||||||
|
"val_auc": 0.941819742134597,
|
||||||
|
"val_topk_overlap": 0.45096912604225076,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 96,
|
||||||
|
"train_loss": 0.39205564993123215,
|
||||||
|
"val_loss": 0.6462149644891421,
|
||||||
|
"val_auc": 0.9415178562729243,
|
||||||
|
"val_topk_overlap": 0.4536663302717089,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 97,
|
||||||
|
"train_loss": 0.39256475990017253,
|
||||||
|
"val_loss": 0.6427710602680842,
|
||||||
|
"val_auc": 0.9416758225940753,
|
||||||
|
"val_topk_overlap": 0.4539412408745828,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 98,
|
||||||
|
"train_loss": 0.3940690668920676,
|
||||||
|
"val_loss": 0.6387203360597292,
|
||||||
|
"val_auc": 0.9422565119894593,
|
||||||
|
"val_topk_overlap": 0.4544070442694809,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"epoch": 99,
|
||||||
|
"train_loss": 0.3922309031089147,
|
||||||
|
"val_loss": 0.6448526407281557,
|
||||||
|
"val_auc": 0.9419215473887895,
|
||||||
|
"val_topk_overlap": 0.45427252886297304,
|
||||||
|
"physics_topk_overlap": 0.13027826412738905,
|
||||||
|
"lr": 6.25e-05
|
||||||
|
}
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue