命名与索引约定#

Genesis World 在其 API 和源码中使用一套规模不大、前后一致的命名规则。本页是其中三条的参考:实体如何被标识(name、uid、idx),索引如何在实体自身的编号与求解器的全局编号之间切换(*_idx_local 与 *_idx),以及你阅读求解器代码时会遇到的 i_* 循环变量与字段命名约定。

标识符回答“这是哪个实体”,索引回答“这是状态数组中的哪个槽位”。两者是互不相关的编号体系,所以把一个当作另一个传入,代码能读也能跑,但会作用到错误的实体上。

标识实体:name、uid 与 idx#

每个实体都带有三个标识符,各司其职:

  • name: 人类可读的字符串。通过 scene.add_entity(..., name=...) 传入,否则 Genesis World 会根据 morph 类型加一个 UID 前缀自动生成一个。之后你就用它来查找实体。

  • uid: 创建时分配的全局唯一 ID。它绝不会在实体之间冲突,并在对象的整个生命周期内保持稳定。它在终端日志中以 short() 形式(7 个字符的前缀)出现。

  • idx: 实体在场景创建顺序中的整数位置。它是一个索引,而不是你应该手写的标识符。它的存在是为了让引擎能够寻址该实体的数据。

命名教程 examples/tutorials/entity_name.py 展示了这三者的用法。可以用名称或短 UID 查找实体:

box = scene.add_entity(gs.morphs.Box(pos=(0, 0, 0.5), size=(0.2, 0.2, 0.2)))
ground = scene.add_entity(gs.morphs.Plane(), name="ground")

scene.get_entity(name="ground")        # exact-name lookup
scene.get_entity(uid=box.uid.short())  # short-UID lookup (substring of the full uid)

scene.entity_names  # tuple of every entity's name, in creation order

手写查找时优先用 name,把 uid 留给需要区分共享同一生成名称的实体。同一对 name / uid 也用于标识刚体内的连杆和关节。entity.get_link(name=...) 与 entity.get_joint(name=...) 的用法相同。

标识符

类型

由谁分配

用途

name

str

你,或自动生成

在场景中查找实体

uid

UID 对象

Genesis World,创建时

消除歧义;通过 uid.short() 从日志中读取

idx

int

Genesis World,创建时

寻址实体的数据(很少手写)

局部索引与全局索引#

刚体求解器把每个实体的状态存放在扁平、共享的数组中:每个 dof(自由度)一行、每个连杆一行、每个配置变量 q 一行,依此类推。因此同一个 dof 有两种编号方式:

  • 全局索引(*_idx): dof 在求解器数组中的绝对行号,跨所有实体计数。双臂场景中,第二条臂的 dof 排在第一条臂的 dof 之后。

  • 局部索引(*_idx_local): dof 在其所属实体内部的位置,总是从零开始。任何实体的第一个 dof 都是局部索引 0,与它之前添加过什么无关。

两者的差是一个按实体计算的偏移量。对 dof 来说这个偏移量是 entity.dof_start;实体还暴露 link_start 和 q_start,分别对应连杆数组和 q 数组。关节的 dofs_idx(全局)与 dofs_idx_local(局部)属性正是按这个偏移定义的:

# genesis/engine/entities/rigid_entity/rigid_joint.py
dofs_idx       = list(range(dof_start, dof_end))
dofs_idx_local = list(range(dof_start - entity.dof_start, dof_end - entity.dof_start))

由于无论场景中还共享了多少其他实体,局部索引都保持不变,所以控制目标应以它们为基础来构建。控制教程 examples/tutorials/control_your_robot.py 按关节名读取 Franka 的电机 dof,然后以局部索引寻址它们:

motors_dof_idx = [franka.get_joint(name).dofs_idx_local[0] for name in joints_name]

franka.set_dofs_kp(
    kp=np.array([4500, 4500, 3500, 3500, 2000, 2000, 2000, 100, 100]),
    dofs_idx_local=motors_dof_idx,
)

索引在 API 中的体现#

读取或写入批量状态的实体方法接受局部选择器,并在内部通过 RigidEntity._get_global_idx 将其转换为全局索引,再调用求解器。参数命名为 *_idx_local,以便在调用处明确编号方式:

franka.set_dofs_position(position, dofs_idx_local=motors_dof_idx)
franka.get_links_pos(links_idx_local=[0, 1])
franka.set_qpos(qpos, qs_idx_local=...)
  • 实体方法接受局部索引。 dofs_idx_local、links_idx_local、qs_idx_local。传入 None 会选择该实体的完整范围。

  • 求解器方法接受全局索引。 RigidSolver 中对应的方法接受已经解析好的 dofs_idx、links_idx 等。只有在求解器层面跨实体操作时才使用它们。

  • envs_idx 没有局部形式。 环境(env)选择是场景范围的,所以批量方法接受单一的 envs_idx,直接按环境索引。批维度的运作方式见 并行仿真。

Note

不带后缀的属性(关节的 dof_start、连杆的 idx、实体的 idx)都是全局的。_local 后缀只在需要选择实体相对编号时才会加上。拿不准时,索引就是全局的。

源码中的字段与循环变量命名#

上述约定会以你在阅读求解器代码时也能看到的名字出现。如果你只使用公共 API,读到这里就可以停下;其余内容是为 genesis/engine/solvers/ 下的源码准备的路线图。

数据字段遵循 <representation>_<computation-type>。 representation 总是复数形式(dofs、links、particles、elements),computation-type 后缀标明字段的用途:

  • *_state: 每次求解器步进都会更新的动态状态(例如 dofs_state、links_state)。

  • *_info: 在整个仿真期间保持固定的静态属性,例如质量或刚度(dofs_info、geom_info)。

  • *_render: 仅供可视化使用、从不参与物理的字段(particles_render)。

  • *_ng: 禁用了 requires_grad 的状态变体(particles_state_ng);*_reordered 标记经过空间重排的缓存。

循环索引是 i_<representation-initial>。 内核用短名称遍历扁平数组,名称中的字母指明了被索引的对象。这些是上文所述的全局索引,而不是局部索引:

变量

索引对象

i_b

batch:环境

i_e

实体

i_l

连杆

i_j

关节

i_d

dof

i_g

几何体(碰撞对中的两个几何体分别用 i_ga、i_gb)

i_v

顶点

各个求解器具体的字段布局(完整的 state 与 info 结构体)请直接阅读求解器源码,从 rigid_solver.py 和 genesis/engine/entities/ 下的实体类开始。

另见#