CelerisLab/tests/audit/Force_region.md
Frank14f b110591433 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>
2026-06-01 18:07:55 +08:00

16 KiB
Raw Permalink Blame History

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 生效
  • 不要求读取区域平均速度
  • 不要求与 sensorbody 的接口完全统一

这意味着第一阶段的问题不是“建立通用 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 放在 statecontrol,还是直接只存在 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。这会多一点样板代码,但可读性更好,也避免后续 sensorforce_region 在 field 中混淆。

action contract 建议

当前 action buffer 在 2D 下每对象为 3 * dim = 6float,而现阶段只真正使用了最后一个 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 > 0fx < 0 响应方向相反
  • fy 路径同理

3. 回归保护

  • 未添加 force_region 时,结果与当前版本一致
  • 添加 force_regionfx=fy=0 时,结果与当前版本一致或只存在舍入级差异

暂不要求的测试

  • 与解析解严格比对
  • 多 region 重叠叠加
  • 强 forcing 的稳定性边界扫描

第一阶段完成后的理想状态

完成后,项目应具备以下能力:

  • 能创建一个圆形局部施力区域
  • 能在运行时改变其 fx, fy
  • 不需要重新编译
  • 不需要引入全场 owner map
  • 不破坏现有 curved boundary 与 sensor 链路
  • 不迫使 bodylbm 做新的深度耦合

从架构角度看,第一阶段的关键成果不是“得到最完美的 actuator 抽象”,而是验证:

  • 稀疏 cell list 路线足以支撑局部 forcing 研究
  • 现有系统能容纳一条新的 compact-list kernel 链路

第二阶段可能的演进方向

这些不属于当前实现范围,但第一阶段设计应避免把它们堵死:

  • 读取 force-region 内平均速度或其他观测量
  • force_regionsensor 的 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 修正
  • 在接口和数据布局上只做最低必要改动

这条路线与现有代码风格最一致,也为后续更严格或更泛化的实现保留了余地。