base_body_part

BodyPart 体系总结(含继承关系/成员/用法)

本文基于 src/shared/bodies/base_body_part.h/.cpp 对 BodyPart 家族做一份“按类索引”的总结,并在末尾保留并完善你原来的 by-cell / by-particle 对比结论。

继承关系(概览)

BodyPart
├─ BodyPartByID
├─ BodyPartByParticle
│  ├─ BodyRegionByParticle
│  ├─ BodySurface
│  ├─ BodySurfaceLayer
│  ├─ AlignedBoxByParticle   (同时继承 AlignedBoxPart)
│  └─ (其它基于粒子的子类)
├─ BodyPartByCell
│  ├─ BodyRegionByCell
│  ├─ NearShapeSurface
│  ├─ AlignedBoxByCell       (同时继承 AlignedBoxPart)
│  └─ (其它基于 cell 的子类)
└─ AlignedBoxPart            (一个“混入”类:保存 aligned_box_)

说明:AlignedBoxByParticle/AlignedBoxByCell 是多重继承:分别继承 BodyPartByParticle/BodyPartByCell + AlignedBoxPart

通用基类:BodyPart

职责

  • 为一个 SPHBody 定义“body 的某一部分(part)”的公共属性。

  • 维护该 part 的 part_id_,并在粒子上建立一个“属于哪个 part”的标记变量(dv_body_part_id_)。

  • 提供一个通用的 mask:TargetParticleMask(用于“只对属于该 part 的粒子生效”的过滤)。

关键成员变量(核心状态)

  • SPHBody &sph_body_:所属 body。

  • BaseParticles &base_particles_:所属粒子容器。

  • int part_id_:该 part 的唯一 id(通过 base_particles_.getNewBodyPartID() 获取)。

  • std::string part_name_:默认名字(bodyName + "Part" + id)。

  • std::optional<std::string> alias_:可选别名(很多派生类会把 shape名/固定名称写到 alias)。

  • SPHAdaptation &sph_adaptation_:便于取 spacing 等信息。

  • SingularVariable<UnsignedInt> *sv_range_size_:记录loop range的规模(粒子数或 cell 数,取决于子类)。

  • DiscreteVariable<int> *dv_body_part_id_:每个粒子一个int,存它被标记成哪个part_id_

  • Vecd *pos_:粒子位置指针(来自Position)。

  • UniquePtrsKeeper<Entity> unique_variable_ptrs_:用于创建 dv_particle_list_ / dv_cell_list_ / sv_range_size_ 等“只属于该 part 的变量”。

关键成员函数

  • BodyPart(SPHBody &sph_body)

    • 分配 part_id_

    • 注册 dv_body_part_id_ = registerStateVariable<int>(part_name_ + "ID")

    • pos_ = getVariableDataByName<Vecd>("Position")

  • std::string getName() const:优先返回 alias_,否则返回 part_name_

  • BaseCellLinkedList &getCellLinkedList():对 sph_body_DynamicCast<RealBody> 并返回其 cell linked list(因此:不是 RealBody 会在这里失败)。

  • SingularVariable<UnsignedInt> *svRangeSize():返回 range size。

内嵌模板:TargetParticleMask

用途直觉:把任意“邻居判据/筛选条件”包装成“只对属于该 part 的粒子生效”的判据。

备注:仓库里也存在其它标识类(比如 SPHBodyBodyPartition)自带同名 TargetParticleMask;使用时到底实例化哪个版本,取决于上层算法里 Identifier 的具体类型。

BodyPartByID

职责

  • 表示“整个 body 作为一个 part”的最简单版本:loop range 就是全体 real particles。

关键点

  • typedef BodyPartByID BaseIdentifier;:用于把它作为 dynamics 的“标识类型(Identifier)”。

  • 构造时把 sv_range_size_ = base_particles_.svTotalRealParticles(),也就是直接用全体粒子数。

BodyPartByParticle

职责

  • 用“显式粒子索引列表”表示一个 body part。

  • 构造后会通过 tagParticles(...) 建立粒子列表,并生成对应的 loop range。

关键成员变量

  • IndexVector body_part_particles_:该 part 的粒子索引集合。

  • DiscreteVariable<UnsignedInt> *dv_particle_list_:把 body_part_particles_ 固化成一个离散变量(用于 device/offload 等)。

  • BoundingBox body_part_bounds_ + bool body_part_bounds_set_:可选的包围盒。

关键成员函数

  • IndexVector &LoopRange():返回 body_part_particles_

  • size_t SizeOfLoopRange():返回粒子数。

  • void setBodyPartBounds(BoundingBox bbox) / BoundingBox getBodyPartBounds()

  • void tagParticles(TaggingParticleMethod &method)

    • 遍历 [0, TotalRealParticles),对满足判据的粒子:

      • dv_body_part_id_->setValue(i, part_id_)(写标记)

      • body_part_particles_.push_back(i)(收集索引)

    • 生成 dv_particle_list_(长度 = 粒子数,内容 = 粒子索引)。

    • 生成 sv_range_size_(记录粒子数)。

重要语义

BodyPartByParticle 的粒子集合通常是“构造时刻的快照”:后续粒子移动后,是否仍在区域内不影响该列表(除非你重新构建/重新 tag)。

BodyPartByCell

职责

  • 用“一组 cell 列表”表示空间区域,粒子集合来自这些 cell 当前包含的粒子。

  • 适合流动/注入/删除等会频繁改变粒子分布的场景:cell-linked-list 更新后,这个区域内“当前有哪些粒子”会随之变化。

关键成员变量

  • ConcurrentCellLists body_part_cells_:该 part 的 cell 集合(每个 cell 对应一个粒子索引数组/并发容器)。

  • BaseCellLinkedList &cell_linked_list_:来源于RealBody

  • DiscreteVariable<UnsignedInt> *dv_cell_list_:保存该 part 选中的 cell 索引。

  • DiscreteVariable<UnsignedInt> *dv_particle_index_DiscreteVariable<UnsignedInt> *dv_cell_offset_:cell linked list 的索引结构(用于按 cell 取粒子)。

关键成员函数

  • ConcurrentCellLists &LoopRange():返回 cell 列表(遍历时通常是“先 cell,再 cell 内粒子”)。

  • size_t SizeOfLoopRange():累加每个 cell 内粒子数。

  • void tagCells(TaggingCellMethod &method)

    • 调用 cell_linked_list_.tagBodyPartByCell(...) 筛选出 cell,并填充 body_part_cells_cell_indexes

    • 遍历 body_part_cells_ 内所有粒子 index:对每个粒子写 dv_body_part_id_->setValue(particle, part_id_)

    • 生成 dv_cell_list_sv_range_size_(此处 size 是 cell 数)。

注意点

BodyPartByCell标记的是cell而非particle。cell是比较粗糙的,它的边界不一定和区域边界重合,所以BodyPartByCell管理的粒子可能不在预期的区域之内。在使用BodyPartByCell的时候建议严格检查粒子是否在预期区域之内。

例如InflowVelocityCondition<...>使用AlignedBoxByCell来遍历粒子,以下是其update函数:

再如BidirectionalBufferTagBufferParticles使用AlignedBoxByCell来遍历粒子,以下是其update函数:

BidirectionalBufferInjection类使用AlignedBoxByCell来遍历粒子,以下是其update函数:

具体子类(按用途)

BodyRegionByParticle

  • 父类:BodyPartByParticle

  • 额外成员:

    • Shape &body_part_shape_

    • SharedPtrKeeper<Shape> shape_ptr_keeper_(当通过 SharedPtr 构造时,用于延长 shape 生命周期)

  • 构造行为:

    • alias_ = shape.getName()

    • tagParticles( bind(tagByContain) )

  • 判据:tagByContain(i) -> shape.checkContain(pos_[i])

BodySurface

  • 父类:BodyPartByParticle

  • 额外成员:Real particle_spacing_min_(取自 adaptation 的 MinimumSpacing)

  • 构造行为:

    • alias_ = "BodySurface"

    • tagParticles( bind(tagNearSurface) )

  • 判据:

    • phi = sph_body_.getInitialShape().findSignedDistance(pos_[i])

    • abs(phi) < particle_spacing_min_

BodySurfaceLayer

  • 父类:BodyPartByParticle

  • 额外成员:Real thickness_threshold_ = ReferenceSpacing * layer_thickness

  • 构造行为:

    • alias_ = "InnerLayers"

    • tagParticles( bind(tagSurfaceLayer) )

  • 判据:

    • distance = abs( findSignedDistance(pos_[i]) ) < thickness_threshold_

BodyRegionByCell

  • 父类:BodyPartByCell

  • 额外成员:

    • Shape &body_part_shape_

    • SharedPtrKeeper<Shape> shape_ptr_keeper_

  • 构造行为:

    • alias_ = shape.getName()

    • tagCells( bind(checkNotFar) )

  • 判据:checkNotFar(cell_pos, threshold) -> shape.checkNotFar(cell_pos, threshold)

NearShapeSurface

  • 父类:BodyPartByCell

  • 额外成员:

    • LevelSetShape &level_set_shape_

    • UniquePtrKeeper<LevelSetShape> level_set_shape_keeper_(当需要从 shape 构造 level set 时持有)

  • 典型构造方式:

    • NearShapeSurface(real_body, SharedPtr<Shape>):内部创建 LevelSetShape(real_body, *shape, true)

    • NearShapeSurface(real_body):从 real_body.getInitialShape() dynamic cast 到 LevelSetShape

    • NearShapeSurface(real_body, sub_shape_name):从 ComplexShape 的子 shape dynamic cast 到 LevelSetShape

  • 构造行为:tagCells( bind(checkNearSurface) )

  • 判据:checkNearSurface(cell_pos, threshold) -> level_set_shape_.checkNearSurface(cell_pos, threshold)

AlignedBox*(混入类 + 两个实现)

AlignedBoxPart

  • 这是一个“存 aligned box 的混入类”,不直接参与粒子/cell 标记。

  • 成员:

    • UniquePtrKeeper<SingularVariable<AlignedBox>> sv_aligned_box_keeper_

    • AlignedBox &aligned_box_(引用到上面 SingularVariable 的数据)

  • 函数:

    • SingularVariable<AlignedBox> *svAlignedBox()

    • AlignedBox &getAlignedBox()

AlignedBoxByParticle

  • 父类:BodyPartByParticle, AlignedBoxPart

  • 构造:AlignedBoxByParticle(real_body, aligned_box)tagParticles(bind(tagByContain))

  • 判据:aligned_box_.checkContain(pos_[i])

AlignedBoxByCell

  • 父类:BodyPartByCell, AlignedBoxPart

  • 构造:AlignedBoxByCell(real_body, aligned_box)tagCells(bind(checkNotFar))

  • 判据:aligned_box_.checkNotFar(cell_pos, threshold)


BodyPartByCell 和 BodyPartByParticle 的区别

主要有两种 body part:一种 by cell,一种 by particle。

结论一句话:BodyPartByParticle 存的是“一串粒子索引”;BodyPartByCell 存的是“一串 cell(每个 cell 里又有若干粒子索引)”。因此它们的遍历方式、适用场景和更新代价都不同。

数据结构上的区别

BodyPartByParticle

主要成员:IndexVector body_part_particles_ LoopRange() 直接返回这串粒子索引(见 base_body_part.h:95-126)。 语义:这个 body part 是“按粒子粒度精确选出来的一组粒子”。

BodyPartByCell

主要成员:ConcurrentCellLists body_part_cells_(一组 cell,每个 cell 是 ConcurrentIndexVector*,内部是一串粒子索引) LoopRange() 返回 cell 列表;真正遍历时会“先遍历 cell,再遍历 cell 内粒子”(见 particle_iterators.h:107-144)。 语义:这个 body part 是“按 cell 粒度粗筛出来的一片区域”,粒子集合来自这些 cell 当前包含的粒子。

构建/标记(tagging)方式的区别

BodyPartByParticle::tagParticles(...) 用一个 bool(size_t particle_index) 判据把粒子一个个挑出来,填进 body_part_particles_BodyPartByCell::tagCells(...) 用一个bool(Vecd cell_position, Real threshold)判据把 cell 挑出来,填进body_part_cells_(见base_body_part.cpp:83-111)。 之后遍历时使用的是这些 cell 里“当前”的粒子索引列表。

使用上的区别(什么时候用哪个)

BodyPartByParticle(精确粒子集合)

  • 需要对“某一组特定粒子”施加操作(例如固定边界粒子层、固定观测点、固定结构节点)。

  • 你希望集合非常精确,不想包含“附近但不在区域内”的粒子。

  • 你能接受它更像“构造时刻的快照”,或者你愿意在需要时重建该集合。

BodyPartByCell(区域/缓冲区,基于 cell linked list)

  • 用于边界条件、缓冲区、局部计算域等“空间区域”问题(例如入口/出口 buffer、阻尼区)。

  • 粒子每步都在流动、排序、注入/删除时,按 cell 存储更自然:只要选中区域 cell,里面有哪些粒子会随 cell linked list 更新自动变化。

性能/维护成本的直觉

BodyPartByParticle:遍历开销是$O(N_\mathrm{part})$,但如果粒子集合需要频繁重建(粒子流动导致集合变化),重建成本可能高。 BodyPartByCell:遍历开销大约是 $O(N_\mathrm{cellsinregion}+N_\mathrm{particlesinthose~cells})$;区域固定时,cell 集合通常相对稳定,而且能很好地跟随 cell linked list 的更新来“自动跟踪”区域内粒子。

AlignedBoxByParticle和AlignedBoxByCell

AlignedBoxByParticle

  • 记录的是被构造时AlignedBox区域的粒子ID。所以后续遍历时,无论这些粒子是否跑出了AlignedBox,都会把这些粒子遍历到。

  • 用于EmitterInflowInjectionEmitterInflowCondition

AlignedBoxByCell

  • 记录的是cell的ID。后续遍历时,无论粒子初始时是否属于这个cell,只要检测到粒子当前在cell里,就会被遍历到。

  • 用于InflowVelocityCondition<...>DisposerOutflowDeletion

Last updated

Was this helpful?