Source code for wbc_mjlab.tasks

"""WBC mjlab task configs and registration."""

from __future__ import annotations

from collections.abc import Iterable

from wbc_mjlab.robots.ids import resolve_robot_id
from wbc_mjlab.tasks.config import WbcTaskConfig

DEFAULT_TASK_ID = "Wbc-G1"
TASK_ID = DEFAULT_TASK_ID  # back-compat

LEGACY_TASK_TO_ID: dict[str, str] = {
  "Wbc": DEFAULT_TASK_ID,
  "Wbc-Tracking-G1": "Wbc-G1",
  "Wbc-Tracking-G1-No-State-Estimation": "Wbc-G1-Zest",
  "Wbc-G1-NoSE": "Wbc-G1-Zest",
}

_TASK_BY_ID: dict[str, WbcTaskConfig] = {}
_BUILTIN_TASKS_LOADED = False
_TASKS_REGISTERED = False
_REGISTERING = False
_MJLAB_REGISTERED: set[str] = set()

_LAST_ROBOT_ID = "g1"
_LAST_TASK_ID = DEFAULT_TASK_ID


def last_registered_robot_id() -> str:
  return _LAST_ROBOT_ID


def last_registered_task_id() -> str:
  return _LAST_TASK_ID


def _set_active_run(robot_id: str, task_id: str) -> None:
  global _LAST_ROBOT_ID, _LAST_TASK_ID
  _LAST_ROBOT_ID = robot_id
  _LAST_TASK_ID = task_id


def _load_builtin_tasks() -> None:
  global _BUILTIN_TASKS_LOADED
  if _BUILTIN_TASKS_LOADED:
    return
  from wbc_mjlab.robots.g1.tasks import G1_WBC_TASKS

  for task in G1_WBC_TASKS:
    _TASK_BY_ID.setdefault(task.task_id, task)
  _BUILTIN_TASKS_LOADED = True


def _ensure_tasks() -> None:
  _load_builtin_tasks()


[docs] def all_wbc_tasks() -> tuple[WbcTaskConfig, ...]: """Return all registered WBC tasks (built-in and extension).""" _ensure_tasks() return tuple(_TASK_BY_ID[tid] for tid in sorted(_TASK_BY_ID))
[docs] def get_task_config(task_id: str) -> WbcTaskConfig: """Look up a task by id (resolves legacy aliases).""" _ensure_tasks() key = LEGACY_TASK_TO_ID.get(task_id, task_id) try: return _TASK_BY_ID[key] except KeyError as exc: known = ", ".join(sorted(_TASK_BY_ID)) raise KeyError(f"Unknown WBC task {task_id!r}. Registered: {known}") from exc
[docs] def list_wbc_task_ids() -> list[str]: """Sorted list of registered task ids (for CLIs and listings).""" _ensure_tasks() return sorted(_TASK_BY_ID)
def list_robot_ids_from_tasks() -> list[str]: _ensure_tasks() return sorted({t.robot_id for t in _TASK_BY_ID.values()}) def _rl_cfg_for_task(task: WbcTaskConfig): from mjlab.rl import RslRlOnPolicyRunnerCfg from wbc_mjlab.robots.env import make_wbc_rl_cfg cfg: RslRlOnPolicyRunnerCfg = make_wbc_rl_cfg(task.robot_id) cfg.experiment_name = task.experiment_name return cfg
[docs] def register_wbc_task(task: WbcTaskConfig) -> None: """Register one task with mjlab (env, play env, RL cfg, runner).""" from mjlab.tasks.registry import register_mjlab_task from wbc_mjlab.robots.env import make_wbc_env_cfg from wbc_mjlab.rl.runner import PolicyOnlyMotionTrackingRunner register_mjlab_task( task_id=task.task_id, env_cfg=make_wbc_env_cfg(task.robot_id, task_id=task.task_id), play_env_cfg=make_wbc_env_cfg(task.robot_id, play=True, task_id=task.task_id), rl_cfg=_rl_cfg_for_task(task), runner_cls=PolicyOnlyMotionTrackingRunner, ) _MJLAB_REGISTERED.add(task.task_id)
[docs] def register_wbc_tasks( tasks: WbcTaskConfig | Iterable[WbcTaskConfig], ) -> None: """Add tasks to the WBC task table; register with mjlab when bootstrap is done.""" if not _REGISTERING: _load_builtin_tasks() batch = (tasks,) if isinstance(tasks, WbcTaskConfig) else tuple(tasks) for task in batch: _TASK_BY_ID[task.task_id] = task if _TASKS_REGISTERED and not _REGISTERING: for task in batch: if task.task_id not in _MJLAB_REGISTERED: register_wbc_task(task)
[docs] def register_all_wbc_tasks() -> None: """Register every known WBC task into mjlab (idempotent).""" global _TASKS_REGISTERED, _REGISTERING if _TASKS_REGISTERED or _REGISTERING: return _REGISTERING = True try: _load_builtin_tasks() from wbc_mjlab.robots.env import _register_g1 _register_g1() for task_id in sorted(_TASK_BY_ID): if task_id not in _MJLAB_REGISTERED: register_wbc_task(_TASK_BY_ID[task_id]) _TASKS_REGISTERED = True finally: _REGISTERING = False
[docs] def resolve_task_id( robot_id: str | None = None, *, task: str | None = None, no_state_estimation: bool = False, ) -> str: """Pick mjlab task id from ``--task``, legacy alias, or robot + NoSE flag.""" if task is not None: return get_task_config(task).task_id rid = resolve_robot_id(robot_id or "g1") if no_state_estimation: return f"Wbc-{rid.upper()}-Zest" return f"Wbc-{rid.upper()}"
def robot_id_for_run( *, task_id: str, robot_id: str | None = None, robot_explicit: bool = False, ) -> str: """Return robot id for data paths; default comes from the task config.""" task = get_task_config(task_id) if robot_explicit: rid = resolve_robot_id(robot_id or "g1") if task.robot_id != rid: raise ValueError( f"Task {task.task_id!r} is for robot {task.robot_id!r}, " f"but --robot is {rid!r}." ) return rid return task.robot_id
[docs] def prepare_wbc_run(*, task_id: str) -> str: """Register tasks and record the active robot/task for this CLI invocation.""" import mjlab # noqa: F401 — load mjlab.tasks entry points (external robots) register_all_wbc_tasks() task = get_task_config(task_id) _set_active_run(task.robot_id, task.task_id) return task.task_id