Source code for wbc_mjlab.presets.zest

"""Zest preset (arXiv:2602.00401)."""

from __future__ import annotations

from dataclasses import replace

from mjlab.envs import ManagerBasedRlEnvCfg
from mjlab.managers.termination_manager import TerminationTermCfg

import wbc_mjlab.env.mdp as mdp
from wbc_mjlab.env.mdp.commands import MotionCommandCfg

# Zest Table S4: exp(-κ‖e‖²/σ²) with κ = 1/4.
_TRACKING_KAPPA = 0.25

KEYBODY_GROUND_CONTACT_FORCE_THRESHOLD = 2000.0


[docs] def apply_zest( cfg: ManagerBasedRlEnvCfg, *, reward_body_names: tuple[str, ...], contact_body_names: tuple[str, ...], force_threshold: float = KEYBODY_GROUND_CONTACT_FORCE_THRESHOLD, ) -> None: """Zest paper repro: no actor SE terms, reward-aligned RSI, assistive wrench. Args: cfg: Env config to mutate in place. reward_body_names: Keybodies for tracking rewards. contact_body_names: Bodies for keybody ground-contact termination. force_threshold: Contact force threshold (N). """ rw = cfg.rewards # --- Table S4 tracking (all weight 1.0) --- rw["motion_global_root_pos"].weight = 1.0 rw["motion_global_root_pos"].params["std"] = 0.4 rw["motion_global_root_ori"].weight = 1.0 rw["motion_global_root_ori"].params["std"] = 0.5 rw["motion_root_lin_vel_b"].weight = 1.0 rw["motion_root_lin_vel_b"].params["std"] = 0.6 rw["motion_root_ang_vel_b"].weight = 1.0 rw["motion_root_ang_vel_b"].params["std"] = 1.5 rw["motion_body_pos"].weight = 1.0 rw["motion_body_pos"].params.pop("std", None) rw["motion_body_pos"].params["sigma_per_keybody"] = 0.2 rw["motion_body_pos"].params["body_error_aggregate"] = "sum" rw["motion_body_pos"].params["body_names"] = reward_body_names rw["motion_body_ori"].weight = 1.0 rw["motion_body_ori"].params.pop("std", None) rw["motion_body_ori"].params["sigma_per_keybody"] = 0.4 rw["motion_body_ori"].params["body_error_aggregate"] = "sum" rw["motion_body_ori"].params["body_names"] = reward_body_names rw["motion_joint_pos"].weight = 1.0 rw["motion_joint_pos"].params.pop("std", None) rw["motion_joint_pos"].params.pop("per_joint", None) rw["motion_joint_pos"].params["sigma_per_joint"] = 0.3 for name in ( "motion_global_root_pos", "motion_global_root_ori", "motion_root_lin_vel_b", "motion_root_ang_vel_b", "motion_body_pos", "motion_body_ori", "motion_joint_pos", ): rw[name].params["kappa"] = _TRACKING_KAPPA # Not in Table S4 (disable extras inherited from the WBC template). rw["motion_body_lin_vel"].weight = 0.0 rw["motion_body_ang_vel"].weight = 0.0 rw["motion_joint_vel"].weight = 0.0 rw["foot_slip"].weight = 0.0 rw["angular_momentum"].weight = 0.0 rw["survival"].weight = 1.0 rw["action_rate_l1"].weight = -0.1 rw["joint_acc"].weight = -5.0e-6 rw["joint_limit"].weight = -1.0 rw["actuator_torque_soft_limit"].weight = -0.1 rw["actuator_torque_soft_limit"].params["soft_ratio"] = 0.9 motion_cmd = cfg.commands["motion"] assert isinstance(motion_cmd, MotionCommandCfg) motion_cmd.assistive_wrench_enabled = True motion_cmd.rsi = replace( motion_cmd.rsi, similarity_from_rewards=True, bin_width_s=4.0, similarity_norm_by_remaining_clip=True, min_bin_span_ratio=0.5, persist_failure_levels=True, ) cfg.observations["actor"].terms.pop("ref_joint_vel", None) # Zest paper: anchor early termination + catastrophic contact only (no EE tracking cutoff). cfg.terminations.pop("ee_body_pos", None) cfg.terminations["anchor_pos"].params["threshold"] = 0.35 cfg.terminations["keybody_ground_contact_force"] = TerminationTermCfg( func=mdp.excessive_keybody_ground_contact_force, params={ "sensor_name": "keybodies_ground_contact", "body_names": contact_body_names, "body_slot_order": contact_body_names, "force_threshold": force_threshold, }, )