feat(body): add force_region, fix sensor API, reorganize docs
- Add force_region object type: local Guo forcing via sparse compact list
- ForceRegionSoA container, ForceRegionKernel, stepper dispatch
- add_body("force_region", ...) + set_force(id, fx, fy) API
- Fix read_sensor(normalize=...) not being passed from Simulation layer
- Fix force_region incorrectly entering curved cut-link path (P0 blocker)
- Clean up module boundaries: body/__init__ no longer imports from lbm
- Circluar import fix: common/streakline <-> pathline
- Package data globs fixed for recursive kernel files
- Version unified to 0.3.0
- Performance analysis: pycuda launch overhead vs GPU compute at various grid sizes
- Nsight Systems + Nsight Compute profiling data and report
- Documentation reorganized under docs/ (audit, validation_specs)
- README overhaul: multi-body examples, validated benchmarks, force_region docs
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
2e052480c2
commit
b110591433
16 files changed
+2076
-18
No files matched your search
@@ -0,0 +1,489 @@
|
||||
# Force region 最小方案设计
|
||||
|
||||
目标是在现有功能冻结的前提下,为项目增加一个**局部体力施加区域**,用于研究局部驱动与流场结构之间的关系。该功能的第一阶段只追求可用、低风险、低侵入,不追求最泛化的抽象,也不与 sensor 合并。
|
||||
|
||||
当前代码已经具备三条很明确的数据链:全场 `OneStep` 推进、稀疏 `curved` link 链、稀疏 `sensor` cell 链。`force_region` 更接近第三类:它是一个静态几何 footprint,加上运行时 action,而不是一个需要改变主域 flag 的边界对象。因此,第一阶段更适合沿用 `sensor` 风格的稀疏 cell 管理,而不是引入全场 owner map。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
| 类别 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 在圆形区域内对流体施加局部体力 |
|
||||
| 目标 | 运行时更新区域体力参数,不重新编译 |
|
||||
| 目标 | 尽量少改现有主热路径和已有稳定功能 |
|
||||
| 非目标 | 与 sensor 统一成单一抽象 |
|
||||
| 非目标 | 非圆形几何 |
|
||||
| 非目标 | 3D |
|
||||
| 非目标 | 多区域重叠叠加规则的完整设计 |
|
||||
| 非目标 | 数值上最严格的“逐格 collision 内联 forcing”实现 |
|
||||
|
||||
## 需求边界
|
||||
|
||||
第一阶段建议只支持以下能力:
|
||||
|
||||
- `force_region` 是一个圆形 footprint
|
||||
- 区域内部施加均匀体力 `fx, fy`
|
||||
- 区域几何在初始化后不变
|
||||
- 体力大小可在运行时更新
|
||||
- 只对 fluid cell 生效
|
||||
- 不要求读取区域平均速度
|
||||
- 不要求与 `sensor` 或 `body` 的接口完全统一
|
||||
|
||||
这意味着第一阶段的问题不是“建立通用 actuator 体系”,而是“在现有框架内插入一条最小局部 forcing 数据链”。
|
||||
|
||||
## 现有代码中与该功能直接相关的结构
|
||||
|
||||
| 模块 | 当前作用 | 与 force region 的关系 |
|
||||
|---|---|---|
|
||||
| `body/geometry/circle` | 生成圆形 footprint | 可复用 |
|
||||
| `body/manager` | 汇总 compact list 并上传 GPU | 适合新增 force-region list |
|
||||
| `lbm/curved_links` | 稀疏 SoA 容器 | 可参考 `SensorSoA` 形式扩展 |
|
||||
| `lbm/kernels/step/aux_kernels` | 稀疏辅助 kernel | 适合新增 `ForceRegionKernel` |
|
||||
| `lbm/stepper` | 编排主 step 与辅助 kernel | 适合插入 force-region launch |
|
||||
| `operators/helpers` | 现有全局 Guo forcing 路径 | 可复用公式与语义 |
|
||||
|
||||
需要特别注意的一点是:现有全局体力通过 `d_params.fx/fy/fz` 进入 `collide_dispatch()`。这条路径服务的是**全场统一 forcing**。局部体力如果要完全沿用同一语义,最严格的做法是让每个 cell 在线程内部读取本地 force。但这会推动设计进入全场 owner map 或 per-cell force field,而这不符合第一阶段的低侵入目标。
|
||||
|
||||
## 推荐的第一阶段总路线
|
||||
|
||||
推荐路线是:
|
||||
|
||||
- `force_region` 作为一种新对象类型存在
|
||||
- 它不修改 domain flag
|
||||
- 它只产出稀疏 cell footprint
|
||||
- 由一个独立的 `ForceRegionKernel` 在主 step 外对这些 cell 做局部 forcing 修正
|
||||
|
||||
换句话说,第一阶段采用的是:
|
||||
|
||||
- **主推进仍保持全场统一**
|
||||
- **局部 forcing 通过稀疏后处理 kernel 注入**
|
||||
|
||||
这不是最终最泛化的架构,但它与现有 `sensor` 管理方式一致,工作量低,也最不容易破坏当前稳定基线。
|
||||
|
||||
## 为什么不优先采用全场 owner map
|
||||
|
||||
全场 owner map 的优点是语义干净:`OneStep` 的每个 cell 都可直接知道自己是否属于某个区域,再把局部体力并入 collision forcing。这在数值上更直接,也更容易扩展到复杂局部 forcing。
|
||||
|
||||
但在当前阶段,这条路的代价偏高:
|
||||
|
||||
- 需要给全场新增一个 device-side 标识数组
|
||||
- 需要改主 step kernel 形参和热路径逻辑
|
||||
- 需要明确多区域覆盖规则
|
||||
- 需要同时处理 double-buffer 与 esopull 路径中的局部 forcing 接入方式
|
||||
|
||||
对当前目标来说,这会把“加一个可用功能”变成“改造主推进语义”。因此,第一阶段更合适的路线仍是稀疏 compact list。
|
||||
|
||||
## 为什么不把 force region 直接并入 sensor
|
||||
|
||||
二者的几何 footprint 确实相似,都可以表示成“圆形覆盖的一批 cell”。但功能角色不同:
|
||||
|
||||
| 类型 | 本质 | 时序位置 |
|
||||
|---|---|---|
|
||||
| `sensor` | 读出局部信息 | step 后 |
|
||||
| `force_region` | 修改 DDF 演化 | step 中或 step 后的局部修正 |
|
||||
|
||||
因此,第一阶段不建议在抽象层面合并二者。更合理的做法是:
|
||||
|
||||
- 复用几何 footprint 生成逻辑
|
||||
- 保持独立对象语义
|
||||
- 保持独立 kernel 与独立 compact list
|
||||
|
||||
这样既能少写代码,也不会把接口语义搅乱。
|
||||
|
||||
## 对象层建议
|
||||
|
||||
`force_region` 建议作为一种新的 body-side 对象类型,而不是把普通刚体、sensor、局部 forcing 区域全部塞进同一语义分支。
|
||||
|
||||
### 推荐最低要求
|
||||
|
||||
| 字段 | 作用 |
|
||||
|---|---|
|
||||
| `obj_id` | 索引 action 与 compact list |
|
||||
| `geometry` | 圆形 footprint 来源 |
|
||||
| `state` | 可沿用通用容器,但第一阶段基本不用 |
|
||||
| `control` 或 action slot | 存 `fx, fy` |
|
||||
| `type tag` | 区分普通 body、sensor、force_region |
|
||||
|
||||
### 不必在第一阶段敲死的点
|
||||
|
||||
下面这些都可以留给 coder 选择:
|
||||
|
||||
- 是加 `is_force_region` 布尔字段,还是引入更清楚的 `role` / `kind` 标签
|
||||
- `fx, fy` 放在 `state`、`control`,还是直接只存在 `action` 打包层
|
||||
- `add_body("force_region", ...)` 还是单独加 `add_force_region(...)`
|
||||
|
||||
第一阶段更重要的是把数据链接起来,而不是先做完美 API。
|
||||
|
||||
## 几何层建议
|
||||
|
||||
`body/geometry/circle` 已经有生成 sensor cells 的逻辑。这里最自然的做法不是复制一份,而是把“圆形 footprint cell 枚举”抽成一个中性函数。
|
||||
|
||||
### 推荐抽象
|
||||
|
||||
建议在 circle geometry 中形成一个中性能力,例如:
|
||||
|
||||
- `build_footprint_cells()`
|
||||
|
||||
然后:
|
||||
|
||||
- `sensor` 使用这批 cells 作为采样点
|
||||
- `force_region` 使用同一批 cells 作为施力点
|
||||
|
||||
### 第一阶段不必决定的点
|
||||
|
||||
- footprint 是否严格包含圆内格点中心
|
||||
- 是否允许边界一圈采用不同准则
|
||||
- 是否为将来的 polygon/mesh 提前抽公共父类 helper
|
||||
|
||||
只要 footprint 定义在 host 和测试中保持一致即可。
|
||||
|
||||
## flag 处理建议
|
||||
|
||||
`force_region` 第一阶段**不应修改 flag field**。
|
||||
|
||||
原因很直接:
|
||||
|
||||
- 它不是 solid
|
||||
- 它不是 boundary condition
|
||||
- 它不是 sensor flag
|
||||
- 它不参与 channel topology 构造
|
||||
|
||||
因此它应当是“有 footprint,但不 overlay flag”的对象。
|
||||
|
||||
这意味着在 `build_flags()` 过程中:
|
||||
|
||||
- 普通 body 继续覆盖 obstacle / curved 标记
|
||||
- sensor 继续覆盖 sensor 标记
|
||||
- force_region 返回零 mask,或根本不进入该分支
|
||||
|
||||
两种实现都可以,留给 coder 决定。若追求语义更清楚,倾向于根本不让它参与 flag merge。
|
||||
|
||||
## compact list 设计建议
|
||||
|
||||
第一阶段建议新增一条与 sensor 平行的数据链:
|
||||
|
||||
- `force_region cells`
|
||||
- `force_region obj_id`
|
||||
|
||||
### 数据最小集
|
||||
|
||||
| 列 | 类型 | 作用 |
|
||||
|---|---|---|
|
||||
| `cells` | `uint32` | 被施力格点索引 |
|
||||
| `obj_id` | `int32` | 从 action 中取 `fx, fy` |
|
||||
|
||||
这与 `SensorSoA` 的结构完全同型,只是消费 kernel 不同。
|
||||
|
||||
### 可选实现
|
||||
|
||||
| 方案 | 特点 |
|
||||
|---|---|
|
||||
| 单独建 `ForceRegionSoA` | 语义清楚,后续好扩展 |
|
||||
| 复用 `SensorSoA` 结构实现新字段 | 改动少,但语义稍混 |
|
||||
|
||||
第一阶段更推荐单独建 `ForceRegionSoA`。这会多一点样板代码,但可读性更好,也避免后续 `sensor` 和 `force_region` 在 field 中混淆。
|
||||
|
||||
## action contract 建议
|
||||
|
||||
当前 `action` buffer 在 2D 下每对象为 `3 * dim = 6` 个 `float`,而现阶段只真正使用了最后一个 `omega`。这给了第一阶段一个很好的低成本入口:
|
||||
|
||||
- 不改 action buffer 总长度
|
||||
- 不改编译期 layout 宏
|
||||
- 先在已有 slot 中约定 `fx, fy` 的位置
|
||||
|
||||
### 一个可行的最小约定
|
||||
|
||||
| slot | 含义 |
|
||||
|---|---|
|
||||
| `0` | `fx` |
|
||||
| `1` | `fy` |
|
||||
| `5` | `omega` |
|
||||
|
||||
其余位置保留。
|
||||
|
||||
### 这里不必现在敲死
|
||||
|
||||
这部分可以留给 coder 做最终选择,只要满足两点:
|
||||
|
||||
- `force_region` kernel 能稳定读到 `fx, fy`
|
||||
- 现有 `CurvedBoundaryKernel` 继续稳定读到 `omega`
|
||||
|
||||
若 coder 认为显式 helper 更重要,可以新增类似 `action_force_x()` / `action_force_y()` 的 device helper,而不是在 kernel 中直接写魔数偏移。
|
||||
|
||||
## kernel 路线建议
|
||||
|
||||
第一阶段最合适的是新增一个独立 kernel,例如:
|
||||
|
||||
- `ForceRegionKernel`
|
||||
|
||||
它应当像 `SensorKernel` 一样按 compact list 启动,而不是并入 `OneStep`。
|
||||
|
||||
### 建议职责
|
||||
|
||||
每个线程处理一个 force-region cell:
|
||||
|
||||
1. 读该 cell 的线性索引 `k`
|
||||
2. 读所属对象 `obj_id`
|
||||
3. 从 `action` 中读 `fx, fy`
|
||||
4. 从当前 DDF 缓冲中加载该 cell 的 `f[NQ]`
|
||||
5. 计算 `rho, ux, uy`
|
||||
6. 按现有 Guo forcing 语义构造 `Fin`
|
||||
7. 对该 cell 的分布函数做 forcing 修正
|
||||
8. 写回 DDF
|
||||
|
||||
### 需要特别控制的事
|
||||
|
||||
第一阶段最好不要在这个 kernel 内部引入过多策略分支。尤其避免一开始就支持:
|
||||
|
||||
- 多个不同 forcing scheme
|
||||
- 复杂边界跳过逻辑
|
||||
- 非均匀空间 force
|
||||
- 区域内按距离衰减的 force
|
||||
|
||||
先只做“圆内均匀体力”。
|
||||
|
||||
## ForceRegionKernel 应该写在哪个时间位置
|
||||
|
||||
现有 `stepper` 顺序大致为:
|
||||
|
||||
1. curved boundary
|
||||
2. main step
|
||||
3. sensor
|
||||
|
||||
第一阶段建议将其改为:
|
||||
|
||||
1. curved boundary
|
||||
2. main step
|
||||
3. force region
|
||||
4. sensor
|
||||
|
||||
### 这样做的原因
|
||||
|
||||
- 主推进链尽量不动
|
||||
- force region 作为局部修正子步存在
|
||||
- sensor 若未来同时存在,可以读到施力后的局部结果
|
||||
|
||||
### 这里保留给 coder 的决策点
|
||||
|
||||
`ForceRegionKernel` 也可以放在 sensor 前后不同位置,但需要统一解释语义:
|
||||
|
||||
| 放置位置 | 语义 |
|
||||
|---|---|
|
||||
| 主 step 之后、sensor 之前 | sensor 读到施力后的状态 |
|
||||
| sensor 之后 | sensor 读到施力前主步结果 |
|
||||
|
||||
若第一阶段不做 sensor 联动,则这只是将来兼容性的选择。默认更建议放在 sensor 之前。
|
||||
|
||||
## DDF 更新方式的设计空间
|
||||
|
||||
这里是实现中最需要留白的部分。第一阶段文档不应把写法锁死,但应给出边界。
|
||||
|
||||
### 必须满足的原则
|
||||
|
||||
- 局部体力的数值语义尽量对齐现有 `forcing_guo.cuh`
|
||||
- 不应在 `ForceRegionKernel` 中引入与现有 collision family 矛盾的私有公式
|
||||
- 若采用 operator splitting,应在注释中明确说明它不是“主 collision 内联 forcing”
|
||||
|
||||
### 可接受的实现方向
|
||||
|
||||
| 方向 | 特点 |
|
||||
|---|---|
|
||||
| 直接按 Guo forcing 增量对 `f[i]` 做修正 | 最小、直观 |
|
||||
| 抽一个独立 helper,复用 `compute_guo_forcing()` | 与现有 forcing 公式最一致 |
|
||||
| 在 kernel 中根据 collision family 调整 prefactor | 更接近现有 `collide_*` 写法,但代码稍重 |
|
||||
|
||||
### 第一阶段更适合的判断
|
||||
|
||||
更推荐:
|
||||
|
||||
- 尽量复用 `compute_guo_forcing()`
|
||||
- 用很薄的一层 helper 完成 DDF 增量写回
|
||||
|
||||
但是否把 `c_tau = 1 - omega/2` 放在 kernel 里、helper 里,还是借新函数收口,可以留给 coder。
|
||||
|
||||
## 数值语义说明
|
||||
|
||||
第一阶段方案本质上是一个**局部 forcing 修正子步**,而不是严格在 `OneStep` 内逐格并入 collision forcing。这一点需要在实现处用注释写清楚。
|
||||
|
||||
### 这个选择的代价
|
||||
|
||||
- 语义上更像 operator splitting
|
||||
- 与全局 forcing 在热路径中的时序并不完全相同
|
||||
- 在强 forcing 情况下,可能与将来更严格实现有差异
|
||||
|
||||
### 这个选择的收益
|
||||
|
||||
- 主 kernel 改动极小
|
||||
- 不需要全场 owner map
|
||||
- 易于复用现有稀疏对象链路
|
||||
- 第一阶段很快能得到可用结果
|
||||
|
||||
对当前“先追求使用”的目标,这个 tradeoff 是合理的。
|
||||
|
||||
## 过滤规则建议
|
||||
|
||||
第一阶段应当保持规则简单。
|
||||
|
||||
### 建议规则
|
||||
|
||||
- 只对 `is_fluid(fl)` 的格点施力
|
||||
- 对 obstacle / wall / inlet / outlet 一律跳过
|
||||
- 与 curved body footprint 重叠时,由 cell flag 决定是否生效,而不是 force-region 自己猜
|
||||
|
||||
### 这里不必先完善的点
|
||||
|
||||
- actuator 与 actuator 的重叠叠加规则
|
||||
- 对 inlet/outlet 邻近区域的特殊处理
|
||||
- 对 sensor / force_region 同 id 绑定的逻辑
|
||||
|
||||
如果第一阶段允许多个 force region 存在,最简单的策略是先约束:
|
||||
|
||||
- footprint 不应重叠
|
||||
|
||||
或者:
|
||||
|
||||
- 重叠行为未定义,由上层调用者避免
|
||||
|
||||
## 配置与 API 建议
|
||||
|
||||
这里也不需要一次性定死。
|
||||
|
||||
### 最小外部接口至少应支持
|
||||
|
||||
- 新建一个圆形 `force_region`
|
||||
- 运行时更新它的 `fx, fy`
|
||||
|
||||
### 可选 API 形式
|
||||
|
||||
| 形式 | 特点 |
|
||||
|---|---|
|
||||
| `add_body("force_region", ...)` | 改动少,沿用现有入口 |
|
||||
| `add_force_region(...)` | 语义更清楚 |
|
||||
|
||||
### 运行时控制也有两种自然路线
|
||||
|
||||
| 形式 | 特点 |
|
||||
|---|---|
|
||||
| 扩展 `set_body(id, fx=..., fy=...)` | 改动小 |
|
||||
| 新增 `set_force_region(id, fx=..., fy=...)` | 语义清楚 |
|
||||
|
||||
第一阶段若追求少改代码,可优先沿用现有 `set_body()`。若 coder 认为 API 语义更重要,也可以新增专门入口。
|
||||
|
||||
## 对 `ObjectManager` 的建议
|
||||
|
||||
`ObjectManager` 已经偏大,因此第一阶段不建议再把很多新判断揉成一团。更合适的方式是:
|
||||
|
||||
- 在现有结构上增加一条最薄的新分支
|
||||
- 尽量与 `sensor` 的组织方式平行
|
||||
- 不在 `build_flags()` 中塞 force-region 的特例
|
||||
|
||||
### 在实现层面建议保持的分工
|
||||
|
||||
| 职责 | 位置 |
|
||||
|---|---|
|
||||
| 圆形 footprint 枚举 | `geometry` |
|
||||
| force-region SoA 打包 | `body/coupling` 或与其平行的位置 |
|
||||
| GPU buffer 生命周期 | `field` / SoA 容器 |
|
||||
| kernel 调度 | `stepper` |
|
||||
|
||||
这能避免 `manager` 再次回到“万能管理器”的方向。
|
||||
|
||||
## 对测试的建议
|
||||
|
||||
第一阶段至少需要三类测试。
|
||||
|
||||
### 1. 结构测试
|
||||
|
||||
- `force_region` 不改变 flag field
|
||||
- compact list cell 数与 footprint 预期一致
|
||||
- action 更新后 GPU buffer 内容可追踪
|
||||
|
||||
### 2. 数值冒烟测试
|
||||
|
||||
- 静止流场中施加恒定 `fx` 后,区域内流体沿 x 方向加速
|
||||
- `fx > 0` 与 `fx < 0` 响应方向相反
|
||||
- `fy` 路径同理
|
||||
|
||||
### 3. 回归保护
|
||||
|
||||
- 未添加 `force_region` 时,结果与当前版本一致
|
||||
- 添加 `force_region` 但 `fx=fy=0` 时,结果与当前版本一致或只存在舍入级差异
|
||||
|
||||
### 暂不要求的测试
|
||||
|
||||
- 与解析解严格比对
|
||||
- 多 region 重叠叠加
|
||||
- 强 forcing 的稳定性边界扫描
|
||||
|
||||
## 第一阶段完成后的理想状态
|
||||
|
||||
完成后,项目应具备以下能力:
|
||||
|
||||
- 能创建一个圆形局部施力区域
|
||||
- 能在运行时改变其 `fx, fy`
|
||||
- 不需要重新编译
|
||||
- 不需要引入全场 owner map
|
||||
- 不破坏现有 curved boundary 与 sensor 链路
|
||||
- 不迫使 `body` 与 `lbm` 做新的深度耦合
|
||||
|
||||
从架构角度看,第一阶段的关键成果不是“得到最完美的 actuator 抽象”,而是验证:
|
||||
|
||||
- 稀疏 cell list 路线足以支撑局部 forcing 研究
|
||||
- 现有系统能容纳一条新的 compact-list kernel 链路
|
||||
|
||||
## 第二阶段可能的演进方向
|
||||
|
||||
这些不属于当前实现范围,但第一阶段设计应避免把它们堵死:
|
||||
|
||||
- 读取 force-region 内平均速度或其他观测量
|
||||
- 将 `force_region` 与 `sensor` 的 footprint builder 进一步共用
|
||||
- 升级为更一般的 `region operator`
|
||||
- 改为在 `OneStep` 内逐格读取局部 force
|
||||
- 支持非圆形几何与 3D
|
||||
|
||||
第一阶段不需要为这些能力写完结构,只需要不要在命名和数据布局上把后路堵死。
|
||||
|
||||
## 建议的实现顺序
|
||||
|
||||
### 阶段 1
|
||||
|
||||
建立最小对象与 footprint 数据链。
|
||||
|
||||
- 新增 `force_region` 类型
|
||||
- 让 circle geometry 能产出中性 footprint cells
|
||||
- 新增 force-region SoA 与 GPU upload
|
||||
|
||||
### 阶段 2
|
||||
|
||||
接入运行时 action。
|
||||
|
||||
- 在 action slot 中为 `fx, fy` 留位置
|
||||
- 增加 host 侧更新接口
|
||||
- 保证不影响现有 `omega` 读取
|
||||
|
||||
### 阶段 3
|
||||
|
||||
新增局部 forcing kernel 并接入调度。
|
||||
|
||||
- 写 `ForceRegionKernel`
|
||||
- 接入 `stepper`
|
||||
- 明确与 sensor 的先后顺序
|
||||
|
||||
### 阶段 4
|
||||
|
||||
补最小测试与文档注释。
|
||||
|
||||
- 结构测试
|
||||
- 冒烟测试
|
||||
- 未启用时的回归保护
|
||||
|
||||
## 最终判断
|
||||
|
||||
第一阶段最稳妥的最小方案不是全场 owner map,也不是把 force-region 并进 sensor,而是:
|
||||
|
||||
- 把它当作一种新的稀疏 cell-list 对象
|
||||
- 复用圆形 footprint 生成逻辑
|
||||
- 通过独立 `ForceRegionKernel` 对局部 DDF 做 forcing 修正
|
||||
- 在接口和数据布局上只做最低必要改动
|
||||
|
||||
这条路线与现有代码风格最一致,也为后续更严格或更泛化的实现保留了余地。
|
||||
Reference in new issue
Block a user