# SINDy 与 SR 背景知识

## 文档作用

这份文档只负责一件事：**给正在工作的 coder 提供背景知识、已经确认的经验、已踩过的坑和当前结论强度。**

它不是任务清单，不直接安排"下一步做什么"。凡是执行顺序、阶段划分、最小交付物，统一写在 `sindy_sr_notes`。这份 knowledge 只保留：

- 已确认的技术事实
- 历史错误与纠正
- 结果该如何理解
- 哪些话可以说，哪些话现在还不能说
- 代码和实验上最容易踩的坑

---

## 一、这条线在项目里的位置

SINDy 与 SR 不是独立课题，而是 pinball 后处理主线中的一段工具链。项目真正要解释的是：

\[
\text{obs} \rightarrow \text{act} \rightarrow \text{flow structure} \rightarrow \text{signature}
\]

SINDy 与 SR 当前只直接处理其中的 `obs -> act` 白箱化，但它们的价值在于：

- 检验控制是否真的依赖少数物理量
- 识别不同 cloak 场景中是否复用了同一类反馈结构
- 为后续把控制律与 force、mean wake、observable-related structure 接起来提供接口

因此，任何 SINDy/SR 结果都不应脱离项目总体物理主线单独解读。

---

## 二、当前已经确认的技术事实（2026-06-23 更新）

### 1. Karman 跨 Re 联合公式成立（2026-06-23 新增）

\[
\alpha_F = \Delta a_F / \Delta t - 14.952 \cdot \mu C_{l,\text{tot}}
\]

四个雷诺数（50/100/200/400）CFD 闭环验证全部通过，平均 0.847，远超旧基线 0.735。**这是目前最硬的统一公式证据。** 关键设计：`daF_dt` 用 `dt_c` 归一化消除采样率依赖，`mu * Cl_tot` 项自适应粘度变化。

### 2. 独立 Re PySR 公式形态各异但都成功（2026-06-23 新增）

四个雷诺数的独立公式闭环均 >0.89，但公式形态完全不同：从 Re=50 的阻力阻尼主导，逐步过渡到 Re=400 的升力主导。这反映 pinball 流态在 Re=50~400 区间经历了分岔，PPO 学会了针对性策略。

### 3. 训练分布偏移的假象项（2026-06-23 新增）

PySR 联合搜索初期返回的公式包含 `daB_dt` 项（`alpha_F = daF_dt*0.84 + ...`），但这是 PPO 轨迹中前后端动作共线导致的**虚假相关**。部署时后端恒速旋转，`daB_dt = 0`，该项无贡献。**最终公式必须手动审查，去除此类假象项。**

### 4. Kármán cloak 的跨 Re_D 统一骨架存在

这是旧的事实，与上述联合公式结论一致。

### 5. 对称性问题已经纠偏

最重要的 bug 是镜像变换 G 对动作的写法错误。

**错误版本**：
\[
[a_F,a_T,a_B] \mapsto [-a_F,a_B,a_T]
\]

**正确版本**：
\[
[a_F,a_T,a_B] \mapsto [-a_F,-a_B,-a_T]
\]

修正后，rear equivariance 误差从约 100% 降到约 10%，原来"PPO 不尊重交换对称性"的结论应正式撤回。

### 6. front no-bias 被数据支持，rear shared-head 是有效结构

所有独立和联合公式均验证：前端公式无偏置项（零输入时输出接近零）。

### 7. one-step R² 与闭环是两回事

\[
\text{one-step R}^2 \gg 0.95 \not\Rightarrow \text{闭环好}
\]

**关键证据**：full-lag 16-dim 模型 R²=0.939 但闭环仅 0.619；static 8-dim 模型 R²=0.321 但闭环 0.745。

### 8. Illusion 0.75L 和 1L 的 PySR 公式已跑通（2026-06-23 新增）

| 场景 | Front 公式 | 闭环 | % of PPO |
|------|-----------|:----:|:--------:|
| 0.75L | `-0.169*(Cl_tot + dCl_tot_dt) - 1.240` | **0.979** | 100.7% |
| 1L | `(du_a_dt + u_a + 26.5)*0.0123` | **0.957** | 98.4% |

两公式结构不同（0.75L 用压升力、1L 用相位），说明**目标尺寸差异导致控制机制不同**。

### 9. Illusion 1.5L 是独立机制（2026-06-25 修正）

1.5L 之前被描述为"bang-bang（动作饱和）"，但实际分析后发现并非如此：
- 动作使用完整 [-1, 1] 范围但仅在极端值处饱和约 1% 的时间
- 控制是**高频率、高振幅的周期调制**，频率 f=0.24（控制空间），是目标涡脱频率的 5.6 倍
- 所有 10 维 ILLUSION_PHASE 特征与动作之间的线性相关性均 < 0.33
- 动作自相关 lag-2 ≈ -0.9，说明每两步切换一次方向
- 标准 PySR/SINDy 均不适用（已确认 PySR 给出 `aF_lag1 * 0.01` 的平凡解）

### 10. Illusion 联合公式已验证（2026-06-25 新增）

联合公式（0.75L + 1L 数据拼接，无 target_diameter 标记）：
- Front: `Cd_tot - (Cd_err + 5.428) - 0.00978*(du_a_dt + u_a)`，R2=0.907
- Top: `(Cd_err - (Cd_rear - Cl_err))*0.535 + 2.782`，R2=0.828
- CFD 闭环：**0.75L=0.978**，**1L=0.970**
- 联合公式能在两场景上都取得接近最优的效果，说明控制器通用性强

方法 A（target_diameter 标记法）确认：target_diameter **被最优公式使用**，说明两场景公式结构确实不同。

### 11. Karman 联合公式泛化到 Vortex 场景（2026-06-25 新增）

Karman 跨 Re 联合 PySR 公式直接应用于 vortex cloak 场景（未重新训练）：
- vortex_lamb（Lamb 偶极子）：CFD 闭环 **0.949**（超越 PPO 基线 0.942）
- vortex_taylor（Taylor 单极子）：CFD 闭环 **0.905**（接近 PPO 基线 0.916）
- 说明联合公式提取的物理反馈结构具有很强的通用性

### 12. Karman re400 最优控制间隔（2026-06-25 新增）

re400 的 PPO 效果较差（相似度 0.795），怀疑是采样间隔 SI=800 过长。
测试不同 SI 下联合公式的 CFD 闭环表现：
- SI=800: 0.806（基线）
- **SI=400: 0.819（最优）**
- SI=200: 0.794
结论：re400 最优控制间隔为 SI=400，但仍受限于 PPO 本身在高 Re 下的控制质量。

---

## 三、哪些结论现在还不能说得太满

### 1. "所有 cloak 已经共享同一骨架"
不能。Karman 和 Illusion 的公式结构不同，不能统一。

### 2. "Karman 跨 Re 联合公式已完全解决"
不能。后端为常数（`alpha_T = 3.414`），说明后端控制信息未被充分利用。且 re400 独立公式未做闭环验证。

### 3. "高 Re 退化已经证明是采样率问题"
这是一个强工作假设，需要在时间尺度显式化后重新检验。

### 4. "SR 已经做完一轮"
Karman 完整，Illusion 联合公式已验证 + 结构比较已完成，1.5L 已分析为不可 SR 的独立机制。

---

## 四、代码与工程层面的已知经验

### 1. 环境分工
- `pycuda_3_10`：CFD、DRL 模型加载、数据采集、SINDy（pysindy）、CFD 闭环验证
- `sr_env`：PySR 符号回归

### 2. 常见坑
- **actions.npz 是归一化动作 [-1,1]，不是物理 omega**。所有拟合代码需通过 `(norm * scale + bias) * u0` 转换。
- **Illusion "2U" 误解**："2U" 在模型名中表示 S_DIM=14（多了2维目标力观测），不是两倍来流速度。u0 始终为 0.01。
- **FIFO bias ≠ DRL action bias**：FIFO bias 用 1U `[0, -U0, U0]` 填充历史；DRL action decoder 用 2U `action*8+[0,-2,2]`。
- **SAMPLE_INTERVAL 因场景而异**：0.75L=400, 1L=600, 1.5L=800, Karman=800。
- **验证器 `n_steps` 不能过短**：需保证控制传播到传感器，S=400→320步, S=600→214步, S=800→160步。
- **保存 `controlled.npz` 时必须包含 `target_forces` 字段**（Illusion 场景的 s_dim=14 推理数据）。
- **单步 validator 中需要传入 `sensors_raw`/`forces_raw`** 以计算相位特征中的导数项。
- **PySR 搜索时必须传 `sensors_raw`/`forces_raw`** 给 `compute_features()`，否则所有 `du_a_dt`, `dCl_tot_dt` 等导数特征均为零。这是 run_pysr.py 的 CRITICAL bug（已修复）。
- **输出目标必须是 alpha 而非 omega**：`Y = actions_phys / u0`，否则 coefficients 受 u0 缩放。
- **联合公式中的 `daB_dt` 是训练分布假象**：PPO 轨迹中前后端动作共线导致虚假相关，部署时后端恒速该项为零，应手动移除。

### 3. 当前可信结果（2026-06-25 更新）

| 场景 | 路线 | 特征 | 输出 | 动作历史? | 闭环 sim | % of PPO |
|------|------|------|:----:|:---------:|:--------:|:--------:|
| illusion_0.75L | PySR | ILLUSION_PHASE (10dim) | alpha | 否 | **0.979** | **100.7%** |
| illusion_1L | PySR | ILLUSION_PHASE (10dim) | alpha | 否 | **0.957** | 98.4% |
| **illusion joint** | **PySR** | **ILLUSION_PHASE (10dim)** | **alpha** | **否** | **0.978/0.970** | — |
| illusion_1.5L | — | — | — | — | N/A | 高频周期调制 |
| **karman 联合** | **PySR** | **PHYS_DADT+mu (17dim)** | **alpha** | **daF_dt** | **re50=0.847, re100=0.888, re200=0.845, re400=0.806** | **统一公式** |
| karman_re50 | PySR | PHYS_DADT+mu | alpha | daF_dt | **0.895** | **153.8%** |
| karman_re100 | PySR | PHYS_DADT+mu | alpha | daF_dt | **0.888** | 98.6% |
| karman_re200 | PySR | PHYS_DADT+mu | alpha | daF_dt | **0.916** | **115.5%** |
| karman_re400 (SI=400) | PySR | PHYS_DADT+mu | alpha | daF_dt | **0.819** | 最优SI |
| **vortex_lamb** | **Karman joint** | — | alpha | daF_dt | **0.949** | **超越PPO(0.942)** |
| **vortex_taylor** | **Karman joint** | — | alpha | daF_dt | **0.905** | 接近PPO(0.916) |

---

## 五、Bug Audit 全部记录

### 2026-06-14~15 发现的 Bug

| # | Bug | 文件 | 严重度 | 修复 |
|---|-----|------|--------|------|
| 13 | `run_pysr.py` 滞后构造错误：`actions_phys` 直接当 `actions_prev` 传入 `compute_features`，导致 `aF_lag1 = alpha(t)` 而非 `alpha(t-1)` — 拟合恒等式 | sindy/run_pysr.py | CRITICAL | `a_prev[1:]=actions_phys[:-1]` |
| 14 | `predict_v23_deriv` 中 `needs_aug` 只检测 `"lag1"` 关键字，忽略 `"_dt"` 结尾的导数特征 → 闭环中 phase-state 的 `du_a_dt` 等始终为零 | validate/run_closed_loop.py | CRITICAL | `needs_aug = any(k.endswith("_dt") or k.endswith("_lag1") ...)` |
| 15 | `compute_features` 中 `target_forces` 未处理 1D 输入 | utils/feature_builder.py | 中 | `if tf.ndim == 1: tf = tf.reshape(1, -1)` |

### 2026-06-23 修复的关键 Bug

| # | Bug | 文件 | 严重度 | 修复 |
|---|-----|------|--------|------|
| **16** | **`run_pysr.py` 调用 `compute_features()` 时未传 `sensors_raw`/`forces_raw`**，导致所有 `du_a_dt`, `dCl_tot_dt`, `dCd_err_dt`, `dCl_err_dt` 等相位导数特征在 `sym` 字典中缺失 → `build_feature_matrix()` 用全零填充 → PySR 用零向量拟合真实动作 → 公式必然古怪 | **sindy/run_pysr.py** | **CRITICAL** | 在 `compute_features()` 调用中增加 `sensors_raw=sensors, forces_raw=forces` |

### 关键方法教训

1. **PPO 验证优先于 SINDy**。在确认 PPO 推理复现正确之前，SINDy 结果无意义。
2. **控制时长必须 >= NX/U0**。50 步验证结果不可靠。
3. **DDF 保存位置决定验证器起始状态**。正确模式：`stabilize → save_ddf(1) → norm → apply_ddf → bias FIFO → save_ddf(2) → apply_ddf`。
4. **状态消融比输出消融更重要**：`x_n` 不够 → 加 `x_{n-1}` 大幅提升 → 加 `a_{n-1}` 边际仅 3% → 说明缺的是相位状态，不是动作历史。
5. **离线 rollout 好 ≠ CFD 闭环好**。最终判据是 CFD 短闭环。
6. **联合公式最终必须人工审查**：移除训练分布中的假象项（如 `daB_dt` 在后端恒速时恒为零）。
7. **PySR 输出的最佳公式不一定合适**：因为 SR 只优化 loss，不考虑部署分布偏移。检查 Pareto 前沿，理解每个项在闭环中是否真正有贡献。
