Skip to content
TO、MPC与WBC在机器人控制中的作用与区别对比文档

TO、MPC与WBC在机器人控制中的作用与区别对比文档

May 6, 2025·chase
本文目录 展开章节导航

TO、MPC 和 WBC 不是三种互斥算法:TO 描述如何优化一段轨迹,MPC 描述如何滚动求解并利用反馈执行,WBC 描述如何协调全身多个任务。一个 MPC 内部可以求解 TO 问题,WBC 也可以采用不同形式的控制器。

轨迹优化生成参考、MPC 滚动规划、WBC 协调全身任务,并由状态估计形成反馈的关系
一种常见架构:TO 提供参考,MPC 根据状态重新规划,WBC 把运动与接触目标转成关节指令。具体系统可以合并或省略某一层。

第一次读可以按三轮推进:先读第 1–4 节画出闭环,再读第 6–8 节把模型写成优化问题,最后跟着第 8.2–9 节运行 Python。本文的最小实验只需要矩阵乘法和 NumPy;动力学 WBC 部分还需要了解雅可比与刚体动力学。

学习目标阅读与操作入口完成后应能解释
看懂分层MPCWBC未来序列和当前拍命令的区别
手算优化问题矩阵推导逐步教程H、g、A 和边界从哪里来
独立运行NumPy 原子实验双关节 WBC为什么约束会改变速度与任务分配
接通两个模块WholeBodyX 对照参考插值、速度前馈与执行反馈
排错与扩展参数实验怎样区分任务残差、约束违约与模型缺失
验证闭环边界扰动对照短时域反例参考失效有反馈、有解、可持续执行分别意味着什么

第一次运行时,建议先完成只依赖 NumPy 的两项实验,再进入 WholeBodyX。下载实验包后,在终端所在目录执行下列命令;需要 Python 3.11+,激活命令适用于 Bash:

python3 -m zipfile -e control-lab.zip .
cd control-lab
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python atomic_control.py
python feedback_demo.py

两项脚本正常结束后,用结果索引核对输出,再回到公式推导。不安装 WholeBodyX,也能完成 MPC 原子问题、加权与层级 WBC、不可达目标和扰动对照;涉及源码集成的步骤在第 9 节另行说明。

1. 从输入与输出理解职责

模块典型输入典型输出主要问题
TO:轨迹优化初末状态、动力学、约束、代价一段状态与控制轨迹这段运动是否可行,代价有多大?
MPC:模型预测控制当前状态、参考、预测模型当前控制量及预测轨迹执行后状态变了,接下来如何调整?
WBC:全身控制任务目标、接触状态、机器人模型关节力矩、速度或加速度等指令多个任务如何共享关节和接触能力?

TO 可以处理动力学、接触和时变约束,也可以在线求解。机器人 TO 往往是非凸问题,求解器得到的通常是局部解,不能笼统保证全局最优。

MPC 在每个周期更新状态,求解有限时域问题,执行当前一小段控制,再滚动向前。它可以采用简化模型,也可以使用更完整的模型。

WBC 常使用加权 QP 或任务优先级优化,但 WBC 本身不等于 QP;基于零空间投影等方法也可以实现全身协调。

2. 为什么需要不同时间尺度

长时域规划需要预见未来运动;关节层还需及时响应测量和接触变化。把问题分层,能让每层使用合适的模型与计算预算。

各层频率取决于问题规模、求解器、机器人硬件和稳定性要求。“TO 秒级、MPC 毫秒级、WBC 更快”只能作为某类系统的例子,不能作为定义。MPC 与 WBC 也不一定需要 GPU。

3. 以足式机器人为例

  1. 规划层产生落足、质心或末端参考。
  2. MPC 根据当前状态与接触计划,预测未来运动并调整控制目标。
  3. WBC 综合平衡、姿态、足端与操作任务,求解满足约束的关节指令。
  4. 状态估计器融合编码器、IMU 等信息,为下一轮规划和控制提供反馈。

对动力学 WBC,常见决策变量包括广义加速度、关节力矩和接触力。应同时检查浮动基动力学、摩擦约束、接触运动约束和执行器限制。高层目标不可行时,底层不能凭空保证精确跟踪。

4. 接口比模块名称更值得检查

接口问题常见后果检查方式
坐标系或力的正负号不一致运动方向或接触力错误单轴、静态场景验证
接触计划与实际接触不同约束不成立、力矩突变对比计划接触与传感器反馈
高层输出超过执行器能力WBC 不可行或长期饱和记录约束余量与求解状态
状态估计或控制消息过期跟踪抖动、稳定性下降测量时间戳与完整控制延迟

5. 延伸阅读与工具

评估实现时,记录求解耗时分布、最坏控制周期、约束违反和失败恢复;只报告平均频率无法说明控制链路是否可靠。

6. MPC:优化未来序列,闭环执行当前一段

从当前估计状态 x^t\hat x_t 出发,通用有限时域问题可以写成:

minx0:N,u0:N1  k=0N1(xk,uk)+f(xN)s.t.  x0=x^t,xk+1=fd(xk,uk,σk),xkX(σk),ukU(σk). \begin{aligned} \min_{x_{0:N},u_{0:N-1}}\;&\sum_{k=0}^{N-1}\ell(x_k,u_k)+\ell_f(x_N)\\ \text{s.t.}\;&x_0=\hat x_t,\quad x_{k+1}=f_d(x_k,u_k,\sigma_k),\\ &x_k\in\mathcal X(\sigma_k),\quad u_k\in\mathcal U(\sigma_k). \end{aligned}

kk 是预测节点索引,N 是预测步数;xkRnxx_k\in\mathbb R^{n_x}ukRnuu_k\in\mathbb R^{n_u} 分别为状态与控制,σk\sigma_k 表示接触模式。这里先把接触日程视为已知;若把接触时刻或接触集合一起优化,问题结构和求解难度也会改变。

代价表示偏好,约束表示允许范围。跟踪误差小的解如果要求地面提供向下拉脚的法向力,仍然不满足单侧接触条件。求解后采用当前第一项或第一小段,获得新测量后重新预测;旧轨迹可以用于 warm start,但不应脱离有效期与状态偏差检查一直播放。MIT 轨迹优化与 MPC

预测模型常见决策量或输出需要继续核对
质心/动量或单刚体模型质心运动、接触力、动量变化是否覆盖关节限位、运动学可达性和执行器力矩
带落脚变量的降阶模型落脚点、接触日程、受力参考接触日程是给定还是优化变量
全身模型全身状态、力矩、接触力、反馈增益与下游 WBC 的职责是否重叠
WholeBodyX 当前关节积分模型关节位置与速度参考没有浮动基、接触力或惯性预测

状态估计与目标命令是两个入口:估计告诉控制器“现在在哪里”,目标说明“希望去哪里”。当前状态还应直接进入 WBC;低层伺服和真实执行器位于 WBC 输出之后,优化结果不能等同于已经实现的动作。

7. WBC:当前拍协调任务与物理约束

7.1 动力学 WBC 的决策变量

常见浮动基动力学方程为:

M(q)v˙+h(q,v)=STτ+Jc(q)Tλ. M(q)\dot v+h(q,v)=S^T\tau+J_c(q)^T\lambda.

qq 是构型,vRnvv\in\mathbb R^{n_v} 是广义速度,v˙\dot v 是广义加速度;MRnv×nvM\in\mathbb R^{n_v\times n_v}hRnvh\in\mathbb R^{n_v} 分别为质量矩阵和偏置项。SRna×nvS\in\mathbb R^{n_a\times n_v} 选择执行器自由度,τRna\tau\in\mathbb R^{n_a} 为关节力矩。JcRnc×nvJ_c\in\mathbb R^{n_c\times n_v}λRnc\lambda\in\mathbb R^{n_c} 是环境施加于机器人的接触力或 wrench,二者必须使用一致的坐标与排列。

浮动基的六个速度自由度没有直接电机驱动;若姿态用四元数存储,构型维数 nqn_q 与速度维数 nvn_v 还可能不同,不能直接用逐元素相加更新构型。单点接触常用三维力,面接触可用六维 wrench 或多个接触点表达。

z=[v˙;λ;τ]z=[\dot v;\lambda;\tau] 为决策变量,可以构造当前拍的加权 QP:

minz  12iAizbiWi2+ρ2z2s.t.  Ez=e,lCzu. \begin{aligned} \min_z\;&\frac12\sum_i\|A_i z-b_i\|_{W_i}^2+\frac\rho2\|z\|^2\\ \text{s.t.}\;&Ez=e,\qquad l\le Cz\le u. \end{aligned}

这里 rW2=rTWr\|r\|_W^2=r^TWrWi0W_i\succeq0ρ0\rho\ge0。任务残差可以来自摆动脚、躯干或姿态的期望加速度;对普通运动学任务有 x¨i=Jiv˙+J˙iv\ddot x_i=J_i\dot v+\dot J_i v。姿态误差需要使用旋转的局部表示,不能直接相减四元数。

保持静止的刚性支撑接触常写成 Jcv˙+J˙cv=0J_c\dot v+\dot J_c v=0;动力学、单侧法向力、摩擦边界和执行器限位分别进入等式或不等式约束。线性化摩擦锥可得到 QP;保留非线性锥或接触互补关系时,求解问题可能不再是同一种 QP。TSID 官方实现

7.2 权重、层级与求解失败

有限权重允许任务之间折中,不能把“大权重”称为严格优先级;第 8.5 节提供了相同任务下的数值对照。层级逆动力学或级联 QP 会约束低层不能破坏高层已经达到的最优结果。两者都还要处理不可行、数值误差与执行延迟。Herzog 等:层级逆动力学与动量控制

右脚离地时,如果它已经不再接触地面,相应接触力应被移除或约束为零;左脚仍承担支撑。若目标超出摩擦或力矩范围,应调整软任务、上层参考或接触计划。只有优化器报告成功还不够:错误的接触判断、质量惯量、状态估计或伺服带宽,都会使“模型可行”与“物理可行”分离。

7.3 从任务误差构造优化器的每一块

先看速度级控制。任务坐标由 xi=ϕi(q)x_i=\phi_i(q) 给出,局部变化满足 x˙i=Ji(q)u\dot x_i=J_i(q)u。如果希望任务向参考位置移动,可以构造速度目标 bi=x˙iref+Kieib_i=\dot x_i^{\mathrm{ref}}+K_i e_i。对普通位置任务,ei=xirefxie_i=x_i^{\mathrm{ref}}-x_i;旋转任务需要换成与雅可比坐标系一致的局部旋转误差。

将各任务堆入同一个决策向量 u,目标可以写为:

L(u)=12i(Jiubi)TWi(Jiubi)+ρ2uTu,H=iJiTWiJi+ρI,g=iJiTWibi. \begin{aligned} L(u)&=\frac12\sum_i(J_i u-b_i)^TW_i(J_i u-b_i) +\frac\rho2 u^Tu,\\ H&=\sum_i J_i^TW_iJ_i+\rho I,\\ g&=-\sum_i J_i^TW_i b_i. \end{aligned}

这里 JiJ_i 的列数都是关节速度维度,行数由任务维度决定。即使两个任务没有使用相同的名称,只要其雅可比涉及相同的关节列,它们就可能发生耦合。H 的非对角元素记录这种耦合;它通常不能被解释为“每个关节单独选一个速度”。位置任务和姿态任务的单位不同,权重还承担误差尺度的协调作用,不能只根据权重数值大小判断物理重要性。

再回到动力学 WBC。以 z=[v˙;λ;τ]z=[\dot v;\lambda;\tau] 排列变量后,一项加速度跟踪任务对应:

Ai=[Ji  0  0],bi=x¨icmdJ˙iv. A_i=[J_i\;0\;0],\qquad b_i=\ddot x_i^{\mathrm{cmd}}-\dot J_i v.

由于 Jiv˙+J˙ivJ_i\dot v+\dot J_i v 才是任务实际加速度,漏掉 J˙iv\dot J_i v 会改变跟踪问题。动力学与静止接触则能拼成硬等式:

[MJcTSTJc00][v˙λτ]=[hJ˙cv]. \begin{bmatrix} M&-J_c^T&-S^T\\ J_c&0&0 \end{bmatrix} \begin{bmatrix}\dot v\\\lambda\\\tau\end{bmatrix}= \begin{bmatrix}-h\\-\dot J_c v\end{bmatrix}.

这给出了第 7.1 节中 E 和 e 的具体来源。力矩边界选择 z 的力矩块,摩擦约束选择接触力块;运动学任务主要作用于加速度块。求解后仍需验证状态、约束残差和接口有效性,再由运行层消费对应输出。接触切换会改变活动雅可比和力变量,不能只改软任务权重而继续保留已经离地脚的刚性支撑约束。

8. WholeBodyX:把概念逐项映射到源码

以下结论来自 2026-09-08 检查的本地 WholeBodyX 工作树。该目录没有可解析的 Git HEAD,本仓库在 docs/wholebodyx-blog-reference.json 保存了所读文件的 SHA-256,用于识别具体源码快照。它目前是固定基座运动学验证框架,不能把其中的 H1 展示解释为动态行走或平衡验证。

源文件与接口实际职责输出/限制
mpc.py: JointMPC.solve有限时域关节速度 QPJointPlan 包含 N+1 个位置、N 个速度、起始时间及步长
runtime.py: MPCReferenceManager.update校验参考并决定重规划或复用当前参考位置、速度、原因;失败清除旧计划
tasks.py: PostureTask.linearize位置误差反馈与速度前馈实际是速度目标:gain*(qref-q)+vref,不是加速度任务
wbc.py: KinematicWBC.step加权速度级最小二乘与硬关节边界VelocityCommand,不输出力矩或接触力
types.py: JointLimits.velocity_bounds组合速度、下一步位置及速度变化率限制在积分模型与离散步长下约束指令
simulation.py: JointIntegrator执行速度积分无质量、惯量、碰撞和接触动力学

WholeBodyX 的预测模型为:

qk+1=qk+Δtuk,uk=q˙k. q_{k+1}=q_k+\Delta t\,u_k,\qquad u_k=\dot q_k.

代价对未来 q1:Nq_{1:N} 跟踪关节目标,并惩罚速度;终端节点使用 terminal weight。约束包括关节位置、速度,以及可选的 ukuk1amaxΔt|u_k-u_{k-1}|\le a_{\max}\Delta t。首个速度差相对当前测量速度计算。速度变化率限制不等于浮动基动力学或力矩可行性。

速度级 WBC 的任务为 Ji(q)ubiJ_i(q)u\approx b_i,加权残差与正则、平滑项进入目标。它同时限制:

max(vmax,qminqΔt,vamaxΔt)umin(vmax,qmaxqΔt,v+amaxΔt). \max\left(-v_{\max},\frac{q_{\min}-q}{\Delta t},v-a_{\max}\Delta t\right) \le u\le \min\left(v_{\max},\frac{q_{\max}-q}{\Delta t},v+a_{\max}\Delta t\right).

若没有配置加速度限值,去掉对应两项。这里使用逐元素上下界,只对当前固定基座积分模型成立。它与第 7 节中同时求 v˙,λ,τ\dot v,\lambda,\tau 的动力学 WBC 是不同层级的模型。

参考管理器会检查起点、时间戳、有效期、积分关系和限位。默认每 0.1 s 周期重规划,目标改变或关节位置预测偏差超过 0.03 rad 可提前触发;控制步长由调用方提供。刷新失败时返回失败并清除旧参考,而不是自动继续执行旧计划;真机停机/降级仍由硬件运行层负责。该同步调度没有硬实时保证。

8.1 从积分模型展开成求解器矩阵

将未来速度按时间堆成 U=[u0;;uN1]U=[u_0;\ldots;u_{N-1}],未来位置堆成 Q=[q1;;qN]Q=[q_1;\ldots;q_N]。对 n 个关节有:

Q=qˉ+BU,B=Δttril(1N×N)In,qˉ=1Nq0. Q=\bar q+BU,\qquad B=\Delta t\,\operatorname{tril}(\mathbf 1_{N\times N})\otimes I_n, \qquad \bar q=\mathbf 1_N\otimes q_0.

U,Q,qˉU,Q,\bar q 均有 Nn 个元素,B 为 Nn×NnNn\times Nn 矩阵。消去位置变量后,优化器只需求 Nn 个速度。对目标序列 QQ^\star,令位置权重为 W、速度权重为 R,目标为:

12qˉ+BUQW2+12UTRU=12UTHU+gTU+const,H=BTWB+R,g=BTW(qˉQ). \frac12\|\bar q+BU-Q^\star\|_W^2+\frac12 U^TRU =\frac12 U^THU+g^TU+\text{const}, \quad H=B^TWB+R,\quad g=B^TW(\bar q-Q^\star).

因此,构造 Hessian 时不能漏掉速度代价 R,梯度也应使用“当前位置减目标”的方向。WholeBodyX 默认 W 的最后一个节点用 30 替换普通节点的 10,R 为 0.1I0.1I,并非再给终端叠加一次普通权重。

约束可堆成 lAUul\le AU\le u:I 对应速度边界,B 对应平移后的位置边界,差分矩阵 D 对应相邻速度变化。单关节两步时 DU=[u0;u1u0]DU=[u_0;u_1-u_0],第一行上下界必须加上当前速度 v0v_0,其余行以零为中心。这与 OSQP 使用的 QP 形式一致;正定目标给出唯一最优解的前提是约束可行。

8.2 仅用 NumPy 跑通原子算例

希望完整复现后续教程,可以下载实验包 control-lab.zip,其中含实验脚本、求解器回归测试、运行说明和基础依赖清单。解压后在 control-lab 目录执行下方步骤;只运行原子算例时,也可以单独下载脚本。WholeBodyX 源码不包含在实验包中,只有第 9 节的集成与参考失效实验需要另外安装它。

下载 atomic_control.py,在有 NumPy 的环境运行即可,不需要 WholeBodyX 源码:

python3 -m venv .venv-atomic
source .venv-atomic/bin/activate
python -m pip install numpy==2.3.5
python atomic_control.py

该固定版本要求 Python 3.11 或更高版本。算例取一个关节、两个预测步骤,Δt=0.1\Delta t=0.1 s,q0=v0=0q_0=v_0=0,目标位置 0.3 rad,位置边界 ±0.5 rad、速度边界 ±1 rad/s、速度变化率边界 ±2 rad/s²。此时:

B=[0.100.10.1],H=[0.50.30.30.4],g=[1.20.9]. \begin{aligned} B&=\begin{bmatrix}0.1&0\\0.1&0.1\end{bmatrix},\\[4pt] H&=\begin{bmatrix}0.5&0.3\\0.3&0.4\end{bmatrix},\\[4pt] g&=\begin{bmatrix}-1.2\\-0.9\end{bmatrix}. \end{aligned}

最优速度为 [0.2,0.4][0.2,0.4] rad/s,预测位置为 [0,0.02,0.06][0,0.02,0.06] rad。两步末端尚未到达目标,因为速度变化率的硬边界限制了推进速度;增大目标权重不会消除这条约束。这次调用展示的是一次规划窗口,脚本的 rollout() 还会重复求解 100 次,形成积分模型上的滚动闭环;下一节再接入 WholeBodyX 的 WBC。

脚本枚举最多两个独立活动约束,求解对应线性方程,并检查可行性、乘子非负与驻点条件。这是可逐行阅读的微型 QP 求解方式,组合数量随约束增长,不适合替代生产求解器,也不是 OSQP 的 ADMM 实现。正向、反向和零目标均有预期结果断言,另有一个上下界矛盾的案例确认失败不会产生命令。

最后一个单自由度算例展示 WBC 任务冲突:两个速度目标分别为 0.8 和 −0.4 rad/s,权重为 4 和 1,正则系数为 0.1。无约束最优值约为 0.5490 rad/s;加入 ±0.2 rad/s 的硬边界后,输出为 0.2 rad/s,两个任务都留下残差。这里标量问题可以用截断得到同样答案;一般耦合 QP 不能靠逐关节截断无约束解代替求解。

该算例解释加权任务与边界,不含全身雅可比或接触动力学。可核对实际运行结果,其中 KKT 驻点残差最大约为 1.11×10161.11\times10^{-16};这个数反映微型问题的数值求解精度,不代表机器人跟踪精度。

8.3 跟着一次求解逐行理解代码

第一步:选定状态、控制和单位

这里位置 q 的单位为 rad,控制 u 是 rad/s,而不是力矩。当前速度 v 仅用于约束首个速度变化;预测位置由积分模型决定。每个节点相隔 0.1 s,两个节点只预测 0.2 s。目标 0.3 rad 是软跟踪目标,没有添加“必须在第二步到达”的终端等式。

展开两个节点就能看懂下三角矩阵的来源:

q1=q0+0.1u0,q2=q0+0.1u0+0.1u1. q_1=q_0+0.1u_0,\qquad q_2=q_0+0.1u_0+0.1u_1.

第一行只受第一个速度影响,第二行同时受两个速度影响。若把 B 写成对角矩阵,就会丢掉第一步对后续位置的累积贡献。

第二步:把代价展开成 H 与 g

本例完整目标是:

J=12[10(q10.3)2+30(q20.3)2+0.1(u02+u12)]. J=\frac12\left[10(q_1-0.3)^2+30(q_2-0.3)^2 +0.1(u_0^2+u_1^2)\right].

代入位置表达式,收集二次项和一次项,得到上一节的 H 和 g。常数项不影响最优速度,可以丢弃;12\frac12 则要与求解器约定保持一致。代码中这三行对应整个展开过程:

weights = np.diag([10., 30.])
h = prediction.T @ weights @ prediction + .1 * np.eye(2)
g = prediction.T @ weights @ np.full(2, q0 - goal)

若忽略约束,可以解线性方程 HU=gHU=-g。但这个解未必满足关节边界,因此它只是活动集为空时的候选,不能直接作为执行指令。

第三步:逐行解释约束,而不是只看矩阵尺寸

A 的行块本例表达式物理或离散含义
单位矩阵 I1u0,u11-1\le u_0,u_1\le1速度上限
预测矩阵 B0.5q0BU0.5q0-0.5-q_0\le BU\le0.5-q_0两个未来位置均在关节范围内
差分矩阵 D 的首行v00.2u0v0+0.2v_0-0.2\le u_0\le v_0+0.2第一个控制相对当前速度变化有限
差分矩阵 D 的次行0.2u1u00.2-0.2\le u_1-u_0\le0.2预测内部相邻速度变化有限

0.2 来自 amaxΔt=2×0.1a_{\max}\Delta t=2\times0.1,单位仍是 rad/s。将步长减半却保持这项不变,会把允许的速度变化率加倍;这是一类容易被“优化成功”掩盖的建模错误。

第四步:用 KKT 条件确认候选解

脚本将双侧边界改写为 CUdCU\le d,其中 C=[A;A]C=[A;-A]d=[u;l]d=[u;-l]。假设某组约束恰好取等号,记为 CaU=daC_aU=d_a,求解:

[HCaTCa0][Uμ]=[gda]. \begin{bmatrix}H&C_a^T\\C_a&0\end{bmatrix} \begin{bmatrix}U\\\mu\end{bmatrix} =\begin{bmatrix}-g\\d_a\end{bmatrix}.

随后检查所有约束可行、活动乘子 μ0\mu\ge0,以及驻点残差 HU+g+CaTμHU+g+C_a^T\mu 足够小。非活动约束乘子取零,活动约束取等号,便满足互补条件。对这里的严格凸问题,这些条件共同证明候选是唯一最优解;只检查“位置没越界”不能证明最优性。

正向目标下,起作用的两个边界是 u0=0.2u_0=0.2u1u0=0.2u_1-u_0=0.2,相应乘子为 1.66、0.68,均非负。因此最优速度为 0.2、0.4。solve_tiny_qp() 枚举空集、单行和双行组合,跳过奇异系统;如果找不到通过检查的候选,会抛出异常,调用方不能继续使用一个不存在的解。

第五步:从一次规划变成滚动闭环

rollout() 每次调用 mpc(goal, q, v),只执行第一个速度,然后读回积分后的新位置和速度:

plan = mpc(goal, q, v)
command = plan["velocity"][0]
next_q = q + .1 * command
q, v = next_q, command

第一拍后位置为 0.02 rad、速度为 0.2 rad/s。下一拍必须用这份新状态重新构造 g、位置边界和首行速度变化边界,而不是再次从零出发求解。后半段预测可以成为下一次求解的初值;本微型枚举器没有 warm start,WholeBodyX 的求解器则会利用旧计划。

运行后当前目录生成 results-atomic/report.jsonresults-atomic/rollout.csv。默认的 100 步运行结束于 10 s,位置在本次数值运行中达到 0.3 rad,误差为零;验收实际采用 10510^{-5} rad 阈值。每一拍还检查速度、位置和速度变化边界。这里的 10 s 是模拟时间,不是程序耗时,更不是计算实时性的证明。可下载本次完整闭环 CSV

8.4 双关节 WBC:为什么逐关节截断不够

前面的标量任务只有一个自由度,无约束最优值截断到区间后恰好就是约束最优解。现在增加第二个自由度,让两个任务同时使用两个关节:

J=[1111],b=[10]. J=\begin{bmatrix}1&1\\1&-1\end{bmatrix},\qquad b=\begin{bmatrix}1\\0\end{bmatrix}.

第一项希望两个关节速度之和为 1 rad/s,权重为 4;第二项希望二者速度之差为零,权重为 1。这是关节线性组合的教学任务,不是由具体人形机器人几何推导的末端雅可比。两个任务共享 u 的两列,因此任何一个关节改变都会同时影响两项残差。

加入 0.1I0.1I 正则后,目标与矩阵为:

L(u)=12[4(u1+u21)2+(u1u2)2+0.1(u12+u22)],H=[5.1335.1],g=[44]. \begin{aligned} L(u)&=\frac12\left[4(u_1+u_2-1)^2+(u_1-u_2)^2 +0.1(u_1^2+u_2^2)\right],\\ H&=\begin{bmatrix}5.1&3\\3&5.1\end{bmatrix},\qquad g=\begin{bmatrix}-4\\-4\end{bmatrix}. \end{aligned}

令第一关节速度边界为 ±0.2 rad/s,第二关节为 ±0.8 rad/s。三种结果如下;代价列均使用上面的完整目标,包含相同的常数项:

求解方式u1u_1u2u_2结果
无约束解0.4938270.493827第一关节越界
对无约束解逐元素截断0.20.493827可行,代价约 0.244844
在边界内重新求 QP0.20.666667可行,最小代价约 0.168667

固定第一关节在上界 0.2 后,对第二关节求导:L/u2=3(0.2)+5.1u24\partial L/\partial u_2=3(0.2)+5.1u_2-4。令其为零,得到 u2=2/3u_2=2/3,仍在第二关节边界内。QP 允许第二关节重新分担第一关节被限幅后留下的任务误差,而逐元素截断把第二关节留在旧值上。

最优解的两项任务残差约为 −0.133333 与 −0.466667,都没有变成零。权重较大的速度和任务获得了更好的满足程度,但这仍是加权折中,不是严格层级控制。对第一个关节,上界乘子为 0.98;对第二个关节,驻点导数为零,与活动约束条件一致。

运行 python atomic_control.py 会自动执行 coupled_wbc(),检查最优速度是否为 [0.2, 2/3],并确认约束 QP 的代价小于截断方案。结果保存在报告的 coupled_wbc 字段,也可以单独查看:

from atomic_control import coupled_wbc

result = coupled_wbc()
print(result["command"])
print(result["clipped_objective"], result["optimal_objective"])

该实验把“任务共享自由度”的含义落到了数值上。换成真实机器人后,J 随构型变化,关节限位也可能产生状态相关的速度边界,但关于耦合优化与逐元素截断的区别仍然成立。

8.5 大权重与严格优先级:用同一组任务对照

上一节展示两个任务如何折中,但没有实现“先保证任务一,再优化任务二”。本节沿用相同关节边界,将速度和目标从 1 改为 0.6 rad/s,让高层任务存在多个可行最优解,观察低层如何使用剩余自由度。

设高优先级任务为 u1+u2=0.6u_1+u_2=0.6,低优先级任务为 u1u2=0u_1-u_2=0,边界仍为 u10.2|u_1|\le0.2u20.8|u_2|\le0.8。两个任务同时精确满足需要 u1=u2=0.3u_1=u_2=0.3,这会使第一关节越界,因此控制器必须处理冲突。

先看有限权重的结果

加权版本在同一个目标中计算:

Lw=12[w(u1+u20.6)2+(u1u2)2+0.1(u12+u22)]. L_w=\frac12\left[w(u_1+u_2-0.6)^2+(u_1-u_2)^2 +0.1(u_1^2+u_2^2)\right].

在本例的三个权重下,第一关节均达到上界 0.2,第二关节为 u2=(0.4w+0.2)/(w+1.1)u_2=(0.4w+0.2)/(w+1.1)。因此高层残差为:

rhigh=u1+u20.6=0.24w+1.1. r_{\mathrm{high}}=u_1+u_2-0.6 =-\frac{0.24}{w+1.1}.

增大 w 会减小残差,但对这些有限权重,残差仍不为零。优化器愿意牺牲少量速度和跟踪精度,换取更小的速度差与正则代价。

再看真正的两级求解

第一级仅最小化 12(u1+u20.6)2\frac12(u_1+u_2-0.6)^2,并保留关节硬边界。速度和在边界内能覆盖 [−1, 1],所以目标 0.6 可达,第一级的最优残差为零;例如 [0.2, 0.4] 就能实现。

第二级将已经达到的高层结果保留下来,在 u1+u2=0.6u_1+u_2=0.6 上优化速度差与小正则。写成 u=[t,0.6t]Tu=[t,0.6-t]^T 后,高层等式自动成立。由两关节边界得到:

max(0.2,0.60.8)tmin(0.2,0.6+0.8), \max(-0.2,0.6-0.8)\le t\le\min(0.2,0.6+0.8),

t[0.2,0.2]t\in[-0.2,0.2]。第二级变成一个标量 QP:

mint  12[(2t0.6)2+0.1(t2+(0.6t)2)]. \min_t\;\frac12\left[(2t-0.6)^2+0.1\big(t^2+(0.6-t)^2\big)\right].

它的无约束最优点为 t=0.3,约束最优点为 t=0.2,最终命令是 [0.2, 0.4]。低层速度差仍为 −0.2,但它没有改变高层已经达到的零残差。

方法u2u_2(rad/s)高层残差(rad/s)低层残差(rad/s)
权重 40.352941−0.047059−0.152941
权重 400.394161−0.005839−0.194161
权重 4000.399402−0.000598−0.199402
两级任务优化0.4000000−0.200000

所有行都满足相同硬边界,表格比较的是任务冲突处理方式。严格层级会接受更差的低层残差;是否采用它,应由任务之间是否允许交换误差决定。把 w 不断调大并不能替代层级建模,还可能增大数值尺度差异。

代码如何保证低层不破坏高层

priority_wbc() 中,第一级通过可达区间和可行解证明零残差最优。第二级使用 anchor=[0,0.6]null=[1,-1]^T,令 u=anchor+nulltu=\mathrm{anchor}+\mathrm{null}\,t;因为 [1,1] @ null = 0,调整 t 不会改变速度和。将低层 Hessian 与梯度变换到 t 上,再调用同一个微型 QP 求解器。

这个实现是特定两任务问题的解析降维,不是可直接接入任意机器人任务的通用层级 QP。第一级没有加入任意正则去预先选定唯一解;正则放在第二级,因而不会提前占用低层可利用的自由度。

运行 python atomic_control.py 后,报告的 priority_wbc 字段包含全部对照。脚本用闭式解检查加权结果,并检查两级结果和高层残差;也可单独调用:

from atomic_control import priority_wbc

result = priority_wbc()
for row in result["weighted"]:
    print(row["weight"], row["high_residual"])
print(result["hierarchical"])

最后要区分“高优先级软任务”与“硬约束”:若高层目标本身不可达,层级优化应先找到它在硬边界内能达到的最优残差,再要求低层不使该结果变差,不能直接把不可达目标强行写成等式。本例能用速度和等式消元,是因为第一级零残差已经得到证明。

8.6 高层目标不可达:保留最优残差,而不是强行等于目标

上一节的高层速度和 0.6 rad/s 可以实现。现在仅把目标改为 1.2 rad/s,保留 u10.2|u_1|\le0.2u20.8|u_2|\le0.8,低层仍希望两个关节速度相同。这时高层任务自身已经无法精确实现。

第一级应求什么

设高层目标为 s,实际速度和为 y。关节边界允许的速度和区间是 [−1, 1],所以第一级等价于:

miny[1,1]  12(ys)2. \min_{y\in[-1,1]}\;\frac12(y-s)^2.

对 s=1.2,最优值出现在 y=1y^\star=1,最优残差是 r=ys=0.2r^\star=y^\star-s=-0.2,代价为 0.02。零残差不可达,但这个带边界的优化问题本身可行;求解器正常返回非零任务残差,并不代表它求解失败。

若反过来把 u1 + u2 = 1.2 当成硬等式,关节上界又要求 u1 + u2 <= 1.0,两者直接矛盾。这里应拒绝产生命令,而不是用软任务的残差解释硬约束违约。

第二级应保留什么

第二级必须保留已经求出的最优高层结果 u1+u2=1u_1+u_2=1,而不是继续要求原始目标 1.2。由于两关节都必须达到上界才能实现速度和 1,唯一可行命令为 [0.2, 0.8]

使用上一节的降维方式,令 u=[t,1t]Tu=[t,1-t]^T,边界给出:

max(0.2,10.8)tmin(0.2,1+0.8), \max(-0.2,1-0.8)\le t\le\min(0.2,1+0.8),

即 t 只能为 0.2。低层已经没有可利用的自由度,只能接受速度差 −0.6 rad/s。它无法在不破坏高层结果或关节边界的前提下继续改善。正向与反向的实际结果如下:

高层目标(rad/s)命令(rad/s)高层残差低层残差
+1.2[+0.2, +0.8]−0.2−0.6
−1.2[−0.2, −0.8]+0.2+0.6

这里的“残差”始终定义为实际任务值减去目标值,两列单位均为 rad/s。正向目标的高层残差为负,是因为实际速度和小于目标;反向目标则相反。

运行与验证

atomic_control.pyunreachable_priority() 先根据区间得到最优速度和,再检查保持该速度和的可行区间是否退化为单点。此例无需再次调用优化器选择低层结果,因为已经没有选择空间。脚本同时把原始目标写成硬等式交给微型 QP,检查这组相互矛盾的硬约束确实被拒绝。

from atomic_control import unreachable_priority

for case in unreachable_priority():
    print(case["target"], case["command"], case["high_residual"])
    print("硬等式被拒绝:", case["hard_target_rejected"])

完整运行 python atomic_control.py 时,这些结果自动写入报告的 unreachable_priority 字段。代码分别检查正反向命令、0.2 rad/s 的最优残差绝对值和硬等式拒绝结果。remaining_interval 的两端可能相差浮点舍入量,判定单点时使用 101210^{-12} 容差,不要求十进制字符串完全相同。

这个例子的区间投影与单点判断依赖于“两个关节的标量速度和、独立盒边界”。一般多维任务需要先实际求解高层,再通过适当的约束保留其最优结果;不能对所有任务目标逐元素截断,就称为层级控制。教学求解器的通用异常仍可能表示数值未解决,本例之所以能明确判定硬等式不可行,是因为速度和上界已经提供了独立证明。

读控制日志时,应分别查看硬约束可行性、求解器状态、高层最优残差与低层残差。持续存在的高层残差提示需要检查目标可达性或上层规划;它不应被自动归因于求解器错误,也不应通过放松原本必须满足的物理边界来掩盖。

9. 可运行 Python 对照:相同 WBC,不同参考

9.1 环境、执行命令与结果

下载 wholebodyx_demo.py。两组都使用同样的六关节模型、初始状态、目标、PostureTask 和 WBC;一组直接跟踪目标,另一组跟踪 MPC 管理器提供的位置与速度参考。这避免把不同任务配置的差异误算成 MPC 收益。

先安装你自己的 WholeBodyX 源码,以下路径是本次参考目录,应按实际位置替换:

python3 -m venv .venv-control
source .venv-control/bin/activate
python -m pip install /home/ubuntu/workspace/chase/WholeBodyX
python -B wholebodyx_demo.py --output results-control

安装后,示例不需要 PyTorch、预训练模型、URDF 或可视化组件;核心依赖是 NumPy、SciPy 和 OSQP。本文实际使用 WholeBodyX 已有环境运行,版本为 NumPy 2.3.5、SciPy 1.17.1、OSQP 1.1.3;新建环境若解析到其他依赖版本,应记录后再比较。

250 个 0.02 s 控制步骤后,两组关节误差二范数分别约为 7.18×10107.18\times10^{-10} rad 与 5.03×10165.03\times10^{-16} rad,初始均为 0.52915 rad。MPC 组实际规划 50 次、复用参考 200 次。可下载运行报告直接 WBC CSVMPC→WBC CSV

脚本逐步检查积分关系和执行速度的组合边界,违约容差为 10610^{-6};运行中的最大速度边界违约分别约为 3.07×10113.07\times10^{-11}1.11×1091.11\times10^{-9} rad/s。这是数值容差内的运动学验证,不是两种方案速度、鲁棒性或真机安全性的排行榜。简单可达的姿态目标本来就不要求 MPC 才能完成。

如果使用仓库现有 h1_mpc_wbc.py,还需要相应 URDF 与可选依赖。其固定骨盆原地踏步、运动学支撑脚对齐和虚拟物体附着,不等于浮动基动力学、刚性抓取接触或物理碰撞仿真。

9.2 一条参考怎样经过 WBC 变成运动

沿 wholebodyx_demo.py 的循环看四个步骤:

  1. plant.read() 读取当前 q、v 和时间戳。此处是仿真状态读取,真机应来自状态估计与关节测量。
  2. manager.update(state, goal, dt) 返回当前参考位置与速度;失败立即报错。直接 WBC 组则使用固定目标位置和零速度前馈。
  3. PostureTask(...).linearize(model, state) 生成速度任务,controller.step(...) 在边界内协调并求解命令。
  4. plant.write(...) 执行积分,随后重新读取状态,检查实际执行结果,并进入下一拍。

对姿态任务,雅可比就是单位矩阵,目标速度为:

b=kp(qrefq)+vref. b=k_p(q^{\mathrm{ref}}-q)+v^{\mathrm{ref}}.

第一项纠正位置误差,第二项提供参考轨迹本身的运动速度。当 MPC 刚从当前状态启动时,qref=qq^{\mathrm{ref}}=q,位置反馈为零,但速度前馈仍可让机器人开始移动。若遗漏前馈,初始这一拍就没有该项驱动力,后续主要依赖位置误差追赶。对末端任务,雅可比不再是 I,多个末端可能争用同一关节自由度。

这与动力学 WBC 的加速度任务相对应,但不能混用单位。动力学版本通常构造 x¨cmd=x¨ref+Kpe+Kd(x˙refx˙)\ddot x^{\mathrm{cmd}}=\ddot x^{\mathrm{ref}}+K_p e+K_d(\dot x^{\mathrm{ref}}-\dot x),再用 Jv˙+J˙vJ\dot v+\dot Jv 跟踪;速度级版本直接用 JuJu 跟踪速度目标。

本例的三个时间量应分开理解:

参数数值决定什么
MPC 预测节点间隔0.1 sN=15 时覆盖 1.5 s 未来
参考管理器默认重规划周期0.1 s周期刷新计划的触发条件之一
WBC/积分步长0.02 s每拍消费参考与执行的模拟间隔

计划内部位置做分段线性插值,速度在每个预测区间内保持常值,因此 WBC 可以在较密的时刻取参考。预测间隔与重规划周期在这里恰好相等,两者不是同一个参数,也不要求永远相等。

9.3 参数实验与排错

先保留默认输出,再一次只改一个条件。atomic_control.py 中的断言针对默认配置;改变模型参数后,应同步重新推导预期结果,保留硬约束检查。

实验修改位置应观察什么
反向目标调用 mpc(-0.3)初始速度应为 −0.2、−0.4,检查符号与边界对称性
位置已经到达目标调用 mpc(0.3, q0=0.3, v0=0.)最优速度为零;常数位置不需要额外控制代价
初始速度非零调用 mpc(0.3, q0=0., v0=0.1)首步速度变化相对 0.1 而非零约束
WBC 交换两项权重wbc() 中改成 [1., 4.]输出由正方向转为负方向,任务权重影响折中
减小速度变化边界MPC 的末两行上下界同步缩小初始运动变缓;不能只修改一侧边界

可以在下载脚本的同一目录运行一个额外调用:

from atomic_control import mpc

result = mpc(0.3, q0=0.0, v0=0.1)
print(result["velocity"])  # 约 [0.3, 0.5]
print(result["kkt"])

QP 输入与错误类型

修改 QP 数据时,先确认下面的输入约定。solve_tiny_qp() 接受实数列表或 NumPy 数组,并统一转换为浮点数组:

输入要求
g一维向量,长度 n 为 1 或 2
Hn×n,对称且正定;对称误差绝对容差为 101210^{-12}
Am×n,至少一行约束
lower / upper两个一维向量,各有 m 个元素
全部数据有限实数,不接受 NaN、Inf 或复数

例如列向量 g.shape == (2, 1) 不能当作 (2,) 使用。形状不匹配会先抛出 InvalidQPInput,不会等到矩阵广播或线性求解时才报错。近似对称且在容差内的 H 会先取对称部分;这里要求 H 正定,是微型求解器的教学范围,并不意味着所有凸 QP 都必须严格正定。

QPSolveError 则表示没有得到有效命令。若明确发现 lower > upper,边界本身已经矛盾;若所有候选都未通过检查,原因可能是约束不可行,也可能是数值未解决。后者不能仅凭异常类型断言“物理任务不可能完成”。这两个异常都继承 ValueError,原有示例的失败处理仍然适用。

求解器现在先检查线性方程解和残差数组是否有限,再计算最大违约量。NaN 不能参与普通大小比较后被当成零残差,Inf 也不能成为关节命令。下载包包含专门的回归测试:

python -m unittest -v test_atomic_control.py

默认应通过九项测试,覆盖合法列表输入、形状错误、非有限/复数输入、非正定或不对称 H、约束矛盾,以及模拟线性求解返回 NaN/Inf 的情况。其中非有限解测试检查的是故障返回路径,不是在模拟真实机器人。新增测试还覆盖 MPC 标量输入、初始状态、不可达软目标,以及错误类型的传播。之后再运行 python atomic_control.pypython feedback_demo.py,检查正常实验结果是否仍然符合预期。

MPC 入口:当前状态与软目标分开检查

mpc(goal, q0, v0) 是单关节示例,三个参数均应为有限的实数标量。goal=[0.3, 0.4] 不会被解释为两步参考轨迹,布尔值和字符串也不作为目标接受。若要预测时变参考,应显式扩展目标序列接口与梯度构造,不能依赖数组广播碰巧生成一个可求解的问题。

该教学入口要求初始位置在 [−0.5, 0.5] rad、初始速度在 [−1, 1] rad/s。规划约束只描述未来节点,不能用未来回到范围内的计划来隐含证明当前状态已经满足边界。超范围初态会先抛出 InvalidQPInput;如果要研究从越界状态恢复,需要另行定义初态与恢复约束。

软目标不受相同的输入范围限制。例如 mpc(2.0) 可以正常求解:第一条计划仍给出约 [0.2, 0.4] rad/s,未来位置遵守关节边界,代价函数接受较大的跟踪误差。这里没有把目标 2.0 强行等同于某个未来状态。

可以用三次调用区分不同层次:

  1. mpc(2.0):输入合法,软目标超出可达位置范围,允许返回有跟踪误差的计划。
  2. mpc(0.3, q0=0.51):违反本例的初态接口,属于 InvalidQPInput
  3. mpc(0.5, q0=0.46, v0=0.6):逐项初态边界满足,但制动余量不足,属于本例已推导的 QPSolveError

短时域反例现在只捕获 QPSolveError。如果传入错误形状或其他无效状态,InvalidQPInput 会继续向外报告,避免测试把“输入构造错了”计为预期的无解结果。两类错误虽然都兼容 ValueError,调用方需要分类诊断时,应优先捕获更具体的类型。

import wholebodyx 失败,先确认运行脚本的解释器与安装源码使用的是同一个环境;NumPy 原子实验不需要该依赖。若求解失败,先检查初始状态、边界交集和步长,再检查目标与任务配置。软目标太远并不必然不可行:优化器可以留下跟踪误差;互相矛盾的硬边界才会直接排除所有可行解。

若命令持续贴住边界而任务误差不降,应查看哪些边界激活、目标是否可达、坐标系是否一致。若模型中没有力矩、摩擦或碰撞约束,即使所有 CSV 检查通过,也不能推导出这些物理量满足要求。扩展到浮动基人形机器人时,需要更换预测/执行模型,并补上接触、动力学和状态估计,而不是只把六关节数组改成更长的数组。

9.4 扰动实验:执行旧指令与重新规划有何不同

先下载 feedback_demo.py,与 atomic_control.py 放在同一目录,在已有 NumPy 环境执行:

python feedback_demo.py --output results-feedback

实验先生成无扰动的名义闭环速度序列,再让两组从相同初值出发。到模拟时间 3 s 时,将位置状态减去 0.08 rad,随后立即向反馈组提供改变后的状态。回放组继续执行记录的名义速度;反馈组每拍重新求解 MPC。两组的积分模型、目标、边界、步长和注入偏移均相同。

这次偏移是人为的位置状态跳变,速度状态保持不变;它用于检查反馈的数据流,不是刚体受推冲量,也不表示模型已经模拟了接触或传感器噪声。回放组使用的是完整名义运行中记录的命令,不能误读成最初一个两步预测窗口覆盖了整段 10 s。

时刻或指标回放名义命令根据新状态重规划
3 s 偏移后的测量位置约 0.22 rad约 0.22 rad
偏移后第一拍命令约 0 rad/s约 0.2 rad/s
执行到 3.1 s 的位置约 0.22 rad约 0.24 rad
10 s 时绝对位置误差约 0.08 rad本次运行中为 0

在 3 秒施加负 0.08 rad 位置偏移后,回放组停在约 0.22 rad,重规划组通过新的速度命令恢复到 0.3 rad
由本次实验 CSV 绘制,放大显示 2.5–5 s;上图为位置,下图为速度命令。虚线是状态偏移时刻,颜色表示两种反馈使用方式。

为什么反馈组第一拍只恢复 0.02 rad?偏移之前速度约为零,速度变化率边界允许新命令最多增加 0.2 rad/s;再乘以 0.1 s,只能前进 0.02 rad。偏差进入新初值后,MPC 改变后续序列,但仍要遵守硬边界。

两组都逐拍检查位置、速度和速度变化约束;脚本也检查最终误差是否符合预期。这个对照说明重新使用测量值能修正该积分模型中的偏移,不能据此认定 MPC 比其他反馈控制器更强。位置反馈、LQR 或其他控制器也可能纠正这种简单误差,本文没有进行这些比较。

下载运行报告回放组 CSV反馈组 CSV。报告中的 first_after_offset 是第 31 拍,time=3.1 表示执行结束时刻;该行 measured_position 对应 3 s 时注入后、执行前的测量。需要自己重画图时,将 plot_feedback.py 放在同一目录:

python -m pip install matplotlib==3.10.6
python plot_feedback.py --input results-feedback --output results-feedback/feedback-comparison.png

绘图依赖是可选项,控制实验本身仍只需要 NumPy。

9.5 当前窗口有解,下一拍为什么仍可能无解

feedback_demo.py 还运行一个容易被忽视的边界案例:初始位置 0.4 rad、初始速度 0.8 rad/s,目标 0.5 rad。两步 MPC 给出速度 0.6、0.4 rad/s,位置依次为 0.46、0.50 rad,预测窗口内每个节点都满足约束。

执行第一步后,以位置 0.46、速度 0.6 重新规划。此时最快也只能每拍减速 0.2 rad/s,因此未来两个速度至少为 0.4、0.2 rad/s,对应位置至少为 0.50、0.52 rad。第二个位置越过 0.5 rad 上限,新问题已经无解。

这个例子中的初始状态虽然位于位置和速度的逐项边界内,却已来不及在上界之前停下。原来的短窗口只看到了到达边界,没有看到到达时仍有正速度。加入终端位置代价、提高其权重,都不能自动证明“之后总能继续满足约束”。

对当前离散积分与速度变化模型,向正方向最快制动时,未来指令依次为 max(vjamaxΔt,0)\max(v-j a_{\max}\Delta t,0)j=1,2,j=1,2,\ldots。从 0.8 rad/s 开始,指令为 0.6、0.4、0.2、0,对应最短前进距离 0.1(0.6+0.4+0.2)=0.120.1(0.6+0.4+0.2)=0.12 rad;剩余位置空间只有 0.10 rad,二者已经矛盾。这里依据的是离散指令模型,不能直接当成真实执行器的连续制动距离。

工程中需要考虑初始状态是否允许后续持续可行、终端集合、可持续执行的局部控制策略,以及模型误差和扰动余量。延长时域能让这个案例更早暴露矛盾,但不会让一个已经无法及时制动的状态重新变得可行。相关有限时域建模背景见 MIT 轨迹优化课程

教学求解器的异常文本同时涵盖“不可行或数值未解决”;本例能判定不可行,是因为上述边界推导已经排除了全部解,而不是因为任意异常都能被当作不可行证明。下载报告中的 horizon_trap 字段记录第一条有效计划与下一次拒绝结果。

9.6 参考失效实验:过期、刷新失败与时间回退

在安装 WholeBodyX 源码的环境中运行 reference_failures.py

python -B reference_failures.py

这个脚本只调用参考管理器,不执行机器人或积分器。为了把失败行为和真实求解器性能分开,它用一个适配器在指定的一次调用中返回错误;其余调用仍交给实际 JointMPC。测试按以下顺序推进:

序号操作应看到的结果
1时间戳 0,正常求解success=true,存在计划
2valid_until 恰好取样抛出异常,终点不属于可取样区间
3时间戳 0.02,改变目标并注入错误success=false,参考位置/速度均为空,旧计划清除
4时间戳 0.04,恢复正常求解新计划生成
5从 0.04 回退到 0.03clock_rollback,计划清除
6时间戳增加到 0.06仍拒绝输出
7manager.reset() 后重新更新可重新生成初始计划

刷新失败时,旧计划在时间上仍覆盖当前控制间隔;脚本刻意使用这种情况,确认“尚未过期”不意味着管理器会自动回退执行旧目标。reason 表示为什么触发更新,例如 goal_changed;它不是求解器成功与否的替代字段,应同时检查 success 和求解器状态。

时间回退会使当前时间轴失效,在此实现中需要显式重置。测试里的重置只是验证 API 状态迁移;真机遇到时钟问题时,还需要重新建立一致的状态、目标与执行时间轴。返回空参考同样不意味着硬件自动停止,运行层必须定义适合执行接口的处理方式,不能无条件把速度或力矩设为零就宣称安全。

可对照实际运行报告。这组验证覆盖参考生命周期和错误传播;不覆盖真实求解超时、通信断线、落脚冲击或动态平衡。

10. 与策略学习怎样组合

PPO、DPO、GRPO 是训练目标或训练方法,MPC/WBC 是在线规划控制模块,两者不是互斥选项。策略可以输出速度命令、目标姿态、任务参考或有界残差,再由控制器处理当前模型中的约束;也可以用优化控制器产生示范,训练策略近似其行为。

若策略动作先经过 WBC/QP 修正,训练时应保存原始采样动作及其概率,同时单独记录实际执行命令;不能拿修正后的命令去匹配原动作分布的 old logp。把控制器作为环境的一部分时,回报应来自整个组合闭环。训练与部署的控制器、限幅和观测协议发生变化,就可能引入分布偏移。更详细的训练衔接见 PPO、DPO 与 GRPO 的控制章节

具体 WholeBodyX 能力以所核对源码为准。进一步研究全身最优控制与反馈增益,可阅读 Crocoddyl 官方项目

实验完成后,按结果复盘

下面的路径均相对解压后的 control-lab 目录。先确认命令正常退出,再检查报告字段;输出目录已经存在,不代表本次运行一定成功,也不代表里面的文件已经被更新。

  1. MPC 原子问题与滚动运行:打开 results-atomic/report.jsonmpc 中速度应为 ±[0.2, 0.4] 或零;rollout 最终误差应通过阈值检查。
  2. 耦合任务如何分配速度:查看同一报告的 coupled_wbc。QP 代价应小于逐关节截断方案,约束均满足。
  3. 加权与严格层级有何区别:查看 priority_wbc。有限权重仍留下高层残差,两级求解保留最优高层结果。
  4. 高层目标不可达怎么办:查看 unreachable_priority。软任务最优残差绝对值为 0.2,相同目标作为硬等式被拒绝。
  5. 反馈是否使用了新状态:打开 results-feedback/report.json。回放组最终误差约 0.08,反馈组恢复目标。
  6. 短窗口是否保证后续可行:查看反馈报告的 horizon_trap。第一条计划可行,下一拍因制动余量不足被拒绝。

读懂一行 CSV:时间、状态与命令怎样对齐

最终误差只描述终点,不能证明中途满足约束。对本文的默认 NumPy 实验,CSV 中第 k 行记录一次执行后的结果:time 是该拍结束时刻,velocity 是这一拍采用的命令,position 是执行后位置。第一拍从位置零开始,以 0.2 rad/s 执行 0.1 s,因此记录的位置为 0.02 rad,误差为 0.30.02=0.28|0.3-0.02|=0.28 rad。

检查相邻行时,使用以下关系;没有偏移的普通运行取 δqk=0\delta q_k=0

qk=qk1+δqk+0.1uk. q_k=q_{k-1}+\delta q_k+0.1u_k.

这里 k 是 CSV 的执行行索引,从 1 开始,与 MPC 窗口内从 0 开始的预测索引不同。feedback.csvreplay.csvoffset 就是 δqk\delta q_kmeasured_position 应等于 qk1+δqkq_{k-1}+\delta q_k。偏移发生在 3 s,而含偏移的那一行结束于 3.1 s,两者描述的是同一拍的开始与结束。

还应检查每行位置绝对值不超过 0.5 rad、命令绝对值不超过 1 rad/s、相邻命令差绝对值不超过 0.2 rad/s;第一拍命令差相对初始速度零计算。absolute_error 应等于实际位置到 0.3 rad 目标的距离,不能用预测窗口末端的位置代替它。最后再把 CSV 末行与报告的 final 字段对照。

如果仅检查 JSON 中最终误差,删掉中间行、写错时间戳或把预测状态误写成执行状态,都可能未被发现。完整轨迹核对能帮助定位记录与模型的不一致,但它验证的是当前积分实验;一份内部自洽的日志仍不等于真实机器人模型准确。

默认实验的误差阈值、步长和参数已写入对应章节与脚本。不要把某个浮点输出恰好为零当成通用精度保证;改变模型或参数后,要重新核对预期结果。若 JSON 中缺少本文所述字段,先确认下载包是否为当前版本,以及运行的是哪个目录下的脚本。

三个迁移问题:先手算,再展开核对

高层速度和改成 0.9 rad/s,严格层级能否精确满足?

能。速度和仍在可达区间 [−1, 1] 内,高层最优残差为零。保留 u1+u2=0.9u_1+u_2=0.9 后,低层希望速度相同,对应无约束解 [0.45, 0.45],第一关节越界;最终取 [0.2, 0.7],高层残差为零,低层速度差为 −0.5 rad/s。这里沿用第 8.5 节的硬边界和第二级正则。

与目标 1.2 的区别在于:0.9 本身可达,低层只是无法实现速度相等;1.2 则连高层零残差都无法达到。

步长从 0.1 s 改成 0.02 s,速度变化边界还能保持 0.2 rad/s 吗?

若保持速度变化率上限为 2 rad/s²,新的每拍速度变化边界应为 2×0.02=0.042\times0.02=0.04 rad/s。继续使用 0.2 rad/s,相当于允许 10 rad/s²,已经改变了模型。

还要同步检查预测矩阵、积分执行与时域长度。N=2 不变时,预测未来从 0.2 s 缩短为 0.04 s;要维持 0.2 s 的时域,需要 N=10。但本文的枚举求解器只支持最多两个决策变量,不能仅修改 N 就继续使用它,需要换成能处理更大问题的求解器。

扰动对照恢复了位置,能否证明 WBC 或动态平衡有效?

不能。该对照直接将 MPC 第一个速度送入单关节积分器,没有经过 WBC,也没有接触力或浮动基动力学。它验证的是这个模型下“重新使用测量状态”与“回放名义命令”的区别。

要评价 WBC,应查看六关节同任务对照及其约束;要评价动态平衡,则需具备对应动力学、接触、估计与执行模型的实验。不同实验回答不同问题,不能将一个层面的通过结果替代另一个层面的证据。

阅读自测与验收

  • 给每一条模块接口写清传递的是状态、轨迹、接触计划还是关节命令,并注明时间戳和坐标系。
  • 模拟估计延迟、优化超时或接触变化,确认下层如何处理过期参考;模块频率高不等于闭环一定稳定。
展开核对:控制接口与验证边界
  • MPC 参考应说明状态/控制含义、时间戳、有效期和坐标系;WBC 输出还需匹配低层伺服接口。
  • 过期参考、求解失败和接触变化必须有运行层处理;平均求解速度无法代替时延与失败恢复检查。
  • WholeBodyX 本文示例只验证固定基座积分关系和关节速度边界;动态平衡、接触摩擦和力矩可行性不在该模型中。
Last updated on