# FP16 与 ddf_shifting 兼容性指导

## 目标

这份文档用于指导下一阶段的兼容性工作，范围限定为：

- `store_precision = FP16S`
- `ddf_shifting = true`
- 与以下能力的兼容性
  - `double_buffer`
  - `esopull`
  - curved boundary
  - 新 body 模块
  - `Kan99b_validation`
  - `Sah04_validation`

这里的核心目标不是“多跑一些测试”，而是先把 **真正有代码级风险的点** 查清楚，再为这些点设计最少但有力的验证。

---

## 总体判断

## 1. FP16S 和 ddf_shifting 不是同一类问题

这两个开关虽然都作用在 DDF 存储层，但风险性质不同。

| 项目 | 风险类型 | 当前判断 |
|---|---|---|
| `FP16S` | 精度风险 | 低到中等，可直接进入 benchmark 验证 |
| `ddf_shifting` | 语义风险 | 中到高，先修高风险代码，再做 benchmark |

更直接地说：

- **FP16S 主要是“误差会不会把结果推偏”的问题**
- **`ddf_shifting` 主要是“现有公式是否仍然在算同一个物理量”的问题**

因此两者不应混在一起推进。

---

## 第一部分：FP16S

## 当前代码结构判断

FP16S 当前结构的优点很明确：

- 所有 DDF 存取都集中在 `load_ddf()` / `store_ddf()`
- 所有数值计算都在 `float` 下完成
- EsoPull helper、macro kernel、sensor、force region、collision 都读入 `float` 后再算
- body 模块的 cut-link、action、obs、geometry 都仍然是 `float32`

这意味着 FP16S 的主要影响路径是：

- DDF 被量化到 half + scale
- 再由 `load_ddf()` 恢复到 `float`
- 后续计算不再额外降精度

因此 FP16S 的主要风险不是语义错，而是：

- 量化误差是否在长期统计量中积累
- curved 邻域和力学统计是否对量化更敏感

## 当前最合理的策略

FP16S 不需要先大规模改代码。当前更合理的是：

- 直接进入最小 benchmark 验证
- 用少量高价值算例判断是否达到可用精度

## FP16S 最小验证集

建议只做三项：

| 编号 | 用例 | 作用 |
|---|---|---|
| F1 | `Kan99b K2` | rotating cylinder 主锚点，覆盖 curved + body force/torque |
| F2 | `Sah04 S2` | 中等 blockage，覆盖 channel + inlet/outlet + curved |
| F3 | `Sah04 S4` | 高 blockage 敏感 case，放大 wall / curved / 精度误差 |

如果计算预算紧，可以先跑：

1. `K2`
2. `S2`
3. `S4`

这三项站住，FP16S 基本就可视为进入正式可用状态。

## FP16S 主要观测量

### Kan99b K2

- `St`
- `mean Cd`
- `mean Cl`
- `C'_L`
- `C'_D`
- torque 趋势
- 最终涡量图

### Sah04 S2 / S4

- `St`
- `Re_real`
- 周期性
- 最终涡量图
- 宏观场是否有明显假结构

## FP16S 评估方式

不建议用“逐点场几乎完全相同”作为判据。更合理的是：

- benchmark 统计量是否稳定
- 流动结构是否保持
- 是否出现明显非物理噪声放大

## FP16S 通过标准建议

沿用现有 benchmark 文档标准即可，不需要另立一套更紧的标准。

若出现轻微偏差，优先看：

- 偏差是否主要体现在力振幅而不是频率
- 是否只在高 blockage 或强近壁 case 放大
- 是否存在长期漂移而不是瞬时差异

## FP16S 失败时优先排查

| 现象 | 优先怀疑 |
|---|---|
| `St` 稳定但振幅偏差大 | curved 邻域对量化敏感 |
| `rho` 漂移明显 | 精度不足导致守恒误差积累 |
| 高 blockage case 失真、低 blockage 正常 | wall-gap 分辨率与量化共同放大误差 |
| 只有 EsoPull 差、double 正常 | single-buffer 路径对量化更敏感 |

---

## 第二部分：ddf_shifting

## 当前代码结构判断

`ddf_shifting` 不是“已跑通但没验证”的状态，而是“已有若干明确高风险点”的状态。

从当前代码看，至少有四类问题必须先处理。

---

## 风险点 S1：inlet rho closure 不兼容 shifting

### 位置

- `boundary/inlet/common.cuh`
- `west_velocity_rho_closure_d2q9`
- `west_velocity_rho_closure_d3q19`

### 问题本质

这些 closure 直接对 `f[]` 求和并做速度闭合。

但在 shifting 模式下，存储变量是：

\[
\tilde f_i = f_i - w_i
\]

因此原来对 `f_i` 成立的 closure，不能直接对 `\tilde f_i` 使用。

### 影响范围

所有依赖 west closure 的 inlet 都会受影响：

- `zou_he_local`
- `channel_stabilized`
- `regularized`

### 结论

这不是 benchmark 才能发现的小问题，而是**公式语义已变**。必须先修。

---

## 风险点 S2：curved force/torque 统计不兼容 shifting

### 位置

- `curved_boundary.cuh`
- `PrepareEsoPullCurvedKernel` 中的 obs 累积
- `CurvedBoundaryKernel` 中的 obs 累积

### 问题本质

当前动量交换统计直接使用：

- `f_toward + f_reflected`

如果这两个量是 shifted population，那么它们包含了权重偏移项，不能再直接当成原始物理分布和来做 force 统计。

### 影响范围

这会直接污染：

- force
- torque
- `Cd`
- `Cl`
- 任何依赖 body obs 的 benchmark 输出

### 结论

只要 `ddf_shifting` 打开，当前 curved force/torque 统计不能默认可信。

---

## 风险点 S3：host upload/download 路径不对称

### 位置

- `field.download_ddf()`
- `field.upload_ddf()`

### 当前状态

- `download_ddf()` 在 shifting 模式下会加回 `w_i`
- `upload_ddf()` 不会在上传前减去 `w_i`

### 影响范围

所有 host 侧修改 DDF 再上传的路径都可能错：

- `initializers.add_vortex()`
- `snapshot()/restore()`
- 任何直接操作 `field.ddf` 的调试脚本

### 结论

当前 host DDF 修改路径与 shifting 语义不对称，必须：

- 要么修正
- 要么显式禁止使用

---

## 风险点 S4：checkpoint 对 streaming 不做匹配检查

### 位置

- `common/checkpoint.py`

### 当前状态

当前 checkpoint load 检查：

- lattice
- grid
- store precision

但不检查：

- `streaming`
- `ddf_shifting`

### 影响范围

可能出现：

- double-buffer checkpoint 被加载到 esopull
- unshifted checkpoint 被加载到 shifted 配置
- 反过来亦然

### 结论

只要 storage semantics 不同，checkpoint 就不应默认兼容。

---

## ddf_shifting 的正确推进顺序

## Step S-A：先修代码级高风险点

在任何 benchmark 之前，建议优先处理以下事项：

1. **修 inlet closure**
   - 让 `west_velocity_rho_closure_*` 对 shifting 成立
2. **修 curved force/torque 统计**
   - 统计时使用物理 population，而不是 shifted storage 量
3. **处理 host upload/download 对称性**
   - 修 `upload_ddf()`
   - 或对 shifting 下 host-DDF-edit 路径显式加 guard
4. **补 checkpoint 配置检查**
   - 至少检查 `streaming`
   - 最好同时检查 `ddf_shifting`

### 为什么必须先修

因为这些不是“跑几个 benchmark 看看”的问题，而是：

- 不修的话，benchmark 结果没有明确物理解释
- 即使数值接近，也可能只是错误抵消

---

## Step S-B：再做最小 sanity 验证

在代码修完之后，先不要直接上长跑 benchmark，先做最小 sanity：

| 编号 | 用例 | 目标 |
|---|---|---|
| S5 | channel init sanity | 确认 shifting 下宏观场与初始条件一致 |
| S6 | inlet closure local check | 确认 west closure 不再系统偏移 |
| S7 | curved force local check | 确认 force/torque 不因 shift 常数偏置 |

这部分不需要设计得很复杂，重点是回答：

- inlet 闭合是否恢复物理意义
- curved force 是否恢复物理意义

---

## Step S-C：再做 benchmark

修完并过了 sanity 之后，再做最小 benchmark：

| 编号 | 用例 | 作用 |
|---|---|---|
| S8 | `Kan99b K2` | 主锚点，最关键 |
| S9 | `Sah04 S2` | 中等 blockage channel 验证 |
| S10 | `Sah04 S4` | 高 blockage 敏感验证 |

这套最小集与 FP16S 相同，便于复用现有脚本和比较口径。

---

## 第三部分：新 body 模块应该怎么理解

## 不要把“body 兼容性”当成一个单独的大问题

新 body 模块本身并不是当前主要风险源。因为它主要负责：

- geometry
- flag overlay
- compact list packing
- action/obs buffer 管理

这些层本身不直接定义 DDF 物理语义。

真正要关注的是，body 模块接入后，哪些 kernel 会读或写 DDF：

- `PrepareEsoPullCurvedKernel`
- `CurvedBoundaryKernel`
- `SensorKernel`
- `ForceRegionKernel`
- `MacroscopicEsoPullKernel`

### 其中需要区分风险等级

| 路径 | FP16 风险 | shifting 风险 |
|---|---|---|
| `MacroscopicEsoPullKernel` | 低 | 低到中等 |
| `SensorKernel` | 低 | 低到中等 |
| `ForceRegionKernel` | 低到中等 | 中等 |
| curved force/torque obs | 中等 | **高** |
| inlet / outlet related source states | 中等 | **高** |

### 结论

新 body 模块不需要另起一套大而全的验证设计。更有效的做法是：

- 在 FP16S / shifting 验证时，自动把 body 的核心使用路径覆盖掉
- 重点盯住 force、torque、sensor、curved、macro 这些 DDF 消费点

---

## 第四部分：推荐推进顺序

## 当前最推荐顺序

1. **先更新文档和注释**
2. **先做 FP16S 最小 benchmark 验证**
3. **再修 ddf_shifting 高风险代码**
4. **修完后做 shifting sanity**
5. **最后做 shifting benchmark**

不要反过来。

### 原因

- FP16S 现在已经具备直接验证条件
- shifting 现在还不具备可信 benchmark 条件

---

## 第五部分：每一步如何评估

## FP16S 评估口径

| 类型 | 关注点 | 判读方式 |
|---|---|---|
| K2 | `St`, `Cd`, `Cl`, amplitude | 是否保持 benchmark 可用精度 |
| S2 | `St`, `Re_real` | 中等 channel case 是否稳定 |
| S4 | `St`, vorticity | 高敏感 case 是否放大量化误差 |

## shifting 评估口径

| 类型 | 关注点 | 判读方式 |
|---|---|---|
| sanity | inlet / force / init 是否语义正确 | 是否恢复清楚的物理解释 |
| K2 | 力学统计量 | 是否与 unshifted 路径一致且可解释 |
| S2/S4 | channel / blockage 行为 | 是否出现边界条件系统偏差 |

---

## 第六部分：执行摘要

## 对 coder 的直接指导

### 先做什么

先做：

- 文档和注释更新
- FP16S 的 `K2 + S2 + S4`

### 暂时不要直接做什么

暂时不要直接把 `ddf_shifting` 扔进长跑 benchmark，然后凭结果判断“看起来能不能用”。

### ddf_shifting 先修什么

先修四个点：

1. inlet rho closure
2. curved force/torque 统计
3. host upload/download 对称性或 guard
4. checkpoint semantic match check

### 修完再怎么测

先做 sanity：

- init
- inlet
- curved force

再做：

- `K2`
- `S2`
- `S4`

---

## 最终一句话结论

**FP16S 现在主要是精度验证问题；`ddf_shifting` 现在首先是代码语义一致性问题。**

因此：

- **FP16S 可以直接用少量 benchmark 判可用性**
- **`ddf_shifting` 必须先修明确高风险代码，再做 benchmark 才有意义**
