# CCD Analysis: Lessons Learned & Knowledge Base

## 项目全局知识

### Re 数定义
- `Re_D` = U0 * D / nu, where D = 20 (single cylinder diameter)
- `Re` (code) = U0 * 2D / nu, where 2D = 40
- Default Re=100 (code) <-> Re_D=50
- Formula: nu = U0 * 2D / Re_code = 0.01 * 40 / 100 = 0.004

### 网格和物理参数
- Grid: 1280 x 512, D2Q9, MRT
- L0 = 20 (base length unit)
- U0 = 0.01 (inlet center velocity, lattice units)
- Inlet: parabolic profile (Zou-He local)
- Walls: bounce-back (no-slip)
- Outlet: NEQ extrapolation

### 核心规则：添加顺序决定 obs 布局

**这是整个项目中最容易被搞错的地方。** Legacy FlowField 的 `obs` 数组的内容完全由对象添加顺序决定，不同脚本可能使用不同的添加顺序。

**`legacy_env_karman_cloak_standard.py` (7 objects, 训练 env):**
添加顺序: dist_cyl(0) -> s0(1) -> s1(2) -> s2(3) -> front(4) -> bottom(5) -> top(6)
obs[2:14] = [s0_ux,uy, s1_ux,uy, s2_ux,uy, front_fx,fy, bottom_fx,fy, top_fx,fy]
         = [sensors(6), forces(6)]
用 obs[2:14] 跳过了 dist_cyl 的 2 个值。

**`legacy_env_imit.py` (6 objects, 训练 env):**
添加顺序: s0(0) -> s1(1) -> s2(2) -> front(3) -> bottom(4) -> top(5)
obs[0:12] = [s0_ux,uy, s1_ux,uy, s2_ux,uy, front_fx,fy, bottom_fx,fy, top_fx,fy]
         = [sensors(6), forces(6)]

**`uni_test.ipynb` (推理脚本, 可能使用不同添加顺序):**
- 对于 Karman cloak: restore DDF + add dist_cyl → obs[0:12] 的布局取决于 DDF 保存时的状态
- 对于 Illusion: 单独 env, 添加顺序取决于代码

**关键教训: 每次处理 obs 时必须先查看对应脚本中的添加顺序。**

### 旧 API (LegacyCelerisLab) 要点
- `flow_field.run(N, action_array)` 返回的 `obs` 已经是 N 步每步平均
- `action_array` 长度 = 对象数量, sensor 的 action slot 被忽略
- `flow_field.run()` 内部有指数平滑 (weight=0.1)
- `save_ddf()/restore_ddf()/apply_ddf()` 用于 checkpoint
- 需要 `context.push()/pop()` 管理 PyTorch + PyCUDA 上下文冲突

### Legacy 环境标准初始化流程
1. 创建 FlowField
2. 添加对象(传感器、圆柱)
3. 稳定 4*NX/U0 步
4. 录制 target 信号 (FIFO_LEN 步)
5. 添加 pinball 圆柱 (延续使用同一个 FlowField)
6. 再次稳定, checkpoint
7. 零动作跑 FIFO_LEN 步, 计算 norm
8. 恢复 checkpoint, 偏置动作跑 FIFO_LEN 步, 保存 FIFO 状态
9. reset = 恢复 checkpoint + 恢复 FIFO

### Cloak (Karman) 环境参数
- Model: d1a3o12_re100.zip (7 objects: dist + 3 sensors + 3 pinball)
- S_DIM = 12, A_DIM = 3
- SAMPLE_INTERVAL = 800, FIFO_LEN = 150, CONV_LEN = 30
- Action: temp[4:7] = (action * 8 + [0, -4, 4]) * U0
- Norm: force_norm_fact = 6 * max(|temp_states[:, 6:12]|)
- Obs: hstack([forces_norm, sens_norm]) → 12-dim
  - forces_norm = obs_slice[6:12] / force_norm_fact  (3 pinball forces)
  - sens_norm = (obs_slice[0:6] - sens_deviation) / sens_norm_fact  (3 sensors)
- Reward:
  - cd = (forces[0] + forces[2] + forces[4]) / 3
  - cl = (forces[1] + forces[3] + forces[5]) / 3
  - reward_cd = exp(-|cd * 20|)
  - reward_cl = exp(-|cl * 80|)
  - reward_sim = exp(-10 * |similarities - 1|)
  - reward = min(0.3*reward_cd + 0.4*reward_cl + 0.3*reward_sim, 1.0)

### Illusion (Imit) 环境参数
- Model: d1a3o14_250525_imit_1L_2U_600S.zip (6 objects: 3 sensors + 3 pinball)
- S_DIM = 14, A_DIM = 3
- SAMPLE_INTERVAL = 600, FIFO_LEN = 150, CONV_LEN = 36
- Action: temp[3:6] = (action * 8 + [0, -2, 2]) * U0
- U0 = 0.02 (2U), nu = 0.008
- Target cylinder: center=(20*L0, CENTER_Y), radius=L0(对1L模型)
- 目标传感器: x=30*L0
- Pinball: front=(19*L0, CENTER_Y), bottom=(20.3*L0, CENTER_Y+0.75*L0),
  top=(20.3*L0, CENTER_Y-0.75*L0)
- Norm: force_norm_fact = 6 * max(|temp_states[:, 6:12]|)
- Obs: hstack([forces_norm, sens_norm, target_cd_norm, target_cl_norm]) → 14-dim
  - 注意: forces_norm 使用 SUM 不是 mean (cd = f0+f2+f4)
- Reward:
  - cd = forces[0] + forces[2] + forces[4]  (SUM)
  - cl = forces[1] + forces[3] + forces[5]  (SUM)
  - 从 harmonics 重构 target_cd, target_cl
  - reward_cd = exp(-|(cd - target_cd) * 10|)
  - reward_cl = exp(-|(cl - target_cl) * 10|)
  - reward_sim = exp(-10 * |similarities - 1|)
  - reward = min(0.3*reward_cd + 0.3*reward_cl + 0.4*reward_sim, 1.0)

### Cloak vs Illusion 关键差异
| 方面 | Cloak | Illusion |
|------|-------|---------|
| S_DIM | 12 | 14 |
| 观测 | [forces(6), sens(6)] | [forces(6), sens(6), target_cd, target_cl] |
| cd/cl 计算 | forces/3 (mean of 3) | sum of 3 (no /3) |
| cd reward | exp(-|cd * 20|) | exp(-|(cd-target_cd) * 10|) |
| cl reward | exp(-|cl * 80|) | exp(-|(cl-target_cl) * 10|) |
| 权重 | cd=0.3, cl=0.4, sim=0.3 | cd=0.3, cl=0.3, sim=0.4 |
| Action bias | [0, -4, 4] | [0, -2, 2] |
| SAMPLE_INTERVAL | 800 | 600 |
| CONV_LEN | 30 | 36 |
| 目标信号 | 传感器时序 (6通道) | 传感器+力 (8通道) + 谐波分解 |
| DTW lag | target[:,1] vs fifo[:,1] | target[:,3] vs fifo[:,1] |

### Norm 计算 (通用)
```python
temp_states = np.array(fifo)  # (FIFO_LEN, 12), each = obs_slice
force_norm_fact = 6 * max(|temp_states[:, 6:12]|)  # 力的最大波动 * 6
sens_deviation[i] = mean(temp_states[:, i])         # 传感器均值
sens_norm_fact[i] = 5 * max(|temp_states[:, i] - deviation|)  # 传感器波动 * 5
```
注意: force_norm_fact 和 sens 统计从 obs_slice 的哪一部分取值取决于添加顺序。

### DTW 相似度计算 (通用模式)
1. 从 target_states[CONV_LEN:2*CONV_LEN, lag_channel] 取参考段
2. 从 fifo[-CONV_LEN:, lag_channel] 取当前段
3. 互相关计算 lag
4. 对 6 个传感器通道, 用 lag 补偿后算 DTW: 1 - distance/len
5. 结果平均

### Strouhal 数
- 本项目 St=0.267 (抛物线入口 + no-slip 壁面)
- 高于经典圆柱的 0.165, 因为 7.8% 阻塞比 + 抛物线入口
- 用 St 做无量纲一致性检查: f_expected = St * U0 / D

### 涡量图
- 正确公式: ω_z = dv/dx - du/dy
- ux.shape = (NY, NX) = (512, 1280)
- 实现: `omega = np.gradient(uy, axis=1) - np.gradient(ux, axis=0)`
  - axis=0 是 y 方向, axis=1 是 x 方向
  - grad(uy, axis=1) = duy/dx, grad(ux, axis=0) = dux/dy

---

## 经验教训总结

### 第一大教训: 不验证就做分析 = 无效
之前没有验证 PPO 控制质量就直接做 CCD 分析, 导致分析可能基于错误的流场。
**必须先验证每个 case 的 reward/similarity 达到论文水平, 再进入分析。**

### 第二大教训: 添加顺序决定一切
obs 布局完全由对象添加顺序决定。不能假设 obs[i] 的含义, 必须检查每个脚本中对象的实际添加顺序。

### 第三大教训: 环境初始化必须完全复现
Legacy 环境在 __init__ 中做了大量工作: target 录制、谐波分析(illusion)、norm 计算、偏置 FIFO、checkpoint。任何一步遗漏都会导致 PPO 推理失败。

### 第四大教训: uni_test 是最可信的参考
uni_test.ipynb 是用户手动验证过的推理脚本, 它的 obs 处理和 norm 逻辑应作为最高优先级参考。

### 第五大教训: 存储管理与采样率
早期存储了 400 帧全场快照 (~2GB), 导致后处理缓慢。应先用高频传感器时序做周期检测, 确定代表周期后再存场。
自适应采样率 (使原始采样 ~24 点/周期) 比统一 SAMPLE_INTERVAL 更合理。

### 工作流建议
1. 数据采集: 用 legacy 环境 + norm, 先跑短试(50步)验证 reward/similarity
2. 再跑完整 rollout (500步), 保存传感器/力/动作为高频, 场为主动选择的窗口
3. 周期检测 + 相位重采样 (纯 CPU)
4. POD + CCD (纯 CPU)
5. 每一步都输出数值指标, 不要只看图

---

## 工具函数状态清单

| 文件 | 状态 | 说明 |
|------|------|------|
| `cfg.py` | 可靠 | 路径和常量, 无 CFD 依赖 |
| `utils.py` | 可靠 | CFD 加载/场读取 |
| `analysis_utils.py` | 可靠 | 周期检测、POD、CCD、重采样(修正了涡量公式) |
| `phase0_standard_freq.py` | 可靠 | 已验证与 Phase 0 一致 |
| `phase1_collect.py` | 部分可靠 | Illusion 的 norm 和 PPO 推理已验证, Cloak 的采集已验证 |
| `phase2_resample.py` | 可靠 | 周期检测和重采样已验证 |
| `phase3_pod.py` | 可靠 | POD 计算已验证 |
| `phase4_ccd.py` | 部分可靠 | CCD 算法正确, 但 action-CCD 的 corr 值为 0 需排查 |
| `phase5_steady.py` | 有问题 | E_mean_uy 因分母过小爆炸, eta_fluc 因环境不匹配错误 |
| `validate_control.py` | 有问题 | 多次迭代仍未能复现论文水平的相似度 |
| `compile_results.py` | 可靠 | 无 CFD 依赖, 纯报告脚本 |
