# Eval Benchmark — 推理评估与流场分析

> 对 V5 Train 模型和 Legacy 旧模型进行全面推理采样和可视化对比。
> 复用 train env 保证与训练流程完全一致，不使用 skeleton injection。

## 文件结构

```
src/drl_pinball/eval/
  README.md                    # 本文件 (自我审查清单 + 复习流程)
  __init__.py
  scene_manifest.py            # 所有 scene 的配置单点真理源
  infer_train.py               # V5 Train 模型推理引擎 (GPU 0)
  infer_reproduce.py           # Reproduce 模型推理引擎 (GPU 1)
  viz_flow.py                  # 涡量图跨场景拼图 (统一 colormap)
  viz_signals.py               # obs/action/DTW/FFT 时序可视化
  generate_report.py           # 汇总表格 + 柱状图
  run_all.sh                   # 主启动器 (GPU 调度)
  output/
    train/{scene}/             # signals.npz, vorticity_*.png, metrics.json, all_seeds.json
    reproduce/{scene}/         # 同上
    reports/
      comparison_table.md      # 汇总对比表
      summary_dtw_barchart.png # DTW 柱状图
      vorticity_panels/        # 跨场景拼图
      signal_plots/            # 诊断信号图
```

## Quick Start

```bash
conda activate pycuda_3_10
cd src/drl_pinball/eval

# === 全量跑 (双 GPU) ===
bash run_all.sh

# === 单场景测试 ===
python infer_train.py --scene re100 --device-id 0
python infer_train.py --scene illusion_1L --device-id 0

# === 仅跑报告生成 (已有 output 数据) ===
python generate_report.py
python viz_signals.py --side train
python viz_flow.py
```

## GPU 调度策略

```
时间轴:

GPU0: [re100] [ill_1L] [ill_15L] [ill_075L] --120s--> [re60] --120s--> [re200] --120s--> [re400]
      (config A: karman_2000x600, nu=0.004)      (config B: re60)  (config C: re200)  (config D: re400)

GPU1: --120s--> [karman_cloak] [steady_cloak] [ill_075L] [ill_1L] [ill_15L] [vortex_lamb] [vortex_taylor]
                (全部 config_pinball.json, 无额外编译)
```

- GPU 1 比 GPU 0 晚 120 秒启动，避免两个不同 config 同时触发编译
- 同 config 内 scene 连续运行，无 recompilation 开销
- 每个 scene 约 5-8 分钟（warmup + FIFO + 360 步推理）

## 场景覆盖率

### Train pipeline (GPU 0)
| Scene | Config | Seeds |
|-------|--------|-------|
| re100_karman | karman_2000x600 (Re=100) | 41-45, 539439 |
| transfer_re60 | karman_2000x600_re60 | 41, 43 |
| transfer_re200 | karman_2000x600_re200 | 41, 43, 45 |
| transfer_re400 | karman_2000x600_re400 | 43 |
| illusion_075L | karman_2000x600 | 41 |
| illusion_1L | karman_2000x600 | 41, 43 |
| illusion_15L | karman_2000x600 | 41, 43 |

### Reproduce pipeline (GPU 1)
| Scene | Config | 模型 |
|-------|--------|------|
| karman_cloak | pinball (1280x512) | d1a3o12_re100 |
| steady_cloak | pinball (1280x512) | open-loop [0,-5.1,5.1] |
| illusion_075L | pinball (1280x512) | d1a3o14_250525_imit_075L_2U_400S |
| illusion_1L | pinball (1280x512) | d1a3o14_250525_imit_1L_2U_600S |
| illusion_15L | pinball (1280x512) | d1a3o14_250525_imit_15L_2U |
| vortex_lamb | pinball (1280x512) | vortex_lamb |
| vortex_taylor | pinball (1280x512) | vortex_taylor |

## 核心设计原则

1. **复用 train env 而非重构 CFD** — KarmanCloakEnv / IllusionCloakEnv 已封装 norm、calibration、DTW、snapshot/restore
2. **PPO.load() + VecNormalize.load()** — SB3 标准 API，不使用 skeleton injection
3. **统一涡量 [-0.03, 0.03] colormap** — 所有场景跨可比
4. **每步全量时序** — sensors(6) + forces(6) + actions(3) + rewards 存入 signals.npz

---

## 自我审查清单

### 启动前
- [ ] `conda activate pycuda_3_10`
- [ ] 所有 `best_model.zip` 和 `vec_normalize.pkl` 路径存在 (见 scene_manifest.py)
- [ ] 所有 `calibration.json` 和 `target.npy` 存在 (train/calibrations/)
- [ ] `nvidia-smi` 显示两个 GPU 空闲
- [ ] 无僵尸 pycuda 进程 (`pkill -f pycuda` 后等待 3 分钟)

### Phase 1 — Train 推理
- [ ] `python infer_train.py --scene re100 --device-id 0` 单场景测试通过
- [ ] 输出的 `signals.npz` 形状: (360,6) sensors, (360,6) forces, (360,3) actions
- [ ] rewards 非 NaN，DTW sim 在 [0,1] 内
- [ ] Zero-action baseline 的 DTW 明显低于 controlled

### Phase 2 — Reproduce 推理
- [ ] `python infer_reproduce.py --scene karman_cloak --device-id 1` 测试通过
- [ ] 旧模型 PPO.load() 不报错
- [ ] Vortex 场景 add_vortex 正确初始化（check target.npz 非全零）

### Phase 3 — 涡量图
- [ ] `python viz_flow.py` 产出全部拼图
- [ ] vorticity range 统一 [-0.03, 0.03]
- [ ] 圆柱边界清晰可见
- [ ] target vs controlled vs zero 三栏对比正常

### Phase 4 — 时序图
- [ ] `python viz_signals.py --side train` 产出全部诊断图
- [ ] FFT 频谱确认 controlled 与 target Strouhal 匹配
- [ ] Action 时序 policy 收敛到稳态 (非振荡)

### Phase 5 — 汇总报告
- [ ] `python generate_report.py` 产出 comparison_table.md
- [ ] Train DTW >= Reproduce DTW (或合理的 gap 原因)

### 最终验收
- [ ] 7 train + 7 reproduce 目录完整
- [ ] 每个目录: signals.npz, vorticity_controlled.png, vorticity_target.png, vorticity_zero.png, metrics.json, all_seeds.json
- [ ] reports/ 下: comparison_table.md, summary_dtw_barchart.png, vorticity_panels/, signal_plots/

---

## 复习流程 (中断后快速恢复)

按顺序读取以下文件重建上下文：

1. **本文件** — 确认当前进度和 GPU 调度图

2. **`eval/scene_manifest.py`** — 所有 scene 配置
   ```bash
   python -c "from drl_pinball.eval.scene_manifest import TRAIN_SCENES; \
              [print(s['scene_id'], s['si']) for s in TRAIN_SCENES]"
   ```

3. **`train/TRAIN_KNOWLEDGE.md`** sections 1-3 — V5 训练管线回顾

4. **验证环境可用**:
   ```bash
   ls eval/output/train/*/metrics.json 2>/dev/null  # 已完成的 scene
   ls eval/output/reproduce/*/metrics.json 2>/dev/null
   ```

5. **从断点继续**:
   ```bash
   # 单 scene:
   python infer_train.py --scene re200 --device-id 0
   # 或 Kill pycuda 进程后重新启动显卡:
   pkill -f pycuda; sleep 180; bash run_all.sh
   ```

### 常见问题排查

| 问题 | 解决方案 |
|------|---------|
| `cuInit failed` | GPU 被占用。`pkill -f pycuda; sleep 180` 后重试 |
| `best_model.zip not found` | 训练未完成。查看 `train/output/{case}/models/` |
| `target.npy not found` | calibration 缺失。重新运行 `calibrate.py` |
| DTW sim = -1.0 (reproduce) | 正常。Reproduce 不计算 DTW，由 report 统一处理 |
| 涡量图空白 | 检查 `render_vorticity_field` 的 `vmin/vmax` 或 cylinders 坐标 |
| VecNormalize.pkl 不存在 | 训练时未保存。用 `best_model.zip` 同目录的 `vec_normalize.pkl` |

---

## 代码关键点

### infer_train.py 的关键差异点

- 每个 seed 独立 `PPO.load()` + `VecNormalize.load()`，不是共用 skeleton
- 推理时 `SymmetryAugmentWrapper(prob=0.0)`（deterministic mode）
- Raw obs 从 `env._read_obs()` 提取，通过 wrapper chain 穿透
- Target vorticity 单独创建 Simulation 生成（dist cylinder only）

### infer_reproduce.py 的关键差异点

- 1280x512 网格，parabolic inlet，bounce-back walls
- Legacy norm 从 `SR_analysis/data/*/norm.json` 加载
- 旧动作缩放: `norm * scale + bias` (各 scene 不同)
- Illusion 模型 obs_dim=14（含 target_cd, target_cl，通过 harmonics 重构）
- Vortex 用 `add_vortex()` 在不同 x 位置添加涡量

### 输出文件命名

| 文件 | 内容 |
|------|------|
| `signals.npz` | sensors (N,6), forces (N,6), actions (N,3), rewards (N,) |
| `vorticity_controlled.png` | DRL 控制后的 final 时刻涡量 |
| `vorticity_target.png` | 目标状态 (disturbance only / target cylinder) |
| `vorticity_zero.png` | 零动作 baseline (无控制) |
| `metrics.json` | DTW sim, reward, action stats |
| `all_seeds.json` | 每个 seed 的详细评分 |
