Physics Metric 统一架构:批判性分析与迁移设计¶
1. 结论¶
建议采用以下单向依赖:
backend / simulator
└── 写 common_rollout.npz
└── pipeline/physics/evaluation.py
└── pipeline/physics/metric.py
└── pipeline/physics/metrics/*
└── pipeline/physics/metrics/utils/*
这个方向总体合理,但需要修正三个容易混淆的点:
evaluation.py不应“负责 rollout”。当前 rollout 是各 backend 在 simulator 中执行并写出的;evaluation.py应是一个薄的兼容边界,只负责把 case、URDF、配置和 rollout 交给统一 metric 入口。否则会把 simulator execution 与 post-hoc measurement 再次耦合。- success 可以作为 metric,但它不是原子测量。Success A/B 是由 tracking、contact 和 completion 派生出的 composite metric;当前明确不读取 progress。代码和配置必须保留这个 依赖关系,不能让 success 模块重新计算一遍底层误差。
- “旋转类别或平移类别”的开关不应覆盖 rollout 里的真实 joint metadata。
common_rollout.npz已保存joint_types;默认应使用auto从 active joint 推断revolute或prismatic。显式配置只能作为一致性断言,不能把 prismatic 数据强行按 rad 解释。
因此,推荐目标不是简单移动文件,而是建立唯一计算真值:
- 原子 metric 全部位于
pipeline/physics/metrics/; - 通用数值工具位于
pipeline/physics/metrics/utils/; pipeline/physics/metric.py是唯一编排和配置入口;- success 与其他 metric 一样由统一入口计算;
evaluation.py不再包含指标公式,只保留兼容调用;- CLI、批量分析和 backend 都调用同一个
metric.py。
2. 为什么现状需要重构¶
当前有两条独立路径:
线上 backend
→ evaluation.py
→ 单一 success / failure_reason
离线 Final Ours
→ compute_metric.py
→ metric.py
→ metrics/*
→ Success A / Success B / continuous metrics
两条路径重复实现了:
- revolute angle unwrap;
- reference phase 识别;
- object articulation progress 和 error;
- human MPJPE;
- contact;
- object root;
- completion / early termination;
- success。
问题不只是代码重复,而是同一个 rollout 存在两个 success truth source。当前 backend 与聚合器实际
使用 evaluation.py,而 Final Ours Metrics 只可离线运行。继续在两套代码上分别修 bug 会让结果
逐渐不可比。
3. 与现有决策材料的冲突¶
本设计参考:
physicalhoi_physics_experiment_decision.htmldocs/experiments/physics_metrics.mddocs/experiments/physics_metric_review.mddocs/experiments/evaluation_strategy.md
这些材料并非完全一致,不能把任意一份直接当成已经冻结的正式协议。
其中决策 HTML 的 progress “边界中位数 + phase 等权”描述已被 2026-08-26
确认的“final 局部窗口方向性 max + phase 帧数加权”替代;HTML 继续作为历史决策证据,
当前实现以本页和 physics_metrics.md 为准。
3.1 决策 HTML 的定义¶
决策 HTML 定义并实现了:
- Success A:episode completion 加可选 mean Human、mean object-joint、contact-missing gates;
- Success B:逐帧 Human、object-joint、contact-missing 的独立 persistent-deviation gates;
- phase-normalized progress、Human MPJPE、active object-joint error、contact coverage 等连续指标。
当前 metric.py 系列基本对应这份定义。
3.2 当前主文档的定义¶
历史 physics_metric_review.md 曾提出:
- outcome 与 success wrapper 分开;
- provisional primary 候选是 terminal-hold normalized progress;
- full-horizon 是 wrapper;
- contact-qualified success 在 pairwise telemetry 通过前只能是 secondary/diagnostic;
- revolute [rad] 和 prismatic [m] 必须分开报告。
3.3 需要人工决定的问题¶
当前决定明确为:
- progress 被计算和报告;
- Success A/B 主要判断 tracking error、contact missing、horizon 和 fall;
- Success A/B 不读取 progress,也不设置 progress threshold;
- progress、Success A、Success B 是三个独立 metric。
统一配置中的 success.primary 只负责从 Success A/B 中选择发布到旧
evaluation.json.success 的 composite metric,不会把 progress 变成 success gate。
3.4 Progress 的当前实现¶
对第 \(k\) 个 movement phase:
- \(q_{0,k}=q^{ref}_{a_k}\):GT/reference 在当前 phase 起始帧的值;
- \(q_{g,k}=q^{ref}_{b_k}\):GT/reference 在当前 phase 终点的值;
- \(\mathcal W_k\):以 phase final 为中心的奇数窗口,默认总宽度为 5 帧;
- max 针对有符号的归一化进度,因此 Open 和 Close 使用同一公式;
- 窗口只读取真实有效帧,不读取 early termination 后的 padding;
- 这是 final 附近的局部 max,不是整个 phase 的 max-over-time。
跨 phase 使用 reference phase 帧数加权:
Hold 不进入 Progress;尚未开始的 movement phase 记 0,但仍保留其 reference 帧数权重。 每个 Open/Close 或拉出/推回 phase 都使用自己的 \(q_{0,k}\),不会共享整条序列的全局起点。
4. 旋转与平移 object metric¶
4.1 统一接口是必要的¶
门、烤箱门等 revolute joint 的配置坐标是角度,单位为 rad;抽屉等 prismatic joint 的配置坐标是 线位移,单位为 m。两者可以共享:
- phase 识别流程;
- normalized progress 公式;
- active-frame 筛选;
- success gate 结构;
- 输出 schema。
但不能共享同一个数值阈值,也不能把两类绝对误差直接平均。
统一接口应输出:
motion_type: revolute | prismatic
position_unit: rad | m
velocity_unit: rad/s | m/s
active_position_mae
active_velocity_mae
per_joint
4.2 开关设计¶
统一配置使用:
语义如下:
auto:从所有 active joints 的joint_types推断;revolute:要求 active joints 都是revolute或continuous;prismatic:要求 active joints 都是prismatic;- 一个 evaluation unit 同时包含 revolute 和 prismatic active joints 时 fail loudly。
最后一条不是技术限制,而是统计保护:rad 与 m 没有可解释的共同平均尺度。未来若确有混合任务,应先按 类型分组计算,再由显式 task-level success 组合,不应在底层偷偷归一化。
4.3 为什么不应使用 object category 名称推断¶
不能通过字符串规则把 drawer 判为 prismatic、把 door 判为 revolute,因为:
- 类别命名可能不稳定;
- 同类资产可能有不同机构;
- 一个资产可能有多个 joint;
- URDF/common rollout 已经提供更直接的 joint type 真值。
类别级开关可以保留给 manifest 人工审核,但运行时必须与 rollout metadata 交叉验证。
5. 目录与职责¶
pipeline/physics/
├── metric.py
├── compute_metric.py
├── evaluation.py # 薄兼容层,不保存公式
├── analyze_metric_rollouts.py
└── metrics/
├── __init__.py
├── config.py
├── engine.py
├── types.py
├── completion.py
├── progress.py
├── human.py
├── object_motion.py
├── contact.py
├── object_root.py
├── success.py
├── legacy.py
└── utils/
├── __init__.py
├── numeric.py
└── signal.py
职责边界:
| 模块 | 允许做什么 | 不允许做什么 |
|---|---|---|
metrics/*.py |
计算一个具名 metric 或 composite metric | 读 YAML、打开 NPZ、写文件 |
metrics/utils/*.py |
无业务命名的校验、平滑、导数、连续区间、角度处理 | 定义 success 或论文口径 |
metric.py |
加载/标准化配置,加载 rollout,构造输入,调度所有 metrics | 启动 simulator |
evaluation.py |
兼容旧调用,解析 case/URDF 需要的 task metadata,调用 metric.py |
重新实现公式 |
compute_metric.py |
CLI 参数和 JSON 输出 | 复制 metric 逻辑 |
analyze_metric_rollouts.py |
调用统一 API 做 sweep/audit | 维护另一套公式 |
legacy.py 是迁移期例外:它保存旧 evaluation.py 的可复现实验协议。它必须有明确的
legacy_contact_actuation_v1 名称,不能伪装成当前 Final metric。
6. 统一配置¶
建议统一顶层为 metrics:
metrics:
schema_version: 1
protocol: final_metrics_v1
object_motion:
type: auto
phase_progress: {}
tracking: {}
human: {}
contact: {}
object_root: {}
completion: {}
success:
primary: null # 只允许 success_a / success_b;与 progress 无关
success_a: {}
success_b: {}
设计原则:
- 每个 metric 只读取自己的 config section;
success只读取其他 metric 的结果和自己的 threshold;primary必须显式配置,不能由 key 顺序或默认值猜测;- 阈值为
null时 fail loudly; - contact reference source 和 simulation source 必须显式声明;
- 配置写
object_motion.type: auto时仍在输出记录解析后的真实类型和单位。
loader 只接受顶层 metrics 和显式 protocol。旧线上公式没有消失,但只能通过
protocol: legacy_contact_actuation_v1 显式选择;不再接受旧
evaluation.thresholds、final_metrics 或 evaluation.final_metrics 包装。
7. Success 作为 metric¶
用户提出“success 与否也是 metric”是合理的,但必须区分:
原子 metric¶
- frame-weighted phase-normalized progress;
- Human global/root-aligned MPJPE;
- object joint position/velocity error;
- contact coverage/missing run;
- object-root pose error;
- valid duration、fall、horizon completion。
Composite metric¶
- Success A;
- Success B;
- outcome success;
- full-horizon success;
- contact-qualified success。
composite metric 只能消费原子 metric 结果,不能重新读取原始数组计算另一份误差。这样可以保证 JSON 中 展示的数值和 success 实际使用的数值完全相同。
evaluation.json 为兼容现有 aggregator,可以继续保留:
但其中 success 应由配置的 success.primary 指向某个具名 composite metric,而不是
evaluation.py 内部硬编码。
8. 对“evaluation 只做 rollout”的批判性修正¶
从概念上说:
- simulator/backend 负责 rollout;
- metric engine 负责 measurement;
- evaluator 负责选择 protocol、绑定 task metadata 并形成可发布 evaluation record;
- aggregator 负责跨 rollout 统计。
因此更准确的说法是:
evaluation.py只做 evaluation orchestration,不做 metric mathematics,也不做 simulator rollout。
如果未来完全取消 evaluation.py,backend 可以直接调用 metric.py;但当前 backend、测试、run record
和 aggregator 都依赖 evaluate_physics_rollout()。保留薄兼容层比一次性删除更安全。
9. 第一阶段实现范围¶
截至 2026-08-26,第一阶段已完成:
- 建立
metrics/与metrics/utils/; - 把 Final Ours 的原子 metric、completion 和 Success A/B 移入具名模块;
- 建立
object_motion.type = auto | revolute | prismatic的统一接口; - 让根
metric.py成为配置、NPZ 和计算的唯一入口; - 删除
metric_entry.py、_metric_progress.py、_metric_tracking.py旧入口; - 把旧线上 evaluator 明确封装为
legacy_contact_actuation_v1; - 让
evaluation.py只保留 orchestration; - 保持现有
evaluation.json与聚合器行为不变; - 增加新结构和 revolute/prismatic 路由测试。
当前仍明确不做:
- 擅自冻结 Final metric 阈值;
- 把 contact proxy 升格为正式 pairwise contact;
- 修改论文 primary;
- 改变已有线上结果语义;
- 把 rad 和 m 混合聚合。
10. 后续迁移 gate¶
只有以下条件完成后,才能把 backend 默认协议从
legacy_contact_actuation_v1 切到 final_metrics_v1:
- metric committee 签署 Success A/B 的 primary 选择、threshold 和 margin;
final_window_frames、phase 分段参数和跨 rollout 聚合方式完成人工签署;- backend 输出统一的 frame-level human-object contact event,或正式关闭 contact gate;
- visible validation 上完成新旧 evaluator 差异审计;
- aggregator 支持新 metric schema、ITT failure 和 valid-prefix coverage;
- revolute/prismatic 分表与 CAD/object-cluster paired bootstrap 测试通过;
- 固定配置和代码 hash;
- private test 解封前停止修改 metric。
在这些 gate 通过前,架构可以统一,但科学口径仍应标记为 provisional。