3.5 仿真主循环:mj_step前后发生了什么


3.5 仿真主循环:mj_step 前后发生了什么

本节摘要:mj_step 是引擎的「一步」,内部拆成前后两半:前半步做正向动力学——读控制、组装方程、解约束、得加速度,顺带把接触清单与传感器数据备好;后半步按积分格式把状态推进一步。理解这个顺序不是学术爱好——控制器该在哪个时机写 ctrl、观测该在哪个时机采样、传感器读数对应哪个时刻的状态,全部由这个流水顺序决定。本章最后一块拼图放好后,全书从建模到原理的铺垫就齐了。

本章前四节分别讲了方程、接触、时间与数据;本节把它们按执行顺序串成一条流水线。读完它,「仿真」这个词就从黑箱变成一张可以逐步指认的流程图。

学习目标

阅读完本节,你应当能够:

  1. 拆解 mj_step 的前后半步,说出每个阶段读写的数据;
  2. 解释 mj_step 与 mj_forward 的关系,知道什么场合该用谁;
  3. 为控制器与观测采样选对时机,避免「晚一步」的隐相位 bug;
  4. 用分步接口自定义控制逻辑,把人类决策嵌进仿真循环。

一、前半步:从控制到加速度

以默认的 implicitfast 积分为例,一次 mj_step 的前半步按固定顺序执行五件事:

第一件:读控制。 执行器按各自的传递语义把 ctrl 映射成广义力矩——力矩电机直通,位置伺服按增益算力,肌肉类执行器先更新自己的激活状态。你在循环里写的 data.ctrl 赋值,在这一刻生效。

第二件:碰撞检测。 按 model 里的碰撞配对规则筛选候选对,对可能相碰的几何对算最近距离,距离小于裕度的生成接触点,连同法向、穿透深度一起写进 data.contact。注意:这一步产出的是「几何事实」,还不是力。

第三件:装配约束。 接触、关节限位、等式约束、摩擦各就各位,按 3.2 的软约束模型生成约束行,雅可比折算到广义坐标空间。data.nefc 在这一步定型,这就是上一节说的「约束行数」。

第四件:解约束优化。 求解器带着上一步的解热启动,迭代出约束力。迭代数、残差写进求解器统计字段——第六章的体检单就是从这里抄的。

第五件:合成加速度。 质量矩阵、偏置力、执行器力、约束力在运动方程里会师,解出 qacc。传感器此刻同步结算——sensordata 反映的是「本步初状态在本次控制与接触下的响应」,这个时点语义后面要考。

前半步在接口上就是 mj_forward:它的字面含义是「正向动力学全套」,产出 qacc 与全部中间量,但不推进时间。想「看看现在这样会怎么样」而不真往前走,就用它——MPC 候选评估、逆动力学前的准备、可视化刷新,都是 mj_forward 的常客。

二、后半步:从加速度到新状态

后半步短得多:按积分格式,用 qacc 更新 qvel,再用(新的或旧的)qvel 更新 qpos——不同积分器的差别全在这两步的先后与采样次数上(3.3 讲过)。时间戳 time 前进一步,主循环回到起点。

后半步也带来一个工程上极其重要的副作用:步进之后、下一步前半步之前,data 里的接触清单、约束力、传感器读数全部「过期一步」——它们描述的是上一步开头的世界。绝大多数任务感知不到这一步之差,但高速接触任务里,「观测滞后一步」会让策略学到错误的因果。对齐的办法很简单:需要「最新」观测时,先手动调一次 mj_forward 刷新中间量再读。

图 3-4 mj_step 流水线:前后半步与三个采样窗口

图 3-4 mj_step 流水线:前后半步与三个采样窗口

三、两个时机的老坑

坑一:奖励与观测不同源。 某环境把观测采样放在 step 之前、奖励计算放在 step 之后,两者描述差一步的世界的状态。策略学不到平稳映射,训练曲线像心电图。处方:环境代码里固定一个采样点,观测、奖励、终止判定全部读同一时刻的 data。第五章封装环境时会把这个时点写死在规范里。

坑二:控制器以为自己在「现在」。 人类控制器的直觉是「读了状态马上出控制」,但若在 step 之后读状态、又不在下个 step 前写 ctrl,控制就晚了整一步。高速任务里一步之差就是数毫秒相位差,够让振型对不上。处方:把「读状态、算控制、写 ctrl、调 step」写成一个不可拆的块,时序纪律写进代码注释。

把坑二展开成一个真实案例。背景:某振子摆起任务,MPC 控制器在仿真里偶尔成功偶尔失败,成功率长期停在两成上下,换代价权重无改善。操作:按本节纪律审查时序,发现控制计算被放在了 mj_step 之后,控制输出写入的时机跨过了一整步——等效于控制器带着固定一步的相位滞后在工作;摆起这类对相位敏感的任务,两毫秒的滞后足以让「该推的时刻」变成「该收的时刻」。结果:把「读状态、算控制、写 ctrl」收敛到 step 之前的同一个代码块,成功率从两成跳到七成,代价参数一行没动。解读:时序 bug 的伪装能力极强——它在相位不敏感的任务里完全无症状,在敏感任务里则被误诊为「调参问题」,所以控制类故障的排查顺序应当是先查时序、再动参数。变式:换成千赫兹级的力控任务,一步滞后占比更大,同样的 bug 症状会更剧烈——症状烈度与「任务带宽乘步长」成正比,这个乘积越大,时序纪律越要当铁律执行。

四、分步接口:把决策嵌进循环

引擎把 mj_step 拆成 mj_step1(前半步)与 mj_step2(后半步)两个接口,用途是「一步之内多次改主意」:仿真推进到接触生成后,看看踩了什么,再决定 ctrl,然后让后半步执行。一个可跑的骨架:

while data.time < 5.0: mujoco.mj_step1(model, data) # 前半步:接触与约束已就绪 n = data.ncon # 此刻可以基于最新接触做决策 if n > 0: data.ctrl[0] = 0.5 # 摸到东西了就换个策略 mujoco.mj_step2(model, data) # 后半步:用刚写的控制推进

绝大多数应用不需要这么细的控制粒度,整步 mj_step 足够;但「接触触发式」的逻辑(碰到才抓、撞到才撤)用分步接口写,语义最干净,不必靠延迟一步的接触清单去猜。

本节要点回顾

  • 一步两半:前半步 mj_forward 算清楚(控制、碰撞、约束、加速度、传感器),后半步积分推进;
  • 过期一步原则:step 后的中间量属于上一步,要「最新」就补一次 mj_forward;
  • 采样纪律:观测、奖励、终止判定固定同一时点,控制器读写不许跨步拖延;
  • mj_forward 独立有用:MPC 评估、可视化刷新、逆动力学前置,都是它;
  • 分步接口:step1 与 step2 之间是接触触发式决策的自留地。

至此引擎内幕全部掀开:方程、接触、时间、数据、时序,五件套凑齐。下一章在完整框架之上加料——流体、柔体、插件与渲染这些高级特性。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U