| |
| """Evaluate a Dropbear checkpoint with deterministic mean actions. |
| |
| This wraps Isaac Lab's stock RSL-RL player, pins the environment to the full |
| command range, and aggregates completed episodes before terminating. Unlike |
| the PPO training dashboard, these results contain no exploration-action noise. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| import math |
| import os |
| import runpy |
| import sys |
| import tempfile |
| from pathlib import Path |
| from typing import Any |
|
|
| import gymnasium as gym |
| import torch |
| import warp as wp |
|
|
|
|
| def _early_pop_float_pair_arg(name: str) -> tuple[float, float] | None: |
| """Consume an environment-shaping pair before task registration.""" |
| if name not in sys.argv: |
| return None |
| index = sys.argv.index(name) |
| try: |
| low = float(sys.argv[index + 1]) |
| high = float(sys.argv[index + 2]) |
| except (IndexError, ValueError) as exc: |
| raise ValueError(f"{name} requires two numbers") from exc |
| del sys.argv[index : index + 3] |
| return low, high |
|
|
|
|
| def _early_pop_float_arg(name: str) -> float | None: |
| """Consume an environment-shaping scalar before task registration.""" |
| if name not in sys.argv: |
| return None |
| index = sys.argv.index(name) |
| try: |
| value = float(sys.argv[index + 1]) |
| except (IndexError, ValueError) as exc: |
| raise ValueError(f"{name} requires a number") from exc |
| del sys.argv[index : index + 2] |
| return value |
|
|
|
|
| ACTOR_BASE_LIN_VEL = "--actor-base-lin-vel" in sys.argv |
| if ACTOR_BASE_LIN_VEL: |
| sys.argv.remove("--actor-base-lin-vel") |
| os.environ["DROPBEAR_ACTOR_BASE_LIN_VEL"] = "1" |
| ADAPT_COM_CHECKPOINT = "--eval-adapt-com-checkpoint" in sys.argv |
| if ADAPT_COM_CHECKPOINT: |
| sys.argv.remove("--eval-adapt-com-checkpoint") |
| RESET_JOINT_POSITION_RANGE = _early_pop_float_pair_arg( |
| "--eval-reset-joint-position-range" |
| ) |
| RESET_JOINT_VELOCITY_RANGE = _early_pop_float_pair_arg( |
| "--eval-reset-joint-velocity-range" |
| ) |
| if RESET_JOINT_POSITION_RANGE is not None: |
| reset_min, reset_max = RESET_JOINT_POSITION_RANGE |
| if not 0.1 <= reset_min <= reset_max <= 2.0: |
| raise ValueError( |
| "--eval-reset-joint-position-range must satisfy " |
| "0.1 <= low <= high <= 2.0" |
| ) |
| os.environ["DROPBEAR_RESET_JOINT_POSITION_MIN"] = f"{reset_min:g}" |
| os.environ["DROPBEAR_RESET_JOINT_POSITION_MAX"] = f"{reset_max:g}" |
| if RESET_JOINT_VELOCITY_RANGE is not None: |
| reset_vel_min, reset_vel_max = RESET_JOINT_VELOCITY_RANGE |
| if not -5.0 <= reset_vel_min <= reset_vel_max <= 5.0: |
| raise ValueError( |
| "--eval-reset-joint-velocity-range must satisfy " |
| "-5.0 <= low <= high <= 5.0" |
| ) |
| os.environ["DROPBEAR_RESET_JOINT_VELOCITY_MIN"] = f"{reset_vel_min:g}" |
| os.environ["DROPBEAR_RESET_JOINT_VELOCITY_MAX"] = f"{reset_vel_max:g}" |
| RESET_POLICY_JOINTS_ONLY = "--eval-reset-policy-joints-only" in sys.argv |
| if RESET_POLICY_JOINTS_ONLY: |
| sys.argv.remove("--eval-reset-policy-joints-only") |
| os.environ["DROPBEAR_RESET_POLICY_JOINTS_ONLY"] = "1" |
| POSE_SEQUENCE = "--eval-pose-sequence" in sys.argv |
| if POSE_SEQUENCE: |
| sys.argv.remove("--eval-pose-sequence") |
| os.environ["DROPBEAR_POSE_SEQUENCE"] = "1" |
| COM_CONTROL = "--eval-com-control" in sys.argv |
| if COM_CONTROL: |
| sys.argv.remove("--eval-com-control") |
| os.environ["DROPBEAR_COM_CONTROL"] = "1" |
| GAIT_MIRROR_ACTION_ADAPTER = ( |
| "--eval-gait-mirror-action-adapter" in sys.argv |
| ) |
| if GAIT_MIRROR_ACTION_ADAPTER: |
| sys.argv.remove("--eval-gait-mirror-action-adapter") |
| os.environ["DROPBEAR_GAIT_MIRROR_ACTION_ADAPTER"] = "1" |
| GAIT_MIRROR_TRANSFER_STRENGTH = _early_pop_float_arg( |
| "--eval-gait-mirror-transfer-strength" |
| ) |
| if GAIT_MIRROR_TRANSFER_STRENGTH is not None: |
| if not GAIT_MIRROR_ACTION_ADAPTER: |
| raise ValueError( |
| "--eval-gait-mirror-transfer-strength requires " |
| "--eval-gait-mirror-action-adapter" |
| ) |
| if not 0.0 <= GAIT_MIRROR_TRANSFER_STRENGTH <= 1.0: |
| raise ValueError( |
| "--eval-gait-mirror-transfer-strength must be between 0 and 1" |
| ) |
| os.environ["DROPBEAR_GAIT_MIRROR_TRANSFER_STRENGTH"] = ( |
| f"{GAIT_MIRROR_TRANSFER_STRENGTH:g}" |
| ) |
| POLICY_MIRROR_PROJECTION = _early_pop_float_arg( |
| "--eval-policy-mirror-projection" |
| ) |
| if POLICY_MIRROR_PROJECTION is not None and not ( |
| 0.0 <= POLICY_MIRROR_PROJECTION <= 1.0 |
| ): |
| raise ValueError( |
| "--eval-policy-mirror-projection must be between 0 and 1" |
| ) |
| EVAL_OBSTACLE_TERRAIN = "--eval-obstacle-terrain" in sys.argv |
| if EVAL_OBSTACLE_TERRAIN: |
| sys.argv.remove("--eval-obstacle-terrain") |
| os.environ["DROPBEAR_OBSTACLE_TERRAIN"] = "1" |
| os.environ["DROPBEAR_TERRAIN_HEIGHT_SCAN"] = "1" |
| EVAL_GAIT_PERIOD = _early_pop_float_arg("--eval-gait-period") |
| if EVAL_GAIT_PERIOD is None: |
| EVAL_GAIT_PERIOD = 0.60 |
| if not 0.20 <= EVAL_GAIT_PERIOD <= 2.0: |
| raise ValueError("--eval-gait-period must be between 0.20 and 2.0 seconds") |
| os.environ["DROPBEAR_GAIT_PERIOD"] = f"{EVAL_GAIT_PERIOD:g}" |
| for cli_name, env_name, minimum, maximum in ( |
| ("--eval-com-stand-height", "DROPBEAR_COM_STAND_HEIGHT", 0.0, 3.0), |
| ("--eval-com-height-delta", "DROPBEAR_COM_HEIGHT_DELTA", 0.0, 1.0), |
| ( |
| "--eval-com-height-error-scale", |
| "DROPBEAR_COM_HEIGHT_ERROR_SCALE", |
| 0.001, |
| 1.0, |
| ), |
| ( |
| "--eval-com-vertical-velocity-error-scale", |
| "DROPBEAR_COM_VERTICAL_VELOCITY_ERROR_SCALE", |
| 0.001, |
| 5.0, |
| ), |
| ): |
| value = _early_pop_float_arg(cli_name) |
| if value is not None: |
| if not minimum <= value <= maximum: |
| raise ValueError( |
| f"{cli_name} must be between {minimum:g} and {maximum:g}" |
| ) |
| os.environ[env_name] = f"{value:g}" |
| POSE_RESIDUAL_SCALE = _early_pop_float_arg("--eval-pose-residual-scale") |
| POSE_PG_PITCH_OFFSET = _early_pop_float_arg("--eval-pose-pg-pitch-offset") |
| POSE_KNEE_OFFSET = _early_pop_float_arg("--eval-pose-knee-offset") |
| POSE_ANKLE67_OFFSET = _early_pop_float_arg("--eval-pose-ankle67-offset") |
| for value, env_name, cli_name in ( |
| ( |
| POSE_PG_PITCH_OFFSET, |
| "DROPBEAR_POSE_PG_PITCH_OFFSET", |
| "--eval-pose-pg-pitch-offset", |
| ), |
| ( |
| POSE_KNEE_OFFSET, |
| "DROPBEAR_POSE_KNEE_OFFSET", |
| "--eval-pose-knee-offset", |
| ), |
| ( |
| POSE_ANKLE67_OFFSET, |
| "DROPBEAR_POSE_ANKLE67_OFFSET", |
| "--eval-pose-ankle67-offset", |
| ), |
| ): |
| if value is not None: |
| if not -2.0 <= value <= 2.0: |
| raise ValueError(f"{cli_name} must be between -2 and 2") |
| os.environ[env_name] = f"{value:g}" |
| POSE_BASELINE_DEPTH = _early_pop_float_arg("--eval-pose-baseline-depth") |
| if POSE_BASELINE_DEPTH is None: |
| POSE_BASELINE_DEPTH = -0.33 |
| if not -2.0 <= POSE_BASELINE_DEPTH <= 2.0: |
| raise ValueError("--eval-pose-baseline-depth must be between -2 and 2") |
| os.environ["DROPBEAR_POSE_BASELINE_DEPTH"] = f"{POSE_BASELINE_DEPTH:g}" |
| POSE_DEPTH_AMPLITUDE = _early_pop_float_arg("--eval-pose-depth-amplitude") |
| if POSE_DEPTH_AMPLITUDE is None: |
| POSE_DEPTH_AMPLITUDE = 1.0 |
| if not 0.0 <= POSE_DEPTH_AMPLITUDE <= 2.0: |
| raise ValueError("--eval-pose-depth-amplitude must be between 0 and 2") |
| os.environ["DROPBEAR_POSE_DEPTH_AMPLITUDE"] = f"{POSE_DEPTH_AMPLITUDE:g}" |
| POSE_STAND_HEIGHT = _early_pop_float_arg("--eval-pose-stand-height") |
| if POSE_STAND_HEIGHT is None: |
| POSE_STAND_HEIGHT = 1.62 |
| POSE_CROUCH_HEIGHT_DELTA = _early_pop_float_arg( |
| "--eval-pose-crouch-height-delta" |
| ) |
| if POSE_CROUCH_HEIGHT_DELTA is None: |
| POSE_CROUCH_HEIGHT_DELTA = 0.18 |
| POSE_ACTION_RESIDUAL = "--eval-pose-action-residual" in sys.argv |
| if POSE_ACTION_RESIDUAL: |
| sys.argv.remove("--eval-pose-action-residual") |
| if not POSE_SEQUENCE: |
| raise ValueError( |
| "--eval-pose-action-residual requires --eval-pose-sequence" |
| ) |
| os.environ["DROPBEAR_POSE_ACTION_RESIDUAL"] = "1" |
| if POSE_RESIDUAL_SCALE is not None: |
| if not 0.0 <= POSE_RESIDUAL_SCALE <= 1.0: |
| raise ValueError("--eval-pose-residual-scale must be between 0 and 1") |
| os.environ["DROPBEAR_POSE_RESIDUAL_SCALE"] = f"{POSE_RESIDUAL_SCALE:g}" |
| elif POSE_RESIDUAL_SCALE is not None: |
| raise ValueError( |
| "--eval-pose-residual-scale requires --eval-pose-action-residual" |
| ) |
| POSE_BIAS_GRID = "--eval-pose-bias-grid" in sys.argv |
| if POSE_BIAS_GRID: |
| sys.argv.remove("--eval-pose-bias-grid") |
| if not POSE_SEQUENCE: |
| raise ValueError("--eval-pose-bias-grid requires --eval-pose-sequence") |
|
|
| import dropbear_walk |
| from isaaclab.utils.math import quat_apply_inverse, yaw_quat |
| from dropbear_walk.isaaclab_asset.dropbear import DISTAL_FOOT_BODIES |
| from dropbear_walk.mdp.observations import ( |
| com_height_reference, |
| mass_weighted_com_state, |
| pose_sequence_reference, |
| ) |
| from dropbear_walk.mdp.symmetry import ( |
| compute_symmetric_states, |
| mirror_dropbear_joints, |
| ) |
| from rsl_rl.runners import OnPolicyRunner |
|
|
|
|
| WORKSPACE_ROOT = Path(__file__).resolve().parents[1] |
| PLAY_SCRIPT = WORKSPACE_ROOT / "IsaacLab" / "scripts" / "reinforcement_learning" / "rsl_rl" / "play.py" |
| ORIGINAL_GYM_MAKE = gym.make |
|
|
|
|
| if POLICY_MIRROR_PROJECTION is not None: |
| _original_get_inference_policy = OnPolicyRunner.get_inference_policy |
|
|
| class _MirrorProjectedPolicy: |
| """Blend policy output toward exact left/right equivariance.""" |
|
|
| def __init__(self, runner, policy, strength: float): |
| self._runner = runner |
| self._policy = policy |
| self._strength = strength |
|
|
| def __call__(self, observations): |
| original_actions = self._policy(observations) |
| mirrored_observations, _ = compute_symmetric_states( |
| self._runner.env, |
| observations, |
| None, |
| ) |
| batch_size = observations.batch_size[0] |
| mirrored_actions = self._policy( |
| mirrored_observations[batch_size:] |
| ) |
| projected_actions = 0.5 * ( |
| original_actions |
| + mirror_dropbear_joints(mirrored_actions) |
| ) |
| return torch.lerp( |
| original_actions, |
| projected_actions, |
| self._strength, |
| ) |
|
|
| def reset(self, dones=None): |
| return self._policy.reset(dones) |
|
|
| def _get_mirror_projected_policy(self, device=None): |
| policy = _original_get_inference_policy(self, device=device) |
| return _MirrorProjectedPolicy( |
| self, |
| policy, |
| POLICY_MIRROR_PROJECTION, |
| ) |
|
|
| OnPolicyRunner.get_inference_policy = _get_mirror_projected_policy |
|
|
|
|
| if ADAPT_COM_CHECKPOINT: |
| _original_runner_load = OnPolicyRunner.load |
|
|
| def _load_with_com_adapter( |
| self, |
| path, |
| load_cfg=None, |
| strict=True, |
| map_location=None, |
| ): |
| checkpoint = torch.load(path, weights_only=False, map_location="cpu") |
|
|
| def _append(state, expected_width: int, model_name: str) -> None: |
| first_layer = state["mlp.0.weight"] |
| old_width = int(first_layer.shape[1]) |
| if old_width == expected_width: |
| return |
| if old_width + 2 != expected_width: |
| raise ValueError( |
| f"{model_name} COM adapter expected {old_width}+2=" |
| f"{expected_width} inputs" |
| ) |
| state["mlp.0.weight"] = torch.cat( |
| ( |
| first_layer, |
| torch.zeros( |
| first_layer.shape[0], |
| 2, |
| dtype=first_layer.dtype, |
| ), |
| ), |
| dim=1, |
| ) |
| for key in ( |
| "obs_normalizer._mean", |
| "obs_normalizer._var", |
| "obs_normalizer._std", |
| ): |
| tensor = state[key] |
| fill = 0.0 if key.endswith("_mean") else 1.0 |
| state[key] = torch.cat( |
| ( |
| tensor, |
| torch.full( |
| (tensor.shape[0], 2), |
| fill, |
| dtype=tensor.dtype, |
| ), |
| ), |
| dim=1, |
| ) |
|
|
| _append( |
| checkpoint["actor_state_dict"], |
| int(self.alg.actor.mlp[0].in_features), |
| "Actor", |
| ) |
| _append( |
| checkpoint["critic_state_dict"], |
| int(self.alg.critic.mlp[0].in_features), |
| "Critic", |
| ) |
| temporary_path: Path | None = None |
| try: |
| with tempfile.NamedTemporaryFile( |
| suffix=".pt", delete=False |
| ) as handle: |
| temporary_path = Path(handle.name) |
| torch.save(checkpoint, temporary_path) |
| return _original_runner_load( |
| self, |
| str(temporary_path), |
| load_cfg={ |
| "actor": True, |
| "critic": True, |
| "optimizer": False, |
| "iteration": True, |
| "rnd": False, |
| }, |
| strict=strict, |
| map_location=map_location, |
| ) |
| finally: |
| if temporary_path is not None: |
| temporary_path.unlink(missing_ok=True) |
|
|
| OnPolicyRunner.load = _load_with_com_adapter |
|
|
|
|
| def _pop_int_arg(name: str, default: int) -> int: |
| if name not in sys.argv: |
| return default |
| index = sys.argv.index(name) |
| try: |
| value = int(sys.argv[index + 1]) |
| except (IndexError, ValueError) as exc: |
| raise ValueError(f"{name} requires an integer") from exc |
| del sys.argv[index : index + 2] |
| return value |
|
|
|
|
| def _pop_float_arg(name: str) -> float | None: |
| if name not in sys.argv: |
| return None |
| index = sys.argv.index(name) |
| try: |
| value = float(sys.argv[index + 1]) |
| except (IndexError, ValueError) as exc: |
| raise ValueError(f"{name} requires a number") from exc |
| del sys.argv[index : index + 2] |
| return value |
|
|
|
|
| def _pop_path_arg(name: str) -> Path | None: |
| if name not in sys.argv: |
| return None |
| index = sys.argv.index(name) |
| try: |
| value = Path(sys.argv[index + 1]) |
| except IndexError as exc: |
| raise ValueError(f"{name} requires a path") from exc |
| del sys.argv[index : index + 2] |
| return value |
|
|
|
|
| TARGET_EPISODES = _pop_int_arg("--eval-episodes", 1024) |
| if TARGET_EPISODES <= 0: |
| raise ValueError("--eval-episodes must be positive") |
| WARMUP_EPISODES = _pop_int_arg("--eval-warmup-episodes", 1) |
| if WARMUP_EPISODES < 0: |
| raise ValueError("--eval-warmup-episodes cannot be negative") |
| OUTPUT_PATH = _pop_path_arg("--eval-output") |
| FORWARD_SPEED = _pop_float_arg("--eval-forward-speed") |
| BACKWARD_SPEED = _pop_float_arg("--eval-backward-speed") |
| LATERAL_SPEED = _pop_float_arg("--eval-lateral-speed") |
| if LATERAL_SPEED is not None and not -2.0 <= LATERAL_SPEED <= 2.0: |
| raise ValueError("--eval-lateral-speed must be between -2 and 2 m/s") |
| if BACKWARD_SPEED is not None and not -2.0 <= BACKWARD_SPEED < 0.0: |
| raise ValueError("--eval-backward-speed must be negative and at least -2 m/s") |
| YAW_RATE = _pop_float_arg("--eval-yaw-rate") |
| if YAW_RATE is not None and not -3.0 <= YAW_RATE <= 3.0: |
| raise ValueError("--eval-yaw-rate must be between -3 and 3 rad/s") |
| PLANAR_CARDINAL_SWEEP = "--eval-planar-cardinal-sweep" in sys.argv |
| if PLANAR_CARDINAL_SWEEP: |
| sys.argv.remove("--eval-planar-cardinal-sweep") |
| if FORWARD_SPEED is None or FORWARD_SPEED <= 0.0: |
| raise ValueError( |
| "--eval-planar-cardinal-sweep requires a positive " |
| "--eval-forward-speed" |
| ) |
| if BACKWARD_SPEED is None: |
| raise ValueError( |
| "--eval-planar-cardinal-sweep requires --eval-backward-speed" |
| ) |
| if LATERAL_SPEED is None or LATERAL_SPEED <= 0.0: |
| raise ValueError( |
| "--eval-planar-cardinal-sweep requires a positive " |
| "--eval-lateral-speed" |
| ) |
| if TARGET_EPISODES < 4: |
| raise ValueError( |
| "--eval-planar-cardinal-sweep requires at least 4 episodes" |
| ) |
| FEET_MIN_DISTANCE = _pop_float_arg("--eval-feet-min-distance") |
| if FEET_MIN_DISTANCE is None: |
| FEET_MIN_DISTANCE = 0.14 |
| if not 0.01 <= FEET_MIN_DISTANCE <= 1.0: |
| raise ValueError("--eval-feet-min-distance must be between 0.01 and 1 m") |
| FEET_MIN_LATERAL_SEPARATION = _pop_float_arg( |
| "--eval-feet-min-lateral-separation" |
| ) |
| if FEET_MIN_LATERAL_SEPARATION is None: |
| FEET_MIN_LATERAL_SEPARATION = 0.08 |
| if not 0.01 <= FEET_MIN_LATERAL_SEPARATION <= 1.0: |
| raise ValueError( |
| "--eval-feet-min-lateral-separation must be between 0.01 and 1 m" |
| ) |
| PLANE_ONLY = "--eval-plane" in sys.argv |
| if PLANE_ONLY: |
| sys.argv.remove("--eval-plane") |
| DISABLE_PUSHES = "--eval-disable-pushes" in sys.argv |
| if DISABLE_PUSHES: |
| sys.argv.remove("--eval-disable-pushes") |
| PROJECT_RECIPROCAL_SHOULDERS = ( |
| "--eval-project-reciprocal-shoulders" in sys.argv |
| ) |
| if PROJECT_RECIPROCAL_SHOULDERS: |
| sys.argv.remove("--eval-project-reciprocal-shoulders") |
| SHOULDER_COUNTERWEIGHT_SCALE = _pop_float_arg( |
| "--eval-shoulder-counterweight-scale" |
| ) |
| if PROJECT_RECIPROCAL_SHOULDERS: |
| if SHOULDER_COUNTERWEIGHT_SCALE is not None: |
| raise ValueError( |
| "--eval-project-reciprocal-shoulders conflicts with " |
| "--eval-shoulder-counterweight-scale" |
| ) |
| SHOULDER_COUNTERWEIGHT_SCALE = 0.0 |
| if ( |
| SHOULDER_COUNTERWEIGHT_SCALE is not None |
| and not 0.0 <= SHOULDER_COUNTERWEIGHT_SCALE <= 1.0 |
| ): |
| raise ValueError( |
| "--eval-shoulder-counterweight-scale must be between 0 and 1" |
| ) |
|
|
|
|
| class DeterministicEvalWrapper(gym.Wrapper): |
| """Collect deterministic episode statistics from a vector environment.""" |
|
|
| def __init__(self, env: gym.Env): |
| super().__init__(env) |
| base = env.unwrapped |
| self._base = base |
| self._num_envs = base.num_envs |
| self._step_dt = float(base.step_dt) |
| self._max_episode_seconds = float(base.max_episode_length_s) |
| action_term = base.action_manager.get_term("joint_pos") |
| action_index = { |
| name: index for index, name in enumerate(action_term._joint_names) |
| } |
| self._shoulder_action_ids = [ |
| action_index["LH_yaw"], |
| action_index["RH_yaw"], |
| ] |
| self._returns = torch.zeros(self._num_envs, device=base.device) |
| self._tracking = torch.zeros_like(self._returns) |
| self._gait = torch.zeros_like(self._returns) |
| self._body_velocity_error = torch.zeros_like(self._returns) |
| self._yaw_velocity_error = torch.zeros_like(self._returns) |
| self._yaw_rate_error = torch.zeros_like(self._returns) |
| self._com_velocity_error = torch.zeros_like(self._returns) |
| self._root_velocity_yaw_xy = torch.zeros( |
| self._num_envs, 2, device=base.device |
| ) |
| self._com_velocity_yaw_xy = torch.zeros_like( |
| self._root_velocity_yaw_xy |
| ) |
| self._root_yaw_rate = torch.zeros_like(self._returns) |
| self._com_vertical_velocity_error = torch.zeros_like(self._returns) |
| self._com_height_abs_error = torch.zeros_like(self._returns) |
| self._arm_swing_score = torch.zeros_like(self._returns) |
| self._arm_extension_l2 = torch.zeros_like(self._returns) |
| self._arm_reciprocal_mode_abs = torch.zeros_like(self._returns) |
| self._arm_counterweight_mode_abs = torch.zeros_like(self._returns) |
| self._arm_reciprocal_sin = torch.zeros_like(self._returns) |
| self._arm_reciprocal_cos = torch.zeros_like(self._returns) |
| self._arm_counterweight_sin = torch.zeros_like(self._returns) |
| self._arm_counterweight_cos = torch.zeros_like(self._returns) |
| self._foot_phase_velocity_score = torch.zeros_like(self._returns) |
| self._foot_min_distance = torch.full_like( |
| self._returns, |
| float("inf"), |
| ) |
| self._foot_min_lateral_separation = torch.full_like( |
| self._returns, |
| float("inf"), |
| ) |
| self._foot_clearance_violation_steps = torch.zeros_like(self._returns) |
| self._foot_lateral_order_violation_steps = torch.zeros_like( |
| self._returns |
| ) |
| self._severe_foot_overlap_steps = torch.zeros_like(self._returns) |
| self._leg_contact_steps = torch.zeros( |
| self._num_envs, 2, device=base.device |
| ) |
| self._leg_touchdown_count = torch.zeros_like( |
| self._leg_contact_steps |
| ) |
| self._leg_stride_interval_sum = torch.zeros_like( |
| self._leg_contact_steps |
| ) |
| self._leg_stride_interval_count = torch.zeros_like( |
| self._leg_contact_steps |
| ) |
| self._leg_flight_time_sum = torch.zeros_like( |
| self._leg_contact_steps |
| ) |
| self._leg_flight_count = torch.zeros_like( |
| self._leg_contact_steps |
| ) |
| self._last_leg_touchdown_step = torch.full( |
| (self._num_envs, 2), |
| -1, |
| dtype=torch.long, |
| device=base.device, |
| ) |
| self._leg_air_steps = torch.zeros( |
| self._num_envs, |
| 2, |
| dtype=torch.long, |
| device=base.device, |
| ) |
| self._was_leg_contact = torch.zeros( |
| self._num_envs, |
| 2, |
| dtype=torch.bool, |
| device=base.device, |
| ) |
| self._knee_position = torch.zeros( |
| self._num_envs, 2, device=base.device |
| ) |
| self._knee_swing_position = torch.zeros_like(self._knee_position) |
| self._knee_stance_position = torch.zeros_like(self._knee_position) |
| self._knee_swing_steps = torch.zeros_like(self._knee_position) |
| self._knee_stance_steps = torch.zeros_like(self._knee_position) |
| self._knee_min = torch.full_like( |
| self._knee_position, |
| float("inf"), |
| ) |
| self._knee_max = torch.full_like( |
| self._knee_position, |
| float("-inf"), |
| ) |
| self._phase_joint_position = torch.zeros( |
| self._num_envs, 6, device=base.device |
| ) |
| self._phase_joint_swing_position = torch.zeros_like( |
| self._phase_joint_position |
| ) |
| self._phase_joint_stance_position = torch.zeros_like( |
| self._phase_joint_position |
| ) |
| self._phase_joint_swing_steps = torch.zeros_like( |
| self._phase_joint_position |
| ) |
| self._phase_joint_stance_steps = torch.zeros_like( |
| self._phase_joint_position |
| ) |
| self._torso_roll = torch.zeros_like(self._returns) |
| self._torso_roll_abs = torch.zeros_like(self._returns) |
| self._torso_roll_squared = torch.zeros_like(self._returns) |
| self._torso_roll_sin = torch.zeros_like(self._returns) |
| self._torso_roll_cos = torch.zeros_like(self._returns) |
| self._torso_roll_positive_steps = torch.zeros_like(self._returns) |
| self._foot_lateral_min = torch.full( |
| (self._num_envs, 2), |
| float("inf"), |
| device=base.device, |
| ) |
| self._foot_lateral_max = torch.full( |
| (self._num_envs, 2), |
| float("-inf"), |
| device=base.device, |
| ) |
| self._foot_position_yaw_xy = torch.zeros( |
| self._num_envs, 2, 2, device=base.device |
| ) |
| self._late_swing_foot_advance = torch.zeros( |
| self._num_envs, 2, device=base.device |
| ) |
| self._late_swing_foot_steps = torch.zeros_like( |
| self._late_swing_foot_advance |
| ) |
| self._obstacle_crossed = torch.zeros( |
| self._num_envs, dtype=torch.bool, device=base.device |
| ) |
| self._obstacle_progressed_past = torch.zeros_like( |
| self._obstacle_crossed |
| ) |
| self._obstacle_zone_seen = torch.zeros_like(self._obstacle_crossed) |
| self._obstacle_zone_max_foot_height = torch.full( |
| (self._num_envs, 2), |
| float("-inf"), |
| device=base.device, |
| ) |
| self._max_forward_progress = torch.full_like( |
| self._returns, |
| float("-inf"), |
| ) |
| self._pose_joint_mse = torch.zeros_like(self._returns) |
| self._pose_depth_abs_error = torch.zeros_like(self._returns) |
| self._pose_height_abs_error = torch.zeros_like(self._returns) |
| self._stand_achieved_depth = torch.zeros_like(self._returns) |
| self._stand_joint_delta = torch.zeros( |
| self._num_envs, 6, device=base.device |
| ) |
| self._stand_root_height = torch.zeros_like(self._returns) |
| self._stand_com_height = torch.zeros_like(self._returns) |
| self._stand_pose_steps = torch.zeros_like(self._returns) |
| self._crouch_achieved_depth = torch.zeros_like(self._returns) |
| self._crouch_joint_delta = torch.zeros( |
| self._num_envs, 6, device=base.device |
| ) |
| self._crouch_root_height = torch.zeros_like(self._returns) |
| self._crouch_com_height = torch.zeros_like(self._returns) |
| self._crouch_pose_steps = torch.zeros_like(self._returns) |
| self._lengths = torch.zeros(self._num_envs, dtype=torch.long, device=base.device) |
| self._completed_per_env = torch.zeros_like(self._lengths) |
| reward_names = base.reward_manager.active_terms |
| self._tracking_index = reward_names.index("track_lin_vel_xy") |
| self._gait_index = reward_names.index("gait") |
| self._episodes = 0 |
| self._fall_episodes = 0 |
| self._timeout_episodes = 0 |
| self._return_sum = 0.0 |
| self._length_sum = 0 |
| self._tracking_sum = 0.0 |
| self._gait_sum = 0.0 |
| self._body_velocity_error_sum = 0.0 |
| self._yaw_velocity_error_sum = 0.0 |
| self._yaw_rate_error_sum = 0.0 |
| self._com_velocity_error_sum = 0.0 |
| self._root_velocity_yaw_xy_sum = torch.zeros(2, device=base.device) |
| self._com_velocity_yaw_xy_sum = torch.zeros(2, device=base.device) |
| self._root_yaw_rate_sum = 0.0 |
| self._com_vertical_velocity_error_sum = 0.0 |
| self._com_height_abs_error_sum = 0.0 |
| self._arm_swing_score_sum = 0.0 |
| self._arm_extension_l2_sum = 0.0 |
| self._arm_reciprocal_mode_abs_sum = 0.0 |
| self._arm_counterweight_mode_abs_sum = 0.0 |
| self._arm_reciprocal_sin_coefficient_sum = 0.0 |
| self._arm_reciprocal_cos_coefficient_sum = 0.0 |
| self._arm_counterweight_sin_coefficient_sum = 0.0 |
| self._arm_counterweight_cos_coefficient_sum = 0.0 |
| self._foot_phase_velocity_score_sum = 0.0 |
| self._foot_min_distance_sum = 0.0 |
| self._foot_min_lateral_separation_sum = 0.0 |
| self._foot_clearance_violation_fraction_sum = 0.0 |
| self._foot_lateral_order_violation_fraction_sum = 0.0 |
| self._severe_foot_overlap_fraction_sum = 0.0 |
| self._leg_contact_fraction_sum = torch.zeros(2, device=base.device) |
| self._leg_touchdown_count_sum = torch.zeros(2, device=base.device) |
| self._leg_stride_interval_total = torch.zeros(2, device=base.device) |
| self._leg_stride_interval_samples = torch.zeros(2, device=base.device) |
| self._leg_flight_time_total = torch.zeros(2, device=base.device) |
| self._leg_flight_samples = torch.zeros(2, device=base.device) |
| self._flight_time_abs_difference_sum = 0.0 |
| self._contact_duty_abs_difference_sum = 0.0 |
| self._touchdown_count_abs_difference_sum = 0.0 |
| self._knee_position_sum = torch.zeros(2, device=base.device) |
| self._knee_swing_position_sum = torch.zeros(2, device=base.device) |
| self._knee_stance_position_sum = torch.zeros(2, device=base.device) |
| self._knee_range_sum = torch.zeros(2, device=base.device) |
| self._phase_joint_position_sum = torch.zeros(6, device=base.device) |
| self._phase_joint_swing_position_sum = torch.zeros( |
| 6, device=base.device |
| ) |
| self._phase_joint_stance_position_sum = torch.zeros( |
| 6, device=base.device |
| ) |
| self._torso_roll_sum = 0.0 |
| self._torso_roll_abs_sum = 0.0 |
| self._torso_roll_squared_sum = 0.0 |
| self._torso_roll_sin_coefficient_sum = 0.0 |
| self._torso_roll_cos_coefficient_sum = 0.0 |
| self._torso_roll_positive_fraction_sum = 0.0 |
| self._foot_lateral_excursion_sum = torch.zeros( |
| 2, device=base.device |
| ) |
| self._foot_position_yaw_xy_sum = torch.zeros( |
| 2, 2, device=base.device |
| ) |
| self._late_swing_foot_advance_sum = torch.zeros( |
| 2, device=base.device |
| ) |
| self._obstacle_crossed_episodes = 0 |
| self._obstacle_progressed_past_episodes = 0 |
| self._obstacle_zone_seen_episodes = 0 |
| self._obstacle_zone_max_foot_height_sum = torch.zeros( |
| 2, device=base.device |
| ) |
| self._max_forward_progress_sum = 0.0 |
| self._pose_joint_mse_sum = 0.0 |
| self._pose_depth_abs_error_sum = 0.0 |
| self._pose_height_abs_error_sum = 0.0 |
| self._stand_achieved_depth_sum = 0.0 |
| self._stand_joint_delta_sum = torch.zeros(6, device=base.device) |
| self._stand_root_height_sum = 0.0 |
| self._stand_com_height_sum = 0.0 |
| self._stand_pose_step_count = 0.0 |
| self._crouch_achieved_depth_sum = 0.0 |
| self._crouch_joint_delta_sum = torch.zeros(6, device=base.device) |
| self._crouch_root_height_sum = 0.0 |
| self._crouch_com_height_sum = 0.0 |
| self._crouch_pose_step_count = 0.0 |
| self._pose_joint_ids: list[int] = [] |
| self._pose_joint_names: list[str] = [] |
| self._pose_offsets: torch.Tensor | None = None |
| self._pose_height_body_names = [ |
| "torso_RMD_X10__1_Rotor_1", |
| "torso_RMD_X10Rotot_1", |
| ] |
| self._pose_bias_by_env: torch.Tensor | None = None |
| self._pose_bias_group_ids: torch.Tensor | None = None |
| self._pose_bias_names: list[str] = [] |
| self._pose_bias_episode_counts: torch.Tensor | None = None |
| self._pose_bias_fall_counts: torch.Tensor | None = None |
| self._pose_bias_stand_height_sums: torch.Tensor | None = None |
| self._pose_bias_stand_step_sums: torch.Tensor | None = None |
| self._pose_bias_crouch_height_sums: torch.Tensor | None = None |
| self._pose_bias_crouch_step_sums: torch.Tensor | None = None |
| self._cardinal_names = ["forward", "backward", "left", "right"] |
| self._cardinal_group_ids: torch.Tensor | None = None |
| self._cardinal_episode_counts: torch.Tensor | None = None |
| self._cardinal_fall_counts: torch.Tensor | None = None |
| self._cardinal_length_sums: torch.Tensor | None = None |
| self._cardinal_com_velocity_sums: torch.Tensor | None = None |
| self._cardinal_com_error_sums: torch.Tensor | None = None |
| self._cardinal_gait_sums: torch.Tensor | None = None |
| self._cardinal_foot_min_distance_sums: torch.Tensor | None = None |
| self._cardinal_foot_min_lateral_separation_sums: ( |
| torch.Tensor | None |
| ) = None |
| self._cardinal_foot_clearance_violation_fraction_sums: ( |
| torch.Tensor | None |
| ) = None |
| self._cardinal_foot_lateral_order_violation_fraction_sums: ( |
| torch.Tensor | None |
| ) = None |
| self._height_debug_steps = 0 |
| robot = base.scene["robot"] |
| self._shoulder_joint_ids, _ = robot.find_joints( |
| ["LH_yaw", "RH_yaw"], |
| preserve_order=True, |
| ) |
| self._knee_joint_names = [ |
| "LL_knee_actuator_joint", |
| "RL_knee_actuator_joint", |
| ] |
| self._knee_joint_ids, matched_knee_names = robot.find_joints( |
| self._knee_joint_names, |
| preserve_order=True, |
| ) |
| if matched_knee_names != self._knee_joint_names: |
| raise ValueError( |
| "Evaluator could not resolve the ordered knees: " |
| f"{matched_knee_names}" |
| ) |
| self._phase_joint_names = [ |
| "PG_left_leg_pitch", |
| "PG_right_leg_pitch", |
| "LL_knee_actuator_joint", |
| "RL_knee_actuator_joint", |
| "LL_Revolute67", |
| "RL_Revolute67", |
| ] |
| self._phase_joint_ids, matched_phase_joint_names = robot.find_joints( |
| self._phase_joint_names, |
| preserve_order=True, |
| ) |
| if matched_phase_joint_names != self._phase_joint_names: |
| raise ValueError( |
| "Evaluator could not resolve ordered sagittal joints: " |
| f"{matched_phase_joint_names}" |
| ) |
| self._phase_joint_leg_ids = torch.tensor( |
| [0, 1, 0, 1, 0, 1], |
| dtype=torch.long, |
| device=base.device, |
| ) |
| self._pose_height_body_ids, _ = robot.find_bodies( |
| self._pose_height_body_names, |
| preserve_order=True, |
| ) |
| self._distal_foot_body_ids, matched_foot_names = robot.find_bodies( |
| DISTAL_FOOT_BODIES, |
| preserve_order=True, |
| ) |
| if matched_foot_names != DISTAL_FOOT_BODIES: |
| raise ValueError( |
| "Evaluator could not resolve the ordered distal feet: " |
| f"{matched_foot_names}" |
| ) |
| gait_term_cfg = base.reward_manager.get_term_cfg("gait") |
| contact_sensor_cfg = gait_term_cfg.params["sensor_cfg"] |
| self._contact_sensor = base.scene.sensors[contact_sensor_cfg.name] |
| self._contact_body_ids = contact_sensor_cfg.body_ids |
| initial_root_pos_w = robot.data.root_pos_w |
| if isinstance(initial_root_pos_w, wp.array): |
| initial_root_pos_w = wp.to_torch(initial_root_pos_w) |
| print( |
| "[EVAL HEIGHT INIT] " |
| f"root_z={float(initial_root_pos_w[0, 2]):.4f} " |
| f"origin_z={float(base.scene.env_origins[0, 2]):.4f}", |
| flush=True, |
| ) |
| if POSE_SEQUENCE: |
| self._pose_joint_names = [ |
| "PG_left_leg_pitch", |
| "PG_right_leg_pitch", |
| "LL_knee_actuator_joint", |
| "RL_knee_actuator_joint", |
| "LL_Revolute67", |
| "RL_Revolute67", |
| ] |
| self._pose_joint_ids, _ = robot.find_joints( |
| self._pose_joint_names, |
| preserve_order=True, |
| ) |
| self._pose_offsets = torch.tensor( |
| [ |
| ( |
| POSE_PG_PITCH_OFFSET |
| if POSE_PG_PITCH_OFFSET is not None |
| else -0.10 |
| ), |
| ( |
| POSE_PG_PITCH_OFFSET |
| if POSE_PG_PITCH_OFFSET is not None |
| else -0.10 |
| ), |
| ( |
| POSE_KNEE_OFFSET |
| if POSE_KNEE_OFFSET is not None |
| else 0.25 |
| ), |
| ( |
| POSE_KNEE_OFFSET |
| if POSE_KNEE_OFFSET is not None |
| else 0.25 |
| ), |
| ( |
| POSE_ANKLE67_OFFSET |
| if POSE_ANKLE67_OFFSET is not None |
| else -0.15 |
| ), |
| ( |
| POSE_ANKLE67_OFFSET |
| if POSE_ANKLE67_OFFSET is not None |
| else -0.15 |
| ), |
| ], |
| device=base.device, |
| ) |
| if POSE_BIAS_GRID: |
| action_term = base.action_manager.get_term("joint_pos") |
| action_names = list(action_term._joint_names) |
| action_index = { |
| name: index for index, name in enumerate(action_names) |
| } |
| pattern_specs: list[tuple[str, dict[str, float]]] = [ |
| ("none", {}), |
| ( |
| "knees_pos_015", |
| { |
| "LL_knee_actuator_joint": 0.15, |
| "RL_knee_actuator_joint": 0.15, |
| }, |
| ), |
| ( |
| "knees_pos_030", |
| { |
| "LL_knee_actuator_joint": 0.30, |
| "RL_knee_actuator_joint": 0.30, |
| }, |
| ), |
| ( |
| "knees_neg_015", |
| { |
| "LL_knee_actuator_joint": -0.15, |
| "RL_knee_actuator_joint": -0.15, |
| }, |
| ), |
| ( |
| "pelvis_pitch_pos_015", |
| { |
| "PG_left_leg_pitch": 0.15, |
| "PG_right_leg_pitch": 0.15, |
| }, |
| ), |
| ( |
| "pelvis_pitch_neg_015", |
| { |
| "PG_left_leg_pitch": -0.15, |
| "PG_right_leg_pitch": -0.15, |
| }, |
| ), |
| ( |
| "ankle67_pos_015", |
| {"LL_Revolute67": 0.15, "RL_Revolute67": 0.15}, |
| ), |
| ( |
| "ankle67_neg_015", |
| {"LL_Revolute67": -0.15, "RL_Revolute67": -0.15}, |
| ), |
| ( |
| "crouch_combo", |
| { |
| "PG_left_leg_pitch": -0.15, |
| "PG_right_leg_pitch": -0.15, |
| "LL_knee_actuator_joint": 0.30, |
| "RL_knee_actuator_joint": 0.30, |
| "LL_Revolute67": -0.15, |
| "RL_Revolute67": -0.15, |
| }, |
| ), |
| ] |
| missing = sorted( |
| { |
| joint_name |
| for _, spec in pattern_specs |
| for joint_name in spec |
| if joint_name not in action_index |
| } |
| ) |
| if missing: |
| raise ValueError( |
| "pose-bias joints are absent from the action term: " |
| + ", ".join(missing) |
| ) |
| patterns = torch.zeros( |
| len(pattern_specs), |
| len(action_names), |
| device=base.device, |
| ) |
| for pattern_id, (_, spec) in enumerate(pattern_specs): |
| for joint_name, bias in spec.items(): |
| patterns[pattern_id, action_index[joint_name]] = bias |
| self._pose_bias_names = [name for name, _ in pattern_specs] |
| self._pose_bias_group_ids = ( |
| torch.arange(self._num_envs, device=base.device) |
| % len(pattern_specs) |
| ) |
| self._pose_bias_by_env = patterns[self._pose_bias_group_ids] |
| group_count = len(pattern_specs) |
| self._pose_bias_episode_counts = torch.zeros( |
| group_count, dtype=torch.long, device=base.device |
| ) |
| self._pose_bias_fall_counts = torch.zeros_like( |
| self._pose_bias_episode_counts |
| ) |
| self._pose_bias_stand_height_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._pose_bias_stand_step_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._pose_bias_crouch_height_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._pose_bias_crouch_step_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| if PLANAR_CARDINAL_SWEEP: |
| group_count = len(self._cardinal_names) |
| self._cardinal_group_ids = ( |
| torch.arange(self._num_envs, device=base.device) |
| % group_count |
| ) |
| self._cardinal_episode_counts = torch.zeros( |
| group_count, dtype=torch.long, device=base.device |
| ) |
| self._cardinal_fall_counts = torch.zeros_like( |
| self._cardinal_episode_counts |
| ) |
| self._cardinal_length_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._cardinal_com_velocity_sums = torch.zeros( |
| group_count, 2, device=base.device |
| ) |
| self._cardinal_com_error_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._cardinal_gait_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._cardinal_foot_min_distance_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._cardinal_foot_min_lateral_separation_sums = torch.zeros( |
| group_count, device=base.device |
| ) |
| self._cardinal_foot_clearance_violation_fraction_sums = ( |
| torch.zeros(group_count, device=base.device) |
| ) |
| self._cardinal_foot_lateral_order_violation_fraction_sums = ( |
| torch.zeros(group_count, device=base.device) |
| ) |
|
|
| def step(self, action): |
| if self._pose_bias_by_env is not None: |
| depth, _ = pose_sequence_reference(self._base) |
| action = action.clone() |
| action += depth.unsqueeze(1) * self._pose_bias_by_env |
| if SHOULDER_COUNTERWEIGHT_SCALE is not None: |
| action = action.clone() |
| shoulder_actions = action[:, self._shoulder_action_ids] |
| reciprocal_action = torch.mean( |
| shoulder_actions, |
| dim=1, |
| keepdim=True, |
| ) |
| action[:, self._shoulder_action_ids] = ( |
| reciprocal_action |
| + SHOULDER_COUNTERWEIGHT_SCALE |
| * (shoulder_actions - reciprocal_action) |
| ) |
| command_term = self._base.command_manager.get_term("base_velocity") |
| command = command_term.vel_command_b.clone() |
| command_xy = command[:, :2] |
| observations, rewards, terminated, truncated, extras = self.env.step(action) |
|
|
| step_rewards = self._base.reward_manager._step_reward |
| robot_data = self._base.scene["robot"].data |
| root_lin_vel_b = robot_data.root_lin_vel_b |
| root_ang_vel_b = robot_data.root_ang_vel_b |
| root_quat_w = robot_data.root_quat_w |
| root_lin_vel_w = robot_data.root_lin_vel_w |
| if isinstance(root_lin_vel_b, wp.array): |
| root_lin_vel_b = wp.to_torch(root_lin_vel_b) |
| if isinstance(root_ang_vel_b, wp.array): |
| root_ang_vel_b = wp.to_torch(root_ang_vel_b) |
| if isinstance(root_quat_w, wp.array): |
| root_quat_w = wp.to_torch(root_quat_w) |
| if isinstance(root_lin_vel_w, wp.array): |
| root_lin_vel_w = wp.to_torch(root_lin_vel_w) |
| root_lin_vel_yaw = quat_apply_inverse(yaw_quat(root_quat_w), root_lin_vel_w) |
| body_velocity_error = torch.linalg.norm(command_xy - root_lin_vel_b[:, :2], dim=-1) |
| yaw_velocity_error = torch.linalg.norm(command_xy - root_lin_vel_yaw[:, :2], dim=-1) |
| yaw_rate_error = torch.abs(command[:, 2] - root_ang_vel_b[:, 2]) |
| com_pos_w, com_lin_vel_w = mass_weighted_com_state(self._base) |
| com_lin_vel_yaw = quat_apply_inverse( |
| yaw_quat(root_quat_w), |
| com_lin_vel_w, |
| ) |
| com_velocity_error = torch.linalg.norm( |
| command_xy - com_lin_vel_yaw[:, :2], |
| dim=-1, |
| ) |
| target_com_height, target_com_vertical_velocity = com_height_reference( |
| self._base, |
| float(os.environ.get("DROPBEAR_COM_STAND_HEIGHT", "1.20")), |
| float(os.environ.get("DROPBEAR_COM_HEIGHT_DELTA", "0.0")), |
| 16.0, |
| ) |
| env_origins = self._base.scene.env_origins.to(com_pos_w.device) |
| com_height = com_pos_w[:, 2] - env_origins[:, 2] |
| com_vertical_velocity_error = torch.abs( |
| com_lin_vel_w[:, 2] - target_com_vertical_velocity |
| ) |
| com_height_abs_error = torch.abs(com_height - target_com_height) |
|
|
| def raw_reward_term(name: str) -> torch.Tensor: |
| term_cfg = self._base.reward_manager.get_term_cfg(name) |
| return term_cfg.func(self._base, **term_cfg.params) |
|
|
| arm_swing_score = raw_reward_term("natural_arm_swing") |
| arm_extension_l2 = raw_reward_term("excessive_arm_extension") |
| foot_phase_velocity_score = raw_reward_term("foot_phase_velocity") |
| body_pos_w = robot_data.body_pos_w |
| if isinstance(body_pos_w, wp.array): |
| body_pos_w = wp.to_torch(body_pos_w) |
| foot_delta_w = ( |
| body_pos_w[:, self._distal_foot_body_ids[0]] |
| - body_pos_w[:, self._distal_foot_body_ids[1]] |
| ) |
| foot_distance = torch.linalg.vector_norm(foot_delta_w, dim=1) |
| foot_delta_yaw = quat_apply_inverse( |
| yaw_quat(root_quat_w), |
| foot_delta_w, |
| ) |
| foot_lateral_separation = foot_delta_yaw[:, 1] |
| foot_clearance_violation = ( |
| foot_distance < FEET_MIN_DISTANCE |
| ) |
| foot_lateral_order_violation = ( |
| foot_lateral_separation < FEET_MIN_LATERAL_SEPARATION |
| ) |
| severe_foot_overlap = ( |
| (foot_distance < 0.10) |
| | (foot_lateral_separation < 0.0) |
| ) |
| current_contact_time = self._contact_sensor.data.current_contact_time |
| if isinstance(current_contact_time, wp.array): |
| current_contact_time = wp.to_torch(current_contact_time) |
| foot_body_contact = ( |
| current_contact_time[:, self._contact_body_ids] > 0 |
| ) |
| contact_split = foot_body_contact.shape[1] // 2 |
| if contact_split == 0: |
| raise ValueError( |
| "Evaluator requires ordered contact bodies for both feet" |
| ) |
| leg_contact = torch.stack( |
| ( |
| foot_body_contact[:, :contact_split].any(dim=1), |
| foot_body_contact[:, contact_split:].any(dim=1), |
| ), |
| dim=1, |
| ) |
| valid_touchdown = ( |
| leg_contact |
| & ~self._was_leg_contact |
| & (self._leg_air_steps * self._step_dt >= 0.06) |
| ) |
| completed_flight_time = self._leg_air_steps.float() * self._step_dt |
| self._leg_flight_time_sum += ( |
| completed_flight_time * valid_touchdown.float() |
| ) |
| self._leg_flight_count += valid_touchdown |
| current_step = self._lengths.unsqueeze(1).expand_as( |
| self._last_leg_touchdown_step |
| ) |
| prior_touchdown = self._last_leg_touchdown_step >= 0 |
| interval_update = valid_touchdown & prior_touchdown |
| stride_interval = ( |
| current_step - self._last_leg_touchdown_step |
| ).float() * self._step_dt |
| self._leg_stride_interval_sum += ( |
| stride_interval * interval_update.float() |
| ) |
| self._leg_stride_interval_count += interval_update |
| self._last_leg_touchdown_step = torch.where( |
| valid_touchdown, |
| current_step, |
| self._last_leg_touchdown_step, |
| ) |
| self._leg_touchdown_count += valid_touchdown |
| self._leg_contact_steps += leg_contact |
| self._leg_air_steps = torch.where( |
| leg_contact, |
| torch.zeros_like(self._leg_air_steps), |
| self._leg_air_steps + 1, |
| ) |
| self._was_leg_contact = leg_contact.clone() |
| joint_pos = robot_data.joint_pos |
| default_joint_pos = robot_data.default_joint_pos |
| if isinstance(joint_pos, wp.array): |
| joint_pos = wp.to_torch(joint_pos) |
| if isinstance(default_joint_pos, wp.array): |
| default_joint_pos = wp.to_torch(default_joint_pos) |
| knee_position = joint_pos[:, self._knee_joint_ids] |
| global_gait_phase = ( |
| ( |
| self._base.episode_length_buf.to(knee_position.dtype) |
| * self._step_dt |
| ) |
| % EVAL_GAIT_PERIOD |
| / EVAL_GAIT_PERIOD |
| ).unsqueeze(1) |
| leg_phase = ( |
| global_gait_phase |
| + torch.tensor( |
| [0.0, 0.5], |
| dtype=knee_position.dtype, |
| device=knee_position.device, |
| ).unsqueeze(0) |
| ) % 1.0 |
| knee_stance_mask = leg_phase < 0.55 |
| knee_swing_mask = ~knee_stance_mask |
| phase_joint_position = joint_pos[:, self._phase_joint_ids] |
| phase_joint_swing_mask = knee_swing_mask[ |
| :, self._phase_joint_leg_ids |
| ] |
| phase_joint_stance_mask = ~phase_joint_swing_mask |
| swing_progress = torch.clamp( |
| (leg_phase - 0.55) / (1.0 - 0.55), |
| min=0.0, |
| max=1.0, |
| ) |
| late_swing_mask = knee_swing_mask & (swing_progress >= 0.80) |
| gravity_w = torch.zeros( |
| self._num_envs, |
| 3, |
| dtype=root_quat_w.dtype, |
| device=root_quat_w.device, |
| ) |
| gravity_w[:, 2] = -1.0 |
| gravity_b = quat_apply_inverse(root_quat_w, gravity_w) |
| torso_roll = torch.atan2(-gravity_b[:, 1], -gravity_b[:, 2]) |
| root_pos_w = robot_data.root_pos_w |
| if isinstance(root_pos_w, wp.array): |
| root_pos_w = wp.to_torch(root_pos_w) |
| foot_relative_w = body_pos_w[:, self._distal_foot_body_ids, :] - ( |
| root_pos_w.unsqueeze(1) |
| ) |
| foot_relative_yaw = torch.stack( |
| [ |
| quat_apply_inverse( |
| yaw_quat(root_quat_w), |
| foot_relative_w[:, leg_index, :], |
| ) |
| for leg_index in range(2) |
| ], |
| dim=1, |
| ) |
| foot_lateral_position = foot_relative_yaw[:, :, 1] |
| travel_sign = torch.where( |
| command_xy[:, 0] >= 0.0, |
| torch.ones_like(command_xy[:, 0]), |
| -torch.ones_like(command_xy[:, 0]), |
| ) |
| foot_advance = torch.stack( |
| ( |
| travel_sign |
| * ( |
| foot_relative_yaw[:, 0, 0] |
| - foot_relative_yaw[:, 1, 0] |
| ), |
| travel_sign |
| * ( |
| foot_relative_yaw[:, 1, 0] |
| - foot_relative_yaw[:, 0, 0] |
| ), |
| ), |
| dim=1, |
| ) |
| forward_progress = root_pos_w[:, 0] - env_origins[:, 0] |
| self._max_forward_progress = torch.maximum( |
| self._max_forward_progress, |
| forward_progress, |
| ) |
| if EVAL_OBSTACLE_TERRAIN: |
| |
| |
| |
| |
| progressed_past = forward_progress > 1.35 |
| self._obstacle_progressed_past |= progressed_past |
| upright = gravity_b[:, 2] < -0.85 |
| controlled_velocity = ( |
| torch.linalg.vector_norm( |
| root_lin_vel_yaw[:, :2] - command_xy, |
| dim=1, |
| ) |
| < 0.35 |
| ) |
| self._obstacle_crossed |= ( |
| progressed_past & upright & controlled_velocity |
| ) |
| foot_forward_from_origin = ( |
| body_pos_w[:, self._distal_foot_body_ids, 0] |
| - env_origins[:, None, 0] |
| ) |
| foot_in_obstacle_zone = ( |
| (foot_forward_from_origin >= 0.75) |
| & (foot_forward_from_origin <= 1.25) |
| ) |
| self._obstacle_zone_seen |= torch.any( |
| foot_in_obstacle_zone, dim=1 |
| ) |
| foot_height_from_origin = ( |
| body_pos_w[:, self._distal_foot_body_ids, 2] |
| - env_origins[:, None, 2] |
| ) |
| zone_height = torch.where( |
| foot_in_obstacle_zone, |
| foot_height_from_origin, |
| torch.full_like(foot_height_from_origin, float("-inf")), |
| ) |
| self._obstacle_zone_max_foot_height = torch.maximum( |
| self._obstacle_zone_max_foot_height, |
| zone_height, |
| ) |
| shoulder_offset = ( |
| joint_pos[:, self._shoulder_joint_ids] |
| - default_joint_pos[:, self._shoulder_joint_ids] |
| ) |
| |
| |
| |
| reciprocal_mode = 0.5 * (shoulder_offset[:, 0] + shoulder_offset[:, 1]) |
| counterweight_mode = 0.5 * ( |
| shoulder_offset[:, 0] - shoulder_offset[:, 1] |
| ) |
| arm_phase = ( |
| 2.0 |
| * torch.pi |
| * self._base.episode_length_buf.to(reciprocal_mode.dtype) |
| * self._step_dt |
| / EVAL_GAIT_PERIOD |
| ) |
| phase_sin = torch.sin(arm_phase) |
| phase_cos = torch.cos(arm_phase) |
|
|
| self._returns += rewards |
| self._tracking += step_rewards[:, self._tracking_index] * self._step_dt |
| self._gait += step_rewards[:, self._gait_index] * self._step_dt |
| self._body_velocity_error += body_velocity_error |
| self._yaw_velocity_error += yaw_velocity_error |
| self._yaw_rate_error += yaw_rate_error |
| self._com_velocity_error += com_velocity_error |
| self._root_velocity_yaw_xy += root_lin_vel_yaw[:, :2] |
| self._com_velocity_yaw_xy += com_lin_vel_yaw[:, :2] |
| self._root_yaw_rate += root_ang_vel_b[:, 2] |
| self._com_vertical_velocity_error += com_vertical_velocity_error |
| self._com_height_abs_error += com_height_abs_error |
| self._arm_swing_score += arm_swing_score |
| self._arm_extension_l2 += arm_extension_l2 |
| self._arm_reciprocal_mode_abs += torch.abs(reciprocal_mode) |
| self._arm_counterweight_mode_abs += torch.abs(counterweight_mode) |
| self._arm_reciprocal_sin += reciprocal_mode * phase_sin |
| self._arm_reciprocal_cos += reciprocal_mode * phase_cos |
| self._arm_counterweight_sin += counterweight_mode * phase_sin |
| self._arm_counterweight_cos += counterweight_mode * phase_cos |
| self._foot_phase_velocity_score += foot_phase_velocity_score |
| self._foot_min_distance = torch.minimum( |
| self._foot_min_distance, |
| foot_distance, |
| ) |
| self._foot_min_lateral_separation = torch.minimum( |
| self._foot_min_lateral_separation, |
| foot_lateral_separation, |
| ) |
| self._foot_clearance_violation_steps += foot_clearance_violation |
| self._foot_lateral_order_violation_steps += ( |
| foot_lateral_order_violation |
| ) |
| self._severe_foot_overlap_steps += severe_foot_overlap |
| self._knee_position += knee_position |
| self._knee_swing_position += ( |
| knee_position * knee_swing_mask.float() |
| ) |
| self._knee_stance_position += ( |
| knee_position * knee_stance_mask.float() |
| ) |
| self._knee_swing_steps += knee_swing_mask |
| self._knee_stance_steps += knee_stance_mask |
| self._knee_min = torch.minimum(self._knee_min, knee_position) |
| self._knee_max = torch.maximum(self._knee_max, knee_position) |
| self._phase_joint_position += phase_joint_position |
| self._phase_joint_swing_position += ( |
| phase_joint_position * phase_joint_swing_mask.float() |
| ) |
| self._phase_joint_stance_position += ( |
| phase_joint_position * phase_joint_stance_mask.float() |
| ) |
| self._phase_joint_swing_steps += phase_joint_swing_mask |
| self._phase_joint_stance_steps += phase_joint_stance_mask |
| self._torso_roll += torso_roll |
| self._torso_roll_abs += torch.abs(torso_roll) |
| self._torso_roll_squared += torch.square(torso_roll) |
| self._torso_roll_sin += torso_roll * phase_sin |
| self._torso_roll_cos += torso_roll * phase_cos |
| self._torso_roll_positive_steps += torso_roll > 0.0 |
| self._foot_lateral_min = torch.minimum( |
| self._foot_lateral_min, |
| foot_lateral_position, |
| ) |
| self._foot_lateral_max = torch.maximum( |
| self._foot_lateral_max, |
| foot_lateral_position, |
| ) |
| self._foot_position_yaw_xy += foot_relative_yaw[:, :, :2] |
| self._late_swing_foot_advance += ( |
| foot_advance * late_swing_mask.float() |
| ) |
| self._late_swing_foot_steps += late_swing_mask |
| if POSE_SEQUENCE and self._pose_offsets is not None: |
| depth, _ = pose_sequence_reference(self._base) |
| target = ( |
| default_joint_pos[:, self._pose_joint_ids] |
| + depth.unsqueeze(1) * self._pose_offsets.unsqueeze(0) |
| ) |
| joint_delta = ( |
| joint_pos[:, self._pose_joint_ids] |
| - default_joint_pos[:, self._pose_joint_ids] |
| ) |
| achieved_depth = torch.sum( |
| joint_delta * self._pose_offsets.unsqueeze(0), |
| dim=1, |
| ) / torch.sum(torch.square(self._pose_offsets)) |
| self._pose_joint_mse += torch.mean( |
| torch.square(joint_pos[:, self._pose_joint_ids] - target), |
| dim=1, |
| ) |
| target_depth = POSE_BASELINE_DEPTH + POSE_DEPTH_AMPLITUDE * depth |
| self._pose_depth_abs_error += torch.abs( |
| achieved_depth - target_depth |
| ) |
| env_origins = self._base.scene.env_origins.to(body_pos_w.device) |
| torso_height = torch.mean( |
| body_pos_w[:, self._pose_height_body_ids, 2], dim=1 |
| ) - env_origins[:, 2] |
| com_pos_w, _ = mass_weighted_com_state(self._base) |
| com_height = com_pos_w[:, 2] - env_origins[:, 2] |
| if self._height_debug_steps < 5: |
| print( |
| "[EVAL HEIGHT STEP] " |
| f"step={self._height_debug_steps + 1} " |
| f"origin_z={float(env_origins[0, 2]):.4f} " |
| f"torso_height={float(torso_height[0]):.4f}", |
| flush=True, |
| ) |
| self._height_debug_steps += 1 |
| target_height = ( |
| POSE_STAND_HEIGHT - POSE_CROUCH_HEIGHT_DELTA * depth |
| ) |
| self._pose_height_abs_error += torch.abs( |
| torso_height - target_height |
| ) |
| stand_mask = depth < 0.05 |
| crouch_mask = depth > 0.95 |
| self._stand_achieved_depth += achieved_depth * stand_mask |
| self._stand_joint_delta += joint_delta * stand_mask.unsqueeze(1) |
| self._stand_root_height += torso_height * stand_mask |
| self._stand_com_height += com_height * stand_mask |
| self._stand_pose_steps += stand_mask |
| self._crouch_achieved_depth += achieved_depth * crouch_mask |
| self._crouch_joint_delta += joint_delta * crouch_mask.unsqueeze(1) |
| self._crouch_root_height += torso_height * crouch_mask |
| self._crouch_com_height += com_height * crouch_mask |
| self._crouch_pose_steps += crouch_mask |
| self._lengths += 1 |
|
|
| done = terminated | truncated |
| done_ids = done.nonzero(as_tuple=False).squeeze(-1) |
| if done_ids.numel() > 0: |
| self._completed_per_env[done_ids] += 1 |
| evaluated_ids = done_ids[self._completed_per_env[done_ids] > WARMUP_EPISODES] |
| completed = int(evaluated_ids.numel()) |
| self._episodes += completed |
| if completed > 0: |
| if ( |
| self._cardinal_group_ids is not None |
| and self._cardinal_episode_counts is not None |
| and self._cardinal_fall_counts is not None |
| and self._cardinal_length_sums is not None |
| and self._cardinal_com_velocity_sums is not None |
| and self._cardinal_com_error_sums is not None |
| and self._cardinal_gait_sums is not None |
| and self._cardinal_foot_min_distance_sums is not None |
| and self._cardinal_foot_min_lateral_separation_sums |
| is not None |
| and self._cardinal_foot_clearance_violation_fraction_sums |
| is not None |
| and self._cardinal_foot_lateral_order_violation_fraction_sums |
| is not None |
| ): |
| evaluated_groups = self._cardinal_group_ids[ |
| evaluated_ids |
| ] |
| for group_id in range(len(self._cardinal_names)): |
| group_ids = evaluated_ids[ |
| evaluated_groups == group_id |
| ] |
| if group_ids.numel() == 0: |
| continue |
| group_lengths = self._lengths[group_ids] |
| self._cardinal_episode_counts[group_id] += ( |
| group_ids.numel() |
| ) |
| self._cardinal_fall_counts[group_id] += terminated[ |
| group_ids |
| ].sum() |
| self._cardinal_length_sums[group_id] += ( |
| group_lengths.sum() |
| ) |
| self._cardinal_com_velocity_sums[group_id] += ( |
| self._com_velocity_yaw_xy[group_ids] |
| / group_lengths.unsqueeze(1) |
| ).sum(dim=0) |
| self._cardinal_com_error_sums[group_id] += ( |
| self._com_velocity_error[group_ids] |
| / group_lengths |
| ).sum() |
| self._cardinal_gait_sums[group_id] += self._gait[ |
| group_ids |
| ].sum() |
| self._cardinal_foot_min_distance_sums[group_id] += ( |
| self._foot_min_distance[group_ids].sum() |
| ) |
| self._cardinal_foot_min_lateral_separation_sums[ |
| group_id |
| ] += self._foot_min_lateral_separation[group_ids].sum() |
| self._cardinal_foot_clearance_violation_fraction_sums[ |
| group_id |
| ] += ( |
| self._foot_clearance_violation_steps[group_ids] |
| / group_lengths |
| ).sum() |
| self._cardinal_foot_lateral_order_violation_fraction_sums[ |
| group_id |
| ] += ( |
| self._foot_lateral_order_violation_steps[group_ids] |
| / group_lengths |
| ).sum() |
| if ( |
| self._pose_bias_group_ids is not None |
| and self._pose_bias_episode_counts is not None |
| and self._pose_bias_fall_counts is not None |
| and self._pose_bias_stand_height_sums is not None |
| and self._pose_bias_stand_step_sums is not None |
| and self._pose_bias_crouch_height_sums is not None |
| and self._pose_bias_crouch_step_sums is not None |
| ): |
| evaluated_groups = self._pose_bias_group_ids[evaluated_ids] |
| for group_id in range(len(self._pose_bias_names)): |
| group_ids = evaluated_ids[ |
| evaluated_groups == group_id |
| ] |
| if group_ids.numel() == 0: |
| continue |
| self._pose_bias_episode_counts[group_id] += ( |
| group_ids.numel() |
| ) |
| self._pose_bias_fall_counts[group_id] += terminated[ |
| group_ids |
| ].sum() |
| self._pose_bias_stand_height_sums[group_id] += ( |
| self._stand_root_height[group_ids].sum() |
| ) |
| self._pose_bias_stand_step_sums[group_id] += ( |
| self._stand_pose_steps[group_ids].sum() |
| ) |
| self._pose_bias_crouch_height_sums[group_id] += ( |
| self._crouch_root_height[group_ids].sum() |
| ) |
| self._pose_bias_crouch_step_sums[group_id] += ( |
| self._crouch_pose_steps[group_ids].sum() |
| ) |
| self._fall_episodes += int(terminated[evaluated_ids].sum().item()) |
| self._timeout_episodes += int(truncated[evaluated_ids].sum().item()) |
| self._return_sum += float(self._returns[evaluated_ids].sum().item()) |
| self._length_sum += int(self._lengths[evaluated_ids].sum().item()) |
| self._tracking_sum += float(self._tracking[evaluated_ids].sum().item()) |
| self._gait_sum += float(self._gait[evaluated_ids].sum().item()) |
| self._body_velocity_error_sum += float( |
| (self._body_velocity_error[evaluated_ids] / self._lengths[evaluated_ids]).sum().item() |
| ) |
| self._yaw_velocity_error_sum += float( |
| (self._yaw_velocity_error[evaluated_ids] / self._lengths[evaluated_ids]).sum().item() |
| ) |
| self._yaw_rate_error_sum += float( |
| ( |
| self._yaw_rate_error[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._com_velocity_error_sum += float( |
| ( |
| self._com_velocity_error[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._root_velocity_yaw_xy_sum += ( |
| self._root_velocity_yaw_xy[evaluated_ids] |
| / self._lengths[evaluated_ids].unsqueeze(1) |
| ).sum(dim=0) |
| self._com_velocity_yaw_xy_sum += ( |
| self._com_velocity_yaw_xy[evaluated_ids] |
| / self._lengths[evaluated_ids].unsqueeze(1) |
| ).sum(dim=0) |
| self._root_yaw_rate_sum += float( |
| ( |
| self._root_yaw_rate[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._com_vertical_velocity_error_sum += float( |
| ( |
| self._com_vertical_velocity_error[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._com_height_abs_error_sum += float( |
| ( |
| self._com_height_abs_error[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._arm_swing_score_sum += float( |
| ( |
| self._arm_swing_score[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._arm_extension_l2_sum += float( |
| ( |
| self._arm_extension_l2[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| evaluated_lengths = self._lengths[evaluated_ids] |
| self._arm_reciprocal_mode_abs_sum += float( |
| ( |
| self._arm_reciprocal_mode_abs[evaluated_ids] |
| / evaluated_lengths |
| ).sum().item() |
| ) |
| self._arm_counterweight_mode_abs_sum += float( |
| ( |
| self._arm_counterweight_mode_abs[evaluated_ids] |
| / evaluated_lengths |
| ).sum().item() |
| ) |
| |
| |
| self._arm_reciprocal_sin_coefficient_sum += float( |
| ( |
| 2.0 |
| * self._arm_reciprocal_sin[evaluated_ids] |
| / evaluated_lengths |
| ).sum().item() |
| ) |
| self._arm_reciprocal_cos_coefficient_sum += float( |
| ( |
| 2.0 |
| * self._arm_reciprocal_cos[evaluated_ids] |
| / evaluated_lengths |
| ).sum().item() |
| ) |
| self._arm_counterweight_sin_coefficient_sum += float( |
| ( |
| 2.0 |
| * self._arm_counterweight_sin[evaluated_ids] |
| / evaluated_lengths |
| ).sum().item() |
| ) |
| self._arm_counterweight_cos_coefficient_sum += float( |
| ( |
| 2.0 |
| * self._arm_counterweight_cos[evaluated_ids] |
| / evaluated_lengths |
| ).sum().item() |
| ) |
| self._foot_phase_velocity_score_sum += float( |
| ( |
| self._foot_phase_velocity_score[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._foot_min_distance_sum += float( |
| self._foot_min_distance[evaluated_ids].sum().item() |
| ) |
| self._foot_min_lateral_separation_sum += float( |
| self._foot_min_lateral_separation[evaluated_ids] |
| .sum() |
| .item() |
| ) |
| self._foot_clearance_violation_fraction_sum += float( |
| ( |
| self._foot_clearance_violation_steps[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ) |
| .sum() |
| .item() |
| ) |
| self._foot_lateral_order_violation_fraction_sum += float( |
| ( |
| self._foot_lateral_order_violation_steps[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ) |
| .sum() |
| .item() |
| ) |
| self._severe_foot_overlap_fraction_sum += float( |
| ( |
| self._severe_foot_overlap_steps[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ) |
| .sum() |
| .item() |
| ) |
| contact_fraction = ( |
| self._leg_contact_steps[evaluated_ids] |
| / self._lengths[evaluated_ids].unsqueeze(1) |
| ) |
| self._leg_contact_fraction_sum += contact_fraction.sum(dim=0) |
| self._leg_touchdown_count_sum += self._leg_touchdown_count[ |
| evaluated_ids |
| ].sum(dim=0) |
| self._leg_stride_interval_total += ( |
| self._leg_stride_interval_sum[evaluated_ids] |
| ).sum(dim=0) |
| self._leg_stride_interval_samples += ( |
| self._leg_stride_interval_count[evaluated_ids] |
| ).sum(dim=0) |
| self._leg_flight_time_total += self._leg_flight_time_sum[ |
| evaluated_ids |
| ].sum(dim=0) |
| self._leg_flight_samples += self._leg_flight_count[ |
| evaluated_ids |
| ].sum(dim=0) |
| episode_flight_mean = ( |
| self._leg_flight_time_sum[evaluated_ids] |
| / torch.clamp( |
| self._leg_flight_count[evaluated_ids], |
| min=1.0, |
| ) |
| ) |
| both_flights_observed = torch.all( |
| self._leg_flight_count[evaluated_ids] > 0, |
| dim=1, |
| ) |
| self._flight_time_abs_difference_sum += float( |
| ( |
| torch.abs( |
| episode_flight_mean[:, 0] |
| - episode_flight_mean[:, 1] |
| ) |
| * both_flights_observed.float() |
| ).sum().item() |
| ) |
| self._contact_duty_abs_difference_sum += float( |
| torch.abs( |
| contact_fraction[:, 0] - contact_fraction[:, 1] |
| ).sum().item() |
| ) |
| self._touchdown_count_abs_difference_sum += float( |
| torch.abs( |
| self._leg_touchdown_count[evaluated_ids, 0] |
| - self._leg_touchdown_count[evaluated_ids, 1] |
| ).sum().item() |
| ) |
| evaluated_lengths_2d = self._lengths[ |
| evaluated_ids |
| ].unsqueeze(1) |
| self._knee_position_sum += ( |
| self._knee_position[evaluated_ids] |
| / evaluated_lengths_2d |
| ).sum(dim=0) |
| self._knee_swing_position_sum += ( |
| self._knee_swing_position[evaluated_ids] |
| / torch.clamp( |
| self._knee_swing_steps[evaluated_ids], |
| min=1.0, |
| ) |
| ).sum(dim=0) |
| self._knee_stance_position_sum += ( |
| self._knee_stance_position[evaluated_ids] |
| / torch.clamp( |
| self._knee_stance_steps[evaluated_ids], |
| min=1.0, |
| ) |
| ).sum(dim=0) |
| self._knee_range_sum += ( |
| self._knee_max[evaluated_ids] |
| - self._knee_min[evaluated_ids] |
| ).sum(dim=0) |
| self._phase_joint_position_sum += ( |
| self._phase_joint_position[evaluated_ids] |
| / evaluated_lengths_2d |
| ).sum(dim=0) |
| self._phase_joint_swing_position_sum += ( |
| self._phase_joint_swing_position[evaluated_ids] |
| / torch.clamp( |
| self._phase_joint_swing_steps[evaluated_ids], |
| min=1.0, |
| ) |
| ).sum(dim=0) |
| self._phase_joint_stance_position_sum += ( |
| self._phase_joint_stance_position[evaluated_ids] |
| / torch.clamp( |
| self._phase_joint_stance_steps[evaluated_ids], |
| min=1.0, |
| ) |
| ).sum(dim=0) |
| self._torso_roll_sum += float( |
| ( |
| self._torso_roll[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._torso_roll_abs_sum += float( |
| ( |
| self._torso_roll_abs[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._torso_roll_squared_sum += float( |
| ( |
| self._torso_roll_squared[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._torso_roll_sin_coefficient_sum += float( |
| ( |
| 2.0 |
| * self._torso_roll_sin[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._torso_roll_cos_coefficient_sum += float( |
| ( |
| 2.0 |
| * self._torso_roll_cos[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._torso_roll_positive_fraction_sum += float( |
| ( |
| self._torso_roll_positive_steps[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._foot_lateral_excursion_sum += ( |
| self._foot_lateral_max[evaluated_ids] |
| - self._foot_lateral_min[evaluated_ids] |
| ).sum(dim=0) |
| self._foot_position_yaw_xy_sum += ( |
| self._foot_position_yaw_xy[evaluated_ids] |
| / self._lengths[evaluated_ids, None, None] |
| ).sum(dim=0) |
| self._late_swing_foot_advance_sum += ( |
| self._late_swing_foot_advance[evaluated_ids] |
| / torch.clamp( |
| self._late_swing_foot_steps[evaluated_ids], |
| min=1.0, |
| ) |
| ).sum(dim=0) |
| self._max_forward_progress_sum += float( |
| self._max_forward_progress[evaluated_ids].sum().item() |
| ) |
| if EVAL_OBSTACLE_TERRAIN: |
| self._obstacle_crossed_episodes += int( |
| self._obstacle_crossed[evaluated_ids].sum().item() |
| ) |
| self._obstacle_progressed_past_episodes += int( |
| self._obstacle_progressed_past[evaluated_ids] |
| .sum() |
| .item() |
| ) |
| self._obstacle_zone_seen_episodes += int( |
| self._obstacle_zone_seen[evaluated_ids].sum().item() |
| ) |
| obstacle_heights = self._obstacle_zone_max_foot_height[ |
| evaluated_ids |
| ] |
| self._obstacle_zone_max_foot_height_sum += torch.where( |
| torch.isfinite(obstacle_heights), |
| obstacle_heights, |
| torch.zeros_like(obstacle_heights), |
| ).sum(dim=0) |
| self._pose_joint_mse_sum += float( |
| (self._pose_joint_mse[evaluated_ids] / self._lengths[evaluated_ids]).sum().item() |
| ) |
| self._pose_depth_abs_error_sum += float( |
| ( |
| self._pose_depth_abs_error[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._pose_height_abs_error_sum += float( |
| ( |
| self._pose_height_abs_error[evaluated_ids] |
| / self._lengths[evaluated_ids] |
| ).sum().item() |
| ) |
| self._stand_achieved_depth_sum += float( |
| self._stand_achieved_depth[evaluated_ids].sum().item() |
| ) |
| self._stand_joint_delta_sum += self._stand_joint_delta[ |
| evaluated_ids |
| ].sum(dim=0) |
| self._stand_root_height_sum += float( |
| self._stand_root_height[evaluated_ids].sum().item() |
| ) |
| self._stand_com_height_sum += float( |
| self._stand_com_height[evaluated_ids].sum().item() |
| ) |
| self._stand_pose_step_count += float( |
| self._stand_pose_steps[evaluated_ids].sum().item() |
| ) |
| self._crouch_achieved_depth_sum += float( |
| self._crouch_achieved_depth[evaluated_ids].sum().item() |
| ) |
| self._crouch_joint_delta_sum += self._crouch_joint_delta[ |
| evaluated_ids |
| ].sum(dim=0) |
| self._crouch_root_height_sum += float( |
| self._crouch_root_height[evaluated_ids].sum().item() |
| ) |
| self._crouch_com_height_sum += float( |
| self._crouch_com_height[evaluated_ids].sum().item() |
| ) |
| self._crouch_pose_step_count += float( |
| self._crouch_pose_steps[evaluated_ids].sum().item() |
| ) |
| self._returns[done_ids] = 0.0 |
| self._tracking[done_ids] = 0.0 |
| self._gait[done_ids] = 0.0 |
| self._body_velocity_error[done_ids] = 0.0 |
| self._yaw_velocity_error[done_ids] = 0.0 |
| self._yaw_rate_error[done_ids] = 0.0 |
| self._com_velocity_error[done_ids] = 0.0 |
| self._root_velocity_yaw_xy[done_ids] = 0.0 |
| self._com_velocity_yaw_xy[done_ids] = 0.0 |
| self._root_yaw_rate[done_ids] = 0.0 |
| self._com_vertical_velocity_error[done_ids] = 0.0 |
| self._com_height_abs_error[done_ids] = 0.0 |
| self._arm_swing_score[done_ids] = 0.0 |
| self._arm_extension_l2[done_ids] = 0.0 |
| self._arm_reciprocal_mode_abs[done_ids] = 0.0 |
| self._arm_counterweight_mode_abs[done_ids] = 0.0 |
| self._arm_reciprocal_sin[done_ids] = 0.0 |
| self._arm_reciprocal_cos[done_ids] = 0.0 |
| self._arm_counterweight_sin[done_ids] = 0.0 |
| self._arm_counterweight_cos[done_ids] = 0.0 |
| self._foot_phase_velocity_score[done_ids] = 0.0 |
| self._foot_min_distance[done_ids] = float("inf") |
| self._foot_min_lateral_separation[done_ids] = float("inf") |
| self._foot_clearance_violation_steps[done_ids] = 0.0 |
| self._foot_lateral_order_violation_steps[done_ids] = 0.0 |
| self._severe_foot_overlap_steps[done_ids] = 0.0 |
| self._leg_contact_steps[done_ids] = 0.0 |
| self._leg_touchdown_count[done_ids] = 0.0 |
| self._leg_stride_interval_sum[done_ids] = 0.0 |
| self._leg_stride_interval_count[done_ids] = 0.0 |
| self._leg_flight_time_sum[done_ids] = 0.0 |
| self._leg_flight_count[done_ids] = 0.0 |
| self._last_leg_touchdown_step[done_ids] = -1 |
| self._leg_air_steps[done_ids] = 0 |
| self._was_leg_contact[done_ids] = False |
| self._knee_position[done_ids] = 0.0 |
| self._knee_swing_position[done_ids] = 0.0 |
| self._knee_stance_position[done_ids] = 0.0 |
| self._knee_swing_steps[done_ids] = 0.0 |
| self._knee_stance_steps[done_ids] = 0.0 |
| self._knee_min[done_ids] = float("inf") |
| self._knee_max[done_ids] = float("-inf") |
| self._phase_joint_position[done_ids] = 0.0 |
| self._phase_joint_swing_position[done_ids] = 0.0 |
| self._phase_joint_stance_position[done_ids] = 0.0 |
| self._phase_joint_swing_steps[done_ids] = 0.0 |
| self._phase_joint_stance_steps[done_ids] = 0.0 |
| self._torso_roll[done_ids] = 0.0 |
| self._torso_roll_abs[done_ids] = 0.0 |
| self._torso_roll_squared[done_ids] = 0.0 |
| self._torso_roll_sin[done_ids] = 0.0 |
| self._torso_roll_cos[done_ids] = 0.0 |
| self._torso_roll_positive_steps[done_ids] = 0.0 |
| self._foot_lateral_min[done_ids] = float("inf") |
| self._foot_lateral_max[done_ids] = float("-inf") |
| self._foot_position_yaw_xy[done_ids] = 0.0 |
| self._late_swing_foot_advance[done_ids] = 0.0 |
| self._late_swing_foot_steps[done_ids] = 0.0 |
| self._obstacle_crossed[done_ids] = False |
| self._obstacle_progressed_past[done_ids] = False |
| self._obstacle_zone_seen[done_ids] = False |
| self._obstacle_zone_max_foot_height[done_ids] = float("-inf") |
| self._max_forward_progress[done_ids] = float("-inf") |
| self._pose_joint_mse[done_ids] = 0.0 |
| self._pose_depth_abs_error[done_ids] = 0.0 |
| self._pose_height_abs_error[done_ids] = 0.0 |
| self._stand_achieved_depth[done_ids] = 0.0 |
| self._stand_joint_delta[done_ids] = 0.0 |
| self._stand_root_height[done_ids] = 0.0 |
| self._stand_com_height[done_ids] = 0.0 |
| self._stand_pose_steps[done_ids] = 0.0 |
| self._crouch_achieved_depth[done_ids] = 0.0 |
| self._crouch_joint_delta[done_ids] = 0.0 |
| self._crouch_root_height[done_ids] = 0.0 |
| self._crouch_com_height[done_ids] = 0.0 |
| self._crouch_pose_steps[done_ids] = 0.0 |
| self._lengths[done_ids] = 0 |
|
|
| target_reached = self._episodes >= TARGET_EPISODES |
| if ( |
| POSE_BIAS_GRID |
| and self._pose_bias_episode_counts is not None |
| and self._pose_bias_names |
| ): |
| episodes_per_group = max( |
| 1, TARGET_EPISODES // len(self._pose_bias_names) |
| ) |
| target_reached = bool( |
| torch.all( |
| self._pose_bias_episode_counts >= episodes_per_group |
| ).item() |
| ) |
| if ( |
| PLANAR_CARDINAL_SWEEP |
| and self._cardinal_episode_counts is not None |
| ): |
| episodes_per_group = max( |
| 1, TARGET_EPISODES // len(self._cardinal_names) |
| ) |
| target_reached = bool( |
| torch.all( |
| self._cardinal_episode_counts >= episodes_per_group |
| ).item() |
| ) |
| if target_reached: |
| arm_reciprocal_sin_coefficient = ( |
| self._arm_reciprocal_sin_coefficient_sum / self._episodes |
| ) |
| arm_reciprocal_cos_coefficient = ( |
| self._arm_reciprocal_cos_coefficient_sum / self._episodes |
| ) |
| arm_counterweight_sin_coefficient = ( |
| self._arm_counterweight_sin_coefficient_sum / self._episodes |
| ) |
| arm_counterweight_cos_coefficient = ( |
| self._arm_counterweight_cos_coefficient_sum / self._episodes |
| ) |
| result = { |
| "episodes": self._episodes, |
| "warmup_episodes_per_env": WARMUP_EPISODES, |
| "forward_speed_command_mps": FORWARD_SPEED, |
| "lateral_speed_command_mps": LATERAL_SPEED, |
| "yaw_rate_command_rad_s": YAW_RATE, |
| "backward_speed_command_mps": BACKWARD_SPEED, |
| "planar_cardinal_sweep": ( |
| [ |
| { |
| "name": name, |
| "command_mps": command, |
| "episodes": int( |
| self._cardinal_episode_counts[index].item() |
| ), |
| "fall_rate": float( |
| self._cardinal_fall_counts[index].item() |
| / self._cardinal_episode_counts[index].item() |
| ), |
| "mean_episode_seconds": float( |
| self._cardinal_length_sums[index].item() |
| * self._step_dt |
| / self._cardinal_episode_counts[index].item() |
| ), |
| "mean_com_velocity_yaw_frame_mps": ( |
| self._cardinal_com_velocity_sums[index] |
| / self._cardinal_episode_counts[index] |
| ).tolist(), |
| "mean_com_velocity_error_mps": float( |
| self._cardinal_com_error_sums[index].item() |
| / self._cardinal_episode_counts[index].item() |
| ), |
| "mean_gait_reward": float( |
| self._cardinal_gait_sums[index].item() |
| / ( |
| self._cardinal_episode_counts[index].item() |
| * self._max_episode_seconds |
| ) |
| ), |
| "mean_episode_min_foot_distance_m": float( |
| self._cardinal_foot_min_distance_sums[ |
| index |
| ].item() |
| / self._cardinal_episode_counts[index].item() |
| ), |
| "mean_episode_min_foot_lateral_separation_m": float( |
| self._cardinal_foot_min_lateral_separation_sums[ |
| index |
| ].item() |
| / self._cardinal_episode_counts[index].item() |
| ), |
| "mean_foot_clearance_violation_fraction": float( |
| self._cardinal_foot_clearance_violation_fraction_sums[ |
| index |
| ].item() |
| / self._cardinal_episode_counts[index].item() |
| ), |
| "mean_foot_lateral_order_violation_fraction": float( |
| self._cardinal_foot_lateral_order_violation_fraction_sums[ |
| index |
| ].item() |
| / self._cardinal_episode_counts[index].item() |
| ), |
| } |
| for index, (name, command) in enumerate( |
| ( |
| ("forward", [FORWARD_SPEED, 0.0]), |
| ("backward", [BACKWARD_SPEED, 0.0]), |
| ("left", [0.0, LATERAL_SPEED]), |
| ("right", [0.0, -LATERAL_SPEED]), |
| ) |
| ) |
| ] |
| if PLANAR_CARDINAL_SWEEP |
| and self._cardinal_episode_counts is not None |
| and self._cardinal_fall_counts is not None |
| and self._cardinal_length_sums is not None |
| and self._cardinal_com_velocity_sums is not None |
| and self._cardinal_com_error_sums is not None |
| and self._cardinal_gait_sums is not None |
| and self._cardinal_foot_min_distance_sums is not None |
| and self._cardinal_foot_min_lateral_separation_sums |
| is not None |
| and self._cardinal_foot_clearance_violation_fraction_sums |
| is not None |
| and self._cardinal_foot_lateral_order_violation_fraction_sums |
| is not None |
| else None |
| ), |
| "foot_clearance_body_names": DISTAL_FOOT_BODIES, |
| "foot_min_distance_threshold_m": FEET_MIN_DISTANCE, |
| "foot_min_lateral_separation_threshold_m": ( |
| FEET_MIN_LATERAL_SEPARATION |
| ), |
| "gait_period_s": EVAL_GAIT_PERIOD, |
| "plane_only": PLANE_ONLY, |
| "obstacle_terrain": EVAL_OBSTACLE_TERRAIN, |
| "obstacle_crossing_rate": ( |
| self._obstacle_crossed_episodes / self._episodes |
| if EVAL_OBSTACLE_TERRAIN |
| else None |
| ), |
| "obstacle_progressed_past_rate": ( |
| self._obstacle_progressed_past_episodes / self._episodes |
| if EVAL_OBSTACLE_TERRAIN |
| else None |
| ), |
| "obstacle_zone_reach_rate": ( |
| self._obstacle_zone_seen_episodes / self._episodes |
| if EVAL_OBSTACLE_TERRAIN |
| else None |
| ), |
| "mean_obstacle_zone_max_foot_height_m": ( |
| ( |
| self._obstacle_zone_max_foot_height_sum |
| / self._episodes |
| ).tolist() |
| if EVAL_OBSTACLE_TERRAIN |
| else None |
| ), |
| "mean_episode_max_forward_progress_m": ( |
| self._max_forward_progress_sum / self._episodes |
| ), |
| "pushes_disabled": DISABLE_PUSHES, |
| "push_interval_s": ( |
| None |
| if DISABLE_PUSHES |
| else float( |
| os.environ.get("DROPBEAR_PUSH_INTERVAL_S", "5.0") |
| ) |
| ), |
| "push_forward_velocity_mps": ( |
| None |
| if DISABLE_PUSHES |
| else float( |
| os.environ.get( |
| "DROPBEAR_PUSH_FORWARD_VELOCITY", |
| "0.5", |
| ) |
| ) |
| ), |
| "push_lateral_velocity_mps": ( |
| None |
| if DISABLE_PUSHES |
| else float( |
| os.environ.get( |
| "DROPBEAR_PUSH_LATERAL_VELOCITY", |
| "0.5", |
| ) |
| ) |
| ), |
| "reciprocal_shoulder_action_projection": ( |
| SHOULDER_COUNTERWEIGHT_SCALE is not None |
| ), |
| "shoulder_counterweight_action_scale": ( |
| SHOULDER_COUNTERWEIGHT_SCALE |
| ), |
| "actor_base_lin_vel_feedback": ACTOR_BASE_LIN_VEL, |
| "policy_mirror_projection": POLICY_MIRROR_PROJECTION, |
| "reset_joint_position_range": RESET_JOINT_POSITION_RANGE, |
| "reset_joint_velocity_range": RESET_JOINT_VELOCITY_RANGE, |
| "reset_policy_joints_only": RESET_POLICY_JOINTS_ONLY, |
| "pose_sequence": POSE_SEQUENCE, |
| "pose_action_residual": POSE_ACTION_RESIDUAL, |
| "pose_baseline_depth": ( |
| POSE_BASELINE_DEPTH if POSE_SEQUENCE else None |
| ), |
| "pose_depth_amplitude": ( |
| POSE_DEPTH_AMPLITUDE if POSE_SEQUENCE else None |
| ), |
| "pose_stand_height_m": ( |
| POSE_STAND_HEIGHT if POSE_SEQUENCE else None |
| ), |
| "pose_height_body_names": ( |
| self._pose_height_body_names if POSE_SEQUENCE else None |
| ), |
| "pose_crouch_height_delta_m": ( |
| POSE_CROUCH_HEIGHT_DELTA if POSE_SEQUENCE else None |
| ), |
| "pose_residual_scale": ( |
| POSE_RESIDUAL_SCALE |
| if POSE_ACTION_RESIDUAL and POSE_RESIDUAL_SCALE is not None |
| else (1.0 if POSE_ACTION_RESIDUAL else None) |
| ), |
| "pose_adapter_offsets": ( |
| self._pose_offsets.tolist() |
| if POSE_SEQUENCE and self._pose_offsets is not None |
| else None |
| ), |
| "pose_bias_grid": ( |
| [ |
| { |
| "name": name, |
| "episodes": int( |
| self._pose_bias_episode_counts[index].item() |
| ), |
| "fall_rate": ( |
| float( |
| self._pose_bias_fall_counts[index].item() |
| / self._pose_bias_episode_counts[index].item() |
| ) |
| if self._pose_bias_episode_counts[index] > 0 |
| else None |
| ), |
| "mean_torso_height_stand_m": ( |
| float( |
| self._pose_bias_stand_height_sums[index].item() |
| / self._pose_bias_stand_step_sums[index].item() |
| ) |
| if self._pose_bias_stand_step_sums[index] > 0 |
| else None |
| ), |
| "mean_torso_height_crouch_hold_m": ( |
| float( |
| self._pose_bias_crouch_height_sums[index].item() |
| / self._pose_bias_crouch_step_sums[index].item() |
| ) |
| if self._pose_bias_crouch_step_sums[index] > 0 |
| else None |
| ), |
| } |
| for index, name in enumerate(self._pose_bias_names) |
| ] |
| if POSE_BIAS_GRID |
| else None |
| ), |
| "mean_return": self._return_sum / self._episodes, |
| "mean_episode_length": self._length_sum / self._episodes, |
| "mean_episode_seconds": self._length_sum * self._step_dt / self._episodes, |
| "mean_tracking_reward": self._tracking_sum |
| / (self._episodes * self._max_episode_seconds), |
| "mean_gait_reward": self._gait_sum / (self._episodes * self._max_episode_seconds), |
| "mean_body_velocity_error_mps": self._body_velocity_error_sum / self._episodes, |
| "mean_yaw_velocity_error_mps": self._yaw_velocity_error_sum / self._episodes, |
| "mean_yaw_rate_error_rad_s": ( |
| self._yaw_rate_error_sum / self._episodes |
| ), |
| "mean_com_velocity_error_mps": ( |
| self._com_velocity_error_sum / self._episodes |
| ), |
| "mean_root_velocity_yaw_frame_mps": ( |
| self._root_velocity_yaw_xy_sum / self._episodes |
| ).tolist(), |
| "mean_com_velocity_yaw_frame_mps": ( |
| self._com_velocity_yaw_xy_sum / self._episodes |
| ).tolist(), |
| "mean_root_yaw_rate_rad_s": ( |
| self._root_yaw_rate_sum / self._episodes |
| ), |
| "mean_com_vertical_velocity_abs_error_mps": ( |
| self._com_vertical_velocity_error_sum / self._episodes |
| ), |
| "mean_com_height_abs_error_m": ( |
| self._com_height_abs_error_sum / self._episodes |
| ), |
| "mean_arm_swing_score": ( |
| self._arm_swing_score_sum / self._episodes |
| ), |
| "mean_arm_extension_l2": ( |
| self._arm_extension_l2_sum / self._episodes |
| ), |
| "mean_arm_reciprocal_mode_abs_rad": ( |
| self._arm_reciprocal_mode_abs_sum / self._episodes |
| ), |
| "mean_arm_counterweight_mode_abs_rad": ( |
| self._arm_counterweight_mode_abs_sum / self._episodes |
| ), |
| "mean_arm_reciprocal_sin_coefficient_rad": ( |
| arm_reciprocal_sin_coefficient |
| ), |
| "mean_arm_reciprocal_cos_coefficient_rad": ( |
| arm_reciprocal_cos_coefficient |
| ), |
| "mean_arm_reciprocal_fundamental_amplitude_rad": math.hypot( |
| arm_reciprocal_sin_coefficient, |
| arm_reciprocal_cos_coefficient, |
| ), |
| "mean_arm_reciprocal_phase_offset_rad": math.atan2( |
| arm_reciprocal_cos_coefficient, |
| arm_reciprocal_sin_coefficient, |
| ), |
| "mean_arm_counterweight_sin_coefficient_rad": ( |
| arm_counterweight_sin_coefficient |
| ), |
| "mean_arm_counterweight_cos_coefficient_rad": ( |
| arm_counterweight_cos_coefficient |
| ), |
| "mean_arm_counterweight_fundamental_amplitude_rad": math.hypot( |
| arm_counterweight_sin_coefficient, |
| arm_counterweight_cos_coefficient, |
| ), |
| "mean_arm_counterweight_phase_offset_rad": math.atan2( |
| arm_counterweight_cos_coefficient, |
| arm_counterweight_sin_coefficient, |
| ), |
| "mean_foot_phase_velocity_score": ( |
| self._foot_phase_velocity_score_sum / self._episodes |
| ), |
| "mean_episode_min_foot_distance_m": ( |
| self._foot_min_distance_sum / self._episodes |
| ), |
| "mean_episode_min_foot_lateral_separation_m": ( |
| self._foot_min_lateral_separation_sum / self._episodes |
| ), |
| "mean_foot_clearance_violation_fraction": ( |
| self._foot_clearance_violation_fraction_sum |
| / self._episodes |
| ), |
| "mean_foot_lateral_order_violation_fraction": ( |
| self._foot_lateral_order_violation_fraction_sum |
| / self._episodes |
| ), |
| "mean_severe_foot_overlap_fraction": ( |
| self._severe_foot_overlap_fraction_sum / self._episodes |
| ), |
| "mean_leg_contact_fraction": ( |
| self._leg_contact_fraction_sum / self._episodes |
| ).tolist(), |
| "mean_contact_duty_abs_difference": ( |
| self._contact_duty_abs_difference_sum / self._episodes |
| ), |
| "mean_leg_touchdowns_per_episode": ( |
| self._leg_touchdown_count_sum / self._episodes |
| ).tolist(), |
| "mean_touchdown_count_abs_difference": ( |
| self._touchdown_count_abs_difference_sum / self._episodes |
| ), |
| "mean_leg_stride_interval_s": ( |
| self._leg_stride_interval_total |
| / torch.clamp( |
| self._leg_stride_interval_samples, |
| min=1.0, |
| ) |
| ).tolist(), |
| "mean_leg_flight_time_s": ( |
| self._leg_flight_time_total |
| / torch.clamp(self._leg_flight_samples, min=1.0) |
| ).tolist(), |
| "mean_flight_time_abs_difference_s": ( |
| self._flight_time_abs_difference_sum / self._episodes |
| ), |
| "knee_joint_names": self._knee_joint_names, |
| "mean_knee_position_rad": ( |
| self._knee_position_sum / self._episodes |
| ).tolist(), |
| "mean_swing_knee_position_rad": ( |
| self._knee_swing_position_sum / self._episodes |
| ).tolist(), |
| "mean_stance_knee_position_rad": ( |
| self._knee_stance_position_sum / self._episodes |
| ).tolist(), |
| "mean_knee_swing_stance_delta_rad": ( |
| ( |
| self._knee_swing_position_sum |
| - self._knee_stance_position_sum |
| ) |
| / self._episodes |
| ).tolist(), |
| "mean_episode_knee_range_rad": ( |
| self._knee_range_sum / self._episodes |
| ).tolist(), |
| "phase_joint_names": self._phase_joint_names, |
| "mean_phase_joint_position_rad": ( |
| self._phase_joint_position_sum / self._episodes |
| ).tolist(), |
| "mean_phase_joint_swing_position_rad": ( |
| self._phase_joint_swing_position_sum / self._episodes |
| ).tolist(), |
| "mean_phase_joint_stance_position_rad": ( |
| self._phase_joint_stance_position_sum / self._episodes |
| ).tolist(), |
| "mean_phase_joint_swing_stance_delta_rad": ( |
| ( |
| self._phase_joint_swing_position_sum |
| - self._phase_joint_stance_position_sum |
| ) |
| / self._episodes |
| ).tolist(), |
| "mean_torso_roll_rad": ( |
| self._torso_roll_sum / self._episodes |
| ), |
| "mean_abs_torso_roll_rad": ( |
| self._torso_roll_abs_sum / self._episodes |
| ), |
| "rms_torso_roll_rad": ( |
| self._torso_roll_squared_sum / self._episodes |
| ) ** 0.5, |
| "mean_episode_foot_lateral_excursion_m": ( |
| self._foot_lateral_excursion_sum / self._episodes |
| ).tolist(), |
| "mean_foot_position_yaw_frame_xy_m": ( |
| self._foot_position_yaw_xy_sum / self._episodes |
| ).tolist(), |
| "mean_late_swing_foot_advance_m": ( |
| self._late_swing_foot_advance_sum / self._episodes |
| ).tolist(), |
| "mean_pose_joint_rmse_rad": ( |
| (self._pose_joint_mse_sum / self._episodes) ** 0.5 |
| if POSE_SEQUENCE |
| else None |
| ), |
| "mean_torso_roll_sin_coefficient_rad": ( |
| self._torso_roll_sin_coefficient_sum / self._episodes |
| ), |
| "mean_torso_roll_cos_coefficient_rad": ( |
| self._torso_roll_cos_coefficient_sum / self._episodes |
| ), |
| "mean_torso_roll_fundamental_amplitude_rad": math.hypot( |
| self._torso_roll_sin_coefficient_sum / self._episodes, |
| self._torso_roll_cos_coefficient_sum / self._episodes, |
| ), |
| "mean_torso_roll_positive_fraction": ( |
| self._torso_roll_positive_fraction_sum / self._episodes |
| ), |
| "mean_pose_depth_abs_error": ( |
| self._pose_depth_abs_error_sum / self._episodes |
| if POSE_SEQUENCE |
| else None |
| ), |
| "mean_pose_height_abs_error_m": ( |
| self._pose_height_abs_error_sum / self._episodes |
| if POSE_SEQUENCE |
| else None |
| ), |
| "mean_achieved_depth_stand": ( |
| self._stand_achieved_depth_sum / self._stand_pose_step_count |
| if POSE_SEQUENCE and self._stand_pose_step_count > 0 |
| else None |
| ), |
| "pose_joint_names": ( |
| self._pose_joint_names if POSE_SEQUENCE else None |
| ), |
| "mean_stand_joint_delta_rad": ( |
| ( |
| self._stand_joint_delta_sum |
| / self._stand_pose_step_count |
| ).tolist() |
| if POSE_SEQUENCE and self._stand_pose_step_count > 0 |
| else None |
| ), |
| "mean_achieved_depth_crouch_hold": ( |
| self._crouch_achieved_depth_sum / self._crouch_pose_step_count |
| if POSE_SEQUENCE and self._crouch_pose_step_count > 0 |
| else None |
| ), |
| "mean_crouch_hold_joint_delta_rad": ( |
| ( |
| self._crouch_joint_delta_sum |
| / self._crouch_pose_step_count |
| ).tolist() |
| if POSE_SEQUENCE and self._crouch_pose_step_count > 0 |
| else None |
| ), |
| "mean_root_height_stand_m": ( |
| self._stand_root_height_sum / self._stand_pose_step_count |
| if POSE_SEQUENCE and self._stand_pose_step_count > 0 |
| else None |
| ), |
| "mean_torso_height_stand_m": ( |
| self._stand_root_height_sum / self._stand_pose_step_count |
| if POSE_SEQUENCE and self._stand_pose_step_count > 0 |
| else None |
| ), |
| "mean_com_height_stand_m": ( |
| self._stand_com_height_sum / self._stand_pose_step_count |
| if POSE_SEQUENCE and self._stand_pose_step_count > 0 |
| else None |
| ), |
| "mean_root_height_crouch_hold_m": ( |
| self._crouch_root_height_sum / self._crouch_pose_step_count |
| if POSE_SEQUENCE and self._crouch_pose_step_count > 0 |
| else None |
| ), |
| "mean_torso_height_crouch_hold_m": ( |
| self._crouch_root_height_sum / self._crouch_pose_step_count |
| if POSE_SEQUENCE and self._crouch_pose_step_count > 0 |
| else None |
| ), |
| "mean_com_height_crouch_hold_m": ( |
| self._crouch_com_height_sum / self._crouch_pose_step_count |
| if POSE_SEQUENCE and self._crouch_pose_step_count > 0 |
| else None |
| ), |
| "fall_rate": self._fall_episodes / self._episodes, |
| "timeout_rate": self._timeout_episodes / self._episodes, |
| } |
| if OUTPUT_PATH is not None: |
| OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) |
| OUTPUT_PATH.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n") |
| print(f"[DETERMINISTIC EVAL] {json.dumps(result, sort_keys=True)}", flush=True) |
| raise KeyboardInterrupt |
|
|
| return observations, rewards, terminated, truncated, extras |
|
|
|
|
| def make_evaluation_env(env_id: str, *args: Any, **kwargs: Any) -> gym.Env: |
| env_cfg = kwargs.get("cfg") |
| if env_cfg is not None: |
| env_cfg.commands.base_velocity.ranges = env_cfg.commands.base_velocity.limit_ranges |
| if PLANAR_CARDINAL_SWEEP: |
| ranges = env_cfg.commands.base_velocity.ranges |
| ranges.lin_vel_x = (BACKWARD_SPEED, FORWARD_SPEED) |
| ranges.lin_vel_y = (-LATERAL_SPEED, LATERAL_SPEED) |
| ranges.ang_vel_z = (0.0, 0.0) |
| env_cfg.commands.base_velocity.cardinal_commands = True |
| env_cfg.commands.base_velocity.cardinal_by_env = True |
| env_cfg.commands.base_velocity.rel_standing_envs = 0.0 |
| env_cfg.commands.base_velocity.rel_heading_envs = 0.0 |
| env_cfg.commands.base_velocity.heading_command = False |
| elif ( |
| FORWARD_SPEED is not None |
| or LATERAL_SPEED is not None |
| or YAW_RATE is not None |
| ): |
| ranges = env_cfg.commands.base_velocity.ranges |
| forward_speed = FORWARD_SPEED or 0.0 |
| lateral_speed = LATERAL_SPEED or 0.0 |
| ranges.lin_vel_x = (forward_speed, forward_speed) |
| ranges.lin_vel_y = (lateral_speed, lateral_speed) |
| yaw_rate = YAW_RATE or 0.0 |
| ranges.ang_vel_z = (yaw_rate, yaw_rate) |
| |
| |
| |
| |
| env_cfg.commands.base_velocity.rel_standing_envs = 0.0 |
| env_cfg.commands.base_velocity.rel_heading_envs = 0.0 |
| env_cfg.commands.base_velocity.heading_command = False |
| env_cfg.curriculum.lin_vel_cmd_levels = None |
| env_cfg.curriculum.terrain_levels = None |
| if EVAL_OBSTACLE_TERRAIN: |
| if PLANE_ONLY: |
| raise ValueError( |
| "--eval-obstacle-terrain conflicts with --eval-plane" |
| ) |
| generator = env_cfg.scene.terrain.terrain_generator |
| generator.sub_terrains["flat"].proportion = 0.0 |
| generator.sub_terrains["forward_box"].proportion = 1.0 |
| generator.num_rows = 2 |
| generator.num_cols = max( |
| 1, |
| math.ceil(env_cfg.scene.num_envs / generator.num_rows), |
| ) |
| env_cfg.scene.terrain.max_init_terrain_level = 1 |
| if DISABLE_PUSHES: |
| env_cfg.events.push_robot = None |
| if PLANE_ONLY: |
| env_cfg.scene.terrain.terrain_type = "plane" |
| env_cfg.scene.terrain.terrain_generator = None |
| env_cfg.scene.terrain.max_init_terrain_level = 0 |
| elif env_cfg.scene.terrain.terrain_generator is not None: |
| env_cfg.scene.terrain.terrain_generator.curriculum = False |
| return DeterministicEvalWrapper(ORIGINAL_GYM_MAKE(env_id, *args, **kwargs)) |
|
|
|
|
| if not PLAY_SCRIPT.is_file(): |
| raise FileNotFoundError(f"Isaac Lab player not found: {PLAY_SCRIPT}") |
|
|
| gym.make = make_evaluation_env |
| sys.path.insert(0, str(PLAY_SCRIPT.parent)) |
| runpy.run_path(str(PLAY_SCRIPT), run_name="__main__") |
|
|