Source code for wbc_mjlab.env.mdp.actions
"""Tracking-specific action terms."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
import torch
from mjlab.actuator.actuator import TransmissionType
from mjlab.envs.mdp.actions.actions import (
BaseAction,
BaseActionCfg,
JointPositionAction,
JointPositionActionCfg,
)
from wbc_mjlab.env.mdp.commands import MotionCommand
if TYPE_CHECKING:
from mjlab.envs import ManagerBasedRlEnv
[docs]
@dataclass(kw_only=True)
class ReferenceJointPositionActionCfg(BaseActionCfg):
"""Joint position targets as motion reference plus scaled residual actions.
Implements Zest-style control: q_cmd = q_ref + scale * a_t (minus encoder bias).
"""
command_name: str = "motion"
def __post_init__(self):
self.transmission_type = TransmissionType.JOINT
if self.offset != 0.0:
raise ValueError(
"ReferenceJointPositionActionCfg does not support 'offset'. "
"Reference joint positions come from the motion command."
)
[docs]
def build(self, env: ManagerBasedRlEnv) -> ReferenceJointPositionAction:
return ReferenceJointPositionAction(self, env)
[docs]
class ReferenceJointPositionAction(BaseAction):
"""Control joints via position targets relative to the motion reference."""
cfg: ReferenceJointPositionActionCfg
_command: MotionCommand
def __init__(self, cfg: ReferenceJointPositionActionCfg, env: ManagerBasedRlEnv):
super().__init__(cfg=cfg, env=env)
command = env.command_manager.get_term(cfg.command_name)
if not isinstance(command, MotionCommand):
raise TypeError(
f"ReferenceJointPositionAction requires MotionCommand, "
f"got {type(command).__name__} for '{cfg.command_name}'."
)
self._command = command
[docs]
def apply_actions(self) -> None:
ref_joint_pos = self._command.tracked_joint_pos
encoder_bias = self._entity.data.encoder_bias[:, self._target_ids]
target = ref_joint_pos + self._processed_actions - encoder_bias
self._entity.set_joint_position_target(target, joint_ids=self._target_ids)
[docs]
@dataclass(kw_only=True)
class DefaultOffsetJointPositionActionCfg(JointPositionActionCfg):
"""Default-relative joint targets; seed ``a`` from ``q_ref`` on RSI resample.
Live command stays deployable: ``q_cmd = q_default + scale · a``.
After motion resample, ``a`` is set to ``(q_ref - q_default) / scale`` so the
first PD target matches the clip. Deploy uses the same formula with
measured joints instead of ``q_ref``.
"""
command_name: str = "motion"
[docs]
def build(self, env: ManagerBasedRlEnv) -> DefaultOffsetJointPositionAction:
return DefaultOffsetJointPositionAction(self, env)
[docs]
class DefaultOffsetJointPositionAction(JointPositionAction):
"""``q_cmd = q_default + scale · a`` with reset warm-start from ``q_ref``."""
cfg: DefaultOffsetJointPositionActionCfg
_command: MotionCommand
def __init__(
self, cfg: DefaultOffsetJointPositionActionCfg, env: ManagerBasedRlEnv
):
super().__init__(cfg=cfg, env=env)
command = env.command_manager.get_term(cfg.command_name)
if not isinstance(command, MotionCommand):
raise TypeError(
f"DefaultOffsetJointPositionAction requires MotionCommand, "
f"got {type(command).__name__} for '{cfg.command_name}'."
)
self._command = command
[docs]
def seed_from_motion_reference(self, env_ids: torch.Tensor) -> None:
"""Set raw actions so ``q_default + scale · a = q_ref`` (resample only)."""
q_ref = self._command.tracked_joint_pos[env_ids]
q_default = self._entity.data.default_joint_pos[env_ids][:, self._target_ids]
scale = self._scale[env_ids] if isinstance(self._scale, torch.Tensor) else self._scale
raw = (q_ref - q_default) / torch.as_tensor(
scale, device=q_ref.device, dtype=q_ref.dtype
).clamp(min=1.0e-6)
self._raw_actions[env_ids] = raw
self._processed_actions[env_ids] = raw * scale + q_default
self._write_manager_action(env_ids, raw)
def _write_manager_action(self, env_ids: torch.Tensor, raw: torch.Tensor) -> None:
manager = self._env.action_manager
idx = 0
for term in manager._terms.values():
width = term.action_dim
if term is self:
manager._action[env_ids, idx : idx + width] = raw
manager._prev_action[env_ids, idx : idx + width] = raw
return
idx += width