From 77d8a8a4ccdf61a500ee656e27d2a0e32fbf9b38 Mon Sep 17 00:00:00 2001 From: zilch Date: Thu, 8 Oct 2026 01:49:33 +0800 Subject: [PATCH] refactor(deploy): unify runtime, task and artifact execution --- AGENTS.md | 4 +- README.md | 8 +- README.zh-CN.md | 8 +- configs/deploy/sim2real/base.yaml | 30 - .../deploy/sim2sim/go2_walk_flat_sim2sim.yaml | 34 - configs/deploy/sim2sim/go2_walk_sim2sim.yaml | 48 -- .../advanced/input_devices_and_bindings.md | 11 +- .../tutorial/advanced/motrix_deploy.md | 132 ++-- .../en/user_guide/tutorial/basic_frame.md | 13 +- .../advanced/input_devices_and_bindings.md | 11 +- .../tutorial/advanced/motrix_deploy.md | 121 ++- .../zh_CN/user_guide/tutorial/basic_frame.md | 12 +- examples/assets/go2-walk-flat.onnx | Bin 0 -> 767534 bytes examples/deploy_to_sim.py | 129 +++ motrix_deploy/README.md | 125 +-- motrix_deploy/pyproject.toml | 4 + .../src/motrix_deploy/artifact/io.py | 4 +- .../src/motrix_deploy/artifact/schema.py | 112 ++- .../src/motrix_deploy/backend/__init__.py | 128 --- motrix_deploy/src/motrix_deploy/cli.py | 115 +-- .../src/motrix_deploy/config/__init__.py | 41 +- .../src/motrix_deploy/config/deploy.yaml | 6 +- .../config/runtime/hardware.yaml | 2 + .../src/motrix_deploy/config/runtime/sim.yaml | 4 + motrix_deploy/src/motrix_deploy/contracts.py | 78 +- motrix_deploy/src/motrix_deploy/env.py | 40 + .../src/motrix_deploy/policy/__init__.py | 35 +- motrix_deploy/src/motrix_deploy/profile.py | 5 +- .../src/motrix_deploy/robot/__init__.py | 4 + .../src/motrix_deploy/robot/interface.py | 72 ++ .../src/motrix_deploy/runtime/__init__.py | 32 +- .../src/motrix_deploy/runtime/base.py | 83 ++ .../src/motrix_deploy/runtime/config.py | 76 ++ .../src/motrix_deploy/runtime/control.py | 294 +++++++ .../src/motrix_deploy/runtime/factory.py | 82 ++ .../src/motrix_deploy/runtime/hardware.py | 68 ++ .../src/motrix_deploy/runtime/loop.py | 271 ------- motrix_deploy/src/motrix_deploy/task.py | 76 +- motrix_deploy/tests/conftest.py | 23 +- .../backend/fake.py => tests/fake_robot.py} | 59 +- motrix_deploy/tests/task_specs.py | 33 + motrix_deploy/tests/test_artifact.py | 68 +- motrix_deploy/tests/test_backend_plugins.py | 80 +- motrix_deploy/tests/test_config.py | 37 +- motrix_deploy/tests/test_contracts.py | 100 ++- .../tests/test_deploy_env_plugins.py | 81 ++ motrix_deploy/tests/test_policy.py | 28 +- motrix_deploy/tests/test_runtime.py | 734 +++++++++++++++--- motrix_deploy/tests/test_runtime_binding.py | 92 +++ .../tests/test_simulation_runtime.py | 166 ++++ motrix_deploy/tests/test_task_plugins.py | 67 ++ motrix_deploy/tests/test_task_spec_codec.py | 238 ++++++ motrix_deploy_mujoco/README.md | 27 +- motrix_deploy_mujoco/pyproject.toml | 4 +- .../src/motrix_deploy_mujoco/__init__.py | 11 +- .../src/motrix_deploy_mujoco/config.py | 79 -- .../src/motrix_deploy_mujoco/interface.py | 374 --------- .../src/motrix_deploy_mujoco/plugin.py | 22 +- .../src/motrix_deploy_mujoco/robot.py | 400 ++++++++++ .../src/motrix_deploy_mujoco/runtime.py | 274 +++++++ .../src/motrix_deploy_mujoco/transform.py | 23 +- .../src/motrix_deploy_mujoco/viewer.py | 65 +- motrix_deploy_mujoco/tests/conftest.py | 30 + motrix_deploy_mujoco/tests/control_helpers.py | 40 + motrix_deploy_mujoco/tests/scene_helpers.py | 23 + motrix_deploy_mujoco/tests/test_examples.py | 211 +++++ .../tests/test_joint_control.py | 143 ++++ .../tests/test_mujoco_deploy_backend.py | 478 +++++++++--- .../tests/test_scene_session.py | 371 +++++++++ .../tests/test_simulation_driver.py | 304 ++++++++ motrix_deploy_mujoco/tests/test_viewer.py | 107 +++ motrix_deploy_tasks/README.md | 39 +- motrix_deploy_tasks/pyproject.toml | 16 +- .../motrix_deploy_tasks_searchpath.py | 17 + .../src/motrix_deploy_tasks/__init__.py | 36 +- .../motrix_deploy_tasks/config/__init__.py | 4 + .../config/task/go2-walk-flat/hardware.yaml | 10 +- .../config/task/go2-walk-flat/sim.yaml | 23 + .../config/task/go2-walk-rough/sim.yaml | 23 + .../src/motrix_deploy_tasks/envs/__init__.py | 4 + .../src/motrix_deploy_tasks/envs/flat.py | 17 + .../src/motrix_deploy_tasks/envs/rough.py | 35 + .../src/motrix_deploy_tasks/go2_walk.py | 188 ----- .../src/motrix_deploy_tasks/tasks/go2_walk.py | 180 +++++ motrix_deploy_tasks/tests/test_cli.py | 330 +++++--- motrix_deploy_tasks/tests/test_go2_walk.py | 341 ++++++-- .../tests/test_training_parity.py | 13 +- motrix_deploy_unitree/README.md | 29 +- motrix_deploy_unitree/pyproject.toml | 2 +- .../src/motrix_deploy_unitree/cli.py | 7 - .../src/motrix_deploy_unitree/config.py | 50 +- .../src/motrix_deploy_unitree/direct.py | 39 +- .../go2_joint_control.py | 6 +- .../src/motrix_deploy_unitree/interface.py | 57 +- .../src/motrix_deploy_unitree/plugin.py | 36 +- .../tests/test_unitree_deploy_backend.py | 201 +++-- .../src/motrix_env_core/config/sim.py | 18 +- motrix_env_core/tests/test_sim_cfg.py | 32 +- motrix_envs/pyproject.toml | 2 +- .../src/motrix_envs/deploy/go2_walk.py | 45 +- motrix_envs/tests/test_deploy_profile.py | 13 +- motrix_rl/src/motrix_rl/deploy/service.py | 4 +- motrix_rl/tests/test_deploy_export.py | 78 +- uv.lock | 91 ++- wiki/design/motrix-deploy.md | 66 +- 105 files changed, 6716 insertions(+), 2446 deletions(-) delete mode 100644 configs/deploy/sim2real/base.yaml delete mode 100644 configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml delete mode 100644 configs/deploy/sim2sim/go2_walk_sim2sim.yaml create mode 100644 examples/assets/go2-walk-flat.onnx create mode 100644 examples/deploy_to_sim.py delete mode 100644 motrix_deploy/src/motrix_deploy/backend/__init__.py create mode 100644 motrix_deploy/src/motrix_deploy/config/runtime/hardware.yaml create mode 100644 motrix_deploy/src/motrix_deploy/config/runtime/sim.yaml create mode 100644 motrix_deploy/src/motrix_deploy/env.py create mode 100644 motrix_deploy/src/motrix_deploy/robot/__init__.py create mode 100644 motrix_deploy/src/motrix_deploy/robot/interface.py create mode 100644 motrix_deploy/src/motrix_deploy/runtime/base.py create mode 100644 motrix_deploy/src/motrix_deploy/runtime/config.py create mode 100644 motrix_deploy/src/motrix_deploy/runtime/control.py create mode 100644 motrix_deploy/src/motrix_deploy/runtime/factory.py create mode 100644 motrix_deploy/src/motrix_deploy/runtime/hardware.py delete mode 100644 motrix_deploy/src/motrix_deploy/runtime/loop.py rename motrix_deploy/{src/motrix_deploy/backend/fake.py => tests/fake_robot.py} (71%) create mode 100644 motrix_deploy/tests/task_specs.py create mode 100644 motrix_deploy/tests/test_deploy_env_plugins.py create mode 100644 motrix_deploy/tests/test_runtime_binding.py create mode 100644 motrix_deploy/tests/test_simulation_runtime.py create mode 100644 motrix_deploy/tests/test_task_plugins.py create mode 100644 motrix_deploy/tests/test_task_spec_codec.py delete mode 100644 motrix_deploy_mujoco/src/motrix_deploy_mujoco/config.py delete mode 100644 motrix_deploy_mujoco/src/motrix_deploy_mujoco/interface.py create mode 100644 motrix_deploy_mujoco/src/motrix_deploy_mujoco/robot.py create mode 100644 motrix_deploy_mujoco/src/motrix_deploy_mujoco/runtime.py create mode 100644 motrix_deploy_mujoco/tests/conftest.py create mode 100644 motrix_deploy_mujoco/tests/control_helpers.py create mode 100644 motrix_deploy_mujoco/tests/scene_helpers.py create mode 100644 motrix_deploy_mujoco/tests/test_examples.py create mode 100644 motrix_deploy_mujoco/tests/test_joint_control.py create mode 100644 motrix_deploy_mujoco/tests/test_scene_session.py create mode 100644 motrix_deploy_mujoco/tests/test_simulation_driver.py create mode 100644 motrix_deploy_tasks/src/hydra_plugins/motrix_deploy_tasks_searchpath.py create mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/config/__init__.py rename configs/deploy/sim2real/go2_walk_flat_sim2real.yaml => motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/hardware.yaml (86%) create mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/sim.yaml create mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-rough/sim.yaml create mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/envs/__init__.py create mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/envs/flat.py create mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/envs/rough.py delete mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/go2_walk.py create mode 100644 motrix_deploy_tasks/src/motrix_deploy_tasks/tasks/go2_walk.py diff --git a/AGENTS.md b/AGENTS.md index a9e246d5..9c0b21bc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,10 +11,10 @@ MotrixLab 是构建在 MotrixSim 仿真后端之上的强化学习框架,提 项目使用 UV workspace,包含以下 package: -- `motrix_env_core`:backend 无关的环境框架,拥有 robot registry 与 `motrix_env_core.robots` entry-point discovery(不依赖任何 simulator 或 `motrix_robots`) +- `motrix_env_core`:backend 无关的环境框架、配置与机器人 registry,不依赖具体 simulator - `motrix_env_motrixsim`:MotrixSim 仿真后端(SimBackend、scene compiler、renderer、torch frontend) - `motrix_env_mujoco`:仅用于编译的 MuJoCo scene 后端 -- `motrix_robots`:仿真 `RobotCfg`、模型自有关键姿态及模型 MJCF/URDF、mesh 与模型纹理资产,仅依赖 core;模型定义模块与 `assets/` 位于 package 根目录,任务场景、地面与 terrain 资产属于 `motrix_envs`。根 package 以显式 `__all__` 提供模型类和类型化 humanoid/quadruped helper 的公共 API;框架内部从定义模块导入。注册回调由首次 robot registry 查询惰性调用;导入 package 或模型类不触发 discovery、注册、资产扫描或读取,也不加载 simulator 或训练环境 +- `motrix_robots`:可复用机器人配置、默认姿态与模型资产;仅依赖 core。框架代码从定义模块导入模型类,例如 `motrix_robots.unitree` - `motrix_envs`:内置环境与任务资产 - `motrix_rl`:RL 控制平面、训练编排与稳定 contracts(runner、run/checkpoint、backend 选择、`motrix_rl.frameworks` discovery、provider/trainer 接口) - `motrix_rl_builtin`:内置 Motrix FastSAC provider 插件 diff --git a/README.md b/README.md index 209dced4..a17a1b63 100644 --- a/README.md +++ b/README.md @@ -178,14 +178,14 @@ MotrixLab is a [uv](https://docs.astral.sh/uv/) workspace with the following pac | Package | PyPI name | Description | | --- | --- | --- | -| **motrix_deploy** | `motrix-deploy` | Framework-independent artifacts, runtime contracts, deployment registry, and CLI | +| **motrix_deploy** | `motrix-deploy` | Artifacts, control sessions, runtime contracts, and deployment CLI | | **motrix_deploy_mujoco** | `motrix-deploy-mujoco` | MuJoCo deployment backend plugin | | **motrix_deploy_unitree** | `motrix-deploy-unitree` | Unitree SDK2 DDS hardware backend plugin | -| **motrix_deploy_tasks** | `motrix-deploy-tasks` | Versioned deployment tasks and executable bootstrap | -| **motrix_env_core** | `motrix-env-core` | Backend-agnostic environment framework, configuration, registry, robot entry-point discovery, lifecycle, and rendering | +| **motrix_deploy_tasks** | `motrix-deploy-tasks` | Walking tasks, deployment scenes, and installed Hydra recipes | +| **motrix_env_core** | `motrix-env-core` | Backend-agnostic environment framework, configuration, registry, and rendering | | **motrix_env_motrixsim** | `motrix-env-motrixsim` | Live MotrixSim backend, renderer, and Torch frontend | | **motrix_env_mujoco** | `motrix-env-mujoco` | Compile-only MuJoCo scene backend | -| **motrix_robots** | `motrix-robots` | Simulation robot configs, model-owned poses, and model MJCF/URDF, meshes, and model textures; core-only dependency | +| **motrix_robots** | `motrix-robots` | Reusable robot configurations, default poses, and model assets | | **motrix_envs** | `motrix-envs` | Built-in environments, task assets, data, and deployment-profile compilers | | **motrix_rl** | `motrix-rl` | RL control plane, provider/trainer contracts, run/checkpoint handling, and discovery | | **motrix_rl_builtin** | `motrix-rl-builtin` | Built-in Motrix FastSAC provider | diff --git a/README.zh-CN.md b/README.zh-CN.md index 2136856b..a0e5c718 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -178,14 +178,14 @@ MotrixLab 是一个 [uv](https://docs.astral.sh/uv/) workspace,包含以下 pa | Package | PyPI 名称 | 说明 | | --- | --- | --- | -| **motrix_deploy** | `motrix-deploy` | 框架无关 artifact、运行时契约、部署 registry 与 CLI | +| **motrix_deploy** | `motrix-deploy` | artifact、控制会话、运行时契约与部署 CLI | | **motrix_deploy_mujoco** | `motrix-deploy-mujoco` | MuJoCo 部署 backend plugin | | **motrix_deploy_unitree** | `motrix-deploy-unitree` | Unitree SDK2 DDS 硬件 backend plugin | -| **motrix_deploy_tasks** | `motrix-deploy-tasks` | 带版本的部署任务与可执行入口 bootstrap | -| **motrix_env_core** | `motrix-env-core` | backend 无关的环境框架、配置、registry、robot entry-point discovery、生命周期与渲染 | +| **motrix_deploy_tasks** | `motrix-deploy-tasks` | 行走任务、部署场景与安装的 Hydra 配方 | +| **motrix_env_core** | `motrix-env-core` | backend 无关的环境框架、配置、registry 与渲染 | | **motrix_env_motrixsim** | `motrix-env-motrixsim` | MotrixSim 实时仿真 backend、renderer 与 Torch frontend | | **motrix_env_mujoco** | `motrix-env-mujoco` | 仅负责编译场景的 MuJoCo backend | -| **motrix_robots** | `motrix-robots` | 仿真机器人配置、模型自有姿态及 模型 MJCF/URDF、mesh 与模型纹理资产;仅依赖 core | +| **motrix_robots** | `motrix-robots` | 可复用机器人配置、默认姿态与模型资产 | | **motrix_envs** | `motrix-envs` | 内置环境、任务资产、数据及部署 profile 编译实现 | | **motrix_rl** | `motrix-rl` | RL 控制平面、provider/trainer contract、run/checkpoint 管理与 discovery | | **motrix_rl_builtin** | `motrix-rl-builtin` | 内置 Motrix FastSAC provider | diff --git a/configs/deploy/sim2real/base.yaml b/configs/deploy/sim2real/base.yaml deleted file mode 100644 index 439b4a71..00000000 --- a/configs/deploy/sim2real/base.yaml +++ /dev/null @@ -1,30 +0,0 @@ -defaults: - - _self_ - - override hydra/job_logging: stdout - -hydra: - output_subdir: null - run: - dir: . - sweep: - dir: . - subdir: ${hydra.job.num} - job: - chdir: false - -artifact: ??? - -rollout: - steps: null - - duration_s: 300.0 -# Shared policy-control gains for physical deployment recipes. -backend: - kp: 50 - kd: 1 - -seed: 1 -hardware: - confirm: false -realtime: true -viewer: false diff --git a/configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml b/configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml deleted file mode 100644 index 4c2abefd..00000000 --- a/configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml +++ /dev/null @@ -1,34 +0,0 @@ -defaults: - - _self_ - - override hydra/job_logging: stdout - -hydra: - output_subdir: null - run: - dir: . - sweep: - dir: . - subdir: ${hydra.job.num} - job: - chdir: false - -artifact: ??? - -backend: - name: mujoco - scene: go2-walk-flat - scene_mode: play - sim_dt: 0.002 - solver_iterations: 100 - # Keep the deterministic default pose clear of the flat ground at reset. - base_position: [0.0, 0.0, 0.331] - base_orientation_xyzw: [0.0, 0.0, 0.0, 1.0] - base_body_name: base - imu_site_name: imu - gyro_sensor_name: gyro - accelerometer_sensor_name: accelerometer - global_linear_velocity_sensor_name: global_linvel - fall_height_m: 0.15 - fall_up_z: 0.5 - -viewer: true diff --git a/configs/deploy/sim2sim/go2_walk_sim2sim.yaml b/configs/deploy/sim2sim/go2_walk_sim2sim.yaml deleted file mode 100644 index 0c93c4e6..00000000 --- a/configs/deploy/sim2sim/go2_walk_sim2sim.yaml +++ /dev/null @@ -1,48 +0,0 @@ -defaults: - - _self_ - - override hydra/job_logging: stdout - -hydra: - output_subdir: null - run: - dir: . - sweep: - dir: . - subdir: ${hydra.job.num} - job: - chdir: false - -artifact: ??? - -backend: - name: mujoco - scene: go2-walk-rough - scene_mode: play - sim_dt: 0.002 - solver_iterations: 100 - # Absolute world position chosen to clear the deterministic rough terrain at the spawn point. - base_position: [0.0, 0.0, 0.42] - base_orientation_xyzw: [0.0, 0.0, 0.0, 1.0] - base_body_name: base - imu_site_name: imu - gyro_sensor_name: gyro - accelerometer_sensor_name: accelerometer - global_linear_velocity_sensor_name: global_linvel - fall_height_m: 0.15 - fall_up_z: 0.5 - -viewer: true - -# Used for headless or non-keyboard backends. Viewer mode uses the focused keyboard instead. -command: - velocity: [0.0, 0.0, 0.0] - -# Leaving both values null runs until the viewer closes or the process is interrupted. -rollout: - steps: null - duration_s: null - -seed: null -realtime: null -hardware: - confirm: false diff --git a/docs/source/en/user_guide/tutorial/advanced/input_devices_and_bindings.md b/docs/source/en/user_guide/tutorial/advanced/input_devices_and_bindings.md index 50977e6d..2e6a55b8 100644 --- a/docs/source/en/user_guide/tutorial/advanced/input_devices_and_bindings.md +++ b/docs/source/en/user_guide/tutorial/advanced/input_devices_and_bindings.md @@ -29,10 +29,9 @@ input_devices_and_bindings/extending Backend-neutral InputDevice, binding, and task-command pipeline ``` -The control path from `CommandBinding` through `RobotInterface` is backend-neutral. The same binding, task command, -`PolicyContext`, task/policy contract, and `RobotCommand` semantics can control simulation and physical-robot targets. Moving -between the two requires replacing only the concrete device provider and `RobotInterface` implementation. A physical backend -that implements `RobotInterface` therefore reuses the same command pipeline, task, and policy. +`ControlSession` combines a binding, task, policy, and `RobotInterface`. It reads the command, builds the task observation, +runs the policy, and writes a `RobotCommand`. A simulation runtime advances physics and manages the viewer; a hardware +runtime schedules control against live robot state. Both use the same task-command semantics. A simulation and a physical robot can select different concrete devices. For example, simulation can use a GLFW keyboard while a physical robot uses a remote controller or network input; both reuse the same binding and task-command semantics. @@ -58,6 +57,8 @@ A high-level task command also has the opposite direction from `RobotCommand`: The runtime obtains a typed task command through `CommandBinding`: ```python +from motrix_deploy.runtime.context import PolicyContext + command = command_binding.read_command(batch_size=1) context = PolicyContext( step=step, @@ -68,7 +69,7 @@ task.validate_command(context.command) observation = task.build_observation(state, context) ``` -For example, MuJoCo deployment obtains a `MujocoKeyboardDevice` from the backend's `KeyboardDeviceProvider` and constructs a +For example, MuJoCo deployment obtains a `MujocoKeyboardDevice` from the runtime's `KeyboardDeviceProvider` and constructs a `KeyboardPlanarVelocityBinding`. Training can use a random binding for the same `PlanarVelocityCommand`. Both paths end at the same typed task contract. diff --git a/docs/source/en/user_guide/tutorial/advanced/motrix_deploy.md b/docs/source/en/user_guide/tutorial/advanced/motrix_deploy.md index d6e0e20e..9e1c7aa1 100644 --- a/docs/source/en/user_guide/tutorial/advanced/motrix_deploy.md +++ b/docs/source/en/user_guide/tutorial/advanced/motrix_deploy.md @@ -1,119 +1,109 @@ # Go2 Flat-Terrain Walking: From Training to Physical Deployment -This tutorial follows the full Go2 flat-terrain workflow: train a policy, export an artifact, check it in MuJoCo, and run -it on a real robot. If you already have a training run, start at “Export the artifact”. - -## 1. Install the environment - -Run this command from the repository root: +Run an exported Go2 policy in MuJoCo before attempting hardware deployment. With the task and MuJoCo plugins installed, +a bounded headless check is: ```bash -sh install.sh --all +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy runtime.viewer=false duration_s=2.0 ``` -This installs the training, MuJoCo, ONNX Runtime, and Unitree SDK2 dependencies. +The following steps create that artifact, explain interactive control, and cover the existing Unitree hardware workflow. -## 2. Train the policy +## 1. Install and train -If you do not have a run yet, train the flat-terrain Go2 policy: +From the repository root: ```bash +sh install.sh --all +source .venv/bin/activate python scripts/train.py task=go2-walk-flat/rslrl.ppo ``` -Training results are saved under `runs/go2-walk-flat/`. - -## 3. Export the artifact +Training results are saved under `runs/go2-walk-flat/`. Skip training if you already have a metadata-backed run. -Export the latest run: +## 2. Export and inspect the artifact ```bash python scripts/export_deploy.py env=go2-walk-flat +motrix-deploy inspect artifact=artifacts/go2-walk-flat.deploy ``` -The output is written to `artifacts/go2-walk-flat.deploy/`. This artifact is the only policy bundle needed for deployment; -it contains the model and its runtime configuration. +Export selects the latest run and creates `artifacts/go2-walk-flat.deploy/`. To select a specific run, use +`run=` instead of `env`; `output=` changes the destination. Existing artifact directories are +not overwritten. Confirm that inspection reports `valid: true`. -## 4. Inspect the artifact +The artifact contains the ONNX policy, robot/control settings, and task specification. Deployment also needs the installed +`motrix_deploy_tasks` plugin, which supplies the walking implementation, deployment scenes, and Hydra recipes, plus the +runtime plugin for the target. The recipes work outside the repository without a workspace config path. +Task defaults select the Hydra runtime group (`sim` or `hardware`); you do not need a separate runtime override. +Common top-level settings are `artifact`, `duration_s`, and `command`. All target-specific settings live directly under +`runtime`. `runtime.kind` selects the simulation or hardware path; `runtime.backend` selects the plugin (`mujoco` +for simulation or `unitree_go2` for hardware). `runtime.backend` is a scalar string selector, not a nested configuration +mapping or Hydra group. -```bash -motrix-deploy inspect \ - artifact=artifacts/go2-walk-flat.deploy -``` +## 3. Check in MuJoCo -Make sure the output reports `valid: true`. - -## 5. Run Sim2Sim first +Use the headless command above for a bounded run, or open the viewer: ```bash -motrix-deploy sim2sim \ - --config-name go2_walk_flat_sim2sim \ - artifact=artifacts/go2-walk-flat.deploy +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy ``` -This opens the MuJoCo viewer. Use `W/S` to move forward and backward, `A/D` to move sideways, and `Q/E` to turn. Close -the window, press Esc, or press Ctrl-C to stop. +Hold `W/S` to move forward/backward, `A/D` sideways, and `Q/E` to turn. Close the window, press Esc, or press Ctrl-C to stop. +The recipe selects the flat scene and the `go2` robot. Headless mode uses constant zero velocity; for a moving check, add +`'command.velocity=[0.5,0.0,0.0]'`. Set `duration_s` for a bounded run: it allows +`ceil(duration_s / control_period_s)` control ticks, using the artifact's control period, not a wall-clock timeout. +Startup and shutdown may take additional wall time. -## 6. Run on the real robot +Simulation-only settings are `runtime.viewer` and `runtime.realtime`. +`runtime.realtime=null` follows `runtime.viewer`; use `runtime.realtime=true` to pace a headless run in real time, +or `runtime.realtime=false` to run without real-time pacing. -Put the robot in low-level/debug mode, connect Ethernet, and keep an emergency stop ready. Replace `enp5s0` with the -actual network interface: +### Assemble a runtime in Python -```bash -motrix-deploy inspect \ - artifact=artifacts/go2-walk-flat.deploy - -motrix-deploy sim2real \ - --config-name go2_walk_flat_sim2real \ - artifact=artifacts/go2-walk-flat.deploy \ - backend.network_interface=enp5s0 \ - hardware.confirm=true -``` - -After startup, press Start on the remote. When the default-pose transition finishes, press A. Hold L1 and move the sticks -to send motion commands. Press B to enter the lie-down sequence; Select triggers the emergency stop. - -You can also inspect the robot state before sending policy commands: +The complete `examples/deploy_to_sim.py` example composes a scene, walking task, sample ONNX policy, and input binding: ```bash -python -m motrix_deploy_unitree.read_lowstate enp5s0 +python examples/deploy_to_sim.py --headless --steps 100 ``` -## Advanced usage +It creates a MuJoCo runtime with `create_simulation_runtime`, attaches a `ControlSession` using +`runtime.bind_control_session(control)`, and runs inside `with runtime:`. The control session handles input, observations, +inference, and robot commands; the runtime handles physics, timing, and the viewer. A raw ONNX file needs explicit task +preprocessing and action settings, whereas the artifact carries those settings for the CLI. -### Override policy PD gains +## 4. Run on the real robot -The physical-runtime base configuration, `configs/deploy/sim2real/base.yaml`, currently sets `backend.kp=50` and -`backend.kd=1`, overriding the gains stored in the artifact `TaskSpec.config`. Set both fields to `null` to keep the artifact -gains, or pass 12 non-negative values on the command line: +Suspend the robot, put it in low-level/debug mode, connect Ethernet, and keep an independent emergency stop and operator +ready. Startup can physically stand the robot down before the remote Start gate. Inspect the artifact first, then replace +`enp5s0` with the actual interface: ```bash -motrix-deploy sim2real \ +motrix-deploy task=go2-walk-flat/hardware \ artifact=artifacts/go2-walk-flat.deploy \ - backend.network_interface=enp5s0 \ - 'backend.kp=[20,25,30,20,25,30,22,27,32,22,27,32]' \ - 'backend.kd=[0.3,0.4,0.5,0.3,0.4,0.5,0.35,0.45,0.55,0.35,0.45,0.55]' \ - hardware.confirm=true + runtime.network_interface=enp5s0 ``` -### Inspect LowState without sending commands +Hardware runs are always real-time internally; the hardware runtime has no `runtime.viewer` or `runtime.realtime` fields. +Press Start, wait for the default-pose transition, then +press A. Hold L1 and move the sticks to command motion. B requests lie-down; Select triggers emergency stop. Stop paths +send damping commands before closing DDS. Software tests do not replace a suspended real-robot check. + +The installed hardware recipe sets `runtime.kp=50` and `runtime.kd=1`. Set both to `null` to retain artifact gains, or +supply non-negative scalars or per-joint values in canonical joint order. -Run the read-only diagnostic before sending any motion command: +For read-only diagnostics without a command publisher: ```bash -python -m motrix_deploy_unitree.read_lowstate enp5s0 +motrix-deploy-unitree read-lowstate enp5s0 ``` -### Send a single-joint motion command - -To bypass the policy and debug a single joint-position motion, use the bounded helper installed with -`motrix_deploy_unitree`. By default, it builds the control contract from the current `go2-walk-flat` deployment profile and -does not require a training run, checkpoint, policy, or deployment artifact: +For a bounded single-joint check, with the same hardware precautions: ```bash -python -m motrix_deploy_unitree.go2_joint_control \ - enp5s0 \ - FL_thigh_joint \ - 0.9 \ - --hardware-confirm +motrix-deploy-unitree joint-control enp5s0 FL_thigh_joint 0.9 \ + --artifact artifacts/go2-walk-flat.deploy ``` + +This helper uses the artifact's robot, gain, timing, and limit settings. It waits for Start and A, moves the joint, holds, +returns to the default pose, and closes with damping. diff --git a/docs/source/en/user_guide/tutorial/basic_frame.md b/docs/source/en/user_guide/tutorial/basic_frame.md index bd2f3f35..407a44f0 100644 --- a/docs/source/en/user_guide/tutorial/basic_frame.md +++ b/docs/source/en/user_guide/tutorial/basic_frame.md @@ -51,7 +51,7 @@ MotrixLab is a UV workspace. The packages are grouped by ownership rather than t | `motrix_deploy` | Policy deployment | Framework-independent artifacts, runtime contracts, and deployment CLI | | `motrix_deploy_mujoco` | Policy deployment | MuJoCo deployment backend plugin | | `motrix_deploy_unitree` | Policy deployment | Unitree SDK2 DDS hardware backend plugin | -| `motrix_deploy_tasks` | Policy deployment | Versioned deployment tasks and executable bootstrap | +| `motrix_deploy_tasks` | Policy deployment | Walking tasks, deployment scenes, and installed Hydra recipes | A simulation backend (such as `motrix_env_motrixsim`) is isolated behind the `SimBackend` interface — when using an environment you normally do not need to care which one it is. To select or integrate a @@ -76,6 +76,17 @@ python scripts/train.py task=cartpole/skrl.ppo python scripts/train.py task=cartpole/rslrl.ppo ``` +## Run an exported policy + +Deployment uses an artifact rather than a training environment: + +```bash +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy runtime.viewer=false duration_s=2.0 +``` + +This runs the exported policy headlessly in MuJoCo. See [Go2 deployment](advanced/motrix_deploy.md) for export, +interactive control, and hardware operation. + ## What the layering buys you 1. **Environment reuse**: one registered environment can be trained by multiple RL frameworks. diff --git a/docs/source/zh_CN/user_guide/tutorial/advanced/input_devices_and_bindings.md b/docs/source/zh_CN/user_guide/tutorial/advanced/input_devices_and_bindings.md index 29926fa6..df91c476 100644 --- a/docs/source/zh_CN/user_guide/tutorial/advanced/input_devices_and_bindings.md +++ b/docs/source/zh_CN/user_guide/tutorial/advanced/input_devices_and_bindings.md @@ -27,10 +27,9 @@ input_devices_and_bindings/extending InputDevice、Binding 与 backend-neutral task command 控制链路 ``` -从 `CommandBinding` 到 `RobotInterface` 的控制链路不依赖具体 backend。同一个 binding、任务指令、 -`PolicyContext`、task/policy contract 和 `RobotCommand` 语义可以同时用于仿真与真机;切换控制目标时,只需 -替换具体 device provider 和 `RobotInterface` 实现。真机 backend 实现 `RobotInterface` 后,即可复用相同的 -指令链路、task 和 policy。 +`ControlSession` 组合 binding、任务、策略和 `RobotInterface`,读取指令、构造任务观察、运行策略,并写入 +`RobotCommand`。仿真运行时推进物理并管理 viewer;硬件运行时根据实时机器人状态调度控制。 +两者使用相同的任务指令语义。 仿真和真机可以选择各自合适的具体 device。例如,仿真使用 GLFW keyboard,真机使用遥控器或网络输入;两端 复用同一个 binding 和任务指令语义。 @@ -55,6 +54,8 @@ InputDevice、Binding 与 backend-neutral task command 控制链路 运行时通过 `CommandBinding` 获取强类型任务指令: ```python +from motrix_deploy.runtime.context import PolicyContext + command = command_binding.read_command(batch_size=1) context = PolicyContext( step=step, @@ -65,7 +66,7 @@ task.validate_command(context.command) observation = task.build_observation(state, context) ``` -例如 MuJoCo deployment 从 backend 的 `KeyboardDeviceProvider` 取得 `MujocoKeyboardDevice`,再创建 +例如 MuJoCo deployment 从运行时的 `KeyboardDeviceProvider` 取得 `MujocoKeyboardDevice`,再创建 `KeyboardPlanarVelocityBinding`。训练环境则可以为同一个 `PlanarVelocityCommand` 使用 random binding。两条路径最终 进入同一个 typed task contract。 diff --git a/docs/source/zh_CN/user_guide/tutorial/advanced/motrix_deploy.md b/docs/source/zh_CN/user_guide/tutorial/advanced/motrix_deploy.md index 98683b00..96258a1b 100644 --- a/docs/source/zh_CN/user_guide/tutorial/advanced/motrix_deploy.md +++ b/docs/source/zh_CN/user_guide/tutorial/advanced/motrix_deploy.md @@ -1,115 +1,102 @@ # Go2 平地行走:从训练到真机部署 -这篇教程用 Go2 平地行走任务演示完整流程:训练策略、导出 artifact、先在 MuJoCo 中检查,最后连接真机运行。 -如果已经有训练好的 run,可以直接从“导出 artifact”开始。 - -## 1. 安装环境 - -在仓库根目录执行: +将导出的 Go2 策略先在 MuJoCo 中检查,再尝试真机部署。安装任务和 MuJoCo 插件后,可用以下命令进行有界无窗口检查: ```bash -sh install.sh --all +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy runtime.viewer=false duration_s=2.0 ``` -这个命令会安装训练、MuJoCo、ONNX Runtime 和 Unitree SDK2 依赖。 +下面介绍如何生成 artifact、交互控制,以及现有 Unitree 真机工作流。 -## 2. 训练策略 +## 1. 安装与训练 -没有现成 run 时,训练 flat-terrain Go2 策略: +在仓库根目录执行: ```bash +sh install.sh --all +source .venv/bin/activate python scripts/train.py task=go2-walk-flat/rslrl.ppo ``` -训练结果保存在 `runs/go2-walk-flat/`。 - -## 3. 导出 artifact +训练结果保存在 `runs/go2-walk-flat/`。已有包含 metadata 的 run 时可跳过训练。 -导出最近一次 run: +## 2. 导出并检查 artifact ```bash python scripts/export_deploy.py env=go2-walk-flat +motrix-deploy inspect artifact=artifacts/go2-walk-flat.deploy ``` -输出目录为 `artifacts/go2-walk-flat.deploy/`。artifact 是部署时唯一需要带走的策略文件,包含模型和运行所需的配置。 - -## 4. 检查 artifact +导出会选择最近一次 run,并创建 `artifacts/go2-walk-flat.deploy/`。用 `run=` 替代 `env` 可选择指定 run, +`output=` 可更改输出位置。已有 artifact 目录不会被覆盖。确认检查结果为 `valid: true`。 -```bash -motrix-deploy inspect \ - artifact=artifacts/go2-walk-flat.deploy -``` +artifact 包含 ONNX 策略、机器人与控制设置,以及任务规格。部署还需要安装 `motrix_deploy_tasks` 插件,提供行走任务实现、 +部署场景和 Hydra 配方,以及目标对应的运行时插件。配方安装在插件中,在仓库外运行也不需要 workspace 配置路径。 +任务 defaults 选择 Hydra 运行时组(`sim` 或 `hardware`),无需另行覆盖运行时组。 +顶层公共设置为 `artifact`、`duration_s` 和 `command`。所有目标专用设置直接位于 `runtime` 下; +`runtime.kind` 选择仿真或硬件执行路径;`runtime.backend` 选择插件(仿真为 `mujoco`,硬件为 `unitree_go2`)。 +`runtime.backend` 是标量字符串选择器,不是嵌套配置映射或 Hydra 配置组。 -确认输出中的 `valid` 为 `true`。 +## 3. 在 MuJoCo 中检查 -## 5. 先跑 Sim2Sim +使用开头的无窗口命令进行有界运行,或打开 viewer: ```bash -motrix-deploy sim2sim \ - --config-name go2_walk_flat_sim2sim \ - artifact=artifacts/go2-walk-flat.deploy +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy ``` -会打开 MuJoCo viewer。按 `W/S` 前后移动,`A/D` 横移,`Q/E` 转向;关闭窗口、按 Esc 或 Ctrl-C 退出。 +按住 `W/S` 前后移动,`A/D` 横移,`Q/E` 转向。关闭窗口、按 Esc 或 Ctrl-C 退出。 +配方选择平地场景和 `go2` 机器人。无窗口模式使用固定零速度;添加 `'command.velocity=[0.5,0.0,0.0]'` 可检查移动。 +通过 `duration_s` 限制运行长度:按 artifact 的控制周期换算为 +`ceil(duration_s / control_period_s)` 个控制 tick,而不是墙钟超时计时器。 +启动和关闭可能额外消耗墙钟时间。 -## 6. 运行真机 +仿真专用设置为 `runtime.viewer` 和 `runtime.realtime`。 +`runtime.realtime=null` 跟随 `runtime.viewer`;用 `runtime.realtime=true` 让无窗口运行按实时节奏执行, +用 `runtime.realtime=false` 则不进行实时节奏控制。 -先确认机器人处于低层/调试模式,网线已连接,并准备好急停。 -将 `enp5s0` 换成实际网卡名称: +### 在 Python 中组装运行时 -```bash -motrix-deploy inspect \ - artifact=artifacts/go2-walk-flat.deploy - -motrix-deploy sim2real \ - --config-name go2_walk_flat_sim2real \ - artifact=artifacts/go2-walk-flat.deploy \ - backend.network_interface=enp5s0 \ - hardware.confirm=true -``` - -启动后按遥控器 Start,完成默认姿态过渡后按 A;按住 L1 并移动左右摇杆,可发送移动命令。按 B 进入趴下流程,Select 触发急停。 - -发送策略前,也可以先查看机器人状态: +完整示例 `examples/deploy_to_sim.py` 组合场景、行走任务、示例 ONNX 策略和输入 binding: ```bash -python -m motrix_deploy_unitree.read_lowstate enp5s0 +python examples/deploy_to_sim.py --headless --steps 100 ``` -## 进阶说明 +它通过 `create_simulation_runtime` 创建 MuJoCo 运行时,用 `runtime.bind_control_session(control)` 连接 +`ControlSession`,并在 `with runtime:` 中运行。控制会话负责输入、观察、推理和机器人指令;运行时负责物理推进、 +调度和 viewer。直接使用 ONNX 文件时需要显式指定任务预处理和动作设置;artifact 则为 CLI 携带这些设置。 -### 显式覆盖策略 PD 增益 +## 4. 运行真机 -真机父配置 `configs/deploy/sim2real/base.yaml` 当前设置 `backend.kp=50`、`backend.kd=1`,会覆盖 artifact -`TaskSpec.config` 中保存的增益。把二者显式设为 `null` 才会保留 artifact 增益;也可以在命令行传入非负标量统一 -覆盖 12 个关节: +将机器人悬空,切换到低层/调试模式,连接网线,并准备好独立急停和操作人员。启动过程可能在遥控器 Start 门控前 +让机器人执行站下动作。先检查 artifact,再将 `enp5s0` 换成实际网卡名称: ```bash -motrix-deploy sim2real \ +motrix-deploy task=go2-walk-flat/hardware \ artifact=artifacts/go2-walk-flat.deploy \ - backend.network_interface=enp5s0 \ - 'backend.kp=[20,25,30,20,25,30,22,27,32,22,27,32]' \ - 'backend.kd=[0.3,0.4,0.5,0.3,0.4,0.5,0.35,0.45,0.55,0.35,0.45,0.55]' \ - hardware.confirm=true + runtime.network_interface=enp5s0 ``` -### 只读检查 LowState 数据 +真机运行在内部强制按实时节奏执行;真机运行时不提供 `runtime.viewer` 或 `runtime.realtime` 字段。 +按 Start,等待默认姿态过渡完成后按 A;按住 L1 并移动摇杆发送运动指令。 +B 请求趴下,Select 触发急停。停止时先发送阻尼指令,再关闭 DDS。软件测试不能替代悬空真机检查。 + +安装的真机配方设置 `runtime.kp=50`、`runtime.kd=1`。把二者设为 `null` 可保留 artifact 增益,也可传入非负标量 +或按规范关节顺序排列的逐关节值。 -发送任何运动指令前,可先运行只读诊断: +只读诊断不创建指令 publisher: ```bash -python -m motrix_deploy_unitree.read_lowstate enp5s0 +motrix-deploy-unitree read-lowstate enp5s0 ``` -### 发送单关节运动指令 - -如需绕过策略、调试一个关节的位置运动,可使用随 `motrix_deploy_unitree` 安装的有界运动脚本。默认从当前 -`go2-walk-flat` deployment profile 构建控制契约,不需要训练 run、checkpoint、策略或 deployment artifact: +在同样的硬件安全准备下,可进行有界单关节检查: ```bash -python -m motrix_deploy_unitree.go2_joint_control \ - enp5s0 \ - FL_thigh_joint \ - 0.9 \ - --hardware-confirm +motrix-deploy-unitree joint-control enp5s0 FL_thigh_joint 0.9 \ + --artifact artifacts/go2-walk-flat.deploy ``` + +该工具使用 artifact 中的机器人、增益、时序和限位设置。等待 Start 和 A 后,移动关节、保持、返回默认姿态, +最后以阻尼模式关闭。 diff --git a/docs/source/zh_CN/user_guide/tutorial/basic_frame.md b/docs/source/zh_CN/user_guide/tutorial/basic_frame.md index 55f94f04..43b1431e 100644 --- a/docs/source/zh_CN/user_guide/tutorial/basic_frame.md +++ b/docs/source/zh_CN/user_guide/tutorial/basic_frame.md @@ -46,7 +46,7 @@ MotrixLab 是一个 UV workspace。下面按所有权而不是按单一整体运 | `motrix_deploy` | 策略的部署 | 框架无关 artifact、运行时契约与部署 CLI | | `motrix_deploy_mujoco` | 策略的部署 | MuJoCo 部署 backend plugin | | `motrix_deploy_unitree` | 策略的部署 | Unitree SDK2 DDS 硬件 backend plugin | -| `motrix_deploy_tasks` | 策略的部署 | 带版本的部署任务与可执行入口 bootstrap | +| `motrix_deploy_tasks` | 策略的部署 | 行走任务、部署场景与安装的 Hydra 配方 | 仿真后端(如 `motrix_env_motrixsim`)通过 `SimBackend` 接口隔离在环境框架之下,使用环境时通常 无需关心它;需要选择或接入仿真后端时,见[SimBackend:与仿真器解耦](building_envs/sim_backend.md)。 @@ -70,6 +70,16 @@ python scripts/train.py task=cartpole/skrl.ppo python scripts/train.py task=cartpole/rslrl.ppo ``` +## 运行导出的策略 + +部署使用 artifact,而不是训练环境: + +```bash +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy runtime.viewer=false duration_s=2.0 +``` + +上例在 MuJoCo 中无窗口运行导出的策略。导出、交互控制及真机操作见 [Go2 部署](advanced/motrix_deploy.md)。 + ## 分层带来的优势 1. **环境复用**:一个注册环境可以由多个 RL 框架训练。 diff --git a/examples/assets/go2-walk-flat.onnx b/examples/assets/go2-walk-flat.onnx new file mode 100644 index 0000000000000000000000000000000000000000..a00280e3482b4bd9d0277cf707d393b1e08737da GIT binary patch literal 767534 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�${}-l|0OI)!tgV)VS434K#L#tcmD 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

iU1oKi~Vc+{&5c5&NhvKYDd29%(R~x~{ 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%0ATtkGm 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�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%0DuY<^3y+d?ib>MX2H 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 %|UzQ69C(28Ey-!m1yYRG~E(Cmq*d)r}3* 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≦~iH8|T0z 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 YOH&gY int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--backend", choices=("mujoco",), default="mujoco", help="Simulation runtime plugin") + parser.add_argument("--headless", action="store_true", help="No window; run fixed-step without wall-clock pacing") + parser.add_argument("--steps", type=int, help="Headless control ticks; GUI mode runs until the window closes") + args = parser.parse_args() + if args.headless and (args.steps is None or args.steps <= 0): + parser.error("--steps must be a positive integer in --headless mode") + if not args.headless and args.steps is not None: + parser.error("--steps is only valid in --headless mode; GUI mode runs until the window closes") + + policy = OnnxPolicyRuntime( + POLICY_PATH, + input_spec=TensorSpec(name="obs", shape=(1, 49)), + output_spec=TensorSpec(name="actions", shape=(1, 12)), + ) + + scene = SceneCfg( + objs=ExampleSceneObjsCfg( + # Placement is an attach transform; the model root is already at z=0.445. + robot=UnitreeGo2Robot(translation=(0.0, 0.0, -0.114), rotation=(0.0, 0.0, 0.0, 1.0)), + floor=FlatTerrainCfg(friction=(0.6, 0.005, 0.0001)), + ), + # Match StandardSceneCfg lighting without importing the training environment package. + visual=SceneVisualCfg( + ambient_light_color=(0.3, 0.3, 0.3), + ambient_light_brightness=1_000.0, + head_light_color=(0.6, 0.6, 0.6), + head_light_luminous_power=1_000.0, + haze=(0.1, 0.1, 0.1, 1.0), + tone_mapping="none", + ), + system_camera=SystemCameraCfg(distance=3.0, elevation=-20.0, azimuth=90.0), + ) + config = SimulationRuntimeConfig( + scene=scene, + physics=SimCfg(dt=0.002, solver_iterations=100), + render=not args.headless, + sensor_bindings=SensorBindings( + base_angular_velocity="gyro", + base_linear_acceleration="accelerometer", + base_linear_velocity="global_linvel", + ), + ) + + runtime = create_simulation_runtime(args.backend, config) + robot = runtime.robot + # Explicit preprocessing/action semantics for this walking policy. These must + # match its training configuration; a raw ONNX file does not describe them. + action_scale = 0.25 + task = Go2WalkDeployTaskV1( + Go2WalkTaskSpec( + action_scale=[action_scale] * robot.spec.joint_count, + kp=[35.0] * robot.spec.joint_count, + kd=[0.5] * robot.spec.joint_count, + action_lower=((robot.spec.position_lower - robot.spec.default_joint_position) / action_scale).tolist(), + action_upper=((robot.spec.position_upper - robot.spec.default_joint_position) / action_scale).tolist(), + command_lower=[-1.0, -0.5, -0.5], + command_upper=[1.0, 0.5, 0.5], + command_scale=[1.0, 1.0, 1.0], + feet_phase_offsets=[0.0, 0.5, 0.5, 0.0], + gait_frequency_hz=2.0, + standing_threshold=0.05, + termination_min_up_z=0.5, + termination_min_base_height=None, + ), + robot.spec, + ) + if not args.headless and isinstance(runtime, KeyboardDeviceProvider): + command_binding = KeyboardPlanarVelocityBinding( + runtime.get_keyboard_device(), + command_lower=task.command_lower, + command_upper=task.command_upper, + ) + else: + command_binding = ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)) + task.validate_command(command_binding.read_command()) + control = ControlSession( + robot=robot, + task=task, + policy=policy, + command_binding=command_binding, + period_s=0.02, + state_timeout_s=0.1, + ) + runtime.bind_control_session(control) + with runtime: + result = runtime.run(steps=args.steps) + print(json.dumps(result.to_dict(), indent=2, sort_keys=True)) + return 0 if result.success else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/motrix_deploy/README.md b/motrix_deploy/README.md index e71bde1a..cf1d59f3 100644 --- a/motrix_deploy/README.md +++ b/motrix_deploy/README.md @@ -1,111 +1,50 @@ # Motrix Deploy -`motrix_deploy` is MotrixLab's framework-independent policy deployment library. It defines artifact, backend, policy, -control-loop, deployment-profile registry, task registry, backend plugin discovery, and CLI without importing concrete -tasks, simulator implementations, training environments, or RL frameworks. The simulator and hardware backends live in the -sibling `motrix_deploy_mujoco` and -`motrix_deploy_unitree` plugin packages. Concrete task semantics live in `motrix_deploy_tasks`, which publishes the -`motrix-deploy` executable bootstrap so tasks are registered before the core CLI runs. Built-in environment profile -compilers live under `motrix_envs.deploy`. +`motrix_deploy` runs exported policies without a training framework. It provides deployment artifacts, robot and task +contracts, `ControlSession`, simulation/hardware runtimes, and the `motrix-deploy` CLI. Install a task plugin and the +runtime plugin for your target; the Go2 recipes below come from `motrix_deploy_tasks` and use `motrix_deploy_mujoco`. -The supported vertical slice exports a metadata-backed Go2 RSL-RL walk policy and runs the same artifact in MuJoCo or -on a physical Go2. -From the repository root, install the required development extras: +From the repository root, install and activate the environment, then export a trained Go2 flat-terrain run: ```bash sh install.sh --all -``` - -Export a new artifact directory, validate it without opening a backend, and run an interactive deployment: - -```bash -python scripts/export_deploy.py env=go2-walk-rough - -motrix-deploy inspect \ - artifact=artifacts/go2-walk-rough.deploy - -motrix-deploy sim2sim \ - artifact=artifacts/go2-walk-rough.deploy -``` - -For the flat-terrain environment, export its own artifact and select the matching runtime recipe: - -```bash +source .venv/bin/activate python scripts/export_deploy.py env=go2-walk-flat - -motrix-deploy inspect \ - artifact=artifacts/go2-walk-flat.deploy - -motrix-deploy sim2sim \ - --config-name go2_walk_flat_sim2sim \ - artifact=artifacts/go2-walk-flat.deploy +motrix-deploy inspect artifact=artifacts/go2-walk-flat.deploy +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy runtime.viewer=false duration_s=2.0 ``` -Export defaults are defined in `configs/deploy/export.yaml`. The default rough-terrain workspace runtime is defined in -`configs/deploy/sim2sim/go2_walk_sim2sim.yaml`; the explicit flat-terrain recipe is -`configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml`. Both can be changed with Hydra overrides. The packaged -`motrix_deploy/config/deploy.yaml` is only a task-agnostic mandatory-field template. The deployment runs until the viewer -closes, the user presses Esc/Ctrl-C, or a runtime failure occurs. It then prints a JSON `RolloutResult`; artifact or -configuration failures exit with code 2. +Export selects the latest metadata-backed run. Use `run= output=` to select a specific run and +output. Export creates a new directory; it does not overwrite an existing artifact. -The minimal MuJoCo GLFW viewer opens by default and uses realtime pacing. Physics stepping remains owned by the backend -control path, and closing the window interrupts deployment safely. +The artifact contains the ONNX policy, robot/control contract, and serialized task specification. The installed task +plugin supplies the matching implementation and Hydra recipes, so deployment does not require workspace config files. +Task defaults select the Hydra runtime group (`sim` or `hardware`). All target-specific settings live directly under +`runtime`: `runtime.kind` selects the simulation or hardware path, while `runtime.backend` selects the plugin (`mujoco` +for simulation or `unitree_go2` for hardware). `runtime.backend` is a scalar string selector, not a nested configuration +mapping or Hydra group. The simulation recipe selects the scene and robot +(`runtime.deploy_env_id=flat`, `runtime.robot_id=go2`). -The artifact writer is create-only. Choose a new output path or move an existing artifact before exporting again. Joint, -actuator range, servo gain, tensor shape, checksum, and command-range mismatches fail before the first command. -Headless and physical runs may set either `rollout.steps` or `rollout.duration_s`; viewer runs may leave both unset. -`realtime` defaults to the viewer mode when omitted. +Omit `runtime.viewer=false duration_s=2.0` to open the MuJoCo viewer. Hold `W/S`, `A/D`, and `Q/E` for forward, lateral, and yaw +commands; Esc, Ctrl-C, or closing the window stops the run. Headless operation uses the recipe's constant zero velocity; +override `command.velocity` to change it. Common settings are `artifact`, `duration_s`, and `command`. +`duration_s` is a control-time budget of `ceil(duration_s / control_period_s)` ticks, not a wall-clock timer. +Simulation-only settings are `runtime.viewer` and `runtime.realtime`; `runtime.realtime=null` follows +`runtime.viewer`. Hardware runs are always real-time internally and expose neither simulation field. -Optional dependencies remain isolated behind extras for smaller runtime environments (ONNX -inference is a core dependency; `mujoco` and `unitree` select the deployment backends): +## Programmatic control -```bash -uv sync --package motrix-deploy-tasks --extra mujoco --extra unitree -``` - -Interactive deployment reads keyboard events directly from the focused GLFW viewer window: +[`examples/deploy_to_sim.py`](../examples/deploy_to_sim.py) assembles a `SceneCfg`, `SimulationRuntimeConfig`, walking task, +ONNX policy, and command binding, then binds a `ControlSession` to a MuJoCo runtime. It includes a sample policy: ```bash -motrix-deploy sim2sim \ - artifact=artifacts/go2-walk-rough.deploy +python examples/deploy_to_sim.py --headless --steps 100 ``` -Viewer mode uses realtime pacing. Hold `W/S`, `A/D`, and `Q/E` to set the signed forward, lateral, and yaw-rate -axes to the lower or upper command bound stored in the artifact; releasing a key removes its contribution, and Esc -interrupts the rollout. Losing viewer focus releases every held key, so typing in another window cannot command the robot. -The deployment viewer does not register MuJoCo's built-in visualization shortcuts, so `W/A/S/D` do not have a second effect. -Drag with the left mouse button to rotate the camera, right-drag to move it, and use the middle button or wheel to zoom. -The built-in interactive keyboard path requires `viewer=true`. - -The standing-probability random binding is task-specific training behavior and is not exposed by the deployment runtime. - -## Physical Go2 Sim2Real - -The hardware plugin imports the Unitree SDK2 Python package only when a physical backend is opened. The normal -workspace sync installs the pinned Motphys-maintained fork into the same environment: - -```bash -sh install.sh --all -``` - -Inspect the artifact first. With the robot suspended, low-level/debug mode enabled, Ethernet connected, and an emergency -stop operator ready, explicitly select the interface and confirm the hardware checklist: - -```bash -motrix-deploy inspect \ - artifact=artifacts/go2-walk-rough.deploy - -motrix-deploy sim2real \ - artifact=artifacts/go2-walk-rough.deploy \ - backend.network_interface=enp3s0 \ - hardware.confirm=true -``` +`ControlSession` reads input and state, builds observations, runs inference, and writes robot commands. The runtime owns +physics advancement, scheduling, and the viewer. A raw ONNX model needs explicit task preprocessing and action settings; +use an exported artifact when those settings should travel with the policy. -`hardware.confirm` defaults to `false`; `viewer=false` and `realtime=true` are mandatory for this backend. Before creating the `LowCmd` publisher, the backend stands down and releases the active MotionSwitcher mode (MCF), then disables the `sport_mode` service through RobotStateClient; any failure aborts startup. `StandDown` is a physical motion that occurs before the Start-button gate, so the area must be clear and an independent emergency stop must be ready. The Unitree remote is the default command source: left-stick Y controls forward velocity, left-stick X lateral velocity, and right-stick X yaw. The default mapping preserves the forward axis and inverts the lateral and yaw axes to match the policy convention. A 0.12 deadzone is applied and L1 is a deadman switch; releasing L1 immediately commands zero, while artifact bounds remain authoritative. The plugin -publishes zero torque until Start, interpolates from the measured joints to the artifact default pose over two seconds, -holds that pose until A, then enables policy commands. B interpolates to the configured backend-owned lie-down pose, then -enters damping and exits with `exit_reason=lie_down`; this does not widen artifact policy limits. Select remains the -higher-priority emergency stop. Select, Ctrl+C, stale state, or a runtime failure enters damping before both DDS channels -are closed. Artifact order `FL, FR, RL, RR` is mapped explicitly -to SDK motor order `FR, FL, RR, RL`. Software and injected-fake-SDK tests cover this path; a suspended real-robot smoke -test is still required before real-world operation. +For the training-to-hardware workflow and safety checklist, see the +[deployment tutorial](../docs/source/en/user_guide/tutorial/advanced/motrix_deploy.md) and +[Unitree plugin guide](../motrix_deploy_unitree/README.md). diff --git a/motrix_deploy/pyproject.toml b/motrix_deploy/pyproject.toml index e2dc4711..0eadbb75 100644 --- a/motrix_deploy/pyproject.toml +++ b/motrix_deploy/pyproject.toml @@ -17,4 +17,8 @@ dependencies = [ "motrix-env-core", "numpy>=1.26", "onnxruntime==1.23.2", + "pydantic===2.12.5", ] + +[project.scripts] +motrix-deploy = "motrix_deploy.cli:main" diff --git a/motrix_deploy/src/motrix_deploy/artifact/io.py b/motrix_deploy/src/motrix_deploy/artifact/io.py index db59d713..d06500bf 100644 --- a/motrix_deploy/src/motrix_deploy/artifact/io.py +++ b/motrix_deploy/src/motrix_deploy/artifact/io.py @@ -149,8 +149,8 @@ def inspect_artifact(root: str | Path) -> dict[str, Any]: "joint_names": list(manifest.robot.joint_names), }, "task": manifest.task.to_dict(), - "observation_size": manifest.task.observation_size, - "action_size": manifest.task.action_size, + "observation_size": manifest.policy.input.shape[1], + "action_size": manifest.policy.output.shape[1], "control_period_s": manifest.control.period_s, } diff --git a/motrix_deploy/src/motrix_deploy/artifact/schema.py b/motrix_deploy/src/motrix_deploy/artifact/schema.py index 341046b5..3f64c6ef 100644 --- a/motrix_deploy/src/motrix_deploy/artifact/schema.py +++ b/motrix_deploy/src/motrix_deploy/artifact/schema.py @@ -4,13 +4,16 @@ """Schema v1 for the deployment manifest.""" import re +from abc import ABC from collections.abc import Mapping from dataclasses import dataclass -from typing import Any +from typing import Any, ClassVar import numpy as np +from pydantic import BaseModel, ConfigDict +from pydantic import ValidationError as PydanticValidationError -from motrix_deploy.contracts import RobotSpec, TensorSpec +from motrix_deploy.contracts import JointControlMode, RobotSpec, TensorSpec from motrix_deploy.errors import ValidationError SCHEMA_VERSION = "motrix-deploy/v1" @@ -157,49 +160,73 @@ def _robot_from_dict(value: object) -> RobotSpec: position_upper=np.asarray(data["position_upper"], dtype=np.float32), torque_limit=np.asarray(data["torque_limit"], dtype=np.float32), ) + except ValidationError: + raise except (TypeError, ValueError) as error: - if isinstance(error, ValidationError): - raise raise ValidationError("robot", "numeric arrays matching joint_names", str(error)) from error -@dataclass(frozen=True) -class TaskSpec: - """Versioned task runtime selection and its JSON-compatible configuration.""" +def _task_validation_error(error: PydanticValidationError) -> ValidationError: + """Translate model diagnostics into the deployment artifact field namespace.""" + detail = error.errors(include_url=False)[0] + location = ".".join(str(part) for part in detail["loc"]) + path = f"task.config.{location}" if location else "task.config" + if detail["type"] == "missing": + return ValidationError( + path, "a required task field (re-export artifacts using an older task schema)", "missing" + ) + return ValidationError(path, detail["msg"], detail.get("input")) - name: str - observation_size: int - action_size: int - config: Mapping[str, Any] - def __post_init__(self) -> None: - if not isinstance(self.name, str) or _TASK_NAME_PATTERN.fullmatch(self.name) is None: - raise ValidationError("task.name", "a versioned task identifier such as go2_walk/v1", self.name) - for field_name in ("observation_size", "action_size"): - value = getattr(self, field_name) - if not isinstance(value, int) or isinstance(value, bool) or value <= 0: - raise ValidationError(f"task.{field_name}", "a positive integer", value) - _mapping(self.config, "task.config") +class TaskSpec(BaseModel, ABC): + """Typed task configuration, resolved through an installed versioned task class. + + Concrete implementations declare ``task_name`` as a ClassVar and use + Pydantic fields and validators for configuration semantics. YAML composition + and interpolation resolution belong to the caller, not the artifact codec. + """ + + model_config = ConfigDict(strict=True, extra="forbid", frozen=True, allow_inf_nan=False, validate_default=True) + task_name: ClassVar[str] + + @property + def name(self) -> str: + return self.task_name def to_dict(self) -> dict[str, Any]: - return { - "name": self.name, - "observation_size": self.observation_size, - "action_size": self.action_size, - "config": dict(self.config), - } + # Frozen models may still contain mutable lists. Revalidate a fresh + # mapping so mutations cannot bypass the artifact serialization boundary. + try: + spec = type(self).model_validate(self.model_dump()) + except PydanticValidationError as error: + raise _task_validation_error(error) from error + return {"name": self.name, "config": spec.model_dump(mode="json")} @classmethod def from_dict(cls, value: object) -> "TaskSpec": data = _mapping(value, "task") - required = {"name", "observation_size", "action_size", "config"} - _keys(data, path="task", required=required) - return cls( - name=data["name"], - observation_size=data["observation_size"], - action_size=data["action_size"], - config=data["config"], - ) + _keys(data, path="task", required={"name", "config"}) + name = data["name"] + if not isinstance(name, str) or _TASK_NAME_PATTERN.fullmatch(name) is None: + raise ValidationError("task.name", "a versioned task identifier such as go2_walk/v1", name) + config = _mapping(data["config"], "task.config") + # Lazy import: the runtime contract itself depends on TaskSpec. + from motrix_deploy.task import load_task_type + + try: + spec_type = getattr(load_task_type(name), "spec_type", None) + except Exception as error: + raise ValidationError("task.name", "an available installed task class plugin", str(error)) from error + if ( + not isinstance(spec_type, type) + or not issubclass(spec_type, TaskSpec) + or getattr(spec_type, "task_name", None) != name + ): + raise ValidationError("task.name", "a TaskSpec BaseModel with matching task_name", name) + try: + return spec_type.model_validate(config) + except PydanticValidationError as error: + raise _task_validation_error(error) from error @dataclass(frozen=True) @@ -211,8 +238,13 @@ class ControlSpec: quaternion_order: str = "xyzw" base_orientation: str = "body_to_world" angular_velocity_frame: str = "body" + mode: JointControlMode = JointControlMode.SERVO def __post_init__(self) -> None: + try: + object.__setattr__(self, "mode", JointControlMode(self.mode)) + except (TypeError, ValueError) as error: + raise ValidationError("control.mode", "joint_servo or joint_torque", self.mode) from error _validate_positive_number(self.period_s, "control.period_s") _validate_positive_number(self.state_timeout_s, "control.state_timeout_s") if self.quaternion_order != "xyzw": @@ -224,6 +256,7 @@ def __post_init__(self) -> None: def to_dict(self) -> dict[str, Any]: return { + "mode": self.mode.value, "period_s": self.period_s, "state_timeout_s": self.state_timeout_s, "quaternion_order": self.quaternion_order, @@ -235,6 +268,7 @@ def to_dict(self) -> dict[str, Any]: def from_dict(cls, value: object) -> "ControlSpec": data = _mapping(value, "control") required = { + "mode", "period_s", "state_timeout_s", "quaternion_order", @@ -259,14 +293,10 @@ class DeploymentManifest: def __post_init__(self) -> None: if self.schema_version != SCHEMA_VERSION: raise ValidationError("schema_version", SCHEMA_VERSION, self.schema_version) - if self.policy.input.shape != (1, self.task.observation_size): - raise ValidationError( - "policy.input.shape", - str((1, self.task.observation_size)), - self.policy.input.shape, - ) - if self.policy.output.shape != (1, self.task.action_size): - raise ValidationError("policy.output.shape", str((1, self.task.action_size)), self.policy.output.shape) + for field_name in ("input", "output"): + shape = getattr(self.policy, field_name).shape + if len(shape) != 2 or shape[0] != 1: + raise ValidationError(f"policy.{field_name}.shape", "(1, size): a single batch-1 vector", shape) def to_dict(self) -> dict[str, Any]: return { diff --git a/motrix_deploy/src/motrix_deploy/backend/__init__.py b/motrix_deploy/src/motrix_deploy/backend/__init__.py deleted file mode 100644 index ec378928..00000000 --- a/motrix_deploy/src/motrix_deploy/backend/__init__.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright Motphys Technology Co., Ltd. 2025, 2026 -# SPDX-License-Identifier: Apache-2.0 - -"""Robot backend lifecycle contract.""" - -from abc import ABC, abstractmethod -from collections.abc import Callable, Mapping -from dataclasses import dataclass -from importlib import metadata -from typing import Any, Protocol, runtime_checkable - -from motrix_deploy.artifact import ControlSpec -from motrix_deploy.contracts import HealthStatus, RobotCapabilities, RobotCommand, RobotSpec, RobotState -from motrix_env_core.input import GamePadDevice, KeyboardDevice - - -class RobotInterface(ABC): - """Backend boundary shared by simulators and physical robots.""" - - @property - @abstractmethod - def capabilities(self) -> RobotCapabilities: - """Return immutable capabilities used during startup negotiation.""" - - @abstractmethod - def open(self, spec: RobotSpec) -> None: - """Open resources and bind backend joints to the canonical spec.""" - - def enable(self, initial_command: RobotCommand) -> None: - """Explicitly enable command output when required by the backend.""" - del initial_command - if self.capabilities.requires_enable: - raise NotImplementedError("backend declares requires_enable=True but does not implement enable()") - - @abstractmethod - def read_state(self, timeout_s: float) -> RobotState: - """Read one state sample or raise on timeout.""" - - @abstractmethod - def write_command(self, command: RobotCommand) -> None: - """Apply one canonical command.""" - - @abstractmethod - def health(self) -> HealthStatus: - """Return current backend health.""" - - @abstractmethod - def stop(self) -> None: - """Enter the backend's declared safe stopped state; must be idempotent.""" - - @abstractmethod - def close(self) -> None: - """Release resources; must be idempotent.""" - - -@runtime_checkable -class KeyboardDeviceProvider(Protocol): - """Optional backend capability that supplies a focused keyboard device.""" - - def get_keyboard_device(self) -> KeyboardDevice: - """Return a device whose lifecycle is owned by the backend.""" - - -@runtime_checkable -class GamePadDeviceProvider(Protocol): - """Optional backend capability that supplies a gamepad-like device.""" - - def get_gamepad_device(self) -> GamePadDevice: - """Return a device whose lifecycle is owned by the backend.""" - - -@dataclass(frozen=True) -class BackendCreateContext: - """Runtime values supplied by the deployment core when constructing a backend.""" - - control: ControlSpec - viewer: bool - realtime: bool = False - hardware_confirmed: bool = False - - -BackendFactory = Callable[[Mapping[str, Any], BackendCreateContext], RobotInterface] -BACKEND_ENTRY_POINT_GROUP = "motrix_deploy.backends" - - -def _backend_entry_points() -> tuple[metadata.EntryPoint, ...]: - return tuple(metadata.entry_points(group=BACKEND_ENTRY_POINT_GROUP)) - - -def registered_backends() -> tuple[str, ...]: - """Return backend names advertised by installed plugin distributions.""" - return tuple(sorted(entry_point.name for entry_point in _backend_entry_points())) - - -def create_backend( - name: str, - config: Mapping[str, Any], - context: BackendCreateContext, -) -> RobotInterface: - """Discover and construct one installed backend plugin by name.""" - matches = [entry_point for entry_point in _backend_entry_points() if entry_point.name == name] - if not matches: - available = ", ".join(registered_backends()) or "none" - raise ValueError(f"Unsupported deployment backend {name!r}; installed backends: {available}") - if len(matches) != 1: - values = sorted(entry_point.value for entry_point in matches) - raise ValueError(f"Multiple deployment backend plugins are registered as {name!r}: {values}") - factory = matches[0].load() - if not callable(factory): - raise TypeError(f"Deployment backend plugin {name!r} must load a callable factory") - backend = factory(config, context) - if not isinstance(backend, RobotInterface): - raise TypeError( - f"Deployment backend plugin {name!r} returned {type(backend).__name__}, expected RobotInterface" - ) - return backend - - -__all__ = [ - "BACKEND_ENTRY_POINT_GROUP", - "BackendCreateContext", - "BackendFactory", - "GamePadDeviceProvider", - "KeyboardDeviceProvider", - "RobotInterface", - "create_backend", - "registered_backends", -] diff --git a/motrix_deploy/src/motrix_deploy/cli.py b/motrix_deploy/src/motrix_deploy/cli.py index 07fc4d92..f1e1bd39 100644 --- a/motrix_deploy/src/motrix_deploy/cli.py +++ b/motrix_deploy/src/motrix_deploy/cli.py @@ -15,26 +15,19 @@ from omegaconf.errors import OmegaConfBaseException from motrix_deploy import __version__ -from motrix_deploy.artifact import inspect_artifact, read_artifact -from motrix_deploy.backend import ( - BackendCreateContext, - GamePadDeviceProvider, - KeyboardDeviceProvider, - create_backend, -) +from motrix_deploy.artifact.io import inspect_artifact, read_artifact from motrix_deploy.config import DeployRunConfig from motrix_deploy.errors import ArtifactError, ValidationError +from motrix_deploy.robot.interface import GamePadDeviceProvider, KeyboardDeviceProvider +from motrix_deploy.runtime.config import SimulationRuntimeConfig +from motrix_deploy.runtime.factory import RuntimeCreateContext, create_hardware_runtime, create_simulation_runtime from motrix_deploy.task import create_task def run(cfg: DeployRunConfig) -> int: """Run one deployment rollout from typed application configuration.""" from motrix_deploy.policy import OnnxPolicyRuntime - from motrix_deploy.runtime import ( - ControlLoop, - FixedStepScheduler, - RealtimeScheduler, - ) + from motrix_deploy.runtime.control import ControlSession from motrix_env_core.input import ( BoundedGamePadPlanarVelocityBinding, ConstantPlanarVelocityBinding, @@ -43,29 +36,55 @@ def run(cfg: DeployRunConfig) -> int: artifact = read_artifact(Path(cfg.artifact)) manifest = artifact.manifest + steps = _duration_steps(cfg.duration_s, manifest.control.period_s) task = create_task(manifest.task, manifest.robot) - viewer = cfg.viewer - realtime = viewer if cfg.realtime is None else cfg.realtime - hardware_confirmed = cfg.hardware.get("confirm", False) - backend = create_backend( - cfg.backend_name, - cfg.backend_options, - BackendCreateContext( - control=manifest.control, - viewer=viewer, - realtime=realtime, - hardware_confirmed=hardware_confirmed, - ), - ) + simulation = cfg.runtime["kind"] == "simulation" + viewer = cfg.runtime["viewer"] if simulation else False + realtime = cfg.runtime["realtime"] if simulation else True + if realtime is None: + realtime = viewer + runtime_options = cfg.runtime_options + if simulation: + from motrix_deploy.env import assemble_deploy_scene + + for field in ("deploy_env_id", "robot_id"): + if not isinstance(runtime_options.get(field), str) or not runtime_options[field]: + raise ValidationError(f"runtime.{field}", "a non-empty registered ID", runtime_options.get(field)) + deploy_env_id = runtime_options.pop("deploy_env_id") + robot_id = runtime_options.pop("robot_id") + scene = assemble_deploy_scene(deploy_env_id, robot_id) + for field in ("translation", "rotation"): + option = f"robot_{field}" + if option in runtime_options: + value = runtime_options.pop(option) + setattr(scene.objs.robot, field, None if value is None else tuple(value)) + runtime_options["scene"] = scene + runtime_config = SimulationRuntimeConfig.from_mapping(runtime_options, render=viewer, realtime=realtime) + runtime = create_simulation_runtime(cfg.backend_name, runtime_config) + else: + runtime = create_hardware_runtime( + cfg.backend_name, + runtime_options, + RuntimeCreateContext( + robot=manifest.robot, + control=manifest.control, + ), + ) + backend = runtime.robot + try: + manifest.robot.validate_compatible(backend.spec) + except ValidationError: + runtime.close() + raise command_lower = getattr(task, "command_lower", None) command_upper = getattr(task, "command_upper", None) if command_lower is None or command_upper is None: raise ValueError(f"Task {manifest.task.name!r} does not provide planar velocity command bounds") command_config = cfg.command or {} command_source = command_config.get("source", "constant") - if viewer and isinstance(backend, KeyboardDeviceProvider): + if viewer and isinstance(runtime, KeyboardDeviceProvider): command_binding = KeyboardPlanarVelocityBinding( - backend.get_keyboard_device(), + runtime.get_keyboard_device(), command_lower=command_lower, command_upper=command_upper, ) @@ -94,44 +113,33 @@ def run(cfg: DeployRunConfig) -> int: task.validate_command(command_binding.read_command()) else: raise ValueError(f"Unsupported command source {command_source!r}; use gamepad or configure command.velocity") - loop = ControlLoop( - robot=manifest.robot, - backend=backend, + control = ControlSession( + robot=backend, task=task, policy=OnnxPolicyRuntime(artifact.policy_path, manifest.policy.input, manifest.policy.output), command_binding=command_binding, - scheduler=( - RealtimeScheduler(manifest.control.period_s) if realtime else FixedStepScheduler(manifest.control.period_s) - ), + period_s=manifest.control.period_s, state_timeout_s=manifest.control.state_timeout_s, ) - result = loop.run(steps=_rollout_steps(cfg.rollout, manifest.control.period_s)) + runtime.bind_control_session(control) + with runtime: + result = runtime.run(steps=steps) print(json.dumps(result.to_dict(), indent=2, sort_keys=True)) return 0 if result.success else 1 -def _rollout_steps(cfg: dict[str, object] | None, control_period_s: float) -> int | None: - if cfg is None: +def _duration_steps(duration_s: float | None, control_period_s: float) -> int | None: + """Convert a control-time duration to a tick budget, not a wall-clock timeout.""" + if duration_s is None: return None - steps = cfg.get("steps") - duration_s = cfg.get("duration_s") - if steps is None and duration_s is None: - return None - if steps is not None and duration_s is not None: - raise ValueError("Exactly one of rollout.steps and rollout.duration_s must be configured") - if steps is not None: - if isinstance(steps, bool) or not isinstance(steps, int) or steps <= 0: - raise ValueError(f"rollout.steps must be a positive integer, got {steps!r}") - return steps if isinstance(duration_s, bool) or not isinstance(duration_s, (int, float)): - raise ValueError(f"rollout.duration_s must be a positive finite number, got {duration_s!r}") + raise ValueError(f"duration_s must be a positive finite number, got {duration_s!r}") if not np.isfinite(duration_s) or duration_s <= 0: - raise ValueError(f"rollout.duration_s must be a positive finite number, got {duration_s!r}") + raise ValueError(f"duration_s must be a positive finite number, got {duration_s!r}") return math.ceil(duration_s / control_period_s) -@hydra.main(version_base=None, config_path="config", config_name="deploy") -def _hydra_main(cfg: DictConfig) -> None: +def _run_config(cfg: DictConfig) -> None: try: typed_cfg = OmegaConf.to_object(OmegaConf.merge(OmegaConf.structured(DeployRunConfig), cfg)) if not isinstance(typed_cfg, DeployRunConfig): @@ -144,12 +152,15 @@ def _hydra_main(cfg: DictConfig) -> None: raise SystemExit(exit_code) +_hydra_main = hydra.main(version_base=None, config_path="pkg://motrix_deploy.config", config_name="deploy")(_run_config) + + def inspect(cfg: DictConfig) -> None: """Validate one deployment artifact without opening a backend.""" print(json.dumps(inspect_artifact(Path(cfg.artifact)), indent=2, sort_keys=True)) -@hydra.main(version_base=None, config_path="config", config_name="inspect") +@hydra.main(version_base=None, config_path="pkg://motrix_deploy.config", config_name="inspect") def _hydra_inspect_main(cfg: DictConfig) -> None: try: inspect(cfg) @@ -178,8 +189,6 @@ def main(argv: Sequence[str] | None = None) -> int | None: return 0 if not args: sys.argv.append("--help") - elif args[:1] in (["sim2sim"], ["sim2real"]): - del sys.argv[1] _hydra_main() return None diff --git a/motrix_deploy/src/motrix_deploy/config/__init__.py b/motrix_deploy/src/motrix_deploy/config/__init__.py index bea3496e..311eef32 100644 --- a/motrix_deploy/src/motrix_deploy/config/__init__.py +++ b/motrix_deploy/src/motrix_deploy/config/__init__.py @@ -3,44 +3,45 @@ """Typed application configuration for the deployment CLI.""" -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any @dataclass class DeployRunConfig: - """Resolved, task-agnostic configuration consumed by one deployment run.""" + """Resolved execution settings with one selected runtime configuration.""" artifact: str - backend: dict[str, Any] - viewer: bool + runtime: dict[str, Any] + duration_s: float | None = None command: dict[str, Any] | None = None - rollout: dict[str, Any] | None = None - seed: int | None = None - realtime: bool | None = None - hardware: dict[str, Any] = field(default_factory=dict) def __post_init__(self) -> None: if not self.artifact: raise ValueError("artifact must be a non-empty path") - if not isinstance(self.viewer, bool): - raise ValueError(f"viewer must be bool, got {self.viewer!r}") - if self.realtime is not None and not isinstance(self.realtime, bool): - raise ValueError(f"realtime must be bool or null, got {self.realtime!r}") - confirmed = self.hardware.get("confirm", False) - if not isinstance(confirmed, bool): - raise ValueError(f"hardware.confirm must be bool, got {confirmed!r}") - backend_name = self.backend.get("name") + kind = self.runtime.get("kind") + if kind not in {"simulation", "hardware"}: + raise ValueError(f"runtime.kind must be simulation or hardware, got {kind!r}") + if kind == "simulation": + if not isinstance(self.runtime.get("viewer"), bool): + raise ValueError("runtime.viewer must be bool") + realtime = self.runtime.get("realtime") + if realtime is not None and not isinstance(realtime, bool): + raise ValueError("runtime.realtime must be bool or null") + backend_name = self.runtime.get("backend") if not isinstance(backend_name, str) or not backend_name: - raise ValueError(f"backend.name must be a non-empty string, got {backend_name!r}") + raise ValueError(f"runtime.backend must be a non-empty string, got {backend_name!r}") @property def backend_name(self) -> str: - return self.backend["name"] + return self.runtime["backend"] @property - def backend_options(self) -> dict[str, Any]: - return {name: value for name, value in self.backend.items() if name != "name"} + def runtime_options(self) -> dict[str, Any]: + execution_fields = {"kind", "backend"} + if self.runtime["kind"] == "simulation": + execution_fields |= {"viewer", "realtime"} + return {name: value for name, value in self.runtime.items() if name not in execution_fields} __all__ = ["DeployRunConfig"] diff --git a/motrix_deploy/src/motrix_deploy/config/deploy.yaml b/motrix_deploy/src/motrix_deploy/config/deploy.yaml index da9b118c..fa7154df 100644 --- a/motrix_deploy/src/motrix_deploy/config/deploy.yaml +++ b/motrix_deploy/src/motrix_deploy/config/deploy.yaml @@ -1,5 +1,6 @@ defaults: - _self_ + - task: null - override hydra/job_logging: stdout hydra: @@ -13,5 +14,6 @@ hydra: chdir: false artifact: ??? -backend: ??? -viewer: ??? +runtime: ??? +duration_s: null +command: null diff --git a/motrix_deploy/src/motrix_deploy/config/runtime/hardware.yaml b/motrix_deploy/src/motrix_deploy/config/runtime/hardware.yaml new file mode 100644 index 00000000..4684c816 --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/config/runtime/hardware.yaml @@ -0,0 +1,2 @@ +kind: hardware +backend: ??? diff --git a/motrix_deploy/src/motrix_deploy/config/runtime/sim.yaml b/motrix_deploy/src/motrix_deploy/config/runtime/sim.yaml new file mode 100644 index 00000000..a0ea0f6d --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/config/runtime/sim.yaml @@ -0,0 +1,4 @@ +kind: simulation +backend: ??? +viewer: true +realtime: null diff --git a/motrix_deploy/src/motrix_deploy/contracts.py b/motrix_deploy/src/motrix_deploy/contracts.py index 9b02f25b..6f4519f0 100644 --- a/motrix_deploy/src/motrix_deploy/contracts.py +++ b/motrix_deploy/src/motrix_deploy/contracts.py @@ -5,6 +5,8 @@ from collections.abc import Mapping from dataclasses import dataclass, field +from enum import Enum +from typing import TypeAlias import numpy as np from numpy.typing import NDArray @@ -99,6 +101,19 @@ def __post_init__(self) -> None: if np.any(self.torque_limit <= 0): raise ValidationError("robot.torque_limit", "positive values", self.torque_limit.tolist()) + def validate_compatible(self, actual: "RobotSpec") -> None: + """Require a runtime robot to match this policy/task contract in canonical order.""" + for name in ("base_link_name", "joint_names"): + expected_value = getattr(self, name) + actual_value = getattr(actual, name) + if expected_value != actual_value: + raise ValidationError(f"robot.{name}", expected_value, actual_value) + for name in ("default_joint_position", "position_lower", "position_upper", "torque_limit"): + expected_value = getattr(self, name) + actual_value = getattr(actual, name) + if not np.allclose(expected_value, actual_value, atol=1e-6, rtol=0.0): + raise ValidationError(f"robot.{name}", expected_value.tolist(), actual_value.tolist()) + @property def joint_count(self) -> int: return len(self.joint_names) @@ -170,9 +185,16 @@ def __post_init__(self) -> None: _validate_float32_array(value, path=f"state.extras.{name}", shape=value.shape) +class JointControlMode(str, Enum): + """Canonical joint command modes shared by artifacts and backends.""" + + SERVO = "joint_servo" + TORQUE = "joint_torque" + + @dataclass -class RobotCommand: - """Canonical joint PD plus feed-forward command in SI units.""" +class JointServoCommand: + """Canonical joint servo plus feed-forward command in SI units.""" joint_position: FloatArray joint_velocity: FloatArray @@ -203,14 +225,40 @@ def __post_init__(self) -> None: if np.any(self.kp < 0) or np.any(self.kd < 0): raise ValidationError("command.gains", "non-negative values", "negative gain") + @property + def mode(self) -> JointControlMode: + return JointControlMode.SERVO + + +@dataclass +class JointTorqueCommand: + """Canonical direct joint torque command in SI units.""" + + torque: FloatArray + + def __post_init__(self) -> None: + if not isinstance(self.torque, np.ndarray) or self.torque.ndim != 1 or not self.torque.size: + actual = self.torque.shape if isinstance(self.torque, np.ndarray) else type(self.torque) + raise ValidationError("command.torque.shape", "(joint_count,)", actual) + _validate_float32_array(self.torque, path="command.torque", shape=(self.torque.size,)) + + @property + def mode(self) -> JointControlMode: + return JointControlMode.TORQUE + + +RobotCommand: TypeAlias = JointServoCommand | JointTorqueCommand + @dataclass(frozen=True) class RobotCapabilities: """Static backend capabilities negotiated before control starts. Attributes: - control_modes: Canonical command modes accepted by the backend, such as ``joint_pd``. + control_modes: Canonical joint command modes accepted by the backend. state_fields: ``RobotState`` fields populated by every state sample. + privileged_state_fields: Subset of state_fields supplied as privileged simulator ground truth, + not measurements guaranteed to be available on hardware. extra_sensors: Additional entries available in ``RobotState.extras``, mapped to their fixed shapes. supports_rendering: Whether the backend can present a live viewer. max_command_rate_hz: Highest command update rate accepted by the backend, or ``None`` when unconstrained. @@ -218,20 +266,37 @@ class RobotCapabilities: stop_semantics: Backend behavior after ``RobotInterface.stop()``, for example ``hold_position`` or ``damping``. """ - control_modes: tuple[str, ...] + control_modes: tuple[JointControlMode, ...] state_fields: frozenset[str] extra_sensors: Mapping[str, tuple[int, ...]] = field(default_factory=dict) supports_rendering: bool = False max_command_rate_hz: float | None = None requires_enable: bool = False stop_semantics: str = "hold_position" + privileged_state_fields: frozenset[str] = frozenset() def __post_init__(self) -> None: _validate_names(self.control_modes, path="capabilities.control_modes") + try: + modes = tuple(JointControlMode(mode) for mode in self.control_modes) + except ValueError as error: + raise ValidationError( + "capabilities.control_modes", "joint_servo or joint_torque", self.control_modes + ) from error + object.__setattr__(self, "control_modes", modes) if not isinstance(self.state_fields, frozenset) or any( not isinstance(name, str) or not name for name in self.state_fields ): raise ValidationError("capabilities.state_fields", "a frozenset of non-empty names", self.state_fields) + if ( + not isinstance(self.privileged_state_fields, frozenset) + or not self.privileged_state_fields <= self.state_fields + ): + raise ValidationError( + "capabilities.privileged_state_fields", + "a frozenset subset of state_fields", + self.privileged_state_fields, + ) if self.max_command_rate_hz is not None and self.max_command_rate_hz <= 0: raise ValidationError("capabilities.max_command_rate_hz", "a positive value", self.max_command_rate_hz) if not self.stop_semantics: @@ -245,7 +310,10 @@ def __post_init__(self) -> None: @dataclass(frozen=True) class HealthStatus: - """Backend health with a reason and last successful communication time.""" + """Backend resource/communication health, not task success or fall detection. + + Task-owned termination is evaluated separately by DeployTask.check_termination. + """ healthy: bool reason: str diff --git a/motrix_deploy/src/motrix_deploy/env.py b/motrix_deploy/src/motrix_deploy/env.py new file mode 100644 index 00000000..2c4f78e7 --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/env.py @@ -0,0 +1,40 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Installed robot-independent deployment world factories.""" + +from importlib import metadata + +from motrix_env_core import registry +from motrix_env_core.config.scene import SceneCfg + +DEPLOY_ENV_ENTRY_POINT_GROUP = "motrix_deploy.envs" + + +def available_deploy_envs() -> tuple[str, ...]: + """List installed deployment environment IDs without loading factories.""" + return tuple(sorted({entry.name for entry in metadata.entry_points(group=DEPLOY_ENV_ENTRY_POINT_GROUP)})) + + +def create_deploy_env(deploy_env_id: str) -> SceneCfg: + """Create a fresh robot-free scene from one installed environment factory.""" + entries = tuple(metadata.entry_points(group=DEPLOY_ENV_ENTRY_POINT_GROUP)) + matches = [entry for entry in entries if entry.name == deploy_env_id] + if not matches: + available = ", ".join(sorted({entry.name for entry in entries})) or "none" + raise ValueError(f"Unknown deployment environment {deploy_env_id!r}; available environments: {available}") + if len(matches) != 1: + raise ValueError(f"Multiple deployment environment plugins provide {deploy_env_id!r}") + scene = matches[0].load()() + if not isinstance(scene, SceneCfg): + raise TypeError(f"Deployment environment {deploy_env_id!r} must return SceneCfg, got {type(scene).__name__}") + if scene.objs.robot is not None: + raise ValueError(f"Deployment environment {deploy_env_id!r} must leave scene.objs.robot empty") + return scene + + +def assemble_deploy_scene(deploy_env_id: str, robot_id: str) -> SceneCfg: + """Assemble an independently selected world and registered robot for deployment.""" + scene = create_deploy_env(deploy_env_id) + scene.objs.robot = registry.make_robot_config(robot_id) + return scene diff --git a/motrix_deploy/src/motrix_deploy/policy/__init__.py b/motrix_deploy/src/motrix_deploy/policy/__init__.py index fdb858d7..7d91d05a 100644 --- a/motrix_deploy/src/motrix_deploy/policy/__init__.py +++ b/motrix_deploy/src/motrix_deploy/policy/__init__.py @@ -14,26 +14,37 @@ class PolicyRuntime(ABC): - """Framework-independent deterministic policy inference.""" + """Decision runtime for a task's vector observation/action interface. - @property - @abstractmethod - def input_spec(self) -> TensorSpec: - """Return the policy input contract.""" - - @property - @abstractmethod - def output_spec(self) -> TensorSpec: - """Return the policy output contract.""" + Implementations may use networks, planners, or fixed decisions. Tensor names + and dimensions are implementation details, not requirements of this contract. + """ def reset(self) -> None: - """Reset recurrent state; v1 policies are stateless.""" + """Reset decision state before a run; stateless implementations do nothing.""" @abstractmethod def infer(self, observation: FloatArray) -> FloatArray: """Infer one unbatched action.""" +class NoOpPolicyRuntime(PolicyRuntime): + """Ignore observations and return zero actions; the task defines their meaning. + + A zero action does not imply zero torque or skipping a command. For example, + a pose-offset task interprets it as holding its reference pose. + """ + + def __init__(self, action_size: int) -> None: + if isinstance(action_size, bool) or not isinstance(action_size, int) or action_size < 0: + raise ValidationError("policy.noop.action_size", "a non-negative integer", action_size) + self._action = np.zeros(action_size, dtype=np.float32) + + def infer(self, observation: FloatArray) -> FloatArray: + del observation + return self._action.copy() + + class OnnxPolicyRuntime(PolicyRuntime): """Single-input, single-output ONNX Runtime policy.""" @@ -100,4 +111,4 @@ def infer(self, observation: FloatArray) -> FloatArray: return float32_array(output[0], path="policy.output", shape=(self.output_spec.shape[1],)) -__all__ = ["OnnxPolicyRuntime", "PolicyRuntime"] +__all__ = ["NoOpPolicyRuntime", "OnnxPolicyRuntime", "PolicyRuntime"] diff --git a/motrix_deploy/src/motrix_deploy/profile.py b/motrix_deploy/src/motrix_deploy/profile.py index e3c60867..df68d774 100644 --- a/motrix_deploy/src/motrix_deploy/profile.py +++ b/motrix_deploy/src/motrix_deploy/profile.py @@ -6,7 +6,7 @@ from collections.abc import Callable from dataclasses import dataclass -from motrix_deploy.artifact import ControlSpec, TaskSpec +from motrix_deploy.artifact.schema import ControlSpec, TaskSpec from motrix_deploy.contracts import RobotSpec @@ -17,6 +17,9 @@ class DeploymentProfile: robot: RobotSpec task: TaskSpec control: ControlSpec + # Training-space dimensions used only to validate ONNX export, not serialized in TaskSpec. + observation_size: int + action_size: int ProfileCompiler = Callable[[str], DeploymentProfile] diff --git a/motrix_deploy/src/motrix_deploy/robot/__init__.py b/motrix_deploy/src/motrix_deploy/robot/__init__.py new file mode 100644 index 00000000..7981cefe --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/robot/__init__.py @@ -0,0 +1,4 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Robot I/O contracts and adapters.""" diff --git a/motrix_deploy/src/motrix_deploy/robot/interface.py b/motrix_deploy/src/motrix_deploy/robot/interface.py new file mode 100644 index 00000000..725e0737 --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/robot/interface.py @@ -0,0 +1,72 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Public robot I/O and device capability contracts.""" + +from abc import ABC, abstractmethod +from typing import Protocol, runtime_checkable + +from motrix_deploy.contracts import HealthStatus, RobotCapabilities, RobotCommand, RobotSpec, RobotState +from motrix_env_core.input import GamePadDevice, KeyboardDevice + + +class RobotInterface(ABC): + """Robot boundary shared by simulation and hardware runtimes.""" + + @property + @abstractmethod + def spec(self) -> RobotSpec: + """Return the canonical robot specification bound to this interface.""" + + @property + @abstractmethod + def capabilities(self) -> RobotCapabilities: + """Return immutable capabilities used during startup negotiation.""" + + @abstractmethod + def open(self) -> None: + """Open resources for the robot specification supplied at construction.""" + + def enable(self) -> None: + """Enable command output using backend-owned startup safety behavior.""" + if self.capabilities.requires_enable: + raise NotImplementedError("robot declares requires_enable=True but does not implement enable()") + + @abstractmethod + def read_state(self, timeout_s: float) -> RobotState: + """Read one state sample or raise on timeout.""" + + @abstractmethod + def write_command(self, command: RobotCommand) -> None: + """Apply one canonical command.""" + + @abstractmethod + def health(self) -> HealthStatus: + """Return current robot I/O health.""" + + @abstractmethod + def stop(self) -> None: + """Enter the declared safe stopped state; must be idempotent.""" + + @abstractmethod + def close(self) -> None: + """Release robot I/O resources; must be idempotent.""" + + +@runtime_checkable +class KeyboardDeviceProvider(Protocol): + """Optional runtime capability supplying keyboard input.""" + + def get_keyboard_device(self) -> KeyboardDevice: + """Return a device whose lifecycle is owned by its provider.""" + + +@runtime_checkable +class GamePadDeviceProvider(Protocol): + """Optional robot capability supplying gamepad input.""" + + def get_gamepad_device(self) -> GamePadDevice: + """Return a device whose lifecycle is owned by its provider.""" + + +__all__ = ["GamePadDeviceProvider", "KeyboardDeviceProvider", "RobotInterface"] diff --git a/motrix_deploy/src/motrix_deploy/runtime/__init__.py b/motrix_deploy/src/motrix_deploy/runtime/__init__.py index 401144a3..560dddbf 100644 --- a/motrix_deploy/src/motrix_deploy/runtime/__init__.py +++ b/motrix_deploy/src/motrix_deploy/runtime/__init__.py @@ -1,26 +1,36 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 -"""Unified policy deployment runtime.""" +"""Unified policy deployment runtime public API.""" +from motrix_deploy.runtime.base import DeploymentRuntime, SimulationRuntime +from motrix_deploy.runtime.config import SensorBindings, SimulationRuntimeConfig from motrix_deploy.runtime.context import PolicyContext +from motrix_deploy.runtime.control import ControlSession +from motrix_deploy.runtime.factory import ( + RuntimeCreateContext, + create_hardware_runtime, + create_simulation_runtime, + registered_runtimes, +) +from motrix_deploy.runtime.hardware import HardwareRuntime from motrix_deploy.runtime.result import RolloutResult from motrix_deploy.runtime.scheduler import FixedStepScheduler, LoopScheduler, RealtimeScheduler - -def __getattr__(name: str): - if name == "ControlLoop": - from motrix_deploy.runtime.loop import ControlLoop - - return ControlLoop - raise AttributeError(name) - - __all__ = [ - "ControlLoop", + "ControlSession", + "DeploymentRuntime", "FixedStepScheduler", + "HardwareRuntime", "LoopScheduler", "PolicyContext", "RealtimeScheduler", "RolloutResult", + "SensorBindings", + "SimulationRuntimeConfig", + "SimulationRuntime", + "RuntimeCreateContext", + "create_simulation_runtime", + "create_hardware_runtime", + "registered_runtimes", ] diff --git a/motrix_deploy/src/motrix_deploy/runtime/base.py b/motrix_deploy/src/motrix_deploy/runtime/base.py new file mode 100644 index 00000000..3fafaef4 --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/runtime/base.py @@ -0,0 +1,83 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Deployment runtime lifecycle contracts.""" + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +from motrix_deploy.errors import ValidationError + +if TYPE_CHECKING: + from motrix_deploy.robot.interface import RobotInterface + from motrix_deploy.runtime.control import ControlSession + from motrix_deploy.runtime.result import RolloutResult + + +class DeploymentRuntime(ABC): + """Own runtime resources while a control session owns the robot backend.""" + + def __init__(self) -> None: + self._opened = False + self._control: ControlSession | None = None + + @property + def control(self) -> "ControlSession | None": + """Control session bound to this runtime, if any.""" + return self._control + + def bind_control_session(self, control: "ControlSession") -> None: + """Bind one control session, rejecting conflicting robot ownership.""" + if control.robot is not self.robot: + raise ValidationError("runtime.control.robot", self.robot, control.robot) + if self._control is not None and self._control is not control: + raise ValidationError("runtime.control", "the already bound control session", control) + self._control = control + + def _resolve_control(self, control: "ControlSession | None") -> "ControlSession": + if control is not None and self.control is not None and control is not self.control: + raise ValidationError("runtime.control", "the already bound control session", control) + resolved = control if control is not None else self.control + if resolved is None: + raise ValidationError("runtime.control", "a supplied or bound control session", None) + if resolved.robot is not self.robot: + raise ValidationError("runtime.robot", "the control session robot", resolved.robot) + return resolved + + @property + @abstractmethod + def robot(self) -> "RobotInterface": + """Robot interface associated with this runtime.""" + raise NotImplementedError + + @property + def opened(self) -> bool: + """Whether this runtime lifecycle is open.""" + return self._opened + + def open(self) -> None: + """Open runtime resources; safe to call repeatedly.""" + self._opened = True + + def close(self) -> None: + """Close runtime resources; safe to call repeatedly.""" + self._opened = False + + def __enter__(self) -> "DeploymentRuntime": + self.open() + return self + + def __exit__(self, exc_type, exc_value, traceback) -> None: + del exc_type, exc_value, traceback + self.close() + + @abstractmethod + def run(self, control: "ControlSession | None" = None, *, steps: int | None = None) -> "RolloutResult": + """Run a control session and return its structured result.""" + + +class SimulationRuntime(DeploymentRuntime, ABC): + """Base for runtimes that own simulation advancement and robot creation.""" + + +__all__ = ["DeploymentRuntime", "SimulationRuntime"] diff --git a/motrix_deploy/src/motrix_deploy/runtime/config.py b/motrix_deploy/src/motrix_deploy/runtime/config.py new file mode 100644 index 00000000..7ec3f0f9 --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/runtime/config.py @@ -0,0 +1,76 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Simulator-independent deployment scene and state-source configuration.""" + +from collections.abc import Mapping +from dataclasses import dataclass, field +from typing import Any + +from motrix_deploy.errors import ValidationError +from motrix_env_core.config.scene import SceneCfg +from motrix_env_core.config.sim import SimCfg + + +@dataclass(frozen=True) +class SensorBindings: + """Semantic robot state fields bound to exact model-local sensor names.""" + + base_angular_velocity: str | None = None + base_linear_acceleration: str | None = None + base_linear_velocity: str | None = None + + def __post_init__(self) -> None: + for field_name in ( + "base_angular_velocity", + "base_linear_acceleration", + "base_linear_velocity", + ): + name = getattr(self, field_name) + if name is None: + continue + if not isinstance(name, str): + raise TypeError(f"{field_name} must be a string or None") + if not name.strip(): + raise ValueError(f"{field_name} must be a non-empty sensor name") + + +@dataclass(frozen=True) +class SimulationRuntimeConfig: + scene: SceneCfg + physics: SimCfg = field(default_factory=lambda: SimCfg(dt=0.002, solver_iterations=100)) + render: bool = False + realtime: bool | None = None + # Deployment recipe: semantic RobotState fields mapped to model-local sensor names. + sensor_bindings: SensorBindings = field(default_factory=SensorBindings) + + def __post_init__(self) -> None: + self.physics.validate() + + @classmethod + def from_mapping( + cls, value: Mapping[str, Any], *, render: bool = False, realtime: bool | None = None + ) -> "SimulationRuntimeConfig": + """Decode an assembled simulation recipe at the application boundary.""" + values = dict(value) + values.pop("name", None) + values.setdefault("sensor_bindings", {}) + expected = {"scene", "physics", "sensor_bindings"} + if set(values) != expected: + raise ValidationError( + "runtime", + f"fields {sorted(expected)}", + f"missing={sorted(expected - set(values))}, unknown={sorted(set(values) - expected)}", + ) + if not isinstance(values["scene"], SceneCfg): + raise ValidationError("runtime.scene", "a complete SceneCfg", values["scene"]) + missing = {"dt", "solver_iterations"} - set(values["physics"]) + if missing: + raise ValidationError("runtime.physics", "dt and solver_iterations", f"missing={sorted(missing)}") + return cls( + scene=values["scene"], + physics=SimCfg(**values["physics"]), + sensor_bindings=SensorBindings(**values["sensor_bindings"]), + render=render, + realtime=realtime, + ) diff --git a/motrix_deploy/src/motrix_deploy/runtime/control.py b/motrix_deploy/src/motrix_deploy/runtime/control.py new file mode 100644 index 00000000..0b113e92 --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/runtime/control.py @@ -0,0 +1,294 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Backend-neutral control executor, independent of scheduling and physics.""" + +import hashlib +import time +from typing import TYPE_CHECKING, Generic, TypeVar + +from motrix_deploy.contracts import JointServoCommand, RobotCommand, RobotState +from motrix_deploy.errors import EmergencyStopError, LieDownRequestedError, ValidationError +from motrix_deploy.policy import PolicyRuntime +from motrix_deploy.robot.interface import RobotInterface +from motrix_deploy.runtime.context import PolicyContext +from motrix_deploy.runtime.result import LatencyRecorder, RolloutResult +from motrix_env_core.input import CommandBinding + +if TYPE_CHECKING: + from motrix_deploy.task import DeployTask + +CommandT = TypeVar("CommandT") + + +class _LoopFailure(RuntimeError): + def __init__(self, reason: str, message: str) -> None: + self.reason = reason + super().__init__(message) + + +class ControlSession(Generic[CommandT]): + """Own one control lifecycle; callers own scheduling and simulation advancement. + + A completed tick means a healthy command write, not a completed physics step. + Errors are recorded by start/tick/fail; only stop performs backend cleanup. + Sessions are single-use, and repeated start/stop calls are idempotent. + Each command selects its mode, which must be supported by the robot backend. + """ + + def __init__( + self, + *, + robot: RobotInterface, + task: "DeployTask[CommandT]", + policy: PolicyRuntime, + command_binding: CommandBinding[CommandT], + period_s: float, + state_timeout_s: float, + ) -> None: + if period_s <= 0 or state_timeout_s <= 0: + raise ValidationError("control.timing", "positive period and timeout", (period_s, state_timeout_s)) + self.robot = robot + self.task = task + self.policy = policy + self.command_binding = command_binding + self.period_s = period_s + self.state_timeout_s = state_timeout_s + self._started = False + self._stopped = False + self._completed_steps = 0 + self._initial_state: RobotState | None = None + self._previous_sample_time: int | None = None + self._recorder = LatencyRecorder() + self._trace = hashlib.sha256() + self._start_ns: int | None = None + self._stop_ns: int | None = None + self._exit_reason = "completed" + self._error_message: str | None = None + + @property + def completed_steps(self) -> int: + return self._completed_steps + + @property + def active(self) -> bool: + return self._started and not self._stopped and self._exit_reason == "completed" + + def start(self) -> bool: + """Open, validate and optionally enable the backend without scheduling.""" + if self._started or self._stopped or self._exit_reason != "completed": + return self.active + self._start_ns = time.monotonic_ns() + try: + self.robot.open() + self._validate_capabilities() + initial_state = self._read_and_validate_state(None) + self._previous_sample_time = initial_state.sample_time_ns + self._check_task_termination(initial_state) + self.policy.reset() + if self.robot.capabilities.requires_enable: + initial_context = self._read_context(0, 0.0) + self.task.reset(initial_state, initial_context) + try: + self.robot.enable() + except (EmergencyStopError, LieDownRequestedError, ValidationError): + raise + except Exception as error: + raise _LoopFailure("backend_enable_error", str(error)) from error + initial_state = self._read_and_validate_state(self._previous_sample_time) + self._previous_sample_time = initial_state.sample_time_ns + self._check_task_termination(initial_state) + self.task.reset(initial_state, initial_context) + self.policy.reset() + self._initial_state = initial_state + self._started = True + except (Exception, KeyboardInterrupt) as error: + self.fail(error) + return self.active + + def tick(self, *, elapsed_time_s: float) -> bool: + """Execute one control tick using caller-supplied relative elapsed time.""" + if not self.active: + return False + step = self.completed_steps + loop_start = time.monotonic_ns() + try: + phase_start = time.monotonic_ns() + context = self._read_context(step, elapsed_time_s) + self._recorder.add("input", time.monotonic_ns() - phase_start) + if step == 0: + self.task.reset(self._initial_state, context) + phase_start = time.monotonic_ns() + state = self._read_and_validate_state(self._previous_sample_time) + self._recorder.add("read", time.monotonic_ns() - phase_start) + self._previous_sample_time = state.sample_time_ns + self._check_task_termination(state) + + phase_start = time.monotonic_ns() + try: + observation = self.task.build_observation(state, context) + except Exception as error: + raise _LoopFailure("invalid_observation", str(error)) from error + self._recorder.add("observation", time.monotonic_ns() - phase_start) + + phase_start = time.monotonic_ns() + try: + raw_action = self.policy.infer(observation) + except Exception as error: + raise _LoopFailure("policy_error", str(error)) from error + self._recorder.add("inference", time.monotonic_ns() - phase_start) + + phase_start = time.monotonic_ns() + try: + command = self.task.process_action(raw_action) + self._validate_command_mode(command) + except Exception as error: + raise _LoopFailure("invalid_action", str(error)) from error + self._recorder.add("action", time.monotonic_ns() - phase_start) + + phase_start = time.monotonic_ns() + try: + self.robot.write_command(command) + except (EmergencyStopError, LieDownRequestedError): + raise + except Exception as error: + raise _LoopFailure("backend_write_error", str(error)) from error + self._recorder.add("write", time.monotonic_ns() - phase_start) + health = self.robot.health() + if not health.healthy: + raise _LoopFailure("backend_unhealthy", health.reason) + self._trace.update(step.to_bytes(8, byteorder="little", signed=False)) + for value in ( + state.joint_position, + state.joint_velocity, + state.base_orientation_xyzw, + observation, + raw_action, + command.joint_position if isinstance(command, JointServoCommand) else command.torque, + ): + self._trace.update(value.tobytes(order="C")) + self._completed_steps += 1 + self._recorder.add("loop", time.monotonic_ns() - loop_start) + except (Exception, KeyboardInterrupt) as error: + self.fail(error) + return False + return True + + def fail(self, error: BaseException) -> None: + """Record the first failure, including errors from an external driver.""" + if not isinstance(error, (Exception, KeyboardInterrupt)): + raise error + if self._exit_reason != "completed": + return + if isinstance(error, KeyboardInterrupt): + self._exit_reason = "interrupted" + self._error_message = "rollout interrupted by user" + return + if isinstance(error, EmergencyStopError): + self._exit_reason = "emergency_stop" + elif isinstance(error, LieDownRequestedError): + self._exit_reason = "lie_down" + elif isinstance(error, _LoopFailure): + self._exit_reason = error.reason + elif isinstance(error, ValidationError): + self._exit_reason = "validation_error" + else: + self._exit_reason = "backend_error" + self._error_message = str(error) + + def stop(self) -> None: + """Stop and close once, preserving any earlier failure over cleanup errors.""" + if self._stopped: + return + self._stopped = True + cleanup_error: BaseException | None = None + for cleanup in (self.robot.stop, self.robot.close): + try: + cleanup() + except (Exception, KeyboardInterrupt) as error: + cleanup_error = cleanup_error or error + if cleanup_error is not None and self._exit_reason == "completed": + self._exit_reason = "cleanup_error" + self._error_message = str(cleanup_error) + self._stop_ns = time.monotonic_ns() + + def result(self, *, steps: int | None, overrun_count: int = 0) -> RolloutResult: + """Summarize completed control ticks; finite success requires the target count.""" + end_ns = self._stop_ns if self._stop_ns is not None else time.monotonic_ns() + wall_time_s = (end_ns - self._start_ns) / 1e9 if self._start_ns is not None else 0.0 + simulation_time_s = self.completed_steps * self.period_s + return RolloutResult( + success=self._exit_reason == "completed" and steps is not None and self.completed_steps == steps, + exit_reason=self._exit_reason, + completed_steps=self.completed_steps, + simulation_time_s=simulation_time_s, + wall_time_s=wall_time_s, + real_time_factor=simulation_time_s / wall_time_s if wall_time_s > 0 else 0.0, + overrun_count=overrun_count, + trace_sha256=self._trace.hexdigest(), + error=self._error_message, + latency=self._recorder.summarize(), + ) + + def _check_task_termination(self, state: RobotState) -> None: + try: + reason = self.task.check_termination(state) + except Exception as error: + raise _LoopFailure("task_termination_error", str(error)) from error + if reason is not None: + raise _LoopFailure("task_terminated", reason) + + def _read_context(self, step: int, elapsed_time_s: float) -> PolicyContext[CommandT]: + try: + command = self.command_binding.read_command(batch_size=1) + self.task.validate_command(command) + return PolicyContext(step=step, elapsed_time_s=elapsed_time_s, command=command) + except Exception as error: + raise _LoopFailure("input_error", str(error)) from error + + def _validate_command_mode(self, command: RobotCommand) -> None: + modes = self.robot.capabilities.control_modes + if command.mode not in modes: + raise ValidationError("backend.control_modes", command.mode.value, modes) + + def _validate_capabilities(self) -> None: + capabilities = self.robot.capabilities + if capabilities.max_command_rate_hz is not None: + requested_rate = 1.0 / self.period_s + if requested_rate > capabilities.max_command_rate_hz + 1e-9: + raise ValidationError( + "control.command_rate_hz", f"<= {capabilities.max_command_rate_hz}", requested_rate + ) + + def _read_and_validate_state(self, previous_sample_time: int | None) -> RobotState: + try: + state = self.robot.read_state(self.state_timeout_s) + except TimeoutError as error: + raise _LoopFailure("state_timeout", str(error)) from error + except (EmergencyStopError, LieDownRequestedError): + raise + except Exception as error: + raise _LoopFailure("backend_read_error", str(error)) from error + try: + self._validate_state(state, previous_sample_time) + except _LoopFailure: + raise + except Exception as error: + raise _LoopFailure("invalid_state", str(error)) from error + health = self.robot.health() + if not health.healthy: + raise _LoopFailure("backend_unhealthy", health.reason) + return state + + def _validate_state(self, state: RobotState, previous_sample_time: int | None) -> None: + expected = (self.robot.spec.joint_count,) + if state.joint_position.shape != expected or state.joint_velocity.shape != expected: + raise _LoopFailure("invalid_state", f"joint shape must be {expected}") + if previous_sample_time is not None and state.sample_time_ns < previous_sample_time: + raise _LoopFailure( + "invalid_state", + f"state sample timestamp moved backwards: {state.sample_time_ns} < {previous_sample_time}", + ) + age_s = (time.monotonic_ns() - state.receive_time_ns) / 1e9 + if age_s > self.state_timeout_s: + raise _LoopFailure("state_timeout", f"state age {age_s:.6f}s exceeds {self.state_timeout_s:.6f}s") diff --git a/motrix_deploy/src/motrix_deploy/runtime/factory.py b/motrix_deploy/src/motrix_deploy/runtime/factory.py new file mode 100644 index 00000000..b973593d --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/runtime/factory.py @@ -0,0 +1,82 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Discover deployment runtimes through installed plugins.""" + +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from importlib import metadata +from typing import Any + +from motrix_deploy.artifact.schema import ControlSpec +from motrix_deploy.contracts import RobotSpec +from motrix_deploy.runtime.base import DeploymentRuntime, SimulationRuntime +from motrix_deploy.runtime.config import SimulationRuntimeConfig +from motrix_deploy.runtime.hardware import HardwareRuntime + + +@dataclass(frozen=True) +class RuntimeCreateContext: + """Artifact-owned robot and control contracts for hardware creation.""" + + robot: RobotSpec + control: ControlSpec + + +RUNTIME_ENTRY_POINT_GROUP = "motrix_deploy.backends" + + +def _runtime_entry_points() -> tuple[metadata.EntryPoint, ...]: + return tuple(metadata.entry_points(group=RUNTIME_ENTRY_POINT_GROUP)) + + +def registered_runtimes() -> tuple[str, ...]: + return tuple(sorted(entry.name for entry in _runtime_entry_points())) + + +def create_simulation_runtime( + name: str, + config: SimulationRuntimeConfig, +) -> SimulationRuntime: + """Create a simulation host with its robot ready for control-session assembly.""" + runtime = _validate_runtime(name, _load_factory(name)(config)) + if not isinstance(runtime, SimulationRuntime): + runtime.close() + raise TypeError(f"Deployment runtime plugin {name!r} does not provide a SimulationRuntime") + return runtime + + +def create_hardware_runtime( + name: str, + config: Mapping[str, Any], + context: RuntimeCreateContext, +) -> HardwareRuntime: + """Create a hardware host, preserving plugin operator safety checks.""" + runtime = _validate_runtime(name, _load_factory(name)(config, context)) + if not isinstance(runtime, HardwareRuntime): + runtime.close() + raise TypeError(f"Deployment runtime plugin {name!r} does not provide a HardwareRuntime") + return runtime + + +def _load_factory(name: str) -> Callable[..., DeploymentRuntime]: + """Resolve the selected plugin; simulation and hardware have distinct contracts.""" + matches = [entry for entry in _runtime_entry_points() if entry.name == name] + if not matches: + available = ", ".join(registered_runtimes()) or "none" + raise ValueError(f"Unsupported deployment runtime {name!r}; installed runtimes: {available}") + if len(matches) != 1: + values = sorted(entry.value for entry in matches) + raise ValueError(f"Multiple deployment runtime plugins are registered as {name!r}: {values}") + factory = matches[0].load() + if not callable(factory): + raise TypeError(f"Deployment runtime plugin {name!r} must load a callable factory") + return factory + + +def _validate_runtime(name: str, runtime: DeploymentRuntime) -> DeploymentRuntime: + if not isinstance(runtime, DeploymentRuntime): + raise TypeError( + f"Deployment runtime plugin {name!r} returned {type(runtime).__name__}, expected DeploymentRuntime" + ) + return runtime diff --git a/motrix_deploy/src/motrix_deploy/runtime/hardware.py b/motrix_deploy/src/motrix_deploy/runtime/hardware.py new file mode 100644 index 00000000..ff2d0a82 --- /dev/null +++ b/motrix_deploy/src/motrix_deploy/runtime/hardware.py @@ -0,0 +1,68 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Standalone hardware runtime driver.""" + +from motrix_deploy.errors import ValidationError +from motrix_deploy.robot.interface import RobotInterface +from motrix_deploy.runtime.base import DeploymentRuntime +from motrix_deploy.runtime.control import ControlSession +from motrix_deploy.runtime.result import RolloutResult +from motrix_deploy.runtime.scheduler import FixedStepScheduler, LoopScheduler, RealtimeScheduler + + +class HardwareRuntime(DeploymentRuntime): + """Drive a control session without owning or advancing robot physics. + + The control session owns robot open/stop/close. This runtime only owns the + scheduler and ensures an active session is stopped when its lifecycle closes. + """ + + def __init__( + self, + robot: RobotInterface, + *, + scheduler: LoopScheduler | None = None, + realtime: bool = True, + ) -> None: + super().__init__() + self._robot = robot + self.scheduler = scheduler + self.realtime = realtime + + @property + def robot(self) -> RobotInterface: + return self._robot + + def close(self) -> None: + control = self.control + if control is not None and control.active: + control.stop() + super().close() + + def run(self, control: ControlSession | None = None, *, steps: int | None = None) -> RolloutResult: + control = self._resolve_control(control) + if steps is not None and (not isinstance(steps, int) or isinstance(steps, bool) or steps <= 0): + raise ValidationError("steps", "a positive integer", steps) + scheduler = self.scheduler + if scheduler is None: + scheduler = RealtimeScheduler(control.period_s) if self.realtime else FixedStepScheduler(control.period_s) + elif scheduler.period_s != control.period_s: + raise ValidationError("runtime.scheduler.period_s", control.period_s, scheduler.period_s) + self.open() + try: + if control.start(): + scheduler.reset() + while control.active and (steps is None or control.completed_steps < steps): + step = control.completed_steps + scheduler.wait(step) + control.tick(elapsed_time_s=scheduler.elapsed_time_s(step)) + except (Exception, KeyboardInterrupt) as error: + control.fail(error) + finally: + control.stop() + self.close() + return control.result(steps=steps, overrun_count=scheduler.overrun_count) + + +__all__ = ["HardwareRuntime"] diff --git a/motrix_deploy/src/motrix_deploy/runtime/loop.py b/motrix_deploy/src/motrix_deploy/runtime/loop.py deleted file mode 100644 index fcbbf0d8..00000000 --- a/motrix_deploy/src/motrix_deploy/runtime/loop.py +++ /dev/null @@ -1,271 +0,0 @@ -# Copyright Motphys Technology Co., Ltd. 2025, 2026 -# SPDX-License-Identifier: Apache-2.0 - -"""Backend-neutral policy control loop.""" - -import hashlib -import time -from typing import Generic, TypeVar - -import numpy as np - -from motrix_deploy.backend import RobotInterface -from motrix_deploy.contracts import RobotSpec, RobotState -from motrix_deploy.errors import EmergencyStopError, LieDownRequestedError, ValidationError -from motrix_deploy.policy import PolicyRuntime -from motrix_deploy.runtime.context import PolicyContext -from motrix_deploy.runtime.result import LatencyRecorder, RolloutResult -from motrix_deploy.runtime.scheduler import LoopScheduler -from motrix_deploy.task import DeployTask -from motrix_env_core.input import CommandBinding - -CommandT = TypeVar("CommandT") - - -class _LoopFailure(RuntimeError): - def __init__(self, reason: str, message: str) -> None: - self.reason = reason - super().__init__(message) - - -class ControlLoop(Generic[CommandT]): - """Execute state -> observation -> policy -> action -> command.""" - - def __init__( - self, - *, - robot: RobotSpec, - backend: RobotInterface, - task: DeployTask[CommandT], - policy: PolicyRuntime, - command_binding: CommandBinding[CommandT], - scheduler: LoopScheduler, - state_timeout_s: float, - ) -> None: - self.robot = robot - self.backend = backend - self.task = task - self.policy = policy - self.command_binding = command_binding - self.scheduler = scheduler - self.state_timeout_s = state_timeout_s - if scheduler.period_s <= 0 or state_timeout_s <= 0: - raise ValidationError( - "control.timing", - "positive period and timeout", - (scheduler.period_s, state_timeout_s), - ) - if policy.input_spec.shape != (1, task.observation_size): - raise ValidationError("policy.input.shape", str((1, task.observation_size)), policy.input_spec.shape) - if policy.output_spec.shape != (1, task.action_size): - raise ValidationError("policy.output.shape", str((1, task.action_size)), policy.output_spec.shape) - - def run(self, *, steps: int | None = None) -> RolloutResult: - if steps is not None and (not isinstance(steps, int) or isinstance(steps, bool) or steps <= 0): - raise ValidationError("steps", "a positive integer", steps) - completed = 0 - recorder = LatencyRecorder() - trace = hashlib.sha256() - start_ns = time.monotonic_ns() - exit_reason = "completed" - error_message: str | None = None - previous_sample_time: int | None = None - cleanup_error: Exception | None = None - try: - self.backend.open(self.robot) - self._validate_capabilities() - initial_state = self._read_and_validate_state(None) - previous_sample_time = initial_state.sample_time_ns - self.policy.reset() - if self.backend.capabilities.requires_enable: - initial_context = self._read_context(0) - self.task.reset(initial_state, initial_context) - try: - initial_command = self.task.process_action(np.zeros(self.task.action_size, dtype=np.float32)) - self.backend.enable(initial_command) - except (EmergencyStopError, LieDownRequestedError): - raise - except Exception as error: - raise _LoopFailure("backend_enable_error", str(error)) from error - initial_state = self._read_and_validate_state(previous_sample_time) - previous_sample_time = initial_state.sample_time_ns - self.task.reset(initial_state, initial_context) - self.policy.reset() - self.scheduler.reset() - while steps is None or completed < steps: - step = completed - loop_start = time.monotonic_ns() - self.scheduler.wait(step) - phase_start = time.monotonic_ns() - context = self._read_context(step) - recorder.add("input", time.monotonic_ns() - phase_start) - if step == 0: - self.task.reset(initial_state, context) - phase_start = time.monotonic_ns() - state = self._read_and_validate_state(previous_sample_time) - recorder.add("read", time.monotonic_ns() - phase_start) - previous_sample_time = state.sample_time_ns - - phase_start = time.monotonic_ns() - try: - observation = self.task.build_observation(state, context) - except Exception as error: - raise _LoopFailure("invalid_observation", str(error)) from error - recorder.add("observation", time.monotonic_ns() - phase_start) - - phase_start = time.monotonic_ns() - try: - raw_action = self.policy.infer(observation) - except Exception as error: - raise _LoopFailure("policy_error", str(error)) from error - recorder.add("inference", time.monotonic_ns() - phase_start) - - phase_start = time.monotonic_ns() - try: - command = self.task.process_action(raw_action) - except Exception as error: - raise _LoopFailure("invalid_action", str(error)) from error - recorder.add("action", time.monotonic_ns() - phase_start) - - phase_start = time.monotonic_ns() - try: - self.backend.write_command(command) - except (EmergencyStopError, LieDownRequestedError): - raise - except Exception as error: - raise _LoopFailure("backend_write_error", str(error)) from error - recorder.add("write", time.monotonic_ns() - phase_start) - health = self.backend.health() - if not health.healthy: - raise _LoopFailure("backend_unhealthy", health.reason) - trace.update(step.to_bytes(8, byteorder="little", signed=False)) - for value in ( - state.joint_position, - state.joint_velocity, - state.base_orientation_xyzw, - observation, - raw_action, - command.joint_position, - ): - trace.update(value.tobytes(order="C")) - completed += 1 - recorder.add("loop", time.monotonic_ns() - loop_start) - except KeyboardInterrupt: - exit_reason = "interrupted" - error_message = "rollout interrupted by user" - except EmergencyStopError as error: - exit_reason = "emergency_stop" - error_message = str(error) - except LieDownRequestedError as error: - exit_reason = "lie_down" - error_message = str(error) - except _LoopFailure as error: - exit_reason = error.reason - error_message = str(error) - except ValidationError as error: - exit_reason = "validation_error" - error_message = str(error) - except Exception as error: - exit_reason = "backend_error" - error_message = str(error) - finally: - try: - self.backend.stop() - except Exception as error: - cleanup_error = cleanup_error or error - try: - self.backend.close() - except Exception as error: - cleanup_error = cleanup_error or error - if cleanup_error is not None and exit_reason == "completed": - exit_reason = "cleanup_error" - error_message = str(cleanup_error) - wall_time_s = (time.monotonic_ns() - start_ns) / 1e9 - simulation_time_s = completed * self.scheduler.period_s - real_time_factor = simulation_time_s / wall_time_s if wall_time_s > 0 else 0.0 - return RolloutResult( - success=exit_reason == "completed" and steps is not None and completed == steps, - exit_reason=exit_reason, - completed_steps=completed, - simulation_time_s=simulation_time_s, - wall_time_s=wall_time_s, - real_time_factor=real_time_factor, - overrun_count=self.scheduler.overrun_count, - trace_sha256=trace.hexdigest(), - error=error_message, - latency=recorder.summarize(), - ) - - def _read_context(self, step: int) -> PolicyContext[CommandT]: - try: - command = self.command_binding.read_command(batch_size=1) - self.task.validate_command(command) - return PolicyContext( - step=step, - elapsed_time_s=self.scheduler.elapsed_time_s(step), - command=command, - ) - except KeyboardInterrupt: - raise - except Exception as error: - raise _LoopFailure("input_error", str(error)) from error - - def _validate_capabilities(self) -> None: - capabilities = self.backend.capabilities - if "joint_pd" not in capabilities.control_modes: - raise ValidationError("backend.control_modes", "joint_pd", capabilities.control_modes) - required_fields = { - "joint_position", - "joint_velocity", - "base_orientation_xyzw", - "base_angular_velocity", - "base_linear_acceleration", - } - missing = required_fields - capabilities.state_fields - if missing: - raise ValidationError( - "backend.state_fields", - f"contains {sorted(required_fields)}", - f"missing {sorted(missing)}", - ) - if capabilities.max_command_rate_hz is not None: - requested_rate = 1.0 / self.scheduler.period_s - if requested_rate > capabilities.max_command_rate_hz + 1e-9: - raise ValidationError( - "control.command_rate_hz", - f"<= {capabilities.max_command_rate_hz}", - requested_rate, - ) - - def _read_and_validate_state(self, previous_sample_time: int | None) -> RobotState: - try: - state = self.backend.read_state(self.state_timeout_s) - except TimeoutError as error: - raise _LoopFailure("state_timeout", str(error)) from error - except (EmergencyStopError, LieDownRequestedError): - raise - except Exception as error: - raise _LoopFailure("backend_read_error", str(error)) from error - try: - self._validate_state(state, previous_sample_time) - except _LoopFailure: - raise - except Exception as error: - raise _LoopFailure("invalid_state", str(error)) from error - health = self.backend.health() - if not health.healthy: - raise _LoopFailure("backend_unhealthy", health.reason) - return state - - def _validate_state(self, state: RobotState, previous_sample_time: int | None) -> None: - expected = (self.robot.joint_count,) - if state.joint_position.shape != expected or state.joint_velocity.shape != expected: - raise _LoopFailure("invalid_state", f"joint shape must be {expected}") - if previous_sample_time is not None and state.sample_time_ns < previous_sample_time: - raise _LoopFailure( - "invalid_state", - f"state sample timestamp moved backwards: {state.sample_time_ns} < {previous_sample_time}", - ) - age_s = (time.monotonic_ns() - state.receive_time_ns) / 1e9 - if age_s > self.state_timeout_s: - raise _LoopFailure("state_timeout", f"state age {age_s:.6f}s exceeds {self.state_timeout_s:.6f}s") diff --git a/motrix_deploy/src/motrix_deploy/task.py b/motrix_deploy/src/motrix_deploy/task.py index 932e5cab..a6576402 100644 --- a/motrix_deploy/src/motrix_deploy/task.py +++ b/motrix_deploy/src/motrix_deploy/task.py @@ -3,12 +3,11 @@ """Runtime contract implemented by concrete deployment-task packages.""" -import re from abc import ABC, abstractmethod -from collections.abc import Callable -from typing import Any, Generic, TypeVar +from importlib import metadata +from typing import Any, ClassVar, Generic, TypeVar -from motrix_deploy.artifact import TaskSpec +from motrix_deploy.artifact.schema import TaskSpec from motrix_deploy.contracts import FloatArray, RobotCommand, RobotSpec, RobotState from motrix_deploy.runtime.context import PolicyContext @@ -16,17 +15,19 @@ class DeployTask(ABC, Generic[CommandT]): - """Own task-specific observation, action, and command semantics.""" + """Own task-specific observation, action, command, and termination semantics. - @property - @abstractmethod - def observation_size(self) -> int: - """Return the flat policy observation size.""" + ControlSession checks backend state and calls validate_command before passing + inputs to reset and build_observation. Tasks check optional state fields at + their usage sites when a feature needs them. Backend capabilities describe + which fields are privileged ground truth. + """ - @property - @abstractmethod - def action_size(self) -> int: - """Return the flat policy action size.""" + spec_type: ClassVar[type[TaskSpec]] + + def check_termination(self, state: RobotState) -> str | None: + """Return a task termination reason, or None to continue, without side effects.""" + return None @abstractmethod def reset(self, state: RobotState, context: PolicyContext[CommandT]) -> None: @@ -38,46 +39,39 @@ def build_observation(self, state: RobotState, context: PolicyContext[CommandT]) @abstractmethod def process_action(self, action: FloatArray) -> RobotCommand: - """Convert one raw policy action into a canonical robot command.""" + """Convert a policy action into a servo or torque command supported by the backend.""" @abstractmethod def validate_command(self, command: CommandT) -> None: """Validate one external high-level command before using it.""" -TaskFactory = Callable[[TaskSpec, RobotSpec], DeployTask[Any]] +TASK_ENTRY_POINT_GROUP = "motrix_deploy.tasks" -_TASK_FACTORIES: dict[str, TaskFactory] = {} -_TASK_NAME_PATTERN = re.compile(r"[^/\s]+/v[1-9][0-9]*") +def available_tasks() -> tuple[str, ...]: + """List installed task plugins without importing their implementations.""" + return tuple(sorted({entry.name for entry in metadata.entry_points(group=TASK_ENTRY_POINT_GROUP)})) -def register_task(name: str) -> Callable[[TaskFactory], TaskFactory]: - """Register one concrete task factory when its implementation module is imported.""" - if not isinstance(name, str) or _TASK_NAME_PATTERN.fullmatch(name) is None: - raise ValueError(f"task name must be a versioned identifier such as go2_walk/v1, got {name!r}") - def register(factory: TaskFactory) -> TaskFactory: - if name in _TASK_FACTORIES: - raise ValueError(f"Deployment task {name} is already registered") - _TASK_FACTORIES[name] = factory - return factory - - return register +def load_task_type(name: str) -> type[DeployTask[Any]]: + """Load only the selected installed task class, shared by codec and runtime.""" + entries = tuple(metadata.entry_points(group=TASK_ENTRY_POINT_GROUP)) + matches = [entry for entry in entries if entry.name == name] + if not matches: + supported = ", ".join(sorted({entry.name for entry in entries})) or "none" + raise ValueError(f"Unsupported deployment task {name}; available tasks: {supported}") + if len(matches) != 1: + raise ValueError(f"Multiple deployment task plugins provide {name}") + task_type = matches[0].load() + if not isinstance(task_type, type) or not issubclass(task_type, DeployTask): + raise TypeError(f"Deployment task plugin {name} must provide a DeployTask class") + return task_type def create_task(spec: TaskSpec, robot: RobotSpec) -> DeployTask[Any]: - """Create the task implementation selected by an artifact.""" - try: - factory = _TASK_FACTORIES[spec.name] - except KeyError as error: - supported = ", ".join(sorted(_TASK_FACTORIES)) or "none" - raise ValueError(f"Unsupported deployment task {spec.name}; supported tasks: {supported}") from error - return factory(spec, robot) - - -def registered_tasks() -> tuple[str, ...]: - """Return versioned task identifiers registered in the current process.""" - return tuple(sorted(_TASK_FACTORIES)) + """Instantiate the installed task class selected by an artifact.""" + return load_task_type(spec.name)(spec, robot) -__all__ = ["DeployTask", "create_task", "register_task", "registered_tasks"] +__all__ = ["DeployTask", "create_task", "available_tasks"] diff --git a/motrix_deploy/tests/conftest.py b/motrix_deploy/tests/conftest.py index 538b9db8..e430f66e 100644 --- a/motrix_deploy/tests/conftest.py +++ b/motrix_deploy/tests/conftest.py @@ -4,16 +4,17 @@ """Shared artifact fixtures.""" from collections.abc import Callable +from types import SimpleNamespace import numpy as np import pytest +from task_specs import TestDeployTask, TestTaskSpec from motrix_deploy.artifact import ( ControlSpec, DeploymentManifest, PolicySpec, SourceSpec, - TaskSpec, sha256_bytes, ) from motrix_deploy.contracts import RobotSpec, TensorSpec @@ -22,7 +23,18 @@ @pytest.fixture -def manifest_factory() -> Callable[[], DeploymentManifest]: +def manifest_factory(monkeypatch) -> Callable[[], DeploymentManifest]: + from motrix_deploy import task + + original = task.metadata.entry_points + + def entry_points(*, group): + if group == task.TASK_ENTRY_POINT_GROUP: + return [SimpleNamespace(name="test/v1", load=lambda: TestDeployTask)] + return original(group=group) + + monkeypatch.setattr(task.metadata, "entry_points", entry_points) + def create() -> DeploymentManifest: return DeploymentManifest( schema_version="motrix-deploy/v1", @@ -42,12 +54,7 @@ def create() -> DeploymentManifest: position_upper=np.array([1.0, 1.0], dtype=np.float32), torque_limit=np.array([3.0, 3.0], dtype=np.float32), ), - task=TaskSpec( - name="test/v1", - observation_size=4, - action_size=2, - config={}, - ), + task=TestTaskSpec(), control=ControlSpec(period_s=0.02, state_timeout_s=0.1), ) diff --git a/motrix_deploy/src/motrix_deploy/backend/fake.py b/motrix_deploy/tests/fake_robot.py similarity index 71% rename from motrix_deploy/src/motrix_deploy/backend/fake.py rename to motrix_deploy/tests/fake_robot.py index 6bf30057..d9ea05b0 100644 --- a/motrix_deploy/src/motrix_deploy/backend/fake.py +++ b/motrix_deploy/tests/fake_robot.py @@ -7,17 +7,25 @@ import numpy as np -from motrix_deploy.backend import RobotInterface -from motrix_deploy.contracts import HealthStatus, RobotCapabilities, RobotCommand, RobotSpec, RobotState +from motrix_deploy.contracts import ( + HealthStatus, + JointControlMode, + JointServoCommand, + RobotCapabilities, + RobotCommand, + RobotSpec, + RobotState, +) from motrix_deploy.errors import ValidationError +from motrix_deploy.robot.interface import RobotInterface class FakeRobotInterface(RobotInterface): - """Echo position commands into state without a physics dependency.""" + """Echo servo positions; record torque commands without inventing physics.""" def __init__( self, - joint_names: tuple[str, ...], + spec: RobotSpec, *, sample_period_s: float = 0.02, response: float = 1.0, @@ -26,7 +34,7 @@ def __init__( unhealthy_at: int | None = None, ) -> None: self._capabilities = RobotCapabilities( - control_modes=("joint_pd",), + control_modes=(JointControlMode.SERVO, JointControlMode.TORQUE), state_fields=frozenset( { "joint_position", @@ -38,16 +46,15 @@ def __init__( ), max_command_rate_hz=1.0 / sample_period_s, ) - self._joint_names = joint_names + self._spec = spec self._period_s = sample_period_s self._period_ns = round(sample_period_s * 1e9) self._response = response self._fail_read_at = fail_read_at self._fail_write_at = fail_write_at self._unhealthy_at = unhealthy_at - self._spec: RobotSpec | None = None - self._position = np.zeros(len(joint_names), dtype=np.float32) - self._velocity = np.zeros(len(joint_names), dtype=np.float32) + self._position = np.zeros(len(spec.joint_names), dtype=np.float32) + self._velocity = np.zeros(len(spec.joint_names), dtype=np.float32) self._sample_time_ns = 0 self._read_count = 0 self._write_count = 0 @@ -56,18 +63,19 @@ def __init__( self.events: list[str] = [] self.commands: list[RobotCommand] = [] + @property + def spec(self) -> RobotSpec: + return self._spec + @property def capabilities(self) -> RobotCapabilities: return self._capabilities - def open(self, spec: RobotSpec) -> None: + def open(self) -> None: if self._opened and not self._closed: raise RuntimeError("fake backend is already open") self.events.append("open") - if len(self._joint_names) != len(spec.joint_names) or set(self._joint_names) != set(spec.joint_names): - raise ValidationError("backend.joint_names", str(spec.joint_names), self._joint_names) - self._spec = spec - self._position = np.array(spec.default_joint_position, copy=True) + self._position = np.array(self._spec.default_joint_position, copy=True) self._velocity.fill(0) self._sample_time_ns = 0 self._read_count = 0 @@ -90,9 +98,19 @@ def write_command(self, command: RobotCommand) -> None: self._require_open() if self._fail_write_at == self._write_count: raise RuntimeError(f"injected write failure at command {self._write_count}") - previous = self._position.copy() - self._position += np.float32(self._response) * (command.joint_position - self._position) - self._velocity = ((self._position - previous) / np.float32(self._period_s)).astype(np.float32) + expected = (self.spec.joint_count,) + if isinstance(command, JointServoCommand): + if command.joint_position.shape != expected: + raise ValidationError("command.joint_position.shape", str(expected), command.joint_position.shape) + previous = self._position.copy() + self._position += np.float32(self._response) * (command.joint_position - self._position) + self._velocity = ((self._position - previous) / np.float32(self._period_s)).astype(np.float32) + else: + if command.torque.shape != expected: + raise ValidationError("command.torque.shape", str(expected), command.torque.shape) + if np.any(np.abs(command.torque) > self.spec.torque_limit): + raise ValidationError("command.torque", "inside robot torque limits", command.torque.tolist()) + self._velocity.fill(0) self._sample_time_ns += self._period_ns self.commands.append(command) self.events.append("write") @@ -133,10 +151,3 @@ def _state(self) -> RobotState: base_position=np.array([0.0, 0.0, 0.3], dtype=np.float32), base_linear_velocity=zeros, ) - - -class LaggedFakeRobotInterface(FakeRobotInterface): - """A second adapter whose state moves halfway to each command.""" - - def __init__(self, joint_names: tuple[str, ...], *, sample_period_s: float = 0.02) -> None: - super().__init__(joint_names, sample_period_s=sample_period_s, response=0.5) diff --git a/motrix_deploy/tests/task_specs.py b/motrix_deploy/tests/task_specs.py new file mode 100644 index 00000000..222aca15 --- /dev/null +++ b/motrix_deploy/tests/task_specs.py @@ -0,0 +1,33 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Lightweight test-owned task spec plugin; never installed in production.""" + +from typing import ClassVar + +from motrix_deploy.artifact.schema import TaskSpec +from motrix_deploy.task import DeployTask + + +class TestTaskSpec(TaskSpec): + task_name: ClassVar[str] = "test/v1" + + +class TestDeployTask(DeployTask[object]): + spec_type = TestTaskSpec + + def __init__(self, spec, robot): + self.spec = spec + self.robot = robot + + def reset(self, state, context): + pass + + def build_observation(self, state, context): + raise NotImplementedError + + def process_action(self, action): + raise NotImplementedError + + def validate_command(self, command): + pass diff --git a/motrix_deploy/tests/test_artifact.py b/motrix_deploy/tests/test_artifact.py index 97825180..b892eb88 100644 --- a/motrix_deploy/tests/test_artifact.py +++ b/motrix_deploy/tests/test_artifact.py @@ -4,6 +4,7 @@ """Artifact schema, safety and checksum tests.""" import json +import os import subprocess import sys from collections.abc import Callable @@ -11,7 +12,8 @@ import pytest -from motrix_deploy.artifact import DeploymentManifest, inspect_artifact, read_artifact, write_artifact +from motrix_deploy.artifact import ControlSpec, DeploymentManifest, inspect_artifact, read_artifact, write_artifact +from motrix_deploy.contracts import JointControlMode from motrix_deploy.errors import ArtifactError, ValidationError POLICY_BYTES = b"deterministic ONNX placeholder" @@ -27,7 +29,9 @@ def test_artifact_round_trip_and_inspect( assert artifact.manifest.to_dict() == read_artifact(output).manifest.to_dict() assert summary["valid"] is True - assert summary["task"]["name"] == "test/v1" + assert summary["task"] == {"name": "test/v1", "config": {}} + assert summary["observation_size"] == artifact.manifest.policy.input.shape[1] + assert summary["action_size"] == artifact.manifest.policy.output.shape[1] assert summary["robot"]["joint_names"] == ["left_joint", "right_joint"] assert summary["policy"]["input"] == {"name": "observation", "shape": [1, 4], "dtype": "float32"} @@ -36,8 +40,19 @@ def test_cli_inspect_outputs_json(tmp_path: Path, manifest_factory: Callable[[], output = tmp_path / "fixture.deploy" write_artifact(output, manifest_factory(), {"policy/model.onnx": POLICY_BYTES}) + # A subprocess cannot inherit monkeypatched discovery. Install only test + # metadata on its temporary import path, not a production fixture plugin. + dist_info = tmp_path / "test_task_spec-1.0.dist-info" + dist_info.mkdir() + (dist_info / "METADATA").write_text("Name: test-task-spec\nVersion: 1.0\n", encoding="utf-8") + (dist_info / "entry_points.txt").write_text( + "[motrix_deploy.tasks]\ntest/v1 = task_specs:TestDeployTask\n", encoding="utf-8" + ) + env = dict(os.environ) + env["PYTHONPATH"] = os.pathsep.join([str(tmp_path), str(Path(__file__).parent), env.get("PYTHONPATH", "")]) result = subprocess.run( [sys.executable, "-m", "motrix_deploy.cli", "inspect", f"artifact={output}"], + env=env, check=False, capture_output=True, text=True, @@ -92,6 +107,55 @@ def test_invalid_task_name_is_rejected( DeploymentManifest.from_dict(manifest) +@pytest.mark.parametrize("field_name", ["input", "output"]) +@pytest.mark.parametrize("shape", [[4], [2, 4], [1, 2, 3], [1, 0], [1, -1], [1, 1.5]]) +def test_policy_requires_positive_batch_one_vector( + manifest_factory: Callable[[], DeploymentManifest], + field_name: str, + shape: list[int | float], +) -> None: + manifest = manifest_factory().to_dict() + manifest["policy"][field_name]["shape"] = shape + + with pytest.raises(ValidationError, match=rf"policy\.{field_name}\.shape"): + DeploymentManifest.from_dict(manifest) + + +@pytest.mark.parametrize("field_name", ["input", "output"]) +def test_policy_vector_size_is_independent_of_robot_joint_count( + manifest_factory: Callable[[], DeploymentManifest], + field_name: str, +) -> None: + manifest = manifest_factory().to_dict() + manifest["policy"][field_name]["shape"] = [1, 7] + + parsed = DeploymentManifest.from_dict(manifest) + + assert getattr(parsed.policy, field_name).shape == (1, 7) + assert parsed.task.to_dict() == {"name": "test/v1", "config": {}} + + +@pytest.mark.parametrize("mode", list(JointControlMode)) +def test_control_mode_serializes_explicitly(mode: JointControlMode) -> None: + control = ControlSpec(period_s=0.02, state_timeout_s=0.1, mode=mode) + assert control.to_dict()["mode"] == mode.value + assert ControlSpec.from_dict(control.to_dict()).mode is mode + assert ControlSpec(period_s=0.02, state_timeout_s=0.1).mode is JointControlMode.SERVO + + +def test_control_mode_is_required_in_manifest(manifest_factory: Callable[[], DeploymentManifest]) -> None: + manifest = manifest_factory().to_dict() + del manifest["control"]["mode"] + with pytest.raises(ValidationError, match="control.*mode"): + DeploymentManifest.from_dict(manifest) + + +@pytest.mark.parametrize("mode", ["invalid", None, 1]) +def test_invalid_control_mode_is_rejected(mode: object) -> None: + with pytest.raises(ValidationError, match="control.mode"): + ControlSpec(period_s=0.02, state_timeout_s=0.1, mode=mode) + + def test_joint_count_mismatch_is_rejected(manifest_factory: Callable[[], DeploymentManifest]) -> None: manifest = manifest_factory().to_dict() manifest["robot"]["joint_names"] = ["left_joint"] diff --git a/motrix_deploy/tests/test_backend_plugins.py b/motrix_deploy/tests/test_backend_plugins.py index 81221f0a..1620beff 100644 --- a/motrix_deploy/tests/test_backend_plugins.py +++ b/motrix_deploy/tests/test_backend_plugins.py @@ -1,17 +1,27 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 -"""Deployment backend plugin discovery contract tests.""" +"""Deployment runtime plugin discovery contract tests.""" from dataclasses import dataclass from typing import Any +import numpy as np import pytest +from fake_robot import FakeRobotInterface -import motrix_deploy.backend as backend_registry +import motrix_deploy.runtime.factory as runtime_factory from motrix_deploy.artifact import ControlSpec -from motrix_deploy.backend import BackendCreateContext, create_backend, registered_backends -from motrix_deploy.backend.fake import FakeRobotInterface +from motrix_deploy.contracts import RobotSpec +from motrix_deploy.runtime.config import SimulationRuntimeConfig +from motrix_deploy.runtime.factory import ( + RuntimeCreateContext, + create_hardware_runtime, + create_simulation_runtime, + registered_runtimes, +) +from motrix_deploy.runtime.hardware import HardwareRuntime +from motrix_env_core.config.scene import SceneCfg @dataclass(frozen=True) @@ -24,41 +34,67 @@ def load(self) -> Any: return self.target -def test_backend_plugin_is_discovered_and_constructed(monkeypatch: pytest.MonkeyPatch) -> None: +def test_runtime_plugin_is_discovered_and_constructed(monkeypatch: pytest.MonkeyPatch) -> None: calls = [] + spec = RobotSpec( + base_link_name="base", + joint_names=("joint",), + default_joint_position=np.zeros(1, dtype=np.float32), + position_lower=np.full(1, -1.0, dtype=np.float32), + position_upper=np.full(1, 1.0, dtype=np.float32), + torque_limit=np.ones(1, dtype=np.float32), + ) def factory(config, context): calls.append((config, context)) - return FakeRobotInterface(("joint",)) + robot = FakeRobotInterface(context.robot, response=config["response"]) + return HardwareRuntime(robot=robot, realtime=True) entry_point = _EntryPoint("fake", "test:factory", factory) - monkeypatch.setattr(backend_registry, "_backend_entry_points", lambda: (entry_point,)) - context = BackendCreateContext(control=ControlSpec(period_s=0.02, state_timeout_s=0.1), viewer=False) + monkeypatch.setattr(runtime_factory, "_runtime_entry_points", lambda: (entry_point,)) + context = RuntimeCreateContext( + robot=spec, + control=ControlSpec(period_s=0.02, state_timeout_s=0.1), + ) - backend = create_backend("fake", {"name": "fake", "response": 0.5}, context) + runtime = create_hardware_runtime("fake", {"name": "fake", "response": 0.5}, context) - assert isinstance(backend, FakeRobotInterface) - assert registered_backends() == ("fake",) + assert isinstance(runtime, HardwareRuntime) + assert isinstance(runtime.robot, FakeRobotInterface) + assert runtime.robot.spec is context.robot + assert registered_runtimes() == ("fake",) assert calls == [({"name": "fake", "response": 0.5}, context)] + with pytest.raises(TypeError, match="does not provide a SimulationRuntime"): + monkeypatch.setattr( + runtime_factory, + "_runtime_entry_points", + lambda: (_EntryPoint("fake", "test:factory", lambda config: runtime),), + ) + create_simulation_runtime("fake", SimulationRuntimeConfig(scene=SceneCfg())) -def test_backend_plugin_discovery_rejects_missing_duplicate_and_invalid_factories( +def test_runtime_plugin_discovery_rejects_missing_duplicate_and_invalid_factories( monkeypatch: pytest.MonkeyPatch, + manifest_factory, ) -> None: - context = BackendCreateContext(control=ControlSpec(period_s=0.02, state_timeout_s=0.1), viewer=False) - monkeypatch.setattr(backend_registry, "_backend_entry_points", lambda: ()) - with pytest.raises(ValueError, match="installed backends: none"): - create_backend("missing", {}, context) + spec = manifest_factory().robot + context = RuntimeCreateContext( + robot=spec, + control=ControlSpec(period_s=0.02, state_timeout_s=0.1), + ) + monkeypatch.setattr(runtime_factory, "_runtime_entry_points", lambda: ()) + with pytest.raises(ValueError, match="installed runtimes: none"): + create_hardware_runtime("missing", {}, context) duplicates = ( _EntryPoint("duplicate", "first:factory", lambda config, context: object()), _EntryPoint("duplicate", "second:factory", lambda config, context: object()), ) - monkeypatch.setattr(backend_registry, "_backend_entry_points", lambda: duplicates) - with pytest.raises(ValueError, match="Multiple deployment backend plugins"): - create_backend("duplicate", {}, context) + monkeypatch.setattr(runtime_factory, "_runtime_entry_points", lambda: duplicates) + with pytest.raises(ValueError, match="Multiple deployment runtime plugins"): + create_hardware_runtime("duplicate", {}, context) invalid = _EntryPoint("invalid", "invalid:factory", lambda config, context: object()) - monkeypatch.setattr(backend_registry, "_backend_entry_points", lambda: (invalid,)) - with pytest.raises(TypeError, match="expected RobotInterface"): - create_backend("invalid", {}, context) + monkeypatch.setattr(runtime_factory, "_runtime_entry_points", lambda: (invalid,)) + with pytest.raises(TypeError, match="expected DeploymentRuntime"): + create_hardware_runtime("invalid", {}, context) diff --git a/motrix_deploy/tests/test_config.py b/motrix_deploy/tests/test_config.py index 5274dcf7..77ac8382 100644 --- a/motrix_deploy/tests/test_config.py +++ b/motrix_deploy/tests/test_config.py @@ -13,8 +13,14 @@ def _run_mapping() -> dict[str, object]: return { "artifact": "artifact.deploy", - "backend": {"name": "test", "option": 1}, - "viewer": False, + "runtime": { + "kind": "simulation", + "backend": "test", + "option": 1, + "viewer": False, + "realtime": None, + }, + "duration_s": 0.1, } @@ -28,9 +34,32 @@ def test_deploy_run_config_parses_resolved_application_tree() -> None: config = _typed_config(_run_mapping()) assert config.artifact == "artifact.deploy" + assert config.runtime == _run_mapping()["runtime"] assert config.backend_name == "test" - assert config.backend_options == {"option": 1} - assert config.viewer is False + assert config.runtime_options == {"option": 1} + assert config.duration_s == pytest.approx(0.1) + assert config.command is None + + +def test_deploy_run_config_defaults_to_unbounded_duration() -> None: + values = _run_mapping() + values.pop("duration_s") + + config = _typed_config(values) + + assert config.duration_s is None + + +def test_deploy_run_config_parses_hardware_runtime() -> None: + runtime = {"kind": "hardware", "backend": "test", "network_interface": "enp3s0"} + + config = _typed_config({"artifact": "artifact.deploy", "runtime": runtime}) + + assert config.runtime == runtime + assert config.backend_name == "test" + assert config.runtime_options == {"network_interface": "enp3s0"} + assert config.duration_s is None + assert config.command is None def test_deploy_run_config_rejects_unknown_top_level_fields() -> None: diff --git a/motrix_deploy/tests/test_contracts.py b/motrix_deploy/tests/test_contracts.py index 6bc5360b..6bc99a87 100644 --- a/motrix_deploy/tests/test_contracts.py +++ b/motrix_deploy/tests/test_contracts.py @@ -8,7 +8,14 @@ import numpy as np import pytest -from motrix_deploy.contracts import RobotCommand, RobotSpec, TensorSpec +from motrix_deploy.contracts import ( + JointControlMode, + JointServoCommand, + JointTorqueCommand, + RobotCapabilities, + RobotSpec, + TensorSpec, +) from motrix_deploy.errors import ValidationError @@ -29,7 +36,7 @@ def test_contracts_preserve_valid_array_references() -> None: source = np.array([0.0, 0.0], dtype=np.float32) spec = robot_spec(default_joint_position=source) zero = np.zeros(2, dtype=np.float32) - command = RobotCommand( + command = JointServoCommand( joint_position=source, joint_velocity=zero, feedforward_torque=zero, @@ -41,6 +48,68 @@ def test_contracts_preserve_valid_array_references() -> None: assert command.joint_position is source +def test_joint_command_modes_are_readonly() -> None: + zero = np.zeros(2, dtype=np.float32) + servo = JointServoCommand(zero, zero, zero, zero, zero) + torque = JointTorqueCommand(zero) + assert servo.mode is JointControlMode.SERVO + assert torque.mode is JointControlMode.TORQUE + assert torque.torque is zero + for command in (servo, torque): + with pytest.raises(AttributeError): + command.mode = JointControlMode.SERVO + + +@pytest.mark.parametrize("modes", [("joint_servo",), ("joint_torque",), ("joint_servo", "joint_torque")]) +def test_capabilities_convert_mode_strings_to_enums(modes: tuple[str, ...]) -> None: + capabilities = RobotCapabilities(control_modes=modes, state_fields=frozenset()) + assert all(isinstance(mode, JointControlMode) for mode in capabilities.control_modes) + assert tuple(mode.value for mode in capabilities.control_modes) == modes + + +@pytest.mark.parametrize("modes", [("unknown",), ("joint_servo", "joint_servo"), (), ["joint_servo"]]) +def test_capabilities_reject_invalid_modes(modes: object) -> None: + with pytest.raises(ValidationError, match="capabilities.control_modes"): + RobotCapabilities(control_modes=modes, state_fields=frozenset()) + + +@pytest.mark.parametrize( + "value", + [ + np.zeros(2, dtype=np.float64), + np.zeros((1, 2), dtype=np.float32), + np.array([np.inf], dtype=np.float32), + np.array([np.nan], dtype=np.float32), + ], +) +def test_torque_command_rejects_invalid_arrays(value: np.ndarray) -> None: + with pytest.raises(ValidationError, match="command.torque"): + JointTorqueCommand(value) + + +def test_robot_spec_compatibility_accepts_matching_contract_with_float_roundoff() -> None: + expected = robot_spec() + actual = robot_spec(torque_limit=expected.torque_limit + np.float32(2e-7)) + expected.validate_compatible(actual) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("base_link_name", "other"), + ("joint_names", ("b", "a")), + ("joint_names", ("a", "other")), + ("default_joint_position", np.array([0.1, 0.0], dtype=np.float32)), + ("position_lower", np.array([-0.9, -1.0], dtype=np.float32)), + ("position_upper", np.array([0.9, 1.0], dtype=np.float32)), + ("torque_limit", np.array([2.0, 3.0], dtype=np.float32)), + ], +) +def test_robot_spec_compatibility_rejects_semantic_mismatch(field, value) -> None: + with pytest.raises(ValidationError, match=f"robot.{field}"): + robot_spec().validate_compatible(robot_spec(**{field: value})) + + def test_robot_spec_fields_are_frozen() -> None: spec = robot_spec() @@ -62,6 +131,31 @@ def test_robot_spec_rejects_invalid_arrays(field: str, value: np.ndarray, error_ robot_spec(**{field: value}) +def test_privileged_capabilities_default_empty_and_require_available_fields() -> None: + capabilities = RobotCapabilities(control_modes=("joint_servo",), state_fields=frozenset({"base_position"})) + assert capabilities.privileged_state_fields == frozenset() + privileged = RobotCapabilities( + control_modes=("joint_servo",), + state_fields=frozenset({"base_position"}), + privileged_state_fields=frozenset({"base_position"}), + ) + assert privileged.privileged_state_fields <= privileged.state_fields + with pytest.raises(ValidationError, match="capabilities.privileged_state_fields"): + RobotCapabilities( + control_modes=("joint_servo",), + state_fields=frozenset(), + privileged_state_fields=frozenset({"base_position"}), + ) + + +@pytest.mark.parametrize("value", [{"base_position"}, frozenset({"unavailable"})]) +def test_invalid_privileged_state_fields_are_rejected(value: object) -> None: + with pytest.raises(ValidationError, match="capabilities.privileged_state_fields"): + RobotCapabilities( + control_modes=("joint_servo",), state_fields=frozenset({"base_position"}), privileged_state_fields=value + ) + + def test_tensor_spec_requires_float32_fixed_shape() -> None: with pytest.raises(ValidationError, match="tensor.dtype"): TensorSpec(name="input", shape=(1, 4), dtype="float64") @@ -70,7 +164,7 @@ def test_tensor_spec_requires_float32_fixed_shape() -> None: def test_robot_command_rejects_negative_gains() -> None: zero = np.zeros(2, dtype=np.float32) with pytest.raises(ValidationError, match="command.gains"): - RobotCommand( + JointServoCommand( joint_position=zero, joint_velocity=zero, feedforward_torque=zero, diff --git a/motrix_deploy/tests/test_deploy_env_plugins.py b/motrix_deploy/tests/test_deploy_env_plugins.py new file mode 100644 index 00000000..5b7e2730 --- /dev/null +++ b/motrix_deploy/tests/test_deploy_env_plugins.py @@ -0,0 +1,81 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Robot-independent deployment environment discovery contracts.""" + +from types import SimpleNamespace +from unittest.mock import Mock + +import pytest + +from motrix_deploy import env as env_module +from motrix_deploy.env import ( + DEPLOY_ENV_ENTRY_POINT_GROUP, + assemble_deploy_scene, + available_deploy_envs, + create_deploy_env, +) +from motrix_env_core.config.scene import MjcfFileCfg, RobotCfg, SceneCfg + + +def _install(monkeypatch, entries): + def entry_points(*, group): + assert group == DEPLOY_ENV_ENTRY_POINT_GROUP + return entries + + monkeypatch.setattr(env_module.metadata, "entry_points", entry_points) + + +def test_world_discovery_is_lazy(monkeypatch): + entries = [SimpleNamespace(name=name, load=Mock()) for name in ("rough", "flat")] + _install(monkeypatch, entries) + assert available_deploy_envs() == ("flat", "rough") + for entry in entries: + entry.load.assert_not_called() + + +def test_factory_creates_instance_local_worlds(monkeypatch): + factory = Mock(side_effect=SceneCfg) + entry = SimpleNamespace(name="flat", load=Mock(return_value=factory)) + other = SimpleNamespace(name="rough", load=Mock()) + _install(monkeypatch, [entry, other]) + first = create_deploy_env("flat") + second = create_deploy_env("flat") + assert first is not second + assert first.objs is not second.objs + assert first.objs.robot is None + other.load.assert_not_called() + + +def test_assembly_uses_independently_registered_robot(monkeypatch): + _install(monkeypatch, [SimpleNamespace(name="world", load=Mock(return_value=SceneCfg))]) + robot = RobotCfg(model=MjcfFileCfg(file="synthetic.xml"), base_link_name="base") + factory = Mock(return_value=robot) + monkeypatch.setattr(env_module.registry, "make_robot_config", factory) + scene = assemble_deploy_scene("world", "custom-robot") + assert scene.objs.robot is robot + factory.assert_called_once_with("custom-robot") + + +def test_unknown_environment_reports_choices(monkeypatch): + _install(monkeypatch, [SimpleNamespace(name="flat", load=Mock())]) + with pytest.raises(ValueError, match="Unknown deployment environment.*available environments: flat"): + create_deploy_env("unknown") + + +def test_duplicate_environment_rejected_before_loading(monkeypatch): + entries = [SimpleNamespace(name="flat", load=Mock()) for _ in range(2)] + _install(monkeypatch, entries) + with pytest.raises(ValueError, match="Multiple deployment environment plugins"): + create_deploy_env("flat") + for entry in entries: + entry.load.assert_not_called() + + +@pytest.mark.parametrize("scene", [object(), SceneCfg()]) +def test_environment_factory_contract(monkeypatch, scene): + if isinstance(scene, SceneCfg): + scene.objs.robot = RobotCfg(model=MjcfFileCfg(file="synthetic.xml"), base_link_name="base") + _install(monkeypatch, [SimpleNamespace(name="bad", load=Mock(return_value=lambda: scene))]) + with pytest.raises((TypeError, ValueError), match="Deployment environment 'bad'"): + create_deploy_env("bad") diff --git a/motrix_deploy/tests/test_policy.py b/motrix_deploy/tests/test_policy.py index 7a8b131b..d39846ba 100644 --- a/motrix_deploy/tests/test_policy.py +++ b/motrix_deploy/tests/test_policy.py @@ -12,7 +12,33 @@ from motrix_deploy.contracts import TensorSpec from motrix_deploy.errors import ValidationError -from motrix_deploy.policy import OnnxPolicyRuntime +from motrix_deploy.policy import NoOpPolicyRuntime, OnnxPolicyRuntime, PolicyRuntime + + +def test_decision_runtime_only_requires_infer() -> None: + class Planner(PolicyRuntime): + def infer(self, observation): + return observation[::-1].copy() + + runtime = Planner() + runtime.reset() + np.testing.assert_array_equal(runtime.infer(np.array([1, 2], dtype=np.float32)), [2, 1]) + + +def test_noop_returns_independent_zero_actions_and_ignores_observation() -> None: + runtime = NoOpPolicyRuntime(3) + first = runtime.infer(np.array([1.0], dtype=np.float32)) + assert first.shape == (3,) and first.dtype == np.float32 + np.testing.assert_array_equal(first, np.zeros(3)) + first[:] = 10 + runtime.reset() + np.testing.assert_array_equal(runtime.infer(np.arange(10, dtype=np.float32)), np.zeros(3)) + + +@pytest.mark.parametrize("size", [-1, True, 1.5]) +def test_noop_rejects_invalid_action_size(size) -> None: + with pytest.raises(ValidationError, match="policy.noop.action_size"): + NoOpPolicyRuntime(size) def _write_linear_model( diff --git a/motrix_deploy/tests/test_runtime.py b/motrix_deploy/tests/test_runtime.py index 7fd3eadb..1ce8d22d 100644 --- a/motrix_deploy/tests/test_runtime.py +++ b/motrix_deploy/tests/test_runtime.py @@ -7,15 +7,26 @@ import numpy as np import pytest +from fake_robot import FakeRobotInterface from motrix_deploy.artifact import DeploymentManifest -from motrix_deploy.backend.fake import FakeRobotInterface, LaggedFakeRobotInterface -from motrix_deploy.contracts import FloatArray, RobotCapabilities, RobotCommand, RobotState, TensorSpec, float32_array -from motrix_deploy.errors import LieDownRequestedError +from motrix_deploy.contracts import ( + FloatArray, + JointControlMode, + JointServoCommand, + JointTorqueCommand, + RobotCapabilities, + RobotCommand, + RobotSpec, + RobotState, + float32_array, +) +from motrix_deploy.errors import LieDownRequestedError, ValidationError from motrix_deploy.policy import PolicyRuntime from motrix_deploy.runtime import ( - ControlLoop, + ControlSession, FixedStepScheduler, + HardwareRuntime, PolicyContext, RealtimeScheduler, ) @@ -25,21 +36,12 @@ class ConstantPolicy(PolicyRuntime): def __init__(self, input_size: int, action: FloatArray) -> None: - self._input_spec = TensorSpec(name="observation", shape=(1, input_size)) - self._output_spec = TensorSpec(name="action", shape=(1, action.size)) + self._input_size = input_size self._action = float32_array(action, path="test.action", shape=(action.size,)) self.observations: list[FloatArray] = [] - @property - def input_spec(self) -> TensorSpec: - return self._input_spec - - @property - def output_spec(self) -> TensorSpec: - return self._output_spec - def infer(self, observation: FloatArray) -> FloatArray: - value = float32_array(observation, path="test.observation", shape=(self.input_spec.shape[1],)) + value = float32_array(observation, path="test.observation", shape=(self._input_size,)) self.observations.append(value) return self._action @@ -47,15 +49,7 @@ def infer(self, observation: FloatArray) -> FloatArray: class _TestDeployTask(DeployTask[PlanarVelocityCommand]): def __init__(self, manifest: DeploymentManifest) -> None: self.robot = manifest.robot - self._previous_action = np.zeros(manifest.task.action_size, dtype=np.float32) - - @property - def observation_size(self) -> int: - return 4 - - @property - def action_size(self) -> int: - return 2 + self._previous_action = np.zeros(self.robot.joint_count, dtype=np.float32) def reset(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> None: del state, context @@ -71,7 +65,7 @@ def process_action(self, action: FloatArray) -> RobotCommand: executed = np.clip(action, -1.0, 1.0).astype(np.float32) self._previous_action = executed zeros = np.zeros(2, dtype=np.float32) - return RobotCommand( + return JointServoCommand( joint_position=self.robot.default_joint_position + np.float32(0.25) * executed, joint_velocity=zeros, feedforward_torque=zeros, @@ -84,27 +78,308 @@ def validate_command(self, command: PlanarVelocityCommand) -> None: raise ValueError("expected one planar velocity command") -def _loop(manifest: DeploymentManifest, backend: FakeRobotInterface) -> ControlLoop: - return ControlLoop( - robot=manifest.robot, - backend=backend, +class _TorqueDeployTask(_TestDeployTask): + def process_action(self, action: FloatArray) -> JointTorqueCommand: + return JointTorqueCommand(np.array(action, dtype=np.float32, copy=True)) + + +@pytest.mark.parametrize("requires_enable", [False, True]) +def test_torque_session_success_is_deterministic_without_fake_physics( + manifest_factory: Callable[[], DeploymentManifest], + requires_enable: bool, +) -> None: + manifest = manifest_factory() + traces = [] + for _ in range(2): + backend = EnablingBackend(manifest.robot) if requires_enable else FakeRobotInterface(manifest.robot) + if requires_enable: + backend._capabilities = RobotCapabilities( + control_modes=(JointControlMode.SERVO, JointControlMode.TORQUE), + state_fields=backend.capabilities.state_fields, + requires_enable=True, + ) + session = _session(manifest, backend) + session.task = _TorqueDeployTask(manifest) + assert session.start() + for step in range(3): + assert session.tick(elapsed_time_s=step * manifest.control.period_s) + session.stop() + result = session.result(steps=3) + assert result.success + if requires_enable: + assert backend.events.index("enable") < backend.events.index("write") + assert all(command.mode is JointControlMode.TORQUE for command in backend.commands) + np.testing.assert_array_equal(backend._position, manifest.robot.default_joint_position) + np.testing.assert_array_equal(backend._velocity, np.zeros(2, dtype=np.float32)) + assert backend._sample_time_ns == 3 * backend._period_ns + traces.append(result.trace_sha256) + assert traces[0] == traces[1] + + +@pytest.mark.parametrize("requires_enable", [False, True]) +def test_unsupported_command_mode_prevents_write_after_enable( + manifest_factory: Callable[[], DeploymentManifest], + requires_enable: bool, +) -> None: + manifest = manifest_factory() + backend = EnablingBackend(manifest.robot) if requires_enable else FakeRobotInterface(manifest.robot) + backend._capabilities = RobotCapabilities( + control_modes=(JointControlMode.SERVO,), + state_fields=backend.capabilities.state_fields, + requires_enable=requires_enable, + ) + session = _session(manifest, backend) + session.task = _TorqueDeployTask(manifest) + assert session.start() + assert not session.tick(elapsed_time_s=0.0) + session.stop() + result = session.result(steps=1) + assert result.exit_reason == "invalid_action" + assert "backend.control_modes" in result.error + assert ("enable" in backend.events) is requires_enable + assert "write" not in backend.events + assert backend.commands == [] + assert backend.events[-2:] == ["stop", "close"] + + +class _AlternatingDeployTask(_TestDeployTask): + def __init__(self, manifest: DeploymentManifest) -> None: + super().__init__(manifest) + self._next_mode = JointControlMode.SERVO + + def process_action(self, action: FloatArray) -> RobotCommand: + if self._next_mode is JointControlMode.SERVO: + self._next_mode = JointControlMode.TORQUE + return super().process_action(action) + self._next_mode = JointControlMode.SERVO + return JointTorqueCommand(np.array(action, dtype=np.float32, copy=True)) + + +@pytest.mark.parametrize("requires_enable", [False, True]) +def test_one_session_alternates_servo_and_torque_commands( + manifest_factory: Callable[[], DeploymentManifest], + requires_enable: bool, +) -> None: + manifest = manifest_factory() + backend = EnablingBackend(manifest.robot) if requires_enable else FakeRobotInterface(manifest.robot) + backend._capabilities = RobotCapabilities( + control_modes=(JointControlMode.SERVO, JointControlMode.TORQUE), + state_fields=backend.capabilities.state_fields, + requires_enable=requires_enable, + ) + session = _session(manifest, backend) + session.task = _AlternatingDeployTask(manifest) + assert session.start() + for step in range(4): + assert session.tick(elapsed_time_s=step * session.period_s) + session.stop() + assert session.result(steps=4).success + expected_modes = [JointControlMode.SERVO, JointControlMode.TORQUE] * 2 + if requires_enable: + assert backend.events.count("enable") == 1 + assert [command.mode for command in backend.commands] == expected_modes + assert backend.events.count("open") == 1 + assert backend.events.count("write") == 4 + + +def test_switch_to_unsupported_mode_preserves_successful_tick( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = FakeRobotInterface(manifest.robot) + backend._capabilities = RobotCapabilities( + control_modes=(JointControlMode.SERVO,), + state_fields=backend.capabilities.state_fields, + ) + session = _session(manifest, backend) + session.task = _AlternatingDeployTask(manifest) + assert session.start() + assert session.tick(elapsed_time_s=0.0) + trace = session.result(steps=1).trace_sha256 + assert not session.tick(elapsed_time_s=session.period_s) + session.stop() + result = session.result(steps=2) + assert result.exit_reason == "invalid_action" + assert "backend.control_modes" in result.error + assert result.completed_steps == 1 + assert result.trace_sha256 == trace + assert len(backend.commands) == 1 + assert backend.commands[0].mode is JointControlMode.SERVO + assert backend.events.count("write") == 1 + + +@pytest.mark.parametrize("torque", [[1.0], [100.0, 0.0]]) +def test_fake_validates_torque_shape_and_limits( + manifest_factory: Callable[[], DeploymentManifest], + torque: list[float], +) -> None: + from motrix_deploy.errors import ValidationError + + backend = FakeRobotInterface(manifest_factory().robot) + backend.open() + try: + with pytest.raises(ValidationError, match="command.torque"): + backend.write_command(JointTorqueCommand(np.array(torque, dtype=np.float32))) + assert backend.commands == [] + assert "write" not in backend.events + finally: + backend.close() + + +def _loop( + manifest: DeploymentManifest, + backend: FakeRobotInterface, + *, + command_binding: CommandBinding[PlanarVelocityCommand] | None = None, +) -> tuple[HardwareRuntime, ControlSession]: + runtime = HardwareRuntime(backend, scheduler=FixedStepScheduler(manifest.control.period_s)) + return runtime, _session(manifest, backend, command_binding=command_binding) + + +def test_fake_interface_binds_readonly_spec_at_construction( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + spec = manifest_factory().robot + backend = FakeRobotInterface(spec) + + assert backend.spec is spec + with pytest.raises(AttributeError): + backend.spec = spec + backend.open() + np.testing.assert_array_equal(backend.read_state(0.1).joint_position, spec.default_joint_position) + backend.close() + + +def _session( + manifest: DeploymentManifest, + backend: FakeRobotInterface, + *, + command_binding: CommandBinding[PlanarVelocityCommand] | None = None, +) -> ControlSession: + return ControlSession( + robot=backend, task=_TestDeployTask(manifest), - policy=ConstantPolicy(manifest.task.observation_size, np.array([0.4, -0.4], dtype=np.float32)), - command_binding=ConstantPlanarVelocityBinding([0.5, 0.0, 0.0]), - scheduler=FixedStepScheduler(manifest.control.period_s), + policy=ConstantPolicy(manifest.policy.input.shape[1], np.array([0.4, -0.4], dtype=np.float32)), + command_binding=command_binding or ConstantPlanarVelocityBinding([0.5, 0.0, 0.0]), + period_s=manifest.control.period_s, state_timeout_s=manifest.control.state_timeout_s, ) -@pytest.mark.parametrize("backend_type", [FakeRobotInterface, LaggedFakeRobotInterface]) -def test_backend_contract_runs_without_control_loop_changes( +class _SimulationOwnedFakeRobotInterface(FakeRobotInterface): + """Cache commands while a separate simulation callback advances state.""" + + def write_command(self, command: RobotCommand) -> None: + self._require_open() + if self._fail_write_at == self._write_count: + raise RuntimeError("injected write failure") + assert command.joint_position.shape == self._position.shape + self.commands.append(command) + self.events.append("write") + self._write_count += 1 + + def advance(self) -> None: + self._require_open() + self.events.append("advance") + previous = self._position.copy() + self._position = self.commands[-1].joint_position.copy() + self._velocity = ((self._position - previous) / np.float32(self._period_s)).astype(np.float32) + self._sample_time_ns += self._period_ns + + +class _TrackingPolicy(ConstantPolicy): + def __init__(self, input_size: int, action: FloatArray, events: list[str]) -> None: + super().__init__(input_size, action) + self.events = events + + def infer(self, observation: FloatArray) -> FloatArray: + self.events.append("infer") + return super().infer(observation) + + +def test_control_session_allows_external_simulation_advance_after_each_tick( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = _SimulationOwnedFakeRobotInterface(manifest.robot) + loop = _session(manifest, backend) + policy = _TrackingPolicy(manifest.policy.input.shape[1], np.array([0.4, -0.4], dtype=np.float32), backend.events) + loop.policy = policy + + assert loop.start() + try: + for step in range(3): + assert loop.tick(elapsed_time_s=step * manifest.control.period_s) + backend.advance() + finally: + loop.stop() + result = loop.result(steps=3) + + assert result.success + assert result.completed_steps == 3 + assert result.simulation_time_s == pytest.approx(3 * manifest.control.period_s) + assert backend.events == ["open", "read", *(["read", "infer", "write", "advance"] * 3), "stop", "close"] + assert backend._sample_time_ns == 3 * backend._period_ns + np.testing.assert_array_equal(policy.observations[0], np.zeros(4, dtype=np.float32)) + np.testing.assert_allclose( + policy.observations[1][:2], backend.commands[0].joint_position - manifest.robot.default_joint_position + ) + np.testing.assert_array_equal(backend._position, backend.commands[-1].joint_position) + + +def test_standalone_driver_does_not_advance_simulation( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = _SimulationOwnedFakeRobotInterface(manifest.robot) + runtime, loop = _loop(manifest, backend) + + result = runtime.run(loop, steps=3) + + assert result.success + assert backend._sample_time_ns == 0 + assert "advance" not in backend.events + np.testing.assert_array_equal(backend._position, manifest.robot.default_joint_position) + for observation in loop.policy.observations: + np.testing.assert_array_equal(observation, np.zeros(4, dtype=np.float32)) + + +def test_external_simulation_failure_preserves_completed_control_tick( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = _SimulationOwnedFakeRobotInterface(manifest.robot) + session = _session(manifest, backend) + assert session.start() + assert session.tick(elapsed_time_s=0.0) + backend.events.append("advance") + session.fail(RuntimeError("simulation advance failed")) + assert not session.active + assert not session.tick(elapsed_time_s=manifest.control.period_s) + session.stop() + result = session.result(steps=3) + + assert not result.success + assert result.exit_reason == "backend_error" + assert result.error == "simulation advance failed" + assert result.completed_steps == 1 + assert result.simulation_time_s == manifest.control.period_s + assert "write" in result.latency + assert "loop" in result.latency + assert backend.events == ["open", "read", "read", "write", "advance", "stop", "close"] + assert len(backend.commands) == 1 + + +@pytest.mark.parametrize("response", [1.0, 0.5]) +def test_control_loop_runs_with_different_robot_responses( manifest_factory: Callable[[], DeploymentManifest], - backend_type: type[FakeRobotInterface], + response: float, ) -> None: manifest = manifest_factory() - backend = backend_type(manifest.robot.joint_names) + backend = FakeRobotInterface(manifest.robot, response=response) - result = _loop(manifest, backend).run(steps=5) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=5) assert result.success assert result.exit_reason == "completed" @@ -117,13 +392,13 @@ def test_backend_contract_runs_without_control_loop_changes( def test_fixed_step_rollout_is_deterministic(manifest_factory: Callable[[], DeploymentManifest]) -> None: manifest = manifest_factory() - first_backend = FakeRobotInterface(manifest.robot.joint_names) - second_backend = FakeRobotInterface(manifest.robot.joint_names) + first_backend = FakeRobotInterface(manifest.robot) + second_backend = FakeRobotInterface(manifest.robot) - first_loop = _loop(manifest, first_backend) - second_loop = _loop(manifest, second_backend) - first = first_loop.run(steps=4) - second = second_loop.run(steps=4) + first_runtime, first_loop = _loop(manifest, first_backend) + second_runtime, second_loop = _loop(manifest, second_backend) + first = first_runtime.run(first_loop, steps=4) + second = second_runtime.run(second_loop, steps=4) assert (first.success, first.exit_reason, first.completed_steps) == ( second.success, @@ -174,17 +449,10 @@ def test_control_loop_reads_one_singleton_command_batch_per_tick( ) -> None: manifest = manifest_factory() binding = _TrackingBinding() - loop = ControlLoop( - robot=manifest.robot, - backend=FakeRobotInterface(manifest.robot.joint_names), - task=_TestDeployTask(manifest), - policy=ConstantPolicy(manifest.task.observation_size, np.zeros(2, dtype=np.float32)), - command_binding=binding, - scheduler=FixedStepScheduler(manifest.control.period_s), - state_timeout_s=manifest.control.state_timeout_s, - ) + runtime, loop = _loop(manifest, FakeRobotInterface(manifest.robot), command_binding=binding) + loop.policy = ConstantPolicy(manifest.policy.input.shape[1], np.zeros(2, dtype=np.float32)) - result = loop.run(steps=3) + result = runtime.run(loop, steps=3) assert result.success assert binding.batch_sizes == [1, 1, 1] @@ -195,17 +463,10 @@ def test_control_loop_runs_without_a_step_bound_until_interrupted( ) -> None: manifest = manifest_factory() binding = _InterruptingBinding(interrupt_at=3) - loop = ControlLoop( - robot=manifest.robot, - backend=FakeRobotInterface(manifest.robot.joint_names), - task=_TestDeployTask(manifest), - policy=ConstantPolicy(manifest.task.observation_size, np.zeros(2, dtype=np.float32)), - command_binding=binding, - scheduler=FixedStepScheduler(manifest.control.period_s), - state_timeout_s=manifest.control.state_timeout_s, - ) + runtime, loop = _loop(manifest, FakeRobotInterface(manifest.robot), command_binding=binding) + loop.policy = ConstantPolicy(manifest.policy.input.shape[1], np.zeros(2, dtype=np.float32)) - result = loop.run() + result = runtime.run(loop) assert not result.success assert result.exit_reason == "interrupted" @@ -214,18 +475,11 @@ def test_control_loop_runs_without_a_step_bound_until_interrupted( def test_command_binding_failure_returns_input_error(manifest_factory: Callable[[], DeploymentManifest]) -> None: manifest = manifest_factory() - backend = FakeRobotInterface(manifest.robot.joint_names) - loop = ControlLoop( - robot=manifest.robot, - backend=backend, - task=_TestDeployTask(manifest), - policy=ConstantPolicy(manifest.task.observation_size, np.zeros(2, dtype=np.float32)), - command_binding=_FailingBinding(), - scheduler=FixedStepScheduler(manifest.control.period_s), - state_timeout_s=manifest.control.state_timeout_s, - ) + backend = FakeRobotInterface(manifest.robot) + runtime, loop = _loop(manifest, backend, command_binding=_FailingBinding()) + loop.policy = ConstantPolicy(manifest.policy.input.shape[1], np.zeros(2, dtype=np.float32)) - result = loop.run(steps=1) + result = runtime.run(loop, steps=1) assert not result.success assert result.exit_reason == "input_error" @@ -248,9 +502,10 @@ def test_backend_failures_return_structured_results_and_cleanup( completed_steps: int, ) -> None: manifest = manifest_factory() - backend = FakeRobotInterface(manifest.robot.joint_names, **backend_kwargs) + backend = FakeRobotInterface(manifest.robot, **backend_kwargs) - result = _loop(manifest, backend).run(steps=5) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=5) assert not result.success assert result.exit_reason == reason @@ -258,24 +513,19 @@ def test_backend_failures_return_structured_results_and_cleanup( assert backend.events[-2:] == ["stop", "close"] -def test_backend_native_joint_order_can_differ_from_canonical_order( +def test_backend_open_validation_failure_cleans_up_before_first_command( manifest_factory: Callable[[], DeploymentManifest], ) -> None: - manifest = manifest_factory() - backend = FakeRobotInterface(tuple(reversed(manifest.robot.joint_names))) - - result = _loop(manifest, backend).run(steps=2) - - assert result.success + class InvalidRobot(FakeRobotInterface): + def open(self) -> None: + self.events.append("open") + raise ValidationError("backend.joint_names", "matching robot joints", "missing_joint") - -def test_missing_backend_joint_fails_before_first_command( - manifest_factory: Callable[[], DeploymentManifest], -) -> None: manifest = manifest_factory() - backend = FakeRobotInterface((*manifest.robot.joint_names[:-1], "missing_joint")) + backend = InvalidRobot(manifest.robot) - result = _loop(manifest, backend).run(steps=2) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=2) assert result.exit_reason == "validation_error" assert result.completed_steps == 0 @@ -291,9 +541,10 @@ def read_state(self, timeout_s: float): def test_user_interrupt_returns_result_and_cleans_up(manifest_factory: Callable[[], DeploymentManifest]) -> None: manifest = manifest_factory() - backend = InterruptingBackend(manifest.robot.joint_names) + backend = InterruptingBackend(manifest.robot) - result = _loop(manifest, backend).run(steps=2) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=2) assert result.exit_reason == "interrupted" assert backend.events[-2:] == ["stop", "close"] @@ -309,9 +560,10 @@ def test_invalid_state_fails_and_lifecycle_cleanup_is_idempotent( manifest_factory: Callable[[], DeploymentManifest], ) -> None: manifest = manifest_factory() - backend = InvalidStateBackend(manifest.robot.joint_names) + backend = InvalidStateBackend(manifest.robot) - result = _loop(manifest, backend).run(steps=2) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=2) events_after_run = list(backend.events) backend.stop() backend.close() @@ -343,20 +595,18 @@ def sleep(duration: float) -> None: class EnablingBackend(FakeRobotInterface): - def __init__(self, joint_names: tuple[str, ...], *, sample_period_s: float = 0.02) -> None: - super().__init__(joint_names, sample_period_s=sample_period_s) + def __init__(self, spec: RobotSpec, *, sample_period_s: float = 0.02) -> None: + super().__init__(spec, sample_period_s=sample_period_s) self._capabilities = RobotCapabilities( - control_modes=("joint_pd",), + control_modes=("joint_servo",), state_fields=self._capabilities.state_fields, max_command_rate_hz=1.0 / sample_period_s, requires_enable=True, ) self.enabled = False - self.initial_command: RobotCommand | None = None - def enable(self, initial_command: RobotCommand) -> None: + def enable(self) -> None: assert "write" not in self.events - self.initial_command = initial_command self.events.append("enable") self.enabled = True @@ -375,9 +625,10 @@ def test_lie_down_request_returns_structured_result_and_cleans_up( manifest_factory: Callable[[], DeploymentManifest], ) -> None: manifest = manifest_factory() - backend = LieDownBackend(manifest.robot.joint_names) + backend = LieDownBackend(manifest.robot) - result = _loop(manifest, backend).run(steps=1) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=1) assert not result.success assert result.exit_reason == "lie_down" @@ -385,13 +636,290 @@ def test_lie_down_request_returns_structured_result_and_cleans_up( assert backend.events[-2:] == ["stop", "close"] +@pytest.mark.parametrize("terminate_at", [0, 1, 3]) +def test_task_termination_prevents_inference_and_commands( + manifest_factory: Callable[[], DeploymentManifest], terminate_at: int +) -> None: + manifest = manifest_factory() + backend = EnablingBackend(manifest.robot) if terminate_at == 0 else FakeRobotInterface(manifest.robot) + runtime, loop = _loop(manifest, backend) + checks = [] + + def check(state: RobotState) -> str | None: + checks.append(state) + return "task fell" if len(checks) - 1 == terminate_at else None + + loop.task.check_termination = check + result = runtime.run(loop, steps=5) + + assert result.exit_reason == "task_terminated" + assert result.error == "task fell" + assert not result.success + assert result.completed_steps == max(0, terminate_at - 1) + assert len(loop.policy.observations) == result.completed_steps + assert len(backend.commands) == result.completed_steps + if terminate_at == 0: + assert "enable" not in backend.events + assert backend.events[-2:] == ["stop", "close"] + + +def test_task_termination_checks_state_after_enable_before_inference( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = EnablingBackend(manifest.robot) + runtime, loop = _loop(manifest, backend) + loop.task.check_termination = lambda state: "fell after enable" if backend.enabled else None + result = runtime.run(loop, steps=1) + assert result.exit_reason == "task_terminated" + assert result.completed_steps == 0 + assert backend.commands == [] + assert loop.policy.observations == [] + assert backend.events == ["open", "read", "enable", "read", "stop", "close"] + + +def test_task_termination_hook_failure_has_task_error_and_cleanup( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = FakeRobotInterface(manifest.robot) + runtime, loop = _loop(manifest, backend) + + def fail(state: RobotState) -> str | None: + raise RuntimeError("termination failed") + + loop.task.check_termination = fail + result = runtime.run(loop, steps=1) + assert result.exit_reason == "task_termination_error" + assert result.error == "termination failed" + assert backend.commands == [] + assert backend.events[-2:] == ["stop", "close"] + + +@pytest.mark.parametrize("privileged", [False, True]) +def test_backend_state_metadata_does_not_gate_task_execution( + manifest_factory: Callable[[], DeploymentManifest], privileged: bool +) -> None: + manifest = manifest_factory() + backend = EnablingBackend(manifest.robot) + backend._capabilities = RobotCapabilities( + control_modes=("joint_servo",), + state_fields=frozenset({"joint_position", "joint_velocity", "base_linear_velocity"}), + privileged_state_fields=frozenset({"base_linear_velocity"}) if privileged else frozenset(), + requires_enable=True, + ) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=1) + assert result.success + assert len(backend.commands) == 1 + assert backend.events.index("enable") < backend.events.index("write") + assert backend.events[-2:] == ["stop", "close"] + + def test_required_enable_completes_before_first_policy_write( manifest_factory: Callable[[], DeploymentManifest], ) -> None: manifest = manifest_factory() - backend = EnablingBackend(manifest.robot.joint_names) + backend = EnablingBackend(manifest.robot) - result = _loop(manifest, backend).run(steps=1) + runtime, control = _loop(manifest, backend) + result = runtime.run(control, steps=1) assert result.success assert backend.events[:6] == ["open", "read", "enable", "read", "read", "write"] + + +def test_enable_does_not_process_a_synthetic_policy_action(manifest_factory) -> None: + manifest = manifest_factory() + backend = EnablingBackend(manifest.robot) + session = _session(manifest, backend) + actions = [] + process_action = session.task.process_action + + def record_action(action): + actions.append(np.array(action, copy=True)) + return process_action(action) + + session.task.process_action = record_action + assert session.start() + assert actions == [] + assert session.tick(elapsed_time_s=0.0) + assert len(actions) == 1 + session.stop() + assert session.result(steps=1).success + + +def test_direct_session_lifecycle_is_single_use_and_cleanup_is_explicit( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = FakeRobotInterface(manifest.robot) + session = _session(manifest, backend) + assert not session.active + assert not session.tick(elapsed_time_s=0.0) + assert session.start() + assert session.start() + assert backend.events == ["open", "read"] + assert session.tick(elapsed_time_s=7.25) + assert session.completed_steps == 1 + assert session.active + session.stop() + events = list(backend.events) + session.stop() + assert not session.start() + assert not session.tick(elapsed_time_s=8.0) + assert not session.active + assert backend.events == events + result = session.result(steps=1, overrun_count=2) + assert result.success + assert result.overrun_count == 2 + assert result.simulation_time_s == manifest.control.period_s + assert not session.result(steps=None).success + assert not session.result(steps=2).success + assert session.result(steps=1).wall_time_s == result.wall_time_s + + +def test_failed_start_records_error_without_implicit_cleanup( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + backend = FakeRobotInterface(manifest.robot, fail_read_at=0) + session = _session(manifest, backend) + assert not session.start() + assert not session.start() + assert not session.active + assert not session.tick(elapsed_time_s=0.0) + assert "stop" not in backend.events + session.stop() + assert session.result(steps=1).exit_reason == "state_timeout" + assert backend.events[-2:] == ["stop", "close"] + + +class _StatefulTask(_TestDeployTask): + def __init__(self, manifest: DeploymentManifest) -> None: + super().__init__(manifest) + self.contexts: list[PolicyContext[PlanarVelocityCommand]] = [] + self.reset_contexts: list[PolicyContext[PlanarVelocityCommand]] = [] + + def reset(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> None: + super().reset(state, context) + self.reset_contexts.append(context) + + def build_observation(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> FloatArray: + self.contexts.append(context) + observation = super().build_observation(state, context) + observation[:2] += self._previous_action + observation[2:] += np.float32(context.elapsed_time_s) + return observation + + +class _StatefulPolicy(ConstantPolicy): + def __init__(self) -> None: + super().__init__(4, np.zeros(2, dtype=np.float32)) + self.reset_count = 0 + self.counter = 99 + + def reset(self) -> None: + self.reset_count += 1 + self.counter = 0 + + def infer(self, observation: FloatArray) -> FloatArray: + super().infer(observation) + self.counter += 1 + return np.tanh(observation[:2] + np.float32(self.counter * 0.1)).astype(np.float32) + + +@pytest.mark.parametrize("enabling", [False, True]) +def test_stateful_session_and_standalone_driver_have_identical_trace_and_reset_semantics( + manifest_factory: Callable[[], DeploymentManifest], + enabling: bool, +) -> None: + manifest = manifest_factory() + backend_type = EnablingBackend if enabling else FakeRobotInterface + direct = _session(manifest, backend_type(manifest.robot)) + runtime, driver = _loop(manifest, backend_type(manifest.robot)) + for executor in (direct, driver): + executor.task = _StatefulTask(manifest) + executor.policy = _StatefulPolicy() + assert direct.start() + for step in range(4): + assert direct.tick(elapsed_time_s=step * direct.period_s) + direct.stop() + direct_result = direct.result(steps=4) + driver_result = runtime.run(driver, steps=4) + assert direct_result.success and driver_result.success + assert direct_result.trace_sha256 == driver_result.trace_sha256 + assert direct.robot.events == driver.robot.events + for executor in (direct, driver): + assert executor.policy.reset_count == (2 if enabling else 1) + assert len(executor.task.reset_contexts) == (3 if enabling else 1) + assert [context.step for context in executor.task.contexts] == list(range(4)) + assert [context.elapsed_time_s for context in executor.task.contexts] == pytest.approx( + [step * executor.period_s for step in range(4)] + ) + for first, second in zip(direct.policy.observations, driver.policy.observations, strict=True): + np.testing.assert_array_equal(first, second) + for first, second in zip(direct.robot.commands, driver.robot.commands, strict=True): + np.testing.assert_array_equal(first.joint_position, second.joint_position) + + +def test_direct_session_uses_supplied_simulation_relative_context_time( + manifest_factory: Callable[[], DeploymentManifest], +) -> None: + manifest = manifest_factory() + session = _session(manifest, FakeRobotInterface(manifest.robot)) + task = _StatefulTask(manifest) + session.task = task + assert session.start() + assert session.tick(elapsed_time_s=0.125) + assert session.tick(elapsed_time_s=0.75) + session.stop() + assert task.reset_contexts[0].elapsed_time_s == 0.125 + assert [context.elapsed_time_s for context in task.contexts] == [0.125, 0.75] + assert session.result(steps=2).simulation_time_s == 2 * session.period_s + + +@pytest.mark.parametrize("phase", ["reset", "wait"]) +def test_driver_scheduler_failure_is_recorded_and_cleans_up( + manifest_factory: Callable[[], DeploymentManifest], + phase: str, +) -> None: + manifest = manifest_factory() + backend = FakeRobotInterface(manifest.robot) + runtime, loop = _loop(manifest, backend) + + def fail(*args) -> None: + raise RuntimeError("scheduler failed") + + setattr(runtime.scheduler, phase, fail) + result = runtime.run(loop, steps=1) + assert result.exit_reason == "backend_error" + assert result.error == "scheduler failed" + assert result.completed_steps == 0 + assert backend.events[-2:] == ["stop", "close"] + + +@pytest.mark.parametrize("prior_failure", [False, True]) +def test_cleanup_errors_do_not_replace_existing_failure_and_close_is_attempted( + manifest_factory: Callable[[], DeploymentManifest], + prior_failure: bool, +) -> None: + manifest = manifest_factory() + backend = FakeRobotInterface(manifest.robot) + session = _session(manifest, backend) + assert session.start() + assert session.tick(elapsed_time_s=0.0) + + def fail_stop() -> None: + backend.events.append("stop-failed") + raise RuntimeError("cleanup failed") + + backend.stop = fail_stop + if prior_failure: + session.fail(LieDownRequestedError("lie down")) + session.stop() + session.stop() + result = session.result(steps=1) + assert result.exit_reason == ("lie_down" if prior_failure else "cleanup_error") + assert result.error == ("lie down" if prior_failure else "cleanup failed") + assert backend.events[-2:] == ["stop-failed", "close"] diff --git a/motrix_deploy/tests/test_runtime_binding.py b/motrix_deploy/tests/test_runtime_binding.py new file mode 100644 index 00000000..f64b75a6 --- /dev/null +++ b/motrix_deploy/tests/test_runtime_binding.py @@ -0,0 +1,92 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Runtime/control ownership binding contracts.""" + +from types import SimpleNamespace + +import pytest +from fake_robot import FakeRobotInterface +from test_runtime import _session + +import motrix_deploy.runtime.factory as runtime_factory +from motrix_deploy.errors import ValidationError +from motrix_deploy.runtime.factory import create_hardware_runtime +from motrix_deploy.runtime.hardware import HardwareRuntime +from motrix_deploy.runtime.scheduler import FixedStepScheduler + + +def test_hardware_runtime_runs_bound_control_without_duplicate_session(manifest_factory): + manifest = manifest_factory() + robot = FakeRobotInterface(manifest.robot) + control = _session(manifest, robot) + runtime = HardwareRuntime(robot, scheduler=FixedStepScheduler(manifest.control.period_s)) + + runtime.bind_control_session(control) + assert runtime.control is control + result = runtime.run(steps=2) + + assert result.success + assert runtime.control is control + assert robot.events[-2:] == ["stop", "close"] + + +def test_binding_rejects_conflicting_robot_and_duplicate_control(manifest_factory): + manifest = manifest_factory() + robot = FakeRobotInterface(manifest.robot) + other = FakeRobotInterface(manifest.robot) + runtime = HardwareRuntime(robot) + first = _session(manifest, robot) + second = _session(manifest, robot) + foreign = _session(manifest, other) + + runtime.bind_control_session(first) + runtime.bind_control_session(first) + with pytest.raises(ValidationError, match="already bound"): + runtime.bind_control_session(second) + with pytest.raises(ValidationError, match="runtime.control.robot"): + runtime.bind_control_session(foreign) + with pytest.raises(ValidationError, match="already bound"): + runtime.run(second, steps=1) + + +def test_unbound_hardware_runtime_requires_control(manifest_factory): + manifest = manifest_factory() + runtime = HardwareRuntime(FakeRobotInterface(manifest.robot)) + + with pytest.raises(ValidationError, match="runtime.control"): + runtime.run(steps=1) + + +def test_hardware_factory_prepares_robot_for_explicit_control_binding(monkeypatch, manifest_factory): + manifest = manifest_factory() + robot = FakeRobotInterface(manifest.robot) + calls = [] + + def factory(config, context): + calls.append((config, context)) + return HardwareRuntime(robot, realtime=False) + + entry = SimpleNamespace(name="fake", value="fake:factory", load=lambda: factory) + monkeypatch.setattr(runtime_factory, "_runtime_entry_points", lambda: (entry,)) + + context = runtime_factory.RuntimeCreateContext(robot=manifest.robot, control=manifest.control) + config = {} + runtime = create_hardware_runtime("fake", config, context) + assert calls == [(config, context)] + assert runtime.robot is robot + assert not runtime.opened + assert robot.events == [] + + control = _session(manifest, runtime.robot) + runtime.bind_control_session(control) + assert runtime.control is control + assert control.robot is runtime.robot + assert robot.events == [] + + with runtime: + assert runtime.opened + result = runtime.run(steps=1) + assert result.success + assert robot.events[-2:] == ["stop", "close"] + assert not runtime.opened diff --git a/motrix_deploy/tests/test_simulation_runtime.py b/motrix_deploy/tests/test_simulation_runtime.py new file mode 100644 index 00000000..3736ec16 --- /dev/null +++ b/motrix_deploy/tests/test_simulation_runtime.py @@ -0,0 +1,166 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Public simulation runtime configuration and plugin selection contracts.""" + +from dataclasses import FrozenInstanceError, asdict +from types import SimpleNamespace + +import numpy as np +import pytest +from fake_robot import FakeRobotInterface + +import motrix_deploy.runtime.factory as runtime_factory +from motrix_deploy.runtime.base import SimulationRuntime +from motrix_deploy.runtime.config import SensorBindings, SimulationRuntimeConfig +from motrix_deploy.runtime.factory import create_simulation_runtime +from motrix_env_core.config.scene import SceneCfg +from motrix_env_core.config.sim import SimCfg + + +def test_simulation_factory_uses_installed_plugin_and_public_config(monkeypatch, manifest_factory): + spec = manifest_factory().robot + + class Runtime(SimulationRuntime): + def __init__(self): + super().__init__() + self._robot = FakeRobotInterface(spec) + + @property + def robot(self): + return self._robot + + def run(self, control, *, steps=None): + raise NotImplementedError + + calls = [] + + def factory(config): + calls.append(config) + assert config.scene.objs.robot is robot_cfg + return Runtime() + + entry = SimpleNamespace(name="test", value="test:factory", load=lambda: factory) + monkeypatch.setattr(runtime_factory, "_runtime_entry_points", lambda: (entry,)) + robot_cfg = object() + config = SimulationRuntimeConfig( + scene=SceneCfg(objs=SimpleNamespace(robot=robot_cfg)), + ) + + runtime = create_simulation_runtime("test", config) + + assert isinstance(runtime, SimulationRuntime) + assert calls == [config] + assert isinstance(runtime.robot, FakeRobotInterface) + assert runtime.robot.spec is spec + with runtime: + assert runtime.opened + assert not runtime.opened + + +def test_simulation_config_uses_independent_typed_sensor_defaults(): + first = SimulationRuntimeConfig(scene=SceneCfg()) + second = SimulationRuntimeConfig(scene=SceneCfg()) + + assert isinstance(first.sensor_bindings, SensorBindings) + assert first.sensor_bindings is not second.sensor_bindings + assert asdict(first.sensor_bindings) == { + "base_angular_velocity": None, + "base_linear_acceleration": None, + "base_linear_velocity": None, + } + with pytest.raises(FrozenInstanceError): + first.sensor_bindings.base_angular_velocity = "gyro" + + +@pytest.mark.parametrize( + "field_name", + ["base_angular_velocity", "base_linear_acceleration", "base_linear_velocity"], +) +@pytest.mark.parametrize("invalid_name", ["", " ", "\t\n", 1, True, b"sensor", ["sensor"]]) +def test_sensor_bindings_reject_invalid_sensor_names(field_name, invalid_name): + expected_error = ValueError if isinstance(invalid_name, str) else TypeError + with pytest.raises(expected_error, match=field_name): + SensorBindings(**{field_name: invalid_name}) + + +def test_sensor_bindings_reject_unknown_semantics(): + with pytest.raises(TypeError, match="base_orientation"): + SensorBindings(**{"base_orientation": "orientation"}) + + +def test_sensor_bindings_validate_plain_dict_without_changing_sensor_names(): + # YAML parsing produces a plain dictionary; validation belongs at that boundary. + bindings = SensorBindings( + **{ + "base_angular_velocity": " gyro-local ", + "base_linear_acceleration": "robot/imu.accel", + "base_linear_velocity": "robot-velocity", + } + ) + config = SimulationRuntimeConfig(scene=SceneCfg(), sensor_bindings=bindings) + + assert config.sensor_bindings is bindings + assert bindings.base_angular_velocity == " gyro-local " + assert bindings.base_linear_acceleration == "robot/imu.accel" + assert bindings.base_linear_velocity == "robot-velocity" + + +@pytest.mark.parametrize( + "values", + [{}, {"base_linear_velocity": None}, {"base_linear_velocity": "velocity"}], +) +def test_sensor_bindings_allow_partial_and_none_bindings(values): + bindings = SensorBindings(**values) + + assert bindings.base_angular_velocity is None + assert bindings.base_linear_acceleration is None + assert bindings.base_linear_velocity == values.get("base_linear_velocity") + + +def test_sensor_bindings_are_available_on_public_runtime_facade(): + from motrix_deploy.runtime import SensorBindings as PublicSensorBindings + + assert PublicSensorBindings is SensorBindings + + +def test_simulation_config_uses_independent_canonical_physics_defaults(): + first = SimulationRuntimeConfig(scene=SceneCfg()) + second = SimulationRuntimeConfig(scene=SceneCfg()) + + assert isinstance(first.physics, SimCfg) + # Deployment's timestep and solver defaults are a preserved runtime contract. + assert first.physics.dt == 0.002 + assert first.physics.solver_iterations == 100 + assert first.physics is not second.physics + first.physics.dt = 0.01 + assert second.physics.dt == 0.002 + + +@pytest.mark.parametrize("dt", [0.0, -0.1, np.nan, np.inf, -np.inf, True, False]) +def test_simulation_config_rejects_invalid_timestep_at_boundary(dt): + physics = SimCfg(dt=dt) + with pytest.raises(ValueError, match=r"sim\.dt must be positive and finite"): + SimulationRuntimeConfig(scene=SceneCfg(), physics=physics) + + +@pytest.mark.parametrize("iterations", [0, -1, 1.5, 1.0, True, False, "100"]) +def test_simulation_config_rejects_invalid_solver_iterations_at_boundary(iterations): + with pytest.raises(ValueError, match=r"sim\.solver_iterations must be a positive integer"): + SimulationRuntimeConfig(scene=SceneCfg(), physics=SimCfg(solver_iterations=iterations)) + + +@pytest.mark.parametrize("tolerance", [0.0, -0.1, np.nan, np.inf]) +def test_simulation_config_rejects_invalid_solver_tolerance_at_boundary(tolerance): + with pytest.raises(ValueError, match=r"sim\.solver_tolerance must be positive and finite"): + SimulationRuntimeConfig(scene=SceneCfg(), physics=SimCfg(solver_tolerance=tolerance)) + + +def test_simulation_config_preserves_optional_backend_physics_settings(): + physics = SimCfg(solver_iterations=None, solver_tolerance=None, gravity=None) + config = SimulationRuntimeConfig(scene=SceneCfg(), physics=physics) + + assert config.physics is physics + assert config.physics.solver_iterations is None + assert config.physics.solver_tolerance is None + assert config.physics.gravity is None diff --git a/motrix_deploy/tests/test_task_plugins.py b/motrix_deploy/tests/test_task_plugins.py new file mode 100644 index 00000000..42cffe63 --- /dev/null +++ b/motrix_deploy/tests/test_task_plugins.py @@ -0,0 +1,67 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Deployment task plugin discovery and loading boundary.""" + +from types import SimpleNamespace +from unittest.mock import Mock + +import pytest +from task_specs import TestDeployTask as FixtureDeployTask + +from motrix_deploy import task as task_module +from motrix_deploy.task import TASK_ENTRY_POINT_GROUP, available_tasks, create_task + + +def _install(monkeypatch, entries): + def entry_points(*, group): + assert group == TASK_ENTRY_POINT_GROUP + return entries + + monkeypatch.setattr(task_module.metadata, "entry_points", entry_points) + + +def test_listing_is_sorted_and_lazy(monkeypatch): + entries = [SimpleNamespace(name=name, load=Mock()) for name in ("other/v1", "test/v1")] + _install(monkeypatch, entries) + assert available_tasks() == ("other/v1", "test/v1") + for entry in entries: + entry.load.assert_not_called() + + +def test_selected_class_receives_artifact_contract(monkeypatch, manifest_factory): + manifest = manifest_factory() + selected = SimpleNamespace(name=manifest.task.name, load=Mock(return_value=FixtureDeployTask)) + other = SimpleNamespace(name="other/v1", load=Mock()) + _install(monkeypatch, [other, selected]) + task = create_task(manifest.task, manifest.robot) + assert isinstance(task, FixtureDeployTask) + assert task.spec is manifest.task + assert task.robot is manifest.robot + selected.load.assert_called_once_with() + other.load.assert_not_called() + + +def test_missing_plugin_reports_available_names(monkeypatch, manifest_factory): + manifest = manifest_factory() + _install(monkeypatch, [SimpleNamespace(name="other/v1", load=Mock())]) + with pytest.raises(ValueError, match="test/v1.*available tasks: other/v1"): + create_task(manifest.task, manifest.robot) + + +def test_duplicate_plugins_fail_before_loading(monkeypatch, manifest_factory): + manifest = manifest_factory() + entries = [SimpleNamespace(name=manifest.task.name, load=Mock()) for _ in range(2)] + _install(monkeypatch, entries) + with pytest.raises(ValueError, match="Multiple deployment task plugins"): + create_task(manifest.task, manifest.robot) + for entry in entries: + entry.load.assert_not_called() + + +@pytest.mark.parametrize("factory", [object(), dict, lambda spec, robot: object()]) +def test_invalid_plugin_class_contract(monkeypatch, manifest_factory, factory): + manifest = manifest_factory() + _install(monkeypatch, [SimpleNamespace(name=manifest.task.name, load=Mock(return_value=factory))]) + with pytest.raises(TypeError, match="Deployment task plugin test/v1"): + create_task(manifest.task, manifest.robot) diff --git a/motrix_deploy/tests/test_task_spec_codec.py b/motrix_deploy/tests/test_task_spec_codec.py new file mode 100644 index 00000000..49d6c0b3 --- /dev/null +++ b/motrix_deploy/tests/test_task_spec_codec.py @@ -0,0 +1,238 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Typed task configuration codec and installed-plugin boundary.""" + +import json +from types import SimpleNamespace +from typing import ClassVar +from unittest.mock import Mock + +import pytest +from omegaconf import OmegaConf +from pydantic import BaseModel, Field, field_validator +from pydantic import ValidationError as PydanticValidationError +from task_specs import TestDeployTask as FixtureDeployTask + +from motrix_deploy import task as task_module +from motrix_deploy.artifact.schema import TaskSpec +from motrix_deploy.errors import ValidationError + + +class NumericSpec(TaskSpec): + task_name: ClassVar[str] = "numeric/v1" + gain: float + values: list[float] + bounds: list[list[float]] + threshold: float | None = None + enabled: bool = False + count: int = 1 + label: str = "literal" + + @field_validator("gain") + @classmethod + def positive_gain(cls, value: float) -> float: + if value <= 0: + raise ValueError("gain must be positive") + return value + + +class NumericTask(FixtureDeployTask): + spec_type = NumericSpec + + +@pytest.fixture +def codec_entries(monkeypatch): + selected = SimpleNamespace(name="numeric/v1", load=Mock(return_value=NumericTask)) + other = SimpleNamespace(name="other/v1", load=Mock()) + entries = [selected, other] + + def entry_points(*, group): + assert group == task_module.TASK_ENTRY_POINT_GROUP + return entries + + monkeypatch.setattr(task_module.metadata, "entry_points", entry_points) + return entries + + +@pytest.fixture +def encoded(): + return NumericSpec(gain=2.0, values=[1.0, 2.0], bounds=[[0.0, 1.0]]).to_dict() + + +def test_roundtrip_frozen_concrete_spec(codec_entries, encoded): + parsed = TaskSpec.from_dict(encoded) + assert type(parsed) is NumericSpec + assert parsed.to_dict() == encoded + codec_entries[0].load.assert_called_once_with() + codec_entries[1].load.assert_not_called() + + +@pytest.mark.parametrize( + ("field", "value", "path"), + [ + ("gain", True, "gain"), + ("gain", "2.0", "gain"), + ("gain", "${oc.env:HOME}", "gain"), + ("values", [True], "values.0"), + ("values", ["1.0"], "values.0"), + ("values", (1.0, 2.0), "values"), + ("values", "1,2", "values"), + ("bounds", [[False]], "bounds.0.0"), + ("bounds", [["${gain}"]], "bounds.0.0"), + ("threshold", True, "threshold"), + ("enabled", 1, "enabled"), + ("count", True, "count"), + ("count", "1", "count"), + ("enabled", "true", "enabled"), + ("label", 1, "label"), + ("gain", float("nan"), "gain"), + ("gain", float("inf"), "gain"), + ("values", [float("-inf")], "values.0"), + ], +) +def test_invalid_types_are_rejected_before_coercion(codec_entries, encoded, field, value, path): + encoded["config"][field] = value + with pytest.raises(ValidationError) as exc: + TaskSpec.from_dict(encoded) + assert exc.value.path == f"task.config.{path}" + + +def test_numeric_integers_and_optional_float(codec_entries, encoded): + encoded["config"].update(gain=2, threshold=3) + parsed = TaskSpec.from_dict(encoded) + assert parsed.gain == 2.0 + assert parsed.threshold == 3.0 + + +@pytest.mark.parametrize("kind", ["missing", "unknown", "semantic"]) +def test_config_validation_paths(codec_entries, encoded, kind): + if kind == "missing": + del encoded["config"]["gain"] + elif kind == "unknown": + encoded["config"]["extra"] = 1 + else: + encoded["config"]["gain"] = -1 + with pytest.raises(ValidationError) as exc: + TaskSpec.from_dict(encoded) + assert exc.value.path == ("task.config.extra" if kind == "unknown" else "task.config.gain") + if kind == "missing": + assert "required task field" in str(exc.value) + assert "re-export" in str(exc.value) + + +@pytest.mark.parametrize("config", [None, [], "not an object"]) +def test_config_must_be_mapping(codec_entries, encoded, config): + encoded["config"] = config + with pytest.raises(ValidationError) as exc: + TaskSpec.from_dict(encoded) + assert exc.value.path == "task.config" + + +@pytest.mark.parametrize( + "kind", ["missing", "duplicate", "unavailable", "invalid", "missing_spec", "invalid_spec", "wrong_name"] +) +def test_spec_plugin_failures(codec_entries, encoded, kind): + selected = codec_entries[0] + if kind == "missing": + codec_entries.remove(selected) + elif kind == "duplicate": + codec_entries.append(SimpleNamespace(name="numeric/v1", load=Mock())) + elif kind == "unavailable": + selected.load.side_effect = ImportError("plugin dependency unavailable") + elif kind == "invalid": + selected.load.return_value = dict + elif kind == "missing_spec": + selected.load.return_value = type("MissingSpecTask", (NumericTask,), {"spec_type": None}) + elif kind == "invalid_spec": + selected.load.return_value = type("InvalidSpecTask", (NumericTask,), {"spec_type": dict}) + else: + wrong_spec = type("WrongName", (NumericSpec,), {"task_name": "wrong/v1"}) + selected.load.return_value = type("WrongTask", (NumericTask,), {"spec_type": wrong_spec}) + with pytest.raises(ValidationError) as exc: + TaskSpec.from_dict(encoded) + assert exc.value.path == "task.name" + if kind == "missing": + codec_entries[0].load.assert_not_called() + if kind == "duplicate": + selected.load.assert_not_called() + + +def test_artifact_cannot_select_arbitrary_import(codec_entries, encoded): + encoded["name"] = "os:system" + with pytest.raises(ValidationError) as exc: + TaskSpec.from_dict(encoded) + assert exc.value.path == "task.name" + for entry in codec_entries: + entry.load.assert_not_called() + + +def test_taskspec_model_contract(): + spec = NumericSpec(gain=2.0, values=[1.0], bounds=[[0.0, 1.0]]) + assert isinstance(spec, BaseModel) + assert spec.name == "numeric/v1" + assert "name" not in spec.model_dump() + assert "task_name" not in spec.model_dump() + with pytest.raises(PydanticValidationError) as exc: + spec.gain = 3.0 + assert exc.value.errors()[0]["type"] == "frozen_instance" + + +@pytest.mark.parametrize("mutation", ["nested_type", "nested_nonfinite", "semantic"]) +def test_serialization_revalidates_mutable_values(mutation): + spec = NumericSpec(gain=2.0, values=[1.0], bounds=[[0.0, 1.0]]) + if mutation == "nested_type": + spec.bounds[0][0] = True + path = "task.config.bounds.0.0" + elif mutation == "nested_nonfinite": + spec.values.append(float("nan")) + path = "task.config.values.1" + else: + # model_construct is an explicit escape hatch; serialization still + # establishes the public artifact contract for such instances. + spec = NumericSpec.model_construct(gain=-1.0, values=[], bounds=[]) + path = "task.config.gain" + with pytest.raises(ValidationError) as exc: + spec.to_dict() + assert exc.value.path == path + + +def test_invalid_plugin_defaults_are_validated(codec_entries, encoded): + class InvalidDefaultSpec(NumericSpec): + tolerance: float = Field(default=-1.0, gt=0) + + codec_entries[0].load.return_value = type("InvalidDefaultTask", (NumericTask,), {"spec_type": InvalidDefaultSpec}) + with pytest.raises(ValidationError) as exc: + TaskSpec.from_dict(encoded) + assert exc.value.path == "task.config.tolerance" + + +def test_serialization_uses_json_primitives(codec_entries): + spec = NumericSpec(gain=2, values=[1, 2], bounds=[[0, 1]], enabled=True) + wire = spec.to_dict() + json_wire = json.loads(json.dumps(wire, allow_nan=False)) + assert json_wire == wire + assert json_wire["config"]["bounds"] == [[0.0, 1.0]] + assert TaskSpec.from_dict(json_wire) == spec + + +def test_literal_interpolation_string_is_not_resolved(codec_entries, encoded): + encoded["config"]["label"] = "${oc.env:HOME}" + parsed = TaskSpec.from_dict(encoded) + assert parsed.label == "${oc.env:HOME}" + assert parsed.to_dict() == encoded + + +def test_yaml_composition_resolves_before_model_validation(tmp_path): + source = tmp_path / "task.yaml" + source.write_text( + "gain: 2.0\nvalues: [1.0, 2.0]\nbounds: [[0.0, 1.0]]\nthreshold: ${gain}\n", + encoding="utf-8", + ) + composed = OmegaConf.merge(OmegaConf.load(source), OmegaConf.create({"gain": 3.0, "enabled": True})) + config = OmegaConf.to_container(composed, resolve=True, throw_on_missing=True) + spec = NumericSpec.model_validate(config) + assert spec.gain == 3.0 + assert spec.threshold == spec.gain + assert spec.enabled is True + assert spec.to_dict()["config"] == spec.model_dump() diff --git a/motrix_deploy_mujoco/README.md b/motrix_deploy_mujoco/README.md index f7953ca8..7b9e2040 100644 --- a/motrix_deploy_mujoco/README.md +++ b/motrix_deploy_mujoco/README.md @@ -1,17 +1,22 @@ # motrix-deploy-mujoco -`motrix-deploy-mujoco` is the MuJoCo backend plugin for `motrix-deploy`. It discovers a registered MotrixLab -environment configuration, builds its `SceneCfg` through `MuJoCoSceneCompiler` with deployment-owned MuJoCo timestep and -solver settings, converts the policy-training position actuators to deployment torque motors, and implements the -`RobotInterface` lifecycle. +`motrix-deploy-mujoco` runs deployment policies in MuJoCo. It compiles a `SceneCfg`, exposes the scene's robot through +`RobotInterface`, and supplies physics stepping, joint control, and an optional keyboard viewer. -The plugin is advertised through the `motrix_deploy.backends` Python entry-point group and is loaded only when a -deployment recipe selects `backend.name=mujoco`. +With `motrix_deploy_tasks` installed and a matching exported artifact: -The workspace provides Go2 recipes for both registered scenes: +```bash +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy runtime.viewer=false duration_s=2.0 +``` -- `configs/deploy/sim2sim/go2_walk_sim2sim.yaml` selects `go2-walk-rough` and remains the CLI default. -- `configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml` selects `go2-walk-flat` explicitly. +Omit the headless overrides for interactive control. The Go2 flat and rough recipes are installed with the task plugin, +not read from workspace config paths. Task defaults select the `sim` Hydra runtime group. Simulation settings are +`runtime.viewer` and `runtime.realtime`; `runtime.realtime=null` follows `runtime.viewer`. MuJoCo options live directly +under `runtime`, including `runtime.backend=mujoco`, `runtime.deploy_env_id`, `runtime.robot_id`, and `runtime.physics`. +`runtime.backend` is a scalar string selector, not a nested configuration mapping or Hydra group. +`duration_s` budgets `ceil(duration_s / control_period_s)` control ticks, not wall-clock time. -Both use a `0.002` second MuJoCo timestep and `100` solver iterations. The flat recipe resets the base at `z=0.331` to keep -the default feet clear of the plane. +Programmatic callers use `create_simulation_runtime("mujoco", SimulationRuntimeConfig(...))`, assemble a `ControlSession` +with `runtime.robot`, and call `runtime.bind_control_session(control)` before running. The runtime advances physics between +control ticks; the control session handles task observations, policy inference, and robot commands. See +[`examples/deploy_to_sim.py`](../examples/deploy_to_sim.py) for a complete headless or viewer example. diff --git a/motrix_deploy_mujoco/pyproject.toml b/motrix_deploy_mujoco/pyproject.toml index c39d8927..28e79efa 100644 --- a/motrix_deploy_mujoco/pyproject.toml +++ b/motrix_deploy_mujoco/pyproject.toml @@ -13,9 +13,9 @@ license = "Apache-2.0" dependencies = [ "motrix-deploy", "motrix-env-mujoco", - "motrix-envs", + "hydra-core>=1.3", "numpy>=1.26", ] [project.entry-points."motrix_deploy.backends"] -mujoco = "motrix_deploy_mujoco.plugin:create_backend" +mujoco = "motrix_deploy_mujoco.plugin:create_runtime" diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/__init__.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/__init__.py index 9b34e2d3..674b4492 100644 --- a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/__init__.py +++ b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/__init__.py @@ -3,17 +3,16 @@ """SceneCfg-backed MuJoCo deployment backend plugin.""" -from motrix_deploy_mujoco.config import MujocoBackendConfig -from motrix_deploy_mujoco.interface import MujocoRobotInterface, wxyz_to_xyzw, xyzw_to_wxyz -from motrix_deploy_mujoco.plugin import create_backend +from motrix_deploy_mujoco.plugin import create_runtime +from motrix_deploy_mujoco.robot import wxyz_to_xyzw, xyzw_to_wxyz +from motrix_deploy_mujoco.runtime import MujocoRuntime from motrix_deploy_mujoco.viewer import MujocoGlfwViewer, MujocoKeyboardDevice __all__ = [ - "MujocoBackendConfig", "MujocoGlfwViewer", "MujocoKeyboardDevice", - "MujocoRobotInterface", - "create_backend", + "MujocoRuntime", + "create_runtime", "wxyz_to_xyzw", "xyzw_to_wxyz", ] diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/config.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/config.py deleted file mode 100644 index 103c2496..00000000 --- a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/config.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright Motphys Technology Co., Ltd. 2025, 2026 -# SPDX-License-Identifier: Apache-2.0 - -"""MuJoCo deployment backend configuration schema.""" - -from collections.abc import Mapping -from dataclasses import dataclass -from typing import Any - -import numpy as np - -from motrix_deploy.errors import ValidationError - - -@dataclass -class MujocoBackendConfig: - """Application-level scene selection and MuJoCo state mapping.""" - - scene: str - scene_mode: str - sim_dt: float - solver_iterations: int - base_position: tuple[float, float, float] - base_orientation_xyzw: tuple[float, float, float, float] - base_body_name: str - imu_site_name: str - gyro_sensor_name: str - accelerometer_sensor_name: str - global_linear_velocity_sensor_name: str - fall_height_m: float - fall_up_z: float - - def __post_init__(self) -> None: - if not self.scene: - raise ValidationError("backend.scene", "a registered environment name", self.scene) - if self.scene_mode not in ("train", "play"): - raise ValidationError("backend.scene_mode", "'train' or 'play'", self.scene_mode) - if not np.isfinite(self.sim_dt) or self.sim_dt <= 0: - raise ValidationError("backend.sim_dt", "a positive finite value", self.sim_dt) - if not isinstance(self.solver_iterations, int) or isinstance(self.solver_iterations, bool): - raise ValidationError("backend.solver_iterations", "a positive integer", self.solver_iterations) - if self.solver_iterations <= 0: - raise ValidationError("backend.solver_iterations", "a positive integer", self.solver_iterations) - for name in ( - "base_body_name", - "imu_site_name", - "gyro_sensor_name", - "accelerometer_sensor_name", - "global_linear_velocity_sensor_name", - ): - if not getattr(self, name): - raise ValidationError(f"backend.{name}", "a non-empty string", getattr(self, name)) - if not np.isfinite(self.fall_height_m) or self.fall_height_m <= 0: - raise ValidationError("backend.fall_height_m", "a positive finite value", self.fall_height_m) - if not np.isfinite(self.fall_up_z) or not -1.0 <= self.fall_up_z <= 1.0: - raise ValidationError("backend.fall_up_z", "a finite value in [-1, 1]", self.fall_up_z) - if len(self.base_position) != 3 or not np.isfinite(self.base_position).all(): - raise ValidationError("backend.base_position", "three finite values", self.base_position) - if len(self.base_orientation_xyzw) != 4 or not np.isfinite(self.base_orientation_xyzw).all(): - raise ValidationError("backend.base_orientation_xyzw", "four finite values", self.base_orientation_xyzw) - quaternion_norm = np.linalg.norm(self.base_orientation_xyzw) - if not np.isclose(quaternion_norm, 1.0, atol=1e-6): - raise ValidationError("backend.base_orientation_xyzw", "a unit quaternion", quaternion_norm) - - @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "MujocoBackendConfig": - """Parse the backend section of a resolved deployment configuration.""" - values: dict[str, Any] = dict(value) - values.pop("name", None) - expected_fields = set(cls.__dataclass_fields__) - if set(values) != expected_fields: - raise ValidationError( - "backend", - f"fields {sorted(expected_fields)}", - f"missing={sorted(expected_fields - set(values))}, unknown={sorted(set(values) - expected_fields)}", - ) - values["base_position"] = tuple(values["base_position"]) - values["base_orientation_xyzw"] = tuple(values["base_orientation_xyzw"]) - return cls(**values) diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/interface.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/interface.py deleted file mode 100644 index c720e19d..00000000 --- a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/interface.py +++ /dev/null @@ -1,374 +0,0 @@ -# Copyright Motphys Technology Co., Ltd. 2025, 2026 -# SPDX-License-Identifier: Apache-2.0 - -"""MuJoCo state/command adapter with no policy tensor logic.""" - -import logging -import time -from collections.abc import Callable -from typing import Any - -import numpy as np - -import motrix_envs # noqa: F401 registers built-in environment configurations -from motrix_deploy.backend import RobotInterface -from motrix_deploy.contracts import HealthStatus, RobotCapabilities, RobotCommand, RobotSpec, RobotState, float32_array -from motrix_deploy.errors import ValidationError -from motrix_deploy_mujoco.config import MujocoBackendConfig -from motrix_deploy_mujoco.transform import convert_position_actuators_to_motors -from motrix_deploy_mujoco.viewer import MujocoGlfwViewer -from motrix_env_core import SimCfg, registry -from motrix_env_core.config.scene import SystemCameraCfg -from motrix_env_core.input import KeyboardDevice -from motrix_env_mujoco.compiler import MuJoCoSceneCompiler - -logger = logging.getLogger(__name__) - - -def wxyz_to_xyzw(quaternion: object) -> np.ndarray: - """Convert MuJoCo's explicit ``wxyz`` order to the public ``xyzw`` order.""" - return np.asarray(quaternion, dtype=np.float32)[[1, 2, 3, 0]] - - -def xyzw_to_wxyz(quaternion: object) -> np.ndarray: - """Convert the public ``xyzw`` order to MuJoCo's explicit ``wxyz`` order.""" - return np.asarray(quaternion)[[3, 0, 1, 2]] - - -class MujocoRobotInterface(RobotInterface): - """Build a MuJoCo scene and apply canonical hybrid-PD commands as torque.""" - - def __init__( - self, - config: MujocoBackendConfig, - *, - control_period_s: float, - render: bool = False, - viewer_factory: Callable[[Any, SystemCameraCfg], Any] = MujocoGlfwViewer, - ) -> None: - try: - import mujoco - except ImportError as error: - raise RuntimeError("MuJoCo backend support requires the 'motrix-deploy-mujoco' package") from error - env_cfg = registry.make_env_config(config.scene, mode=config.scene_mode) - if not np.isclose(control_period_s, env_cfg.ctrl_dt, atol=1e-12, rtol=0.0): - raise ValidationError("backend.scene.ctrl_dt", str(control_period_s), env_cfg.ctrl_dt) - ratio = control_period_s / config.sim_dt - if not np.isclose(ratio, round(ratio), atol=1e-9) or round(ratio) < 1: - raise ValidationError( - "backend.control_period_s", - "an integer multiple of scene SimCfg.dt", - control_period_s, - ) - self.config = config - self._mj = mujoco - self._scene = env_cfg.scene - self._sim = SimCfg(dt=config.sim_dt, solver_iterations=config.solver_iterations) - self._control_period_s = control_period_s - self._physics_substeps = round(ratio) - self._render = render - self._model: Any = None - self._data: Any = None - self._viewer = viewer_factory(mujoco, self._scene.system_camera) if render else None - self._spec: RobotSpec | None = None - self._capabilities: RobotCapabilities | None = None - self._joint_qpos_indices = np.empty(0, dtype=np.int64) - self._joint_qvel_indices = np.empty(0, dtype=np.int64) - self._actuator_indices = np.empty(0, dtype=np.int64) - self._base_body_id = -1 - self._base_qpos_address = -1 - self._last_communication_ns = 0 - self._health_reason = "backend is not open" - self._opened = False - self._closed = False - - @property - def capabilities(self) -> RobotCapabilities: - if self._capabilities is None: - raise RuntimeError("MuJoCo capabilities are available after open()") - return self._capabilities - - def open(self, spec: RobotSpec) -> None: - if self._opened and not self._closed: - raise RuntimeError("MuJoCo backend is already open") - model = self._build_model(spec) - data = self._mj.MjData(model) - self._model = model - self._data = data - self._spec = spec - self._bind_base() - self._bind_joints_and_actuators(spec) - self._validate_torque_actuator_contract(spec) - self._validate_sensors() - self._mj.mj_resetData(model, data) - qpos = data.qpos - qpos[self._base_qpos_address : self._base_qpos_address + 3] = self.config.base_position - qpos[self._base_qpos_address + 3 : self._base_qpos_address + 7] = xyzw_to_wxyz( - self.config.base_orientation_xyzw - ) - qpos[self._joint_qpos_indices] = spec.default_joint_position - data.qvel.fill(0.0) - data.ctrl[self._actuator_indices] = 0.0 - self._mj.mj_forward(model, data) - self._capabilities = RobotCapabilities( - control_modes=("joint_pd",), - state_fields=frozenset( - { - "joint_position", - "joint_velocity", - "base_orientation_xyzw", - "base_angular_velocity", - "base_linear_acceleration", - "base_position", - "base_linear_velocity", - } - ), - supports_rendering=True, - max_command_rate_hz=1.0 / self._control_period_s, - stop_semantics="zero_torque", - ) - self._opened = True - self._closed = False - self._last_communication_ns = time.monotonic_ns() - self._health_reason = "" - if self._render: - assert self._viewer is not None - self._viewer.open(model, data) - self._sync_viewer() - - def get_keyboard_device(self) -> KeyboardDevice: - """Return keyboard events from the deployment viewer window.""" - if self._viewer is None: - raise RuntimeError("MuJoCo keyboard input requires viewer=true") - return self._viewer.keyboard_device - - def read_state(self, timeout_s: float) -> RobotState: - self._require_open() - if timeout_s <= 0: - raise TimeoutError(f"MuJoCo state timeout must be positive, got {timeout_s}") - self._check_viewer_running() - self._last_communication_ns = time.monotonic_ns() - return self._state() - - def write_command(self, command: RobotCommand) -> None: - self._require_open() - assert self._model is not None and self._data is not None and self._spec is not None - joint_count = self._spec.joint_count - for field_name in ("joint_position", "joint_velocity", "feedforward_torque", "kp", "kd"): - value = getattr(command, field_name) - if value.shape != (joint_count,) or not np.isfinite(value).all(): - raise ValidationError(f"command.{field_name}", f"finite shape ({joint_count},)", value) - lower = self._spec.position_lower - upper = self._spec.position_upper - if np.any(command.joint_position < lower) or np.any(command.joint_position > upper): - raise ValidationError("command.joint_position", "inside RobotSpec position range", command.joint_position) - for _ in range(self._physics_substeps): - position = self._data.qpos[self._joint_qpos_indices] - velocity = self._data.qvel[self._joint_qvel_indices] - torque = ( - command.kp * (command.joint_position - position) - + command.kd * (command.joint_velocity - velocity) - + command.feedforward_torque - ) - self._data.ctrl[self._actuator_indices] = np.clip( - torque, - -self._spec.torque_limit, - self._spec.torque_limit, - ) - self._mj.mj_step(self._model, self._data) - self._last_communication_ns = time.monotonic_ns() - self._update_fall_health() - self._sync_viewer() - - def health(self) -> HealthStatus: - return HealthStatus( - healthy=self._opened and not self._closed and not self._health_reason, - reason=self._health_reason, - last_successful_communication_ns=self._last_communication_ns, - ) - - def stop(self) -> None: - if not self._opened or self._closed or self._data is None: - return - if self._joint_qpos_indices.size: - self._data.ctrl[self._actuator_indices] = 0.0 - - def close(self) -> None: - if self._closed: - return - viewer = self._viewer - try: - if viewer is not None: - viewer.close() - deadline = time.monotonic() + 2.0 - while viewer.is_running() and time.monotonic() < deadline: - time.sleep(0.001) - if viewer.is_running(): - raise RuntimeError("MuJoCo viewer did not stop within 2 seconds") - finally: - self._data = None - self._model = None - self._closed = True - - def _sync_viewer(self) -> None: - if self._viewer is None: - return - self._check_viewer_running() - self._viewer.sync() - - def _check_viewer_running(self) -> None: - if self._viewer is None: - return - if not self._viewer.is_running(): - raise KeyboardInterrupt("MuJoCo viewer was closed") - - def _bind_base(self) -> None: - assert self._model is not None - body_id = self._name_id(self._mj.mjtObj.mjOBJ_BODY, self.config.base_body_name, "base body") - joint_address = int(self._model.body_jntadr[body_id]) - if joint_address < 0 or self._model.jnt_type[joint_address] != self._mj.mjtJoint.mjJNT_FREE: - raise ValidationError("backend.base_body_name", "a body with a free joint", self.config.base_body_name) - self._base_body_id = body_id - self._base_qpos_address = int(self._model.jnt_qposadr[joint_address]) - - def _build_model(self, robot: RobotSpec) -> Any: - model_spec = MuJoCoSceneCompiler().create_spec(self._scene, self._sim) - source_model = model_spec.compile() - self._model = source_model - self._bind_joints_and_actuators(robot) - self._validate_position_actuator_contract(robot) - - actuator_names = convert_position_actuators_to_motors( - self._mj, - model_spec, - source_model, - self._actuator_indices, - robot, - ) - logger.info( - "Converting %d actuators from position servos to torque motors with MjSpec: %s", - len(actuator_names), - ", ".join(actuator_names), - ) - model = model_spec.compile() - logger.info("Built MuJoCo deployment model from SceneCfg %s", self.config.scene) - return model - - def _bind_joints_and_actuators(self, spec: RobotSpec) -> None: - assert self._model is not None - qpos_indices: list[int] = [] - qvel_indices: list[int] = [] - actuator_indices: list[int] = [] - for name in spec.joint_names: - joint_id = self._name_id(self._mj.mjtObj.mjOBJ_JOINT, name, "joint") - if self._model.jnt_type[joint_id] != self._mj.mjtJoint.mjJNT_HINGE: - raise ValidationError(f"backend.joints.{name}", "a one-DoF hinge joint", self._model.jnt_type[joint_id]) - matches = np.flatnonzero(self._model.actuator_trnid[:, 0] == joint_id) - if matches.size != 1: - raise ValidationError(f"backend.actuators.{name}", "exactly one actuator", matches.tolist()) - qpos_indices.append(int(self._model.jnt_qposadr[joint_id])) - qvel_indices.append(int(self._model.jnt_dofadr[joint_id])) - actuator_indices.append(int(matches[0])) - self._joint_qpos_indices = np.asarray(qpos_indices, dtype=np.int64) - self._joint_qvel_indices = np.asarray(qvel_indices, dtype=np.int64) - self._actuator_indices = np.asarray(actuator_indices, dtype=np.int64) - - def _validate_position_actuator_contract(self, spec: RobotSpec) -> None: - assert self._model is not None - indices = self._actuator_indices - ctrl_range = self._model.actuator_ctrlrange[indices] - force_range = self._model.actuator_forcerange[indices] - kp = self._model.actuator_gainprm[indices, 0] - stiffness = -self._model.actuator_biasprm[indices, 1] - kd = -self._model.actuator_biasprm[indices, 2] - checks = ( - ("ctrl_lower", ctrl_range[:, 0], spec.position_lower), - ("ctrl_upper", ctrl_range[:, 1], spec.position_upper), - ("force_lower", force_range[:, 0], -spec.torque_limit), - ("force_upper", force_range[:, 1], spec.torque_limit), - ) - for name, actual, expected in checks: - if not np.allclose(actual, expected, atol=1e-6, rtol=0.0): - raise ValidationError(f"backend.actuators.{name}", expected.tolist(), actual.tolist()) - if not np.allclose(kp, stiffness, atol=1e-7, rtol=0.0): - raise ValidationError("backend.actuators.kp", "gain equals stiffness", (kp.tolist(), stiffness.tolist())) - if np.any(kp < 0) or np.any(kd < 0): - raise ValidationError("backend.actuators.gains", "non-negative kp/kd", (kp.tolist(), kd.tolist())) - - def _validate_torque_actuator_contract(self, spec: RobotSpec) -> None: - assert self._model is not None - indices = self._actuator_indices - checks = ( - ("ctrl_lower", self._model.actuator_ctrlrange[indices, 0], -spec.torque_limit), - ("ctrl_upper", self._model.actuator_ctrlrange[indices, 1], spec.torque_limit), - ("force_lower", self._model.actuator_forcerange[indices, 0], -spec.torque_limit), - ("force_upper", self._model.actuator_forcerange[indices, 1], spec.torque_limit), - ("gain", self._model.actuator_gainprm[indices, 0], np.ones(spec.joint_count)), - ("bias", self._model.actuator_biastype[indices], np.zeros(spec.joint_count)), - ) - for name, actual, expected in checks: - if not np.allclose(actual, expected, atol=1e-6, rtol=0.0): - raise ValidationError(f"backend.torque_actuators.{name}", expected.tolist(), actual.tolist()) - - def _validate_sensors(self) -> None: - assert self._model is not None - self._name_id(self._mj.mjtObj.mjOBJ_SITE, self.config.imu_site_name, "IMU site") - for field_name, expected_dimension in ( - ("gyro_sensor_name", 3), - ("accelerometer_sensor_name", 3), - ("global_linear_velocity_sensor_name", 3), - ): - sensor_name = getattr(self.config, field_name) - sensor_id = self._name_id(self._mj.mjtObj.mjOBJ_SENSOR, sensor_name, "sensor") - actual_dimension = int(self._model.sensor_dim[sensor_id]) - if actual_dimension != expected_dimension: - raise ValidationError(f"backend.{field_name}", f"a {expected_dimension}D sensor", actual_dimension) - - def _state(self) -> RobotState: - assert self._model is not None and self._data is not None and self._spec is not None - orientation_xyzw = wxyz_to_xyzw(self._data.xquat[self._base_body_id]) - return RobotState( - sample_time_ns=round(self._data.time * 1e9), - receive_time_ns=self._last_communication_ns, - joint_position=np.asarray(self._data.qpos[self._joint_qpos_indices], dtype=np.float32), - joint_velocity=np.asarray(self._data.qvel[self._joint_qvel_indices], dtype=np.float32), - base_orientation_xyzw=orientation_xyzw, - base_angular_velocity=self._sensor(self.config.gyro_sensor_name), - base_linear_acceleration=self._sensor(self.config.accelerometer_sensor_name), - base_position=np.asarray(self._data.xpos[self._base_body_id], dtype=np.float32), - base_linear_velocity=self._sensor(self.config.global_linear_velocity_sensor_name), - ) - - def _sensor(self, name: str) -> np.ndarray: - assert self._model is not None and self._data is not None - sensor_id = self._name_id(self._mj.mjtObj.mjOBJ_SENSOR, name, "sensor") - address = int(self._model.sensor_adr[sensor_id]) - dimension = int(self._model.sensor_dim[sensor_id]) - return float32_array( - np.asarray(self._data.sensordata[address : address + dimension], dtype=np.float32), - path=f"backend.sensors.{name}", - shape=(dimension,), - ) - - def _update_fall_health(self) -> None: - assert self._data is not None - height = self._data.xpos[self._base_body_id, 2] - rotation = self._data.xmat[self._base_body_id].reshape(3, 3) - up_z = rotation[2, 2] - if not np.isfinite(height) or not np.isfinite(up_z): - self._health_reason = f"non-finite base pose: height={height}, up_z={up_z}" - elif height < self.config.fall_height_m: - self._health_reason = f"base height {height:.6f}m is below {self.config.fall_height_m:.6f}m" - elif up_z < self.config.fall_up_z: - self._health_reason = f"base up_z {up_z:.6f} is below {self.config.fall_up_z:.6f}" - - def _name_id(self, object_type: Any, name: str, description: str) -> int: - assert self._model is not None - object_id = int(self._mj.mj_name2id(self._model, object_type, name)) - if object_id < 0: - raise ValidationError(f"backend.{description}", f"existing name {name!r}", "missing") - return object_id - - def _require_open(self) -> None: - if not self._opened or self._closed or self._model is None or self._data is None: - raise RuntimeError("MuJoCo backend is not open") diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/plugin.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/plugin.py index e45bdbb1..2e24fa34 100644 --- a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/plugin.py +++ b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/plugin.py @@ -1,23 +1,15 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 -"""Entry-point factory for the MuJoCo deployment backend.""" +"""Typed entry-point factory for the MuJoCo simulation runtime.""" -from collections.abc import Mapping -from typing import Any +from motrix_deploy.runtime.config import SimulationRuntimeConfig +from motrix_deploy_mujoco.runtime import MujocoRuntime -from motrix_deploy.backend import BackendCreateContext, RobotInterface -from motrix_deploy_mujoco.config import MujocoBackendConfig -from motrix_deploy_mujoco.interface import MujocoRobotInterface +def create_runtime(config: SimulationRuntimeConfig) -> MujocoRuntime: + """Create a simulation whose robot contract is derived from its scene.""" + return MujocoRuntime(config) -def create_backend(config: Mapping[str, Any], context: BackendCreateContext) -> RobotInterface: - """Construct the MuJoCo backend selected by a deployment recipe.""" - return MujocoRobotInterface( - MujocoBackendConfig.from_mapping(config), - control_period_s=context.control.period_s, - render=context.viewer, - ) - -__all__ = ["create_backend"] +__all__ = ["create_runtime"] diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/robot.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/robot.py new file mode 100644 index 00000000..5ea4ef33 --- /dev/null +++ b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/robot.py @@ -0,0 +1,400 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Robot I/O adapter bound to an externally owned MuJoCo simulation.""" + +from __future__ import annotations + +import logging +import time +from typing import TYPE_CHECKING, cast + +import numpy as np +from numpy.typing import ArrayLike, NDArray + +from motrix_deploy.contracts import ( + HealthStatus, + JointControlMode, + JointServoCommand, + JointTorqueCommand, + RobotCapabilities, + RobotCommand, + RobotSpec, + RobotState, +) +from motrix_deploy.errors import ValidationError +from motrix_deploy.robot.interface import RobotInterface +from motrix_deploy_mujoco.transform import convert_actuators_to_motors +from motrix_env_core.config.scene.base import RobotCfg + +if TYPE_CHECKING: + import mujoco + + from motrix_deploy_mujoco.runtime import MujocoRuntime + +logger = logging.getLogger(__name__) + + +def wxyz_to_xyzw(quaternion: ArrayLike) -> NDArray[np.float32]: + """Convert MuJoCo's explicit ``wxyz`` order to the public ``xyzw`` order.""" + return np.asarray(quaternion, dtype=np.float32)[[1, 2, 3, 0]] + + +def xyzw_to_wxyz(quaternion: ArrayLike) -> NDArray[np.generic]: + """Convert the public ``xyzw`` order to MuJoCo's ``wxyz`` order, preserving dtype.""" + return np.asarray(quaternion)[[3, 0, 1, 2]] + + +class Robot(RobotInterface): + """Robot I/O and joint control in an externally owned MuJoCo simulation.""" + + def __init__(self, runtime: MujocoRuntime, spec: RobotSpec) -> None: + self.config = runtime.config + self._runtime = runtime + self._cfg = cast(RobotCfg, self._runtime.scene.objs.robot) + self._command: RobotCommand | None = None + self._spec = spec + self._capabilities: RobotCapabilities | None = None + self._joint_qpos_indices: NDArray[np.int64] = np.empty(0, dtype=np.int64) + self._joint_qvel_indices: NDArray[np.int64] = np.empty(0, dtype=np.int64) + self._actuator_indices: NDArray[np.int64] = np.empty(0, dtype=np.int64) + self._base_body_id = -1 + self._sensor_slices: dict[str, slice] = {} + self._last_communication_ns = 0 + self._health_reason = "backend is not open" + self._opened = False + self._closed = False + + @property + def spec(self) -> RobotSpec: + return self._spec + + @property + def capabilities(self) -> RobotCapabilities: + if self._capabilities is None: + raise RuntimeError("MuJoCo capabilities are available after open()") + return self._capabilities + + def bind(self) -> None: + spec = self._spec + self._bind_base() + self._bind_joints_and_actuators(spec) + self._validate_torque_actuator_contract(spec) + self._bind_sensors() + self._runtime.reset(self._joint_qpos_indices, spec.default_joint_position) + self._command = None + self._capabilities = RobotCapabilities( + control_modes=(JointControlMode.SERVO, JointControlMode.TORQUE), + state_fields=frozenset( + { + "joint_position", + "joint_velocity", + "base_orientation_xyzw", + "base_angular_velocity", + "base_linear_acceleration", + "base_position", + "base_linear_velocity", + } + ), + privileged_state_fields=frozenset({"base_position", "base_linear_velocity"}), + supports_rendering=True, + max_command_rate_hz=1.0 / self._runtime.sim.dt, + stop_semantics="zero_torque", + ) + self._opened = True + self._closed = False + self._last_communication_ns = time.monotonic_ns() + self._health_reason = "" + + def read_state(self, timeout_s: float) -> RobotState: + self._require_open() + if timeout_s <= 0: + raise TimeoutError(f"MuJoCo state timeout must be positive, got {timeout_s}") + self._runtime.check_viewer_running() + self._last_communication_ns = time.monotonic_ns() + return self._state() + + def write_command(self, command: RobotCommand) -> None: + self._require_open() + assert self._runtime.model is not None and self._runtime.data is not None + joint_count = self._spec.joint_count + fields: tuple[str, ...] + if isinstance(command, JointServoCommand): + fields = ("joint_position", "joint_velocity", "feedforward_torque", "kp", "kd") + elif isinstance(command, JointTorqueCommand): + fields = ("torque",) + else: + raise ValidationError("command", "JointServoCommand or JointTorqueCommand", type(command).__name__) + for field_name in fields: + value = getattr(command, field_name) + if value.shape != (joint_count,) or not np.isfinite(value).all(): + raise ValidationError(f"command.{field_name}", f"finite shape ({joint_count},)", value) + if isinstance(command, JointServoCommand): + if np.any(command.joint_position < self._spec.position_lower) or np.any( + command.joint_position > self._spec.position_upper + ): + raise ValidationError( + "command.joint_position", "inside RobotSpec position range", command.joint_position + ) + if np.any(command.kp < 0) or np.any(command.kd < 0): + raise ValidationError("command.gains", "non-negative kp/kd", (command.kp, command.kd)) + torque = command.feedforward_torque + torque_field = "feedforward_torque" + else: + torque = command.torque + torque_field = "torque" + if np.any(np.abs(torque) > self._spec.torque_limit): + raise ValidationError(f"command.{torque_field}", "inside RobotSpec torque limits", torque) + + # Snapshot the command: the caller may reuse or mutate its arrays afterwards. + self._command = type(command)(**{name: getattr(command, name).copy() for name in fields}) + self._last_communication_ns = time.monotonic_ns() + + def apply_control(self) -> None: + assert self._runtime.data is not None + command = self._command + if command is None: + self._runtime.data.ctrl[self._actuator_indices] = 0.0 + return + if isinstance(command, JointTorqueCommand): + self._runtime.data.ctrl[self._actuator_indices] = command.torque + return + position = self._runtime.data.qpos[self._joint_qpos_indices] + velocity = self._runtime.data.qvel[self._joint_qvel_indices] + torque = ( + command.kp * (command.joint_position - position) + + command.kd * (command.joint_velocity - velocity) + + command.feedforward_torque + ) + self._runtime.data.ctrl[self._actuator_indices] = np.clip( + torque, -self._spec.torque_limit, self._spec.torque_limit + ) + + def health(self) -> HealthStatus: + return HealthStatus( + healthy=self._opened and not self._closed and not self._health_reason, + reason=self._health_reason, + last_successful_communication_ns=self._last_communication_ns, + ) + + def stop(self) -> None: + self._command = None + if not self._opened or self._closed or self._runtime.data is None: + return + if self._joint_qpos_indices.size: + self._runtime.data.ctrl[self._actuator_indices] = 0.0 + + def open(self) -> None: + """Check that the owning runtime has prepared and bound robot I/O.""" + self._require_open() + + def close(self) -> None: + """Detach robot I/O without closing the externally owned simulation.""" + self.stop() + self._closed = True + self._health_reason = "robot interface is closed" + + def invalidate(self) -> None: + self._command = None + self._opened = False + self._closed = True + self._health_reason = "simulation is not open" + + def _bind_base(self) -> None: + assert self._runtime.model is not None + body_name = self._cfg.resolved_base_link_name + body_id = self._name_id(self._runtime.mj.mjtObj.mjOBJ_BODY, body_name, "base body") + joint_address = int(self._runtime.model.body_jntadr[body_id]) + if joint_address < 0 or self._runtime.model.jnt_type[joint_address] != self._runtime.mj.mjtJoint.mjJNT_FREE: + raise ValidationError("backend.robot.base_link_name", "a body with a free joint", body_name) + self._base_body_id = body_id + + def prepare(self, source_model: mujoco.MjModel, model_spec: mujoco.MjSpec) -> None: + robot = self._spec + self._bind_joints_and_actuators(robot) + self._validate_actuator_transmission() + motors = source_model.actuator_biastype[self._actuator_indices] == self._runtime.mj.mjtBias.mjBIAS_NONE + if np.all(motors): + self._validate_torque_actuator_contract(robot) + joint_ids = source_model.actuator_trnid[self._actuator_indices, 0] + if not np.all(source_model.jnt_limited[joint_ids]) or not np.allclose( + source_model.jnt_range[joint_ids], + np.column_stack((robot.position_lower, robot.position_upper)), + atol=1e-6, + rtol=0.0, + ): + raise ValidationError("backend.joints.position_range", "RobotSpec position limits", "mismatch") + return + if np.any(motors): + raise ValidationError("backend.actuators", "uniform position servos or torque motors", "mixed actuators") + self._validate_position_actuator_contract(robot) + + actuator_names = convert_actuators_to_motors( + model_spec, + source_model, + self._actuator_indices, + robot, + ) + logger.info( + "Converting %d actuators from position servos to torque motors with MjSpec: %s", + len(actuator_names), + ", ".join(actuator_names), + ) + + def _bind_joints_and_actuators(self, spec: RobotSpec) -> None: + assert self._runtime.model is not None + qpos_indices: list[int] = [] + qvel_indices: list[int] = [] + actuator_indices: list[int] = [] + for name in spec.joint_names: + joint_id = self._name_id(self._runtime.mj.mjtObj.mjOBJ_JOINT, self._cfg.resolve_name(name), "joint") + if self._runtime.model.jnt_type[joint_id] != self._runtime.mj.mjtJoint.mjJNT_HINGE: + raise ValidationError( + f"backend.joints.{name}", "a one-DoF hinge joint", self._runtime.model.jnt_type[joint_id] + ) + matches = np.flatnonzero(self._runtime.model.actuator_trnid[:, 0] == joint_id) + if matches.size != 1: + raise ValidationError(f"backend.actuators.{name}", "exactly one actuator", matches.tolist()) + qpos_indices.append(int(self._runtime.model.jnt_qposadr[joint_id])) + qvel_indices.append(int(self._runtime.model.jnt_dofadr[joint_id])) + actuator_indices.append(int(matches[0])) + self._joint_qpos_indices = np.asarray(qpos_indices, dtype=np.int64) + self._joint_qvel_indices = np.asarray(qvel_indices, dtype=np.int64) + self._actuator_indices = np.asarray(actuator_indices, dtype=np.int64) + + def _validate_actuator_transmission(self) -> None: + assert self._runtime.model is not None + model = self._runtime.model + indices = self._actuator_indices + if not np.all(model.actuator_trntype[indices] == self._runtime.mj.mjtTrn.mjTRN_JOINT): + raise ValidationError("backend.actuators.transmission", "direct joint transmission", "unsupported") + gear = np.zeros((indices.size, 6)) + gear[:, 0] = 1.0 + if not np.allclose(model.actuator_gear[indices], gear): + raise ValidationError("backend.actuators.gear", "unit joint transmission", model.actuator_gear[indices]) + for field, expected in ( + ("dyntype", self._runtime.mj.mjtDyn.mjDYN_NONE), + ("gaintype", self._runtime.mj.mjtGain.mjGAIN_FIXED), + ("ctrllimited", True), + ("forcelimited", True), + ): + actual = getattr(model, f"actuator_{field}")[indices] + if not np.all(actual == expected): + raise ValidationError(f"backend.actuators.{field}", str(expected), actual) + + def _validate_position_actuator_contract(self, spec: RobotSpec) -> None: + assert self._runtime.model is not None + indices = self._actuator_indices + ctrl_range = self._runtime.model.actuator_ctrlrange[indices] + force_range = self._runtime.model.actuator_forcerange[indices] + kp = self._runtime.model.actuator_gainprm[indices, 0] + stiffness = -self._runtime.model.actuator_biasprm[indices, 1] + kd = -self._runtime.model.actuator_biasprm[indices, 2] + checks = ( + ("ctrl_lower", ctrl_range[:, 0], spec.position_lower), + ("ctrl_upper", ctrl_range[:, 1], spec.position_upper), + ("force_lower", force_range[:, 0], -spec.torque_limit), + ("force_upper", force_range[:, 1], spec.torque_limit), + ) + for name, actual, expected in checks: + if not np.allclose(actual, expected, atol=1e-6, rtol=0.0): + raise ValidationError(f"backend.actuators.{name}", expected.tolist(), actual.tolist()) + if not np.allclose(kp, stiffness, atol=1e-7, rtol=0.0): + raise ValidationError("backend.actuators.kp", "gain equals stiffness", (kp.tolist(), stiffness.tolist())) + if not np.all(self._runtime.model.actuator_biastype[indices] == self._runtime.mj.mjtBias.mjBIAS_AFFINE): + raise ValidationError("backend.actuators.bias", "position servo affine bias", "unsupported") + if not np.allclose(self._runtime.model.actuator_biasprm[indices, 0], 0.0): + raise ValidationError("backend.actuators.bias", "no constant force bias", "unsupported") + if not np.isfinite(kp).all() or not np.isfinite(kd).all() or np.any(kp < 0) or np.any(kd < 0): + raise ValidationError("backend.actuators.gains", "finite non-negative kp/kd", (kp.tolist(), kd.tolist())) + + def _validate_torque_actuator_contract(self, spec: RobotSpec) -> None: + assert self._runtime.model is not None + indices = self._actuator_indices + checks = ( + ("ctrl_lower", self._runtime.model.actuator_ctrlrange[indices, 0], -spec.torque_limit), + ("ctrl_upper", self._runtime.model.actuator_ctrlrange[indices, 1], spec.torque_limit), + ("force_lower", self._runtime.model.actuator_forcerange[indices, 0], -spec.torque_limit), + ("force_upper", self._runtime.model.actuator_forcerange[indices, 1], spec.torque_limit), + ("gain", self._runtime.model.actuator_gainprm[indices, 0], np.ones(spec.joint_count)), + ("bias", self._runtime.model.actuator_biastype[indices], np.zeros(spec.joint_count)), + ) + for name, actual, expected in checks: + if not np.allclose(actual, expected, atol=1e-6, rtol=0.0): + raise ValidationError(f"backend.torque_actuators.{name}", str(expected.tolist()), actual.tolist()) + + def _bind_sensors(self) -> None: + assert self._runtime.model is not None + self._sensor_slices.clear() + for role, expected_type in ( + ("base_angular_velocity", self._runtime.mj.mjtSensor.mjSENS_GYRO), + ("base_linear_acceleration", self._runtime.mj.mjtSensor.mjSENS_ACCELEROMETER), + ("base_linear_velocity", self._runtime.mj.mjtSensor.mjSENS_FRAMELINVEL), + ): + path = f"runtime.sensor_bindings.{role}" + local_name = getattr(self.config.sensor_bindings, role) + if local_name is None: + raise ValidationError(path, "an explicit model-local sensor name", "missing") + sensor_name = self._cfg.resolve_name(local_name) + sensor_id = self._name_id(self._runtime.mj.mjtObj.mjOBJ_SENSOR, sensor_name, f"sensor_bindings.{role}") + actual_dimension = int(self._runtime.model.sensor_dim[sensor_id]) + if actual_dimension != 3: + raise ValidationError(path, "a 3D sensor", actual_dimension) + actual_type = self._runtime.model.sensor_type[sensor_id] + if actual_type != expected_type: + raise ValidationError(path, expected_type.name, int(actual_type)) + if role in ("base_angular_velocity", "base_linear_acceleration"): + site_id = int(self._runtime.model.sensor_objid[sensor_id]) + if ( + self._runtime.model.sensor_objtype[sensor_id] != self._runtime.mj.mjtObj.mjOBJ_SITE + or self._runtime.model.site_bodyid[site_id] != self._base_body_id + or not np.allclose(self._runtime.model.site_quat[site_id, 1:], 0.0, atol=1e-7, rtol=0.0) + ): + raise ValidationError(path, "a site on the base body aligned with its body frame", sensor_name) + else: + model = self._runtime.model + object_type = model.sensor_objtype[sensor_id] + object_id = int(model.sensor_objid[sensor_id]) + body_id = ( + int(model.site_bodyid[object_id]) + if object_type == self._runtime.mj.mjtObj.mjOBJ_SITE + else object_id + ) + if ( + model.sensor_refid[sensor_id] != -1 + or object_type not in (self._runtime.mj.mjtObj.mjOBJ_SITE, self._runtime.mj.mjtObj.mjOBJ_BODY) + or body_id != self._base_body_id + ): + raise ValidationError(path, "a world-frame linear velocity sensor on the base body", sensor_name) + address = int(self._runtime.model.sensor_adr[sensor_id]) + self._sensor_slices[role] = slice(address, address + actual_dimension) + + def _state(self) -> RobotState: + assert self._runtime.model is not None and self._runtime.data is not None + orientation_xyzw = wxyz_to_xyzw(self._runtime.data.xquat[self._base_body_id]) + return RobotState( + sample_time_ns=round(self._runtime.data.time * 1e9), + receive_time_ns=self._last_communication_ns, + joint_position=np.asarray(self._runtime.data.qpos[self._joint_qpos_indices], dtype=np.float32), + joint_velocity=np.asarray(self._runtime.data.qvel[self._joint_qvel_indices], dtype=np.float32), + base_orientation_xyzw=orientation_xyzw, + base_angular_velocity=self._sensor("base_angular_velocity"), + base_linear_acceleration=self._sensor("base_linear_acceleration"), + base_position=np.asarray(self._runtime.data.xpos[self._base_body_id], dtype=np.float32), + base_linear_velocity=self._sensor("base_linear_velocity"), + ) + + def _sensor(self, role: str) -> NDArray[np.float32]: + assert self._runtime.data is not None + return np.array(self._runtime.data.sensordata[self._sensor_slices[role]], dtype=np.float32, copy=True) + + def _name_id(self, object_type: mujoco.mjtObj, name: str, description: str) -> int: + assert self._runtime.model is not None + object_id = int(self._runtime.mj.mj_name2id(self._runtime.model, object_type, name)) + if object_id < 0: + raise ValidationError(f"backend.{description}", f"existing name {name!r}", "missing") + return object_id + + def _require_open(self) -> None: + if not self._opened or self._closed or self._runtime.model is None or self._runtime.data is None: + raise RuntimeError("MuJoCo backend is not open") diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/runtime.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/runtime.py new file mode 100644 index 00000000..9cd8e238 --- /dev/null +++ b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/runtime.py @@ -0,0 +1,274 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Explicit external owner of MuJoCo resources, robot bindings and physics.""" + +from __future__ import annotations + +import logging +import time +from collections.abc import Callable +from copy import deepcopy +from dataclasses import replace +from types import ModuleType +from typing import TYPE_CHECKING, TypeVar, cast + +from motrix_deploy.contracts import RobotSpec +from motrix_deploy.robot.interface import RobotInterface +from motrix_deploy.runtime.base import SimulationRuntime +from motrix_deploy.runtime.config import SimulationRuntimeConfig +from motrix_env_core.input import KeyboardDevice + +if TYPE_CHECKING: + import mujoco + + from motrix_deploy.runtime.control import ControlSession + +import numpy as np +from numpy.typing import NDArray + +from motrix_deploy.errors import ValidationError +from motrix_deploy.runtime.result import RolloutResult +from motrix_deploy.runtime.scheduler import FixedStepScheduler, RealtimeScheduler +from motrix_deploy_mujoco.robot import Robot +from motrix_deploy_mujoco.viewer import MujocoGlfwViewer +from motrix_env_core.config.scene import SystemCameraCfg +from motrix_env_core.config.scene.base import RobotCfg +from motrix_env_mujoco.compiler import MuJoCoSceneCompiler + +CommandT = TypeVar("CommandT") + +logger = logging.getLogger(__name__) + + +class MujocoRuntime(SimulationRuntime): + """Own a scene and expose independently controlled robot I/O and physics.""" + + def __init__( + self, + config: SimulationRuntimeConfig, + control_period_s: float | None = None, + viewer_factory: Callable[[ModuleType, SystemCameraCfg], MujocoGlfwViewer] = MujocoGlfwViewer, + ) -> None: + super().__init__() + try: + import mujoco + except ImportError as error: + raise RuntimeError("MuJoCo backend support requires the 'motrix-deploy-mujoco' package") from error + self.scene = deepcopy(config.scene) + if not isinstance(self.scene.objs.robot, RobotCfg): + raise ValidationError("backend.scene.objs.robot", "a RobotCfg", self.scene.objs.robot) + self.mj = mujoco + self.sim = config.physics + self.physics_substeps: int | None = None + self.model: mujoco.MjModel | None = None + self.data: mujoco.MjData | None = None + self.viewer = viewer_factory(mujoco, self.scene.system_camera) if config.render else None + self.config = config + self.control_period_s: float | None = None + if control_period_s is not None: + self._configure_control_period(control_period_s) + self.realtime = config.realtime + self._closed = False + self._source_spec: mujoco.MjSpec | None = None + robot_spec = self._derive_robot_spec() + self._robot = Robot(self, robot_spec) + + @property + def robot(self) -> RobotInterface: + return self._robot + + def _configure_control_period(self, period_s: float) -> None: + ratio = period_s / self.sim.dt + if not np.isfinite(ratio) or ratio < 1 or not np.isclose(ratio, round(ratio), atol=1e-9): + raise ValidationError("control.period_s", "an integer multiple of physics.dt", period_s) + self.control_period_s = period_s + self.physics_substeps = round(ratio) + + def bind_control_session(self, control: ControlSession[CommandT]) -> None: + """Bind control ownership and derive physics cadence from the session.""" + # Resolve identity conflicts before changing the runtime's timing. + self._resolve_control(control) + self._configure_control_period(control.period_s) + super().bind_control_session(control) + + def _derive_robot_spec(self) -> RobotSpec: + """Inspect physical limits, never controller gains, before binding robot I/O.""" + robot = cast(RobotCfg, self.scene.objs.robot) + self._source_spec = MuJoCoSceneCompiler().create_spec(self.scene, self.sim) + model = self._source_spec.compile() + joint_names = tuple(robot.key_pose.joint_names) + actuators = [] + for name in joint_names: + joint_id = model.joint(robot.resolve_name(name)).id + matches = np.flatnonzero( + (model.actuator_trntype == self.mj.mjtTrn.mjTRN_JOINT) & (model.actuator_trnid[:, 0] == joint_id) + ) + if matches.size != 1: + raise ValidationError(f"backend.actuators.{name}", "exactly one joint actuator", matches.tolist()) + actuators.append(int(matches[0])) + indices = np.asarray(actuators, dtype=np.int64) + joint_ids = np.asarray([model.joint(robot.resolve_name(name)).id for name in joint_names]) + motors = model.actuator_biastype[indices] == self.mj.mjtBias.mjBIAS_NONE + position_range = np.where(motors[:, None], model.jnt_range[joint_ids], model.actuator_ctrlrange[indices]) + return RobotSpec( + base_link_name=robot.base_link_name, + joint_names=joint_names, + default_joint_position=np.asarray(robot.key_pose.poses[robot.init_key_pose], dtype=np.float32), + position_lower=position_range[:, 0].astype(np.float32), + position_upper=position_range[:, 1].astype(np.float32), + torque_limit=model.actuator_forcerange[indices, 1].astype(np.float32), + ) + + def get_keyboard_device(self) -> KeyboardDevice: + """Return keyboard input owned by this simulation's viewer.""" + if self.viewer is None: + raise RuntimeError("MuJoCo keyboard input requires viewer=true") + return self.viewer.keyboard_device + + def build_model(self) -> mujoco.MjModel: + """Validate and transform registered robot servos before final compilation.""" + model_spec = self._source_spec or MuJoCoSceneCompiler().create_spec(self.scene, self.sim) + self._source_spec = None + source_model = model_spec.compile() + self.model = source_model + self._robot.prepare(source_model, model_spec) + model = model_spec.compile() + logger.info("Built MuJoCo deployment model from SceneCfg %s", type(self.scene).__name__) + return model + + def open(self) -> None: + """Compile, prepare, bind, and reset the registered robot.""" + if self._opened: + raise RuntimeError("MuJoCo simulation is already open") + self._closed = False + try: + self.model = self.build_model() + self.data = self.mj.MjData(self.model) + self._robot.bind() + if self.viewer is not None: + self.viewer.open(self.model, self.data) + self.sync_viewer() + self._opened = True + except BaseException: + try: + self.close() + except Exception: + logger.exception("Cleanup failed after MuJoCo session open failure") + raise + + def reset( + self, + joint_qpos_indices: NDArray[np.int64], + joint_position: NDArray[np.float32], + ) -> None: + assert self.model is not None and self.data is not None + self.mj.mj_resetData(self.model, self.data) + qpos = self.data.qpos + # qpos0 already composes RobotCfg's attach transform with the imported + # model root pose. Keep it intact instead of applying placement twice. + qpos[joint_qpos_indices] = joint_position + self.data.qvel.fill(0.0) + self.data.ctrl.fill(0.0) + self.mj.mj_forward(self.model, self.data) + + def run( + self, + control: ControlSession[CommandT] | None = None, + *, + steps: int | None = None, + realtime: bool | None = None, + ) -> RolloutResult: + """Drive shared control at physics boundaries inside an already-open world. + + Control writes cached commands only. This owner re-evaluates robot PD on + every physics substep, and paces complete control intervals independently + of the shared control lifecycle. Stopping control leaves this world open. + """ + if not self._opened: + raise RuntimeError("MuJoCo simulation with a registered robot is not open") + control = self._resolve_control(control) + if steps is not None and (not isinstance(steps, int) or isinstance(steps, bool) or steps <= 0): + raise ValidationError("steps", "a positive integer", steps) + self._configure_control_period(control.period_s) + if realtime is None: + realtime = self.realtime + paced = self.viewer is not None if realtime is None else realtime + assert self.physics_substeps is not None and self.control_period_s is not None + scheduler = (RealtimeScheduler if paced else FixedStepScheduler)(self.control_period_s) + assert self.data is not None and self.model is not None + initial_time_s = float(self.data.time) + simulation_time_s = 0.0 + try: + if control.start(): + # Establish relative simulation time after control initialization. + initial_time_s = float(self.data.time) + scheduler.reset() + substep = 0 + while control.active: + if substep % self.physics_substeps == 0: + if steps is not None and control.completed_steps >= steps: + break + self.check_viewer_running() + if not control.tick(elapsed_time_s=float(self.data.time) - initial_time_s): + break + self._robot.apply_control() + self.mj.mj_step(self.model, self.data) + substep += 1 + if substep % self.physics_substeps == 0: + self.sync_viewer() + # Absolute deadlines avoid accumulated wall-clock drift, + # and include the final interval without an extra tick. + scheduler.wait(substep // self.physics_substeps) + except (Exception, KeyboardInterrupt) as error: + control.fail(error) + finally: + simulation_time_s = float(self.data.time) - initial_time_s + control.stop() + result = control.result(steps=steps, overrun_count=scheduler.overrun_count) + return replace( + result, + simulation_time_s=simulation_time_s, + real_time_factor=simulation_time_s / result.wall_time_s if result.wall_time_s > 0 else 0.0, + ) + + def advance_control_period(self) -> None: + """Advance physics, re-evaluating cached robot PD at every substep.""" + if not self._opened: + raise RuntimeError("MuJoCo simulation with a registered robot is not open") + if self.physics_substeps is None: + raise RuntimeError("Bind a control session before advancing a control period") + self.check_viewer_running() + for _ in range(self.physics_substeps): + self._robot.apply_control() + self.mj.mj_step(self.model, self.data) + self.sync_viewer() + + def check_viewer_running(self) -> None: + if self.viewer is not None and not self.viewer.is_running(): + raise KeyboardInterrupt("MuJoCo viewer was closed") + + def sync_viewer(self) -> None: + if self.viewer is not None: + self.check_viewer_running() + self.viewer.sync() + + def close(self) -> None: + if self._closed: + return + try: + if self.control is not None and self.control.active: + self.control.stop() + if self.viewer is not None: + self.viewer.close() + deadline = time.monotonic() + 2.0 + while self.viewer.is_running() and time.monotonic() < deadline: + time.sleep(0.001) + if self.viewer.is_running(): + raise RuntimeError("MuJoCo viewer did not stop within 2 seconds") + finally: + self._robot.invalidate() + self.data = None + self.model = None + self._opened = False + self._closed = True diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/transform.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/transform.py index 2a45fcd1..120a9685 100644 --- a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/transform.py +++ b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/transform.py @@ -3,22 +3,29 @@ """Deployment-specific MuJoCo specification transforms.""" -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING import numpy as np +from numpy.typing import NDArray from motrix_deploy.contracts import RobotSpec from motrix_deploy.errors import ValidationError +if TYPE_CHECKING: + import mujoco + -def convert_position_actuators_to_motors( - mj: Any, - model_spec: Any, - source_model: Any, - actuator_indices: np.ndarray, +def convert_actuators_to_motors( + model_spec: mujoco.MjSpec, + source_model: mujoco.MjModel, + actuator_indices: NDArray[np.int64], robot: RobotSpec, ) -> list[str]: - """Convert canonical position actuators in an assembled MjSpec to torque motors.""" + """Convert selected, prevalidated position servos to torque motors.""" + import mujoco as mj + actuator_names: list[str] = [] for index in actuator_indices: name = mj.mj_id2name(source_model, mj.mjtObj.mjOBJ_ACTUATOR, index) @@ -38,4 +45,4 @@ def convert_position_actuators_to_motors( return actuator_names -__all__ = ["convert_position_actuators_to_motors"] +__all__ = ["convert_actuators_to_motors"] diff --git a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/viewer.py b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/viewer.py index cb151873..ba852f04 100644 --- a/motrix_deploy_mujoco/src/motrix_deploy_mujoco/viewer.py +++ b/motrix_deploy_mujoco/src/motrix_deploy_mujoco/viewer.py @@ -3,7 +3,16 @@ """Minimal GLFW viewer and focused keyboard device for MuJoCo deployment.""" -from typing import Any +from __future__ import annotations + +from types import ModuleType +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import ctypes + + import glfw + import mujoco from motrix_env_core.config.scene import SystemCameraCfg from motrix_env_core.input import KeyboardDevice @@ -12,7 +21,7 @@ class MujocoKeyboardDevice(KeyboardDevice): """Freeze keyboard events delivered to one focused GLFW viewer window.""" - def __init__(self, viewer: "MujocoGlfwViewer") -> None: + def __init__(self, viewer: MujocoGlfwViewer) -> None: self._viewer = viewer self._pressing: set[str] = set() self._pending_down: set[str] = set() @@ -45,7 +54,9 @@ def is_key_up(self, key: str) -> bool: def is_pressing(self, key: str) -> bool: return self._normalize_query(key) in self._frame_pressing - def _on_key(self, window: Any, key: int, scancode: int, action: int, modifiers: int) -> None: + def _on_key( + self, window: ctypes._Pointer[glfw._GLFWwindow], key: int, scancode: int, action: int, modifiers: int + ) -> None: del window, modifiers glfw = self._viewer._glfw assert glfw is not None @@ -62,13 +73,13 @@ def _on_key(self, window: Any, key: int, scancode: int, action: int, modifiers: self._pressing.remove(name) self._pending_up.add(name) - def _on_focus(self, window: Any, focused: int) -> None: + def _on_focus(self, window: ctypes._Pointer[glfw._GLFWwindow], focused: int) -> None: del window if not focused: self._pending_up.update(self._pressing) self._pressing.clear() - def _on_close(self, window: Any) -> None: + def _on_close(self, window: ctypes._Pointer[glfw._GLFWwindow]) -> None: del window self._interrupted = True @@ -114,27 +125,27 @@ class MujocoGlfwViewer: def __init__( self, - mujoco_module: Any, + mujoco_module: ModuleType, camera_config: SystemCameraCfg = SystemCameraCfg(), *, - glfw_module: Any | None = None, + glfw_module: ModuleType | None = None, ) -> None: self._mj = mujoco_module self._glfw = glfw_module self._camera_config = camera_config - self._model: Any = None - self._data: Any = None - self._window: Any = None - self._scene: Any = None - self._context: Any = None - self._camera: Any = None - self._option: Any = None - self._perturb: Any = None + self._model: mujoco.MjModel | None = None + self._data: mujoco.MjData | None = None + self._window: ctypes._Pointer[glfw._GLFWwindow] | None = None + self._scene: mujoco.MjvScene | None = None + self._context: mujoco.MjrContext | None = None + self._camera: mujoco.MjvCamera | None = None + self._option: mujoco.MjvOption | None = None + self._perturb: mujoco.MjvPerturb | None = None self._glfw_initialized = False self._cursor_position = (0.0, 0.0) self.keyboard_device = MujocoKeyboardDevice(self) - def open(self, model: Any, data: Any) -> None: + def open(self, model: mujoco.MjModel, data: mujoco.MjData) -> None: """Create the window and MuJoCo rendering resources.""" if self._window is not None: raise RuntimeError("MuJoCo GLFW viewer is already open") @@ -180,9 +191,13 @@ def open(self, model: Any, data: Any) -> None: raise def sync(self) -> None: - """Render current state without polling input or advancing physics.""" + """Pump window events and render without consuming keyboard input or stepping.""" window = self._require_window() - if self._glfw.window_should_close(window): + assert self._glfw is not None + # Window responsiveness must not depend on a keyboard command binding. + # Callbacks retain pending key edges until KeyboardDevice.poll freezes them. + self._glfw.poll_events() + if self.keyboard_device._interrupted or self._glfw.window_should_close(window): raise KeyboardInterrupt assert self._model is not None assert self._data is not None @@ -208,7 +223,10 @@ def sync(self) -> None: self._glfw.swap_buffers(window) def is_running(self) -> bool: - return self._window is not None and not self._glfw.window_should_close(self._window) + if self._window is None: + return False + assert self._glfw is not None + return not self._glfw.window_should_close(self._window) def close(self) -> None: """Release rendering resources and the GLFW window; safe to repeat.""" @@ -237,7 +255,8 @@ def close(self) -> None: self._perturb = None self.keyboard_device._clear() - def _on_cursor_position(self, window: Any, xpos: float, ypos: float) -> None: + def _on_cursor_position(self, window: ctypes._Pointer[glfw._GLFWwindow], xpos: float, ypos: float) -> None: + assert self._glfw is not None previous_x, previous_y = self._cursor_position self._cursor_position = (xpos, ypos) dx = xpos - previous_x @@ -262,16 +281,16 @@ def _on_cursor_position(self, window: Any, xpos: float, ypos: float) -> None: action = self._mj.mjtMouse.mjMOUSE_ZOOM self._move_camera(action, dx / height, dy / height) - def _on_scroll(self, window: Any, xoffset: float, yoffset: float) -> None: + def _on_scroll(self, window: ctypes._Pointer[glfw._GLFWwindow], xoffset: float, yoffset: float) -> None: del window, xoffset self._move_camera(self._mj.mjtMouse.mjMOUSE_ZOOM, 0.0, -0.05 * yoffset) - def _move_camera(self, action: Any, dx: float, dy: float) -> None: + def _move_camera(self, action: mujoco.mjtMouse, dx: float, dy: float) -> None: if self._model is None or self._scene is None or self._camera is None: return self._mj.mjv_moveCamera(self._model, action, dx, dy, self._scene, self._camera) - def _require_window(self) -> Any: + def _require_window(self) -> ctypes._Pointer[glfw._GLFWwindow]: if self._window is None: raise RuntimeError("MuJoCo GLFW viewer is not open") return self._window diff --git a/motrix_deploy_mujoco/tests/conftest.py b/motrix_deploy_mujoco/tests/conftest.py new file mode 100644 index 00000000..f4b04e11 --- /dev/null +++ b/motrix_deploy_mujoco/tests/conftest.py @@ -0,0 +1,30 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Shared explicit scene fixture for robot control integration tests.""" + +import numpy as np +import pytest +from scene_helpers import build_scene + +from motrix_deploy.runtime.config import SensorBindings, SimulationRuntimeConfig +from motrix_env_core.config.sim import SimCfg + + +@pytest.fixture +def control_gains() -> tuple[np.ndarray, np.ndarray]: + """Static test controller configuration, independent of model servo gains.""" + return np.ones(12, dtype=np.float32) * 30.0, np.ones(12, dtype=np.float32) * 0.8 + + +@pytest.fixture +def control_scene_config() -> SimulationRuntimeConfig: + return SimulationRuntimeConfig( + scene=build_scene(), + physics=SimCfg(dt=0.002, solver_iterations=100), + sensor_bindings=SensorBindings( + base_angular_velocity="gyro", + base_linear_acceleration="accelerometer", + base_linear_velocity="global_linvel", + ), + ) diff --git a/motrix_deploy_mujoco/tests/control_helpers.py b/motrix_deploy_mujoco/tests/control_helpers.py new file mode 100644 index 00000000..e9c64a81 --- /dev/null +++ b/motrix_deploy_mujoco/tests/control_helpers.py @@ -0,0 +1,40 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Test-owned controller for backend lifecycle and cadence contracts.""" + +import numpy as np + +from motrix_deploy.contracts import FloatArray, JointServoCommand, RobotSpec, RobotState +from motrix_deploy.runtime.context import PolicyContext +from motrix_deploy.task import DeployTask +from motrix_env_core.input import PlanarVelocityCommand + + +class DummyServoTask(DeployTask[PlanarVelocityCommand]): + """Keep generic driver tests independent of examples and trained artifacts.""" + + def __init__(self, robot: RobotSpec, kp: FloatArray, kd: FloatArray) -> None: + self.robot = robot + self.kp = kp + self.kd = kd + self._zeros = np.zeros(robot.joint_count, dtype=np.float32) + + def reset(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> None: + pass + + def build_observation(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> FloatArray: + return self._zeros.copy() + + def process_action(self, action: FloatArray) -> JointServoCommand: + return JointServoCommand( + joint_position=self.robot.default_joint_position + action, + joint_velocity=self._zeros.copy(), + feedforward_torque=self._zeros.copy(), + kp=self.kp, + kd=self.kd, + ) + + def validate_command(self, command: PlanarVelocityCommand) -> None: + if command.batch_size != 1 or np.any(command.values != 0.0): + raise ValueError("DummyServoTask accepts one zero velocity command") diff --git a/motrix_deploy_mujoco/tests/scene_helpers.py b/motrix_deploy_mujoco/tests/scene_helpers.py new file mode 100644 index 00000000..5197b913 --- /dev/null +++ b/motrix_deploy_mujoco/tests/scene_helpers.py @@ -0,0 +1,23 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Complete scenes assembled locally by adapter tests.""" + +from motrix_robots.unitree import UnitreeGo2Robot + +from motrix_env_core.config import configclass +from motrix_env_core.config.scene import FlatTerrainCfg, SceneCfg, SceneObjsCfg + + +@configclass +class AdapterSceneObjsCfg(SceneObjsCfg): + floor: FlatTerrainCfg | None = None + + +def build_scene() -> SceneCfg: + return SceneCfg( + objs=AdapterSceneObjsCfg( + robot=UnitreeGo2Robot(translation=(0.0, 0.0, -0.114)), + floor=FlatTerrainCfg(friction=(0.6, 0.005, 0.0001)), + ) + ) diff --git a/motrix_deploy_mujoco/tests/test_examples.py b/motrix_deploy_mujoco/tests/test_examples.py new file mode 100644 index 00000000..139a9b5f --- /dev/null +++ b/motrix_deploy_mujoco/tests/test_examples.py @@ -0,0 +1,211 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""End-to-end smoke tests for the standalone MuJoCo deployment example.""" + +from __future__ import annotations + +import json +import runpy +import subprocess +import sys +from pathlib import Path + +import numpy as np +import pytest +from control_helpers import DummyServoTask + +ROOT = Path(__file__).parents[2] +EXAMPLES = ROOT / "examples" +EXAMPLE_SCRIPT = EXAMPLES / "deploy_to_sim.py" + + +@pytest.fixture +def onnx_policy(tmp_path, control_scene_config, control_gains): + """Generate raw ONNX locally; never depend on a trained checkout policy.""" + import onnx + from onnx import TensorProto, helper, numpy_helper + + from motrix_deploy.task import create_task + from motrix_deploy_mujoco.runtime import MujocoRuntime + from motrix_deploy_tasks.tasks.go2_walk import Go2WalkTaskSpec + + runtime = MujocoRuntime(control_scene_config, control_period_s=0.02) + robot = runtime.robot.spec + kp, kd = control_gains + task_spec = Go2WalkTaskSpec( + action_scale=[0.1] * robot.joint_count, + command_lower=[-1.0] * 3, + command_upper=[1.0] * 3, + command_scale=[1.0] * 3, + feet_phase_offsets=[0.0, 0.5, 0.5, 0.0], + gait_frequency_hz=1.0, + standing_threshold=0.05, + termination_min_up_z=-1.0, + termination_min_base_height=None, + kp=kp.tolist(), + kd=kd.tolist(), + action_lower=[-1.0] * robot.joint_count, + action_upper=[1.0] * robot.joint_count, + ) + task = create_task(task_spec, robot) + from motrix_deploy.runtime.context import PolicyContext + from motrix_env_core.input import ConstantPlanarVelocityBinding + + with runtime: + runtime.robot.open() + context = PolicyContext( + step=0, + elapsed_time_s=0.0, + command=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)).read_command(), + ) + state = runtime.robot.read_state(0.1) + task.reset(state, context) + observation_size = task.build_observation(state, context).size + runtime.robot.close() + weight = numpy_helper.from_array(np.zeros((observation_size, robot.joint_count), dtype=np.float32), name="weight") + graph = helper.make_graph( + [helper.make_node("MatMul", ["obs", "weight"], ["actions"])], + "deterministic_example_fixture", + [helper.make_tensor_value_info("obs", TensorProto.FLOAT, [1, observation_size])], + [helper.make_tensor_value_info("actions", TensorProto.FLOAT, [1, robot.joint_count])], + [weight], + ) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 17)]) + model.ir_version = 10 + onnx.checker.check_model(model) + path = tmp_path / "fixture.onnx" + onnx.save(model, path) + return path + + +def _result(stdout: str) -> dict[str, object]: + start = stdout.find("{") + assert start >= 0, stdout + return json.loads(stdout[start:]) + + +def test_example_builders_are_import_safe() -> None: + namespace = runpy.run_path(str(EXAMPLE_SCRIPT), run_name="motrix_deploy_mujoco_example_test") + assert callable(namespace["main"]) + assert callable(namespace["ExampleSceneObjsCfg"]) + + +@pytest.mark.parametrize("backend", ["mujoco"]) +def test_programmatic_example_uses_shared_control_execution(tmp_path, onnx_policy, backend) -> None: + command = [ + sys.executable, + "-c", + "import runpy, sys; from pathlib import Path; " + "namespace = runpy.run_path(sys.argv.pop(1)); " + "namespace['main'].__globals__['POLICY_PATH'] = Path(sys.argv.pop(1)); " + "raise SystemExit(namespace['main']())", + str(EXAMPLE_SCRIPT), + str(onnx_policy), + "--backend", + backend, + "--headless", + "--steps", + "3", + ] + completed = subprocess.run(command, check=False, capture_output=True, text=True, cwd=tmp_path) + assert completed.returncode == 0, completed.stderr + result = _result(completed.stdout) + assert result["success"] is True + assert result["completed_steps"] == 3 + assert result["exit_reason"] == "completed" + assert result["error"] is None + assert result["simulation_time_s"] == pytest.approx(0.06) + assert "latency" in result # Shared control execution report, not a separate preview loop. + repeated = subprocess.run(command, check=False, capture_output=True, text=True, cwd=tmp_path) + assert repeated.returncode == 0, repeated.stderr + assert _result(repeated.stdout)["trace_sha256"] == result["trace_sha256"] + + +def test_simulation_factory_supports_explicit_bound_control_and_world_ownership( + control_scene_config, control_gains +) -> None: + from motrix_deploy.policy import NoOpPolicyRuntime + from motrix_deploy.robot.interface import RobotInterface + from motrix_deploy.runtime.config import SimulationRuntimeConfig + from motrix_deploy.runtime.control import ControlSession + from motrix_deploy.runtime.factory import create_simulation_runtime + from motrix_env_core.input import ConstantPlanarVelocityBinding + + config = SimulationRuntimeConfig( + scene=control_scene_config.scene, + physics=control_scene_config.physics, + sensor_bindings=control_scene_config.sensor_bindings, + ) + runtime = create_simulation_runtime("mujoco", config) + robot = runtime.robot + assert isinstance(robot, RobotInterface) + assert not runtime.opened + assert runtime.model is None and runtime.data is None + control = ControlSession( + robot=robot, + task=DummyServoTask(robot.spec, *control_gains), + policy=NoOpPolicyRuntime(robot.spec.joint_count), + command_binding=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)), + period_s=0.012, + state_timeout_s=0.1, + ) + runtime.bind_control_session(control) + assert runtime.control_period_s == control.period_s + assert runtime.physics_substeps == 6 + assert runtime.control is control and control.robot is robot + assert not runtime.opened + assert runtime.model is None and runtime.data is None + + with runtime: + result = runtime.run(steps=2) + assert result.success and result.completed_steps == 2 + assert runtime.robot is robot and runtime.control is control + assert not control.active + # Control shutdown releases robot I/O, not the simulation world's resources. + assert runtime.opened and runtime.model is not None and runtime.data is not None + before = runtime.data.time + runtime.advance_control_period() + assert runtime.data.time > before + assert not runtime.opened + assert runtime.model is None and runtime.data is None + + +def test_robot_commands_do_not_advance_or_own_the_world(control_scene_config, control_gains) -> None: + from motrix_deploy.runtime.context import PolicyContext + from motrix_deploy_mujoco.runtime import MujocoRuntime + from motrix_env_core.input import ConstantPlanarVelocityBinding + + config = control_scene_config + simulation = MujocoRuntime(config, control_period_s=0.02) + spec = simulation.robot.spec + task = DummyServoTask(spec, *control_gains) + interface = simulation.robot + assert interface.spec is spec + with simulation: + interface.open() + before = interface.read_state(0.1) + context = PolicyContext( + step=0, + elapsed_time_s=0.0, + command=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)).read_command(), + ) + observation = task.build_observation(before, context) + np.testing.assert_array_equal(observation, np.zeros(spec.joint_count, dtype=np.float32)) + assert observation.dtype == np.float32 + interface.write_command(task.process_action(np.zeros(spec.joint_count, dtype=np.float32))) + after_write = interface.read_state(0.1) + assert after_write.sample_time_ns == before.sample_time_ns + np.testing.assert_array_equal(after_write.joint_position, before.joint_position) + simulation.advance_control_period() + advanced = interface.read_state(0.1) + assert advanced.sample_time_ns > before.sample_time_ns + interface.stop() + interface.close() + # Closing the robot port releases control, not the shared simulation. + assert simulation.model is not None and simulation.data is not None + time_before = simulation.data.time + simulation.advance_control_period() + assert simulation.data.time > time_before + np.testing.assert_array_equal(simulation.data.ctrl, np.zeros(simulation.model.nu)) + assert simulation.model is None and simulation.data is None diff --git a/motrix_deploy_mujoco/tests/test_joint_control.py b/motrix_deploy_mujoco/tests/test_joint_control.py new file mode 100644 index 00000000..a3c9d24c --- /dev/null +++ b/motrix_deploy_mujoco/tests/test_joint_control.py @@ -0,0 +1,143 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Servo and torque command boundaries and physics-owned application cadence.""" + +import numpy as np +import pytest + +from motrix_deploy.contracts import JointControlMode, JointServoCommand, JointTorqueCommand +from motrix_deploy.errors import ValidationError +from motrix_deploy_mujoco.runtime import MujocoRuntime +from motrix_env_core.config.scene.actuator import MotorActuatorCfg +from motrix_env_mujoco.compiler import MuJoCoSceneCompiler + + +def _servo(spec, **overrides): + zeros = np.zeros(spec.joint_count, dtype=np.float32) + fields = dict( + joint_position=spec.default_joint_position, joint_velocity=zeros, feedforward_torque=zeros, kp=zeros, kd=zeros + ) + fields.update(overrides) + return JointServoCommand(**fields) + + +def test_auto_spec_inspection_reuses_original_source_spec_on_first_open(control_scene_config, monkeypatch): + original = MuJoCoSceneCompiler.create_spec + specs = [] + + def record_spec(compiler, scene, sim): + spec = original(compiler, scene, sim) + specs.append(spec) + return spec + + monkeypatch.setattr(MuJoCoSceneCompiler, "create_spec", record_spec) + runtime = MujocoRuntime(control_scene_config, 0.02) + assert len(specs) == 1 and runtime._source_spec is specs[0] + assert runtime.model is runtime.data is None + with runtime: + assert len(specs) == 1 + with runtime: + assert len(specs) == 2 # Reopen starts from source, not already transformed motors. + + +def test_torque_is_snapshotted_and_applied_directly_every_physics_substep(control_scene_config, monkeypatch): + with MujocoRuntime(control_scene_config, 0.02) as runtime: + assert runtime.robot.capabilities.control_modes == (JointControlMode.SERVO, JointControlMode.TORQUE) + torque = np.linspace(-1, 1, runtime.robot.spec.joint_count, dtype=np.float32) + expected = torque.copy() + snapshots = [] + step = runtime.mj.mj_step + + def record_step(model, data): + snapshots.append(data.ctrl[runtime.robot._actuator_indices].copy()) + step(model, data) + + monkeypatch.setattr(runtime.mj, "mj_step", record_step) + runtime.robot.write_command(JointTorqueCommand(torque)) + torque[:] = 0 + assert runtime.data.time == 0 + runtime.advance_control_period() + assert len(snapshots) == runtime.physics_substeps + for applied in snapshots: + np.testing.assert_array_equal(applied, expected) + assert runtime.data.time == pytest.approx(runtime.control_period_s) + runtime.robot.stop() + np.testing.assert_array_equal(runtime.data.ctrl, 0) + + +@pytest.mark.parametrize("kind", ["torque", "feedforward", "position", "shape"]) +def test_command_limits_are_validated_before_application(control_scene_config, kind): + with MujocoRuntime(control_scene_config, 0.02) as runtime: + spec = runtime.robot.spec + if kind == "torque": + command = JointTorqueCommand(spec.torque_limit + np.float32(1)) + field = "torque" + elif kind == "feedforward": + command = _servo(spec, feedforward_torque=spec.torque_limit + np.float32(1)) + field = "feedforward_torque" + elif kind == "position": + command = _servo(spec, joint_position=spec.position_upper + np.float32(1)) + field = "joint_position" + else: + command = JointTorqueCommand(np.zeros(spec.joint_count - 1, dtype=np.float32)) + field = "torque" + with pytest.raises(ValidationError, match=f"command.{field}"): + runtime.robot.write_command(command) + np.testing.assert_array_equal(runtime.data.ctrl, 0) + + +def test_servo_total_pd_torque_is_clipped_and_mode_can_change(control_scene_config): + with MujocoRuntime(control_scene_config, 0.02) as runtime: + spec = runtime.robot.spec + runtime.robot.write_command( + _servo(spec, joint_position=spec.position_upper, kp=np.full(spec.joint_count, 1e6, dtype=np.float32)) + ) + runtime.robot.apply_control() + np.testing.assert_allclose(runtime.data.ctrl[runtime.robot._actuator_indices], spec.torque_limit) + torque = np.zeros(spec.joint_count, dtype=np.float32) + runtime.robot.write_command(JointTorqueCommand(torque)) + runtime.robot.apply_control() + np.testing.assert_array_equal(runtime.data.ctrl, 0) + + +def test_source_motor_model_derives_joint_limits_and_accepts_torque(control_scene_config): + robot = control_scene_config.scene.objs.robot + # Test-owned effort bounds differ from joint limits to exercise motor spec derivation. + robot.actuators = { + f"motor_{name}": MotorActuatorCfg(joint_name=name, ctrl_range=(-5.0, 5.0), force_range=(-5.0, 5.0)) + for name in robot.key_pose.joint_names + } + with MujocoRuntime(control_scene_config, 0.02) as runtime: + ids = [runtime.model.joint(name).id for name in runtime.robot.spec.joint_names] + np.testing.assert_allclose(runtime.robot.spec.position_lower, runtime.model.jnt_range[ids, 0]) + np.testing.assert_allclose(runtime.robot.spec.position_upper, runtime.model.jnt_range[ids, 1]) + np.testing.assert_array_equal(runtime.robot.spec.torque_limit, 5.0) + runtime.robot.write_command(JointTorqueCommand(np.ones(runtime.robot.spec.joint_count, dtype=np.float32))) + runtime.advance_control_period() + np.testing.assert_array_equal(runtime.data.ctrl, 1) + + +@pytest.mark.parametrize("field", ["gear", "ctrllimited", "forcelimited", "dyntype"]) +def test_noncanonical_source_actuation_is_rejected(control_scene_config, monkeypatch, field): + original = MuJoCoSceneCompiler.create_spec + + def invalid_spec(compiler, scene, sim): + spec = original(compiler, scene, sim) + actuator = next(iter(spec.actuators)) + if field == "gear": + actuator.gear = [2, 0, 0, 0, 0, 0] + elif field in {"ctrllimited", "forcelimited"}: + setattr(actuator, field, 0) + else: + import mujoco + + actuator.dyntype = mujoco.mjtDyn.mjDYN_FILTER + actuator.dynprm[0] = 0.01 + return spec + + monkeypatch.setattr(MuJoCoSceneCompiler, "create_spec", invalid_spec) + runtime = MujocoRuntime(control_scene_config, 0.02) + with pytest.raises(ValidationError, match=f"backend.actuators.{field}"): + runtime.open() + assert runtime.model is runtime.data is None diff --git a/motrix_deploy_mujoco/tests/test_mujoco_deploy_backend.py b/motrix_deploy_mujoco/tests/test_mujoco_deploy_backend.py index 74c441f0..b247b1de 100644 --- a/motrix_deploy_mujoco/tests/test_mujoco_deploy_backend.py +++ b/motrix_deploy_mujoco/tests/test_mujoco_deploy_backend.py @@ -4,7 +4,9 @@ """MuJoCo Go2 adapter contract tests.""" import logging +import subprocess import sys +import textwrap from collections.abc import Callable from dataclasses import replace from pathlib import Path @@ -12,22 +14,26 @@ import numpy as np import pytest +from motrix_robots.unitree import UnitreeGo2Robot from omegaconf import OmegaConf +from scene_helpers import AdapterSceneObjsCfg, build_scene -from motrix_deploy.backend import KeyboardDeviceProvider -from motrix_deploy.contracts import RobotCommand, RobotSpec +from motrix_deploy.contracts import JointServoCommand, RobotSpec from motrix_deploy.errors import ValidationError +from motrix_deploy.robot.interface import KeyboardDeviceProvider +from motrix_deploy.runtime.config import SensorBindings, SimulationRuntimeConfig from motrix_deploy_mujoco import ( - MujocoBackendConfig, - MujocoRobotInterface, + MujocoRuntime, wxyz_to_xyzw, xyzw_to_wxyz, ) -from motrix_env_core.config.scene import SystemCameraCfg +from motrix_env_core.config.scene import FlatTerrainCfg, SceneCfg, SystemCameraCfg +from motrix_env_core.config.sim import SimCfg +from motrix_env_mujoco.compiler import MuJoCoSceneCompiler ROOT = Path(__file__).parents[2] -CONFIG_PATH = ROOT / "configs/deploy/sim2sim/go2_walk_sim2sim.yaml" -FLAT_CONFIG_PATH = ROOT / "configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml" +CONFIG_PATH = ROOT / "motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/sim.yaml" +FLAT_CONFIG_PATH = CONFIG_PATH JOINT_NAMES = ( "FL_hip_joint", "FL_thigh_joint", @@ -45,8 +51,34 @@ CONTROL_PERIOD_S = 0.02 -def _config(config_path: Path = CONFIG_PATH) -> MujocoBackendConfig: - return MujocoBackendConfig.from_mapping(OmegaConf.load(config_path).backend) +def _mapping(config_path: Path = CONFIG_PATH) -> dict[str, Any]: + values = OmegaConf.to_container(OmegaConf.load(config_path).runtime) + values.pop("backend") + values.pop("deploy_env_id") + values.pop("robot_id") + values["scene"] = build_scene() + values["scene"].objs.robot.translation = tuple(values.pop("robot_translation")) + values["scene"].objs.robot.rotation = tuple(values.pop("robot_rotation")) + return values + + +def _config(config_path: Path | None = None) -> SimulationRuntimeConfig: + if config_path is not None: + return SimulationRuntimeConfig.from_mapping(_mapping(config_path)) + return SimulationRuntimeConfig( + scene=build_scene(), + physics=SimCfg(dt=0.002, solver_iterations=100), + sensor_bindings=SensorBindings( + base_angular_velocity="gyro", + base_linear_acceleration="accelerometer", + base_linear_velocity="global_linvel", + ), + ) + + +def create_runtime(config, *, control_period_s, render=False, **kwargs): + runtime = MujocoRuntime(replace(config, render=render), control_period_s=control_period_s, **kwargs) + return runtime def _backend( @@ -54,9 +86,9 @@ def _backend( render: bool = False, control_period_s: float = CONTROL_PERIOD_S, viewer_factory: Callable[[Any, SystemCameraCfg], Any] | None = None, -) -> MujocoRobotInterface: +) -> MujocoRuntime: kwargs = {} if viewer_factory is None else {"viewer_factory": viewer_factory} - return MujocoRobotInterface( + return create_runtime( _config(), control_period_s=control_period_s, render=render, @@ -78,9 +110,9 @@ def _spec(joint_names: tuple[str, ...] = JOINT_NAMES) -> RobotSpec: ) -def _command(position: np.ndarray, *, kp: float = 35.0, kd: float = 0.5) -> RobotCommand: +def _command(position: np.ndarray, *, kp: float = 35.0, kd: float = 0.5) -> JointServoCommand: zeros = np.zeros(12, dtype=np.float32) - return RobotCommand( + return JointServoCommand( joint_position=position, joint_velocity=zeros, feedforward_torque=zeros, @@ -89,8 +121,8 @@ def _command(position: np.ndarray, *, kp: float = 35.0, kd: float = 0.5) -> Robo ) -def test_backend_control_period_must_match_scene_config() -> None: - with pytest.raises(ValidationError, match="backend.scene.ctrl_dt"): +def test_backend_control_period_requires_integral_physics_substeps() -> None: + with pytest.raises(ValidationError, match="integer multiple"): _backend(control_period_s=0.015) @@ -103,36 +135,113 @@ def test_quaternion_order_conversion_is_explicit_and_round_trips() -> None: np.testing.assert_array_equal(xyzw_to_wxyz(xyzw), wxyz) -def test_config_rejects_ambiguous_wxyz_orientation_key() -> None: - values = dict(OmegaConf.load(CONFIG_PATH).backend) - values["base_orientation_wxyz"] = values.pop("base_orientation_xyzw") +def test_from_mapping_accepts_ready_scene_without_reconstructing_it() -> None: + values = _mapping() + robot = values["scene"].objs.robot + robot.translation = (1.0, -0.5, 2.0) + robot.rotation = (0.0, 0.0, 1.0, 0.0) + config = SimulationRuntimeConfig.from_mapping(values) + assert config.scene is values["scene"] + assert config.scene.objs.robot is robot + assert robot.translation == (1.0, -0.5, 2.0) + assert robot.rotation == (0.0, 0.0, 1.0, 0.0) + assert config.physics.dt == values["physics"]["dt"] + + +def test_mapping_parses_all_canonical_physics_fields() -> None: + values = _mapping() + physics = {"dt": 0.004, "solver_iterations": 19, "solver_tolerance": 4e-7, "gravity": (0.2, -0.3, -5.0)} + values["physics"] = physics + config = SimulationRuntimeConfig.from_mapping(values) + assert isinstance(config.physics, SimCfg) + for name, value in physics.items(): + assert getattr(config.physics, name) == value + + +@pytest.mark.parametrize("field", ["dt", "solver_iterations"]) +def test_mapping_preserves_required_physics_fields(field) -> None: + values = _mapping() + del values["physics"][field] + with pytest.raises(ValidationError, match=field): + SimulationRuntimeConfig.from_mapping(values) + + +def test_mapping_rejects_unknown_physics_fields() -> None: + values = _mapping() + values["physics"]["unexpected_physics_setting"] = 1 + with pytest.raises(TypeError, match="unexpected_physics_setting"): + SimulationRuntimeConfig.from_mapping(values) + + +@pytest.mark.parametrize( + "field,value", [("dt", 0), ("solver_iterations", -1), ("solver_tolerance", 0), ("gravity", (1, 2))] +) +def test_backend_config_validates_physics_at_public_boundary(field, value) -> None: + with pytest.raises(ValueError, match=f"sim.{field}"): + SimulationRuntimeConfig(scene=build_scene(), physics=SimCfg(**{field: value})) + + +@pytest.mark.parametrize( + "scene", + [None, "flat", [], {}, {"_target_": "motrix_env_core.config.scene.SceneCfg"}], +) +def test_from_mapping_rejects_unassembled_scene_at_backend_boundary(scene: object) -> None: + values = _mapping() + values["scene"] = scene + with pytest.raises(ValidationError, match="runtime.scene.*complete SceneCfg"): + SimulationRuntimeConfig.from_mapping(values) - with pytest.raises(ValidationError, match="base_orientation_xyzw"): - MujocoBackendConfig.from_mapping(values) + +@pytest.mark.parametrize("sensor_name", [7, "", " "]) +def test_state_sensor_mapping_validates_model_local_names(sensor_name: object) -> None: + values = _mapping() + values["sensor_bindings"]["base_angular_velocity"] = sensor_name + + with pytest.raises((TypeError, ValueError), match="base_angular_velocity"): + SimulationRuntimeConfig.from_mapping(values) + + +def test_state_sensor_mapping_rejects_unknown_roles() -> None: + values = _mapping() + values["sensor_bindings"]["unexpected_role"] = "gyro" + with pytest.raises(TypeError, match="unexpected_role"): + SimulationRuntimeConfig.from_mapping(values) + + +@pytest.mark.parametrize("role", ["base_angular_velocity", "base_linear_acceleration", "base_linear_velocity"]) +def test_missing_state_sensor_fails_at_model_binding(role: str) -> None: + config = _config() + config = replace(config, sensor_bindings=replace(config.sensor_bindings, **{role: None})) + backend = create_runtime(config, control_period_s=CONTROL_PERIOD_S) + with pytest.raises(ValidationError, match=f"runtime.sensor_bindings.{role}"): + backend.open() + backend.close() def test_initial_state_command_step_contract_is_headless() -> None: spec = _spec() backend = _backend() viewer_modules_before = {name for name in sys.modules if name.startswith("mujoco.viewer")} - backend.open(spec) + backend.open() - initial = backend.read_state(timeout_s=0.1) - assert backend._data is not None - assert backend._data.ncon == 0 + initial = backend.robot.read_state(timeout_s=0.1) + assert backend.data is not None + assert backend.data.ncon == 0 target = spec.default_joint_position + np.float32(0.01) - backend.write_command(_command(target)) - stepped = backend.read_state(timeout_s=0.1) - backend.stop() + backend.robot.write_command(_command(target)) + backend.advance_control_period() + stepped = backend.robot.read_state(timeout_s=0.1) + backend.robot.stop() backend.close() - backend.stop() + backend.robot.stop() backend.close() np.testing.assert_allclose(initial.joint_position, spec.default_joint_position, atol=1e-6) np.testing.assert_array_equal(initial.base_orientation_xyzw, [0.0, 0.0, 0.0, 1.0]) assert stepped.sample_time_ns == 20_000_000 assert np.isfinite(stepped.joint_position).all() - assert backend.capabilities.max_command_rate_hz == 50.0 + # The robot's physical command-rate limit is independent of controller cadence. + assert backend.robot.capabilities.max_command_rate_hz == pytest.approx(1.0 / backend.sim.dt) assert {name for name in sys.modules if name.startswith("mujoco.viewer")} == viewer_modules_before @@ -172,13 +281,14 @@ def viewer_factory(mujoco: Any, camera_config: SystemCameraCfg) -> FakeViewer: spec = _spec() assert backend.get_keyboard_device() is viewer.keyboard_device - assert camera_configs == [backend._scene.system_camera] - backend.open(spec) - initial = backend.read_state(timeout_s=0.1) - backend.write_command(_command(spec.default_joint_position)) - stepped = backend.read_state(timeout_s=0.1) - - assert backend.capabilities.supports_rendering is True + assert camera_configs == [backend.scene.system_camera] + backend.open() + initial = backend.robot.read_state(timeout_s=0.1) + backend.robot.write_command(_command(spec.default_joint_position)) + backend.advance_control_period() + stepped = backend.robot.read_state(timeout_s=0.1) + + assert backend.robot.capabilities.supports_rendering is True assert viewer.open_count == 1 assert initial.sample_time_ns == 0 assert stepped.sample_time_ns == 20_000_000 @@ -186,11 +296,11 @@ def viewer_factory(mujoco: Any, camera_config: SystemCameraCfg) -> FakeViewer: viewer.running = False with pytest.raises(KeyboardInterrupt, match="viewer was closed"): - backend.read_state(timeout_s=0.1) + backend.robot.read_state(timeout_s=0.1) backend.close() assert viewer.close_count == 1 - backend.open(spec) + backend.open() assert backend.get_keyboard_device() is viewer.keyboard_device assert viewer.open_count == 2 backend.close() @@ -205,82 +315,261 @@ def test_headless_backend_does_not_provide_keyboard_input() -> None: backend.get_keyboard_device() -def test_swapped_joint_contract_fails_during_open() -> None: - swapped = list(JOINT_NAMES) - swapped[0], swapped[1] = swapped[1], swapped[0] +def test_swapped_artifact_joint_contract_fails_compatibility_comparison() -> None: backend = _backend() + actual = backend.robot.spec + swapped = list(actual.joint_names) + swapped[0], swapped[1] = swapped[1], swapped[0] + expected = replace(actual, joint_names=tuple(swapped)) - with pytest.raises(ValidationError, match="ctrl_lower"): - backend.open(_spec(tuple(swapped))) + with pytest.raises(ValidationError, match="joint_names"): + expected.validate_compatible(actual) -def test_missing_joint_contract_fails_during_open() -> None: - joint_names = list(JOINT_NAMES) +def test_artifact_base_link_must_match_selected_robot() -> None: + backend = _backend() + actual = backend.robot.spec + expected = replace(actual, base_link_name="wrong_base") + with pytest.raises(ValidationError, match="base_link_name"): + expected.validate_compatible(actual) + + +def test_missing_artifact_joint_fails_compatibility_comparison() -> None: + backend = _backend() + actual = backend.robot.spec + joint_names = list(actual.joint_names) joint_names[0] = "missing_joint" + expected = replace(actual, joint_names=tuple(joint_names)) + + with pytest.raises(ValidationError, match="joint_names"): + expected.validate_compatible(actual) + + +@pytest.mark.parametrize( + ("role", "sensor_name", "message"), + [ + ("base_angular_velocity", "missing_gyro", "missing_gyro"), + ("base_angular_velocity", "global_angvel", "mjSENS_GYRO"), + ("base_linear_acceleration", "gyro", "mjSENS_ACCELEROMETER"), + ("base_linear_velocity", "local_linvel", "mjSENS_FRAMELINVEL"), + ("base_linear_velocity", "FR_global_linvel", "on the base body"), + ], +) +def test_invalid_state_sensor_mapping_fails_during_open(role: str, sensor_name: str, message: str) -> None: + config = _config() + config = replace(config, sensor_bindings=replace(config.sensor_bindings, **{role: sensor_name})) + backend = create_runtime(config, control_period_s=CONTROL_PERIOD_S) + + with pytest.raises(ValidationError, match=message): + backend.open() + backend.close() + + +@pytest.mark.parametrize("role", ["base_angular_velocity", "base_linear_acceleration"]) +def test_imu_sensor_must_use_base_body_frame(role: str, monkeypatch: pytest.MonkeyPatch) -> None: backend = _backend() + build_model = backend.build_model + + def rotated_imu_model() -> Any: + model = build_model() + sensor_id = backend.mj.mj_name2id( + model, + backend.mj.mjtObj.mjOBJ_SENSOR, + backend.robot._cfg.resolve_name(getattr(backend.config.sensor_bindings, role)), + ) + site_id = model.sensor_objid[sensor_id] + model.site_quat[site_id] = [0.7071067812, 0.0, 0.0, 0.7071067812] + return model + + monkeypatch.setattr(backend, "build_model", rotated_imu_model) + with pytest.raises(ValidationError, match="aligned with its body frame"): + backend.open() + backend.close() - with pytest.raises(ValidationError, match="missing_joint"): - backend.open(_spec(tuple(joint_names))) +def test_sensor_bindings_resolve_robot_names_and_are_cached(monkeypatch: pytest.MonkeyPatch) -> None: + config = _config() + config.scene.objs.robot.prefix = "robot_" + config.scene.objs.robot.suffix = "_instance" + backend = create_runtime(config, control_period_s=CONTROL_PERIOD_S) + assert backend.robot.spec.base_link_name == "base" + assert backend.robot.spec.joint_names == JOINT_NAMES + backend.open() + + for index, name in enumerate(backend.robot.spec.joint_names): + joint = backend.model.joint(backend.robot._cfg.resolve_name(name)) + assert backend.robot._joint_qpos_indices[index] == joint.qposadr[0] + assert backend.robot._joint_qvel_indices[index] == joint.dofadr[0] + actuator = backend.robot._actuator_indices[index] + assert backend.model.actuator_trnid[actuator, 0] == joint.id + assert backend.robot._base_body_id == backend.model.body(backend.robot._cfg.resolve_name("base")).id + + def unexpected_lookup(*args: Any) -> int: + pytest.fail("State reads must use bindings cached during open, not name lookups") + + monkeypatch.setattr(backend.mj, "mj_name2id", unexpected_lookup) + assert backend.data is not None + backend.data.qvel[:6] = [0.3, -0.2, 0.1, 0.4, -0.5, 0.6] + backend.mj.mj_forward(backend.model, backend.data) + state = backend.robot.read_state(0.1) + for role in ("base_angular_velocity", "base_linear_acceleration", "base_linear_velocity"): + np.testing.assert_allclose(getattr(state, role), backend.data.sensordata[backend.robot._sensor_slices[role]]) + np.testing.assert_allclose(state.base_position, backend.model.qpos0[:3]) + np.testing.assert_allclose(state.base_angular_velocity, [0.4, -0.5, 0.6], atol=1e-6) + backend.close() -def test_actuator_force_range_mismatch_fails_during_open() -> None: - spec = replace(_spec(), torque_limit=np.full(12, 23.0, dtype=np.float32)) + +def test_actuator_force_range_mismatch_fails_during_open(monkeypatch: pytest.MonkeyPatch) -> None: backend = _backend() + prepare = backend.robot.prepare + + def invalid_actuator_model(source_model: Any, model_spec: Any) -> None: + source_model.actuator_forcerange[:, 0] = -23.0 + prepare(source_model, model_spec) + monkeypatch.setattr(backend.robot, "prepare", invalid_actuator_model) with pytest.raises(ValidationError, match="force_lower"): - backend.open(spec) + backend.open() + backend.close() def test_open_converts_actuators_and_uses_scene_cfg_ground(caplog: pytest.LogCaptureFixture) -> None: spec = _spec() backend = _backend() - with caplog.at_level(logging.INFO, logger="motrix_deploy_mujoco.interface"): - backend.open(spec) + with caplog.at_level(logging.INFO, logger="motrix_deploy_mujoco"): + backend.open() - model = backend._model + model = backend.model assert model is not None assert model.opt.timestep == pytest.approx(0.002) assert model.opt.iterations == 100 - floor_id = backend._mj.mj_name2id(model, backend._mj.mjtObj.mjOBJ_GEOM, "floor") + floor_id = backend.mj.mj_name2id(model, backend.mj.mjtObj.mjOBJ_GEOM, "floor") assert floor_id >= 0 np.testing.assert_allclose(model.geom_friction[floor_id], [0.6, 0.005, 0.0001]) np.testing.assert_array_equal( - model.actuator_biastype[backend._actuator_indices], - backend._mj.mjtBias.mjBIAS_NONE, + model.actuator_biastype[backend.robot._actuator_indices], + backend.mj.mjtBias.mjBIAS_NONE, ) - np.testing.assert_allclose(model.actuator_gainprm[backend._actuator_indices, 0], 1.0) + np.testing.assert_allclose(model.actuator_gainprm[backend.robot._actuator_indices, 0], 1.0) np.testing.assert_allclose( - model.actuator_ctrlrange[backend._actuator_indices], + model.actuator_ctrlrange[backend.robot._actuator_indices], np.column_stack((-spec.torque_limit, spec.torque_limit)), ) assert "Converting 12 actuators from position servos to torque motors" in caplog.text - assert "Built MuJoCo deployment model from SceneCfg go2-walk-rough" in caplog.text - backend.close() - - -def test_rough_scene_uses_configured_initial_base_position() -> None: - config = _config() - backend = MujocoRobotInterface(config, control_period_s=CONTROL_PERIOD_S) - backend.open(_spec()) - - initial = backend.read_state(timeout_s=0.1) - - assert initial.base_position == pytest.approx(config.base_position) + assert "Built MuJoCo deployment model from SceneCfg" in caplog.text backend.close() -def test_flat_scene_uses_nonpenetrating_initial_base_position() -> None: +def test_direct_scene_cfg_headless_uses_supplied_robot_floor_and_placement() -> None: + robot = UnitreeGo2Robot(translation=(1.0, -0.5, 0.155), rotation=(0.0, 0.0, 1.0, 0.0)) + scene = SceneCfg(objs=AdapterSceneObjsCfg(robot=robot, floor=FlatTerrainCfg(height=0.2))) + config = replace(_config(), scene=scene) + # Direct scenes have no training ctrl_dt: only integer physics substeps are required. + backend = create_runtime(config, control_period_s=0.01) + assert backend.robot._cfg is not robot + backend.open() + try: + state = backend.robot.read_state(0.1) + assert backend.robot.capabilities.privileged_state_fields == frozenset( + {"base_position", "base_linear_velocity"} + ) + np.testing.assert_allclose(state.base_position, backend.model.qpos0[:3]) + np.testing.assert_allclose(state.base_orientation_xyzw, robot.rotation) + floor_id = backend.mj.mj_name2id(backend.model, backend.mj.mjtObj.mjOBJ_GEOM, "floor") + assert backend.model.geom_pos[floor_id, 2] == pytest.approx(0.2) + backend.robot.write_command(_command(_spec().default_joint_position)) + backend.advance_control_period() + assert backend.robot.read_state(0.1).sample_time_ns == 10_000_000 + finally: + backend.close() + + +@pytest.mark.parametrize("robot_type", [UnitreeGo2Robot]) +def test_robot_description_switch_needs_no_backend_specific_wiring(robot_type) -> None: + robot = robot_type() + scene = SceneCfg(objs=AdapterSceneObjsCfg(robot=robot, floor=FlatTerrainCfg())) + source = MuJoCoSceneCompiler().create_spec(scene, SimCfg()).compile() + joints = tuple(robot.key_pose.joint_names) + actuators = np.asarray([np.flatnonzero(source.actuator_trnid[:, 0] == source.joint(name).id)[0] for name in joints]) + spec = RobotSpec( + base_link_name=robot.base_link_name, + joint_names=joints, + default_joint_position=np.asarray(robot.key_pose.poses[robot.init_key_pose], dtype=np.float32), + position_lower=source.actuator_ctrlrange[actuators, 0].astype(np.float32), + position_upper=source.actuator_ctrlrange[actuators, 1].astype(np.float32), + torque_limit=source.actuator_forcerange[actuators, 1].astype(np.float32), + ) + config = replace(_config(), scene=scene) + backend = create_runtime(config, control_period_s=CONTROL_PERIOD_S) + spec.validate_compatible(backend.robot.spec) + backend.open() + try: + state = backend.robot.read_state(0.1) + np.testing.assert_allclose(state.joint_position, spec.default_joint_position) + zeros = np.zeros(spec.joint_count, dtype=np.float32) + backend.robot.write_command(JointServoCommand(spec.default_joint_position, zeros, zeros, zeros, zeros)) + backend.advance_control_period() + assert backend.robot.read_state(0.1).sample_time_ns > state.sample_time_ns + assert backend.robot.health().healthy + finally: + backend.close() + + +def test_fallen_pose_does_not_change_resource_health() -> None: config = _config(FLAT_CONFIG_PATH) - backend = MujocoRobotInterface(config, control_period_s=CONTROL_PERIOD_S) - backend.open(_spec()) - - initial = backend.read_state(timeout_s=0.1) - - assert config.scene == "go2-walk-flat" - assert initial.base_position == pytest.approx(config.base_position) - assert backend._data is not None - assert backend._data.ncon == 0 - backend.close() + config.scene.objs.robot.translation = (0.0, 0.0, 0.545) + config.scene.objs.robot.rotation = (1.0, 0.0, 0.0, 0.0) + backend = create_runtime(config, control_period_s=CONTROL_PERIOD_S) + backend.open() + try: + backend.robot.write_command(_command(_spec().default_joint_position, kp=0.0, kd=0.0)) + backend.advance_control_period() + assert backend.robot.health().healthy + finally: + backend.close() + + +def test_direct_scene_recipe_runs_with_training_imports_blocked() -> None: + script = textwrap.dedent( + """ + import importlib.abc + import sys + class BlockTraining(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, path=None, target=None): + if fullname == 'motrix_envs' or fullname.startswith('motrix_envs.'): + raise AssertionError('Direct deployment must not import training environments') + sys.meta_path.insert(0, BlockTraining()) + from omegaconf import OmegaConf + from motrix_deploy_mujoco import MujocoRuntime + from motrix_deploy.runtime.config import SimulationRuntimeConfig + import numpy as np + from motrix_env_core.config import configclass + from motrix_env_core.config.scene import SceneCfg, SceneObjsCfg, FlatTerrainCfg + @configclass + class FlatWorldObjsCfg(SceneObjsCfg): + floor: FlatTerrainCfg = FlatTerrainCfg() + from motrix_robots.unitree import UnitreeGo2Robot + values = OmegaConf.to_container(OmegaConf.load(sys.argv[1]).runtime) + values.pop('backend') + values.pop('deploy_env_id') + values.pop('robot_id') + robot = UnitreeGo2Robot( + translation=tuple(values.pop('robot_translation')), + rotation=tuple(values.pop('robot_rotation')), + ) + values['scene'] = SceneCfg(objs=FlatWorldObjsCfg(robot=robot, floor=FlatTerrainCfg())) + config = SimulationRuntimeConfig.from_mapping(values) + backend = MujocoRuntime(config, control_period_s=0.02) + backend.open() + assert np.isfinite(backend.robot.read_state(0.1).joint_position).all() + backend.close() + assert not any(name == 'motrix_envs' or name.startswith('motrix_envs.') for name in sys.modules) + """ + ) + result = subprocess.run( + [sys.executable, "-c", script, str(FLAT_CONFIG_PATH)], capture_output=True, text=True, check=False + ) + assert result.returncode == 0, result.stdout + result.stderr def test_hybrid_pd_command_is_recomputed_as_torque_each_physics_substep( @@ -288,14 +577,14 @@ def test_hybrid_pd_command_is_recomputed_as_torque_each_physics_substep( ) -> None: spec = _spec() backend = _backend() - backend.open(spec) - initial = backend.read_state(0.1) + backend.open() + initial = backend.robot.read_state(0.1) desired_position = initial.joint_position + np.linspace(0.01, 0.02, 12, dtype=np.float32) desired_velocity = np.linspace(-0.3, 0.3, 12, dtype=np.float32) feedforward_torque = np.linspace(-1.0, 1.0, 12, dtype=np.float32) kp = np.linspace(10.0, 21.0, 12, dtype=np.float32) kd = np.linspace(0.1, 1.2, 12, dtype=np.float32) - command = RobotCommand( + command = JointServoCommand( joint_position=desired_position, joint_velocity=desired_velocity, feedforward_torque=feedforward_torque, @@ -303,23 +592,24 @@ def test_hybrid_pd_command_is_recomputed_as_torque_each_physics_substep( kd=kd, ) snapshots: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = [] - mj_step = backend._mj.mj_step + mj_step = backend.mj.mj_step def record_step(model: object, data: object) -> None: snapshots.append( ( - data.qpos[backend._joint_qpos_indices].copy(), - data.qvel[backend._joint_qvel_indices].copy(), - data.ctrl[backend._actuator_indices].copy(), + data.qpos[backend.robot._joint_qpos_indices].copy(), + data.qvel[backend.robot._joint_qvel_indices].copy(), + data.ctrl[backend.robot._actuator_indices].copy(), ) ) mj_step(model, data) - monkeypatch.setattr(backend._mj, "mj_step", record_step) + monkeypatch.setattr(backend.mj, "mj_step", record_step) - backend.write_command(command) + backend.robot.write_command(command) + backend.advance_control_period() - assert backend._data is not None + assert backend.data is not None assert len(snapshots) == 10 for position, velocity, applied_torque in snapshots: expected_torque = np.clip( @@ -329,7 +619,7 @@ def record_step(model: object, data: object) -> None: ) np.testing.assert_allclose(applied_torque, expected_torque, atol=1e-6) assert not np.array_equal(snapshots[0][2], snapshots[1][2]) - assert backend.read_state(0.1).sample_time_ns == 20_000_000 - backend.stop() - np.testing.assert_array_equal(backend._data.ctrl[backend._actuator_indices], 0.0) + assert backend.robot.read_state(0.1).sample_time_ns == 20_000_000 + backend.robot.stop() + np.testing.assert_array_equal(backend.data.ctrl[backend.robot._actuator_indices], 0.0) backend.close() diff --git a/motrix_deploy_mujoco/tests/test_scene_session.py b/motrix_deploy_mujoco/tests/test_scene_session.py new file mode 100644 index 00000000..a4df8a3a --- /dev/null +++ b/motrix_deploy_mujoco/tests/test_scene_session.py @@ -0,0 +1,371 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Complete scene ownership, absolute placement and runtime lifecycle contracts.""" + +from dataclasses import replace + +import numpy as np +import pytest +from motrix_robots.unitree import UnitreeGo2Robot +from scene_helpers import AdapterSceneObjsCfg, build_scene +from test_mujoco_deploy_backend import _command, _spec, create_runtime + +from motrix_deploy.errors import ValidationError +from motrix_deploy.runtime.config import SensorBindings, SimulationRuntimeConfig +from motrix_env_core.config import configclass +from motrix_env_core.config.scene import FlatTerrainCfg, SceneCfg, SceneObjsCfg, SystemCameraCfg +from motrix_env_core.config.sim import SimCfg +from motrix_env_mujoco.compiler import MuJoCoSceneCompiler + + +@configclass +class CustomSceneObjsCfg(SceneObjsCfg): + platform: FlatTerrainCfg = FlatTerrainCfg(height=0.2) + + +def _config(scene=None): + return SimulationRuntimeConfig( + scene=build_scene() if scene is None else scene, + physics=SimCfg(dt=0.002, solver_iterations=20), + sensor_bindings=SensorBindings( + base_angular_velocity="gyro", + base_linear_acceleration="accelerometer", + base_linear_velocity="global_linvel", + ), + ) + + +def test_complete_scene_copies_all_fields_without_reconstructing_derived_contacts() -> None: + @configclass + class DerivedScene(SceneCfg): + contact_name: str | None = None + + def __post_init__(self): + self.contact_name = self.objs.robot.resolved_base_link_name + + robot = UnitreeGo2Robot(translation=(4, 5, 6), rotation=(0, 0, 1, 0), prefix="deployment_") + source = DerivedScene(objs=CustomSceneObjsCfg(robot=robot), system_camera=SystemCameraCfg(distance=4.0)) + backend = create_runtime(_config(source), control_period_s=0.02) + assert backend.robot.spec.base_link_name == robot.base_link_name + assert backend.robot.spec.joint_names == tuple(robot.key_pose.joint_names) + scene = backend.scene + assert type(scene.objs) is CustomSceneObjsCfg + assert scene.objs.platform is not source.objs.platform + assert scene.assets is not source.assets and scene.sensors is not source.sensors + assert scene.system_camera is not source.system_camera + assert scene.system_camera.distance == source.system_camera.distance + assert scene.contact_name == source.contact_name == "deployment_base" + assert robot.translation == (4, 5, 6) and robot.rotation == (0, 0, 1, 0) + assert scene.objs.robot.translation == robot.translation and scene.objs.robot.rotation == robot.rotation + scene.objs.platform.height = 9 + assert source.objs.platform.height == 0.2 + + +def test_complete_scene_reuses_one_copy_without_inserting_another_robot() -> None: + robot = UnitreeGo2Robot(translation=(4, 5, 6), rotation=(0, 0, 1, 0)) + scene = SceneCfg(objs=AdapterSceneObjsCfg(robot=robot, floor=FlatTerrainCfg())) + backend = create_runtime(_config(scene), control_period_s=0.02) + assert backend.scene is not scene + assert len(list(backend.scene.iter_objs())) == 2 + backend.open() + try: + assert backend.model.njnt == 13 + np.testing.assert_allclose(backend.robot.read_state(0.1).base_orientation_xyzw, robot.rotation, atol=1e-6) + finally: + backend.close() + assert robot.translation == (4, 5, 6) + + +def test_file_scene_and_custom_objects_preserved_placement_has_no_double_transform(tmp_path) -> None: + path = tmp_path / "world.xml" + path.write_text( + '' + ) + robot = UnitreeGo2Robot(translation=(5, 6, 7), rotation=(0.6, 0, 0, 0.8)) + scene = SceneCfg(file=path, objs=CustomSceneObjsCfg(robot=robot), system_camera=SystemCameraCfg(distance=3)) + config = _config(scene) + backend = create_runtime(config, control_period_s=0.02) + backend.open() + try: + state = backend.robot.read_state(0.1) + np.testing.assert_allclose(state.base_position, backend.model.qpos0[:3]) + np.testing.assert_allclose(state.base_orientation_xyzw, robot.rotation, atol=1e-6) + np.testing.assert_allclose(backend.model.geom("obstacle").pos, [5, 6, 7]) + assert backend.model.geom("platform").pos[2] == pytest.approx(0.2) + assert state.sample_time_ns == 0 + assert backend.robot.read_state(0.1).receive_time_ns >= state.receive_time_ns + backend.robot.write_command(_command(_spec().default_joint_position)) + backend.advance_control_period() + assert backend.robot.read_state(0.1).sample_time_ns == 20_000_000 + finally: + backend.close() + assert scene.file == path and scene.objs.robot is robot + assert robot.translation == (5, 6, 7) and robot.rotation == (0.6, 0, 0, 0.8) + + +@pytest.mark.parametrize("rotation", [None, (0.6, 0.0, 0.0, 0.8)]) +def test_robot_attach_transform_and_init_key_pose_are_reset_once(rotation) -> None: + import mujoco + + from motrix_deploy.runtime.config import SimulationRuntimeConfig + from motrix_deploy.runtime.factory import create_simulation_runtime + + robot = UnitreeGo2Robot() + scene = SceneCfg(objs=AdapterSceneObjsCfg(robot=robot)) + intrinsic = MuJoCoSceneCompiler().create_spec(scene, SimCfg()).compile().qpos0[:7].copy() + translation = (1.2, -0.7, 2.1) + robot.translation = translation + robot.rotation = rotation + robot.key_pose.poses["custom"] = tuple(np.asarray(robot.key_pose.poses["default"]) + 0.01) + robot.init_key_pose = "custom" + attach_quat = np.asarray((0, 0, 0, 1) if rotation is None else rotation)[[3, 0, 1, 2]] + rotated_position = np.empty(3) + expected_quat = np.empty(4) + mujoco.mju_rotVecQuat(rotated_position, intrinsic[:3], attach_quat) + mujoco.mju_mulQuat(expected_quat, attach_quat, intrinsic[3:]) + expected_position = np.asarray(translation) + rotated_position + runtime = create_simulation_runtime( + "mujoco", SimulationRuntimeConfig(scene=scene, sensor_bindings=_config().sensor_bindings) + ) + with runtime: + for _ in range(2): + state = runtime.robot.read_state(0.1) + np.testing.assert_allclose(state.base_position, expected_position, atol=1e-6) + np.testing.assert_allclose(state.base_orientation_xyzw, expected_quat[[1, 2, 3, 0]], atol=1e-6) + np.testing.assert_allclose(state.joint_position, robot.key_pose.poses["custom"], atol=1e-6) + runtime.data.qpos[:] = 0.0 + runtime.data.qvel[:] = 1.0 + runtime.reset(runtime.robot._joint_qpos_indices, runtime.robot.spec.default_joint_position) + np.testing.assert_array_equal(runtime.data.qvel, 0.0) + assert scene.objs.robot.translation == translation and scene.objs.robot.rotation == rotation + + +def test_environment_scene_is_resolved_by_application_before_backend() -> None: + import motrix_envs # noqa: F401 registers configurations for this application test + from motrix_env_core import registry + + source = registry.make_env_config("go2-walk-rough", mode="play").scene + backend = create_runtime(_config(source), control_period_s=0.02) + backend.open() + try: + assert backend.model.njnt == 13 + assert backend.scene is not source + np.testing.assert_allclose(backend.robot.read_state(0.1).base_position, backend.model.qpos0[:3]) + finally: + backend.close() + + +@pytest.mark.parametrize("failure", ["prepare", "bind", "viewer"]) +def test_failed_open_releases_session_and_viewer_idempotently(failure, monkeypatch) -> None: + class Viewer: + running = False + closes = 0 + + def open(self, model, data): + self.running = True + if failure == "viewer": + raise ValueError("viewer failed") + + def close(self): + self.closes += 1 + self.running = False + + def is_running(self): + return self.running + + def sync(self): + pass + + viewer = Viewer() + config = _config() + if failure == "bind": + config = replace(config, sensor_bindings=SensorBindings()) + backend = create_runtime(config, control_period_s=0.02, render=True, viewer_factory=lambda *args: viewer) + if failure == "prepare": + prepare = backend.robot.prepare + + def invalid_actuator_model(source_model, model_spec): + source_model.actuator_forcerange[:, 0] = -23.0 + prepare(source_model, model_spec) + + monkeypatch.setattr(backend.robot, "prepare", invalid_actuator_model) + with pytest.raises((ValidationError, ValueError)): + backend.open() + assert backend.model is None and backend.data is None + assert not backend.robot.health().healthy + assert not viewer.running and viewer.closes == 1 + backend.close() + backend.close() + assert viewer.closes == 1 + with pytest.raises(RuntimeError, match="not open"): + backend.robot.read_state(0.1) + + +def test_robot_io_lifecycle_preserves_world_and_rebinds_on_runtime_reopen(control_scene_config) -> None: + from motrix_deploy.contracts import JointTorqueCommand + from motrix_deploy.robot.interface import RobotInterface + from motrix_deploy_mujoco.runtime import MujocoRuntime + + runtime = MujocoRuntime(control_scene_config, control_period_s=0.02) + robot = runtime.robot + assert isinstance(robot, RobotInterface) + spec = robot.spec + with pytest.raises(RuntimeError, match="not open"): + robot.open() + + with runtime: + robot.open() + model, data = runtime.model, runtime.data + robot.write_command(JointTorqueCommand(np.ones(spec.joint_count, dtype=np.float32))) + runtime.advance_control_period() + time_before_close = data.time + robot.close() + robot.close() + robot.stop() + assert runtime.opened + assert runtime.model is model and runtime.data is data + assert data.time == time_before_close + np.testing.assert_array_equal(data.ctrl, 0.0) + assert not robot.health().healthy + with pytest.raises(RuntimeError, match="not open"): + robot.read_state(0.1) + with pytest.raises(RuntimeError, match="not open"): + robot.open() + + assert runtime.model is None and runtime.data is None + with runtime: + assert runtime.robot is robot and robot.spec is spec + robot.open() + assert robot.health().healthy + state = robot.read_state(0.1) + assert state.sample_time_ns == 0 + np.testing.assert_array_equal(state.joint_position, spec.default_joint_position) + assert not robot.health().healthy + with pytest.raises(RuntimeError, match="not open"): + robot.read_state(0.1) + + +def test_runtime_requires_primary_scene_robot() -> None: + with pytest.raises(ValidationError, match="backend.scene.objs.robot"): + create_runtime(_config(SceneCfg()), control_period_s=0.02) + + +def test_physics_requires_integral_substeps() -> None: + with pytest.raises(ValidationError, match="integer multiple"): + create_runtime(_config(), control_period_s=0.003) + + +@pytest.mark.parametrize("gravity,tolerance", [((0.3, -0.2, -4.1), 2e-7), ((-0.1, 0.4, -6.3), 3e-6)]) +def test_public_factory_forwards_shared_physics_once_to_compiler(monkeypatch, gravity, tolerance) -> None: + from motrix_deploy.runtime.config import SimulationRuntimeConfig + from motrix_deploy.runtime.factory import create_simulation_runtime + + physics = SimCfg(dt=0.003, solver_iterations=17, solver_tolerance=tolerance, gravity=gravity) + config = _config() + calls = [] + original = MuJoCoSceneCompiler.create_spec + + def create_spec(compiler, scene, sim): + calls.append(sim) + return original(compiler, scene, sim) + + monkeypatch.setattr(MuJoCoSceneCompiler, "create_spec", create_spec) + runtime = create_simulation_runtime( + "mujoco", SimulationRuntimeConfig(scene=config.scene, physics=physics, sensor_bindings=config.sensor_bindings) + ) + assert runtime.config.physics is physics + assert runtime.config.sensor_bindings is config.sensor_bindings + with runtime: + assert calls == [physics] + assert calls[0] is physics + assert runtime.model.opt.timestep == physics.dt + assert runtime.model.opt.iterations == physics.solver_iterations + assert runtime.model.opt.tolerance == physics.solver_tolerance + np.testing.assert_allclose(runtime.model.opt.gravity, physics.gravity) + + +@pytest.mark.parametrize("render", [False, True]) +@pytest.mark.parametrize("realtime", [None, False, True]) +def test_direct_runtime_and_plugin_use_config_render_and_realtime(control_scene_config, render, realtime) -> None: + from motrix_deploy_mujoco.plugin import create_runtime as create_plugin_runtime + from motrix_deploy_mujoco.runtime import MujocoRuntime + + config = replace(control_scene_config, render=render, realtime=realtime) + direct = MujocoRuntime(config) + plugin = create_plugin_runtime(config) + for runtime in (direct, plugin): + assert runtime.config is config + assert (runtime.viewer is not None) == render + assert runtime.realtime is realtime + assert runtime.model is None and runtime.data is None + + +def test_artifact_recipe_normalizes_to_simulation_config(control_scene_config) -> None: + from motrix_deploy_mujoco.plugin import create_runtime as create_plugin_runtime + + config = control_scene_config + runtime_config = SimulationRuntimeConfig.from_mapping( + { + "name": "mujoco", + "scene": config.scene, + "physics": {"dt": config.physics.dt, "solver_iterations": config.physics.solver_iterations}, + "sensor_bindings": { + "base_angular_velocity": "gyro", + "base_linear_acceleration": "accelerometer", + "base_linear_velocity": "global_linvel", + }, + }, + render=True, + realtime=False, + ) + runtime = create_plugin_runtime(runtime_config) + assert isinstance(runtime.config, SimulationRuntimeConfig) + assert runtime.config.scene is config.scene + assert runtime.config.render is True and runtime.config.realtime is False + assert runtime.viewer is not None and runtime.realtime is False + assert runtime.robot.spec.joint_names == tuple(config.scene.objs.robot.key_pose.joint_names) + + +def test_public_simulation_factory_returns_model_derived_robot_spec() -> None: + from motrix_deploy.runtime.base import SimulationRuntime + from motrix_deploy.runtime.config import SimulationRuntimeConfig + from motrix_deploy.runtime.factory import create_simulation_runtime as create_selected_runtime + + config = _config() + runtime = create_selected_runtime( + "mujoco", SimulationRuntimeConfig(scene=config.scene, sensor_bindings=config.sensor_bindings) + ) + assert isinstance(runtime, SimulationRuntime) + assert runtime.config.scene is config.scene + spec = runtime.robot.spec + assert runtime.model is None and runtime.data is None + assert spec.joint_names == tuple(config.scene.objs.robot.key_pose.joint_names) + port = runtime.robot + assert runtime.robot is port and port.spec is spec + with runtime: + port.open() + np.testing.assert_array_equal(port.read_state(0.1).joint_position, spec.default_joint_position) + assert runtime.model is None and runtime.data is None + + +@pytest.mark.parametrize("field", ["default_joint_position", "position_lower", "position_upper", "torque_limit"]) +def test_artifact_robot_vectors_are_compared_against_derived_spec(field) -> None: + runtime = create_runtime(_config(), control_period_s=0.02) + actual = runtime.robot.spec + expected = replace(actual, **{field: getattr(actual, field) + np.float32(0.01)}) + with pytest.raises(ValidationError, match=field): + expected.validate_compatible(actual) + assert runtime.model is None and runtime.data is None + + +@pytest.mark.parametrize("field", ["default_joint_position", "position_lower", "position_upper", "torque_limit"]) +def test_artifact_robot_vector_comparison_uses_absolute_tolerance(field) -> None: + runtime = create_runtime(_config(), control_period_s=0.02) + actual = runtime.robot.spec + expected = replace(actual, **{field: getattr(actual, field) + np.float32(5e-7)}) + expected.validate_compatible(actual) + # Values such as torque limits must not get a scale-dependent relative tolerance. + expected = replace(actual, **{field: getattr(actual, field) + np.float32(5e-6)}) + with pytest.raises(ValidationError, match=field): + expected.validate_compatible(actual) diff --git a/motrix_deploy_mujoco/tests/test_simulation_driver.py b/motrix_deploy_mujoco/tests/test_simulation_driver.py new file mode 100644 index 00000000..545c785b --- /dev/null +++ b/motrix_deploy_mujoco/tests/test_simulation_driver.py @@ -0,0 +1,304 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Physics-owned control cadence, pacing, and shared lifecycle integration.""" + +import numpy as np +import pytest +from control_helpers import DummyServoTask + +from motrix_deploy.errors import ValidationError +from motrix_deploy.policy import NoOpPolicyRuntime +from motrix_deploy.runtime.control import ControlSession +from motrix_deploy_mujoco.runtime import MujocoRuntime +from motrix_env_core.input import ConstantPlanarVelocityBinding + + +@pytest.fixture +def driver(control_scene_config, control_gains): + config = control_scene_config + simulation = MujocoRuntime(config, control_period_s=0.02) + task = DummyServoTask(simulation.robot.spec, *control_gains) + interface = simulation.robot + control = ControlSession( + robot=interface, + task=task, + policy=NoOpPolicyRuntime(interface.spec.joint_count), + command_binding=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)), + period_s=simulation.control_period_s, + state_timeout_s=0.1, + ) + with simulation: + yield simulation, control, interface, task + + +def test_physics_substeps_and_control_sample_times(driver, monkeypatch): + simulation, control, interface, task = driver + events = [] + samples = [] + contexts = [] + apply = simulation.robot.apply_control + step = simulation.mj.mj_step + observe = task.build_observation + + def record_apply(): + events.append("pd") + apply() + + def record_step(model, data): + events.append("physics") + step(model, data) + + def record_observation(state, context): + events.append("tick") + samples.append(state.sample_time_ns) + contexts.append(context.elapsed_time_s) + return observe(state, context) + + monkeypatch.setattr(simulation.robot, "apply_control", record_apply) + monkeypatch.setattr(simulation.mj, "mj_step", record_step) + monkeypatch.setattr(task, "build_observation", record_observation) + # The driver must not delegate a full physics period to a control callback. + monkeypatch.setattr(simulation, "advance_control_period", lambda: pytest.fail("legacy advance called")) + result = simulation.run(control, steps=3) + assert result.success and result.completed_steps == 3 + assert samples == [0, 20_000_000, 40_000_000] + assert contexts == pytest.approx([0.0, 0.02, 0.04]) + assert events == (["tick"] + ["pd", "physics"] * simulation.physics_substeps) * 3 + assert result.simulation_time_s == pytest.approx(0.06) + assert simulation.data.time == pytest.approx(0.06) + assert simulation.model is not None and not interface.health().healthy + np.testing.assert_array_equal(simulation.data.ctrl, np.zeros(simulation.model.nu)) + + +def test_torque_control_session_preserves_control_and_physics_cadence(driver, monkeypatch): + from motrix_deploy.contracts import JointTorqueCommand + + simulation, _, interface, task = driver + samples = [] + torques = [] + observe = task.build_observation + step = simulation.mj.mj_step + desired = np.linspace(-1, 1, interface.spec.joint_count, dtype=np.float32) + + def record_observation(state, context): + samples.append(state.sample_time_ns) + return observe(state, context) + + def record_step(model, data): + torques.append(data.ctrl[simulation.robot._actuator_indices].copy()) + step(model, data) + + monkeypatch.setattr(task, "build_observation", record_observation) + monkeypatch.setattr(task, "process_action", lambda action: JointTorqueCommand(desired)) + monkeypatch.setattr(simulation.mj, "mj_step", record_step) + control = ControlSession( + robot=interface, + task=task, + policy=NoOpPolicyRuntime(interface.spec.joint_count), + command_binding=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)), + period_s=simulation.control_period_s, + state_timeout_s=0.1, + ) + result = simulation.run(control, steps=3) + assert result.success and result.completed_steps == 3 + assert samples == [0, 20_000_000, 40_000_000] + assert len(torques) == 3 * simulation.physics_substeps + for torque in torques: + np.testing.assert_array_equal(torque, desired) + assert result.simulation_time_s == pytest.approx(0.06) + np.testing.assert_array_equal(simulation.data.ctrl, 0) + + +def test_shared_tick_never_advances_physics(driver): + simulation, control, _, _ = driver + try: + assert control.start() + before = simulation.data.time + assert control.tick(elapsed_time_s=0.0) + assert simulation.data.time == before + finally: + control.stop() + + +def test_headless_driver_is_deterministic_and_never_paces(monkeypatch, control_scene_config, control_gains): + from motrix_deploy_mujoco import runtime + + monkeypatch.setattr(runtime, "RealtimeScheduler", lambda *args: pytest.fail("headless pacing")) + config = control_scene_config + traces = [] + for _ in range(2): + simulation = MujocoRuntime(config, control_period_s=0.02) + task = DummyServoTask(simulation.robot.spec, *control_gains) + interface = simulation.robot + with simulation: + control = ControlSession( + robot=interface, + task=task, + policy=NoOpPolicyRuntime(interface.spec.joint_count), + command_binding=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)), + period_s=simulation.control_period_s, + state_timeout_s=0.1, + ) + result = simulation.run(control, steps=4) + assert result.success and result.overrun_count == 0 + traces.append(result.trace_sha256) + assert traces[0] == traces[1] + + +def test_realtime_paces_every_complete_interval_without_extra_tick(driver, monkeypatch): + from motrix_deploy_mujoco import runtime + + simulation, control, _, _ = driver + waits = [] + + class Scheduler: + overrun_count = 2 + + def __init__(self, period_s): + assert period_s == simulation.control_period_s + + def reset(self): + waits.append("reset") + + def wait(self, step): + waits.append(step) + + monkeypatch.setattr(runtime, "RealtimeScheduler", Scheduler) + result = simulation.run(control, steps=3, realtime=True) + assert result.success and result.completed_steps == 3 + assert waits == ["reset", 1, 2, 3] + assert result.overrun_count == 2 + + +def test_physics_failure_cleans_robot_but_keeps_world_and_reports_actual_time(driver, monkeypatch): + simulation, control, interface, _ = driver + step = simulation.mj.mj_step + calls = 0 + + def fail_mid_interval(model, data): + nonlocal calls + calls += 1 + if calls == 3: + raise RuntimeError("physics failed") + step(model, data) + + monkeypatch.setattr(simulation.mj, "mj_step", fail_mid_interval) + result = simulation.run(control, steps=3) + assert not result.success and result.exit_reason == "backend_error" + assert result.error == "physics failed" + assert result.completed_steps == 1 + assert result.simulation_time_s == pytest.approx(2 * simulation.config.physics.dt) + assert result.real_time_factor == pytest.approx(result.simulation_time_s / result.wall_time_s) + assert simulation.model is not None and simulation.data is not None + assert not interface.health().healthy and not control.active + np.testing.assert_array_equal(simulation.data.ctrl, np.zeros(simulation.model.nu)) + control.stop() # Shared cleanup remains idempotent after driver failure. + + +def test_start_exception_still_cleans_shared_control(driver, monkeypatch): + simulation, control, interface, _ = driver + + def failed_start(): + raise RuntimeError("start failed") + + monkeypatch.setattr(control, "start", failed_start) + result = simulation.run(control, steps=3) + assert result.exit_reason == "backend_error" and result.error == "start failed" + assert result.completed_steps == 0 and result.simulation_time_s == 0.0 + assert simulation.model is not None and not interface.health().healthy + + +def test_elapsed_control_time_is_relative_to_this_run(driver, monkeypatch): + simulation, control, _, task = driver + simulation.advance_control_period() + before = simulation.data.time + contexts = [] + observe = task.build_observation + + def record_context(state, context): + contexts.append(context.elapsed_time_s) + return observe(state, context) + + monkeypatch.setattr(task, "build_observation", record_context) + result = simulation.run(control, steps=2) + assert result.success + assert contexts == pytest.approx([0.0, simulation.control_period_s]) + assert result.simulation_time_s == pytest.approx(2 * simulation.control_period_s) + assert simulation.data.time - before == pytest.approx(result.simulation_time_s) + + +def test_start_termination_does_not_advance_physics(driver, monkeypatch): + simulation, control, interface, task = driver + monkeypatch.setattr(task, "check_termination", lambda state: "done") + result = simulation.run(control, steps=3) + assert not result.success and result.exit_reason == "task_terminated" + assert result.completed_steps == 0 and result.simulation_time_s == 0.0 + assert simulation.data.time == 0.0 and not interface.health().healthy + + +def test_tick_termination_has_no_extra_physics_interval(driver, monkeypatch): + simulation, control, _, task = driver + monkeypatch.setattr(task, "check_termination", lambda state: "done" if state.sample_time_ns > 0 else None) + result = simulation.run(control, steps=3) + assert result.exit_reason == "task_terminated" and result.completed_steps == 1 + assert result.simulation_time_s == pytest.approx(simulation.control_period_s) + + +def test_viewer_interrupt_uses_shared_cleanup(driver, monkeypatch): + simulation, control, interface, _ = driver + + def interrupt(): + raise KeyboardInterrupt("viewer closed") + + monkeypatch.setattr(simulation, "check_viewer_running", interrupt) + result = simulation.run(control, steps=3) + assert result.exit_reason == "interrupted" and result.completed_steps == 0 + assert result.simulation_time_s == 0.0 and not interface.health().healthy + assert simulation.model is not None + + +@pytest.mark.parametrize("steps", [0, -1, True, 1.5]) +def test_run_rejects_invalid_tick_count(driver, steps): + simulation, control, _, _ = driver + with pytest.raises(ValidationError, match="steps"): + simulation.run(control, steps=steps) + + +def test_run_uses_supplied_control_session_period(driver): + simulation, _, interface, task = driver + control = ControlSession( + robot=interface, + task=task, + policy=NoOpPolicyRuntime(interface.spec.joint_count), + command_binding=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)), + period_s=0.01, + state_timeout_s=0.1, + ) + result = simulation.run(control, steps=2) + assert result.success + assert simulation.control_period_s == control.period_s + assert result.simulation_time_s == pytest.approx(2 * control.period_s) + + +def test_binding_rejects_nonintegral_period_without_binding_control(driver): + from types import SimpleNamespace + + simulation, _, _, _ = driver + period_s = simulation.control_period_s + substeps = simulation.physics_substeps + invalid = SimpleNamespace(period_s=simulation.sim.dt * 1.5, robot=simulation.robot) + with pytest.raises(ValidationError, match="control.period_s"): + simulation.bind_control_session(invalid) + assert simulation.control is None + assert simulation.control_period_s == period_s and simulation.physics_substeps == substeps + + +def test_run_requires_integral_control_period(driver): + simulation, _, _, _ = driver + # Physics cannot resample a control period that does not span whole substeps. + from types import SimpleNamespace + + mismatched = SimpleNamespace(period_s=simulation.sim.dt * 1.5, robot=simulation.robot) + with pytest.raises(ValidationError, match="control.period_s"): + simulation.run(mismatched, steps=1) diff --git a/motrix_deploy_mujoco/tests/test_viewer.py b/motrix_deploy_mujoco/tests/test_viewer.py index 764ca942..eca539a0 100644 --- a/motrix_deploy_mujoco/tests/test_viewer.py +++ b/motrix_deploy_mujoco/tests/test_viewer.py @@ -49,6 +49,7 @@ def __init__(self) -> None: self.poll_count = 0 self.destroy_count = 0 self.terminate_count = 0 + self.events: list[Any] = [] def init(self) -> bool: return True @@ -92,6 +93,9 @@ def set_scroll_callback(self, window: _Window, callback: Any) -> None: def poll_events(self) -> None: self.poll_count += 1 + events, self.events = self.events, [] + for event in events: + event() def window_should_close(self, window: _Window) -> bool: return window.should_close @@ -252,6 +256,108 @@ def test_glfw_escape_and_window_close_interrupt_input() -> None: viewer.close() +def test_render_event_pump_retains_keyboard_edges_for_command_input() -> None: + viewer, glfw, _ = _viewer() + device = viewer.keyboard_device + try: + glfw.events.append(lambda: glfw.callbacks["key"](glfw.window, glfw.KEY_A, 0, glfw.PRESS, 0)) + viewer.sync() + viewer.sync() + # Rendering delivers callbacks but never freezes or consumes input frames. + assert not device.is_key_down("a") + device.poll() + assert device.is_key_down("a") and device.is_pressing("a") + viewer.sync() + assert device.is_key_down("a") + device.poll() + assert not device.is_key_down("a") and device.is_pressing("a") + glfw.events.append(lambda: glfw.callbacks["key"](glfw.window, glfw.KEY_A, 0, glfw.RELEASE, 0)) + viewer.sync() + device.poll() + assert device.is_key_up("a") and not device.is_pressing("a") + finally: + viewer.close() + + +@pytest.mark.parametrize("event", ["escape", "close"]) +def test_render_event_pump_interrupts_without_keyboard_binding(event) -> None: + viewer, glfw, mujoco = _viewer() + try: + if event == "escape": + glfw.events.append(lambda: glfw.callbacks["key"](glfw.window, glfw.KEY_ESCAPE, 0, glfw.PRESS, 0)) + else: + glfw.events.append(lambda: glfw.callbacks["close"](glfw.window)) + with pytest.raises(KeyboardInterrupt): + viewer.sync() + assert glfw.poll_count == 1 + assert mujoco.render_count == 0 + finally: + viewer.close() + + +def test_gui_runtime_pumps_startup_camera_and_close_with_constant_input( + control_scene_config, control_gains, monkeypatch +) -> None: + from control_helpers import DummyServoTask + + from motrix_deploy.policy import NoOpPolicyRuntime + from motrix_deploy.runtime.control import ControlSession + from motrix_deploy_mujoco import runtime + from motrix_env_core.input import ConstantPlanarVelocityBinding + + glfw = _FakeGlfw() + mujoco = _FakeMujoco() + viewer = MujocoGlfwViewer(mujoco, glfw_module=glfw) + from dataclasses import replace + + simulation = runtime.MujocoRuntime(replace(control_scene_config, render=True), viewer_factory=lambda *_: viewer) + robot = simulation.robot + control = ControlSession( + robot=robot, + task=DummyServoTask(robot.spec, *control_gains), + policy=NoOpPolicyRuntime(robot.spec.joint_count), + command_binding=ConstantPlanarVelocityBinding((0.0, 0.0, 0.0)), + period_s=0.02, + state_timeout_s=0.1, + ) + waits = [] + + class Scheduler: + overrun_count = 0 + + def __init__(self, period_s): + assert period_s == simulation.control_period_s + + def reset(self): + pass + + def wait(self, step): + waits.append(step) + + monkeypatch.setattr(runtime, "RealtimeScheduler", Scheduler) + poll = glfw.poll_events + + def deliver_events(): + if glfw.poll_count == 1: + glfw.events.append(lambda: glfw.callbacks["scroll"](glfw.window, 0.0, 2.0)) + elif glfw.poll_count == 3: + glfw.events.append(lambda: glfw.callbacks["close"](glfw.window)) + poll() + + monkeypatch.setattr(glfw, "poll_events", deliver_events) + simulation.bind_control_session(control) + with simulation: + assert glfw.poll_count == 1 and glfw.swap_count == 1 + result = simulation.run(steps=10) + assert result.exit_reason == "interrupted" and result.completed_steps == 3 + assert result.simulation_time_s == pytest.approx(0.06) + assert waits == [1, 2] + assert mujoco.camera_moves == [(mujoco.mjtMouse.mjMOUSE_ZOOM, 0.0, -0.1)] + assert not control.active + assert glfw.destroy_count == 1 and glfw.terminate_count == 1 + assert mujoco.context.free_count == 1 and not viewer.is_running() + + def test_glfw_viewer_renders_and_moves_camera_without_stepping_physics() -> None: camera_config = SystemCameraCfg(lookat=(1.0, 2.0, 3.0), distance=4.0, elevation=-25.0, azimuth=120.0) viewer, glfw, mujoco = _viewer(camera_config) @@ -265,6 +371,7 @@ def test_glfw_viewer_renders_and_moves_camera_without_stepping_physics() -> None assert mujoco.update_count == 1 assert mujoco.render_count == 1 assert glfw.swap_count == 1 + assert glfw.poll_count == 1 glfw.mouse_buttons[glfw.MOUSE_BUTTON_LEFT] = glfw.PRESS glfw.callbacks["cursor"](glfw.window, 100.0, 200.0) diff --git a/motrix_deploy_tasks/README.md b/motrix_deploy_tasks/README.md index bc8e7283..f1655dec 100644 --- a/motrix_deploy_tasks/README.md +++ b/motrix_deploy_tasks/README.md @@ -1,23 +1,28 @@ # Motrix Deploy Tasks -`motrix_deploy_tasks` contains concrete, versioned task implementations. It publishes the `motrix-deploy` executable as a -thin bootstrap that imports these tasks before delegating to `motrix_deploy.cli`. -In the MotrixLab workspace, the bootstrap selects `configs/deploy/sim2sim/go2_walk_sim2sim.yaml` for `sim2sim` and -`configs/deploy/sim2real/go2_walk_flat_sim2real.yaml` for `sim2real`. Outside the workspace, -pass an explicit Hydra `--config-path` and `--config-name`. - -The default sim2sim recipe targets `go2-walk-rough`; the default sim2real recipe targets `go2-walk-flat`. From the workspace root, select the flat-terrain recipe while retaining the -workspace config path with: +`motrix_deploy_tasks` supplies the Go2 walking task, flat/rough deployment scenes, and installed Hydra recipes for +`motrix-deploy`. Install it alongside the MuJoCo runtime plugin to run an exported policy: ```bash -motrix-deploy sim2sim \ - --config-name go2_walk_flat_sim2sim \ - artifact=artifacts/go2-walk-flat.deploy +motrix-deploy task=go2-walk-flat/sim artifact=artifacts/go2-walk-flat.deploy runtime.viewer=false duration_s=2.0 ``` -The package currently provides the `go2_walk/v1` task used by the Go2 flat and rough walking environments. -Importing `motrix_deploy_tasks` registers its concrete implementations with the registry in `motrix_deploy.task`. The -artifact stores this versioned task name; the runtime resolves it to -`motrix_deploy_tasks.go2_walk.Go2WalkDeployTaskV1` for both sim2sim and sim2real. Deployment recipes select the backend and -input binding, while the artifact remains the authority for task selection. -The environment-to-artifact profile compiler is owned by `motrix_envs.deploy`, alongside the source environment config. +The recipes are packaged with the plugin; the command works outside the repository without `--config-path`. +`task=go2-walk-rough/sim` selects the rough scene, while `task=go2-walk-flat/hardware` selects the Unitree runtime. +Task defaults choose the `sim` or `hardware` Hydra runtime group. Target settings live directly under `runtime`; +`runtime.backend` is `mujoco` for simulation or `unitree_go2` for hardware. It is a scalar string selector, not a nested +configuration mapping or Hydra group. +Use an artifact exported for the matching task. Common settings are `artifact`, `duration_s`, and `command`; +`duration_s` budgets `ceil(duration_s / control_period_s)` control ticks, not wall-clock time. +Simulation exposes `runtime.viewer` and `runtime.realtime`; `runtime.realtime=null` follows `runtime.viewer`. +Hardware is always real-time internally and has neither field. For hardware networking, override +`runtime.network_interface=`. +Follow the [deployment tutorial](../docs/source/en/user_guide/tutorial/advanced/motrix_deploy.md) for physical operator +safety, startup/enable gates, and emergency stop procedures before commanding a robot. + +The artifact's `go2_walk/v1` specification defines observation preprocessing, action conversion, gains, command bounds, +and termination settings. `Go2WalkTaskSpec` and `Go2WalkDeployTaskV1` are defined in +`motrix_deploy_tasks.tasks.go2_walk`. Training profile compilers live in `motrix_envs.deploy`. + +For a scene/task/policy assembled directly in Python, see +[`examples/deploy_to_sim.py`](../examples/deploy_to_sim.py). diff --git a/motrix_deploy_tasks/pyproject.toml b/motrix_deploy_tasks/pyproject.toml index 9497b08f..d3cd6fc7 100644 --- a/motrix_deploy_tasks/pyproject.toml +++ b/motrix_deploy_tasks/pyproject.toml @@ -13,12 +13,18 @@ license = "Apache-2.0" dependencies = [ "motrix-deploy", "motrix-env-core", + "motrix-robots", + "hydra-core>=1.3,<1.4", "numpy>=1.26", ] -[project.optional-dependencies] -mujoco = ["motrix-deploy-mujoco"] -unitree = ["motrix-deploy-unitree"] +[project.entry-points."motrix_deploy.tasks"] +"go2_walk/v1" = "motrix_deploy_tasks.tasks.go2_walk:Go2WalkDeployTaskV1" -[project.scripts] -motrix-deploy = "motrix_deploy_tasks:main" +[project.entry-points."motrix_deploy.envs"] +flat = "motrix_deploy_tasks.envs.flat:build_scene" +rough = "motrix_deploy_tasks.envs.rough:build_scene" + +[tool.uv.build-backend] +module-name = ["motrix_deploy_tasks", "hydra_plugins"] +namespace = true diff --git a/motrix_deploy_tasks/src/hydra_plugins/motrix_deploy_tasks_searchpath.py b/motrix_deploy_tasks/src/hydra_plugins/motrix_deploy_tasks_searchpath.py new file mode 100644 index 00000000..6285285b --- /dev/null +++ b/motrix_deploy_tasks/src/hydra_plugins/motrix_deploy_tasks_searchpath.py @@ -0,0 +1,17 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Expose installed deployment recipes without importing task implementations.""" + +from hydra.core.config_search_path import ConfigSearchPath +from hydra.plugins.search_path_plugin import SearchPathPlugin + + +class MotrixDeployTasksSearchPathPlugin(SearchPathPlugin): + def manipulate_search_path(self, search_path: ConfigSearchPath) -> None: + # Deployment task options share Hydra's task group name with training; + # expose them only for the deployment application's configuration root. + if any( + entry.provider == "main" and entry.path == "pkg://motrix_deploy.config" for entry in search_path.get_path() + ): + search_path.append(provider="motrix-deploy-tasks", path="pkg://motrix_deploy_tasks.config") diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/__init__.py b/motrix_deploy_tasks/src/motrix_deploy_tasks/__init__.py index be70cc27..50ae5b38 100644 --- a/motrix_deploy_tasks/src/motrix_deploy_tasks/__init__.py +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/__init__.py @@ -1,40 +1,6 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 -"""Import concrete tasks and register them with :mod:`motrix_deploy.task`.""" - -import sys -from collections.abc import Sequence -from pathlib import Path - -from motrix_deploy.cli import main as deploy_main -from motrix_deploy_tasks.go2_walk import Go2WalkDeployTaskV1 +"""Concrete deployment tasks discovered through installed package entry points.""" __version__ = "0.3.0" - -_DEFAULT_CONFIG_ROOT = Path("configs/deploy") -_DEFAULT_CONFIG_NAMES = { - "sim2sim": "go2_walk_sim2sim", - "sim2real": "go2_walk_flat_sim2real", -} - - -def main(argv: Sequence[str] | None = None) -> int | None: - """Register concrete tasks and run the core CLI with the workspace recipe by default.""" - args = list(sys.argv[1:] if argv is None else argv) - subcommand = args[0] if args[:1] in (["sim2sim"], ["sim2real"]) else "sim2sim" - default_config_name = _DEFAULT_CONFIG_NAMES[subcommand] - uses_runtime_config = bool(args) and args[:1] != ["inspect"] and args != ["--version"] - has_config_path = any(arg == "--config-path" or arg.startswith("--config-path=") for arg in args) - has_config_name = any(arg == "--config-name" or arg.startswith("--config-name=") for arg in args) - default_config_dir = Path.cwd() / _DEFAULT_CONFIG_ROOT / subcommand - default_config = default_config_dir / f"{default_config_name}.yaml" - if uses_runtime_config and default_config.is_file(): - if not has_config_path: - args.extend(("--config-path", str(default_config_dir))) - if not has_config_name: - args.extend(("--config-name", default_config_name)) - return deploy_main(args) - - -__all__ = ["Go2WalkDeployTaskV1", "main"] diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/config/__init__.py b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/__init__.py new file mode 100644 index 00000000..e3818141 --- /dev/null +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/__init__.py @@ -0,0 +1,4 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Packaged Hydra task groups and deployment recipes.""" diff --git a/configs/deploy/sim2real/go2_walk_flat_sim2real.yaml b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/hardware.yaml similarity index 86% rename from configs/deploy/sim2real/go2_walk_flat_sim2real.yaml rename to motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/hardware.yaml index fa69fc2f..0d9d1faa 100644 --- a/configs/deploy/sim2real/go2_walk_flat_sim2real.yaml +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/hardware.yaml @@ -1,9 +1,12 @@ +# @package _global_ defaults: - - base + - /runtime: hardware - _self_ -backend: - name: unitree_go2 +runtime: + backend: unitree_go2 + kp: 50 + kd: 1 network_interface: enp5s0 domain_id: 0 lowcmd_topic: rt/lowcmd @@ -34,3 +37,4 @@ command: invert_linear_y: true invert_yaw: true deadman_button: L1 +duration_s: 300.0 diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/sim.yaml b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/sim.yaml new file mode 100644 index 00000000..ccaa36a1 --- /dev/null +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-flat/sim.yaml @@ -0,0 +1,23 @@ +# @package _global_ +defaults: + - /runtime: sim + - _self_ + +runtime: + backend: mujoco + sensor_bindings: + base_angular_velocity: gyro + base_linear_acceleration: accelerometer + base_linear_velocity: global_linvel + deploy_env_id: flat + robot_id: go2 + # Attach offset from the model's root height (0.445), keeping the default stance clear of the floor. + robot_translation: [0.0, 0.0, -0.114] + robot_rotation: [0.0, 0.0, 0.0, 1.0] + physics: + dt: 0.002 + solver_iterations: 100 + +command: + source: constant + velocity: [0.0, 0.0, 0.0] diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-rough/sim.yaml b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-rough/sim.yaml new file mode 100644 index 00000000..9c266cc9 --- /dev/null +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/config/task/go2-walk-rough/sim.yaml @@ -0,0 +1,23 @@ +# @package _global_ +defaults: + - /runtime: sim + - _self_ + +runtime: + backend: mujoco + sensor_bindings: + base_angular_velocity: gyro + base_linear_acceleration: accelerometer + base_linear_velocity: global_linvel + deploy_env_id: rough + robot_id: go2 + # Attach offset from the model's root height (0.445), clearing the terrain at reset. + robot_translation: [0.0, 0.0, -0.025] + robot_rotation: [0.0, 0.0, 0.0, 1.0] + physics: + dt: 0.002 + solver_iterations: 100 + +command: + source: constant + velocity: [0.0, 0.0, 0.0] diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/__init__.py b/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/__init__.py new file mode 100644 index 00000000..fbb283fd --- /dev/null +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/__init__.py @@ -0,0 +1,4 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Robot-independent deployment worlds discovered through entry points.""" diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/flat.py b/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/flat.py new file mode 100644 index 00000000..20ccb359 --- /dev/null +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/flat.py @@ -0,0 +1,17 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""A robot-free flat-ground deployment world.""" + +from motrix_env_core.config import configclass +from motrix_env_core.config.scene import FlatTerrainCfg, SceneCfg, SceneObjsCfg + + +@configclass +class FlatWorldObjsCfg(SceneObjsCfg): + floor: FlatTerrainCfg = FlatTerrainCfg(friction=(0.6, 0.005, 0.0001)) + + +def build_scene() -> SceneCfg: + """Build fresh flat ground with an empty robot slot.""" + return SceneCfg(objs=FlatWorldObjsCfg()) diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/rough.py b/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/rough.py new file mode 100644 index 00000000..69bbede3 --- /dev/null +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/envs/rough.py @@ -0,0 +1,35 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Deterministic procedural rough ground, independent of robots and RL tasks.""" + +from motrix_env_core.config import configclass +from motrix_env_core.config.scene import ( + HFieldTerrainCfg, + NoiseTerrainGeneratorCfg, + ProceduralHFieldAssetCfg, + SceneAssetsCfg, + SceneCfg, + SceneObjsCfg, +) + + +@configclass +class RoughWorldAssetsCfg(SceneAssetsCfg): + # Match the built-in quadruped rough-walk terrain without importing its + # robot-scoped scene or training task configuration. + terrain: ProceduralHFieldAssetCfg = ProceduralHFieldAssetCfg( + generator=NoiseTerrainGeneratorCfg(seed=0, height_scale=0.1, flip_y=True), + size=(64.0, 64.0), + shape=(320, 320), + ) + + +@configclass +class RoughWorldObjsCfg(SceneObjsCfg): + floor: HFieldTerrainCfg = HFieldTerrainCfg(hfield="terrain", friction=(0.6, 0.005, 0.0001)) + + +def build_scene() -> SceneCfg: + """Build fresh seeded rough ground with an empty robot slot.""" + return SceneCfg(assets=RoughWorldAssetsCfg(), objs=RoughWorldObjsCfg()) diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/go2_walk.py b/motrix_deploy_tasks/src/motrix_deploy_tasks/go2_walk.py deleted file mode 100644 index ffaa0c9d..00000000 --- a/motrix_deploy_tasks/src/motrix_deploy_tasks/go2_walk.py +++ /dev/null @@ -1,188 +0,0 @@ -# Copyright Motphys Technology Co., Ltd. 2025, 2026 -# SPDX-License-Identifier: Apache-2.0 - -"""Direct observation and action implementation for ``go2_walk/v1``.""" - -import numpy as np - -from motrix_deploy.artifact import TaskSpec -from motrix_deploy.contracts import FloatArray, RobotCommand, RobotSpec, RobotState, float32_array -from motrix_deploy.errors import ValidationError -from motrix_deploy.runtime import PolicyContext -from motrix_deploy.task import DeployTask, register_task -from motrix_env_core.input import PlanarVelocityCommand - -TASK_NAME = "go2_walk/v1" -OBSERVATION_SIZE = 49 -ACTION_SIZE = 12 - - -@register_task(TASK_NAME) -class Go2WalkDeployTaskV1(DeployTask[PlanarVelocityCommand]): - """Go2 walking policy I/O semantics frozen as task version 1.""" - - def __init__(self, spec: TaskSpec, robot: RobotSpec) -> None: - if spec.name != TASK_NAME: - raise ValidationError("task.name", TASK_NAME, spec.name) - if spec.observation_size != OBSERVATION_SIZE or spec.action_size != ACTION_SIZE: - raise ValidationError( - "task.policy_shape", - f"observation={OBSERVATION_SIZE}, action={ACTION_SIZE}", - f"observation={spec.observation_size}, action={spec.action_size}", - ) - if robot.joint_count != ACTION_SIZE: - raise ValidationError("robot.joint_count", str(ACTION_SIZE), robot.joint_count) - config = spec.config - shape = (ACTION_SIZE,) - - def cfg(key: str, shp: tuple[int, ...] = shape) -> FloatArray: - return _config_array(config[key], f"task.config.{key}", shp) - - self.spec = spec - self.robot = robot - self._action_scale = cfg("action_scale") - self._kp = cfg("kp") - self._kd = cfg("kd") - self._raw_lower, self._raw_upper = _config_range(config["raw_clip"], "task.config.raw_clip", shape) - self._command_lower = cfg("command_lower", (3,)) - self._command_upper = cfg("command_upper", (3,)) - self._command_scale = cfg("command_scale", (3,)) - self._feet_phase_offsets = cfg("feet_phase_offsets", (4,)) - gait_frequency = config["gait_frequency_hz"] - if isinstance(gait_frequency, bool) or not isinstance(gait_frequency, (int, float)): - raise ValidationError("task.config.gait_frequency_hz", "a positive finite number", gait_frequency) - self._gait_frequency_hz = gait_frequency - if not np.isfinite(self._gait_frequency_hz) or self._gait_frequency_hz <= 0: - raise ValidationError("task.config.gait_frequency_hz", "a positive finite number", gait_frequency) - standing_threshold = config["standing_threshold"] - if isinstance(standing_threshold, bool) or not isinstance(standing_threshold, (int, float)): - raise ValidationError("task.config.standing_threshold", "a non-negative finite number", standing_threshold) - self._standing_threshold = float(standing_threshold) - if not np.isfinite(self._standing_threshold) or self._standing_threshold < 0.0: - raise ValidationError("task.config.standing_threshold", "a non-negative finite number", standing_threshold) - if ( - np.any(self._action_scale < 0) - or np.any(self._kp < 0) - or np.any(self._kd < 0) - or np.any(self._raw_lower > self._raw_upper) - or np.any(self._command_lower > self._command_upper) - or np.any(self._command_scale < 0.0) - or np.any(self._command_scale > 1.0) - ): - raise ValidationError( - "task.config", - "ordered ranges, non-negative control values, and command_scale inside [0, 1]", - "invalid value", - ) - self._previous_action: FloatArray = np.zeros(ACTION_SIZE, dtype=np.float32) - - @property - def observation_size(self) -> int: - return OBSERVATION_SIZE - - @property - def action_size(self) -> int: - return ACTION_SIZE - - @property - def command_lower(self) -> FloatArray: - return self._command_lower * self._command_scale - - @property - def command_upper(self) -> FloatArray: - return self._command_upper * self._command_scale - - def reset(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> None: - self._validate_state(state) - if context.step != 0: - raise ValidationError("context.step", "0 during task reset", context.step) - self.validate_command(context.command) - self._previous_action = np.zeros(ACTION_SIZE, dtype=np.float32) - - def build_observation(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> FloatArray: - self._validate_state(state) - velocity = self._velocity_command(context) - phase = np.zeros(4, dtype=np.float32) - if context.step > 0 and np.linalg.norm(velocity) >= self._standing_threshold: - phase = np.mod( - context.elapsed_time_s * self._gait_frequency_hz + self._feet_phase_offsets, - 1.0, - ).astype(np.float32) - observation = np.concatenate( - ( - state.base_angular_velocity, - _projected_gravity(state.base_orientation_xyzw), - state.joint_position - self.robot.default_joint_position, - state.joint_velocity, - self._previous_action, - velocity, - phase, - ) - ).astype(np.float32) - return float32_array(observation, path="observation", shape=(OBSERVATION_SIZE,)) - - def process_action(self, action: FloatArray) -> RobotCommand: - raw = float32_array(action, path="action.raw", shape=(ACTION_SIZE,)) - executed = np.clip(raw, self._raw_lower, self._raw_upper).astype(np.float32) - target = self.robot.default_joint_position + self._action_scale * executed - target = np.clip(target, self.robot.position_lower, self.robot.position_upper).astype(np.float32) - self._previous_action = executed - zeros = np.zeros(ACTION_SIZE, dtype=np.float32) - return RobotCommand( - joint_position=target, - joint_velocity=zeros, - feedforward_torque=zeros, - kp=self._kp, - kd=self._kd, - ) - - def validate_command(self, command: PlanarVelocityCommand) -> None: - if not isinstance(command, PlanarVelocityCommand): - raise ValidationError("command.type", "PlanarVelocityCommand", type(command).__name__) - if command.batch_size != 1: - raise ValidationError("command.batch_size", "1", command.batch_size) - velocity = command.values[0, :] - if np.any(velocity < self._command_lower) or np.any(velocity > self._command_upper): - raise ValidationError( - "command.velocity", - f"inside [{self._command_lower.tolist()}, {self._command_upper.tolist()}]", - velocity.tolist(), - ) - - def _velocity_command(self, context: PolicyContext[PlanarVelocityCommand]) -> FloatArray: - self.validate_command(context.command) - return context.command.values[0, :] - - def _validate_state(self, state: RobotState) -> None: - expected = (ACTION_SIZE,) - if state.joint_position.shape != expected or state.joint_velocity.shape != expected: - raise ValidationError("state.joints.shape", str(expected), state.joint_position.shape) - - -def _config_array(value: object, path: str, shape: tuple[int, ...]) -> FloatArray: - array = np.asarray(value, dtype=np.float32) - if array.ndim == 0: # broadcast scalar config across the required shape - array = np.broadcast_to(array, shape) - return float32_array(array, path=path, shape=shape) - - -def _config_range(value: object, path: str, shape: tuple[int, ...]) -> tuple[FloatArray, FloatArray]: - if not isinstance(value, list) or len(value) != 2: - raise ValidationError(path, "[lower, upper] scalars or vectors", value) - return _config_array(value[0], f"{path}[0]", shape), _config_array(value[1], f"{path}[1]", shape) - - -def _projected_gravity(orientation_xyzw: FloatArray) -> FloatArray: - x, y, z, w = orientation_xyzw - rotation = np.array( - [ - [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)], - [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)], - [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)], - ], - dtype=np.float32, - ) - return rotation @ np.array([0.0, 0.0, -1.0], dtype=np.float32) - - -__all__ = ["Go2WalkDeployTaskV1"] diff --git a/motrix_deploy_tasks/src/motrix_deploy_tasks/tasks/go2_walk.py b/motrix_deploy_tasks/src/motrix_deploy_tasks/tasks/go2_walk.py new file mode 100644 index 00000000..9e453b78 --- /dev/null +++ b/motrix_deploy_tasks/src/motrix_deploy_tasks/tasks/go2_walk.py @@ -0,0 +1,180 @@ +# Copyright Motphys Technology Co., Ltd. 2025, 2026 +# SPDX-License-Identifier: Apache-2.0 + +"""Typed artifact specification and runtime for ``go2_walk/v1``.""" + +from typing import Annotated, ClassVar + +import numpy as np +from pydantic import Field, model_validator + +from motrix_deploy.artifact.schema import TaskSpec +from motrix_deploy.contracts import FloatArray, JointServoCommand, RobotSpec, RobotState, float32_array +from motrix_deploy.errors import ValidationError +from motrix_deploy.runtime import PolicyContext +from motrix_deploy.task import DeployTask +from motrix_env_core.input import PlanarVelocityCommand + +FLOAT32_MAX = float(np.finfo(np.float32).max) +FiniteFloat = Annotated[float, Field(allow_inf_nan=False)] +Float32Float = Annotated[FiniteFloat, Field(ge=-FLOAT32_MAX, le=FLOAT32_MAX)] +NonNegativeFloat = Annotated[FiniteFloat, Field(ge=0)] +PositiveFloat = Annotated[FiniteFloat, Field(gt=0)] +JointVector = Annotated[list[Float32Float], Field(min_length=1)] +NonNegativeJointVector = Annotated[list[Annotated[Float32Float, Field(ge=0)]], Field(min_length=1)] +CommandVector = Annotated[list[Float32Float], Field(min_length=3, max_length=3)] +CommandScale = Annotated[list[Annotated[Float32Float, Field(ge=0, le=1)]], Field(min_length=3, max_length=3)] +FeetPhaseOffsets = Annotated[list[Float32Float], Field(min_length=4, max_length=4)] + + +class Go2WalkTaskSpec(TaskSpec): + """Self-contained control, command, gait and termination parameters.""" + + task_name: ClassVar[str] = "go2_walk/v1" + + action_scale: NonNegativeJointVector + kp: NonNegativeJointVector + kd: NonNegativeJointVector + # Policy action bounds before scaling/default offset, not robot position limits. + action_lower: JointVector + action_upper: JointVector + command_lower: CommandVector + command_upper: CommandVector + command_scale: CommandScale + feet_phase_offsets: FeetPhaseOffsets + gait_frequency_hz: PositiveFloat + standing_threshold: NonNegativeFloat + termination_min_up_z: Annotated[FiniteFloat, Field(ge=-1, le=1)] + termination_min_base_height: FiniteFloat | None + + @model_validator(mode="after") + def validate_joint_vectors_and_ranges(self) -> "Go2WalkTaskSpec": + joint_count = len(self.action_scale) + for key in ("kp", "kd", "action_lower", "action_upper"): + if len(getattr(self, key)) != joint_count: + raise ValueError(f"{key} must have the same length as action_scale ({joint_count})") + if any(lower > upper for lower, upper in zip(self.action_lower, self.action_upper)): + raise ValueError("action_lower must be less than or equal to action_upper") + if any(lower > upper for lower, upper in zip(self.command_lower, self.command_upper)): + raise ValueError("command_lower must be less than or equal to command_upper") + return self + + +class Go2WalkDeployTaskV1(DeployTask[PlanarVelocityCommand]): + """Go2 walking policy I/O and artifact-owned fall termination. + + Orientation termination is supported by both simulated and real IMU state. + Optional world-frame base-height termination requires base_position when + enabled; no height is invented when the backend cannot provide it. + """ + + spec_type = Go2WalkTaskSpec + + def __init__(self, spec: Go2WalkTaskSpec, robot: RobotSpec) -> None: + if len(spec.action_scale) != robot.joint_count: + raise ValidationError( + "task.config.action_scale", f"{robot.joint_count} joint values", len(spec.action_scale) + ) + self.spec = spec + self.robot = robot + self._action_scale = np.asarray(spec.action_scale, dtype=np.float32) + self._kp = np.asarray(spec.kp, dtype=np.float32) + self._kd = np.asarray(spec.kd, dtype=np.float32) + self._action_lower = np.asarray(spec.action_lower, dtype=np.float32) + self._action_upper = np.asarray(spec.action_upper, dtype=np.float32) + self._command_lower = np.asarray(spec.command_lower, dtype=np.float32) + self._command_upper = np.asarray(spec.command_upper, dtype=np.float32) + self._command_scale = np.asarray(spec.command_scale, dtype=np.float32) + self._feet_phase_offsets = np.asarray(spec.feet_phase_offsets, dtype=np.float32) + self._gait_frequency_hz = spec.gait_frequency_hz + self._standing_threshold = spec.standing_threshold + self._termination_min_up_z = spec.termination_min_up_z + self._termination_min_base_height = spec.termination_min_base_height + self._previous_action: FloatArray = np.zeros(self.robot.joint_count, dtype=np.float32) + + @property + def command_lower(self) -> FloatArray: + return self._command_lower * self._command_scale + + @property + def command_upper(self) -> FloatArray: + return self._command_upper * self._command_scale + + def check_termination(self, state: RobotState) -> str | None: + x, y, _, _ = state.base_orientation_xyzw + up_z = 1.0 - 2.0 * (x * x + y * y) + if up_z <= self._termination_min_up_z: + return "fall_orientation" + if self._termination_min_base_height is not None: + if state.base_position is None: + raise ValidationError("state.base_position", "available for enabled base-height termination", "missing") + if state.base_position[2] <= self._termination_min_base_height: + return "fall_height" + return None + + def reset(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> None: + """Reset action history; callers provide validated state and command.""" + self._previous_action.fill(0) + + def build_observation(self, state: RobotState, context: PolicyContext[PlanarVelocityCommand]) -> FloatArray: + """Consume state and command validated by ControlSession or the caller.""" + velocity = context.command.values[0, :] + phase = np.zeros(4, dtype=np.float32) + if context.step > 0 and np.linalg.norm(velocity) >= self._standing_threshold: + phase = np.mod( + context.elapsed_time_s * self._gait_frequency_hz + self._feet_phase_offsets, + 1.0, + ).astype(np.float32) + observation = np.concatenate( + ( + state.base_angular_velocity, + _projected_gravity(state.base_orientation_xyzw), + state.joint_position - self.robot.default_joint_position, + state.joint_velocity, + self._previous_action, + velocity, + phase, + ) + ).astype(np.float32) + return observation + + def process_action(self, action: FloatArray) -> JointServoCommand: + raw = float32_array(action, path="action.raw", shape=(self.robot.joint_count,)) + executed = np.clip(raw, self._action_lower, self._action_upper).astype(np.float32) + target = self.robot.default_joint_position + self._action_scale * executed + target = np.clip(target, self.robot.position_lower, self.robot.position_upper).astype(np.float32) + self._previous_action = executed + zeros = np.zeros(self.robot.joint_count, dtype=np.float32) + return JointServoCommand( + joint_position=target, + joint_velocity=zeros, + feedforward_torque=zeros, + kp=self._kp, + kd=self._kd, + ) + + def validate_command(self, command: PlanarVelocityCommand) -> None: + if not isinstance(command, PlanarVelocityCommand): + raise ValidationError("command.type", "PlanarVelocityCommand", type(command).__name__) + if command.batch_size != 1: + raise ValidationError("command.batch_size", "1", command.batch_size) + velocity = command.values[0, :] + if np.any(velocity < self._command_lower) or np.any(velocity > self._command_upper): + raise ValidationError( + "command.velocity", + f"inside [{self._command_lower.tolist()}, {self._command_upper.tolist()}]", + velocity.tolist(), + ) + + +def _projected_gravity(orientation_xyzw: FloatArray) -> FloatArray: + x, y, z, w = orientation_xyzw + rotation = np.array( + [ + [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)], + [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)], + [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)], + ], + dtype=np.float32, + ) + return rotation @ np.array([0.0, 0.0, -1.0], dtype=np.float32) diff --git a/motrix_deploy_tasks/tests/test_cli.py b/motrix_deploy_tasks/tests/test_cli.py index 4ec7c3d7..f82cf230 100644 --- a/motrix_deploy_tasks/tests/test_cli.py +++ b/motrix_deploy_tasks/tests/test_cli.py @@ -1,18 +1,21 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 -"""Task bootstrap and deployment CLI configuration tests.""" +"""Installed task discovery and deployment CLI configuration tests.""" import json import subprocess import sys +from dataclasses import asdict from pathlib import Path -from typing import Any import numpy as np import onnx import pytest -from hydra import compose, initialize_config_dir +from hydra import compose, initialize_config_dir, initialize_config_module +from hydra.core.global_hydra import GlobalHydra +from hydra.core.object_type import ObjectType +from motrix_robots.unitree import UnitreeGo2Robot from onnx import TensorProto, helper, numpy_helper from motrix_deploy.artifact import ( @@ -20,16 +23,13 @@ DeploymentManifest, PolicySpec, SourceSpec, - TaskSpec, sha256_bytes, write_artifact, ) from motrix_deploy.contracts import RobotSpec, TensorSpec - -ROOT = Path(__file__).parents[2] -ROUGH_CONFIG_PATH = ROOT / "configs/deploy/sim2sim/go2_walk_sim2sim.yaml" -FLAT_CONFIG_PATH = ROOT / "configs/deploy/sim2sim/go2_walk_flat_sim2sim.yaml" -REAL_CONFIG_PATH = ROOT / "configs/deploy/sim2real/go2_walk_flat_sim2real.yaml" +from motrix_deploy.env import assemble_deploy_scene, create_deploy_env +from motrix_deploy_tasks.tasks.go2_walk import Go2WalkTaskSpec +from motrix_env_core.config.scene import FlatTerrainCfg, HFieldTerrainCfg, SceneCfg def _rollout_result(stdout: str) -> dict[str, object]: @@ -38,12 +38,26 @@ def _rollout_result(stdout: str) -> dict[str, object]: return json.loads(stdout[json_start:]) +@pytest.mark.parametrize("args", [[], ["--help"]]) +def test_cli_general_help(args: list[str]) -> None: + result = subprocess.run( + [sys.executable, "-m", "motrix_deploy.cli", *args], + check=False, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + assert "artifact: ???" in result.stdout + assert "runtime: ???" in result.stdout + + def test_cli_help() -> None: result = subprocess.run( [ sys.executable, "-c", - "from motrix_deploy_tasks import main; raise SystemExit(main())", + "from motrix_deploy.cli import main; raise SystemExit(main())", + "task=go2-walk-rough/sim", "--help", ], check=False, @@ -53,16 +67,16 @@ def test_cli_help() -> None: assert result.returncode == 0 assert "artifact: ???" in result.stdout - assert "scene: go2-walk-rough" in result.stdout + assert "deploy_env_id: rough" in result.stdout -def test_sim2real_cli_help_selects_hardware_recipe() -> None: +def test_hardware_cli_help_selects_hardware_recipe() -> None: result = subprocess.run( [ sys.executable, "-c", - "from motrix_deploy_tasks import main; raise SystemExit(main())", - "sim2real", + "from motrix_deploy.cli import main; raise SystemExit(main())", + "task=go2-walk-flat/hardware", "--help", ], check=False, @@ -71,17 +85,52 @@ def test_sim2real_cli_help_selects_hardware_recipe() -> None: ) assert result.returncode == 0 - assert "name: unitree_go2" in result.stdout - assert "confirm: false" in result.stdout + assert "backend: unitree_go2" in result.stdout + assert "kind: hardware" in result.stdout -def test_import_registers_go2_task_with_motrix_deploy() -> None: - script = ( - "from motrix_deploy.task import registered_tasks; " - "assert 'go2_walk/v1' not in registered_tasks(); " - "import motrix_deploy_tasks; " - "assert 'go2_walk/v1' in registered_tasks()" +@pytest.mark.parametrize( + ("task", "runtime_kind", "backend"), + [ + ("go2-walk-flat/sim", "simulation", "mujoco"), + ("go2-walk-rough/sim", "simulation", "mujoco"), + ("go2-walk-flat/hardware", "hardware", "unitree_go2"), + ], +) +def test_installed_recipes_compose_outside_workspace(tmp_path, task, runtime_kind, backend) -> None: + result = subprocess.run( + [sys.executable, "-m", "motrix_deploy.cli", f"task={task}", "--cfg", "job"], + cwd=tmp_path, + check=False, + capture_output=True, + text=True, ) + assert result.returncode == 0, result.stderr + assert f"backend: {backend}" in result.stdout + assert "artifact: ???" in result.stdout + assert f"kind: {runtime_kind}" in result.stdout + if runtime_kind == "simulation": + assert "sensor_bindings:" in result.stdout + assert "viewer: true" in result.stdout + assert "realtime: null" in result.stdout + + +def test_deployment_plugin_preserves_other_app_task_discovery(tmp_path: Path) -> None: + config_dir = tmp_path / "config" + task_dir = config_dir / "task" + task_dir.mkdir(parents=True) + (task_dir / "custom.yaml").write_text("value: custom\n") + (config_dir / "app.yaml").write_text("defaults:\n - task: custom\n") + + with initialize_config_dir(version_base=None, config_dir=str(config_dir)): + loader = GlobalHydra.instance().config_loader() + assert loader.get_group_options("task", results_filter=ObjectType.CONFIG) == ["custom"] + cfg = compose(config_name="app") + assert cfg.task.value == "custom" + + +def test_installed_go2_task_is_available_without_preimport() -> None: + script = "from motrix_deploy.task import available_tasks; assert 'go2_walk/v1' in available_tasks()" result = subprocess.run([sys.executable, "-c", script], check=False, capture_output=True, text=True) @@ -89,99 +138,129 @@ def test_import_registers_go2_task_with_motrix_deploy() -> None: @pytest.mark.parametrize( - ("config_path", "scene", "base_height"), + ("task", "deploy_env_id", "terrain_type"), [ - (ROUGH_CONFIG_PATH, "go2-walk-rough", 0.42), - (FLAT_CONFIG_PATH, "go2-walk-flat", 0.331), + ("go2-walk-rough/sim", "rough", HFieldTerrainCfg), + ("go2-walk-flat/sim", "flat", FlatTerrainCfg), ], ) -def test_sim2sim_hydra_configs_contain_all_runtime_parameters( - config_path: Path, - scene: str, - base_height: float, -) -> None: - with initialize_config_dir(version_base=None, config_dir=str(config_path.parent)): - cfg = compose( - config_name=config_path.stem, - overrides=["artifact=artifact.deploy"], - ) - - assert cfg.artifact == "artifact.deploy" - assert cfg.backend.name == "mujoco" - assert cfg.backend.scene == scene - assert cfg.backend.sim_dt == 0.002 - assert cfg.backend.solver_iterations == 100 - assert cfg.backend.base_position[2] == pytest.approx(base_height) - assert cfg.viewer is True - - -def test_workspace_bootstrap_keeps_default_path_with_explicit_flat_config_name( - monkeypatch: pytest.MonkeyPatch, +def test_mujoco_hydra_recipes_assemble_robot_and_selected_world( + task: str, + deploy_env_id: str, + terrain_type: type, ) -> None: - import motrix_deploy_tasks + with initialize_config_module(version_base=None, config_module="motrix_deploy.config"): + cfg = compose(config_name="deploy", overrides=[f"task={task}", "artifact=artifact.deploy"]) - captured: list[str] = [] + assert cfg.runtime.kind == "simulation" - def capture(args: list[str]) -> int: - captured.extend(args) - return 0 - - monkeypatch.chdir(ROOT) - monkeypatch.setattr(motrix_deploy_tasks, "deploy_main", capture) + scene = assemble_deploy_scene(cfg.runtime.deploy_env_id, cfg.runtime.robot_id) + assert isinstance(scene, SceneCfg) + assert isinstance(scene.objs.robot, UnitreeGo2Robot) + assert isinstance(scene.objs.floor, terrain_type) + assert cfg.runtime.deploy_env_id == deploy_env_id + assert cfg.runtime.robot_id == "go2" + assert cfg.artifact == "artifact.deploy" + assert cfg.runtime.backend == "mujoco" + assert cfg.runtime.physics.dt > 0 + assert cfg.runtime.physics.solver_iterations > 0 + assert len(cfg.runtime.robot_translation) == 3 + assert np.linalg.norm(cfg.runtime.robot_rotation) == pytest.approx(1.0) + assert cfg.runtime.viewer is True + assert cfg.runtime.realtime is None + assert cfg.duration_s is None + + +@pytest.mark.parametrize("deploy_env_id", ["flat", "rough"]) +def test_installed_world_factories_are_robot_free_and_fresh(deploy_env_id: str) -> None: + first = create_deploy_env(deploy_env_id) + second = create_deploy_env(deploy_env_id) + assert isinstance(first, SceneCfg) + assert first.objs.robot is None + assert second.objs.robot is None + assert first.objs.floor is not second.objs.floor + first.objs.robot = UnitreeGo2Robot() + assert second.objs.robot is None + if isinstance(first.objs.floor, HFieldTerrainCfg): + terrain = first.assets[first.objs.floor.hfield] + other = second.assets[second.objs.floor.hfield] + heights = terrain.generator.generate(terrain.size, terrain.shape) + np.testing.assert_array_equal(heights, other.generator.generate(other.size, other.shape)) + assert np.ptp(heights) > 0 + + +def test_installed_go2_task_can_be_created_without_preimport(tmp_path: Path) -> None: + specs_path = tmp_path / "specs.json" + robot = { + key: value.tolist() if isinstance(value, np.ndarray) else value for key, value in asdict(_robot_spec()).items() + } + specs_path.write_text(json.dumps({"task": _task_spec().to_dict(), "robot": robot})) + script = ( + "import json, sys; import numpy as np; " + "from motrix_deploy.artifact import TaskSpec; " + "from motrix_deploy.contracts import RobotSpec; " + "from motrix_deploy.task import create_task; " + "specs = json.load(open(sys.argv[1])); " + "specs['robot']['joint_names'] = tuple(specs['robot']['joint_names']); " + "specs['robot'].update({key: np.asarray(value, dtype=np.float32) " + "for key, value in specs['robot'].items() if isinstance(value, list)}); " + "spec = TaskSpec.from_dict(specs['task']); " + "assert type(spec).__name__ == 'Go2WalkTaskSpec'; " + "assert type(spec).__module__ == 'motrix_deploy_tasks.tasks.go2_walk'; " + "assert not any(name in sys.modules for name in ('motrix_envs', 'motrixsim', 'mujoco', 'torch')); " + "task = create_task(spec, RobotSpec(**specs['robot'])); " + "assert type(task).__module__ == 'motrix_deploy_tasks.tasks.go2_walk'; " + "assert type(task).__name__ == 'Go2WalkDeployTaskV1'" + ) - result: Any = motrix_deploy_tasks.main( - ["sim2sim", "--config-name", "go2_walk_flat_sim2sim", "artifact=artifact.deploy"] + result = subprocess.run( + [sys.executable, "-c", script, str(specs_path)], check=False, capture_output=True, text=True ) - assert result == 0 - assert captured.count("--config-name") == 1 - assert captured[captured.index("--config-name") + 1] == "go2_walk_flat_sim2sim" - assert captured[captured.index("--config-path") + 1] == str(ROOT / "configs/deploy/sim2sim") + assert result.returncode == 0, result.stderr -def test_sim2real_hydra_config_contains_hardware_guards() -> None: - with initialize_config_dir(version_base=None, config_dir=str(REAL_CONFIG_PATH.parent)): +def test_hardware_hydra_config_selects_runtime_and_command_defaults() -> None: + with initialize_config_module(version_base=None, config_module="motrix_deploy.config"): cfg = compose( - config_name=REAL_CONFIG_PATH.stem, + config_name="deploy", overrides=[ + "task=go2-walk-flat/hardware", "artifact=artifact.deploy", - "backend.network_interface=enp3s0", + "runtime.network_interface=enp3s0", ], ) - assert cfg.backend.name == "unitree_go2" - assert cfg.rollout.steps is None - assert cfg.rollout.duration_s == 300.0 - assert cfg.hardware.confirm is False - assert cfg.realtime is True - assert cfg.viewer is False + assert cfg.runtime.kind == "hardware" + assert cfg.runtime.backend == "unitree_go2" + assert cfg.duration_s is not None and cfg.duration_s > 0 assert cfg.command.source == "gamepad" - assert cfg.backend.kp == 50 - assert cfg.backend.kd == 1 - assert cfg.backend.lie_down_button == "B" - assert cfg.backend.lie_down_duration_s == pytest.approx(2.0) + assert cfg.runtime.lie_down_button == "B" assert cfg.command.gamepad.deadman_button == "L1" assert cfg.command.gamepad.invert_linear_x is False assert cfg.command.gamepad.invert_linear_y is True assert cfg.command.gamepad.invert_yaw is True -def test_sim2real_hydra_config_accepts_explicit_gain_overrides() -> None: - with initialize_config_dir(version_base=None, config_dir=str(REAL_CONFIG_PATH.parent)): +def test_hardware_hydra_config_accepts_explicit_gain_overrides() -> None: + with initialize_config_module(version_base=None, config_module="motrix_deploy.config"): cfg = compose( - config_name=REAL_CONFIG_PATH.stem, + config_name="deploy", overrides=[ + "task=go2-walk-flat/hardware", "artifact=artifact.deploy", - "backend.kp=25.0", - "backend.kd=[0.4,0.4,0.4,0.5,0.5,0.5,0.6,0.6,0.6,0.7,0.7,0.7]", + "runtime.kp=25.0", + "runtime.kd=[0.4,0.4,0.4,0.5,0.5,0.5,0.6,0.6,0.6,0.7,0.7,0.7]", ], ) - assert cfg.backend.kp == pytest.approx(25.0) - assert list(cfg.backend.kd) == pytest.approx([0.4, 0.4, 0.4, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6, 0.7, 0.7, 0.7]) + assert cfg.runtime.kp == pytest.approx(25.0) + assert list(cfg.runtime.kd) == pytest.approx([0.4, 0.4, 0.4, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6, 0.7, 0.7, 0.7]) -def test_headless_sim2sim_cli_runs_deterministic_onnx_fixture(tmp_path: Path) -> None: +@pytest.mark.parametrize("backend", ["mujoco"]) +@pytest.mark.parametrize("task", ["go2-walk-flat", "go2-walk-rough"]) +def test_headless_simulation_cli_runs_deterministic_onnx_fixture(tmp_path: Path, task: str, backend: str) -> None: policy_path = tmp_path / "fixture.onnx" weight = numpy_helper.from_array(np.zeros((49, 12), dtype=np.float32), name="weight") graph = helper.make_graph( @@ -214,41 +293,40 @@ def test_headless_sim2sim_cli_runs_deterministic_onnx_fixture(tmp_path: Path) -> command = [ sys.executable, "-c", - "from motrix_deploy_tasks import main; raise SystemExit(main())", - "sim2sim", + "from motrix_deploy.cli import main; raise SystemExit(main())", + f"task={task}/sim", + f"runtime.backend={backend}", f"artifact={artifact_path}", "command.velocity=[0.5,0.0,0.0]", - "rollout.steps=5", - "seed=1", - "viewer=false", + "duration_s=0.1", + "runtime.viewer=false", ] first = subprocess.run(command, check=False, capture_output=True, text=True) - second = subprocess.run(command, check=False, capture_output=True, text=True) - duration_command = command.copy() - steps_index = duration_command.index("rollout.steps=5") - duration_command[steps_index : steps_index + 1] = ["rollout.steps=null", "rollout.duration_s=0.1"] - duration = subprocess.run(duration_command, check=False, capture_output=True, text=True) - invalid_bound = subprocess.run( - [*command, "rollout.duration_s=0.1"], - check=False, - capture_output=True, - text=True, - ) - first_result = _rollout_result(first.stdout) - second_result = _rollout_result(second.stdout) - duration_result = _rollout_result(duration.stdout) - assert first.returncode == 0, first.stderr - assert second.returncode == 0, second.stderr - assert duration.returncode == 0, duration.stderr - assert invalid_bound.returncode == 2 - assert "Exactly one of rollout.steps and rollout.duration_s" in invalid_bound.stderr + first_result = _rollout_result(first.stdout) assert first_result["success"] is True assert first_result["completed_steps"] == 5 - assert first_result["trace_sha256"] == second_result["trace_sha256"] - assert duration_result["completed_steps"] == 5 - assert "Converting 12 actuators from position servos to torque motors" in first.stdout + + # Both worlds smoke-test each backend; repeat only one world per backend. + if task == "go2-walk-flat": + second = subprocess.run(command, check=False, capture_output=True, text=True) + assert second.returncode == 0, second.stderr + assert first_result["trace_sha256"] == _rollout_result(second.stdout)["trace_sha256"] + + # Duration validation is shared, so cover it once rather than per world/backend. + if task == "go2-walk-flat" and backend == "mujoco": + for invalid_duration in ("0", "-0.1", "nan", "inf"): + invalid_command = command.copy() + invalid_command[invalid_command.index("duration_s=0.1")] = f"duration_s={invalid_duration}" + invalid_bound = subprocess.run( + invalid_command, + check=False, + capture_output=True, + text=True, + ) + assert invalid_bound.returncode == 2, invalid_bound.stderr + assert "duration_s" in invalid_bound.stderr def _robot_spec() -> RobotSpec: @@ -278,21 +356,19 @@ def _robot_spec() -> RobotSpec: ) -def _task_spec() -> TaskSpec: - return TaskSpec( - name="go2_walk/v1", - observation_size=49, - action_size=12, - config={ - "action_scale": 0.25, - "command_lower": [0.5, 0.0, 0.0], - "command_upper": [0.5, 0.0, 0.0], - "command_scale": [1.0, 1.0, 1.0], - "feet_phase_offsets": [0.0, 0.5, 0.5, 0.0], - "gait_frequency_hz": 2.0, - "standing_threshold": 0.05, - "kp": [35.0] * 12, - "kd": [0.5] * 12, - "raw_clip": [[-1.0] * 12, [1.0] * 12], - }, +def _task_spec() -> Go2WalkTaskSpec: + return Go2WalkTaskSpec( + action_scale=[0.25] * 12, + command_lower=[0.5, 0.0, 0.0], + command_upper=[0.5, 0.0, 0.0], + command_scale=[1.0, 1.0, 1.0], + feet_phase_offsets=[0.0, 0.5, 0.5, 0.0], + gait_frequency_hz=2.0, + standing_threshold=0.05, + termination_min_up_z=0.25, + termination_min_base_height=None, + kp=[35.0] * 12, + kd=[0.5] * 12, + action_lower=[-1.0] * 12, + action_upper=[1.0] * 12, ) diff --git a/motrix_deploy_tasks/tests/test_go2_walk.py b/motrix_deploy_tasks/tests/test_go2_walk.py index 2f8012cd..809a4b3a 100644 --- a/motrix_deploy_tasks/tests/test_go2_walk.py +++ b/motrix_deploy_tasks/tests/test_go2_walk.py @@ -3,17 +3,24 @@ """Direct Go2 walking deployment-task tests.""" +from dataclasses import replace + import numpy as np import pytest +from pydantic import ValidationError as PydanticValidationError -from motrix_deploy.artifact import TaskSpec +from motrix_deploy.artifact.schema import TaskSpec from motrix_deploy.contracts import RobotSpec, RobotState from motrix_deploy.errors import ValidationError from motrix_deploy.runtime import PolicyContext -from motrix_deploy_tasks.go2_walk import Go2WalkDeployTaskV1 +from motrix_deploy_tasks.tasks.go2_walk import Go2WalkDeployTaskV1, Go2WalkTaskSpec from motrix_env_core.input import PlanarVelocityCommand +def _replace_spec(spec: Go2WalkTaskSpec, **overrides: object) -> Go2WalkTaskSpec: + return type(spec).model_validate({**spec.model_dump(), **overrides}) + + def _context(step: int, elapsed_time_s: float, velocity: list[float] | np.ndarray) -> PolicyContext: return PolicyContext( step=step, @@ -45,31 +52,262 @@ def _state(robot: RobotSpec) -> RobotState: ) -def _task_spec() -> TaskSpec: - return TaskSpec( - name="go2_walk/v1", - observation_size=49, - action_size=12, - config={ - "action_scale": 0.25, - "command_lower": [-1.0, -1.0, -1.0], - "command_upper": [1.0, 1.0, 1.0], - "command_scale": [0.5, 0.5, 0.5], - "feet_phase_offsets": [0.0, 0.5, 0.5, 0.0], - "gait_frequency_hz": 2.0, - "standing_threshold": 0.05, - "kp": [35.0] * 12, - "kd": [0.5] * 12, - "raw_clip": [[-1.0] * 12, [1.0] * 12], - }, +def _task_spec() -> Go2WalkTaskSpec: + return Go2WalkTaskSpec( + action_scale=[0.25] * 12, + command_lower=[-1.0, -1.0, -1.0], + command_upper=[1.0, 1.0, 1.0], + command_scale=[0.5, 0.5, 0.5], + feet_phase_offsets=[0.0, 0.5, 0.5, 0.0], + gait_frequency_hz=2.0, + standing_threshold=0.05, + termination_min_up_z=0.25, + termination_min_base_height=None, + kp=[35.0] * 12, + kd=[0.5] * 12, + action_lower=[-1.0] * 12, + action_upper=[1.0] * 12, ) +@pytest.mark.parametrize("up_z", [-1.0, 0.0, 0.75, 1.0]) +def test_fall_orientation_uses_artifact_threshold(up_z: float) -> None: + robot = _robot() + state = _state(robot) + angle = np.arccos(up_z) + state.base_orientation_xyzw = np.array([np.sin(angle / 2), 0.0, 0.0, np.cos(angle / 2)], dtype=np.float32) + spec = _task_spec() + task = Go2WalkDeployTaskV1(spec, robot) + reason = task.check_termination(state) + assert (reason == "fall_orientation") == (up_z <= spec.termination_min_up_z) + + +def test_fall_orientation_includes_threshold_boundary() -> None: + spec = _task_spec() + robot = _robot() + task = Go2WalkDeployTaskV1(_replace_spec(spec, termination_min_up_z=1.0), robot) + assert task.check_termination(_state(robot)) == "fall_orientation" + + +def test_enabled_fall_height_requires_position_and_uses_artifact_threshold() -> None: + robot = _robot() + state = _state(robot) + spec = _task_spec() + threshold = 0.25 # Exactly representable synthetic cutoff, not a tuned task default. + task = Go2WalkDeployTaskV1(_replace_spec(spec, termination_min_base_height=threshold), robot) + assert Go2WalkDeployTaskV1(spec, robot).check_termination(state) is None + with pytest.raises(ValidationError, match="state.base_position.*enabled base-height termination"): + task.check_termination(state) + state.base_position = np.array([0.0, 0.0, threshold - 0.1], dtype=np.float32) + assert task.check_termination(state) == "fall_height" + assert Go2WalkDeployTaskV1(spec, robot).check_termination(state) is None + state.base_position[2] = threshold + assert task.check_termination(state) == "fall_height" + state.base_position[2] = threshold + 0.1 + assert task.check_termination(state) is None + + +@pytest.mark.parametrize("key", ["termination_min_up_z", "termination_min_base_height", "standing_threshold"]) +def test_missing_required_artifact_field_is_rejected_during_decoding(key: str) -> None: + wire = _task_spec().to_dict() + del wire["config"][key] + with pytest.raises(ValidationError, match=key): + TaskSpec.from_dict(wire) + + +def test_typed_task_spec_roundtrips_through_common_codec() -> None: + spec = _task_spec() + decoded = TaskSpec.from_dict(spec.to_dict()) + assert type(decoded) is Go2WalkTaskSpec + assert decoded == spec + assert decoded.name == "go2_walk/v1" + + +@pytest.mark.parametrize( + ("key", "value"), + [("termination_min_up_z", 2.0), ("termination_min_up_z", True), ("termination_min_base_height", float("nan"))], +) +def test_termination_config_is_validated(key: str, value: object) -> None: + with pytest.raises(PydanticValidationError, match=key): + _replace_spec(_task_spec(), **{key: value}) + + +@pytest.mark.parametrize( + ("key", "value"), + [ + ("action_scale", [-0.1] * 12), + ("kp", [-1.0] * 12), + ("kd", [-1.0] * 12), + ("action_lower", [2.0] * 12), + ("action_upper", [-2.0] * 12), + ("command_lower", [2.0] * 3), + ("command_scale", [1.1] * 3), + ("command_scale", [-0.1] * 3), + ("gait_frequency_hz", 0.0), + ("standing_threshold", -0.1), + ("termination_min_up_z", -1.1), + ("action_lower", [-1.0] * 11), + ("action_upper", [1.0] * 11), + ("action_lower", [float("nan")] * 12), + ("action_upper", [float("inf")] * 12), + ("action_scale", [0.25] * 11), + ("action_scale", []), + ("kp", [float("inf")] * 12), + ("command_scale", [float("nan")] * 3), + ("feet_phase_offsets", [0.0] * 3), + ("feet_phase_offsets", [0.0] * 5), + ("feet_phase_offsets", [float("inf")] * 4), + ("command_lower", [-1.0] * 2), + ("command_upper", [1.0] * 4), + ("command_scale", [0.5] * 4), + ("kp", []), + ("kd", [0.5] * 11), + ("gait_frequency_hz", float("inf")), + ("standing_threshold", float("nan")), + ], +) +def test_semantic_validation_runs_for_construction_and_wire_decoding(key: str, value: object) -> None: + spec = _task_spec() + with pytest.raises(PydanticValidationError): + _replace_spec(spec, **{key: value}) + wire = spec.to_dict() + wire["config"][key] = value + with pytest.raises(ValidationError, match="task.config"): + TaskSpec.from_dict(wire) + + +@pytest.mark.parametrize( + ("key", "value"), + [ + ("action_scale", 0.25), + ("action_lower", -1.0), + ("action_upper", 1.0), + ("action_lower", [[-1.0] * 12]), + ("action_upper", [True] * 12), + ("termination_min_up_z", True), + ], +) +def test_invalid_wire_types_are_rejected_by_common_codec(key: str, value: object) -> None: + wire = _task_spec().to_dict() + wire["config"][key] = value + with pytest.raises(ValidationError, match="task.config"): + TaskSpec.from_dict(wire) + + +@pytest.mark.parametrize("key", ["gait_frequency_hz", "standing_threshold", "termination_min_base_height"]) +def test_programmatic_numeric_fields_reject_booleans(key: str) -> None: + with pytest.raises(PydanticValidationError, match=key): + _replace_spec(_task_spec(), **{key: True}) + + +@pytest.mark.parametrize( + ("key", "value"), + [ + ("action_scale", (0.25,) * 12), + ("kp", ["35.0"] * 12), + ("kd", [True] * 12), + ("command_lower", [[-1.0] * 3]), + ("gait_frequency_hz", "2.0"), + ("unknown_field", 1.0), + ], +) +def test_programmatic_construction_is_strict(key: str, value: object) -> None: + with pytest.raises(PydanticValidationError, match=key): + _replace_spec(_task_spec(), **{key: value}) + + +def test_task_spec_fields_are_frozen_but_lists_remain_public() -> None: + spec = _task_spec() + assert isinstance(spec.action_scale, list) + with pytest.raises(PydanticValidationError, match="frozen_instance"): + spec.standing_threshold = 0.1 + + +@pytest.mark.parametrize("key", ["action_scale", "kp", "kd", "action_lower", "action_upper"]) +def test_mutated_joint_vector_length_is_rejected_during_serialization(key: str) -> None: + spec = _task_spec() + getattr(spec, key).pop() + with pytest.raises(ValidationError, match="task.config"): + spec.to_dict() + + +@pytest.mark.parametrize( + ("key", "value"), + [("kp", -1.0), ("action_upper", -2.0), ("command_lower", 2.0), ("feet_phase_offsets", float("nan"))], +) +def test_mutated_list_values_are_rejected_during_serialization(key: str, value: float) -> None: + spec = _task_spec() + getattr(spec, key)[0] = value + with pytest.raises(ValidationError, match="task.config"): + spec.to_dict() + + +@pytest.mark.parametrize( + "key", + [ + "action_scale", + "kp", + "kd", + "action_lower", + "action_upper", + "command_lower", + "command_upper", + "feet_phase_offsets", + ], +) +@pytest.mark.parametrize("value", [-1e100, 1e100]) +def test_vectors_reject_finite_values_outside_float32_range(key: str, value: float) -> None: + # Runtime vectors are float32, so finite Python floats must not overflow on conversion. + spec = _task_spec() + vector = list(getattr(spec, key)) + vector[0] = value + with pytest.raises(PydanticValidationError, match=key): + _replace_spec(spec, **{key: vector}) + wire = spec.to_dict() + wire["config"][key] = vector + with pytest.raises(ValidationError, match=f"task.config.{key}.0"): + TaskSpec.from_dict(wire) + getattr(spec, key)[0] = value + with pytest.raises(ValidationError, match=f"task.config.{key}.0"): + spec.to_dict() + + +def test_runtime_checks_robot_joint_count() -> None: + robot = _robot() + short_robot = replace( + robot, + joint_names=robot.joint_names[:-1], + default_joint_position=robot.default_joint_position[:-1], + position_lower=robot.position_lower[:-1], + position_upper=robot.position_upper[:-1], + torque_limit=robot.torque_limit[:-1], + ) + with pytest.raises(ValidationError, match="task.config.action_scale"): + Go2WalkDeployTaskV1(_task_spec(), short_robot) + + joint_count = short_robot.joint_count + spec = _replace_spec( + _task_spec(), + action_scale=[0.25] * joint_count, + kp=[35.0] * joint_count, + kd=[0.5] * joint_count, + action_lower=[-1.0] * joint_count, + action_upper=[1.0] * joint_count, + ) + task = Go2WalkDeployTaskV1(spec, short_robot) + command = task.process_action(np.zeros(joint_count, dtype=np.float32)) + assert command.joint_position.shape == (joint_count,) + assert command.joint_velocity.shape == (joint_count,) + with pytest.raises(ValidationError, match="action.raw.shape"): + task.process_action(np.zeros(joint_count + 1, dtype=np.float32)) + + def test_go2_observation_has_exact_49_element_contract() -> None: robot = _robot() state = _state(robot) task = Go2WalkDeployTaskV1(_task_spec(), robot) context = _context(0, 0.0, [0.5, 0.0, -0.2]) + task.validate_command(context.command) task.reset(state, context) observation = task.build_observation(state, context) @@ -88,13 +326,13 @@ def test_previous_action_and_trot_phase_advance_after_first_tick() -> None: state = _state(robot) task = Go2WalkDeployTaskV1(_task_spec(), robot) reset_context = _context(0, 0.0, [0.5, 0.0, 0.0]) + task.validate_command(reset_context.command) task.reset(state, reset_context) task.process_action(np.ones(12, dtype=np.float32)) - observation = task.build_observation( - state, - _context(1, 0.02, [0.5, 0.0, 0.0]), - ) + context = _context(1, 0.02, [0.5, 0.0, 0.0]) + task.validate_command(context.command) + observation = task.build_observation(state, context) np.testing.assert_array_equal(observation[30:42], 1.0) np.testing.assert_allclose(observation[45:49], [0.04, 0.54, 0.54, 0.04]) @@ -104,46 +342,41 @@ def test_standing_command_freezes_deployment_gait_phase() -> None: robot = _robot() task = Go2WalkDeployTaskV1(_task_spec(), robot) - observation = task.build_observation( - _state(robot), - _context(10, 0.2, np.zeros(3, dtype=np.float32)), - ) + context = _context(10, 0.2, np.zeros(3, dtype=np.float32)) + task.validate_command(context.command) + observation = task.build_observation(_state(robot), context) np.testing.assert_array_equal(observation[45:49], np.zeros(4, dtype=np.float32)) -def test_standing_threshold_is_required() -> None: - spec = _task_spec() - config = dict(spec.config) - del config["standing_threshold"] - spec = TaskSpec( - name=spec.name, - observation_size=spec.observation_size, - action_size=spec.action_size, - config=config, - ) - - with pytest.raises(KeyError, match="standing_threshold"): - Go2WalkDeployTaskV1(spec, _robot()) - - def test_action_processing_applies_clip_scale_and_position_limits() -> None: - robot = _robot() + # Synthetic asymmetric action bounds and nonzero offsets distinguish policy + # clipping from the subsequent robot-position clipping. + robot = replace( + _robot(), + default_joint_position=np.full(12, 0.5, dtype=np.float32), + position_lower=np.full(12, 0.25, dtype=np.float32), + position_upper=np.full(12, 0.75, dtype=np.float32), + ) state = _state(robot) - task = Go2WalkDeployTaskV1(_task_spec(), robot) - task.reset( - state, - _context(0, 0.0, np.zeros(3, dtype=np.float32)), + spec = _replace_spec( + _task_spec(), + action_lower=[-2.0] * 12, + action_upper=[0.5] * 12, ) + task = Go2WalkDeployTaskV1(spec, robot) + reset_context = _context(0, 0.0, np.zeros(3, dtype=np.float32)) + task.validate_command(reset_context.command) + task.reset(state, reset_context) - command = task.process_action(np.full(12, 2.0, dtype=np.float32)) - next_observation = task.build_observation( - state, - _context(1, 0.02, np.zeros(3, dtype=np.float32)), - ) + raw_action = np.tile(np.array([-3.0, 2.0], dtype=np.float32), 6) + command = task.process_action(raw_action) + next_context = _context(1, 0.02, np.zeros(3, dtype=np.float32)) + task.validate_command(next_context.command) + next_observation = task.build_observation(state, next_context) - np.testing.assert_allclose(command.joint_position, 0.25) - np.testing.assert_array_equal(next_observation[30:42], 1.0) + np.testing.assert_allclose(command.joint_position, np.tile([0.25, 0.625], 6)) + np.testing.assert_array_equal(next_observation[30:42], np.tile([-2.0, 0.5], 6)) np.testing.assert_array_equal(command.kp, 35.0) np.testing.assert_array_equal(command.kd, 0.5) diff --git a/motrix_deploy_tasks/tests/test_training_parity.py b/motrix_deploy_tasks/tests/test_training_parity.py index cfe0ab5b..00c421c1 100644 --- a/motrix_deploy_tasks/tests/test_training_parity.py +++ b/motrix_deploy_tasks/tests/test_training_parity.py @@ -10,6 +10,7 @@ from motrix_deploy.contracts import RobotState from motrix_deploy.runtime import PolicyContext from motrix_deploy.task import create_task +from motrix_deploy_tasks.tasks.go2_walk import Go2WalkTaskSpec from motrix_env_core import registry from motrix_env_core.input import PlanarVelocityCommand from motrix_envs.deploy import build_deployment_profile @@ -30,11 +31,20 @@ def test_go2_training_and_deployment_task_golden_probe(env_name: str) -> None: command=PlanarVelocityCommand(env._commands), ) task = create_task(profile.task, profile.robot) - command_scale = np.asarray(profile.task.config["command_scale"], dtype=np.float32) + assert isinstance(profile.task, Go2WalkTaskSpec) + command_scale = np.asarray(profile.task.command_scale, dtype=np.float32) assert command_scale.shape == (3,) np.testing.assert_allclose(task.command_lower, env.cfg.commands.velocity.lower * command_scale) np.testing.assert_allclose(task.command_upper, env.cfg.commands.velocity.upper * command_scale) + task.validate_command(context.command) task.reset(robot_state, context) + # Mainline training's compute_transition uses orientation-only up_z <= 0.5; + # these probes reproduce that behavior rather than require new training config. + for up_z in (-1.0, 0.0, 1.0): + angle = np.arccos(up_z) + robot_state.base_orientation_xyzw = np.array([np.sin(angle / 2), 0.0, 0.0, np.cos(angle / 2)], dtype=np.float32) + assert (task.check_termination(robot_state) is not None) == (up_z <= 0.5) + robot_state = _robot_state_from_env(env, env_state, probe) np.testing.assert_allclose( task.build_observation(robot_state, context), @@ -55,6 +65,7 @@ def test_go2_training_and_deployment_task_golden_probe(env_name: str) -> None: elapsed_time_s=profile.control.period_s, command=PlanarVelocityCommand(env._commands), ) + task.validate_command(next_context.command) np.testing.assert_allclose( task.build_observation(next_state, next_context), stepped.obs.policy[0], diff --git a/motrix_deploy_unitree/README.md b/motrix_deploy_unitree/README.md index 58e9329a..06152691 100644 --- a/motrix_deploy_unitree/README.md +++ b/motrix_deploy_unitree/README.md @@ -15,9 +15,8 @@ The Motphys fork includes the CRC shared libraries in built distributions. The S and `opencv-python`; uv therefore installs them into the environment as transitive dependencies. The DDS control backend does not import OpenCV directly, but the official SDK includes camera/video clients in the same distribution. -Deployment recipes inherit common physical-runtime guards and default policy gains from -`configs/deploy/sim2real/base.yaml`. Concrete recipes such as -`configs/deploy/sim2real/go2_walk_flat_sim2real.yaml` own their robot-specific backend fields and command input configuration. +The installed `task=go2-walk-flat/hardware` recipe in `motrix_deploy_tasks` supplies hardware startup settings, +policy gains, robot-specific SDK fields, and command input settings. The SDK is imported only when `open()` is called. Runtime behavior: - Subscribe to `rt/lowstate`, validate CRC, and map joint `q/dq` plus IMU data into canonical `RobotState`. @@ -41,7 +40,6 @@ from motrix_deploy_unitree import UnitreeGo2DirectInterface robot = UnitreeGo2DirectInterface.from_artifact( "artifacts/go2-walk-rough.deploy", network_interface="enp5s0", - hardware_confirmed=True, ) robot.open() @@ -76,7 +74,7 @@ motrix-deploy-unitree read-lowstate enp5s0 ``` It subscribes to `rt/lowstate` for 10 seconds by default and prints at most one sample every 0.5 seconds. It does not need -an artifact or `--hardware-confirm`, and it never creates a `LowCmd` publisher. Each printed sample contains: +an artifact, and it never creates a `LowCmd` publisher. Each printed sample contains: | Output | Contents and units | | ---------------- | ---------------------------------------------------------------------------------- | @@ -100,8 +98,7 @@ motrix-deploy-unitree joint-control \ enp5s0 \ FL_thigh_joint \ 0.9 \ - --artifact artifacts/go2-walk-rough.deploy \ - --hardware-confirm + --artifact artifacts/go2-walk-rough.deploy ``` The three positional arguments are the network interface, canonical joint name, and absolute target position in radians. @@ -117,20 +114,22 @@ helper changes one target, each Unitree `LowCmd` necessarily contains commands f ## Policy gain overrides -`UnitreeGo2BackendConfig` defaults `backend.kp` and `backend.kd` to `null`, while -`configs/deploy/sim2real/base.yaml` sets them to `50` and `1` for physical deployment recipes. Each accepts either one +`UnitreeGo2BackendConfig` defaults `runtime.kp` and `runtime.kd` to `null`, while +the installed Go2 hardware recipe sets them to `50` and `1`. Each accepts either one non-negative scalar for all joints or 12 values in canonical `FL, FR, RL, RR` order. Overrides apply to the -default-pose transition and policy commands; damping stop still uses `kp=0` and `backend.damping_kd`. +default-pose transition and policy commands; damping stop still uses `kp=0` and `runtime.damping_kd`. From the repository root: ```bash -motrix-deploy sim2real \ - artifact=artifacts/go2-walk-rough.deploy \ - backend.network_interface=enp5s0 \ - hardware.confirm=true +motrix-deploy task=go2-walk-flat/hardware \ + artifact=artifacts/go2-walk-flat.deploy \ + runtime.network_interface=enp5s0 ``` -`hardware.confirm=true`, `viewer=false`, and `realtime=true` are enforced before DDS initialization. Before opening LowCmd, the backend uses MotionSwitcher to stand down and release the active MCF mode, then calls `RobotStateClient.ServiceSwitch("sport_mode", False)`; failure aborts startup. Use a suspended +All hardware settings live directly under `runtime`, with `runtime.backend=unitree_go2`. `runtime.backend` is a scalar +string selector, not a nested configuration mapping or Hydra group. The hardware runtime always runs +in real time internally and exposes no `runtime.viewer` or `runtime.realtime` fields. Before opening LowCmd, the backend uses MotionSwitcher to stand down and +release the active MCF mode, then calls `RobotStateClient.ServiceSwitch("sport_mode", False)`; failure aborts startup. Use a suspended robot, low-level/debug mode, and an operator-ready emergency stop. The implementation is validated with an injected fake SDK; the suspended real-robot smoke test remains pending. diff --git a/motrix_deploy_unitree/pyproject.toml b/motrix_deploy_unitree/pyproject.toml index 32a97745..988fb45b 100644 --- a/motrix_deploy_unitree/pyproject.toml +++ b/motrix_deploy_unitree/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ motrix-deploy-unitree = "motrix_deploy_unitree.cli:main" [project.entry-points."motrix_deploy.backends"] -unitree_go2 = "motrix_deploy_unitree.plugin:create_backend" +unitree_go2 = "motrix_deploy_unitree.plugin:create_runtime" [tool.uv.sources] unitree-sdk2py = { git = "https://github.com/motphys-developers/unitree_sdk2_python", rev = "ff87e731174f7258e0d09980897c55116634939a" } diff --git a/motrix_deploy_unitree/src/motrix_deploy_unitree/cli.py b/motrix_deploy_unitree/src/motrix_deploy_unitree/cli.py index b6a3bcae..537f0784 100644 --- a/motrix_deploy_unitree/src/motrix_deploy_unitree/cli.py +++ b/motrix_deploy_unitree/src/motrix_deploy_unitree/cli.py @@ -81,12 +81,6 @@ def _parser() -> argparse.ArgumentParser: joint.add_argument("--move-duration", type=_positive_float, default=2.0) joint.add_argument("--hold-duration", type=_non_negative_float, default=1.0) joint.add_argument("--return-duration", type=_non_negative_float, default=2.0) - joint.add_argument( - "--hardware-confirm", - action="store_true", - required=True, - help="Confirm the robot is suspended, in low-level/debug mode, and an operator is ready", - ) return parser @@ -112,7 +106,6 @@ def main(argv: Sequence[str] | None = None) -> int: move_duration=args.move_duration, hold_duration=args.hold_duration, return_duration=args.return_duration, - hardware_confirmed=args.hardware_confirm, ) return 0 diff --git a/motrix_deploy_unitree/src/motrix_deploy_unitree/config.py b/motrix_deploy_unitree/src/motrix_deploy_unitree/config.py index 69a6607b..3e5e43b1 100644 --- a/motrix_deploy_unitree/src/motrix_deploy_unitree/config.py +++ b/motrix_deploy_unitree/src/motrix_deploy_unitree/config.py @@ -74,15 +74,15 @@ class UnitreeGo2BackendConfig: def __post_init__(self) -> None: if not isinstance(self.network_interface, str) or not self.network_interface: - raise ValidationError("backend.network_interface", "a non-empty interface name", self.network_interface) + raise ValidationError("runtime.network_interface", "a non-empty interface name", self.network_interface) for name in ("wait_for_remote_buttons", "validate_crc"): value = getattr(self, name) if not isinstance(value, bool): - raise ValidationError(f"backend.{name}", "a boolean", value) + raise ValidationError(f"runtime.{name}", "a boolean", value) for name in ("lowcmd_topic", "lowstate_topic"): value = getattr(self, name) if not isinstance(value, str) or not value: - raise ValidationError(f"backend.{name}", "a non-empty DDS topic", value) + raise ValidationError(f"runtime.{name}", "a non-empty DDS topic", value) for name in ( "connect_timeout_s", "default_transition_duration_s", @@ -92,31 +92,31 @@ def __post_init__(self) -> None: ): value = getattr(self, name) if not isinstance(value, (int, float)) or isinstance(value, bool) or not np.isfinite(value): - raise ValidationError(f"backend.{name}", "a finite number", value) + raise ValidationError(f"runtime.{name}", "a finite number", value) if self.connect_timeout_s <= 0: - raise ValidationError("backend.connect_timeout_s", "a positive duration", self.connect_timeout_s) + raise ValidationError("runtime.connect_timeout_s", "a positive duration", self.connect_timeout_s) if self.default_transition_duration_s <= 0: raise ValidationError( - "backend.default_transition_duration_s", + "runtime.default_transition_duration_s", "a positive duration", self.default_transition_duration_s, ) if self.damping_duration_s < 0 or self.damping_kd < 0: - raise ValidationError("backend.damping", "non-negative duration and kd", "invalid value") + raise ValidationError("runtime.damping", "non-negative duration and kd", "invalid value") if self.lie_down_duration_s <= 0: - raise ValidationError("backend.lie_down_duration_s", "a positive duration", self.lie_down_duration_s) + raise ValidationError("runtime.lie_down_duration_s", "a positive duration", self.lie_down_duration_s) self.lie_down_position() self._validate_gain_override("kp", self.kp) self._validate_gain_override("kd", self.kd) if not isinstance(self.domain_id, int) or isinstance(self.domain_id, bool) or self.domain_id < 0: - raise ValidationError("backend.domain_id", "a non-negative integer", self.domain_id) + raise ValidationError("runtime.domain_id", "a non-negative integer", self.domain_id) if ( not isinstance(self.subscriber_queue_depth, int) or isinstance(self.subscriber_queue_depth, bool) or self.subscriber_queue_depth <= 0 ): raise ValidationError( - "backend.subscriber_queue_depth", + "runtime.subscriber_queue_depth", "a positive integer", self.subscriber_queue_depth, ) @@ -125,17 +125,17 @@ def __post_init__(self) -> None: or isinstance(self.motor_mode, bool) or not 0 <= self.motor_mode <= 0xFF ): - raise ValidationError("backend.motor_mode", "an integer in [0, 255]", self.motor_mode) + raise ValidationError("runtime.motor_mode", "an integer in [0, 255]", self.motor_mode) for name in ("start_button", "enable_button", "emergency_stop_button", "lie_down_button"): value = getattr(self, name) if value not in BUTTON_NAMES: - raise ValidationError(f"backend.{name}", f"one of {sorted(BUTTON_NAMES)}", value) + raise ValidationError(f"runtime.{name}", f"one of {sorted(BUTTON_NAMES)}", value) buttons = (self.start_button, self.enable_button, self.emergency_stop_button, self.lie_down_button) if len(set(buttons)) != len(buttons): - raise ValidationError("backend.remote_buttons", "four distinct buttons", "duplicate button") + raise ValidationError("runtime.remote_buttons", "four distinct buttons", "duplicate button") if not isinstance(self.joint_name_to_motor_index, Mapping): raise ValidationError( - "backend.joint_name_to_motor_index", + "runtime.joint_name_to_motor_index", "a joint-name to motor-index mapping", type(self.joint_name_to_motor_index).__name__, ) @@ -153,9 +153,9 @@ def from_mapping(cls, value: Mapping[str, Any]) -> "UnitreeGo2BackendConfig": } actual_fields = set(values) if missing := required_fields - actual_fields: - raise ValidationError("backend", f"required fields {sorted(required_fields)}", f"missing={sorted(missing)}") + raise ValidationError("runtime", f"required fields {sorted(required_fields)}", f"missing={sorted(missing)}") if unknown := actual_fields - expected_fields: - raise ValidationError("backend", f"known fields {sorted(expected_fields)}", f"unknown={sorted(unknown)}") + raise ValidationError("runtime", f"known fields {sorted(expected_fields)}", f"unknown={sorted(unknown)}") mapping = values.get("joint_name_to_motor_index") if isinstance(mapping, Mapping): values["joint_name_to_motor_index"] = dict(mapping) @@ -166,18 +166,18 @@ def _validate_gain_override(name: str, value: object | None) -> None: if value is None: return if isinstance(value, bool): - raise ValidationError(f"backend.{name}", "a non-negative scalar or 12 values", value) + raise ValidationError(f"runtime.{name}", "a non-negative scalar or 12 values", value) try: array = np.asarray(value, dtype=np.float32) except (TypeError, ValueError) as error: raise ValidationError( - f"backend.{name}", + f"runtime.{name}", "a non-negative scalar or 12 values", value, ) from error if array.shape not in ((), (GO2_MOTOR_COUNT,)) or not np.all(np.isfinite(array)) or np.any(array < 0): raise ValidationError( - f"backend.{name}", + f"runtime.{name}", "a non-negative scalar or 12 finite non-negative values", value, ) @@ -188,13 +188,13 @@ def lie_down_position(self) -> np.ndarray: position = np.asarray(self.lie_down_joint_position, dtype=np.float32) except (TypeError, ValueError) as error: raise ValidationError( - "backend.lie_down_joint_position", + "runtime.lie_down_joint_position", "12 finite canonical joint positions", self.lie_down_joint_position, ) from error if position.shape != (GO2_MOTOR_COUNT,) or not np.all(np.isfinite(position)): raise ValidationError( - "backend.lie_down_joint_position", + "runtime.lie_down_joint_position", "12 finite canonical joint positions", self.lie_down_joint_position, ) @@ -209,7 +209,7 @@ def gain_override(self, name: str, joint_count: int) -> np.ndarray | None: if array.shape == (): return np.full(joint_count, array.item(), dtype=np.float32) if array.shape != (joint_count,): - raise ValidationError(f"backend.{name}", f"{joint_count} canonical joint gains", value) + raise ValidationError(f"runtime.{name}", f"{joint_count} canonical joint gains", value) return np.array(array, dtype=np.float32, copy=True) def motor_indices(self, joint_names: tuple[str, ...]) -> np.ndarray: @@ -218,16 +218,16 @@ def motor_indices(self, joint_names: tuple[str, ...]) -> np.ndarray: actual = set(self.joint_name_to_motor_index) if actual != expected: raise ValidationError( - "backend.joint_name_to_motor_index", + "runtime.joint_name_to_motor_index", f"exactly joints {sorted(expected)}", f"missing={sorted(expected - actual)}, unknown={sorted(actual - expected)}", ) values = tuple(self.joint_name_to_motor_index[name] for name in joint_names) if any(not isinstance(index, int) or isinstance(index, bool) for index in values): - raise ValidationError("backend.motor_indices", "integer motor indices", values) + raise ValidationError("runtime.motor_indices", "integer motor indices", values) if len(set(values)) != len(values) or any(not 0 <= index < GO2_MOTOR_COUNT for index in values): raise ValidationError( - "backend.motor_indices", + "runtime.motor_indices", f"unique values in [0, {GO2_MOTOR_COUNT - 1}]", values, ) diff --git a/motrix_deploy_unitree/src/motrix_deploy_unitree/direct.py b/motrix_deploy_unitree/src/motrix_deploy_unitree/direct.py index 037a9659..5a4ee0d3 100644 --- a/motrix_deploy_unitree/src/motrix_deploy_unitree/direct.py +++ b/motrix_deploy_unitree/src/motrix_deploy_unitree/direct.py @@ -5,6 +5,7 @@ import time from collections.abc import Callable, Mapping +from dataclasses import replace from pathlib import Path from types import TracebackType from typing import Any @@ -12,7 +13,7 @@ import numpy as np from motrix_deploy.artifact import read_artifact -from motrix_deploy.contracts import HealthStatus, RobotCommand, RobotSpec, RobotState +from motrix_deploy.contracts import HealthStatus, JointServoCommand, RobotSpec, RobotState from motrix_deploy.errors import ValidationError from motrix_deploy.profile import DeploymentProfile from motrix_deploy_unitree.config import ( @@ -53,8 +54,11 @@ def __init__( self.backend = backend self._opened = False self._closed = False - self._task_gain("kp") - self._task_gain("kd") + backend.config = replace( + backend.config, + kp=self._task_gain("kp") if backend.config.kp is None else backend.config.kp, + kd=self._task_gain("kd") if backend.config.kd is None else backend.config.kd, + ) @classmethod def from_profile( @@ -62,7 +66,6 @@ def from_profile( profile: DeploymentProfile, *, network_interface: str, - hardware_confirmed: bool, joint_name_to_motor_index: Mapping[str, int] = GO2_JOINT_NAME_TO_MOTOR_INDEX, backend_options: Mapping[str, Any] | None = None, sdk: UnitreeSdkBindings | None = None, @@ -72,11 +75,10 @@ def from_profile( """Build the direct API from an explicit robot, gain, and timing profile.""" return cls._from_contract( robot=profile.robot, - task_config=profile.task.config, + task_config=profile.task.model_dump(mode="python"), control_period_s=profile.control.period_s, state_timeout_s=profile.control.state_timeout_s, network_interface=network_interface, - hardware_confirmed=hardware_confirmed, joint_name_to_motor_index=joint_name_to_motor_index, backend_options=backend_options, sdk=sdk, @@ -90,7 +92,6 @@ def from_artifact( artifact_path: str | Path, *, network_interface: str, - hardware_confirmed: bool, joint_name_to_motor_index: Mapping[str, int] = GO2_JOINT_NAME_TO_MOTOR_INDEX, backend_options: Mapping[str, Any] | None = None, sdk: UnitreeSdkBindings | None = None, @@ -102,11 +103,10 @@ def from_artifact( manifest = artifact.manifest return cls._from_contract( robot=manifest.robot, - task_config=manifest.task.config, + task_config=manifest.task.model_dump(mode="python"), control_period_s=manifest.control.period_s, state_timeout_s=manifest.control.state_timeout_s, network_interface=network_interface, - hardware_confirmed=hardware_confirmed, joint_name_to_motor_index=joint_name_to_motor_index, backend_options=backend_options, sdk=sdk, @@ -123,7 +123,6 @@ def _from_contract( control_period_s: float, state_timeout_s: float, network_interface: str, - hardware_confirmed: bool, joint_name_to_motor_index: Mapping[str, int], backend_options: Mapping[str, Any] | None, sdk: UnitreeSdkBindings | None, @@ -141,7 +140,7 @@ def _from_contract( config, control_period_s=control_period_s, state_timeout_s=state_timeout_s, - hardware_confirmed=hardware_confirmed, + spec=robot, sdk=sdk, clock_ns=clock_ns, sleep=sleep, @@ -161,7 +160,7 @@ def open(self) -> None: if self._opened: raise RuntimeError("direct Unitree interface is already open") try: - self.backend.open(self.robot) + self.backend.open() except Exception: try: self.backend.close() @@ -175,10 +174,10 @@ def read_data(self, timeout_s: float | None = None) -> RobotState: self._require_open() return self.backend.read_state(self.state_timeout_s if timeout_s is None else timeout_s) - def default_pose_command(self) -> RobotCommand: + def default_pose_command(self) -> JointServoCommand: """Build the zero-action default-pose command stored by the control contract.""" zeros = np.zeros(self.robot.joint_count, dtype=np.float32) - return RobotCommand( + return JointServoCommand( joint_position=np.array(self.robot.default_joint_position, copy=True), joint_velocity=zeros, feedforward_torque=zeros, @@ -186,10 +185,10 @@ def default_pose_command(self) -> RobotCommand: kd=self._task_gain("kd"), ) - def enable_command_output(self, initial_command: RobotCommand | None = None) -> None: + def enable_command_output(self) -> None: """Run the production Start -> default pose -> A hardware enable sequence.""" self._require_open() - self.backend.enable(self.default_pose_command() if initial_command is None else initial_command) + self.backend.enable() def make_joint_command( self, @@ -199,10 +198,10 @@ def make_joint_command( feedforward_torque: object | None = None, kp: object | None = None, kd: object | None = None, - ) -> RobotCommand: + ) -> JointServoCommand: """Construct one canonical float32 command using configured gains by default.""" zeros = np.zeros(self.robot.joint_count, dtype=np.float32) - return RobotCommand( + return JointServoCommand( joint_position=self._command_array(joint_position, "joint_position"), joint_velocity=(zeros if joint_velocity is None else self._command_array(joint_velocity, "joint_velocity")), feedforward_torque=( @@ -212,7 +211,7 @@ def make_joint_command( kd=self._task_gain("kd") if kd is None else self._command_array(kd, "kd"), ) - def send_command(self, command: RobotCommand) -> None: + def send_command(self, command: JointServoCommand) -> None: """Send one canonical command through the production LowCmd writer.""" self._require_open() self.backend.write_command(command) @@ -225,7 +224,7 @@ def send_joint_command( feedforward_torque: object | None = None, kp: object | None = None, kd: object | None = None, - ) -> RobotCommand: + ) -> JointServoCommand: """Construct and send one command, returning the exact command sent.""" command = self.make_joint_command( joint_position, diff --git a/motrix_deploy_unitree/src/motrix_deploy_unitree/go2_joint_control.py b/motrix_deploy_unitree/src/motrix_deploy_unitree/go2_joint_control.py index 056b7541..d25423ff 100644 --- a/motrix_deploy_unitree/src/motrix_deploy_unitree/go2_joint_control.py +++ b/motrix_deploy_unitree/src/motrix_deploy_unitree/go2_joint_control.py @@ -16,13 +16,10 @@ def _build_robot( artifact: str | Path, network_interface: str, - *, - hardware_confirmed: bool, ) -> UnitreeGo2DirectInterface: return UnitreeGo2DirectInterface.from_artifact( artifact, network_interface=network_interface, - hardware_confirmed=hardware_confirmed, ) @@ -75,7 +72,6 @@ def run_joint_control( move_duration: float, hold_duration: float, return_duration: float, - hardware_confirmed: bool, ) -> None: _validate_duration("--move-duration", move_duration, allow_zero=False) _validate_duration("--hold-duration", hold_duration, allow_zero=True) @@ -83,7 +79,7 @@ def run_joint_control( if not math.isfinite(target_position): raise ValueError(f"target_position must be finite, got {target_position!r}") - robot = _build_robot(artifact, network_interface, hardware_confirmed=hardware_confirmed) + robot = _build_robot(artifact, network_interface) joint_names: Sequence[str] = robot.robot.joint_names if joint_name not in joint_names: raise ValueError(f"unknown joint {joint_name!r}; expected one of {list(joint_names)}") diff --git a/motrix_deploy_unitree/src/motrix_deploy_unitree/interface.py b/motrix_deploy_unitree/src/motrix_deploy_unitree/interface.py index a0abab7b..e24a2323 100644 --- a/motrix_deploy_unitree/src/motrix_deploy_unitree/interface.py +++ b/motrix_deploy_unitree/src/motrix_deploy_unitree/interface.py @@ -12,9 +12,17 @@ import numpy as np -from motrix_deploy.backend import RobotInterface -from motrix_deploy.contracts import HealthStatus, RobotCapabilities, RobotCommand, RobotSpec, RobotState +from motrix_deploy.contracts import ( + HealthStatus, + JointControlMode, + JointServoCommand, + RobotCapabilities, + RobotCommand, + RobotSpec, + RobotState, +) from motrix_deploy.errors import EmergencyStopError, LieDownRequestedError, ValidationError +from motrix_deploy.robot.interface import RobotInterface from motrix_deploy_unitree.config import GO2_MOTOR_COUNT, UnitreeGo2BackendConfig from motrix_deploy_unitree.remote import UnitreeRemoteGamePadDevice, UnitreeRemoteState, decode_wireless_remote from motrix_env_core.input import GamePadDevice @@ -76,8 +84,8 @@ def __init__( config: UnitreeGo2BackendConfig, control_period_s: float, state_timeout_s: float, - hardware_confirmed: bool, *, + spec: RobotSpec, sdk: UnitreeSdkBindings | None = None, clock_ns: Callable[[], int] = time.monotonic_ns, sleep: Callable[[float], None] = time.sleep, @@ -89,11 +97,8 @@ def __init__( ): if not isinstance(value, (int, float)) or isinstance(value, bool) or not np.isfinite(value) or value <= 0: raise ValidationError(path, "a positive finite number", value) - if not isinstance(hardware_confirmed, bool): - raise ValidationError("hardware.confirm", "a boolean", hardware_confirmed) self._control_period_s = float(control_period_s) self._state_timeout_s = float(state_timeout_s) - self._hardware_confirmed = hardware_confirmed self._sdk = sdk self._clock_ns = clock_ns self._sleep = sleep @@ -104,7 +109,7 @@ def __init__( self._low_cmd: Any = None self._command_crc: Any = None self._state_crc: Any = None - self._spec: RobotSpec | None = None + self._spec = spec self._motor_indices = np.empty(0, dtype=np.int64) self._kp_override: np.ndarray | None = None self._kd_override: np.ndarray | None = None @@ -127,6 +132,10 @@ def __init__( self._stopped = False self._closed = False + @property + def spec(self) -> RobotSpec: + return self._spec + @property def capabilities(self) -> RobotCapabilities: if self._capabilities is None: @@ -175,15 +184,10 @@ def _disable_high_level_motion_services(self, sdk: UnitreeSdkBindings) -> None: if service_status != 0: raise RuntimeError(f"RobotState ServiceSwitch(sport_mode, false) failed with code {service_status}") - def open(self, spec: RobotSpec) -> None: + def open(self) -> None: + spec = self._spec if self._opened and not self._closed: raise RuntimeError("Unitree backend is already open") - if not self._hardware_confirmed: - raise ValidationError( - "hardware.confirm", - "true after the operator completes the physical safety checklist", - False, - ) self._motor_indices = self.config.motor_indices(spec.joint_names) self._spec = spec self._kp_override = self.config.gain_override("kp", spec.joint_count) @@ -216,7 +220,7 @@ def open(self, spec: RobotSpec) -> None: len(self._low_cmd.motor_cmd), ) self._capabilities = RobotCapabilities( - control_modes=("joint_pd",), + control_modes=(JointControlMode.SERVO,), state_fields=frozenset( { "joint_position", @@ -238,18 +242,25 @@ def open(self, spec: RobotSpec) -> None: self._health_reason = "" self._last_communication_ns = self._latest_receive_time_ns - def enable(self, initial_command: RobotCommand) -> None: + def enable(self) -> None: self._require_open() if self._enabled: return - self._validate_command(initial_command) - assert self._spec is not None - if not np.allclose(initial_command.joint_position, self._spec.default_joint_position, atol=1e-6, rtol=0.0): + if self._kp_override is None or self._kd_override is None: raise ValidationError( - "backend.initial_command.joint_position", - "RobotSpec.default_joint_position", - initial_command.joint_position.tolist(), + "backend.enable", + "explicit backend kp and kd safety gains for startup", + "missing safety gains", ) + zeros = np.zeros(self._spec.joint_count, dtype=np.float32) + initial_command = JointServoCommand( + joint_position=self._spec.default_joint_position, + joint_velocity=zeros, + feedforward_torque=zeros, + kp=self._kp_override, + kd=self._kd_override, + ) + self._validate_command(initial_command) kp, kd = self._effective_gains(initial_command) self._last_position_kp = np.array(kp, dtype=np.float32, copy=True) @@ -464,6 +475,8 @@ def _validate_message_fields(self, message: Any) -> None: raise ValidationError(f"state.imu.{name}", f"{expected} finite values", value) def _validate_command(self, command: RobotCommand) -> None: + if not isinstance(command, JointServoCommand): + raise ValidationError("command.mode", "joint_servo", command.mode) assert self._spec is not None expected = (self._spec.joint_count,) for name in ("joint_position", "joint_velocity", "feedforward_torque", "kp", "kd"): diff --git a/motrix_deploy_unitree/src/motrix_deploy_unitree/plugin.py b/motrix_deploy_unitree/src/motrix_deploy_unitree/plugin.py index a0a89325..798f7df1 100644 --- a/motrix_deploy_unitree/src/motrix_deploy_unitree/plugin.py +++ b/motrix_deploy_unitree/src/motrix_deploy_unitree/plugin.py @@ -1,40 +1,30 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 -"""Entry-point factory for the Unitree Go2 deployment backend.""" +"""Entry-point factory for the Unitree Go2 deployment runtime.""" from collections.abc import Mapping from typing import Any -from motrix_deploy.backend import BackendCreateContext, RobotInterface -from motrix_deploy.errors import ValidationError +from motrix_deploy.runtime.factory import RuntimeCreateContext +from motrix_deploy.runtime.hardware import HardwareRuntime from motrix_deploy_unitree.config import UnitreeGo2BackendConfig from motrix_deploy_unitree.interface import UnitreeGo2RobotInterface -def create_backend(config: Mapping[str, Any], context: BackendCreateContext) -> RobotInterface: - """Construct a guarded Unitree Go2 hardware backend.""" - if context.viewer: - raise ValidationError("viewer", "false for physical hardware", context.viewer) - if not isinstance(context.realtime, bool): - raise ValidationError("realtime", "a boolean", context.realtime) - if not context.realtime: - raise ValidationError("realtime", "true for physical hardware", context.realtime) - if not isinstance(context.hardware_confirmed, bool): - raise ValidationError("hardware.confirm", "a boolean", context.hardware_confirmed) - if not context.hardware_confirmed: - raise ValidationError( - "hardware.confirm", - "true after the operator completes the physical safety checklist", - context.hardware_confirmed, - ) - - return UnitreeGo2RobotInterface( +def create_runtime( + config: Mapping[str, Any], + context: RuntimeCreateContext, +) -> HardwareRuntime: + """Construct a realtime runtime around the existing SDK2 hardware facade.""" + + robot = UnitreeGo2RobotInterface( UnitreeGo2BackendConfig.from_mapping(config), control_period_s=context.control.period_s, state_timeout_s=context.control.state_timeout_s, - hardware_confirmed=context.hardware_confirmed, + spec=context.robot, ) + return HardwareRuntime(robot=robot, realtime=True) -__all__ = ["create_backend"] +__all__ = ["create_runtime"] diff --git a/motrix_deploy_unitree/tests/test_unitree_deploy_backend.py b/motrix_deploy_unitree/tests/test_unitree_deploy_backend.py index 7b28ecf8..beb4fd24 100644 --- a/motrix_deploy_unitree/tests/test_unitree_deploy_backend.py +++ b/motrix_deploy_unitree/tests/test_unitree_deploy_backend.py @@ -11,10 +11,12 @@ import pytest from motrix_deploy.artifact import ControlSpec, TaskSpec -from motrix_deploy.backend import BackendCreateContext, GamePadDeviceProvider -from motrix_deploy.contracts import RobotCommand, RobotSpec +from motrix_deploy.contracts import JointServoCommand, JointTorqueCommand, RobotSpec from motrix_deploy.errors import EmergencyStopError, LieDownRequestedError, ValidationError from motrix_deploy.profile import DeploymentProfile +from motrix_deploy.robot.interface import GamePadDeviceProvider +from motrix_deploy.runtime.factory import RuntimeCreateContext +from motrix_deploy.runtime.hardware import HardwareRuntime from motrix_deploy_unitree import ( UnitreeGo2BackendConfig, UnitreeGo2DirectInterface, @@ -23,8 +25,8 @@ UnitreeSdkBindings, decode_wireless_remote, ) -from motrix_deploy_unitree.cli import _parser -from motrix_deploy_unitree.plugin import create_backend +from motrix_deploy_unitree.cli import _parser, main +from motrix_deploy_unitree.plugin import create_runtime JOINT_NAMES = ( "FL_hip_joint", @@ -253,6 +255,8 @@ def _config(**overrides) -> UnitreeGo2BackendConfig: "default_transition_duration_s": 0.02, "damping_duration_s": 0.04, "wait_for_remote_buttons": False, + "kp": 35.0, + "kd": 0.5, } values.update(overrides) return UnitreeGo2BackendConfig(**values) @@ -261,22 +265,20 @@ def _config(**overrides) -> UnitreeGo2BackendConfig: def _backend( config: UnitreeGo2BackendConfig, bus: _Bus, - *, - hardware_confirmed: bool = True, ) -> UnitreeGo2RobotInterface: return UnitreeGo2RobotInterface( config, control_period_s=0.02, state_timeout_s=0.1, - hardware_confirmed=hardware_confirmed, + spec=_spec(), sdk=bus.sdk(), sleep=lambda _: None, ) -def _command(position: np.ndarray) -> RobotCommand: +def _command(position: np.ndarray) -> JointServoCommand: zeros = np.zeros(12, dtype=np.float32) - return RobotCommand( + return JointServoCommand( joint_position=np.asarray(position, dtype=np.float32), joint_velocity=zeros, feedforward_torque=zeros, @@ -322,7 +324,7 @@ def test_open_releases_mcf_mode_and_disables_sport_service_before_lowcmd() -> No bus = _Bus() backend = _backend(_config(), bus) - backend.open(_spec()) + backend.open() call_names = [name for name, _ in bus.service_calls] assert call_names.index("stand_down") < call_names.index("release_mode") @@ -337,34 +339,24 @@ def test_sport_service_shutdown_failure_aborts_before_lowcmd_channels() -> None: backend = _backend(_config(), bus) with pytest.raises(RuntimeError, match="ServiceSwitch.*code 42"): - backend.open(_spec()) + backend.open() assert bus.callback is None assert bus.commands == [] -def test_hardware_confirmation_fails_before_dds_initialization() -> None: - bus = _Bus() - backend = _backend(_config(), bus, hardware_confirmed=False) - - with pytest.raises(ValidationError, match="hardware.confirm"): - backend.open(_spec()) - - assert bus.factory_calls == [] - - def test_lowstate_and_lowcmd_are_mapped_by_canonical_joint_name() -> None: bus = _Bus() spec = _spec() backend = _backend(_config(), bus) - backend.open(spec) + backend.open() state = backend.read_state(0.1) expected_position = np.asarray([MOTOR_MAPPING[name] for name in JOINT_NAMES], dtype=np.float32) np.testing.assert_array_equal(state.joint_position, expected_position) np.testing.assert_array_equal(state.base_orientation_xyzw, [0.0, 0.0, 0.0, 1.0]) - backend.enable(_command(spec.default_joint_position)) + backend.enable() target = spec.default_joint_position + np.linspace(-0.01, 0.01, 12, dtype=np.float32) command = _command(target) backend.write_command(command) @@ -390,14 +382,38 @@ def test_lowstate_and_lowcmd_are_mapped_by_canonical_joint_name() -> None: assert backend.health().reason == "backend is closed" +def test_enable_requires_explicit_hardware_safety_gains_before_publishing() -> None: + bus = _Bus() + backend = _backend(_config(kp=None, kd=None), bus) + backend.open() + + with pytest.raises(ValidationError, match="safety gains"): + backend.enable() + assert bus.commands == [] + backend.close() + + +def test_servo_only_interface_rejects_torque_commands_before_publishing() -> None: + bus = _Bus() + backend = _backend(_config(), bus) + backend.open() + backend.enable() + command_count = len(bus.commands) + + with pytest.raises(ValidationError, match="joint_servo"): + backend.write_command(JointTorqueCommand(torque=np.zeros(12, dtype=np.float32))) + assert len(bus.commands) == command_count + backend.close() + + def test_runtime_gain_overrides_apply_to_transition_and_policy_commands() -> None: bus = _Bus() spec = _spec() kd = np.linspace(0.2, 1.3, 12, dtype=np.float32) backend = _backend(_config(kp=25.0, kd=kd.tolist()), bus) - backend.open(spec) + backend.open() - backend.enable(_command(spec.default_joint_position)) + backend.enable() transition = copy.deepcopy(bus.commands[-1]) backend.write_command(_command(spec.default_joint_position)) policy = copy.deepcopy(bus.commands[-1]) @@ -454,15 +470,20 @@ def test_enable_waits_for_start_transitions_then_waits_for_a() -> None: 2: _button("start"), 3: _button("start") | _button("A"), } - spec = _spec() backend = _backend( - _config(wait_for_remote_buttons=True, damping_duration_s=0.0), + UnitreeGo2BackendConfig( + network_interface="enp3s0", + default_transition_duration_s=0.02, + damping_duration_s=0.0, + kp=35.0, + kd=0.5, + ), bus, ) - backend.open(spec) + backend.open() backend.read_state(0.1) - backend.enable(_command(spec.default_joint_position)) + backend.enable() assert len(bus.commands) == 3 assert all(motor.kp == 0.0 and motor.kd == 0.0 for motor in bus.commands[0].motor_cmd) @@ -473,14 +494,13 @@ def test_enable_waits_for_start_transitions_then_waits_for_a() -> None: def test_select_remote_button_raises_emergency_stop_and_stop_publishes_damping() -> None: bus = _Bus() - spec = _spec() backend = _backend( _config(damping_duration_s=0.02), bus, ) - backend.open(spec) + backend.open() backend.read_state(0.1) - backend.enable(_command(spec.default_joint_position)) + backend.enable() commands_before_stop = len(bus.commands) bus.state.wireless_remote = _remote_payload(_button("select")) @@ -498,12 +518,11 @@ def test_select_remote_button_raises_emergency_stop_and_stop_publishes_damping() def test_b_button_lies_down_then_enters_damping() -> None: bus = _Bus() - spec = _spec() config = _config(lie_down_duration_s=0.04, damping_duration_s=0.02) backend = _backend(config, bus) - backend.open(spec) + backend.open() backend.read_state(0.1) - backend.enable(_command(spec.default_joint_position)) + backend.enable() commands_before_stop = len(bus.commands) bus.state.wireless_remote = _remote_payload(_button("B")) @@ -530,11 +549,10 @@ def test_b_button_lies_down_then_enters_damping() -> None: def test_select_has_priority_over_simultaneous_lie_down_request() -> None: bus = _Bus() - spec = _spec() backend = _backend(_config(damping_duration_s=0.02), bus) - backend.open(spec) + backend.open() backend.read_state(0.1) - backend.enable(_command(spec.default_joint_position)) + backend.enable() commands_before_stop = len(bus.commands) bus.state.wireless_remote = _remote_payload(_button("select") | _button("B")) @@ -551,7 +569,7 @@ def test_select_has_priority_over_simultaneous_lie_down_request() -> None: def test_lie_down_config_rejects_invalid_pose() -> None: - with pytest.raises(ValidationError, match="backend.lie_down_joint_position"): + with pytest.raises(ValidationError, match="runtime.lie_down_joint_position"): _config(lie_down_joint_position=[0.0] * 11) @@ -571,50 +589,58 @@ def test_motor_mapping_must_exactly_cover_artifact_joint_names() -> None: config.motor_indices(JOINT_NAMES) -def test_hardware_confirmation_must_be_a_real_boolean() -> None: - with pytest.raises(ValidationError, match="hardware.confirm"): - UnitreeGo2RobotInterface( - _config(), - control_period_s=0.02, - state_timeout_s=0.1, - hardware_confirmed="true", - ) - - -def _context( - *, - viewer: bool = False, - realtime: bool = True, - hardware_confirmed: bool = True, -) -> BackendCreateContext: - return BackendCreateContext( +def _context() -> RuntimeCreateContext: + return RuntimeCreateContext( + robot=_spec(), control=ControlSpec(period_s=0.02, state_timeout_s=0.1), - viewer=viewer, - realtime=realtime, - hardware_confirmed=hardware_confirmed, ) -def test_plugin_factory_enforces_hardware_guards_and_strict_config() -> None: +def test_plugin_factory_creates_realtime_hardware_runtime_and_strict_config() -> None: config = { "name": "unitree_go2", "network_interface": "enp3s0", } - with pytest.raises(ValidationError, match="viewer"): - create_backend(config, _context(viewer=True)) - with pytest.raises(ValidationError, match="realtime"): - create_backend(config, _context(realtime=False)) - with pytest.raises(ValidationError, match="hardware.confirm"): - create_backend(config, _context(hardware_confirmed=False)) - - backend = create_backend(config, _context()) - assert isinstance(backend, UnitreeGo2RobotInterface) + runtime = create_runtime(config, _context()) + assert isinstance(runtime, HardwareRuntime) + assert runtime.realtime is True + assert isinstance(runtime.robot, UnitreeGo2RobotInterface) + assert runtime.robot.spec.joint_names == JOINT_NAMES with pytest.raises(ValidationError, match="unknown"): UnitreeGo2BackendConfig.from_mapping({**config, "unexpected": 1}) +def test_plugin_runtime_uses_production_interface_with_injected_sdk(monkeypatch) -> None: + bus = _Bus() + monkeypatch.setattr("motrix_deploy_unitree.interface._load_sdk_bindings", bus.sdk) + runtime = create_runtime( + { + "network_interface": "enp3s0", + "kp": 35.0, + "kd": 0.5, + "default_transition_duration_s": 0.02, + "damping_duration_s": 0.0, + "wait_for_remote_buttons": False, + }, + _context(), + ) + robot = runtime.robot + try: + robot.open() + robot.read_state(0.1) + robot.enable() + robot.write_command(_command(robot.spec.default_joint_position)) + assert robot.health().healthy + assert bus.factory_calls == [(0, "enp3s0")] + assert bus.commands[-1].crc == 123 + finally: + robot.close() + runtime.close() + assert robot.health().reason == "backend is closed" + + @pytest.mark.parametrize( ("name", "value"), [ @@ -625,20 +651,23 @@ def test_plugin_factory_enforces_hardware_guards_and_strict_config() -> None: ], ) def test_backend_config_rejects_invalid_gain_overrides(name: str, value: object) -> None: - with pytest.raises(ValidationError, match=f"backend.{name}"): + with pytest.raises(ValidationError, match=f"runtime.{name}"): _config(**{name: value}) +class _JointControlTaskSpec(TaskSpec): + task_name = "unitree_joint_control/v1" + kp: list[float] + kd: list[float] + + def _profile() -> DeploymentProfile: return DeploymentProfile( robot=_spec(), - task=TaskSpec( - name="go2_walk/v1", - observation_size=1, - action_size=12, - config={"kp": [35.0] * 12, "kd": [0.5] * 12}, - ), + task=_JointControlTaskSpec(kp=[35.0] * 12, kd=[0.5] * 12), control=ControlSpec(period_s=0.02, state_timeout_s=0.1), + observation_size=1, + action_size=12, ) @@ -647,7 +676,6 @@ def test_direct_interface_builds_from_profile_without_artifact() -> None: direct = UnitreeGo2DirectInterface.from_profile( _profile(), network_interface="enp5s0", - hardware_confirmed=True, backend_options={ "default_transition_duration_s": 0.02, "damping_duration_s": 0.0, @@ -671,9 +699,25 @@ def test_direct_interface_builds_from_profile_without_artifact() -> None: assert bus.commands +def test_joint_control_cli_dispatches_artifact_and_motion_defaults(monkeypatch) -> None: + calls = [] + monkeypatch.setattr("motrix_deploy_unitree.cli.run_joint_control", lambda **kwargs: calls.append(kwargs)) + + assert main(["joint-control", "enp5s0", "FL_thigh_joint", "0.9", "--artifact", "example.deploy"]) == 0 + + assert len(calls) == 1 + assert str(calls[0]["artifact"]) == "example.deploy" + assert calls[0]["network_interface"] == "enp5s0" + assert calls[0]["joint_name"] == "FL_thigh_joint" + assert calls[0]["target_position"] == pytest.approx(0.9) + assert calls[0]["move_duration"] == pytest.approx(2.0) + assert calls[0]["hold_duration"] == pytest.approx(1.0) + assert calls[0]["return_duration"] == pytest.approx(2.0) + + def test_joint_control_requires_artifact() -> None: with pytest.raises(SystemExit): - _parser().parse_args(["joint-control", "enp5s0", "FL_thigh_joint", "0.9", "--hardware-confirm"]) + _parser().parse_args(["joint-control", "enp5s0", "FL_thigh_joint", "0.9"]) def test_read_lowstate_uses_unified_cli_defaults() -> None: @@ -697,6 +741,5 @@ def test_joint_control_rejects_environment_option() -> None: "example.deploy", "--env", "go2-walk-rough", - "--hardware-confirm", ] ) diff --git a/motrix_env_core/src/motrix_env_core/config/sim.py b/motrix_env_core/src/motrix_env_core/config/sim.py index 96ac442f..c1632cd5 100644 --- a/motrix_env_core/src/motrix_env_core/config/sim.py +++ b/motrix_env_core/src/motrix_env_core/config/sim.py @@ -1,6 +1,8 @@ # Copyright Motphys Technology Co., Ltd. 2025, 2026 # SPDX-License-Identifier: Apache-2.0 +from math import isfinite + from motrix_env_core.config.decorate import configclass @@ -14,11 +16,15 @@ class SimCfg: gravity: tuple[float, float, float] | None = None def validate(self) -> None: - if self.dt <= 0.0: - raise ValueError(f"sim.dt must be positive, got {self.dt}") - if self.solver_iterations is not None and self.solver_iterations <= 0: - raise ValueError(f"sim.solver_iterations must be positive, got {self.solver_iterations}") - if self.solver_tolerance is not None and self.solver_tolerance <= 0.0: - raise ValueError(f"sim.solver_tolerance must be positive, got {self.solver_tolerance}") + if isinstance(self.dt, bool) or not isfinite(self.dt) or self.dt <= 0.0: + raise ValueError(f"sim.dt must be positive and finite, got {self.dt}") + if self.solver_iterations is not None and ( + isinstance(self.solver_iterations, bool) + or not isinstance(self.solver_iterations, int) + or self.solver_iterations <= 0 + ): + raise ValueError(f"sim.solver_iterations must be a positive integer, got {self.solver_iterations}") + if self.solver_tolerance is not None and (not isfinite(self.solver_tolerance) or self.solver_tolerance <= 0.0): + raise ValueError(f"sim.solver_tolerance must be positive and finite, got {self.solver_tolerance}") if self.gravity is not None and len(self.gravity) != 3: raise ValueError(f"sim.gravity must contain 3 values, got {self.gravity!r}") diff --git a/motrix_env_core/tests/test_sim_cfg.py b/motrix_env_core/tests/test_sim_cfg.py index a38eda5e..cce35ab9 100644 --- a/motrix_env_core/tests/test_sim_cfg.py +++ b/motrix_env_core/tests/test_sim_cfg.py @@ -3,8 +3,9 @@ import pytest -from motrix_env_core.base import EnvCfg, SimCfg +from motrix_env_core.base import EnvCfg from motrix_env_core.config.scene import SceneCfg, SceneCompiler +from motrix_env_core.config.sim import SimCfg def test_scene_compiler_is_an_abstract_backend_boundary(): @@ -31,3 +32,32 @@ def test_env_cfg_uses_sim_config_dt_for_substeps_and_validation(): def test_env_cfg_requires_scene_config(): with pytest.raises(ValueError, match="EnvCfg.scene must be configured"): EnvCfg().validate() + + +@pytest.mark.parametrize("dt", [0.0, -0.1, float("nan"), float("inf"), -float("inf"), True, False]) +def test_sim_cfg_rejects_invalid_timestep(dt): + with pytest.raises(ValueError, match=r"sim\.dt must be positive and finite"): + SimCfg(dt=dt).validate() + + +@pytest.mark.parametrize("iterations", [0, -1, 1.5, 1.0, True, False, "100"]) +def test_sim_cfg_requires_positive_integer_solver_iterations(iterations): + with pytest.raises(ValueError, match=r"sim\.solver_iterations must be a positive integer"): + SimCfg(solver_iterations=iterations).validate() + + +@pytest.mark.parametrize("tolerance", [0.0, -0.1, float("nan"), float("inf"), -float("inf")]) +def test_sim_cfg_requires_positive_finite_solver_tolerance(tolerance): + with pytest.raises(ValueError, match=r"sim\.solver_tolerance must be positive and finite"): + SimCfg(solver_tolerance=tolerance).validate() + + +def test_sim_cfg_supports_backend_defaults_and_explicit_physics(): + # None preserves model/backend solver and gravity defaults. + SimCfg(solver_iterations=None, solver_tolerance=None, gravity=None).validate() + SimCfg(dt=1, solver_iterations=10, solver_tolerance=1e-6, gravity=(0.0, 0.0, -9.81)).validate() + + +def test_sim_cfg_retains_gravity_length_validation(): + with pytest.raises(ValueError, match=r"sim\.gravity must contain 3 values"): + SimCfg(gravity=(0.0, -9.81)).validate() diff --git a/motrix_envs/pyproject.toml b/motrix_envs/pyproject.toml index 2a75a2e4..55ad3b86 100644 --- a/motrix_envs/pyproject.toml +++ b/motrix_envs/pyproject.toml @@ -20,4 +20,4 @@ dependencies = [ ] [project.optional-dependencies] -deploy = ["motrix-deploy"] +deploy = ["motrix-deploy", "motrix-deploy-tasks"] diff --git a/motrix_envs/src/motrix_envs/deploy/go2_walk.py b/motrix_envs/src/motrix_envs/deploy/go2_walk.py index f2733dd7..8af0d3db 100644 --- a/motrix_envs/src/motrix_envs/deploy/go2_walk.py +++ b/motrix_envs/src/motrix_envs/deploy/go2_walk.py @@ -6,13 +6,13 @@ import numpy as np from motrix_robots.unitree import UnitreeGo2Robot -from motrix_deploy.artifact import ControlSpec, TaskSpec +from motrix_deploy.artifact import ControlSpec from motrix_deploy.profile import DeploymentProfile, register_profile_compiler +from motrix_deploy_tasks.tasks.go2_walk import Go2WalkTaskSpec from motrix_env_core import registry from motrix_envs.deploy.robot import build_robot_model, build_robot_spec, read_position_servo_gains from motrix_envs.locomotion.quadruped.cfg import QuadrupedWalkEnvCfg -GO2_TASK_NAME = "go2_walk/v1" GO2_COMMAND_SCALES = { "go2-walk-flat": np.ones(3, dtype=np.float32), "go2-walk-rough": np.full(3, 0.5, dtype=np.float32), @@ -33,11 +33,13 @@ def build_go2_walk_profile(env_name: str) -> DeploymentProfile: if env_cfg.control_config.simulate_action_latency: raise ValueError("Go2 deployment v1 requires control_config.simulate_action_latency=false") + model = build_robot_model(robot_cfg) robot = build_robot_spec( robot_cfg, key_pose_name=env_cfg.key_pose_name, + model=model, ) - kp, kd = read_position_servo_gains(build_robot_model(robot_cfg), robot.joint_names) + kp, kd = read_position_servo_gains(model, robot.joint_names) action_shape = env.action_space.shape observation_shape = env.policy_observation_space.shape if len(action_shape) != 1 or len(observation_shape) != 1: @@ -47,31 +49,32 @@ def build_go2_walk_profile(env_name: str) -> DeploymentProfile: if action_size != robot.joint_count: raise ValueError("Go2 deployment requires every model actuator to target one canonical robot joint") action_scale = env_cfg.control_config.action_scale - raw_lower = env.action_space.low - raw_upper = env.action_space.high + action_lower = env.action_space.low + action_upper = env.action_space.high command_cfg = env_cfg.commands.velocity feet_offsets = _feet_phase_offsets(env_cfg) - task = TaskSpec( - name=GO2_TASK_NAME, - observation_size=observation_size, - action_size=action_size, - config={ - "action_scale": action_scale, - "command_lower": command_cfg.lower.tolist(), - "command_upper": command_cfg.upper.tolist(), - "command_scale": GO2_COMMAND_SCALES[env_name].tolist(), - "feet_phase_offsets": feet_offsets.tolist(), - "gait_frequency_hz": env_cfg.gait_frequency, - "standing_threshold": env_cfg.commands.velocity.standing_threshold, - "kp": kp.tolist(), - "kd": kd.tolist(), - "raw_clip": [raw_lower.tolist(), raw_upper.tolist()], - }, + task = Go2WalkTaskSpec( + action_scale=np.broadcast_to(action_scale, (action_size,)).tolist(), + command_lower=command_cfg.lower.tolist(), + command_upper=command_cfg.upper.tolist(), + command_scale=GO2_COMMAND_SCALES[env_name].tolist(), + feet_phase_offsets=feet_offsets.tolist(), + gait_frequency_hz=env_cfg.gait_frequency, + standing_threshold=env_cfg.commands.velocity.standing_threshold, + # Match QuadrupedWalkEnv.compute_transition: orientation-only falls. + termination_min_up_z=0.5, + termination_min_base_height=None, + kp=kp.tolist(), + kd=kd.tolist(), + action_lower=action_lower.tolist(), + action_upper=action_upper.tolist(), ) return DeploymentProfile( robot=robot, task=task, control=ControlSpec(period_s=env_cfg.ctrl_dt, state_timeout_s=0.1), + observation_size=observation_size, + action_size=action_size, ) diff --git a/motrix_envs/tests/test_deploy_profile.py b/motrix_envs/tests/test_deploy_profile.py index 0470ef9d..973b7783 100644 --- a/motrix_envs/tests/test_deploy_profile.py +++ b/motrix_envs/tests/test_deploy_profile.py @@ -9,6 +9,7 @@ import pytest from motrix_robots.unitree import UnitreeGo2Robot +from motrix_deploy_tasks.tasks.go2_walk import Go2WalkTaskSpec from motrix_env_core import registry from motrix_envs.deploy import build_deployment_profile, registered_profile_compilers from motrix_envs.deploy.robot import ( @@ -36,14 +37,16 @@ def test_go2_walk_environments_compile_shared_deployment_profiles( env_cfg = registry.make_env_config(env_name) assert {"go2-walk-flat", "go2-walk-rough"} <= set(registered_profile_compilers()) + assert isinstance(profile.task, Go2WalkTaskSpec) assert profile.task.name == "go2_walk/v1" - assert profile.task.observation_size == 49 - assert profile.task.action_size == 12 + env = registry.make(env_name, num_envs=1, mode="train") + assert profile.observation_size == env.policy_observation_space.shape[0] + assert profile.action_size == env.action_space.shape[0] assert profile.control.period_s == pytest.approx(env_cfg.ctrl_dt) - assert profile.task.config["command_lower"] == env_cfg.commands.velocity.lower.tolist() - assert profile.task.config["command_upper"] == env_cfg.commands.velocity.upper.tolist() + assert profile.task.command_lower == env_cfg.commands.velocity.lower.tolist() + assert profile.task.command_upper == env_cfg.commands.velocity.upper.tolist() # Command scale is an artifact/runtime input contract and intentionally differs by deployment environment. - assert profile.task.config["command_scale"] == command_scale + assert profile.task.command_scale == command_scale def test_robot_spec_is_built_without_an_environment_config() -> None: diff --git a/motrix_rl/src/motrix_rl/deploy/service.py b/motrix_rl/src/motrix_rl/deploy/service.py index ace236cb..c1609026 100644 --- a/motrix_rl/src/motrix_rl/deploy/service.py +++ b/motrix_rl/src/motrix_rl/deploy/service.py @@ -50,12 +50,12 @@ def export_deploy_run( policy_bytes = exported.model_bytes input_spec = _deployment_tensor_spec( exported.report.input_spec, - expected_size=profile.task.observation_size, + expected_size=profile.observation_size, kind="input", ) output_spec = _deployment_tensor_spec( exported.report.output_spec, - expected_size=profile.task.action_size, + expected_size=profile.action_size, kind="output", ) diff --git a/motrix_rl/tests/test_deploy_export.py b/motrix_rl/tests/test_deploy_export.py index e5ef112b..ac11c0b6 100644 --- a/motrix_rl/tests/test_deploy_export.py +++ b/motrix_rl/tests/test_deploy_export.py @@ -3,17 +3,58 @@ """Task-agnostic deployment export service tests.""" +from importlib import metadata +from types import SimpleNamespace +from typing import ClassVar + import numpy as np import pytest from motrix_deploy.artifact import ControlSpec, TaskSpec from motrix_deploy.contracts import RobotSpec from motrix_deploy.profile import DeploymentProfile +from motrix_deploy.task import DeployTask from motrix_rl import checkpoints, runs from motrix_rl.deploy.api import OnnxExportReport, OnnxModelArtifact, OnnxParityMetrics, PolicyTensorSpec from motrix_rl.deploy.service import export_deploy_run +class TestTaskSpec(TaskSpec): + __test__ = False + task_name: ClassVar[str] = "test/v1" + + +class TestDeployTask(DeployTask): + __test__ = False + spec_type = TestTaskSpec + + def reset(self, state, context) -> None: + pass + + def build_observation(self, state, context): + raise NotImplementedError + + def process_action(self, action): + raise NotImplementedError + + def validate_command(self, command) -> None: + pass + + +@pytest.fixture(autouse=True) +def test_task_spec_plugin(monkeypatch: pytest.MonkeyPatch) -> None: + installed_entry_points = metadata.entry_points + entry = SimpleNamespace(name=TestTaskSpec.task_name, load=lambda: TestDeployTask) + + def entry_points(**kwargs): + entries = installed_entry_points(**kwargs) + if kwargs.get("group") == "motrix_deploy.tasks": + return (*entries, entry) + return entries + + monkeypatch.setattr(metadata, "entry_points", entry_points) + + def _profile() -> DeploymentProfile: return DeploymentProfile( robot=RobotSpec( @@ -24,19 +65,23 @@ def _profile() -> DeploymentProfile: position_upper=np.full(2, 1.0, dtype=np.float32), torque_limit=np.full(2, 3.0, dtype=np.float32), ), - task=TaskSpec( - name="test/v1", - observation_size=4, - action_size=2, - config={}, - ), + task=TestTaskSpec(), control=ControlSpec(period_s=0.02, state_timeout_s=0.1), + observation_size=4, + action_size=2, ) +@pytest.mark.parametrize( + ("input_size", "output_size", "invalid_kind"), + [(4, 2, None), (5, 2, "input"), (4, 3, "output")], +) def test_deployment_export_injects_profile_builder_selected_by_run_metadata( tmp_path, monkeypatch: pytest.MonkeyPatch, + input_size: int, + output_size: int, + invalid_kind: str | None, ) -> None: run = runs.create_run_context( env_name="cartpole", @@ -59,14 +104,25 @@ def test_deployment_export_injects_profile_builder_selected_by_run_metadata( model = OnnxModelArtifact( model_bytes=b"validated ONNX payload", report=OnnxExportReport( - input_spec=PolicyTensorSpec(name="obs", shape=(None, 4), dtype="float32"), - output_spec=PolicyTensorSpec(name="actions", shape=(None, 2), dtype="float32"), + input_spec=PolicyTensorSpec(name="obs", shape=(None, input_size), dtype="float32"), + output_spec=PolicyTensorSpec(name="actions", shape=(None, output_size), dtype="float32"), parity=OnnxParityMetrics(samples=8, max_abs_error=1e-6, max_rel_error=2e-6), ), ) monkeypatch.setattr("motrix_rl.deploy.service.export_onnx_model", lambda *args, **kwargs: model) selected: list[str] = [] + if invalid_kind is not None: + with pytest.raises(ValueError, match=f"deployment policy {invalid_kind} shape"): + export_deploy_run( + run.run_dir, + tmp_path / "test.deploy", + profile_builder=lambda env_name: selected.append(env_name) or _profile(), + validation_samples=8, + ) + assert not (tmp_path / "test.deploy").exists() + return + result = export_deploy_run( run.run_dir, tmp_path / "test.deploy", @@ -75,7 +131,11 @@ def test_deployment_export_injects_profile_builder_selected_by_run_metadata( ) assert selected == ["cartpole"] - assert result.artifact.manifest.task.name == "test/v1" + assert isinstance(result.artifact.manifest.task, TestTaskSpec) + manifest = result.artifact.manifest.to_dict() + assert manifest["task"] == {"name": "test/v1", "config": {}} + assert result.artifact.manifest.policy.input.shape == (1, _profile().observation_size) + assert result.artifact.manifest.policy.output.shape == (1, _profile().action_size) assert result.artifact.manifest.source.framework == "skrl.ppo/torch" assert result.artifact.policy_path.read_bytes() == model.model_bytes assert result.validation_samples == 8 diff --git a/uv.lock b/uv.lock index 6246c943..1fc1a259 100644 --- a/uv.lock +++ b/uv.lock @@ -73,6 +73,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/cf/bacc741d9926d662e76fd694f5bc63dd4c2471e32cedfb1c3cca7e47aa3c/amdsmi-7.0.2-py3-none-any.whl", hash = "sha256:db5aa757f8ed82dfd799c4d39e2828542678dc4e485b0ab7fabe5f398fec5652", size = 64366, upload-time = "2025-10-11T05:17:44.047Z" }, ] +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + [[package]] name = "antlr4-python3-runtime" version = "4.9.3" @@ -912,6 +921,7 @@ dependencies = [ { name = "motrix-env-core" }, { name = "numpy" }, { name = "onnxruntime" }, + { name = "pydantic" }, ] [package.metadata] @@ -920,6 +930,7 @@ requires-dist = [ { name = "motrix-env-core", editable = "motrix_env_core" }, { name = "numpy", specifier = ">=1.26" }, { name = "onnxruntime", specifier = "==1.23.2" }, + { name = "pydantic", specifier = "===2.12.5" }, ] [[package]] @@ -927,17 +938,17 @@ name = "motrix-deploy-mujoco" version = "0.4.0b0" source = { editable = "motrix_deploy_mujoco" } dependencies = [ + { name = "hydra-core" }, { name = "motrix-deploy" }, { name = "motrix-env-mujoco" }, - { name = "motrix-envs" }, { name = "numpy" }, ] [package.metadata] requires-dist = [ + { name = "hydra-core", specifier = ">=1.3" }, { name = "motrix-deploy", editable = "motrix_deploy" }, { name = "motrix-env-mujoco", editable = "motrix_env_mujoco" }, - { name = "motrix-envs", editable = "motrix_envs" }, { name = "numpy", specifier = ">=1.26" }, ] @@ -946,28 +957,21 @@ name = "motrix-deploy-tasks" version = "0.4.0b0" source = { editable = "motrix_deploy_tasks" } dependencies = [ + { name = "hydra-core" }, { name = "motrix-deploy" }, { name = "motrix-env-core" }, + { name = "motrix-robots" }, { name = "numpy" }, ] -[package.optional-dependencies] -mujoco = [ - { name = "motrix-deploy-mujoco" }, -] -unitree = [ - { name = "motrix-deploy-unitree" }, -] - [package.metadata] requires-dist = [ + { name = "hydra-core", specifier = ">=1.3,<1.4" }, { name = "motrix-deploy", editable = "motrix_deploy" }, - { name = "motrix-deploy-mujoco", marker = "extra == 'mujoco'", editable = "motrix_deploy_mujoco" }, - { name = "motrix-deploy-unitree", marker = "extra == 'unitree'", editable = "motrix_deploy_unitree" }, { name = "motrix-env-core", editable = "motrix_env_core" }, + { name = "motrix-robots", editable = "motrix_robots" }, { name = "numpy", specifier = ">=1.26" }, ] -provides-extras = ["mujoco", "unitree"] [[package]] name = "motrix-deploy-unitree" @@ -1083,12 +1087,14 @@ dependencies = [ [package.optional-dependencies] deploy = [ { name = "motrix-deploy" }, + { name = "motrix-deploy-tasks" }, ] [package.metadata] requires-dist = [ { name = "gymnasium", specifier = "==1.1.1" }, { name = "motrix-deploy", marker = "extra == 'deploy'", editable = "motrix_deploy" }, + { name = "motrix-deploy-tasks", marker = "extra == 'deploy'", editable = "motrix_deploy_tasks" }, { name = "motrix-env-core", extras = ["numba", "video"], editable = "motrix_env_core" }, { name = "motrix-env-motrixsim", editable = "motrix_env_motrixsim" }, { name = "motrix-robots", editable = "motrix_robots" }, @@ -2111,6 +2117,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/b4/46310463b4f6ceef310f8348786f3cff181cea671578e3d9743ba61a459e/protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", size = 170477, upload-time = "2025-11-13T16:44:17.633Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, +] + [[package]] name = "pydata-sphinx-theme" version = "0.16.1" @@ -2953,6 +3006,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + [[package]] name = "unitree-sdk2py" version = "1.0.1" diff --git a/wiki/design/motrix-deploy.md b/wiki/design/motrix-deploy.md index 7a5069d7..bf368498 100644 --- a/wiki/design/motrix-deploy.md +++ b/wiki/design/motrix-deploy.md @@ -403,7 +403,7 @@ event-based device 执行一次 `poll()`。`ControlLoop.run()` 的单步顺序 和确定性 CI。 两种 scheduler 向 `PolicyContext` 提供相同的 step 和 elapsed control time;wall-clock latency 单独计量。 -realtime/fixed-step scheduler 由 runtime recipe 显式选择,不从 device 推断。 +仿真的 realtime/fixed-step scheduler 由 `runtime.realtime` 选择;`null` 跟随 `runtime.viewer`。硬件始终在内部实时调度,不从 device 推断。 Fixed-step 模式记录“若实时运行是否 overrun”,但默认不因此终止 rollout。第一版 policy rate 与 command rate 相同;未来需要多速率时通过 scheduler 扩展,不能由 backend 私自重复或插值 policy action。 @@ -853,9 +853,10 @@ timestep。初始化时调用 `mj_resetData`、写入确定的初始 root pose ### 11.4 运行终止与指标 -sim2sim 的 Hydra runtime config 只配置 artifact、backend 和 viewer。CLI 不提供 rollout steps、duration、seed、input -type 或 command scale override;Go2 command scale 由 artifact 的 `TaskSpec` 持有。启动后持续运行,直到 viewer -关闭、用户按 Esc/Ctrl-C、机器人跌倒或发生错误。运行时还须: +sim2sim 的 Hydra 配置将 artifact、duration_s 和 command 放在顶层,仿真设置直接放在 runtime 下。 +`runtime.backend=mujoco` 选择 MuJoCo;`runtime.viewer` 和 `runtime.realtime` 只用于仿真。`duration_s` 按 artifact 的 +控制周期换算为 `ceil(duration_s / control_period_s)` 个控制 tick,不是墙钟超时。未设置 duration_s 时持续运行, +直到 viewer 关闭、用户按 Esc/Ctrl-C、机器人跌倒或发生错误。运行时还须: - 使用 task 从 artifact 读取的 command range 乘以 scale 后创建 keyboard binding; - reset 后从 step 0 重新初始化 phase、previous action 和 scheduler; @@ -864,9 +865,10 @@ type 或 command scale override;Go2 command scale 由 artifact 的 `TaskSpec` - 可配置任务指标:base height、fall、速度跟踪误差等,但这些指标不进入通用控制循环。 deployment backend 使用最小 GLFW viewer:每次 `sync()` 只更新 MuJoCo scene 并 render,不拥有仿真步进;同一 -window 的 callbacks 提供 keyboard event frame 与 mouse camera control,失焦后立即释放 held keys。viewer 模式使用 -realtime scheduler。headless backend 仍可配合程序传入的 binding 和 fixed-step scheduler 用于测试,但内置交互式 -CLI keyboard path 要求 `viewer=true`。`ControlLoop.run(steps=...)` 保留为内部测试和确定性验证能力,不进入用户配置。 +window 的 callbacks 提供 keyboard event frame 与 mouse camera control,失焦后立即释放 held keys。viewer 模式默认使用 +realtime scheduler,可通过 `runtime.realtime=false` 关闭实时节奏控制。headless CLI 使用配方中的 constant command,可通过 `command.velocity` 覆盖; +`runtime.realtime=null` 时跟随 `runtime.viewer`,也可显式配置实时节奏。`ControlLoop.run(steps=...)` 保留为 +程序化确定步数运行能力,不进入 CLI 配置。 ## 12. CLI 设计 @@ -876,30 +878,33 @@ package 暴露统一入口: # 只检查 artifact,不创建设备 motrix-deploy inspect artifact=path/to/go2_walk.deploy -# sim2sim 持续运行,直到用户退出、机器人跌倒或发生错误 -motrix-deploy sim2sim \ - artifact=path/to/go2_walk.deploy +# sim2sim 有界无窗口检查 +motrix-deploy task=go2-walk-flat/sim \ + artifact=path/to/go2_walk.deploy \ + runtime.viewer=false duration_s=2.0 # 实时可视化 -motrix-deploy sim2sim \ +motrix-deploy task=go2-walk-flat/sim \ artifact=path/to/go2_walk.deploy \ - viewer=true + runtime.viewer=true -# 后续阶段的真机入口 -motrix-deploy sim2real \ +# 真机入口 +motrix-deploy task=go2-walk-flat/hardware \ artifact=path/to/go2_walk.deploy \ - backend.name=unitree \ - hardware.confirm=true + runtime.network_interface=enp5s0 ``` `inspect` 只执行 artifact、RobotSpec 和 policy 静态校验,不查询 robot registry,也不打开 backend。 -当前 sim2sim 的类型化 Hydra 配置只包含 artifact、backend 和 viewer,CLI override 只修改所需字段;application -从 backend 的可选 keyboard device capability 创建 binding。后续 sim2real 必须使用独立模式、显式确认参数和 adapter 自身的 enable 流程, -默认配置不能把真机 enable 设为 true。 +Hydra 配置的公共执行字段是 artifact、duration_s 和 command;task 配方选择互斥的 runtime/sim 或 runtime/hardware 配置组。 +所有目标专用设置直接位于 runtime 下:`runtime.backend` 是标量字符串选择器,取值为 `mujoco` 或 `unitree_go2`, +不是嵌套配置映射或 Hydra 配置组;场景、physics、sensor_bindings、network_interface、kp、kd 等字段也直接位于 runtime 下。viewer 和 realtime 仅用于仿真,硬件由 runtime 在内部强制实时调度。 +application 从 runtime 的可选 keyboard device capability 创建 binding。真机不使用静态确认布尔值, +必须保留 adapter 的启动等待、enable、急停和安全停止流程;默认配置等待操作员按下启动和使能按钮。 +duration_s 换算为控制 tick 上限,不是墙钟超时;程序化 runtime.run(steps=...) 保留确定步数运行。 -具体 task/backend recipe 属于应用层,放在根目录 `configs/deploy/`,例如 -`configs/deploy/sim2sim/go2_walk_sim2sim.yaml`。`motrix_deploy` 包内只保留不含具体机器人、模型路径或 command 默认值的 -通用 mandatory-field 模板;workspace bootstrap 可以选择默认 recipe,外部部署则显式传入 Hydra config path/name。 +具体 task/runtime recipe 属于应用层,由 `motrix_deploy_tasks` 插件安装,提供 flat/rough 场景、机器人和 command 默认值。 +`motrix_deploy` 包内保留通用配置;任务 defaults 选择 runtime/sim 或 runtime/hardware 配置组。 +安装任务和目标 runtime 插件后,部署可在仓库外运行,不需要 workspace 配置路径。 ## 13. 与现有 MotrixLab 的集成 @@ -907,7 +912,7 @@ motrix-deploy sim2real \ policy exporter 与 deployment profile compiler 复用现有: -- `metadata.json`:训练环境、framework、backend、算法、seed 和版本来源; +- `metadata.json`:训练环境、framework、backend、算法和版本来源; - `task_config.yaml`:已解析的训练配置快照; - `checkpoints/manifest.json`:按语义定位 `best_policy`,不按文件名搜索 checkpoint。 @@ -969,14 +974,15 @@ uv run scripts/export_deploy.py env=go2-walk-rough ### 14.2 Contract test -提供 `FakeRobotInterface` 和可复用 adapter contract suite,验证: +`FakeRobotInterface` 是测试目录内共享的确定性 robot I/O 替身,不作为部署包的运行时 adapter 发布。控制面测试使用它验证: - lifecycle 调用顺序; -- name-based joint mapping; -- reset 与 seed; -- state timeout、backend unhealthy、write failure; -- stop/close 在正常结束、异常和用户中断下都执行; -- 第二个 fake backend 无需修改控制循环即可通过。 +- 不同状态反馈响应下的控制循环与确定性闭环; +- state timeout、backend unhealthy、write failure 和启动校验失败; +- stop/close 在正常结束、异常和用户中断下都执行。 + +name-based joint mapping、模型关节缺失以及 reset 等具体 adapter 行为由对应后端的测试验证, +不使用 fake 的名字集合检查替代真实映射测试。 command input 的 device contract suite 独立维护在 [Command Input 第一版测试边界](./deploy-command-input.md#9-第一版测试边界) 中。 @@ -994,7 +1000,7 @@ command input 的 device contract suite 独立维护在 CI smoke test 使用短 rollout,不以训练 reward 达标作为框架正确性的唯一条件。策略质量回归可以在 #139 基于 同一 CLI 和 `RolloutResult` 增加独立阈值。 -Go2 vertical slice 的验收基线为:固定 seed 的 1000-tick headless rollout trace SHA-256 +Go2 vertical slice 的验收基线为:确定性初始化的 1000-tick headless rollout trace SHA-256 `8db7e74506dbe36147746696cb15a57b0a4bd2ddf95096f3dce597e94dececda`;GLFW viewer 的 2 秒真实 GUI smoke 完成 100 ticks、无 overrun,并正常退出。viewer 与 headless 的同长度 trace SHA-256 均为 `b7b94aaa2cfba6e4700e92b650d408da13dd73091ce870c55e326e2012ce21a0`,两种模式共用同一 backend stepping 路径。