Source code for wbc_mjlab.env.mdp.observations

from __future__ import annotations

from typing import TYPE_CHECKING, cast

import torch

from mjlab.entity import Entity
from mjlab.managers.scene_entity_config import SceneEntityCfg
from mjlab.sensor import ContactSensor
from mjlab.utils.lab_api.math import (
  matrix_from_quat,
  quat_apply_inverse,
  quat_box_minus,
  subtract_frame_transforms,
)

from wbc_mjlab.env.mdp.torque_envelope import torque_speed_limits

from .commands import MotionCommand

if TYPE_CHECKING:
  from mjlab.envs import ManagerBasedRlEnv

_DEFAULT_ASSET_CFG = SceneEntityCfg("robot")


def _motion_command(env: ManagerBasedRlEnv, command_name: str) -> MotionCommand:
  return cast(MotionCommand, env.command_manager.get_term(command_name))


# --- Actor reference features (configurable obs terms; were stacked in MotionCommand.command) ---
# SE actor layouts: ``apply_se_actor`` in ``presets/se_actor.py``.


[docs] def ref_base_height(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference anchor height relative to env origin (z_I r̂_IB).""" return _motion_command(env, command_name).ref_base_height
[docs] def ref_anchor_pos_w(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference anchor xyz position relative to env origin (SE reference command).""" command = _motion_command(env, command_name) return command.anchor_pos_w - env.scene.env_origins
[docs] def ref_base_lin_vel_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference anchor linear velocity in anchor frame (B v̂_IB).""" return _motion_command(env, command_name).ref_base_lin_vel_b
[docs] def ref_base_ang_vel_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference anchor angular velocity in anchor frame (B ω̂_IB).""" return _motion_command(env, command_name).ref_base_ang_vel_b
[docs] def ref_gravity_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference gravity in anchor frame (B ĝ_I).""" return _motion_command(env, command_name).ref_gravity_b
[docs] def ref_joint_pos(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference joint positions for tracked DoFs (absolute).""" return _motion_command(env, command_name).tracked_joint_pos
def _body_index(asset: Entity, body_name: str) -> int: return asset.body_names.index(body_name) def torso_ang_vel( env: ManagerBasedRlEnv, asset_cfg: SceneEntityCfg = _DEFAULT_ASSET_CFG, body_name: str = "torso_link", ) -> torch.Tensor: """Torso angular velocity in the torso frame (Zest Table S3: T ω_IT).""" asset: Entity = env.scene[asset_cfg.name] idx = _body_index(asset, body_name) quat_w = asset.data.body_link_quat_w[:, idx] ang_vel_w = asset.data.body_link_ang_vel_w[:, idx] return quat_apply_inverse(quat_w, ang_vel_w) def torso_projected_gravity( env: ManagerBasedRlEnv, asset_cfg: SceneEntityCfg = _DEFAULT_ASSET_CFG, body_name: str = "torso_link", ) -> torch.Tensor: """Gravity direction in the torso frame (Zest Table S3: T g_I).""" asset: Entity = env.scene[asset_cfg.name] idx = _body_index(asset, body_name) quat_w = asset.data.body_link_quat_w[:, idx] return quat_apply_inverse(quat_w, asset.data.gravity_vec_w)
[docs] def ref_joint_vel( env: ManagerBasedRlEnv, command_name: str, asset_cfg: SceneEntityCfg = _DEFAULT_ASSET_CFG, ) -> torch.Tensor: """Reference joint velocities (critic privileged).""" command = _motion_command(env, command_name) if asset_cfg.joint_ids is not None: return command.joint_vel[:, asset_cfg.joint_ids] return command.tracked_joint_vel
[docs] def ref_anchor_ori_6d(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference anchor orientation as 6D rotation matrix columns (world frame).""" command = _motion_command(env, command_name) mat = matrix_from_quat(command.anchor_quat_w) return mat[..., :2].reshape(mat.shape[0], -1)
def ref_base_lin_acc_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference anchor linear acceleration in anchor frame (critic privileged).""" return _motion_command(env, command_name).ref_base_lin_acc_b def ref_base_ang_acc_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference anchor angular acceleration in anchor frame (critic privileged).""" return _motion_command(env, command_name).ref_base_ang_acc_b # --- Critic privileged keybody / anchor-relative features --- def _body_lin_vel_in_anchor_frame( anchor_quat_w: torch.Tensor, body_lin_vel_w: torch.Tensor, ) -> torch.Tensor: num_envs, num_bodies, _ = body_lin_vel_w.shape quat = anchor_quat_w[:, None, :].expand(num_envs, num_bodies, 4).reshape(-1, 4) vel = body_lin_vel_w.reshape(-1, 3) vel_b = quat_apply_inverse(quat, vel) return vel_b.view(num_envs, num_bodies * 3) def _body_ang_vel_in_anchor_frame( anchor_quat_w: torch.Tensor, body_ang_vel_w: torch.Tensor, ) -> torch.Tensor: num_envs, num_bodies, _ = body_ang_vel_w.shape quat = anchor_quat_w[:, None, :].expand(num_envs, num_bodies, 4).reshape(-1, 4) vel = body_ang_vel_w.reshape(-1, 3) vel_b = quat_apply_inverse(quat, vel) return vel_b.view(num_envs, num_bodies * 3)
[docs] def motion_anchor_pos_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Full xyz anchor tracking error in robot anchor frame (non-SE layouts).""" command = _motion_command(env, command_name) pos, _ = subtract_frame_transforms( command.robot_anchor_pos_w, command.robot_anchor_quat_w, command.anchor_pos_w, command.anchor_quat_w, ) return pos.view(env.num_envs, -1)
def motion_anchor_pos_z_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Z-only anchor tracking error in robot anchor frame (SE measurement).""" return motion_anchor_pos_b(env, command_name)[:, 2:3]
[docs] def motion_anchor_ori_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: command = _motion_command(env, command_name) _, ori = subtract_frame_transforms( command.robot_anchor_pos_w, command.robot_anchor_quat_w, command.anchor_pos_w, command.anchor_quat_w, ) mat = matrix_from_quat(ori) return mat[..., :2].reshape(mat.shape[0], -1)
[docs] def motion_anchor_pos_error_w( env: ManagerBasedRlEnv, command_name: str ) -> torch.Tensor: """World-frame anchor position tracking error (ref − robot).""" command = _motion_command(env, command_name) return command.anchor_pos_w - command.robot_anchor_pos_w
[docs] def motion_anchor_ori_error( env: ManagerBasedRlEnv, command_name: str ) -> torch.Tensor: """Anchor orientation tracking error as axis-angle (3); ‖·‖ = quat_error_magnitude.""" command = _motion_command(env, command_name) return quat_box_minus(command.anchor_quat_w, command.robot_anchor_quat_w)
[docs] def robot_body_pos_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: command = _motion_command(env, command_name) num_bodies = len(command.cfg.body_names) pos_b, _ = subtract_frame_transforms( command.robot_anchor_pos_w[:, None, :].repeat(1, num_bodies, 1), command.robot_anchor_quat_w[:, None, :].repeat(1, num_bodies, 1), command.robot_body_pos_w, command.robot_body_quat_w, ) return pos_b.view(env.num_envs, -1)
[docs] def robot_body_ori_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: command = _motion_command(env, command_name) num_bodies = len(command.cfg.body_names) _, ori_b = subtract_frame_transforms( command.robot_anchor_pos_w[:, None, :].repeat(1, num_bodies, 1), command.robot_anchor_quat_w[:, None, :].repeat(1, num_bodies, 1), command.robot_body_pos_w, command.robot_body_quat_w, ) mat = matrix_from_quat(ori_b) return mat[..., :2].reshape(mat.shape[0], -1)
[docs] def ref_body_pos_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference keybody positions in the robot anchor frame.""" command = _motion_command(env, command_name) num_bodies = len(command.cfg.body_names) pos_b, _ = subtract_frame_transforms( command.robot_anchor_pos_w[:, None, :].repeat(1, num_bodies, 1), command.robot_anchor_quat_w[:, None, :].repeat(1, num_bodies, 1), command.body_pos_w, command.body_quat_w, ) return pos_b.view(env.num_envs, -1)
[docs] def ref_body_ori_b(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference keybody orientations in the robot anchor frame (6D rotation columns).""" command = _motion_command(env, command_name) num_bodies = len(command.cfg.body_names) _, ori_b = subtract_frame_transforms( command.robot_anchor_pos_w[:, None, :].repeat(1, num_bodies, 1), command.robot_anchor_quat_w[:, None, :].repeat(1, num_bodies, 1), command.body_pos_w, command.body_quat_w, ) mat = matrix_from_quat(ori_b) return mat[..., :2].reshape(mat.shape[0], -1)
def motion_body_lin_vel( env: ManagerBasedRlEnv, command_name: str ) -> torch.Tensor: """Actual keybody linear velocities in the robot anchor frame.""" command = _motion_command(env, command_name) return _body_lin_vel_in_anchor_frame( command.robot_anchor_quat_w, command.robot_body_lin_vel_w ) def motion_body_ang_vel( env: ManagerBasedRlEnv, command_name: str ) -> torch.Tensor: """Actual keybody angular velocities in the robot anchor frame.""" command = _motion_command(env, command_name) return _body_ang_vel_in_anchor_frame( command.robot_anchor_quat_w, command.robot_body_ang_vel_w ) def ref_body_lin_vel(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference keybody linear velocities in the robot anchor frame.""" command = _motion_command(env, command_name) return _body_lin_vel_in_anchor_frame( command.robot_anchor_quat_w, command.body_lin_vel_w ) def ref_body_ang_vel(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Reference keybody angular velocities in the robot anchor frame.""" command = _motion_command(env, command_name) return _body_ang_vel_in_anchor_frame( command.robot_anchor_quat_w, command.body_ang_vel_w ) def keybody_contact_forces(env: ManagerBasedRlEnv, sensor_name: str) -> torch.Tensor: """Log-scaled contact forces for tracked keybodies (critic privileged).""" sensor: ContactSensor = env.scene[sensor_name] sensor_data = sensor.data assert sensor_data.force is not None forces_flat = sensor_data.force.flatten(start_dim=1) return torch.sign(forces_flat) * torch.log1p(torch.abs(forces_flat)) # --- Actuation (G1 Unitree envelope) --- def joint_mechanical_power( env: ManagerBasedRlEnv, asset_cfg: SceneEntityCfg = _DEFAULT_ASSET_CFG, ) -> torch.Tensor: """Per-joint mechanical power τ·ω (actuator generalized force).""" asset: Entity = env.scene[asset_cfg.name] ids = asset_cfg.joint_ids tau = asset.data.qfrc_actuator[:, ids] qd = asset.data.joint_vel[:, ids] return tau * qd def joint_torque_envelope_ratio( env: ManagerBasedRlEnv, asset_cfg: SceneEntityCfg = _DEFAULT_ASSET_CFG, ) -> torch.Tensor: """|τ| / max admissible torque magnitude at current velocity (≈1 at envelope).""" from wbc_mjlab.robots.g1.envelope import g1_joint_envelope_tensors asset: Entity = env.scene[asset_cfg.name] ids = asset_cfg.joint_ids tau = asset.data.qfrc_actuator[:, ids] qd = asset.data.joint_vel[:, ids] envl = g1_joint_envelope_tensors(env, asset_cfg) tau_low, tau_high = torque_speed_limits(qd, envl) cap = torch.maximum(tau_high, -tau_low).clamp(min=1.0e-6) return (tau.abs() / cap).flatten(start_dim=1) def joint_torque_envelope_margin( env: ManagerBasedRlEnv, asset_cfg: SceneEntityCfg = _DEFAULT_ASSET_CFG, ) -> torch.Tensor: """Normalized distance to torque envelope: 0 on boundary, negative if inside.""" from wbc_mjlab.robots.g1.envelope import g1_joint_envelope_tensors asset: Entity = env.scene[asset_cfg.name] ids = asset_cfg.joint_ids tau = asset.data.qfrc_actuator[:, ids] qd = asset.data.joint_vel[:, ids] envl = g1_joint_envelope_tensors(env, asset_cfg) tau_low, tau_high = torque_speed_limits(qd, envl) cap = torch.maximum(tau_high, -tau_low).clamp(min=1.0e-6) margin_high = (tau_high - tau) / cap margin_low = (tau - tau_low) / cap return torch.minimum(margin_high, margin_low).flatten(start_dim=1)
[docs] def motion_segment_phase(env: ManagerBasedRlEnv, command_name: str) -> torch.Tensor: """Normalized clip progress in ``[0, 1]`` (privileged critic feature).""" command = _motion_command(env, command_name) seg_start = command.motion.segment_start_idx[command.trajectory_ids] seg_len = command.motion.segment_length[command.trajectory_ids].float().clamp(min=1.0) local_step = (command.time_steps - seg_start).float() phase = local_step / seg_len return phase.clamp(0.0, 1.0).unsqueeze(-1)
[docs] def motion_tracking_step_rewards( env: ManagerBasedRlEnv, command_name: str ) -> torch.Tensor: """Unweighted per-step ``motion_*`` reward values (privileged critic feature).""" if not hasattr(env, "reward_manager"): n_terms = sum(1 for name in env.cfg.rewards if name.startswith("motion_")) return torch.zeros(env.num_envs, n_terms, device=env.device) command = _motion_command(env, command_name) if not hasattr(command, "_critic_motion_reward_indices"): reward_manager = env.reward_manager command._critic_motion_reward_indices = [ idx for idx, name in enumerate(reward_manager._term_names) if name.startswith("motion_") ] indices = command._critic_motion_reward_indices return env.reward_manager._step_reward[:, indices]