Pinocchio 浮动基专题 03:接口调用、数值技巧与自检实验

这篇关注一个实际问题:程序能运行,是否就说明我调用对了?

对浮动基机器人,数组尺寸对了,也可能用错速度坐标系;姿态看着正常,也可能把四元数当成了欧拉角;得到一个对称矩阵,也可能把质量矩阵对角线加了两遍。比记住函数名更重要的是:为每次调用写出输入约定、缓存前置条件、输出所在空间,以及一个能暴露错误的检查。

阅读路线:专题入口01:模型与流形02:动力学源码 → 本篇。原来的 ABA 入门笔记 保留原地址。

本文固定研究 Pinocchio v3.8.0,源码提交为 655877b314baed68c7e2d4dd56b0a0200bb9f98e;这是教学版本基线,不是“最新版”的声明。下面的 Python 自检与 C++ 调用片段均针对这一版本。不要把不同版本网页中相似的函数签名拼在一起。

1. 先给每次调用写一张小合同

设模型包含一个 free-flyer 和两个单自由度转动关节:nq=9nv=8。这不是所有机器人的固定尺寸,只是本专题自检模型的尺寸。

对象 尺寸 它到底是什么
q model.nq 配置存储;根关节包含位置和单位四元数
v model.nv 切空间速度;根关节前六维为局部线速度、角速度
a / ddq model.nv 与该速度表示配套的广义加速度,不是四元数二阶导
tau model.nv 广义力;根六维是力与力矩,不是电机力矩
M nv × nv 同一广义速度坐标中的质量矩阵
J 6 × nv 空间雅可比,必须同时注明参考系
dtau_dq nv × nv 对配置的切空间扰动求导,不是对 nq 个存储数直接求导

坐标、符号和索引要一起记录。例如:q 的四元数顺序为 x,y,z,wMotion.vector 的顺序是线分量在前、角分量在后。不要仅写“一个 6D 向量”。相关定义见 free-flyerMotion

2. Model 是结构,Data 是会被覆盖的计算现场

一个常见误会是:做了一次 FK,后面的任何查询就都有效。实际应按查询对象满足前置条件。

需要什么 清楚、可读的调用顺序
关节位姿 data.oMi forwardKinematics(model, data, q)
所有 frame 位姿 data.oMf FK 后 updateFramePlacements(model, data);或 framesForwardKinematics
某 frame 的速度 forwardKinematics(model, data, q, v) 后查询 getFrameVelocity
某 frame 的经典线加速度 forwardKinematics(model, data, q, v, a) 后查询 getFrameClassicalAcceleration
frame 雅可比 computeJointJacobians(model, data, q),更新 frame 位姿,再 getFrameJacobian
逆动力学、前向动力学 直接调用相应高层 rnea / aba;它们执行各自需要的递归

这里给的是方便审计的顺序,不是在说每个额外调用都是最小必需集合。先明确数据由谁写入,再根据版本文档优化冗余工作。运动学源码frame 源码 能确认具体缓存的来源。

同一个 Data,不等于每个字段都对应同一个时刻

例如先以 q1 计算 FK,再以 q2 调用另一个算法,不能无条件认为 data 中所有 frame 缓存也都更新到了 q2。如果只调用了只接受 q 的 FK,也不要把此前留下的 data.v 当成本次速度结果。

建议开始时采用两条规则:

  • 一个计算线程使用自己的 Data,不要让多个线程同时写同一个实例。
  • 保存跨调用结果时显式复制;C++ 返回的引用、Eigen 表达式,以及 Python 暴露的缓存视图,都不应被当成独立快照。
data_tau = model.createData()
tau_snapshot = pin.rnea(model, data_tau, q, v, a).copy()

data_mass = model.createData()
M_raw = pin.crba(model, data_mass, q).copy()

这是“容易确认正确”的基线,不是让实时循环不停创建 Data。结构固定后,应在循环外创建工作区、数组和必要副本缓冲;再检查实际调用有没有动态分配。

3. CRBA:矩阵尺寸正确,仍然可能读错一半

v3.8.0 的 C++ CRBA 核心接口需要特别注意上三角结果:不要依赖下三角中某次调用碰巧留下的数值;以完整矩阵做代数运算前,明确恢复对称部分。CRBA 核心约定

但这个版本的 Python 包装已经做了 make_symmetricpin.crba 返回的是完整对称矩阵。下面的 triu 写法是让跨语言教学和核心契约保持一致的防御性处理,不是在说 Python 返回了旧的下三角。这正是为什么读源码时不能停在同名 C++ 函数,还要继续看包装层。Python crba_proxy

upper = np.triu(pin.crba(model, data_mass, q).copy())
M = upper + np.triu(upper, 1).T

这里第二项的 1 很重要:它只复制严格上三角,不再次累加对角线。M + M.T 会把对角线加倍,数值看起来仍很整齐,但物理上已经错了。

C++ 中可以从上三角构造一份完整的自伴矩阵。这段假定 model、q、v、a 已按第 01 篇建立,是算法调用片段,不是独立 main

#include <pinocchio/algorithm/crba.hpp>
#include <pinocchio/algorithm/rnea.hpp>
#include <pinocchio/algorithm/aba.hpp>
#include <Eigen/Cholesky>

pinocchio::Data mass_data(model), bias_data(model), id_data(model);
pinocchio::crba(model, mass_data, q);
Eigen::MatrixXd M = mass_data.M.selfadjointView<Eigen::Upper>();
Eigen::VectorXd h = pinocchio::nonLinearEffects(model, bias_data, q, v);
Eigen::VectorXd tau = pinocchio::rnea(model, id_data, q, v, a);

Eigen::LDLT<Eigen::MatrixXd> ldlt(M);
// 教学前提:惯量物理有效、独立坐标、没有质量退化。
// 真实工程还要检查分解状态、有限性、尺度与求解残差。
Eigen::VectorXd a_from_solve = ldlt.solve(tau - h);

不要替换成 M.inverse() * (tau - h)。分解求解避免构造完整逆矩阵,但 LDLT 不是“输入再坏也稳定”的补丁:质量为零、惯量不合理、数量级悬殊仍然需要处理。

4. 先验证动力学恒等式,再跑复杂控制器

不带外力、接触约束时,定义

h(q,v)=RNEA(q,v,0).h(q,v) = \operatorname{RNEA}(q,v,0).

同一模型、同一 q,v,a 下,应满足

RNEA(q,v,a)=M(q)a+h(q,v).\operatorname{RNEA}(q,v,a) = M(q)a + h(q,v).

再做一次逆向检查:

ABA(q,v,RNEA(q,v,a))a.\operatorname{ABA}\left(q,v,\operatorname{RNEA}(q,v,a)\right) \approx a.

第一条检查质量矩阵恢复、惯量和逆动力学约定;第二条检查逆动力学与前向动力学的一致性。它们仍可能共同使用同一种错误的模型,所以还要增加独立的几何检查,而不是只让两个接口互相证明。

这个闭环不是“机器人已经能执行这个加速度”

将任意 a 输入 RNEA,通常会得到非零的基座六维广义力。这是实现该加速度所需的广义力,不代表自由漂浮基座有六个驱动器。

对本专题的无接触浮动基示例,给电机关节施加命令时,应把广义力的基座部分设成零,再让 ABA 计算整个系统的响应:

tau_command = np.zeros(model.nv)
root_id = model.getJointId("root_joint")
root = model.joints[root_id]
# 基座对应片段保持 0;其余部分才填入这个模型中的关节命令。
tau_command[root.idx_v + root.nv:] = motor_command
a_response = pin.aba(model, model.createData(), q, v, tau_command).copy()

这段切片只适用于本专题“根 free-flyer 后紧接两个电机关节”的模型。一般机器人应建立显式执行器映射,不能认为所有 nv 都是可驱动转轴。接触时还要加入接触力或约束求解,不能把 tau[:6]=0 理解成“基座不会运动”。

4.1 加入 armature 后,哪些恒等式应该改变?

model.armature 是与广义速度对应的附加对角惯量,不是库自动替你建立的电机电气模型。对常数向量 b,本版本把它加到 CRBA 的对角线,并在 RNEA 中加入 b * a;动能接口也计入相应的二次项。CRBARNEA能量实现

把同一模型分别设为零 armature 和非零 armature,保持 q,v,a 不变,可以检查:

MbM0=diag(b),τbτ0=diag(b)a.M_b-M_0=\operatorname{diag}(b),\qquad \tau_b-\tau_0=\operatorname{diag}(b)a.

此时 h = RNEA(q,v,0) 不会凭空多出 b * v:那是黏性阻尼形式,不是惯量项。惯量对应加速度,阻尼对应速度,二者单位也不同。

实验时只给两个电机关节分配小的正 armature,基座六维保留零;先记录原向量,或使用独立模型实例。修改模型后重新计算全部相关量,不能拿“修改前的 M”和“修改后的 RNEA”比较。也不要在 API 已经计入 armature 后,再手动加一遍。

若输入的是转子惯量与减速比,要先确认折算到关节侧的物理约定;不能仅因为字段名里有 rotor,就假定所有接口都会自动使用它。更复杂的转子耦合需要明确模型,不由这项对角参数代替。

4.2 外力必须在等式的两边使用同一份定义

fext[j] 是施加在关节 j 所附刚体上、表达在该关节局部坐标系、关于该关节原点的外部 wrench;数组长度是 model.njoints,包含 universe 槽位。不要把 6 × nv 矩阵或电机命令当成这个容器。

固定同一组外力时,先用带外力的 RNEA 定义偏置,而不是复用无外力 nonLinearEffects

hf(q,v)=RNEA(q,v,0,fext),τf=M(q)a+hf(q,v).h_f(q,v)=\operatorname{RNEA}(q,v,0,f_{ext}),\qquad \tau_f=M(q)a+h_f(q,v).

再将同一 fext 传给 ABA,检查 ABA(q,v,tau_f,fext) ≈ a。这是“外力重载的一致性”,不是已经求解了接触力。RNEA 从刚体所需净力中减去外力,符号可直接从 rnea.hxx 的外力分支核对。

还可以独立核验 tau_no_ext - tau_ext = J_local.T @ wrench_local:只施加一处外力,使用该关节的 LOCAL 雅可比,并保证 wrench 与雅可比具有相同原点。若力实际作用在偏置工具点,先把力矩连同作用点一起变换到关节原点;只旋转三维力会遗漏力臂。

差分时尤其要记录“固定外力”指固定哪一种坐标。保持局部六个数不变,和保持世界中同一个 wrench 不变,是不同实验;后者需要随扰动后的位姿重新变换外力。第 06 篇 进一步说明相应链式法则。

5. 不要对四元数的七维数组做普通有限差分

如果目标是验证 computeRNEADerivatives 的配置导数,扰动属于切空间:

τδjf(integrate(q,ϵej),v,a)f(integrate(q,ϵej),v,a)2ϵ.\frac{\partial \tau}{\partial \delta_j} \approx \frac{f(\operatorname{integrate}(q,\epsilon e_j),v,a) -f(\operatorname{integrate}(q,-\epsilon e_j),v,a)}{2\epsilon}.

其中 e_j 的长度是 nv,不是 nq。这与源码把配置导数表达成流形上的局部扰动相一致;官方接口和 Python 绑定测试见 rnea-derivatives.hpp绑定测试

derivative_data = model.createData()
analytic_q, analytic_v, analytic_a = pin.computeRNEADerivatives(
model, derivative_data, q, v, a
)
analytic_q = analytic_q.copy()
eps = 1e-6
fd = np.empty((model.nv, model.nv))
for j in range(model.nv):
delta = np.zeros(model.nv)
delta[j] = eps
plus = pin.integrate(model, q, delta)
minus = pin.integrate(model, q, -delta)
tau_plus = pin.rnea(model, model.createData(), plus, v, a).copy()
tau_minus = pin.rnea(model, model.createData(), minus, v, a).copy()
fd[:, j] = (tau_plus - tau_minus) / (2 * eps)

这里让 derivative_data 保持存活,并立即复制要保存的矩阵;该版本 Python 包装向结果元组放入缓存的引用,不能忽略其所有权。导数包装层

这次差分保持 v,a 的数值坐标不变,用来比较这个 API 所定义的偏导。如果你的目标是“保持同一个世界坐标速度不变”,而配置变化导致局部坐标也变化,那是另一个扰动实验,需要转换速度并应用链式法则。

为什么 eps 不能一味减小?

中心差分的截断误差通常随 eps² 缩小;浮点相减消去造成的相对影响却大致随机器精度除以 eps 放大。可试 1e-4、1e-5、1e-6、1e-7,看误差是否出现合理的平台或谷底,而不是把最小步长当成真值。

配置中平移用米、旋转用弧度,力与力矩的单位也不同。统一阈值适合一个固定尺度的回归 fixture,不是所有机器人的物理误差标准;生产指标应按每组量的单位和尺度分开检查。

扫描步长:看趋势,不挑一个最好看的数字

把上面的中心差分封装成 central_difference(eps) 后,至少保留完整扫描表。下面是实验框架;函数内部仍必须用 integrate,每一侧独立 Data,并复制输出:

# analytic_q 和 central_difference 来自本节同一 q,v,a、同一模型。
for eps in (1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8):
fd = central_difference(eps)
residual = fd - analytic_q
absolute_error = np.max(np.abs(residual))
relative_error = np.linalg.norm(residual) / max(
1.0, np.linalg.norm(fd), np.linalg.norm(analytic_q)
)
print(eps, absolute_error, relative_error)

这里 1.0 只是固定单位制下避免近零分母的诊断尺度,不是通用的无量纲标准。绝对误差说明“错了多少”,相对指标帮助分辨大矩阵数值大是否就是精度差;不要只保留一个相对数就丢掉行列信息。

对于单位混合的工程模型,应选配置尺度矩阵 D_q 和力/力矩尺度矩阵 D_tau,比较无量纲残差:

Es=Dτ1(JFDJanalytic)Dq.E_s=D_\tau^{-1}(J_{FD}-J_{analytic})D_q.

这时差分第 j 列应沿 eps * D_q[:,j] 扰动,得到的是已经按配置尺度缩放的列;比较时不能再把 D_q 重复乘一次。平移尺度可取典型长度,旋转尺度可取典型角度,输出尺度分别取典型力和力矩,并写入实验记录。

若误差随步长变化几乎不动且明显偏大,优先查参考系、正负号、列顺序、外力是否随姿态更新和缓存所有权;若先下降后上升,再考虑截断与舍入平衡。若只有接近 pi 的姿态出问题,先判断是否跨越对数分支,不能直接放宽全局阈值。

建议额外保存最大误差的 (row, column),映射回关节名及其 idx_v。这样“第 0 行第 5 列失败”才能解释成“哪一个力分量对哪一个局部旋转扰动不一致”。下载脚本保留的是固定步长基线,尚未包含这里的多步长扫描实验

6. LOCALWORLDLOCAL_WORLD_ALIGNED 不是三个别名

参考系 轴方向 空间运动所用原点 常见用途
LOCAL 被查询 frame 的轴 该 frame 原点 局部位姿误差、工具坐标控制
LOCAL_WORLD_ALIGNED 世界轴 该 frame 原点 世界方向表达的足端/末端线速度
WORLD 世界轴 世界原点 完整空间运动变换及相应动力学运算

后两者方向相同,但原点不同。对运动向量,仅旋转线分量和角分量,不等于完整 SE3.act;平移会通过角速度叉乘产生额外项。源码中的 getFrameVelocitygetFrameJacobian 对三种参考系分别处理,见 frames.hxx

一个很有效的自检是:在每一种相同参考系中分别比较

JF(q)vVF(q,v).J_F(q)v \approx V_F(q,v).

然后人为把一边改成 LOCAL、另一边改成 WORLD,检查测试是否真的会失败。fixture 需要非零姿态、非零平移、非零角速度;在单位姿态和原点下,许多错误会被巧合掩盖。

空间线加速度也不能直接当作点的经典加速度

当你要和位置轨迹的二阶导比较时,要辨清 spatial acceleration 与 classical acceleration。Pinocchio 的 frame 经典加速度接口在线分量上加入 omega × v_linear。否则,“运动学位置差分”和“空间加速度接口”之间出现差值,未必是动力学算错了。具体实现

7. 读源码时值得停下来的几个数值细节

7.1 小角度:不是直接把 sin(theta)/theta 算到底

旋转指数映射里有接近 0/0 的系数。数学上它们有连续极限,但直接浮点求值可能消去有效数字。v3.8.0 的四元数 exp3 使用小角度展开;例如,向量部分的系数围绕 sin(theta/2)/theta 展开,而标量部分围绕 cos(theta/2) 展开。

阅读时分别追踪:平方角度 t2、数值阈值、一般角度分支、小角度分支和最终条件选择。不要把某个内部阈值照抄成全项目统一 epsilon。exp3/exp6 四元数实现

7.2 一阶归一化:快,但有“已经接近单位长度”的前提

firstOrderNormalize 的核心可以理解为:设四元数的平方模长为 s,在 s≈1 附近用

1s3s2\frac{1}{\sqrt{s}} \approx \frac{3-s}{2}

对四元数做轻微修正。它用于把刚刚经过稳定运算、仅偏离单位长度一点点的四元数拉回来;不是给任意输入做完整归一化,更不能修复零四元数。源码包含接近单位长度的断言。quaternion.hpp

外部日志或传感器数据进来时,先检查有限性和模长;偏差大时做明确的完整归一化或拒绝输入,再进入算法。Release 模式可能不执行断言,应用层检查不能省掉。

7.3 四元数符号连续性:同一个旋转,可以有两组系数

q-q 表示同一旋转。SE(3) 的积分实现把结果四元数与输入四元数做点积,必要时翻转结果符号,使局部更新更连续。因此日志中的四个数发生整体变号,不代表机器人真的转了一圈。

比较姿态应用旋转误差、difference 或合适的配置等价检查;不要仅比较四元数四个存储分量。接近 pi 的旋转对数还存在分支问题,差分测试应区分局部平滑区域与分支边界。SE(3) integrate 实现

7.4 noalias() 不是“随手加上就更快”

Eigen 表达式可能延迟求值;auto 接住的东西不一定是独立矩阵。noalias() 则是在向 Eigen 保证输出和输入不重叠,错误保证可能产生错误结果。

对于需要跨调用保存的量,先明确写 Eigen::VectorXd / Eigen::MatrixXd.eval()。对于循环中的大矩阵,明确缓冲区所有权后再评估临时对象和 noalias()。官方 IK 示例 同时展示了工作区复用、LDLT 求解和 integrate,值得逐行观察,但它不是完整的实时控制器。

7.5 位姿误差的雅可比还需要 Jlog6

如果误差定义为 log6(current.inverse() * desired),误差雅可比通常不等于原始的几何雅可比。官方 IK 示例对误差所选方向应用 Jlog6 和相应符号,再解阻尼最小二乘。

这里最值得学的不是某个固定负号,而是四件事必须一起成立:误差的乘法顺序、误差所在坐标系、雅可比参考系、配置更新的 integrate 约定。更换其中一个,其他公式可能也要跟着变。

阻尼有助于抑制奇异位形附近的过大步长,却不保证收敛、碰撞安全或关节限位满足。位置和旋转误差混合时,还要定义合理权重。

7.6 接口说明也可能有笔误,调用签名要交叉核对

在 v3.8.0 的 Python ABA 包装中,无外力重载的说明字符串把 vtau 的文字描述写反了,但实际绑定参数顺序与 C++ 接口仍然是 model, data, q, v, tau:第四个是速度,第五个是广义力。不要因为 help() 中某一行文字而交换两个同为 nv 维的数组——这种错误可能根本不会触发尺寸异常。expose-aba.cpp

这也是数值自检的价值:参数名字、绑定签名、底层实现与物理预期应当互相支持,而不是只相信一个入口的描述。

8. 可下载自检:先让错误变得可观测

下载 Python 自检脚本运行说明。脚本手工构建“free-flyer + 两个转轴”的小模型,不依赖机器人网格、私有 URDF 或随机下载的模型包。

它检查的是:状态尺寸、四元数与流形局部闭环、局部线速度到世界位移的关系、RNEA/CRBA/ABA 一致性、无基座驱动的命令响应、切空间导数,以及三种参考系下的 Jv

在已经配置好 Pinocchio 3.8.0 的 Python 环境中运行:

python3 source/downloads/code/pinocchio_floating_base/check_floating_base.py

注意:Python 导入名是 pinocchio,官方 Python 包安装渠道中的 PyPI 项目名是 pin,见 官方安装入口。不要因为模块名相同,就随意安装另一个同名包。环境安装应纳入项目的 Dockerfile 或受控环境配置,不要求为了读笔记修改宿主机的全局 Python。

当前验证边界

本次源码、文章与下载脚本按固定版本核对;尚未执行 Pinocchio 数值实验,也未编译配套 C++ 工程。本专题没有向现有 Docker 环境新增依赖,因此不提供实测误差或运行耗时。后续在选定容器内执行脚本后,应补记 pinocchio.__version__、Python/NumPy 版本、CPU 架构、每项误差和退出码。

脚本产生的通过结果也只适用于这个小模型与这些断言,不代表你的 URDF、接触模型或控制器自动通过。尤其不能把带非零基座广义力的 RNEA→ABA 代数闭环,当成无驱动浮动基的可执行运动计划。

能量漂移要和积分器误差分开看

Pinocchio 的 aba 给出某个状态下的加速度;integrate 是“配置与切空间增量”的流形运算。把它们串在一起,不会自动得到高阶、能量守恒、带碰撞的物理引擎。

例如,以下定义的是一个明确的一阶离散步进,而不是库替你选好的通用仿真器:

# step_data 在循环外创建;tau 的物理来源必须另行定义。
acc = pin.aba(model, step_data, q, v, tau).copy()
v_next = v + dt * acc
q_next = pin.integrate(model, q, dt * v_next).copy()
q, v = q_next, v_next

先更新速度、再更新配置,通常称为半隐式 Euler 风格步进。不过 free-flyer 的局部速度不是欧氏笛卡尔速度或正则动量,不能仅凭这两行就证明它是严格的辛积分器。增加 RK 阶段也不能直接对 nq 维四元数数组套普通 RK4;中间状态、切空间表达和运输同样需要一致。

关闭电机功率输入、非保守外力、阻尼和接触后,连续系统的机械能应守恒。在固定重力场里,应比较 E=T+U,而不是只看自由落体时不断增加的动能;computeKineticEnergycomputePotentialEnergy 的缓存版需要同一状态的 FK,带 q,v / q 参数的高层重载会计算对应状态。能量源码与接口

可按以下顺序增加实验:

  1. 单状态核对 T ≈ 0.5 * v.T @ M @ v,先排除能量与质量矩阵用的模型不同。
  2. 无重力、无外力短时运行;分别记录动能和线/角动量,检查趋势。
  3. 打开恒定重力,比较机械能,并确认势能符号与重力方向一致。
  4. 固定总时长,使用 dt、dt/2、dt/4 重跑,比较终态配置的 difference、速度差与最大能量漂移。
  5. 再加入执行器与外力,检查能量变化是否能由功率积分解释;此时不能再要求 E(t)=E(0)

能量指标可写成 max(abs(E(t)-E(0))) / E_scale,其中 E_scale 是独立记录的正能量尺度。不要直接除以可能为零或因势能零点而任意变化的 E(0)。四元数保持单位长度只说明配置表示有效,不说明能量没有漂移。上述时间积分与能量轨迹目前属于待运行实验,下载自检没有执行长时仿真。

9. 遇到问题时的排查顺序

  1. 打印版本、nq/nv 和每个关节的 idx_q/idx_v/nq/nv,先排除硬编码切片。
  2. 检查四元数顺序、模长、有限性;从 neutral 开始构造小扰动,不从任意七维随机数组开始。
  3. 确认 v 的线/角顺序和局部坐标,不要直接塞世界速度。
  4. 确认 M 上三角已正确恢复,且结果没有被后续调用覆盖。
  5. 为每个 Data 字段找到产生它的调用,排除旧位姿、旧速度或跨线程写入。
  6. 用无外力的 RNEA = M a + h 隔离基础动力学,再逐项增加外力、执行器和接触。
  7. 最后才做性能优化;记录矩阵尺寸、配置、种子与误差,确保优化前后算的是同一个问题。

把失败状态保存为快照,而不只是保存一个随机种子

随机种子有帮助,但模型生成顺序、NumPy 版本或你新增的一次随机调用,都可能改变最终数组。真正可复现的失败案例应保存实际输入,再保存构造它的来源。

# 仅在实验环境中写入自己指定的输出目录;不写入源码或 public。
np.savez(
"failed_case.npz",
q=q.copy(), v=v.copy(), a=a.copy(), tau=tau.copy(),
armature=model.armature.copy(), gravity=model.gravity.vector.copy(),
)

旁边再放一份文本元数据,至少记录:

  • pinocchio.__version__、源码提交、Python/NumPy/Eigen 相关版本、CPU 架构和构建模式。
  • 模型来源及校验和;手工模型要记录所有关节放置、惯量、质量和 COM,不能只记模型名称。
  • q/v/a/tau 单位与坐标约定、frame/joint ID 对应名称、执行器映射与外力作用点。
  • 使用的外力六维数值、每项所用参考系、接触模式、eps 扫描表、误差尺度和容差。
  • 入口命令、退出码、JSON 检查结果;优化实验还要记录线程数、编译选项和时间统计方法。

恢复时先检查有限性、形状、版本和模型校验和,再依次跑几何、单状态动力学、导数、一步离散映射,最后才跑长轨迹。这样可以区分“旧数据在新模型里解释错了”和“算法本身改变了”。读取自建 NPZ 时使用 allow_pickle=False;别用任意来源的 pickle 当作数据快照。

这套记录是在为以后 Docker 内的真实复现准备材料,不等于当前已经有了容器运行证据。将来补报告时,应明确分开“源码已核对”“静态检查通过”“单状态数值通过”和“时间仿真通过”。

10. 用五个问题检查是否真的理解了

  • 为什么 q 有九维,但 dtau_dq 是八乘八?
  • 同一个线速度 [1,0,0],为什么基座旋转九十度后世界位移方向变了?
  • 为什么 crba 的结果不能无脑加上自己的转置?
  • 为什么 tau = rnea(q,v,a) 再交给 aba 成功,不证明电机能让机器人实现这个 a
  • 如果 Jv 与 frame 速度不一致,你会先看误差阈值,还是先看参考系和缓存更新?

能结合小脚本解释这些问题,再沿第 02 篇追到 joint visitor 和三趟递归,会比只背接口名扎实得多。

下一步读 04:完整 C++ 工程,把这些合同落到具名工作区和实际函数调用中。需要接触约束与总动量关系时转 05,需要优化器里的导数和一步 A/B 时转 06

3d打印 actor-critic adaptive sampling ai辅助设计 algorithm algorithms anymal apriltag ardupilot atlas attention axis-angle bang-bang belief encoder blender bode c++ cadquery calibration camera calibration camera-intrinsics chrome cmake cmakelists cnn colcon computer-vision conan control controller_manager cpp cpu d435i dagger data_struct db depth camera depth-camera design-pattern direct collocation dots dtof economics eigen elevation map executor factory-pattern fcpx fiducial marker figure finance forge fourier fov freecad gae gazebo gdb geometry git gnu gru guitar hardware humanoid ibus imu interest isaac gym isaac lab isaaclab kdl laplace latent variable latex launch learning-notes legged locomotion legged robotics legged-robot legged_gym life linux linux-kernel mac math matlab matrix memory mlp money motion imitation motion-control motor moveit mpc mujoco music-theory network neural mapping ocs2 ode openscad operator optimal algorithm optimal-control perceptive locomotion perf performance personal-finance piano pinhole-camera pinocchio pixhawk pixhawk 6c point-cloud policy distillation ppo privileged learning profiling px4 python qgroundcontrol qos quadrotor realsense reinforcement learning representation learning reward tuning rnn robot robot parkour robotics ros ros2 ros2_control rsl_rl rtb security sensor-fusion shell signal-processing sim-to-real simulation socket soft dynamics constraints spot stairs stl stm32 tcp-ip teacher policy teacher student teacher-student temporal convolution terrain reconstruction thread tools tron1 twist ubuntu uml uncertainty unitree unitree g1 urdf vae valgrind vcxsrv velocity vim web wifi wiring work workflow wsl z-transform zero-shot transfer 中文输入 交叉编译 人形机器人 依赖管理 分支管理 动力学 四旋翼 四足机器人 实验诊断 强化学习 接触动力学 数值计算 机器人 机器人控制 机器人视觉 构建系统 浮动基 深度学习 深度相机 点云 版本控制
知识共享许可协议