跳转至

Physics Metric 统一架构:批判性分析与迁移设计

1. 结论

建议采用以下单向依赖:

backend / simulator
  └── 写 common_rollout.npz
        └── pipeline/physics/evaluation.py
              └── pipeline/physics/metric.py
                    └── pipeline/physics/metrics/*
                          └── pipeline/physics/metrics/utils/*

这个方向总体合理,但需要修正三个容易混淆的点:

  1. evaluation.py 不应“负责 rollout”。当前 rollout 是各 backend 在 simulator 中执行并写出的; evaluation.py 应是一个薄的兼容边界,只负责把 case、URDF、配置和 rollout 交给统一 metric 入口。否则会把 simulator execution 与 post-hoc measurement 再次耦合。
  2. success 可以作为 metric,但它不是原子测量。Success A/B 是由 tracking、contact 和 completion 派生出的 composite metric;当前明确不读取 progress。代码和配置必须保留这个 依赖关系,不能让 success 模块重新计算一遍底层误差。
  3. “旋转类别或平移类别”的开关不应覆盖 rollout 里的真实 joint metadata。 common_rollout.npz 已保存 joint_types;默认应使用 auto 从 active joint 推断 revoluteprismatic。显式配置只能作为一致性断言,不能把 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.html
  • docs/experiments/physics_metrics.md
  • docs/experiments/physics_metric_review.md
  • docs/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:

\[ \tilde p_k = \max_{t\in\mathcal W_k} \frac{q_t^{sim}-q_{0,k}}{q_{g,k}-q_{0,k}}, \qquad p_k=\operatorname{clip}(\tilde p_k,0,1). \]
  • \(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 帧数加权:

\[ P_e=\frac{\sum_k n_kp_k}{\sum_kn_k}, \qquad n_k=b_k-a_k+1. \]

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 开关设计

统一配置使用:

object_motion:
  type: auto  # auto | revolute | prismatic

语义如下:

  • auto:从所有 active joints 的 joint_types 推断;
  • revolute:要求 active joints 都是 revolutecontinuous
  • 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.thresholdsfinal_metricsevaluation.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": false,
  "failure_reason": "...",
  "metrics": {}
}

但其中 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,第一阶段已完成:

  1. 建立 metrics/metrics/utils/
  2. 把 Final Ours 的原子 metric、completion 和 Success A/B 移入具名模块;
  3. 建立 object_motion.type = auto | revolute | prismatic 的统一接口;
  4. 让根 metric.py 成为配置、NPZ 和计算的唯一入口;
  5. 删除 metric_entry.py_metric_progress.py_metric_tracking.py 旧入口;
  6. 把旧线上 evaluator 明确封装为 legacy_contact_actuation_v1
  7. evaluation.py 只保留 orchestration;
  8. 保持现有 evaluation.json 与聚合器行为不变;
  9. 增加新结构和 revolute/prismatic 路由测试。

当前仍明确不做:

  • 擅自冻结 Final metric 阈值;
  • 把 contact proxy 升格为正式 pairwise contact;
  • 修改论文 primary;
  • 改变已有线上结果语义;
  • 把 rad 和 m 混合聚合。

10. 后续迁移 gate

只有以下条件完成后,才能把 backend 默认协议从 legacy_contact_actuation_v1 切到 final_metrics_v1

  1. metric committee 签署 Success A/B 的 primary 选择、threshold 和 margin;
  2. final_window_frames、phase 分段参数和跨 rollout 聚合方式完成人工签署;
  3. backend 输出统一的 frame-level human-object contact event,或正式关闭 contact gate;
  4. visible validation 上完成新旧 evaluator 差异审计;
  5. aggregator 支持新 metric schema、ITT failure 和 valid-prefix coverage;
  6. revolute/prismatic 分表与 CAD/object-cluster paired bootstrap 测试通过;
  7. 固定配置和代码 hash;
  8. private test 解封前停止修改 metric。

在这些 gate 通过前,架构可以统一,但科学口径仍应标记为 provisional。