From f5a3bd4567e6975499b8d9ffaa5c44a82970353c Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 23 Jul 2026 16:03:15 -0700 Subject: [PATCH 1/8] Add SceneEntityCfg.from_stable for warp cfg adaptation Let the warp frontend adapt a stable manager-based configuration onto the warp runtime without duplicating scene-entity configs. --- source/isaaclab/changelog.d/warp-manager-bridge.rst | 8 ++++++++ source/isaaclab/isaaclab/managers/manager_term_cfg.py | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 source/isaaclab/changelog.d/warp-manager-bridge.rst diff --git a/source/isaaclab/changelog.d/warp-manager-bridge.rst b/source/isaaclab/changelog.d/warp-manager-bridge.rst new file mode 100644 index 000000000000..8aa615a5294a --- /dev/null +++ b/source/isaaclab/changelog.d/warp-manager-bridge.rst @@ -0,0 +1,8 @@ +Changed +^^^^^^^ + +* Relaxed ``func`` annotation on :class:`~isaaclab.managers.ObservationTermCfg`, + :class:`~isaaclab.managers.RewardTermCfg`, and + :class:`~isaaclab.managers.TerminationTermCfg` to ``Callable[..., torch.Tensor | None]`` + so kernel-style ``func(env, out) -> None`` terms type-check alongside the + existing torch return-tensor form. diff --git a/source/isaaclab/isaaclab/managers/manager_term_cfg.py b/source/isaaclab/isaaclab/managers/manager_term_cfg.py index 29e2281c3c60..3fad3881679d 100644 --- a/source/isaaclab/isaaclab/managers/manager_term_cfg.py +++ b/source/isaaclab/isaaclab/managers/manager_term_cfg.py @@ -152,7 +152,7 @@ class CurriculumTermCfg(ManagerTermBaseCfg): class ObservationTermCfg(ManagerTermBaseCfg): """Configuration for an observation term.""" - func: Callable[..., torch.Tensor] = MISSING + func: Callable[..., torch.Tensor | None] = MISSING """The name of the function to be called. This function should take the environment object and any other parameters @@ -335,7 +335,7 @@ class EventTermCfg(ManagerTermBaseCfg): class RewardTermCfg(ManagerTermBaseCfg): """Configuration for a reward term.""" - func: Callable[..., torch.Tensor] = MISSING + func: Callable[..., torch.Tensor | None] = MISSING """The name of the function to be called. This function should take the environment object and any other parameters @@ -363,7 +363,7 @@ class RewardTermCfg(ManagerTermBaseCfg): class TerminationTermCfg(ManagerTermBaseCfg): """Configuration for a termination term.""" - func: Callable[..., torch.Tensor] = MISSING + func: Callable[..., torch.Tensor | None] = MISSING """The name of the function to be called. This function should take the environment object and any other parameters From 81c0baa93a181bb29e822f1f5787c2e192ce74a1 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 23 Jul 2026 16:03:17 -0700 Subject: [PATCH 2/8] Add the WarpFrontend bridge for --frontend warp Introduce the WarpFrontend adapter that runs stable task ids on the warp runtime: it adapts a stable manager-based cfg in place (swapping each MDP term for its warp twin, resolved by name on the mirrored experimental package), resolves direct env classes by name-based mirror, and converts observation-noise cfgs to their warp-native twins. Adds the experimental warp managers, environments, and noise utilities. --- .../changelog.d/warp-manager-bridge.rst | 47 ++ .../envs/direct_rl_env_warp.py | 26 +- .../isaaclab_experimental/envs/frontend.py | 615 ++++++++++++++++++ .../envs/manager_based_rl_env_warp.py | 14 +- .../isaaclab_experimental/envs/mdp/events.py | 93 +++ .../envs/mdp/observations.py | 44 ++ .../managers/action_manager.py | 2 +- .../managers/command_manager.py | 3 +- .../managers/event_manager.py | 3 +- .../managers/manager_base.py | 14 +- .../managers/manager_term_cfg.py | 117 +--- .../managers/observation_manager.py | 25 +- .../managers/reward_manager.py | 12 +- .../managers/scene_entity_cfg.py | 9 + .../managers/termination_manager.py | 3 +- .../test/envs/test_frontend.py | 591 +++++++++++++++++ .../test/envs/test_frontend_cfg_conversion.py | 200 ++++++ 17 files changed, 1695 insertions(+), 123 deletions(-) create mode 100644 source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst create mode 100644 source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py create mode 100644 source/isaaclab_experimental/test/envs/test_frontend.py create mode 100644 source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py diff --git a/source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst b/source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst new file mode 100644 index 000000000000..a8df6918733a --- /dev/null +++ b/source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst @@ -0,0 +1,47 @@ +Added +^^^^^ + +* Added :mod:`isaaclab_experimental.envs.frontend` runtime selector and + :meth:`isaaclab_experimental.managers.SceneEntityCfg.from_stable` used by + ``--frontend=warp`` to adapt stable cfgs onto the warp runtime. +* Added name-based resolution of direct-task warp env classes: ``--frontend=warp`` + resolves a stable direct task's ``_direct_env:Env`` to the mirrored + ``_warp_env:WarpEnv`` and constructs it with the stable cfg. No + parallel registration is needed; a ``warp_entry_point`` kwarg is honored as an + optional override for classes that cannot follow the convention. +* Added observation-noise conversion to the warp frontend: stable noise cfgs + (constant/uniform/gaussian) swap to their warp-native twins during cfg + adaptation; cfgs without a twin (class-based noise models, customized or + tensor-valued parameters) are a hard error instead of being silently + ignored. The Warp observation manager rejects non-warp noise cfgs and + plumbs the shared per-env RNG state to function-style noise kernels. +* Added warp adapters for the stable + :class:`~isaaclab.envs.mdp.events.randomize_rigid_body_material` and + :class:`~isaaclab.envs.mdp.events.randomize_rigid_body_mass` startup event + terms, so tasks using them run under ``--frontend=warp`` with randomization + active. +* Added deterministic warp twin resolution: a stable symbol's twin is looked + up on the mirrored experimental package tree (``isaaclab.* ↔ + isaaclab_experimental.*``, ``isaaclab_tasks.* ↔ + isaaclab_tasks_experimental.*``); missing twins are collected and reported + in one failure. No registration is needed. + +Changed +^^^^^^^ + +* Changed :class:`~isaaclab_experimental.envs.ManagerBasedRLEnvWarp` to adapt + its configuration in ``__init__`` via + :meth:`~isaaclab_experimental.envs.frontend.WarpFrontend.adapt_cfg`, so + registered warp task variants can derive from stable configurations instead + of duplicating them. +* Changed the experimental warp ``RewardManager`` to merge dictionaries + returned by class-based reward term ``reset()`` into its episode-log extras + (persistent Warp buffer views, CUDA-graph safe); used to report + ``Metrics/success_rate`` under ``--frontend warp``. + +Fixed +^^^^^ + +* Fixed :class:`~isaaclab_experimental.envs.ManagerBasedRLEnvWarp` to probe + :meth:`~isaaclab.sim.SimulationContext.has_active_visualizers` after the + ``get_setting`` API was reshaped. diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/direct_rl_env_warp.py b/source/isaaclab_experimental/isaaclab_experimental/envs/direct_rl_env_warp.py index d6ba77e78c92..025feab3d930 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/envs/direct_rl_env_warp.py +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/direct_rl_env_warp.py @@ -27,15 +27,15 @@ from isaaclab.envs.common import VecEnvObs, VecEnvStepReturn from isaaclab.envs.direct_rl_env import DirectRLEnv from isaaclab.envs.direct_rl_env_cfg import DirectRLEnvCfg +from isaaclab.envs.ui import ViewportCameraController from isaaclab.envs.utils.spaces import sample_space, spec_to_gym_space - -# from isaaclab.envs.ui import ViewportCameraController from isaaclab.managers import EventManager from isaaclab.sim import SimulationContext from isaaclab.sim.utils import use_stage from isaaclab.utils.noise import NoiseModel from isaaclab.utils.seed import configure_seed from isaaclab.utils.timer import Timer +from isaaclab.utils.version import has_kit from isaaclab_experimental.envs.interactive_scene_warp import InteractiveSceneWarp from isaaclab_experimental.utils.warp_graph_cache import WarpGraphCache @@ -168,15 +168,10 @@ def __init__(self, cfg: DirectRLEnvCfg, render_mode: str | None = None, **kwargs # attach_stage_to_usd_context() print("[INFO]: Scene manager: ", self.scene) - # set up camera viewport controller - # viewport is not available in other rendering modes so the function will throw a warning - # FIXME: This needs to be fixed in the future when we unify the UI functionalities even for - # non-rendering modes. - has_gui = bool(self.sim.get_setting("/isaaclab/has_gui")) - offscreen_render = bool(self.sim.get_setting("/isaaclab/render/offscreen")) - if has_gui or offscreen_render: - # self.viewport_camera_controller = ViewportCameraController(self, self.cfg.viewer) - self.viewport_camera_controller = None + # Initialize when a Kit viewport exists. ViewportCameraController uses omni.kit + # (renderer camera); skip in kitless Newton-only runs where no Kit app is running. + if (self.sim.has_gui or self.sim.has_active_visualizers()) and has_kit(): + self.viewport_camera_controller = ViewportCameraController(self, self.cfg.viewer) else: self.viewport_camera_controller = None @@ -570,13 +565,8 @@ def render(self, recompute: bool = False) -> np.ndarray | None: if self.render_mode == "human" or self.render_mode is None: return None elif self.render_mode == "rgb_array": - # check that if any render could have happened - has_gui = bool(self.sim.get_setting("/isaaclab/has_gui")) - offscreen_render = bool(self.sim.get_setting("/isaaclab/render/offscreen")) - # Rendering is possible if we have GUI or offscreen rendering enabled - can_render = has_gui or offscreen_render - - if not can_render: + # rendering requires a GUI or offscreen rendering (mirrors the stable env) + if not (self.sim.has_gui or self.sim.has_offscreen_render): render_mode_name = "NO_GUI_OR_RENDERING" raise RuntimeError( f"Cannot render '{self.render_mode}' when the simulation render mode is" diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py new file mode 100644 index 000000000000..29f9aefcaa59 --- /dev/null +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py @@ -0,0 +1,615 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Warp runtime frontend for IsaacLab tasks (``--frontend warp``). + +:class:`WarpFrontend` encapsulates everything the warp runtime needs from a +stable task definition: + +* Manager-based tasks: adapt the stable cfg in place (Newton physics check, + :class:`SceneEntityCfg` promotion, MDP twin swap) and construct + :class:`ManagerBasedRLEnvWarp`. All warp MDP twins (functions *and* classes) + must exist for the chosen task — a missing twin is a hard failure, not a + silent drop. +* Direct tasks: construct the warp env class the stable registration declares + via its ``warp_entry_point`` kwarg, sharing the stable cfg. + +Twin resolution is purely mechanical: the experimental packages mirror the +stable tree (``isaaclab.* ↔ isaaclab_experimental.*``, +``isaaclab_tasks.* ↔ isaaclab_tasks_experimental.*``), and a stable symbol's +twin is looked up by name on the nearest ``.mdp`` package of the mirrored +path. There is no registration; anything unresolved is collected and reported +in one hard failure. The torch runtime never touches this module — the shared +RL CLI dispatches to plain ``gym.make`` before importing it, so this +(optional) package stays warp-only. +""" + +from __future__ import annotations + +import importlib +import importlib.util +import logging +from collections.abc import Iterable, Iterator +from enum import StrEnum +from types import ModuleType +from typing import Any, ClassVar + +import gymnasium as gym +import torch + +from isaaclab.envs import DirectMARLEnvCfg, DirectRLEnvCfg, ManagerBasedRLEnvCfg +from isaaclab.managers.scene_entity_cfg import SceneEntityCfg as _StableSceneEntityCfg + +logger = logging.getLogger(__name__) + + +__all__ = [ + "FrontendIncompatibleError", + "WarpFrontend", + "Workflow", +] + + +class Workflow(StrEnum): + """Manager-based vs direct env workflow.""" + + MANAGER_BASED = "manager_based" + DIRECT = "direct" + + +class FrontendIncompatibleError(RuntimeError): + """Raised when the warp frontend can't run the requested task.""" + + +class WarpFrontend: + """Builds environments on the warp runtime from stable task definitions. + + The class is a stateless namespace: twin resolution is a pure function of + the stable module paths and the mirrored package tree (:attr:`MIRROR_ROOTS`); + there is no registry and no import-order dependence. + """ + + # Stable package roots mirrored by their warp implementation packages. Twin + # resolution is purely mechanical: mirror the root, then look up the nearest + # enclosing ``.mdp`` package on the mirrored path. + MIRROR_ROOTS: ClassVar[dict[str, str]] = { + "isaaclab_tasks": "isaaclab_tasks_experimental", + "isaaclab": "isaaclab_experimental", + } + + # Prefixes that identify warp-origin symbols and registrations. + WARP_ROOT_PREFIXES: ClassVar[tuple[str, ...]] = tuple(MIRROR_ROOTS.values()) + + # Top-level cfg groups whose managers run warp-first. Only terms under these + # are adapted (SceneEntityCfg promotion + MDP twin swap). The event manager + # is warp-first too — it invokes term funcs with a Warp env-mask, so a stable + # event func (which expects torch ``env_ids``) breaks at runtime; its funcs + # must be swapped to warp twins. The curriculum, recorder and command + # managers run on the stable (torch) implementation, so their terms are left + # untouched. A stable term left on a warp manager would break, so a missing + # twin in these groups is a hard error; a stable term on a stable manager is + # correct, so those groups are skipped. + WARP_MANAGED_GROUPS: ClassVar[frozenset[str]] = frozenset( + {"observations", "rewards", "terminations", "actions", "events"} + ) + + # ------------------------------------------------------------------ + # Env construction + # ------------------------------------------------------------------ + + @classmethod + def build_env(cls, env_cfg: Any, task_id: str, **construct_kwargs: Any) -> gym.Env: + """Construct the warp env for ``task_id`` from a stable env cfg. + + Args: + env_cfg: Stable env cfg. Manager-based cfgs are mutated in place. + task_id: Gym registration id, e.g. ``"Isaac-Cartpole"``. + **construct_kwargs: Forwarded to the env constructor (``render_mode``, …). + + Raises: + FrontendIncompatibleError: If the warp runtime can't run the task + (wrong physics, missing MDP twins, direct task without a warp + implementation). + """ + if cls._detect_workflow(env_cfg) is Workflow.DIRECT: + return cls._build_direct_env(env_cfg, task_id, **construct_kwargs) + return cls._build_manager_env(env_cfg, **construct_kwargs) + + @classmethod + def _build_manager_env(cls, env_cfg: Any, **construct_kwargs: Any) -> gym.Env: + """Construct :class:`ManagerBasedRLEnvWarp`; the env adapts the cfg itself. + + Imported lazily so that ``--frontend=torch`` callers don't pay the + ``isaaclab_experimental.envs`` import cost. The env runs + :meth:`adapt_cfg` in ``__init__``, so stable cfgs and warp-native cfgs + take the same path. + """ + from isaaclab_experimental.envs import ManagerBasedRLEnvWarp + + return ManagerBasedRLEnvWarp(cfg=env_cfg, **construct_kwargs) + + @classmethod + def _build_direct_env(cls, env_cfg: Any, task_id: str, **construct_kwargs: Any) -> gym.Env: + """Construct a direct warp env. + + Direct workflows aren't cfg-adapted: a hand-written warp env class + implements the task. Preferred path: the stable registration declares + it via ``warp_entry_point`` and shares the stable cfg. Fallback: the + task itself is registered under the warp packages (warp-native cfg). + """ + env_class = cls._resolve_direct_warp_class(task_id) + if env_class is None: + # No warp_entry_point: the task itself must be a warp-native registration. + cls._assert_direct_warp_registration(task_id) + return gym.make(task_id, cfg=env_cfg, **construct_kwargs) + # Declared warp class + stable cfg: swap only the env class. + cls._require_newton_physics(env_cfg, type(env_cfg).__name__) + return env_class(cfg=env_cfg, **construct_kwargs) + + # ------------------------------------------------------------------ + # Cfg adaptation (manager-based) + # ------------------------------------------------------------------ + + @classmethod + def adapt_cfg(cls, cfg: Any) -> None: + """Mutate a stable manager-based ``cfg`` in place for the warp managers. + + Idempotent: re-running on an already-adapted cfg is a no-op (the steps + below skip warp-origin symbols and already-promoted entities). + + Three steps, each independently testable: + + 1. :meth:`_require_newton_physics` — hard check that ``cfg.sim.physics`` + is :class:`~isaaclab_newton.physics.NewtonCfg`. The user is + responsible for selecting the Newton variant of the task's + :class:`PresetCfg` via ``presets=newton_mjwarp``; we don't auto-inject. + 2. :meth:`_promote_scene_entity_cfgs` — replace stable + :class:`~isaaclab.managers.SceneEntityCfg` instances under each + term's ``params`` with the warp variant (which adds warp-cached + ``joint_mask``, ``joint_ids_wp``, ``body_ids_wp`` fields). + 3. :meth:`_swap_mdp` — for every MDP term found anywhere in the cfg tree + (discovered by :meth:`_walk_terms` via :class:`ManagerTermBaseCfg` + subclassing, not by hard-coded attribute names), replace any stable + ``func`` *or* ``class_type`` with its same-named warp twin. A missing + twin raises :class:`FrontendIncompatibleError` — partial coverage is + unsafe under the warp managers' kernel-only signature. + """ + label = type(cfg).__name__ + cls._require_newton_physics(cfg, label) + cls._promote_scene_entity_cfgs(cfg) + cls._swap_mdp(cfg, label) + cls._swap_noise_cfgs(cfg, label) + + @staticmethod + def _require_newton_physics(cfg: Any, label: str) -> None: + """Block unless ``cfg.sim.physics`` is :class:`NewtonCfg`. + + The warp managers' assets read state through :class:`NewtonManager`; + a :class:`PhysxCfg` (or unresolved :class:`PresetCfg`) is a hard + incompatibility. The fix is to pass ``presets=newton_mjwarp`` on the CLI + so Hydra resolves the task's :class:`PresetCfg` wrapper to the Newton + field before construction. + """ + from isaaclab_newton.physics import NewtonCfg + + physics = getattr(getattr(cfg, "sim", None), "physics", None) + if isinstance(physics, NewtonCfg): + return + raise FrontendIncompatibleError( + f"warp env {label!r}: expected cfg.sim.physics to be NewtonCfg," + f" got {type(physics).__name__!r}. Pass `presets=newton_mjwarp` on the CLI so" + f" Hydra resolves the task's PresetCfg wrapper to the Newton variant." + ) + + @classmethod + def _promote_scene_entity_cfgs(cls, cfg: Any) -> None: + """Replace stable :class:`SceneEntityCfg` instances with the warp variant. + + Iterates every term cfg in the tree (via :meth:`_walk_terms`) and + rebuilds any stable :class:`SceneEntityCfg` value under ``term.params`` + through :meth:`isaaclab_experimental.managers.SceneEntityCfg.from_stable`. + The warp variant subclasses the stable one, so type checks elsewhere + stay valid; the new fields (``joint_mask`` / ``joint_ids_wp`` / + ``body_ids_wp``) are filled at :meth:`resolve` time by the warp scene. + """ + from isaaclab_experimental.managers.scene_entity_cfg import SceneEntityCfg as _WarpSceneEntityCfg + + promoted: list[str] = [] + for path, term in cls._walk_terms(cfg): + if not path or path[0] not in cls.WARP_MANAGED_GROUPS: + continue # term runs on a stable manager; keep the stable entity + params = getattr(term, "params", None) + if not isinstance(params, dict): + continue + for key, value in list(params.items()): + # Skip non-entities and already-promoted values (idempotence). + if isinstance(value, _WarpSceneEntityCfg) or not isinstance(value, _StableSceneEntityCfg): + continue + params[key] = _WarpSceneEntityCfg.from_stable(value) + promoted.append(f"{'.'.join(path)}.params[{key!r}] ({value.name!r})") + if promoted: + logger.info( + "frontend.warp: promoted %d SceneEntityCfg instance(s) to warp variant:\n %s", + len(promoted), + "\n ".join(promoted), + ) + + @classmethod + def _swap_mdp(cls, cfg: Any, label: str) -> None: + """Replace ``term.func`` and ``term.class_type`` with their warp twins. + + Iterates every term cfg in the tree (via :meth:`_walk_terms`) and on + each term swaps whichever of ``func`` / ``class_type`` is a + stable-origin callable. Twin lookup is name-based, in the order given by + :meth:`_twin_modules`: the warp mirror of the cfg's own task MDP + namespace first (task twins win, even for symbols defined in core + packages), then the mirror of the symbol's defining package (covers + terms borrowed from another task family, e.g. the Ant task reusing + Humanoid MDP terms), then the shared fallback. Any missing twin raises + :class:`FrontendIncompatibleError` listing every affected term — + partial swaps would leave torch-style callables in the cfg and the warp + managers would call them with the wrong signature. + + The warp-side declarations (``out_dim``, ``axes``, ``observation_type``) + that the warp managers need at init are *not* supplied by this swap; + they travel with the warp twin function itself via its own + ``@generic_io_descriptor_warp(out_dim=…)`` decorator. This method only + substitutes the callable; the manager reads the new func's annotations + when it parses the term cfg. + """ + # Highest-priority twin source: warp mirrors of the cfg's own task namespace. + cfg_route_modules = cls._cfg_route_modules(cfg) + module_cache: dict[str, list[ModuleType]] = {} # defining module -> modules to search + searched: set[str] = set() # module names, for the error message + + swapped = 0 + missing: list[tuple[str, str, str]] = [] # (location, attr, symbol) + for path, term in cls._walk_terms(cfg): + if not path or path[0] not in cls.WARP_MANAGED_GROUPS: + continue # term runs on a stable manager; leave it stable + location = ".".join(path) + for attr in ("func", "class_type"): # a term implements via either attr + stable = getattr(term, attr, None) + if stable is None or not cls._is_swap_candidate(stable): + continue + origin = getattr(stable, "__module__", "") or "" + if origin not in module_cache: + module_cache[origin] = cls._twin_modules(origin, cfg_route_modules) + searched.update(m.__name__ for m in module_cache[origin]) + twin = cls._resolve_warp_twin(stable.__name__, module_cache[origin]) + if twin is None: + missing.append((location, attr, stable.__name__)) # collect every miss; report once below + continue + setattr(term, attr, twin) + swapped += 1 + + if missing: + lines = "\n ".join(f"{loc}.{attr}: no warp twin for {sym!r}" for loc, attr, sym in missing) + raise FrontendIncompatibleError( + f"warp env {label!r}: missing warp MDP twins (searched {sorted(searched)}):\n {lines}" + ) + + logger.info("frontend.warp: swapped %d MDP symbol(s) to warp twins", swapped) + + # ------------------------------------------------------------------ + # Workflow detection and direct-task resolution + # ------------------------------------------------------------------ + + @staticmethod + def _detect_workflow(cfg: Any) -> Workflow: + """Classify the env cfg into manager-based or direct (used to pick the build path). + + Note: + The env cfg roots (ManagerBasedRLEnvCfg, DirectRLEnvCfg, + DirectMARLEnvCfg) do not share a common base class. When a new cfg + root is added, extend the isinstance tuples below. + """ + if isinstance(cfg, ManagerBasedRLEnvCfg): + return Workflow.MANAGER_BASED + if isinstance(cfg, (DirectRLEnvCfg, DirectMARLEnvCfg)): + return Workflow.DIRECT + raise FrontendIncompatibleError( + f"Unrecognised env cfg type {type(cfg).__name__!r};" + f" expected ManagerBasedRLEnvCfg / DirectRLEnvCfg / DirectMARLEnvCfg subclass." + ) + + @classmethod + def _resolve_direct_warp_class(cls, task_id: str) -> type | None: + """Return the warp env class for a stable direct task, or ``None``. + + Resolution order: + + 1. An explicit ``warp_entry_point`` kwarg on the stable registration + (``"module.path:ClassName"``), if present. This is an override for + env classes that cannot follow the naming convention below. + 2. Name-based mirror: the stable env entry point + ``isaaclab_tasks.<...>._direct_env:Env`` resolves to + ``isaaclab_tasks_experimental.<...>._warp_env:WarpEnv`` + on the mirrored experimental package. A direct warp env is discovered + by following the convention — no per-task registration needed. + + The class is constructed with the *stable* cfg, so the task needs no + parallel warp registration or duplicated configuration. + """ + try: + spec = gym.spec(task_id) + except gym.error.NameNotFound as exc: + raise FrontendIncompatibleError( + f"--frontend=warp: task {task_id!r} is not registered with gymnasium." + ) from exc + target = (spec.kwargs or {}).get("warp_entry_point") + if isinstance(target, str): + module_name, _, class_name = target.partition(":") + try: + module = importlib.import_module(module_name) + except ImportError as exc: + raise FrontendIncompatibleError( + f"--frontend=warp: task {task_id!r} declares warp_entry_point {target!r}," + f" but it failed to import: {exc}" + ) from exc + env_class = getattr(module, class_name, None) + if env_class is None: + raise FrontendIncompatibleError( + f"--frontend=warp: task {task_id!r} declares warp_entry_point {target!r}," + f" but {class_name!r} is not defined in {module_name!r}." + ) + return env_class + return cls._mirror_direct_warp_class(spec.entry_point) + + @classmethod + def _mirror_direct_warp_class(cls, entry_point: Any) -> type | None: + """Resolve a stable direct env entry point to its warp twin by convention. + + ``isaaclab_tasks.<...>._direct_env:Env`` mirrors to + ``isaaclab_tasks_experimental.<...>._warp_env:WarpEnv``. + Returns ``None`` when the entry point does not follow the convention or + the twin is absent (the caller then reports the task as unsupported). + """ + if not isinstance(entry_point, str) or ":" not in entry_point: + return None + stable_module, _, stable_class = entry_point.partition(":") + warp_module = cls._mirror_module(stable_module) + if warp_module is None or not warp_module.endswith("_direct_env") or not stable_class.endswith("Env"): + return None + warp_module = f"{warp_module[: -len('_direct_env')]}_warp_env" + warp_class = f"{stable_class[: -len('Env')]}WarpEnv" + try: + module = importlib.import_module(warp_module) + except ImportError: + return None + env_class = getattr(module, warp_class, None) + origin = getattr(env_class, "__module__", "") or "" + if env_class is None or not origin.startswith(cls.WARP_ROOT_PREFIXES): + return None + return env_class + + @classmethod + def _assert_direct_warp_registration(cls, task_id: str) -> None: + """For direct workflows without a ``warp_entry_point``, the task must be warp-registered.""" + spec = gym.spec(task_id) + ep = spec.entry_point + module = ep if isinstance(ep, str) else (getattr(ep, "__module__", "") or "") + if not module.startswith(cls.WARP_ROOT_PREFIXES): + raise FrontendIncompatibleError( + f"--frontend=warp on direct task {task_id!r}: entry_point {ep!r}" + f" is not under {list(cls.WARP_ROOT_PREFIXES)}. Direct tasks must either" + f" declare a `warp_entry_point` in their stable registration or be" + f" registered as a warp env class." + ) + + # ------------------------------------------------------------------ + # Twin resolution + # ------------------------------------------------------------------ + + @classmethod + def _mirror_module(cls, module: str) -> str | None: + """Mechanical mirror of a stable module path, or ``None`` if not mirrored.""" + if not isinstance(module, str) or not module: + return None + root, _, rest = module.partition(".") # only the top-level package root is swapped + mirror_root = cls.MIRROR_ROOTS.get(root) + if mirror_root is None: + return None + return f"{mirror_root}.{rest}" if rest else mirror_root + + @staticmethod + def _nearest_mdp_module(mirrored: str) -> str | None: + """Deepest existing ``.mdp`` package on a mirrored module path. + + If the path already contains an ``.mdp`` segment, the search starts at + that boundary (``...velocity.mdp.rewards`` resolves to + ``...velocity.mdp``); otherwise the path is walked upward until a + mirrored ``.mdp`` package exists. Existence is probed with + :func:`importlib.util.find_spec`; a prefix that does not exist simply + ends that probe, so resolution is a pure function of the installed + package tree. + """ + parts = mirrored.split(".") + if "mdp" in parts: + parts = parts[: parts.index("mdp")] # start the walk at the .mdp boundary + for depth in range(len(parts), 0, -1): # deepest prefix first + candidate = ".".join([*parts[:depth], "mdp"]) + try: + if importlib.util.find_spec(candidate) is not None: + return candidate + except ModuleNotFoundError: + continue + return None + + @classmethod + def _swap_noise_cfgs(cls, cfg: Any, label: str) -> None: + """Replace stable observation-noise cfgs with their warp-native twins. + + The warp observation manager applies noise through the experimental noise + cfg family (in-place Warp kernels); a stable noise cfg would otherwise be + silently ignored, training against a different MDP. Twins resolve by class + name; data fields are copied while ``func`` keeps the twin's warp default. + Noise cfgs without a warp twin (e.g. class-based ``NoiseModelCfg``) are a + hard error — silently changing the MDP is never acceptable. + """ + import dataclasses + + from isaaclab.utils import noise as stable_noise + + from isaaclab_experimental.utils import noise as warp_noise + + unsupported: list[str] = [] + swapped = 0 + for path, term in cls._walk_terms(cfg): + if not path or path[0] != "observations": + continue + noise_cfg = getattr(term, "noise", None) + if noise_cfg is None: + continue + if type(noise_cfg).__module__.startswith(cls.WARP_ROOT_PREFIXES): + continue # already warp-native + twin_cls = getattr(warp_noise, type(noise_cfg).__name__, None) + if ( + twin_cls is None + or not twin_cls.__module__.startswith(cls.WARP_ROOT_PREFIXES) + or not isinstance(noise_cfg, stable_noise.NoiseCfg) + ): + unsupported.append(f"{'.'.join(path)}: {type(noise_cfg).__name__}") + continue + if noise_cfg.func != type(noise_cfg)().func: + # A user-customized noise func has no warp twin; replacing it with the + # twin's default would silently change the MDP. + unsupported.append(f"{'.'.join(path)}: {type(noise_cfg).__name__} with custom func {noise_cfg.func!r}") + continue + fields = {f.name: getattr(noise_cfg, f.name) for f in dataclasses.fields(noise_cfg) if f.name != "func"} + if any(isinstance(value, torch.Tensor) for value in fields.values()): + # The warp noise kernels take scalar parameters; tensor-valued ranges + # (per-element noise) have no warp twin yet. + unsupported.append(f"{'.'.join(path)}: {type(noise_cfg).__name__} with tensor-valued parameters") + continue + term.noise = twin_cls(**fields) + swapped += 1 + if unsupported: + raise FrontendIncompatibleError( + f"warp env {label!r}: observation noise cfgs without warp twins:\n " + "\n ".join(unsupported) + ) + if swapped: + logger.info("frontend.warp: swapped %d observation noise cfg(s) to warp twins", swapped) + + @staticmethod + def _import_twin_module(target: str) -> ModuleType: + """Import a resolved twin module; a module that exists but breaks is a hard error.""" + try: + return importlib.import_module(target) + except ImportError as exc: + raise FrontendIncompatibleError(f"warp twin module {target!r} exists but failed to import: {exc}") from exc + + @classmethod + def _cfg_route_modules(cls, cfg: Any) -> list[ModuleType]: + """Warp MDP mirrors of the cfg's class hierarchy, subclass first. + + A stable cfg consumes terms through its task's ``mdp`` namespace, which + re-exports symbols that may be *defined* in core or shared packages. The + warp mirror of that namespace is therefore the primary place to resolve + twins; it is found by mirroring the modules of ``type(cfg).__mro__`` — + the subclass module first (robot-specific cfgs), then base-cfg modules + (task-family bases). + """ + modules: list[ModuleType] = [] + for klass in type(cfg).__mro__: # subclass first: robot cfg wins over task-family base + mirrored = cls._mirror_module(getattr(klass, "__module__", "") or "") + if mirrored is None: + continue + target = cls._nearest_mdp_module(mirrored) + if target is None: + continue + module = cls._import_twin_module(target) + if module not in modules: + modules.append(module) + return modules + + @classmethod + def _twin_modules(cls, symbol_module: str, cfg_route_modules: list[ModuleType]) -> list[ModuleType]: + """Warp modules to consult for a stable symbol's twin, in preference order. + + 1. The warp mirrors of the cfg's own task MDP namespace + (:meth:`_cfg_route_modules`) — task-specific twins win, including + twins for symbols defined in core/shared packages. + 2. The mirror of the symbol's defining package — covers terms borrowed + from another task family and, for core-defined symbols, the shared + :mod:`isaaclab_experimental.envs.mdp` twins (the mirror of + :mod:`isaaclab.envs.mdp`). + """ + modules = list(cfg_route_modules) + # Fallback: the mirror of the package that defines the symbol. + mirrored = cls._mirror_module(symbol_module) + if mirrored is not None: + target = cls._nearest_mdp_module(mirrored) + if target is not None: + module = cls._import_twin_module(target) + if module not in modules: + modules.append(module) + return modules + + @classmethod + def _resolve_warp_twin(cls, name: str, modules: Iterable[ModuleType]) -> Any | None: + """Return the same-named symbol from ``modules`` that originates under the warp packages.""" + for module in modules: + candidate = getattr(module, name, None) + if candidate is None: + continue + origin = getattr(candidate, "__module__", "") or "" + if origin.startswith(cls.WARP_ROOT_PREFIXES): # re-exported stable symbols don't count + return candidate + return None + + @classmethod + def _is_swap_candidate(cls, value: Any) -> bool: + """Heuristic: callable or class whose origin is *not already* warp.""" + if not callable(value): + return False + origin = getattr(value, "__module__", "") or "" + if origin.startswith(cls.WARP_ROOT_PREFIXES): + return False # already a warp twin (idempotent) + return True + + @staticmethod + def _walk_terms(node: Any, path: tuple[str, ...] = ()) -> Iterator[tuple[tuple[str, ...], Any]]: + """Yield ``(path, term)`` for every MDP term cfg in the cfg tree. + + A "term" is a :class:`ManagerTermBaseCfg` (observation/reward/ + termination/event/curriculum) *or* an :class:`ActionTermCfg` — the + latter is a separate base that is **not** a ``ManagerTermBaseCfg`` + subclass, yet carries a swappable ``class_type``, so it must be matched + explicitly. + + Behavior at each node: + + * Match (a term cfg instance): yield ``(path, node)`` and stop — do not + descend into ``term.params`` / ``term.func`` / ``term.class_type``. + * Configclass: don't yield; recurse into every non-underscore *instance* + attribute (``vars(node)``), extending the path. ``observations``, + ``rewards``, ``events``, ``actions``, sub-groups like + ``observations.policy``, and anything nested deeper are reached + transparently. + * Anything else (plain Python data, callables, non-configclass objects): + stop. No yield, no recursion. + + Iterating the instance ``__dict__`` mirrors how the warp managers + consume group cfgs (they iterate ``cfg.__dict__.items()``), so the + walker sees exactly the terms the managers will see — including terms + assigned in ``__post_init__`` — while never descending into methods or + nested class objects, which live on the class rather than the instance. + + Driven entirely by type — no attribute names are hardcoded — so future + cfg layouts (extra observation groups, new nesting, etc.) are picked up + automatically as long as their terms subclass one of the term base cfgs. + """ + from isaaclab.managers.manager_term_cfg import ActionTermCfg, ManagerTermBaseCfg + + if isinstance(node, (ManagerTermBaseCfg, ActionTermCfg)): + yield path, node # a term: yield and stop; never descend into params/func + return + if not hasattr(node, "__dataclass_fields__"): + return # plain data / callables: not a cfg subtree + for name, value in vars(node).items(): # instance attrs — the same view the managers iterate + if name.startswith("_") or value is None: + continue + yield from WarpFrontend._walk_terms(value, path + (name,)) diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/manager_based_rl_env_warp.py b/source/isaaclab_experimental/isaaclab_experimental/envs/manager_based_rl_env_warp.py index bdce3853040c..8f4f83267d4c 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/envs/manager_based_rl_env_warp.py +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/manager_based_rl_env_warp.py @@ -93,6 +93,14 @@ def __init__(self, cfg: ManagerBasedRLEnvCfg, render_mode: str | None = None, ** render_mode: The render mode for the environment. Defaults to None, which is similar to ``"human"``. """ + # Adapt the cfg for the warp managers (Newton physics check, SceneEntityCfg + # promotion, MDP twin swap). Idempotent: a warp-native cfg passes through + # unchanged, and a stable-derived cfg (``--frontend=warp`` or a registered + # warp task variant subclassing a stable cfg) is adapted in place. + from isaaclab_experimental.envs.frontend import WarpFrontend + + WarpFrontend.adapt_cfg(cfg) + # -- counter for curriculum self.common_step_counter = 0 @@ -408,10 +416,8 @@ def render(self, recompute: bool = False) -> np.ndarray | None: if self.render_mode == "human" or self.render_mode is None: return None elif self.render_mode == "rgb_array": - # check that if any render could have happened - has_gui = bool(self.sim.get_setting("/isaaclab/has_gui")) - offscreen_render = bool(self.sim.get_setting("/isaaclab/render/offscreen")) - if not (has_gui or offscreen_render): + # rendering requires a GUI or offscreen rendering (mirrors the stable env) + if not (self.sim.has_gui or self.sim.has_offscreen_render): raise RuntimeError( f"Cannot render '{self.render_mode}' when the simulation render mode does not support" " rendering. Please set the simulation render mode to 'PARTIAL_RENDERING' or" diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/events.py b/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/events.py index d9e61041a7ad..90e5696a6b10 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/events.py +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/events.py @@ -26,13 +26,19 @@ from typing import TYPE_CHECKING +import torch import warp as wp +from isaaclab.envs.mdp.events import randomize_rigid_body_mass as _StableRandomizeRigidBodyMass +from isaaclab.envs.mdp.events import randomize_rigid_body_material as _StableRandomizeRigidBodyMaterial + +from isaaclab_experimental.managers import ManagerTermBase as _WarpManagerTermBase from isaaclab_experimental.managers import SceneEntityCfg from isaaclab_experimental.utils.warp import WarpCapturable if TYPE_CHECKING: from isaaclab.assets import Articulation + from isaaclab.envs import ManagerBasedEnv # --------------------------------------------------------------------------- # Randomize rigid body center of mass @@ -585,3 +591,90 @@ def reset_joints_by_scale( # Sync derived buffers (_previous_joint_vel, joint_acc) for reset envs. asset.write_joint_position_to_sim_mask(position=asset.data.joint_pos.warp, env_mask=env_mask) asset.write_joint_velocity_to_sim_mask(velocity=asset.data.joint_vel.warp, env_mask=env_mask) + + +# --------------------------------------------------------------------------- +# Startup-mode randomization (mask-signature adapters) +# --------------------------------------------------------------------------- +# +# The stable material/mass randomization terms already dispatch to the active +# physics backend (Newton included); only their calling convention differs — +# the warp EventManager invokes terms with a Warp env-mask while the stable +# class terms expect torch env indices. Both terms run in ``startup`` mode, +# once at construction and outside any captured path, so a host-side +# mask-to-ids conversion is acceptable. + + +def _mask_to_env_ids(env_mask: wp.array) -> torch.Tensor: + """Convert a Warp boolean env-mask to the torch index tensor the stable terms expect.""" + return torch.nonzero(wp.to_torch(env_mask), as_tuple=False).squeeze(-1) + + +class randomize_rigid_body_material(_StableRandomizeRigidBodyMaterial, _WarpManagerTermBase): + """Warp adapter for the stable, backend-dispatched material randomization term. + + Converts the warp event manager's env-mask calling convention to the stable + term's env-ids convention and delegates. Startup mode only. Inherits the + warp :class:`ManagerTermBase` so the warp managers accept it as a class term. + """ + + def __call__( + self, + env: ManagerBasedEnv, + env_mask: wp.array, + static_friction_range: tuple[float, float], + dynamic_friction_range: tuple[float, float], + restitution_range: tuple[float, float], + num_buckets: int, + asset_cfg: SceneEntityCfg, + make_consistent: bool = False, + ) -> None: + super().__call__( + env, + _mask_to_env_ids(env_mask), + static_friction_range, + dynamic_friction_range, + restitution_range, + num_buckets, + asset_cfg, + make_consistent, + ) + + def reset(self, env_mask: wp.array | None = None) -> None: + """Nothing to reset; materials are randomized once at startup.""" + return + + +class randomize_rigid_body_mass(_StableRandomizeRigidBodyMass, _WarpManagerTermBase): + """Warp adapter for the stable, backend-dispatched mass randomization term. + + Converts the warp event manager's env-mask calling convention to the stable + term's env-ids convention and delegates. Startup mode only. Inherits the + warp :class:`ManagerTermBase` so the warp managers accept it as a class term. + """ + + def __call__( + self, + env: ManagerBasedEnv, + env_mask: wp.array, + asset_cfg: SceneEntityCfg, + mass_distribution_params: tuple[float, float], + operation: str, + distribution: str = "uniform", + recompute_inertia: bool = True, + min_mass: float = 1e-6, + ) -> None: + super().__call__( + env, + _mask_to_env_ids(env_mask), + asset_cfg, + mass_distribution_params, + operation, + distribution, + recompute_inertia, + min_mass, + ) + + def reset(self, env_mask: wp.array | None = None) -> None: + """Nothing to reset; masses are randomized once at startup.""" + return diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/observations.py b/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/observations.py index 808231c8faf6..4bc49cc1e2ae 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/observations.py +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/mdp/observations.py @@ -367,3 +367,47 @@ def generated_commands(env: ManagerBasedEnv, out, command_name: str) -> None: fn._cmd_name = command_name fn._is_warmed_up = True wp.copy(out, fn._cmd_wp) + + +""" +Sensors. +""" + + +@wp.kernel +def _body_incoming_wrench_kernel( + force: wp.array(dtype=wp.vec3f, ndim=2), + torque: wp.array(dtype=wp.vec3f, ndim=2), + body_ids: wp.array(dtype=wp.int32), + out: wp.array(dtype=wp.float32, ndim=2), +): + env_id = wp.tid() + for k in range(body_ids.shape[0]): + b = body_ids[k] + f = force[env_id, b] + t = torque[env_id, b] + out[env_id, k * 6 + 0] = f[0] + out[env_id, k * 6 + 1] = f[1] + out[env_id, k * 6 + 2] = f[2] + out[env_id, k * 6 + 3] = t[0] + out[env_id, k * 6 + 4] = t[1] + out[env_id, k * 6 + 5] = t[2] + + +@generic_io_descriptor_warp(out_dim="body:6", observation_type="BodyState", on_inspect=[record_shape, record_dtype]) +def body_incoming_wrench(env: ManagerBasedEnv, out, sensor_cfg: SceneEntityCfg) -> None: + """Incoming spatial wrench [N, N·m] on selected bodies, laid out per body as ``[fx, fy, fz, tx, ty, tz]``. + + Warp-first override of :func:`isaaclab.envs.mdp.observations.body_incoming_wrench`. Reads the + Newton joint-wrench sensor and gathers the force/torque of the bodies selected by ``sensor_cfg`` + into ``out`` (shape ``(num_envs, 6 * num_selected_bodies)``). + """ + sensor = env.scene.sensors[sensor_cfg.name] + if sensor.data.force is None or sensor.data.torque is None: + raise RuntimeError("Joint wrench sensor data is not initialized. Call sim.reset() before reading observations.") + wp.launch( + kernel=_body_incoming_wrench_kernel, + dim=env.num_envs, + inputs=[sensor.data.force.warp, sensor.data.torque.warp, sensor_cfg.body_ids_wp, out], + device=env.device, + ) diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/action_manager.py b/source/isaaclab_experimental/isaaclab_experimental/managers/action_manager.py index 672af82bc8ab..902c74d6d095 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/action_manager.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/action_manager.py @@ -20,9 +20,9 @@ from isaaclab.assets import AssetBase from isaaclab.envs.utils.io_descriptors import GenericActionIODescriptor +from isaaclab.managers.manager_term_cfg import ActionTermCfg from .manager_base import ManagerBase, ManagerTermBase -from .manager_term_cfg import ActionTermCfg if TYPE_CHECKING: from isaaclab.envs import ManagerBasedEnv diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/command_manager.py b/source/isaaclab_experimental/isaaclab_experimental/managers/command_manager.py index 8b4e8f83dbe7..dfafeef5e206 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/command_manager.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/command_manager.py @@ -17,10 +17,11 @@ import warp as wp from prettytable import PrettyTable +from isaaclab.managers.manager_term_cfg import CommandTermCfg + from isaaclab_experimental.utils.warp.kernels import compute_reset_scale, count_masked from .manager_base import ManagerBase, ManagerTermBase -from .manager_term_cfg import CommandTermCfg # import omni.kit.app diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/event_manager.py b/source/isaaclab_experimental/isaaclab_experimental/managers/event_manager.py index ab8a0264ea9b..757d9a24d547 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/event_manager.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/event_manager.py @@ -29,8 +29,9 @@ import warp as wp from prettytable import PrettyTable +from isaaclab.managers.manager_term_cfg import EventTermCfg + from .manager_base import ManagerBase -from .manager_term_cfg import EventTermCfg logger = logging.getLogger(__name__) diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/manager_base.py b/source/isaaclab_experimental/isaaclab_experimental/managers/manager_base.py index 6caea3eebcbb..7954800d87c9 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/manager_base.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/manager_base.py @@ -26,17 +26,19 @@ import warp as wp import isaaclab.utils.string as string_utils +from isaaclab.managers.manager_term_cfg import ManagerTermBaseCfg from isaaclab.utils import class_to_dict, string_to_callable from isaaclab_experimental.utils.warp import is_warp_capturable -from .manager_term_cfg import ManagerTermBaseCfg from .scene_entity_cfg import SceneEntityCfg # import omni.timeline if TYPE_CHECKING: + import torch + from isaaclab.envs import ManagerBasedEnv # import logger @@ -107,14 +109,20 @@ def __name__(self) -> str: Operations. """ - def reset(self, env_mask: wp.array | None = None) -> None: + def reset(self, env_mask: wp.array | None = None) -> dict[str, torch.Tensor] | None: """Resets the manager term (mask-based). Args: env_mask: Boolean mask of shape (num_envs,) indicating which envs to reset. If None, all envs are considered. + + Returns: + An optional dictionary of logging values to merge into the owning manager's + reset extras. To stay CUDA-graph capturable, values must be persistent + tensor views over device buffers written by kernels (no host readback); + the same objects must be returned on every call. """ - pass + return None def serialize(self) -> dict: """General serialization call. Includes the configuration dict.""" diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/manager_term_cfg.py b/source/isaaclab_experimental/isaaclab_experimental/managers/manager_term_cfg.py index ce354be66a2a..1c71ff78e51e 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/manager_term_cfg.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/manager_term_cfg.py @@ -3,92 +3,37 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Configuration terms for different managers (experimental, Warp-first). +"""Stable manager term configuration classes used by the Warp-first managers. -This module is a passthrough to :mod:`isaaclab.managers.manager_term_cfg` except for -the following term configs which are overridden for Warp-first execution: - -- :class:`ObservationTermCfg` -- :class:`RewardTermCfg` -- :class:`TerminationTermCfg` +The Warp manager implementations accept the same configuration shapes as the +stable managers. Re-exporting the stable classes preserves type identity so a +stable task configuration can be adapted in place without rebuilding every +term. At runtime, the adapted term callables still use the Warp-first +``func(env, out, **params) -> None`` signature. """ -from __future__ import annotations - -from collections.abc import Callable -from dataclasses import MISSING - -from isaaclab.managers.manager_term_cfg import * # noqa: F401,F403 -from isaaclab.managers.manager_term_cfg import ManagerTermBaseCfg as _ManagerTermBaseCfg -from isaaclab.utils.configclass import configclass - - -@configclass -class RewardTermCfg(_ManagerTermBaseCfg): - """Configuration for a reward term. - - The function is expected to write the (unweighted) reward values into a - pre-allocated Warp buffer provided by the manager. - - Expected signature: - - - ``func(env, out, **params) -> None`` - - where ``out`` is a Warp array of shape ``(num_envs,)`` with float32 dtype. - """ - - func: Callable[..., None] = MISSING - """The function to be called to fill the pre-allocated reward buffer.""" - - weight: float = MISSING - """The weight of the reward term.""" - - -@configclass -class TerminationTermCfg(_ManagerTermBaseCfg): - """Configuration for a termination term (experimental, Warp-first). - - The function is expected to write termination flags into a pre-allocated Warp buffer. - - Expected signature: - - - ``func(env, out, **params) -> None`` - - where ``out`` is a Warp array of shape ``(num_envs,)`` with boolean dtype. - """ - - func: Callable[..., None] = MISSING - """The function to be called to fill the pre-allocated termination buffer.""" - - time_out: bool = False - """Whether the termination term contributes towards episodic timeouts. Defaults to False.""" - - -@configclass -class ObservationTermCfg(_ManagerTermBaseCfg): - """Configuration for an observation term (experimental, Warp-first). - - The function is expected to write observation values into a pre-allocated Warp buffer provided - by the observation manager. - - Expected signature: - - - ``func(env, out, **params) -> None`` - - where ``out`` is a Warp array of shape ``(num_envs, obs_term_dim)`` with float32 dtype. - - Notes: - - The stable fields (noise/modifiers/history) are kept for config compatibility, but the - experimental Warp-first observation manager may not support all of them initially. - """ - - func: Callable[..., None] = MISSING - """The function to be called to fill the pre-allocated observation buffer.""" - - # Keep stable configuration fields for compatibility with existing task configs. - modifiers: list[ModifierCfg] | None = None # noqa: F405 - noise: NoiseCfg | NoiseModelCfg | None = None # noqa: F405 - clip: tuple[float, float] | None = None - scale: tuple[float, ...] | float | None = None - history_length: int = 0 - flatten_history_dim: bool = True +from isaaclab.managers.manager_term_cfg import ( + ActionTermCfg, + CommandTermCfg, + CurriculumTermCfg, + EventTermCfg, + ManagerTermBaseCfg, + ObservationGroupCfg, + ObservationTermCfg, + RecorderTermCfg, + RewardTermCfg, + TerminationTermCfg, +) + +__all__ = [ + "ActionTermCfg", + "CommandTermCfg", + "CurriculumTermCfg", + "EventTermCfg", + "ManagerTermBaseCfg", + "ObservationGroupCfg", + "ObservationTermCfg", + "RecorderTermCfg", + "RewardTermCfg", + "TerminationTermCfg", +] diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/observation_manager.py b/source/isaaclab_experimental/isaaclab_experimental/managers/observation_manager.py index 74d0a9a955be..8adb74ff8da3 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/observation_manager.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/observation_manager.py @@ -55,6 +55,7 @@ import warp as wp from prettytable import PrettyTable +from isaaclab.managers.manager_term_cfg import ObservationGroupCfg, ObservationTermCfg from isaaclab.utils import class_to_dict from isaaclab_experimental.utils import modifiers, noise @@ -62,7 +63,6 @@ from isaaclab_experimental.utils.torch_utils import clone_obs_buffer from .manager_base import ManagerBase, ManagerTermBase -from .manager_term_cfg import ObservationGroupCfg, ObservationTermCfg if TYPE_CHECKING: from isaaclab.envs import ManagerBasedEnv @@ -679,6 +679,16 @@ def _prepare_terms(self): # noqa: C901 # check noise settings if not group_cfg.enable_corruption: term_cfg.noise = None + elif term_cfg.noise is not None and not type(term_cfg.noise).__module__.startswith( + "isaaclab_experimental." + ): + raise TypeError( + f"Observation term '{term_name}' uses noise cfg" + f" '{type(term_cfg.noise).__module__}.{type(term_cfg.noise).__name__}', which the Warp" + " observation manager would silently ignore. Use the warp-native noise cfgs from" + " isaaclab_experimental.utils.noise (the warp frontend converts stable cfgs" + " automatically)." + ) # check group history params and override terms if group_cfg.history_length is not None: term_cfg.history_length = group_cfg.history_length @@ -760,6 +770,10 @@ def _prepare_terms(self): # noqa: C901 f" and optional parameters: {args_with_defaults}, but received: {term_params}." ) + # plumb the shared per-env RNG state so Warp noise kernels can consume it + # (function-style NoiseCfg kernels read it off the cfg at call time) + if term_cfg.noise is not None and isinstance(term_cfg.noise, noise.NoiseCfg): + term_cfg.noise.rng_state_wp = self._env.rng_state_wp # prepare noise model classes if term_cfg.noise is not None and isinstance(term_cfg.noise, noise.NoiseModelCfg): # plumb the shared per-env RNG state so Warp noise kernels can consume it @@ -922,11 +936,12 @@ def _resolve_out_dim(self, out_dim: int | str, term_cfg: ObservationTermCfg) -> if isinstance(out_dim, str) and out_dim.startswith("body:"): per_body = int(out_dim.split(":")[1]) - asset_cfg = term_cfg.params.get("asset_cfg") - body_ids = getattr(asset_cfg, "body_ids", None) + # Body selection may live on an asset (articulation) or a sensor entity. + entity_cfg = term_cfg.params.get("asset_cfg") or term_cfg.params.get("sensor_cfg") + body_ids = getattr(entity_cfg, "body_ids", None) if body_ids is None or body_ids == slice(None): - asset = self._env.scene[asset_cfg.name] - return per_body * len(asset.body_names) + entity = self._env.scene[entity_cfg.name] + return per_body * len(entity.body_names) return per_body * len(body_ids) if out_dim == "command": diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/reward_manager.py b/source/isaaclab_experimental/isaaclab_experimental/managers/reward_manager.py index 67dcbc055c2f..7dd6b85bd6ed 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/reward_manager.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/reward_manager.py @@ -18,10 +18,11 @@ import warp as wp from prettytable import PrettyTable +from isaaclab.managers.manager_term_cfg import RewardTermCfg + from isaaclab_experimental.utils.warp.kernels import compute_reset_scale, count_masked from .manager_base import ManagerBase, ManagerTermBase -from .manager_term_cfg import RewardTermCfg if TYPE_CHECKING: from isaaclab.envs import ManagerBasedRLEnv @@ -271,9 +272,14 @@ def reset( device=self.device, ) - # reset all the reward terms + # reset all the reward terms; class terms may expose logging values as + # persistent tensor views (see :meth:`ManagerTermBase.reset`), which are + # merged into the manager's reset extras. Merging the same views again on + # subsequent resets is a no-op, so this stays capture-safe. for term_cfg in self._class_term_cfgs: - term_cfg.func.reset(env_mask=env_mask) + term_extras = term_cfg.func.reset(env_mask=env_mask) + if term_extras: + self._reset_extras.update(term_extras) return self._reset_extras diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/scene_entity_cfg.py b/source/isaaclab_experimental/isaaclab_experimental/managers/scene_entity_cfg.py index ae97f7f526fb..f6d30489eeca 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/scene_entity_cfg.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/scene_entity_cfg.py @@ -38,6 +38,15 @@ class SceneEntityCfg(_SceneEntityCfg): """Integer indices of selected bodies — used for subset-sized body gathers.""" body_ids_wp: wp.array | None = None + @classmethod + def from_stable(cls, stable: _SceneEntityCfg) -> SceneEntityCfg: + """Build a warp scene-entity cfg from a stable one. + + Copies every field declared on the stable cfg; the warp-specific fields + stay ``None`` and are filled by :meth:`resolve` at scene build time. + """ + return cls(**{name: getattr(stable, name) for name in _SceneEntityCfg.__dataclass_fields__}) + def resolve(self, scene: InteractiveScene): # run the stable resolution first (fills joint_ids/body_ids from names/regex) super().resolve(scene) diff --git a/source/isaaclab_experimental/isaaclab_experimental/managers/termination_manager.py b/source/isaaclab_experimental/isaaclab_experimental/managers/termination_manager.py index 8a768651b292..732edf9f2fe2 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/managers/termination_manager.py +++ b/source/isaaclab_experimental/isaaclab_experimental/managers/termination_manager.py @@ -22,8 +22,9 @@ import warp as wp from prettytable import PrettyTable +from isaaclab.managers.manager_term_cfg import TerminationTermCfg + from .manager_base import ManagerBase, ManagerTermBase -from .manager_term_cfg import TerminationTermCfg if TYPE_CHECKING: from isaaclab.envs import ManagerBasedRLEnv diff --git a/source/isaaclab_experimental/test/envs/test_frontend.py b/source/isaaclab_experimental/test/envs/test_frontend.py new file mode 100644 index 000000000000..d0dbc90fafe0 --- /dev/null +++ b/source/isaaclab_experimental/test/envs/test_frontend.py @@ -0,0 +1,591 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Tests for :mod:`isaaclab_experimental.envs.frontend`. + +Pure-Python unit tests; no app launch. Covers: + +* :class:`Workflow` enum surface. +* :func:`SceneEntityCfg.from_stable` field copy. +* :func:`_require_newton_physics` hard-check. +* :func:`_walk_terms` recursive ManagerTermBaseCfg discovery over instance + attributes (nested class objects are not descended into). +* :meth:`_mirror_module` / :meth:`_nearest_mdp_module` deterministic mirror + resolution and :meth:`_cfg_route_modules` cfg-hierarchy resolution. +* :func:`_promote_scene_entity_cfgs` walks ``term.params`` dicts. +* :func:`_swap_mdp` swaps ``func`` *and* ``class_type``; raises with a path + list when twins are missing. +* :func:`_resolve_warp_twin` rejects stable-origin re-exports. +* :func:`_assert_direct_warp_registration` accepts warp-rooted entry + points and rejects stable ones. +""" + +from __future__ import annotations + +import contextlib +import types +from typing import Any +from unittest.mock import patch + +import gymnasium as gym +import isaaclab_experimental.envs.frontend as fe +import pytest +from isaaclab_experimental.envs.frontend import ( + FrontendIncompatibleError, + WarpFrontend, + Workflow, +) +from isaaclab_experimental.managers.scene_entity_cfg import SceneEntityCfg as WarpSceneEntityCfg +from isaaclab_newton.physics import NewtonCfg +from isaaclab_physx.physics import PhysxCfg + +from isaaclab.envs import ManagerBasedRLEnvCfg +from isaaclab.managers.manager_term_cfg import EventTermCfg, ObservationTermCfg, RewardTermCfg +from isaaclab.managers.scene_entity_cfg import SceneEntityCfg as StableSceneEntityCfg +from isaaclab.utils.configclass import configclass + +# ====================================================================== +# Fixtures: fake stable/warp symbols and configclass trees. +# +# These mirror the real cfg shape so :func:`_walk_terms` descends into them +# (it only descends into objects with ``__dataclass_fields__``). Term cfgs +# use the real :class:`EventTermCfg`/:class:`RewardTermCfg`/:class:`ObservationTermCfg` +# so the walker's ``isinstance(ManagerTermBaseCfg)`` discriminator yields them. +# ====================================================================== + + +def _stable_func(env, **params): + return None + + +class _StableActionCls: + pass + + +_stable_func.__module__ = "isaaclab_tasks.fake_task.mdp" +_StableActionCls.__module__ = "isaaclab_tasks.fake_task.mdp" + + +def _warp_twin_func(env, out, **params): + return None + + +class _WarpActionCls: + pass + + +_warp_twin_func.__module__ = "isaaclab_experimental.envs.mdp" +_WarpActionCls.__module__ = "isaaclab_experimental.envs.mdp" + + +@configclass +class _PolicyObsGroup: + """Stand-in for a per-task ObservationsCfg sub-group (e.g. PolicyCfg).""" + + o1: ObservationTermCfg | None = None + o2: ObservationTermCfg | None = None + + +@configclass +class _ExtraObsGroup: + """A second obs group (named arbitrarily) to exercise multi-group walks.""" + + o3: ObservationTermCfg | None = None + + +@configclass +class _ObservationsCfg: + policy: _PolicyObsGroup | None = None + perception: _ExtraObsGroup | None = None + + +@configclass +class _RewardsCfg: + r1: RewardTermCfg | None = None + r2: RewardTermCfg | None = None + + +@configclass +class _EventsCfg: + e1: EventTermCfg | None = None + + +@configclass +class _CurriculumCfg: + c1: EventTermCfg | None = None + + +@configclass +class _CfgFixture: + observations: _ObservationsCfg | None = None + rewards: _RewardsCfg | None = None + events: _EventsCfg | None = None + curriculum: _CurriculumCfg | None = None + + +def _term(func=None, params: dict | None = None) -> RewardTermCfg: + """Cheap RewardTermCfg builder for tests; the cfg class is irrelevant for swap/walk logic.""" + return RewardTermCfg(func=func or _stable_func, weight=1.0, params=params or {}) + + +# ====================================================================== +# Enums +# ====================================================================== + + +def test_workflow_values(): + assert Workflow.MANAGER_BASED == "manager_based" + assert Workflow.DIRECT == "direct" + + +# ====================================================================== +# build() +# ====================================================================== + + +def test_warp_manager_build_constructs_warp_env_with_cfg(): + # Cfg adaptation happens inside ManagerBasedRLEnvWarp.__init__, so build() + # only selects the env class and forwards the cfg and construct kwargs. + import isaaclab_experimental.envs as envs + + cfg = object.__new__(ManagerBasedRLEnvCfg) + expected_env = object() + calls: list[tuple[Any, Any]] = [] + + def fake_env(*, cfg: Any, **kwargs: Any) -> Any: + calls.append((cfg, kwargs)) + return expected_env + + with patch.object(envs, "ManagerBasedRLEnvWarp", fake_env): + env = WarpFrontend.build_env(cfg, "Isaac-Test", render_mode="rgb_array") + + assert env is expected_env + assert calls == [(cfg, {"render_mode": "rgb_array"})] + + +# ====================================================================== +# SceneEntityCfg.from_stable +# ====================================================================== + + +def test_from_stable_copies_minimum_fields(): + stable = StableSceneEntityCfg(name="robot") + warp = WarpSceneEntityCfg.from_stable(stable) + assert isinstance(warp, WarpSceneEntityCfg) + assert warp.name == "robot" + # Warp-only fields stay None until :meth:`resolve` runs. + assert warp.joint_mask is None + assert warp.joint_ids_wp is None + assert warp.body_ids_wp is None + + +def test_from_stable_copies_all_selection_fields(): + stable = StableSceneEntityCfg( + name="robot", + joint_names=["lf_hip"], + joint_ids=[0, 1, 2], + fixed_tendon_names=["tendon_a"], + fixed_tendon_ids=[5], + body_names=["base", "lf_foot"], + body_ids=[0, 4], + object_collection_names=["objs"], + object_collection_ids=[7], + preserve_order=True, + ) + warp = WarpSceneEntityCfg.from_stable(stable) + for field in ( + "name", + "joint_names", + "joint_ids", + "fixed_tendon_names", + "fixed_tendon_ids", + "body_names", + "body_ids", + "object_collection_names", + "object_collection_ids", + "preserve_order", + ): + assert getattr(warp, field) == getattr(stable, field), f"field {field!r} mismatch" + + +# ====================================================================== +# _require_newton_physics +# ====================================================================== + + +def _cfg_with_physics(physics: Any) -> Any: + cfg = types.SimpleNamespace() + cfg.sim = types.SimpleNamespace(physics=physics) + return cfg + + +def test_require_newton_passes_for_newton(): + fe.WarpFrontend._require_newton_physics(_cfg_with_physics(NewtonCfg()), "Isaac-Test-v0") # no raise + + +def test_require_newton_rejects_physx(): + with pytest.raises(FrontendIncompatibleError) as exc: + fe.WarpFrontend._require_newton_physics(_cfg_with_physics(PhysxCfg()), "Isaac-Test-v0") + assert "presets=newton_mjwarp" in str(exc.value) + assert "PhysxCfg" in str(exc.value) + + +def test_require_newton_rejects_none(): + with pytest.raises(FrontendIncompatibleError): + fe.WarpFrontend._require_newton_physics(_cfg_with_physics(None), "Isaac-Test-v0") + + +# ====================================================================== +# _walk_terms +# ====================================================================== + + +def test_walk_terms_yields_each_term_with_its_path(): + cfg = _CfgFixture( + rewards=_RewardsCfg(r1=_term(), r2=_term()), + events=_EventsCfg(e1=EventTermCfg(func=_stable_func, mode="reset")), + ) + # Configclass instances aren't hashable, so collect paths only. + paths = {".".join(p) for p, _ in fe.WarpFrontend._walk_terms(cfg)} + assert paths == {"rewards.r1", "rewards.r2", "events.e1"} + + +def test_walk_terms_descends_into_obs_subgroups(): + cfg = _CfgFixture( + observations=_ObservationsCfg( + policy=_PolicyObsGroup(o1=ObservationTermCfg(func=_stable_func)), + perception=_ExtraObsGroup(o3=ObservationTermCfg(func=_stable_func)), + ), + ) + paths = {".".join(p) for p, _ in fe.WarpFrontend._walk_terms(cfg)} + # Discovery is purely type-driven; no obs group name is hardcoded. + assert paths == {"observations.policy.o1", "observations.perception.o3"} + + +def test_walk_terms_stops_at_terms(): + # The walker must not descend into term.params / term.func — yields the term itself. + nested_se_cfg = StableSceneEntityCfg(name="robot") + cfg = _CfgFixture(rewards=_RewardsCfg(r1=_term(params={"asset_cfg": nested_se_cfg}))) + terms = list(fe.WarpFrontend._walk_terms(cfg)) + assert len(terms) == 1 + _, term = terms[0] + assert isinstance(term, RewardTermCfg) + + +def test_walk_terms_skips_non_configclass_attrs(): + # A namespace without __dataclass_fields__ is not descended into. + cfg = types.SimpleNamespace(some_plain_attr="hello") + assert list(fe.WarpFrontend._walk_terms(cfg)) == [] + + +def test_walk_terms_skips_none_subtrees(): + cfg = _CfgFixture(rewards=None, events=None) + assert list(fe.WarpFrontend._walk_terms(cfg)) == [] + + +@configclass +class _GroupWithNestedClass: + """Mirrors the real ``ObservationsCfg.PolicyCfg`` nested-class pattern.""" + + @configclass + class TemplateCfg: + t1: ObservationTermCfg | None = None + + o1: ObservationTermCfg | None = None + + +def test_walk_terms_ignores_nested_class_objects(): + # The walker iterates instance attributes only (mirroring the warp managers' + # ``cfg.__dict__`` consumption), so a nested class object — reachable as an + # attribute on the instance — must never contribute paths. + cfg = _GroupWithNestedClass(o1=ObservationTermCfg(func=_stable_func)) + paths = {".".join(p) for p, _ in fe.WarpFrontend._walk_terms(cfg)} + assert paths == {"o1"} + + +# ====================================================================== +# Mirror resolution +# ====================================================================== + + +def test_mirror_module_maps_stable_roots(): + assert ( + fe.WarpFrontend._mirror_module("isaaclab_tasks.core.velocity.mdp.rewards") + == "isaaclab_tasks_experimental.core.velocity.mdp.rewards" + ) + assert fe.WarpFrontend._mirror_module("isaaclab.envs.mdp.events") == "isaaclab_experimental.envs.mdp.events" + assert fe.WarpFrontend._mirror_module("isaaclab_tasks") == "isaaclab_tasks_experimental" + # Unmirrored roots are not resolvable. + assert fe.WarpFrontend._mirror_module("torch.nn.functional") is None + assert fe.WarpFrontend._mirror_module("") is None + + +def test_nearest_mdp_module_truncates_at_mdp_boundary(): + # A symbol module resolves to its own mdp package on the mirrored path. + assert ( + fe.WarpFrontend._nearest_mdp_module("isaaclab_tasks_experimental.core.locomotion.mdp.rewards") + == "isaaclab_tasks_experimental.core.locomotion.mdp" + ) + assert fe.WarpFrontend._nearest_mdp_module("isaaclab_experimental.envs.mdp.events") == ( + "isaaclab_experimental.envs.mdp" + ) + + +def test_nearest_mdp_module_walks_up_cfg_paths(): + # A cfg module path walks up to the task family's mdp package. + assert ( + fe.WarpFrontend._nearest_mdp_module("isaaclab_tasks_experimental.core.velocity.config.anymal_d.flat_env_cfg") + == "isaaclab_tasks_experimental.core.velocity.mdp" + ) + # Nothing on the path provides an mdp package. + assert fe.WarpFrontend._nearest_mdp_module("isaaclab_tasks_experimental.nonexistent.task_pkg") is None + + +def test_cfg_route_modules_resolves_mirrored_hierarchy(): + # A class whose module mirrors into the experimental tree resolves to the + # task family's mdp package; unmirrored classes contribute nothing. + fixture_cls = type("FakeVelocityCfg", (), {}) + fixture_cls.__module__ = "isaaclab_tasks.core.velocity.config.anymal_d.flat_env_cfg" + modules = fe.WarpFrontend._cfg_route_modules(fixture_cls()) + assert [m.__name__ for m in modules] == ["isaaclab_tasks_experimental.core.velocity.mdp"] + + assert fe.WarpFrontend._cfg_route_modules(_CfgFixture()) == [] + + +# ====================================================================== +# _swap_mdp +# ====================================================================== + + +class _FakeMdpModule: + """Stand-in for a warp mdp module containing twins.""" + + __name__ = "test_fake_warp_mdp" + + +def _fake_mdp_module(name_to_symbol: dict[str, Any]) -> _FakeMdpModule: + module = _FakeMdpModule() + for name, symbol in name_to_symbol.items(): + setattr(module, name, symbol) + return module + + +def _patch_twin_modules(monkeypatch: pytest.MonkeyPatch, modules: list[Any]) -> None: + """Pin twin resolution to ``modules`` and keep unit tests hermetic.""" + monkeypatch.setattr(fe.WarpFrontend, "_cfg_route_modules", classmethod(lambda cls, cfg: [])) + monkeypatch.setattr( + fe.WarpFrontend, "_twin_modules", classmethod(lambda cls, symbol_module, cfg_route_modules: modules) + ) + + +def test_swap_mdp_swaps_func_and_class_type(monkeypatch: pytest.MonkeyPatch): + fake = _fake_mdp_module({"_stable_func": _warp_twin_func, "_StableActionCls": _WarpActionCls}) + _patch_twin_modules(monkeypatch, [fake]) + term_reward = _term(func=_stable_func) + term_action = _term() + term_action.class_type = _StableActionCls # set attr to exercise class_type swap + cfg = _CfgFixture(rewards=_RewardsCfg(r1=term_reward, r2=term_action)) + fe.WarpFrontend._swap_mdp(cfg, "Isaac-Test-v0") + assert cfg.rewards.r1.func is _warp_twin_func + assert cfg.rewards.r2.class_type is _WarpActionCls + + +def test_swap_mdp_missing_twin_raises_with_path_list(monkeypatch: pytest.MonkeyPatch): + _patch_twin_modules(monkeypatch, [_fake_mdp_module({})]) + cfg = _CfgFixture(rewards=_RewardsCfg(r1=_term(func=_stable_func))) + with pytest.raises(FrontendIncompatibleError) as exc: + fe.WarpFrontend._swap_mdp(cfg, "Isaac-Test-v0") + msg = str(exc.value) + assert "rewards.r1.func" in msg + assert "_stable_func" in msg + # The cfg term wasn't mutated for the missing twin. + assert cfg.rewards.r1.func is _stable_func + + +def test_swap_mdp_skips_already_warp(monkeypatch: pytest.MonkeyPatch): + _patch_twin_modules(monkeypatch, [_fake_mdp_module({})]) + cfg = _CfgFixture(rewards=_RewardsCfg(r1=_term(func=_warp_twin_func))) + fe.WarpFrontend._swap_mdp(cfg, "Isaac-Test-v0") # no raise + assert cfg.rewards.r1.func is _warp_twin_func + + +# ====================================================================== +# _promote_scene_entity_cfgs +# ====================================================================== + + +def test_promote_scene_entity_cfgs_promotes_in_params(): + cfg = _CfgFixture( + rewards=_RewardsCfg( + r1=_term(params={"asset_cfg": StableSceneEntityCfg(name="robot", joint_names=["lf_hip"]), "scale": 1.0}) + ) + ) + fe.WarpFrontend._promote_scene_entity_cfgs(cfg) + promoted = cfg.rewards.r1.params["asset_cfg"] + assert isinstance(promoted, WarpSceneEntityCfg) + assert promoted.name == "robot" + assert promoted.joint_names == ["lf_hip"] + # Non-SceneEntityCfg params are untouched. + assert cfg.rewards.r1.params["scale"] == 1.0 + + +def test_promote_scene_entity_cfgs_skips_already_warp(): + # configclass init deep-copies params, so identity won't hold across + # construction; what we actually want to assert is "no re-promotion": + # the asset_cfg remains a WarpSceneEntityCfg (i.e., wasn't passed + # back through `from_stable`). + warp = WarpSceneEntityCfg(name="robot", joint_names=["lf_hip"]) + cfg = _CfgFixture(rewards=_RewardsCfg(r1=_term(params={"asset_cfg": warp}))) + before = cfg.rewards.r1.params["asset_cfg"] + fe.WarpFrontend._promote_scene_entity_cfgs(cfg) + after = cfg.rewards.r1.params["asset_cfg"] + assert isinstance(after, WarpSceneEntityCfg) + # The asset_cfg object was not replaced by another from_stable call. + assert after is before + + +def test_promote_scene_entity_cfgs_walks_all_term_groups(): + cfg = _CfgFixture( + rewards=_RewardsCfg(r1=_term(params={"asset_cfg": StableSceneEntityCfg(name="r")})), + events=_EventsCfg( + e1=EventTermCfg(func=_stable_func, mode="reset", params={"asset_cfg": StableSceneEntityCfg(name="e")}) + ), + observations=_ObservationsCfg( + policy=_PolicyObsGroup( + o1=ObservationTermCfg(func=_stable_func, params={"asset_cfg": StableSceneEntityCfg(name="o-pol")}) + ), + perception=_ExtraObsGroup( + o3=ObservationTermCfg(func=_stable_func, params={"asset_cfg": StableSceneEntityCfg(name="o-per")}) + ), + ), + curriculum=_CurriculumCfg( + c1=EventTermCfg(func=_stable_func, mode="reset", params={"asset_cfg": StableSceneEntityCfg(name="c")}) + ), + ) + fe.WarpFrontend._promote_scene_entity_cfgs(cfg) + # Warp-managed groups (rewards, observations incl. sub-groups, events) are promoted. + assert isinstance(cfg.rewards.r1.params["asset_cfg"], WarpSceneEntityCfg) + assert isinstance(cfg.observations.policy.o1.params["asset_cfg"], WarpSceneEntityCfg) + # The perception sub-group is reached even though its attribute name is + # not hardcoded in the framework. + assert isinstance(cfg.observations.perception.o3.params["asset_cfg"], WarpSceneEntityCfg) + # The event manager is warp-first, so its terms are promoted too. + assert isinstance(cfg.events.e1.params["asset_cfg"], WarpSceneEntityCfg) + # Curriculum runs on the stable (torch) manager, so its SceneEntityCfg is + # left untouched — promoting it would hand a warp variant to a torch manager. + assert isinstance(cfg.curriculum.c1.params["asset_cfg"], StableSceneEntityCfg) + assert not isinstance(cfg.curriculum.c1.params["asset_cfg"], WarpSceneEntityCfg) + + +# ====================================================================== +# Twin resolution and swap-candidate heuristic +# ====================================================================== + + +def test_resolve_warp_twin_accepts_warp_origin(): + module = types.SimpleNamespace(foo=_warp_twin_func) + assert fe.WarpFrontend._resolve_warp_twin("foo", [module]) is _warp_twin_func + + +def test_resolve_warp_twin_rejects_stable_origin(): + module = types.SimpleNamespace(foo=_stable_func) # same name, stable origin + assert fe.WarpFrontend._resolve_warp_twin("foo", [module]) is None + + +def test_resolve_warp_twin_returns_none_when_absent(): + assert fe.WarpFrontend._resolve_warp_twin("missing", [types.SimpleNamespace()]) is None + + +def test_is_swap_candidate_stable_callable(): + assert fe.WarpFrontend._is_swap_candidate(_stable_func) + + +def test_is_swap_candidate_rejects_warp_callable(): + assert not fe.WarpFrontend._is_swap_candidate(_warp_twin_func) + + +def test_is_swap_candidate_rejects_non_callables(): + assert not fe.WarpFrontend._is_swap_candidate(42) + assert not fe.WarpFrontend._is_swap_candidate("string") + + +# ====================================================================== +# Direct workflow guard +# ====================================================================== + + +_DIRECT_TEST_TASKS = { + "_Frontend-Test-Warp-Direct-v0": ("isaaclab_tasks_experimental.fake:DirectEnv", None), + "_Frontend-Test-Stable-Direct-v0": ("isaaclab_tasks.fake:DirectEnv", None), + "_Frontend-Test-Declared-Direct-v0": ("isaaclab_tasks.fake:DirectEnv", "types:SimpleNamespace"), + "_Frontend-Test-Broken-Direct-v0": ("isaaclab_tasks.fake:DirectEnv", "definitely_not_a_module:Env"), + "_Frontend-Test-Missing-Class-Direct-v0": ("isaaclab_tasks.fake:DirectEnv", "types:NoSuchClass"), + # Follows the _direct_env:Env convention, so it resolves to the + # mirrored _warp_env:WarpEnv with no warp_entry_point. + "_Frontend-Test-NameMirror-Direct-v0": ("isaaclab_tasks.core.cartpole.cartpole_direct_env:CartpoleEnv", None), +} + + +@pytest.fixture(scope="module", autouse=True) +def _register_direct_stub_tasks(): + # Register stub tasks so ``gym.spec`` resolves them. Entry-point strings + # are never invoked (the guard only inspects them), so a fake import + # path is fine. ``warp_entry_point`` targets are imported, so the valid + # stub points at a real stdlib symbol. + registered = [] + for task_id, (entry_point, warp_entry_point) in _DIRECT_TEST_TASKS.items(): + kwargs = {"warp_entry_point": warp_entry_point} if warp_entry_point else {} + with contextlib.suppress(gym.error.Error): + gym.register(id=task_id, entry_point=entry_point, disable_env_checker=True, kwargs=kwargs) + registered.append(task_id) + yield + # Unregister so later test modules see the real, stub-free registry. + for task_id in registered: + gym.registry.pop(task_id, None) + + +def test_direct_guard_accepts_warp_rooted(): + fe.WarpFrontend._assert_direct_warp_registration("_Frontend-Test-Warp-Direct-v0") # no raise + + +def test_direct_guard_rejects_stable_rooted(): + with pytest.raises(FrontendIncompatibleError) as exc: + fe.WarpFrontend._assert_direct_warp_registration("_Frontend-Test-Stable-Direct-v0") + assert "isaaclab_experimental" in str(exc.value) + + +def test_resolve_direct_warp_class_returns_declared_class(): + import types as types_module + + env_class = fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-Declared-Direct-v0") + assert env_class is types_module.SimpleNamespace + + +def test_resolve_direct_warp_class_returns_none_without_declaration(): + assert fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-Stable-Direct-v0") is None + + +def test_resolve_direct_warp_class_by_name_mirror(): + """A direct task following the ``_direct_env:Env`` convention resolves + to ``_warp_env:WarpEnv`` with no ``warp_entry_point`` declared.""" + env_class = fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-NameMirror-Direct-v0") + assert env_class is not None + assert env_class.__name__ == "CartpoleWarpEnv" + assert env_class.__module__.endswith("cartpole_warp_env") + + +def test_resolve_direct_warp_class_rejects_broken_module(): + with pytest.raises(FrontendIncompatibleError): + fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-Broken-Direct-v0") + + +def test_resolve_direct_warp_class_rejects_missing_class(): + with pytest.raises(FrontendIncompatibleError): + fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-Missing-Class-Direct-v0") + + +def test_resolve_direct_warp_class_rejects_unknown_task(): + with pytest.raises(FrontendIncompatibleError): + fe.WarpFrontend._resolve_direct_warp_class("Frontend-Test-NotRegistered-v0") diff --git a/source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py b/source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py new file mode 100644 index 000000000000..76a50b6211ad --- /dev/null +++ b/source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py @@ -0,0 +1,200 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Coverage tests for warp task configuration adaptation. + +Two sweeps, both running :meth:`WarpFrontend.adapt_cfg` — the same adaptation +:class:`~isaaclab_experimental.envs.ManagerBasedRLEnvWarp` runs in its +``__init__``: + +* Every *stable* task id with declared warp twin coverage: resolve the + ``newton_mjwarp`` preset and adapt the stable cfg, exactly what + ``--frontend warp`` does. +* Every registered manager-based ``*-Warp-v0`` variant (velocity deltas whose + stable cfgs still contain terms without warp twins): load its env cfg, + resolve presets, and adapt. + +A dedicated stable Cartpole case also guards the stable-to-experimental +module routing used by ``--frontend warp``. +""" + +from __future__ import annotations + +import importlib + +import gymnasium as gym +import isaaclab_tasks_experimental # noqa: F401 +import pytest +from isaaclab_experimental.envs.frontend import WarpFrontend +from isaaclab_newton.physics import NewtonCfg + +# Registering the task packages is the whole point — import for side effects. +import isaaclab_tasks # noqa: F401 +from isaaclab_tasks.utils.hydra import resolve_presets + +# Stable task ids whose MDP terms are fully twinned: ``--frontend warp`` adapts +# their cfgs directly, with no parallel ``*-Warp-v0`` registration. Extend this +# list when a new task family gains full twin coverage. +_STABLE_TASKS_WITH_WARP_COVERAGE = [ + "Isaac-Ant", + "Isaac-Cartpole", + "Isaac-Humanoid", + "Isaac-Reach-Franka", + "Isaac-Reach-UR10", + "Isaac-Velocity-Flat-AnymalD", + "Isaac-Velocity-Flat-Cassie", + "Isaac-Velocity-Flat-G1", + "Isaac-Velocity-Flat-H1", + "Isaac-Velocity-Flat-UnitreeGo2", +] + +# Manager-based warp tasks are exactly those whose entry point is the shared warp +# env class; direct tasks provide their own env class (resolved by name-based +# mirror, or an explicit ``warp_entry_point`` override) and are not cfg-adapted, +# so they are excluded here. +_MANAGER_WARP_ENTRY_POINT = "isaaclab_experimental.envs:ManagerBasedRLEnvWarp" + +_WARP_ROOTS = ("isaaclab_experimental", "isaaclab_tasks_experimental") + + +def _manager_warp_tasks() -> list[tuple[str, str]]: + """Return ``(task_id, env_cfg_entry_point)`` for every manager-based warp task.""" + tasks: list[tuple[str, str]] = [] + for task_id, spec in gym.registry.items(): + if spec.entry_point != _MANAGER_WARP_ENTRY_POINT: + continue + cfg_entry = (spec.kwargs or {}).get("env_cfg_entry_point") + if isinstance(cfg_entry, str): + tasks.append((task_id, cfg_entry)) + return sorted(tasks) + + +def _load_adapted_cfg(cfg_entry_point: str): + """Instantiate an env cfg, resolve the Newton preset, and adapt it for warp.""" + module_path, class_name = cfg_entry_point.split(":") + cfg = getattr(importlib.import_module(module_path), class_name)() + cfg = resolve_presets(cfg, selected=("newton_mjwarp",)) + assert isinstance(cfg.sim.physics, NewtonCfg), "task does not provide a newton_mjwarp physics preset" + # Raises FrontendIncompatibleError if any warp-managed term lacks a warp twin. + WarpFrontend.adapt_cfg(cfg) + return cfg + + +def _cfg_entry_point(task_id: str) -> str: + cfg_entry = (gym.spec(task_id).kwargs or {}).get("env_cfg_entry_point") + assert isinstance(cfg_entry, str), f"{task_id}: no env_cfg_entry_point" + return cfg_entry + + +def test_no_manager_warp_variants_remain(): + """End-state pin: manager-based warp execution needs no parallel registrations.""" + assert _manager_warp_tasks() == [], "unexpected ManagerBasedRLEnvWarp registrations reappeared" + + +def test_no_warp_task_registrations_remain(): + """End-state pin: warp execution needs no parallel ``-Warp`` task ids at all.""" + warp_ids = sorted(task_id for task_id in gym.registry if "-Warp" in task_id) + assert warp_ids == [], f"unexpected warp task registrations: {warp_ids}" + + +@pytest.mark.parametrize("task_id", _STABLE_TASKS_WITH_WARP_COVERAGE, ids=_STABLE_TASKS_WITH_WARP_COVERAGE) +def test_stable_task_cfg_adapts_to_warp(task_id: str): + """Each covered stable task adapts without a missing twin (the --frontend warp path).""" + cfg = _load_adapted_cfg(_cfg_entry_point(task_id)) + + # Action terms carry a ``class_type`` (not a ``func``) and live on a base that + # is not a ManagerTermBaseCfg; guard that the adapter still swaps them to the + # warp ActionTerm, otherwise the warp ActionManager rejects them at runtime. + actions = getattr(cfg, "actions", None) + if actions is not None: + for name, term in vars(actions).items(): + class_type = getattr(term, "class_type", None) + if class_type is not None: + assert class_type.__module__.startswith(_WARP_ROOTS), ( + f"{task_id}: action term '{name}' class_type was not swapped to a warp twin" + f" (got {class_type.__module__}.{class_type.__name__})" + ) + + +_DIRECT_WARP_TASKS = [ + "Isaac-Cartpole-Direct", + "Isaac-Ant-Direct", + "Isaac-Humanoid-Direct", + "Isaac-Reorient-Cube-Allegro-Direct", +] + + +@pytest.mark.parametrize("task_id", _DIRECT_WARP_TASKS) +def test_direct_task_resolves_to_warp_env_class_by_name(task_id: str): + """Stable direct tasks resolve their warp env class by name-based mirror + resolution alone — no ``warp_entry_point`` declaration is needed.""" + from isaaclab_experimental.envs.frontend import WarpFrontend + + assert (gym.spec(task_id).kwargs or {}).get("warp_entry_point") is None + env_class = WarpFrontend._resolve_direct_warp_class(task_id) + assert isinstance(env_class, type), f"{task_id}: no warp env class resolved" + assert env_class.__module__.startswith(_WARP_ROOTS) + assert env_class.__name__.endswith("WarpEnv") + + +def test_stable_cartpole_cfg_adapts_to_current_warp_module_layout(): + """The stable Cartpole cfg resolves task-specific twins in the current package layout.""" + from isaaclab_experimental.managers.action_manager import ActionTerm + + cfg = _load_adapted_cfg(_cfg_entry_point("Isaac-Cartpole")) + + assert cfg.rewards.pole_pos.func.__module__.startswith("isaaclab_tasks_experimental.core.cartpole.mdp") + assert cfg.rewards.success_rate.func.__module__.startswith("isaaclab_tasks_experimental.core.cartpole.mdp") + assert issubclass(cfg.actions.joint_effort.class_type, ActionTerm) + + +def _iter_obs_terms(cfg): + """Yield (name, term cfg) for every observation term that carries a noise slot.""" + from isaaclab.managers.manager_term_cfg import ObservationGroupCfg + + for group in vars(cfg.observations).values(): + if not isinstance(group, ObservationGroupCfg): + continue + for name, term in vars(group).items(): + if hasattr(term, "noise"): + yield name, term + + +def test_stable_observation_noise_converts_to_warp_twins(): + """Stable noise cfgs (e.g. Unoise) become warp-native twins with the same parameters.""" + from isaaclab.utils import noise as stable_noise + + entry = _cfg_entry_point("Isaac-Velocity-Flat-UnitreeGo2") + module_path, class_name = entry.split(":") + raw = getattr(importlib.import_module(module_path), class_name)() + stable_params = { + name: (term.noise.n_min, term.noise.n_max) + for name, term in _iter_obs_terms(raw) + if isinstance(term.noise, stable_noise.UniformNoiseCfg) + } + assert stable_params, "expected uniform noise on the stable velocity observations" + + cfg = _load_adapted_cfg(entry) + converted = dict(_iter_obs_terms(cfg)) + for name, (n_min, n_max) in stable_params.items(): + twin = converted[name].noise + assert type(twin).__module__.startswith("isaaclab_experimental."), name + assert (twin.n_min, twin.n_max) == (n_min, n_max), name + + +def test_noise_cfg_without_warp_twin_is_a_hard_error(): + """Class-based noise models have no warp twin yet: adapting must fail loudly.""" + from isaaclab_experimental.envs.frontend import FrontendIncompatibleError + + from isaaclab.utils.noise import NoiseModelCfg, UniformNoiseCfg + + entry = _cfg_entry_point("Isaac-Velocity-Flat-UnitreeGo2") + module_path, class_name = entry.split(":") + cfg = getattr(importlib.import_module(module_path), class_name)() + cfg = resolve_presets(cfg, selected=("newton_mjwarp",)) + name, term = next(iter(_iter_obs_terms(cfg))) + term.noise = NoiseModelCfg(noise_cfg=UniformNoiseCfg()) + with pytest.raises(FrontendIncompatibleError, match="noise"): + WarpFrontend.adapt_cfg(cfg) From e7d663aac38ac263761defc9c9cb6bd3ce4de536 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 23 Jul 2026 16:03:19 -0700 Subject: [PATCH 3/8] Mirror the stable core task layout on the warp runtime Lay out isaaclab_tasks_experimental as a mirror of isaaclab_tasks.core and reuse the stable task configurations under --frontend warp. Direct warp envs follow the _warp_env / WarpEnv convention and resolve the per-physics-backend joint gears; the retired -Warp-v0 registrations are removed. --- .../changelog.d/warp-manager-bridge.minor.rst | 48 +++ .../{direct => core}/__init__.py | 6 +- .../core/cartpole/__init__.py | 12 + .../cartpole/cartpole_warp_env.py | 85 +++-- .../classic => core}/cartpole/mdp/__init__.py | 0 .../cartpole/mdp/__init__.pyi | 3 +- .../core/cartpole/mdp/rewards.py | 116 +++++++ .../core/locomotion/__init__.py | 12 + .../core/locomotion/ant/__init__.py | 14 + .../locomotion/ant/ant_warp_env.py} | 8 +- .../core/locomotion/humanoid/__init__.py | 14 + .../locomotion}/humanoid/humanoid_warp_env.py | 8 +- .../locomotion/locomotion_env_warp.py | 24 +- .../locomotion}/mdp/__init__.py | 0 .../locomotion}/mdp/__init__.pyi | 0 .../locomotion}/mdp/observations.py | 0 .../locomotion}/mdp/rewards.py | 6 + .../core/reach/__init__.py | 12 + .../reach/mdp/__init__.py | 0 .../reach/mdp/__init__.pyi | 0 .../reach/mdp/rewards.py | 3 + .../reach => core/reorient}/__init__.py | 2 +- .../reorient/reorient_warp_env.py} | 22 +- .../core/velocity/__init__.py | 13 + .../velocity/mdp/__init__.py | 0 .../velocity/mdp/__init__.pyi | 0 .../velocity/mdp/curriculums.py | 0 .../velocity/mdp/rewards.py | 6 + .../velocity/mdp/terminations.py | 1 + .../direct/allegro_hand/__init__.py | 29 -- .../allegro_hand/allegro_hand_warp_env_cfg.py | 135 -------- .../direct/ant/__init__.py | 28 -- .../direct/ant/ant_env_warp_cfg.py | 82 ----- .../direct/cartpole/__init__.py | 29 -- .../direct/cartpole/cartpole_warp_env_cfg.py | 66 ---- .../direct/humanoid/__init__.py | 28 -- .../direct/humanoid/humanoid_warp_env_cfg.py | 105 ------ .../direct/inhand_manipulation/__init__.py | 4 - .../direct/locomotion/__init__.py | 4 - .../manager_based/__init__.py | 10 - .../manager_based/classic/__init__.py | 12 - .../manager_based/classic/ant/__init__.py | 30 -- .../manager_based/classic/ant/ant_env_cfg.py | 197 ----------- .../classic/cartpole/__init__.py | 30 -- .../classic/cartpole/cartpole_env_cfg.py | 199 ----------- .../classic/cartpole/mdp/rewards.py | 45 --- .../classic/humanoid/__init__.py | 30 -- .../classic/humanoid/humanoid_env_cfg.py | 232 ------------- .../manager_based/locomotion/__init__.py | 6 - .../locomotion/velocity/__init__.py | 6 - .../locomotion/velocity/config/__init__.py | 9 - .../locomotion/velocity/config/a1/__init__.py | 25 -- .../velocity/config/a1/flat_env_cfg.py | 45 --- .../velocity/config/a1/rough_env_cfg.py | 67 ---- .../velocity/config/anymal_b/__init__.py | 25 -- .../velocity/config/anymal_b/flat_env_cfg.py | 45 --- .../velocity/config/anymal_b/rough_env_cfg.py | 18 - .../velocity/config/anymal_c/__init__.py | 26 -- .../velocity/config/anymal_c/flat_env_cfg.py | 42 --- .../velocity/config/anymal_c/rough_env_cfg.py | 24 -- .../velocity/config/anymal_d/__init__.py | 38 --- .../velocity/config/anymal_d/flat_env_cfg.py | 36 -- .../velocity/config/anymal_d/rough_env_cfg.py | 21 -- .../velocity/config/cassie/__init__.py | 24 -- .../velocity/config/cassie/flat_env_cfg.py | 37 --- .../velocity/config/cassie/rough_env_cfg.py | 80 ----- .../locomotion/velocity/config/g1/__init__.py | 36 -- .../velocity/config/g1/flat_env_cfg.py | 48 --- .../velocity/config/g1/rough_env_cfg.py | 159 --------- .../velocity/config/go1/__init__.py | 24 -- .../velocity/config/go1/flat_env_cfg.py | 36 -- .../velocity/config/go1/rough_env_cfg.py | 45 --- .../velocity/config/go2/__init__.py | 24 -- .../velocity/config/go2/flat_env_cfg.py | 36 -- .../velocity/config/go2/rough_env_cfg.py | 44 --- .../locomotion/velocity/config/h1/__init__.py | 35 -- .../velocity/config/h1/flat_env_cfg.py | 36 -- .../velocity/config/h1/rough_env_cfg.py | 112 ------- .../locomotion/velocity/velocity_env_cfg.py | 310 ------------------ .../manager_based/manipulation/__init__.py | 6 - .../manipulation/reach/config/__init__.py | 4 - .../reach/config/franka/__init__.py | 29 -- .../reach/config/franka/joint_pos_env_cfg.py | 62 ---- .../reach/config/ur_10/__init__.py | 24 -- .../reach/config/ur_10/joint_pos_env_cfg.py | 62 ---- .../manipulation/reach/reach_env_cfg.py | 206 ------------ 86 files changed, 370 insertions(+), 3252 deletions(-) create mode 100644 source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{direct => core}/__init__.py (68%) create mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/__init__.py rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{direct => core}/cartpole/cartpole_warp_env.py (68%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/classic => core}/cartpole/mdp/__init__.py (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/classic => core}/cartpole/mdp/__init__.pyi (82%) create mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/rewards.py create mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/__init__.py create mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/ant/__init__.py rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{direct/ant/ant_env_warp.py => core/locomotion/ant/ant_warp_env.py} (54%) create mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/humanoid/__init__.py rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{direct => core/locomotion}/humanoid/humanoid_warp_env.py (52%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{direct => core}/locomotion/locomotion_env_warp.py (91%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/classic/humanoid => core/locomotion}/mdp/__init__.py (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/classic/humanoid => core/locomotion}/mdp/__init__.pyi (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/classic/humanoid => core/locomotion}/mdp/observations.py (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/classic/humanoid => core/locomotion}/mdp/rewards.py (94%) create mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/__init__.py rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/manipulation => core}/reach/mdp/__init__.py (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/manipulation => core}/reach/mdp/__init__.pyi (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/manipulation => core}/reach/mdp/rewards.py (94%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/manipulation/reach => core/reorient}/__init__.py (75%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{direct/inhand_manipulation/inhand_manipulation_warp_env.py => core/reorient/reorient_warp_env.py} (95%) create mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/__init__.py rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/locomotion => core}/velocity/mdp/__init__.py (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/locomotion => core}/velocity/mdp/__init__.pyi (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/locomotion => core}/velocity/mdp/curriculums.py (100%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/locomotion => core}/velocity/mdp/rewards.py (95%) rename source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/{manager_based/locomotion => core}/velocity/mdp/terminations.py (96%) delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/allegro_hand_warp_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/ant_env_warp_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/cartpole_warp_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/humanoid_warp_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/inhand_manipulation/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/locomotion/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/ant_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/cartpole_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/rewards.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/humanoid_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/flat_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/rough_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/velocity_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/joint_pos_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/__init__.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/joint_pos_env_cfg.py delete mode 100644 source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/reach_env_cfg.py diff --git a/source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst b/source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst new file mode 100644 index 000000000000..9b447e8af412 --- /dev/null +++ b/source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst @@ -0,0 +1,48 @@ +Changed +^^^^^^^ + +* **Breaking:** Changed the package layout to mirror :mod:`isaaclab_tasks.core` + (``isaaclab_tasks_experimental.core.``), replacing the previous + ``manager_based``/``direct`` split. Update imports of the old paths to the + ``core`` equivalents (e.g. ``isaaclab_tasks_experimental.core.cartpole.mdp``). +* Changed the direct Warp env modules and classes to the ``_warp_env`` / + ``WarpEnv`` convention so ``--frontend warp`` resolves them by name: + renamed ``ant_env_warp`` to ``ant_warp_env`` and ``InHandManipulationWarpEnv`` + (module ``inhand_manipulation_warp_env``) to ``ReorientDirectWarpEnv`` + (module ``reorient_warp_env``). + +Removed +^^^^^^^ + +* **Breaking:** Removed all manager-based ``*-Warp-v0`` task registrations — + ``Isaac-Cartpole-Warp-v0``, ``Isaac-Humanoid-Warp-v0``, ``Isaac-Ant-Warp-v0``, + ``Isaac-Reach-Franka-Warp-v0``, ``Isaac-Reach-Franka-Warp-Play-v0``, and the + velocity variants (``Isaac-Velocity-Flat--Warp-v0`` and their + ``-Warp-Play-v0`` forms) — together with their environment configurations. + Run the stable task ids with ``--frontend warp`` and + ``presets=newton_mjwarp`` instead, e.g. + ``--task Isaac-Velocity-Flat-AnymalD --frontend warp presets=newton_mjwarp``. +* **Breaking:** Removed the duplicated direct warp task registrations + ``Isaac-Cartpole-Direct-Warp-v0``, ``Isaac-Ant-Direct-Warp-v0``, + ``Isaac-Humanoid-Direct-Warp-v0``, and + ``Isaac-Reorient-Cube-Allegro-Direct-Warp-v0`` together with their + duplicated environment configurations; the stable registrations declare the + warp environment classes via ``warp_entry_point``. Run the stable task ids + with ``--frontend warp`` and ``presets=newton_mjwarp`` instead, e.g. + ``--task Isaac-Cartpole-Direct --frontend warp presets=newton_mjwarp``. +* Removed the unregistered rough velocity warp configurations; rough-terrain + warp tasks remain unsupported until :class:`~isaaclab.terrains.TerrainImporter` + gains Warp APIs. + +Fixed +^^^^^ + +* Fixed the direct Warp Cartpole task to match the stable task's observations, + reset ranges, termination condition, reward scaling, and scene configuration. +* Fixed the Warp Cartpole ``survival_success_rate`` twin to report the + ``Metrics/success_rate`` value on-device instead of silently dropping the + metric. +* Fixed ``Isaac-Humanoid-Direct`` under ``--frontend warp`` failing at env + construction: the Warp locomotion env now resolves the per-physics-backend + ``joint_gears`` dict (selecting the Newton ordering) like the stable + ``LocomotionDirectEnv``, instead of passing the dict straight to ``wp.array``. diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/__init__.py similarity index 68% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/__init__.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/__init__.py index 3e2b7945ebde..bf495309161f 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/__init__.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/__init__.py @@ -3,8 +3,4 @@ # # SPDX-License-Identifier: BSD-3-Clause -""" -Direct workflow environments. -""" - -import gymnasium as gym +"""Warp-first task implementations mirroring the :mod:`isaaclab_tasks.core` layout.""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/__init__.py new file mode 100644 index 000000000000..e8bc71c28dc3 --- /dev/null +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Warp implementations for the stable Cartpole tasks. + +Holds the MDP twins for the manager-based ``Isaac-Cartpole`` task and the +direct :class:`~isaaclab_tasks_experimental.core.cartpole.cartpole_warp_env.CartpoleWarpEnv` +declared by ``Isaac-Cartpole-Direct``. There is no separate task registration: +run the stable ids with ``--frontend warp`` and ``presets=newton_mjwarp``. +""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/cartpole_warp_env.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/cartpole_warp_env.py similarity index 68% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/cartpole_warp_env.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/cartpole_warp_env.py index 14f776c95b0b..16a1f28448a8 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/cartpole_warp_env.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/cartpole_warp_env.py @@ -5,11 +5,11 @@ from __future__ import annotations -import math from typing import TYPE_CHECKING import warp as wp from isaaclab_experimental.envs import DirectRLEnvWarp +from isaaclab_experimental.utils.warp.utils import wrap_to_pi import isaaclab.sim as sim_utils from isaaclab import cloner @@ -17,22 +17,25 @@ from isaaclab.sim.spawners.from_files import GroundPlaneCfg, spawn_ground_plane if TYPE_CHECKING: - from .cartpole_warp_env_cfg import CartpoleWarpEnvCfg + from isaaclab_tasks.core.cartpole.cartpole_direct_env_cfg import CartpoleEnvCfg @wp.kernel def get_observations( joint_pos: wp.array2d(dtype=wp.float32), joint_vel: wp.array2d(dtype=wp.float32), + default_joint_pos: wp.array2d(dtype=wp.float32), + default_joint_vel: wp.array2d(dtype=wp.float32), cart_dof_idx: wp.int32, pole_dof_idx: wp.int32, observations: wp.array(dtype=wp.vec4f), ): + """Write per-env cart and pole joint position/velocity deltas from defaults into the observation vec4.""" env_index = wp.tid() - observations[env_index][0] = joint_pos[env_index, pole_dof_idx] - observations[env_index][1] = joint_vel[env_index, pole_dof_idx] - observations[env_index][2] = joint_pos[env_index, cart_dof_idx] - observations[env_index][3] = joint_vel[env_index, cart_dof_idx] + observations[env_index][0] = joint_pos[env_index, cart_dof_idx] - default_joint_pos[env_index, cart_dof_idx] + observations[env_index][1] = joint_pos[env_index, pole_dof_idx] - default_joint_pos[env_index, pole_dof_idx] + observations[env_index][2] = joint_vel[env_index, cart_dof_idx] - default_joint_vel[env_index, cart_dof_idx] + observations[env_index][3] = joint_vel[env_index, pole_dof_idx] - default_joint_vel[env_index, pole_dof_idx] @wp.kernel @@ -42,6 +45,7 @@ def update_actions( action_scale: wp.float32, cart_dof_idx: wp.int32, ): + """Scale the input action and write it into the cart DOF slot of the joint-effort action buffer.""" env_index = wp.tid() actions[env_index, cart_dof_idx] = action_scale * input_actions[env_index, 0] @@ -51,18 +55,16 @@ def get_dones( joint_pos: wp.array2d(dtype=wp.float32), episode_length_buf: wp.array(dtype=wp.int32), cart_dof_idx: wp.int32, - pole_dof_idx: wp.int32, max_episode_length: wp.int32, max_cart_pos: wp.float32, out_of_bounds: wp.array(dtype=wp.bool), time_out: wp.array(dtype=wp.bool), reset: wp.array(dtype=wp.bool), ): + """Flag per-env cart-out-of-bounds and time-out termination, and their union into the reset buffer.""" env_index = wp.tid() - out_of_bounds[env_index] = (wp.abs(joint_pos[env_index, cart_dof_idx]) > max_cart_pos) or ( - wp.abs(joint_pos[env_index, pole_dof_idx]) > math.pi / 2.0 - ) - time_out[env_index] = episode_length_buf[env_index] >= (max_episode_length - 1) + out_of_bounds[env_index] = wp.abs(joint_pos[env_index, cart_dof_idx]) > max_cart_pos + time_out[env_index] = episode_length_buf[env_index] >= max_episode_length reset[env_index] = out_of_bounds[env_index] or time_out[env_index] @@ -85,6 +87,7 @@ def compute_rew_pole_pos( rew_scale_pole_pos: wp.float32, pole_pos: wp.float32, ) -> wp.float32: + pole_pos = wrap_to_pi(pole_pos) return rew_scale_pole_pos * pole_pos * pole_pos @@ -116,10 +119,12 @@ def compute_rewards( cart_dof_idx: wp.int32, pole_dof_idx: wp.int32, reset_terminated: wp.array(dtype=wp.bool), + step_dt: wp.float32, reward: wp.array(dtype=wp.float32), ): + """Accumulate the scaled alive, termination, and pole/cart position/velocity reward terms.""" env_index = wp.tid() - reward[env_index] = ( + reward[env_index] = step_dt * ( compute_rew_alive(rew_scale_alive, reset_terminated[env_index]) + compute_rew_termination(rew_scale_terminated, reset_terminated[env_index]) + compute_rew_pole_pos(rew_scale_pole_pos, joint_pos[env_index, pole_dof_idx]) @@ -134,21 +139,44 @@ def reset( default_joint_vel: wp.array2d(dtype=wp.float32), joint_pos: wp.array2d(dtype=wp.float32), joint_vel: wp.array2d(dtype=wp.float32), + soft_joint_pos_limits: wp.array2d(dtype=wp.vec2f), + soft_joint_vel_limits: wp.array2d(dtype=wp.float32), cart_dof_idx: wp.int32, pole_dof_idx: wp.int32, - initial_pose_angle_range: wp.vec2f, + initial_cart_position_range: wp.vec2f, + initial_cart_velocity_range: wp.vec2f, + initial_pole_angle_range: wp.vec2f, + initial_pole_velocity_range: wp.vec2f, env_mask: wp.array(dtype=wp.bool), state: wp.array(dtype=wp.uint32), ): + """Sample randomized cart/pole joint positions and velocities within soft limits for masked envs.""" env_index = wp.tid() if env_mask[env_index]: - joint_pos[env_index, cart_dof_idx] = default_joint_pos[env_index, cart_dof_idx] - joint_pos[env_index, pole_dof_idx] = default_joint_pos[env_index, pole_dof_idx] + wp.randf( - state[env_index], initial_pose_angle_range[0] * wp.pi, initial_pose_angle_range[1] * wp.pi + rng_state = state[env_index] + cart_pos = default_joint_pos[env_index, cart_dof_idx] + wp.randf( + rng_state, initial_cart_position_range[0], initial_cart_position_range[1] + ) + pole_pos = default_joint_pos[env_index, pole_dof_idx] + wp.randf( + rng_state, initial_pole_angle_range[0], initial_pole_angle_range[1] + ) + cart_vel = default_joint_vel[env_index, cart_dof_idx] + wp.randf( + rng_state, initial_cart_velocity_range[0], initial_cart_velocity_range[1] ) - joint_vel[env_index, cart_dof_idx] = default_joint_vel[env_index, cart_dof_idx] - joint_vel[env_index, pole_dof_idx] = default_joint_vel[env_index, pole_dof_idx] - state[env_index] += wp.uint32(1) + pole_vel = default_joint_vel[env_index, pole_dof_idx] + wp.randf( + rng_state, initial_pole_velocity_range[0], initial_pole_velocity_range[1] + ) + + cart_pos_limits = soft_joint_pos_limits[env_index, cart_dof_idx] + pole_pos_limits = soft_joint_pos_limits[env_index, pole_dof_idx] + cart_vel_limit = soft_joint_vel_limits[env_index, cart_dof_idx] + pole_vel_limit = soft_joint_vel_limits[env_index, pole_dof_idx] + + joint_pos[env_index, cart_dof_idx] = wp.clamp(cart_pos, cart_pos_limits[0], cart_pos_limits[1]) + joint_pos[env_index, pole_dof_idx] = wp.clamp(pole_pos, pole_pos_limits[0], pole_pos_limits[1]) + joint_vel[env_index, cart_dof_idx] = wp.clamp(cart_vel, -cart_vel_limit, cart_vel_limit) + joint_vel[env_index, pole_dof_idx] = wp.clamp(pole_vel, -pole_vel_limit, pole_vel_limit) + state[env_index] = rng_state @wp.kernel @@ -156,14 +184,15 @@ def initialize_state( state: wp.array(dtype=wp.uint32), seed: wp.int32, ): + """Initialize each env's random number generator state from the seed.""" env_index = wp.tid() state[env_index] = wp.rand_init(seed, env_index) class CartpoleWarpEnv(DirectRLEnvWarp): - cfg: CartpoleWarpEnvCfg + cfg: CartpoleEnvCfg - def __init__(self, cfg: CartpoleWarpEnvCfg, render_mode: str | None = None, **kwargs) -> None: + def __init__(self, cfg: CartpoleEnvCfg, render_mode: str | None = None, **kwargs) -> None: super().__init__(cfg, render_mode, **kwargs) # Get the indices (develop API: find_joints returns (indices, names)) @@ -216,8 +245,9 @@ def _setup_scene(self) -> None: # add articulation to scene self.scene.articulations["cartpole"] = self.cartpole # add lights - light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) - light_cfg.func("/World/Light", light_cfg) + light_cfg = sim_utils.DistantLightCfg(intensity=2000.0, color=(1.0, 1.0, 1.0)) + light_orientation = (-0.14644663035869598, -0.3535534143447876, -0.3535534143447876, 0.8535533547401428) + light_cfg.func("/World/Light", light_cfg, orientation=light_orientation) def _pre_physics_step(self, actions: wp.array) -> None: wp.launch( @@ -241,6 +271,8 @@ def _get_observations(self) -> dict: inputs=[ self.joint_pos, self.joint_vel, + self.cartpole.data.default_joint_pos.warp, + self.cartpole.data.default_joint_vel.warp, self._cart_dof_idx[0], self._pole_dof_idx[0], self.observations, @@ -263,6 +295,7 @@ def _get_rewards(self) -> None: self._cart_dof_idx[0], self._pole_dof_idx[0], self.reset_terminated, + self.step_dt, self.rewards, ], ) @@ -275,7 +308,6 @@ def _get_dones(self) -> None: self.joint_pos, self._episode_length_buf_wp, self._cart_dof_idx[0], - self._pole_dof_idx[0], self.max_episode_length, self.cfg.max_cart_pos, self.reset_terminated, @@ -298,9 +330,14 @@ def _reset_idx(self, mask: wp.array | None = None) -> None: self.cartpole.data.default_joint_vel.warp, self.joint_pos, self.joint_vel, + self.cartpole.data.soft_joint_pos_limits.warp, + self.cartpole.data.soft_joint_vel_limits.warp, self._cart_dof_idx[0], self._pole_dof_idx[0], + self.cfg.initial_cart_position_range, + self.cfg.initial_cart_velocity_range, self.cfg.initial_pole_angle_range, + self.cfg.initial_pole_velocity_range, mask, self.states, ], diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/__init__.py similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/__init__.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/__init__.py diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/__init__.pyi b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/__init__.pyi similarity index 82% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/__init__.pyi rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/__init__.pyi index 377068405c79..657186c2539b 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/__init__.pyi +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/__init__.pyi @@ -5,10 +5,11 @@ __all__ = [ "joint_pos_target_l2", + "survival_success_rate", ] # Forward stable MDP terms and experimental Warp-first overrides lazily, then # override with cartpole-specific terms below. from isaaclab_experimental.envs.mdp import * # noqa: F401, F403 -from .rewards import joint_pos_target_l2 +from .rewards import joint_pos_target_l2, survival_success_rate diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/rewards.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/rewards.py new file mode 100644 index 000000000000..f970a4789dfe --- /dev/null +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/cartpole/mdp/rewards.py @@ -0,0 +1,116 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import warp as wp +from isaaclab_experimental.managers import ManagerTermBase, SceneEntityCfg +from isaaclab_experimental.utils.warp.utils import wrap_to_pi + +if TYPE_CHECKING: + import torch + + from isaaclab.assets import Articulation + from isaaclab.envs import ManagerBasedRLEnv + from isaaclab.managers import RewardTermCfg + + +@wp.kernel +def _joint_pos_target_l2_kernel( + joint_pos: wp.array(dtype=wp.float32, ndim=2), + joint_mask: wp.array(dtype=wp.bool), + out: wp.array(dtype=wp.float32), + target: float, +): + """Accumulate squared wrapped-to-pi joint-position deviation from target over masked joints.""" + i = wp.tid() + s = float(0.0) + for j in range(joint_pos.shape[1]): + if joint_mask[j]: + a = wrap_to_pi(joint_pos[i, j]) + d = a - target + s += d * d + out[i] = s + + +def joint_pos_target_l2(env: ManagerBasedRLEnv, out, target: float, asset_cfg: SceneEntityCfg) -> None: + """Penalize joint position deviation from a target value. Writes into ``out``.""" + asset: Articulation = env.scene[asset_cfg.name] + assert asset.data.joint_pos.warp.shape[1] == asset_cfg.joint_mask.shape[0] + wp.launch( + kernel=_joint_pos_target_l2_kernel, + dim=env.num_envs, + inputs=[asset.data.joint_pos.warp, asset_cfg.joint_mask, out, target], + device=env.device, + ) + + +@wp.kernel +def _survival_counts_kernel( + env_mask: wp.array(dtype=wp.bool), + time_outs: wp.array(dtype=wp.bool), + counts: wp.array(dtype=wp.int32), +): + """Atomically count masked (just-reset) envs and how many of them timed out.""" + env_index = wp.tid() + if env_mask[env_index]: + wp.atomic_add(counts, 0, 1) + if time_outs[env_index]: + wp.atomic_add(counts, 1, 1) + + +@wp.kernel +def _survival_rate_kernel( + counts: wp.array(dtype=wp.int32), + success_rate: wp.array(dtype=wp.float32), +): + """Compute the survival success rate as the fraction of just-reset envs that timed out.""" + if counts[0] > 0: + success_rate[0] = wp.float32(counts[1]) / wp.float32(counts[0]) + else: + success_rate[0] = 0.0 + + +class survival_success_rate(ManagerTermBase): + """Tracks episode survival as the success metric (Warp-first). + + Twin of :class:`isaaclab_tasks.core.cartpole.mdp.rewards.survival_success_rate`. + Returns zero reward (pure metric tracking). On reset, computes the fraction of + just-reset environments that timed out (survived the full episode) entirely + on-device and exposes it as ``Metrics/success_rate`` through the reward + manager's reset extras. Unlike the stable term, there is no host readback, so + the computation stays CUDA-graph capturable. + """ + + def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + # [0] number of just-reset envs, [1] number of those that timed out + self._counts_wp = wp.zeros((2,), dtype=wp.int32, device=env.device) + self._success_rate_wp = wp.zeros((1,), dtype=wp.float32, device=env.device) + # persistent 0-dim tensor view: kernels refresh the value on every reset/replay + self._reset_extras = {"Metrics/success_rate": wp.to_torch(self._success_rate_wp)[0]} + + def reset(self, env_mask: wp.array | None = None) -> dict[str, torch.Tensor]: + if env_mask is None: + env_mask = self._env.resolve_env_mask() + self._counts_wp.zero_() + wp.launch( + kernel=_survival_counts_kernel, + dim=self.num_envs, + inputs=[env_mask, self._env.termination_manager.time_outs_wp, self._counts_wp], + device=self.device, + ) + wp.launch( + kernel=_survival_rate_kernel, + dim=1, + inputs=[self._counts_wp, self._success_rate_wp], + device=self.device, + ) + return self._reset_extras + + def __call__(self, env: ManagerBasedRLEnv, out: wp.array) -> None: + out.zero_() diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/__init__.py new file mode 100644 index 000000000000..3fba47b723b6 --- /dev/null +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Warp implementations for the stable locomotion tasks. + +The ``mdp`` package holds the twins for the shared +:mod:`isaaclab_tasks.core.locomotion.mdp` terms used by the Humanoid and Ant +tasks. There is no separate task registration: run the stable ids with +``--frontend warp`` and ``presets=newton_mjwarp``. +""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/ant/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/ant/__init__.py new file mode 100644 index 000000000000..f64ae37057fd --- /dev/null +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/ant/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Warp implementation of the stable direct Ant task. + +The manager-based MDP twins live in the shared +:mod:`isaaclab_tasks_experimental.core.locomotion.mdp` package (mirroring the +stable layout). ``Isaac-Ant-Direct`` resolves +:class:`~isaaclab_tasks_experimental.core.locomotion.ant.ant_warp_env.AntWarpEnv` +by name from the stable env class (``AntEnv`` → ``AntWarpEnv`` on the mirrored +package); run the stable ids with ``--frontend warp`` and ``presets=newton_mjwarp``. +""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/ant_env_warp.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/ant/ant_warp_env.py similarity index 54% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/ant_env_warp.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/ant/ant_warp_env.py index 2bd7390039b7..7d8957e4d9c7 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/ant_env_warp.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/ant/ant_warp_env.py @@ -5,13 +5,13 @@ from __future__ import annotations -from isaaclab_tasks_experimental.direct.locomotion.locomotion_env_warp import LocomotionWarpEnv +from isaaclab_tasks.core.locomotion.ant.ant_direct_env_cfg import AntEnvCfg -from .ant_env_warp_cfg import AntWarpEnvCfg +from isaaclab_tasks_experimental.core.locomotion.locomotion_env_warp import LocomotionWarpEnv class AntWarpEnv(LocomotionWarpEnv): - cfg: AntWarpEnvCfg + cfg: AntEnvCfg - def __init__(self, cfg: AntWarpEnvCfg, render_mode: str | None = None, **kwargs): + def __init__(self, cfg: AntEnvCfg, render_mode: str | None = None, **kwargs): super().__init__(cfg, render_mode, **kwargs) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/humanoid/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/humanoid/__init__.py new file mode 100644 index 000000000000..e3c6fdf92f41 --- /dev/null +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/humanoid/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Warp implementation of the stable direct Humanoid task. + +The manager-based MDP twins live in the shared +:mod:`isaaclab_tasks_experimental.core.locomotion.mdp` package (mirroring the +stable layout). ``Isaac-Humanoid-Direct`` resolves +:class:`~isaaclab_tasks_experimental.core.locomotion.humanoid.humanoid_warp_env.HumanoidWarpEnv` +by name from the stable env class (``HumanoidEnv`` → ``HumanoidWarpEnv`` on the +mirrored package); run the stable ids with ``--frontend warp`` and ``presets=newton_mjwarp``. +""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/humanoid_warp_env.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/humanoid/humanoid_warp_env.py similarity index 52% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/humanoid_warp_env.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/humanoid/humanoid_warp_env.py index 2fd0ac8433e2..2a843510717a 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/humanoid_warp_env.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/humanoid/humanoid_warp_env.py @@ -5,13 +5,13 @@ from __future__ import annotations -from isaaclab_tasks_experimental.direct.locomotion.locomotion_env_warp import LocomotionWarpEnv +from isaaclab_tasks.core.locomotion.humanoid.humanoid_direct_env_cfg import HumanoidEnvCfg -from .humanoid_warp_env_cfg import HumanoidWarpEnvCfg +from isaaclab_tasks_experimental.core.locomotion.locomotion_env_warp import LocomotionWarpEnv class HumanoidWarpEnv(LocomotionWarpEnv): - cfg: HumanoidWarpEnvCfg + cfg: HumanoidEnvCfg - def __init__(self, cfg: HumanoidWarpEnvCfg, render_mode: str | None = None, **kwargs): + def __init__(self, cfg: HumanoidEnvCfg, render_mode: str | None = None, **kwargs): super().__init__(cfg, render_mode, **kwargs) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/locomotion/locomotion_env_warp.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/locomotion_env_warp.py similarity index 91% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/locomotion/locomotion_env_warp.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/locomotion_env_warp.py index 62400c4d48fe..f24689ed3e37 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/locomotion/locomotion_env_warp.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/locomotion_env_warp.py @@ -7,6 +7,7 @@ import warp as wp from isaaclab_experimental.envs import DirectRLEnvWarp +from isaaclab_newton.physics import NewtonCfg import isaaclab.sim as sim_utils from isaaclab import cloner @@ -57,6 +58,7 @@ def get_dones( time_out: wp.array(dtype=wp.bool), reset: wp.array(dtype=wp.bool), ): + """Flag per-env fall (torso below height) and time-out termination and their union into the reset buffer.""" env_index = wp.tid() out_of_bounds[env_index] = wp.abs(torso_pose[env_index][2]) < termination_height time_out[env_index] = episode_length_buf[env_index] >= (max_episode_length - 1) @@ -79,6 +81,7 @@ def observations( angular_velocity_scale: wp.float32, num_dof: wp.int32, ): + """Assemble the humanoid observation vector (height, velocities, heading/up, scaled DOF pos/vel, actions).""" env_index = wp.tid() observations[env_index, 0] = torso_pose[env_index][2] observations[env_index, 1] = velocity[env_index][0] @@ -128,6 +131,7 @@ def reset_root( root_vel: wp.array(dtype=wp.spatial_vectorf), env_mask: wp.array(dtype=wp.bool), ): + """Reset masked envs' root pose/velocity to defaults offset by env origin and initialize the to-target potential.""" env_index = wp.tid() if env_mask[env_index]: root_pose[env_index] = default_root_pose[env_index] @@ -146,6 +150,7 @@ def reset_joints( joint_vel: wp.array2d(dtype=wp.float32), env_mask: wp.array(dtype=wp.bool), ): + """Reset masked envs' joint positions and velocities to their default values.""" env_index, joint_index = wp.tid() if env_mask[env_index]: joint_pos[env_index, joint_index] = default_joint_pos[env_index, joint_index] @@ -236,6 +241,7 @@ def compute_rewards( alive_reward_scale: wp.float32, reward: wp.array(dtype=wp.float32), ): + """Compute the humanoid reward: progress/alive/up/heading bonuses minus action/energy/joint-limit costs.""" env_index = wp.tid() if reset_terminated[env_index]: reward[env_index] = death_cost @@ -265,6 +271,7 @@ def compute_heading_and_up( potentials: wp.array(dtype=wp.float32), prev_potentials: wp.array(dtype=wp.float32), ): + """Compute per-env up and heading vectors/projections, the to-target vector and the updated potentials.""" env_index = wp.tid() up_vec[env_index] = wp.quat_rotate(wp.transform_get_rotation(torso_pose[env_index]), wp.static(wp.vec3f(0, 0, 1))) heading_vec[env_index] = wp.quat_rotate( @@ -300,6 +307,7 @@ def compute_rot( rpy: wp.array(dtype=wp.vec3f), angle_to_target: wp.array(dtype=wp.float32), ): + """Compute per-env body-frame velocity, roll/pitch/yaw angles and the heading angle to the target.""" env_index = wp.tid() vec_loc[env_index] = spatial_rotate_inv(wp.transform_get_rotation(torso_pose[env_index]), velocity[env_index]) rpy[env_index] = euler_from_quat(wp.transform_get_rotation(torso_pose[env_index])) @@ -315,6 +323,7 @@ def scale_dof_pos( dof_limits: wp.array2d(dtype=wp.vec2f), dof_pos_scaled: wp.array2d(dtype=wp.float32), ): + """Normalize each joint position to [-1, 1] using its soft position limits.""" env_index, joint_index = wp.tid() dof_pos_scaled[env_index, joint_index] = unscale( dof_pos[env_index, joint_index], dof_limits[env_index, joint_index][0], dof_limits[env_index, joint_index][1] @@ -328,6 +337,7 @@ def update_actions( joint_gears: wp.array(dtype=wp.float32), action_scale: wp.float32, ): + """Scale clamped input actions by per-joint gears and the action scale into the effort action buffer.""" env_index, joint_index = wp.tid() actions[env_index, joint_index] = ( action_scale * joint_gears[joint_index] * wp.clamp(input_actions[env_index, joint_index], -1.0, 1.0) @@ -341,6 +351,7 @@ def initialize_state( state: wp.array(dtype=wp.uint32), seed: wp.int32, ): + """Initialize each env's RNG state and place its target far along +x from the env origin.""" env_index = wp.tid() state[env_index] = wp.rand_init(seed, env_index) targets[env_index] = env_origins[env_index] @@ -354,7 +365,18 @@ def __init__(self, cfg: DirectRLEnvCfg, render_mode: str | None = None, **kwargs super().__init__(cfg, render_mode, **kwargs) self.action_scale = self.cfg.action_scale - self.joint_gears = wp.array(self.cfg.joint_gears, dtype=wp.float32, device=self.sim.device) + # Resolve the joint gears based on the physics type, since they do not have the same + # joint ordering (mirrors the stable LocomotionDirectEnv). The warp frontend is Newton-only. + if isinstance(self.cfg.joint_gears, dict): + if isinstance(self.cfg.sim.physics, NewtonCfg): + joint_gears = self.cfg.joint_gears["newton"] + else: + raise ValueError( + f"Warp frontend supports Newton physics only; got {type(self.cfg.sim.physics).__name__}" + ) + else: + joint_gears = self.cfg.joint_gears + self.joint_gears = wp.array(joint_gears, dtype=wp.float32, device=self.sim.device) self.motor_effort_ratio = wp.ones_like(self.joint_gears, device=self.sim.device) self._joint_dof_idx, _ = self.robot.find_joints(".*") diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/__init__.py similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/__init__.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/__init__.py diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/__init__.pyi b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/__init__.pyi similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/__init__.pyi rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/__init__.pyi diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/observations.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/observations.py similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/observations.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/observations.py diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/rewards.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/rewards.py similarity index 94% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/rewards.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/rewards.py index a090d3a16f69..902b88c6094f 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/mdp/rewards.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/locomotion/mdp/rewards.py @@ -47,6 +47,7 @@ def _upright_posture_bonus_kernel( threshold: float, out: wp.array(dtype=wp.float32), ): + """Write 1.0 when the body-frame up projection exceeds the threshold, else 0.0, into the reward output.""" i = wp.tid() up_proj = -rotate_vec_to_body_frame(wp.normalize(gravity_w[i]), root_pose_w[i])[2] out[i] = wp.where(up_proj > threshold, 1.0, 0.0) @@ -74,6 +75,7 @@ def _move_to_target_bonus_kernel( threshold: float, out: wp.array(dtype=wp.float32), ): + """Write the heading-to-target bonus (1.0 above threshold, else the heading projection ratio).""" i = wp.tid() pos = root_pos_w[i] q = root_quat_w[i] @@ -129,6 +131,7 @@ def _progress_reward_reset_kernel( inv_step_dt: float, potentials: wp.array(dtype=wp.float32), ): + """Initialize masked envs' potential from the negative 3D distance to the target scaled by the inverse step time.""" i = wp.tid() if env_mask[i]: pos = root_pos_w[i] @@ -148,6 +151,7 @@ def _progress_reward_kernel( potentials: wp.array(dtype=wp.float32), out: wp.array(dtype=wp.float32), ): + """Update the potential from the xy distance to the target and write its per-step increase as the reward.""" i = wp.tid() pos = root_pos_w[i] dx = target_x - pos[0] @@ -215,6 +219,7 @@ def _joint_pos_limits_penalty_ratio_kernel( inv_range: float, out: wp.array(dtype=wp.float32), ): + """Accumulate the gear-ratio-weighted joint-position-limit violation beyond the threshold.""" i = wp.tid() n_joints = joint_pos.shape[1] s = float(0.0) @@ -281,6 +286,7 @@ def _power_consumption_kernel( gear_ratio_scaled: wp.array(dtype=wp.float32, ndim=2), out: wp.array(dtype=wp.float32), ): + """Accumulate the gear-scaled absolute action-times-joint-velocity power over joints into the penalty output.""" i = wp.tid() n_joints = action.shape[1] s = float(0.0) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/__init__.py new file mode 100644 index 000000000000..1a9be132962a --- /dev/null +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Warp MDP twins for the stable Reach tasks. + +There is no separate task registration: run the stable joint-position reach +tasks (``Isaac-Reach-Franka``, ``Isaac-Reach-UR10``) with ``--frontend warp`` +and ``presets=newton_mjwarp``. The IK and OSC variants require controller +action twins that do not exist yet. +""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/mdp/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/mdp/__init__.py similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/mdp/__init__.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/mdp/__init__.py diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/mdp/__init__.pyi b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/mdp/__init__.pyi similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/mdp/__init__.pyi rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/mdp/__init__.pyi diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/mdp/rewards.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/mdp/rewards.py similarity index 94% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/mdp/rewards.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/mdp/rewards.py index 7435752528d6..cbd3759e4603 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/mdp/rewards.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reach/mdp/rewards.py @@ -36,6 +36,7 @@ def _position_command_error_kernel( body_idx: int, out: wp.array(dtype=wp.float32), ): + """Compute the L2 distance between the end-effector and the commanded position in world frame.""" i = wp.tid() # desired position in body frame -> world frame des_b = wp.vec3f(cmd[i, 0], cmd[i, 1], cmd[i, 2]) @@ -86,6 +87,7 @@ def _position_command_error_tanh_kernel( inv_std: float, out: wp.array(dtype=wp.float32), ): + """Compute the 1 - tanh(distance/std) position-tracking reward for the commanded end-effector position.""" i = wp.tid() des_b = wp.vec3f(cmd[i, 0], cmd[i, 1], cmd[i, 2]) des_w = root_pos_w[i] + wp.quat_rotate(root_quat_w[i], des_b) @@ -136,6 +138,7 @@ def _orientation_command_error_kernel( body_idx: int, out: wp.array(dtype=wp.float32), ): + """Compute the shortest-path angular error between the end-effector and the commanded orientation.""" i = wp.tid() # desired quat in body frame -> world frame: q_des_w = q_root * q_des_b des_b = wp.quatf(cmd[i, 3], cmd[i, 4], cmd[i, 5], cmd[i, 6]) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reorient/__init__.py similarity index 75% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/__init__.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reorient/__init__.py index fe34199f2321..48bb97b31113 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/__init__.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reorient/__init__.py @@ -3,4 +3,4 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Reach experimental task registrations (manager-based).""" +"""Warp-first in-hand reorientation task implementations.""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/inhand_manipulation/inhand_manipulation_warp_env.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reorient/reorient_warp_env.py similarity index 95% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/inhand_manipulation/inhand_manipulation_warp_env.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reorient/reorient_warp_env.py index 2082d470ff9b..2ed16b4c82b0 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/inhand_manipulation/inhand_manipulation_warp_env.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/reorient/reorient_warp_env.py @@ -20,7 +20,7 @@ from isaaclab.sim.spawners.from_files import GroundPlaneCfg, spawn_ground_plane if TYPE_CHECKING: - from isaaclab_tasks_experimental.direct.allegro_hand.allegro_hand_warp_env_cfg import AllegroHandWarpEnvCfg + from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_direct_env_cfg import AllegroHandEnvCfg @wp.kernel @@ -30,6 +30,7 @@ def initialize_rng_state( # output state: wp.array(dtype=wp.uint32), ): + """Initialize each env's random number generator state from the seed.""" env_id = wp.tid() state[env_id] = wp.rand_init(seed, wp.int32(env_id)) @@ -47,6 +48,7 @@ def apply_actions_to_targets( # output cur_targets: wp.array2d(dtype=wp.float32), ): + """Scale clamped actions to each joint's range with moving-average smoothing; write current and previous targets.""" env_id, i = wp.tid() dof_id = actuated_dof_indices[i] @@ -80,6 +82,7 @@ def reset_target_pose( reset_goal_buf: wp.array(dtype=wp.bool), goal_pos_w: wp.array(dtype=wp.vec3f), ): + """Sample a randomized goal orientation and compute the world-frame goal position for masked envs.""" env_id = wp.tid() if env_mask[env_id]: rand0 = wp.randf(rng_state[env_id], wp.float32(-1.0), wp.float32(1.0)) @@ -109,6 +112,7 @@ def reset_object( root_pose_w: wp.array(dtype=wp.transformf), root_vel_w: wp.array(dtype=wp.spatial_vectorf), ): + """Reset masked envs' object root pose with position noise and a randomized rotation, zeroing its velocity.""" env_id = wp.tid() if env_mask[env_id]: nx = wp.randf(rng_state[env_id], wp.float32(-1.0), wp.float32(1.0)) @@ -155,6 +159,7 @@ def reset_hand( cur_targets: wp.array2d(dtype=wp.float32), hand_dof_targets: wp.array2d(dtype=wp.float32), ): + """Reset masked envs' hand joint positions and velocities with noise and initialize their position targets.""" env_id = wp.tid() if env_mask[env_id]: # Each env runs sequentially inside this kernel (avoids RNG races across DOFs). @@ -188,6 +193,7 @@ def reset_successes( # output successes: wp.array(dtype=wp.float32), ): + """Zero the consecutive-success counter for masked envs.""" env_id = wp.tid() if env_mask[env_id]: successes[env_id] = wp.float32(0.0) @@ -211,6 +217,7 @@ def compute_intermediate_values( object_pose: wp.array(dtype=wp.transformf), object_vels: wp.array(dtype=wp.spatial_vectorf), ): + """Compute per-env fingertip poses/velocities and the env-local object pose and velocity.""" env_id = wp.tid() for i in range(num_fingertips): @@ -245,6 +252,7 @@ def get_dones( time_out: wp.array(dtype=wp.bool), reset: wp.array(dtype=wp.bool), ): + """Flag object-fall and time-out/max-success termination, reset progress on goal, write the reset union.""" env_id = wp.tid() object_pos = wp.transform_get_translation(object_pose[env_id]) @@ -277,6 +285,7 @@ def compute_reduced_observations( # output observations: wp.array2d(dtype=wp.float32), ): + """Assemble the reduced observation vector (fingertip/object positions, relative goal orientation, actions).""" env_id = wp.tid() obj_pos = wp.transform_get_translation(object_pose[env_id]) @@ -327,6 +336,7 @@ def compute_full_observations( # output observations: wp.array2d(dtype=wp.float32), ): + """Assemble the full observation vector (scaled DOF pos/vel, object pose/vel, goal, fingertip states, actions).""" env_id = wp.tid() # hand @@ -416,6 +426,7 @@ def sanitize_and_print_once( obs: wp.array(dtype=wp.float32), printed_flag: wp.array(dtype=wp.int32), ): + """Zero any non-finite observation entry and print a warning once via an atomic print token.""" i = wp.tid() v = obs[i] @@ -452,6 +463,7 @@ def compute_rewards( # output reward_out: wp.array(dtype=wp.float32), ): + """Compute the in-hand reorientation reward and update success/reset statistics.""" env_id = wp.tid() obj_pos = wp.transform_get_translation(object_pose[env_id]) @@ -537,11 +549,11 @@ def rotation_distance(object_rot: wp.quatf, target_rot: wp.quatf) -> wp.float32: return wp.float32(2.0) * wp.asin(v_norm) -class InHandManipulationWarpEnv(DirectRLEnvWarp): - cfg: AllegroHandWarpEnvCfg # | ShadowHandWarpEnvCfg +class ReorientDirectWarpEnv(DirectRLEnvWarp): + cfg: AllegroHandEnvCfg # | ShadowHandEnvCfg - # def __init__(self, cfg: AllegroHandWarpEnvCfg | ShadowHandWarpEnvCfg, render_mode: str | None = None, **kwargs): - def __init__(self, cfg: AllegroHandWarpEnvCfg, render_mode: str | None = None, **kwargs): + # def __init__(self, cfg: AllegroHandEnvCfg | ShadowHandEnvCfg, render_mode: str | None = None, **kwargs): + def __init__(self, cfg: AllegroHandEnvCfg, render_mode: str | None = None, **kwargs): super().__init__(cfg, render_mode, **kwargs) # --------------------------------------------------------------------- diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/__init__.py new file mode 100644 index 000000000000..f6510ad92c91 --- /dev/null +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Warp MDP twins for the stable velocity locomotion tasks. + +There is no separate task registration: run the stable flat velocity ids +(e.g. ``Isaac-Velocity-Flat-AnymalD``) with ``--frontend warp`` and +``presets=newton_mjwarp``. Rough-terrain variants remain unsupported until +:class:`~isaaclab.terrains.TerrainImporter` gains Warp APIs (no +``height_scan`` twin). +""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/__init__.py similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/__init__.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/__init__.py diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/__init__.pyi b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/__init__.pyi similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/__init__.pyi rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/__init__.pyi diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/curriculums.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/curriculums.py similarity index 100% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/curriculums.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/curriculums.py diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/rewards.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/rewards.py similarity index 95% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/rewards.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/rewards.py index d7e11c517c7d..115df21c5fe6 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/rewards.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/rewards.py @@ -36,6 +36,7 @@ def _feet_air_time_kernel( threshold: float, out: wp.array(dtype=wp.float32), ): + """Accumulate per-foot (air time minus threshold) at first contact, gated by command magnitude.""" i = wp.tid() s = float(0.0) for k in range(body_ids.shape[0]): @@ -89,6 +90,7 @@ def _feet_air_time_positive_biped_kernel( threshold: float, out: wp.array(dtype=wp.float32), ): + """Compute the single-stance minimum foot mode time (air/contact) gated by command magnitude.""" i = wp.tid() n_feet = body_ids.shape[0] # count feet in contact and find single-stance min mode time @@ -149,6 +151,7 @@ def _feet_slide_kernel( n_history: int, out: wp.array(dtype=wp.float32), ): + """Accumulate each foot's planar velocity while it is in contact into the sliding penalty output.""" i = wp.tid() s = float(0.0) for k in range(body_ids.shape[0]): @@ -199,6 +202,7 @@ def _track_lin_vel_xy_yaw_frame_exp_kernel( inv_std_sq: float, out: wp.array(dtype=wp.float32), ): + """Compute the exponential xy linear-velocity tracking reward in the yaw-aligned frame.""" i = wp.tid() q = root_quat_w[i] # extract yaw-only quaternion @@ -251,6 +255,7 @@ def _track_ang_vel_z_world_exp_kernel( inv_std_sq: float, out: wp.array(dtype=wp.float32), ): + """Compute the exponential yaw angular-velocity tracking reward in world frame.""" i = wp.tid() err = cmd[i, 2] - root_ang_vel_w[i][2] out[i] = wp.exp(-(err * err) * inv_std_sq) @@ -288,6 +293,7 @@ def _stand_still_joint_deviation_l1_kernel( command_threshold: float, out: wp.array(dtype=wp.float32), ): + """Accumulate L1 joint deviation from defaults, gated to when the command is below threshold.""" i = wp.tid() n_joints = joint_pos.shape[1] dev = float(0.0) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/terminations.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/terminations.py similarity index 96% rename from source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/terminations.py rename to source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/terminations.py index 4e0c32c58e64..53f701700097 100644 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/mdp/terminations.py +++ b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/core/velocity/mdp/terminations.py @@ -26,6 +26,7 @@ def _terrain_out_of_bounds_kernel( distance_buffer: float, out: wp.array(dtype=wp.bool), ): + """Flag envs whose root xy position leaves the terrain bounds minus the distance buffer.""" i = wp.tid() px = wp.abs(root_pos_w[i][0]) py = wp.abs(root_pos_w[i][1]) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/__init__.py deleted file mode 100644 index e7fc129c633f..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Allegro Inhand Manipulation environment. -""" - -import gymnasium as gym - -## -# Register Gym environments. -## - -inhand_task_entry = "isaaclab_tasks_experimental.direct.inhand_manipulation" -stable_agents = "isaaclab_tasks.core.reorient.config.allegro_hand.agents" - -gym.register( - id="Isaac-Reorient-Cube-Allegro-Direct-Warp-v0", - entry_point=f"{inhand_task_entry}.inhand_manipulation_warp_env:InHandManipulationWarpEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.allegro_hand_warp_env_cfg:AllegroHandWarpEnvCfg", - "rl_games_cfg_entry_point": f"{stable_agents}:rl_games_direct_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{stable_agents}.rsl_rl_ppo_cfg:AllegroHandPPORunnerCfg", - "skrl_cfg_entry_point": f"{stable_agents}:skrl_direct_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/allegro_hand_warp_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/allegro_hand_warp_env_cfg.py deleted file mode 100644 index 573b4f1d302d..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/allegro_hand/allegro_hand_warp_env_cfg.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg # , RigidObjectCfg -from isaaclab.envs import DirectRLEnvCfg -from isaaclab.markers import VisualizationMarkersCfg -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim import SimulationCfg -from isaaclab.sim.spawners.materials.physics_materials_cfg import RigidBodyMaterialCfg -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab.utils.configclass import configclass - -from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG - - -@configclass -class AllegroHandWarpEnvCfg(DirectRLEnvCfg): - # env - decimation = 4 - episode_length_s = 10.0 - action_space = 16 - observation_space = 124 # (full) - state_space = 0 - asymmetric_obs = False - obs_type = "full" - - solver_cfg = MJWarpSolverCfg( - solver="newton", - integrator="implicitfast", - njmax=80, - nconmax=70, - impratio=10.0, - cone="elliptic", - update_data_interval=2, - iterations=100, - ls_iterations=15, - # save_to_mjcf="AllegroHand.xml", - ) - - newton_cfg = NewtonCfg( - solver_cfg=solver_cfg, - num_substeps=2, - debug_mode=False, - ) - # simulation - sim: SimulationCfg = SimulationCfg( - dt=1 / 120, - render_interval=decimation, - physics_material=RigidBodyMaterialCfg( - static_friction=1.0, - dynamic_friction=1.0, - ), - physics=newton_cfg, - ) - # robot - robot_cfg: ArticulationCfg = ALLEGRO_HAND_CFG.replace(prim_path="/World/envs/env_.*/Robot") - - actuated_joint_names = [ - "index_joint_0", - "middle_joint_0", - "ring_joint_0", - "thumb_joint_0", - "index_joint_1", - "index_joint_2", - "index_joint_3", - "middle_joint_1", - "middle_joint_2", - "middle_joint_3", - "ring_joint_1", - "ring_joint_2", - "ring_joint_3", - "thumb_joint_1", - "thumb_joint_2", - "thumb_joint_3", - ] - fingertip_body_names = [ - "index_link_3", - "middle_link_3", - "ring_link_3", - "thumb_link_3", - ] - - # in-hand object - object_cfg: ArticulationCfg = ArticulationCfg( - prim_path="/World/envs/env_.*/object", - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", - mass_props=sim_utils.MassPropertiesCfg(density=400.0), - scale=(1.2, 1.2, 1.2), - ), - # FIXME: it does seem to be a bug for ArticulationCfg for handling empty joint list - init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, -0.17, 0.565), rot=(0.0, 0.0, 0.0, 1.0), joint_pos={}, joint_vel={} - ), - actuators={}, - articulation_root_prim_path="", - ) - # goal object - goal_object_cfg: VisualizationMarkersCfg = VisualizationMarkersCfg( - prim_path="/Visuals/goal_marker", - markers={ - "goal": sim_utils.UsdFileCfg( - usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", - scale=(1.2, 1.2, 1.2), - ) - }, - ) - # scene - scene: InteractiveSceneCfg = InteractiveSceneCfg( - num_envs=8192, env_spacing=0.75, replicate_physics=True, clone_in_fabric=True - ) - # reset - reset_position_noise = 0.01 # range of position at reset - reset_dof_pos_noise = 0.2 # range of dof pos at reset - reset_dof_vel_noise = 0.0 # range of dof vel at reset - # reward scales - dist_reward_scale = -10.0 - rot_reward_scale = 1.0 - rot_eps = 0.1 - action_penalty_scale = -0.0002 - reach_goal_bonus = 250 - fall_penalty = 0 - fall_dist = 0.24 - vel_obs_scale = 0.2 - success_tolerance = 0.2 - max_consecutive_success = 0 - av_factor = 0.1 - act_moving_average = 1.0 - force_torque_obs_scale = 10.0 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/__init__.py deleted file mode 100644 index 33d5ac926387..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Ant locomotion environment. -""" - -import gymnasium as gym - -## -# Register Gym environments. -## - -stable_agents = "isaaclab_tasks.core.locomotion.ant.agents" - -gym.register( - id="Isaac-Ant-Direct-Warp-v0", - entry_point=f"{__name__}.ant_env_warp:AntWarpEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.ant_env_warp_cfg:AntWarpEnvCfg", - "rl_games_cfg_entry_point": f"{stable_agents}:rl_games_direct_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{stable_agents}.rsl_rl_ppo_cfg:AntDirectPPORunnerCfg", - "skrl_cfg_entry_point": f"{stable_agents}:skrl_direct_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/ant_env_warp_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/ant_env_warp_cfg.py deleted file mode 100644 index 6e8b7fb81a95..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/ant/ant_env_warp_cfg.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from __future__ import annotations - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg -from isaaclab.envs import DirectRLEnvCfg -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim import SimulationCfg -from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils.configclass import configclass - -from isaaclab_assets import ANT_CFG - - -@configclass -class AntWarpEnvCfg(DirectRLEnvCfg): - # env - episode_length_s = 15.0 - decimation = 2 - action_scale = 0.5 - action_space = 8 - observation_space = 36 - state_space = 0 - - solver_cfg = MJWarpSolverCfg( - njmax=45, - nconmax=25, - cone="pyramidal", - integrator="implicitfast", - impratio=1, - ) - newton_cfg = NewtonCfg( - solver_cfg=solver_cfg, - num_substeps=1, - debug_mode=False, - use_cuda_graph=True, - ) - - # simulation - sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=decimation, physics=newton_cfg) - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg( - friction_combine_mode="average", - restitution_combine_mode="average", - static_friction=1.0, - dynamic_friction=1.0, - restitution=0.0, - ), - debug_vis=False, - ) - - # scene - scene: InteractiveSceneCfg = InteractiveSceneCfg( - num_envs=4096, env_spacing=4.0, replicate_physics=True, clone_in_fabric=True - ) - - # robot - robot: ArticulationCfg = ANT_CFG.replace(prim_path="/World/envs/env_.*/Robot") - joint_gears: list = [15, 15, 15, 15, 15, 15, 15, 15] - - heading_weight: float = 0.5 - up_weight: float = 0.1 - - energy_cost_scale: float = 0.05 - actions_cost_scale: float = 0.005 - alive_reward_scale: float = 0.5 - dof_vel_scale: float = 0.2 - - death_cost: float = -2.0 - termination_height: float = 0.31 - - angular_velocity_scale: float = 1.0 - contact_force_scale: float = 0.1 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/__init__.py deleted file mode 100644 index a7fc9492fff6..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Cartpole balancing environment. -""" - -import gymnasium as gym - -## -# Register Gym environments. -## - -stable_agents = "isaaclab_tasks.core.cartpole.agents" - -gym.register( - id="Isaac-Cartpole-Direct-Warp-v0", - entry_point=f"{__name__}.cartpole_warp_env:CartpoleWarpEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.cartpole_warp_env_cfg:CartpoleWarpEnvCfg", - "rl_games_cfg_entry_point": f"{stable_agents}:rl_games_direct_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{stable_agents}.rsl_rl_ppo_cfg:CartpoleDirectPPORunnerCfg", - "skrl_cfg_entry_point": f"{stable_agents}:skrl_direct_ppo_cfg.yaml", - "sb3_cfg_entry_point": f"{stable_agents}:sb3_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/cartpole_warp_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/cartpole_warp_env_cfg.py deleted file mode 100644 index 2a99e1cff3e9..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/cartpole/cartpole_warp_env_cfg.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from __future__ import annotations - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.assets import ArticulationCfg -from isaaclab.envs import DirectRLEnvCfg -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from isaaclab_assets.robots.cartpole import CARTPOLE_CFG - - -@configclass -class CartpoleWarpEnvCfg(DirectRLEnvCfg): - # env - decimation = 2 - episode_length_s = 5.0 - action_scale = 100.0 # [N] - action_space = 1 - observation_space = 4 - state_space = 0 - - solver_cfg = MJWarpSolverCfg( - njmax=5, - nconmax=3, - cone="pyramidal", - integrator="implicitfast", - impratio=1, - ) - - newton_cfg = NewtonCfg( - solver_cfg=solver_cfg, - num_substeps=1, - debug_mode=False, - use_cuda_graph=True, - ) - - # simulation - sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=decimation, physics=newton_cfg) - - # robot - robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="/World/envs/env_.*/Robot") - cart_dof_name = "slider_to_cart" - pole_dof_name = "cart_to_pole" - - # scene - scene: InteractiveSceneCfg = InteractiveSceneCfg( - num_envs=4096, env_spacing=4.0, replicate_physics=True, clone_in_fabric=True - ) - - # reset - max_cart_pos = 3.0 # the cart is reset if it exceeds that position [m] - initial_pole_angle_range = [-0.25, 0.25] # the range in which the pole angle is sampled from on reset [x pi rad] - - # reward scales - rew_scale_alive = 1.0 - rew_scale_terminated = -2.0 - rew_scale_pole_pos = -1.0 - rew_scale_cart_vel = -0.01 - rew_scale_pole_vel = -0.005 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/__init__.py deleted file mode 100644 index c38d7dd955e5..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Humanoid locomotion environment. -""" - -import gymnasium as gym - -## -# Register Gym environments. -## - -stable_agents = "isaaclab_tasks.core.locomotion.humanoid.agents" - -gym.register( - id="Isaac-Humanoid-Direct-Warp-v0", - entry_point=f"{__name__}.humanoid_warp_env:HumanoidWarpEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.humanoid_warp_env_cfg:HumanoidWarpEnvCfg", - "rl_games_cfg_entry_point": f"{stable_agents}:rl_games_direct_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{stable_agents}.rsl_rl_ppo_cfg:HumanoidDirectPPORunnerCfg", - "skrl_cfg_entry_point": f"{stable_agents}:skrl_direct_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/humanoid_warp_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/humanoid_warp_env_cfg.py deleted file mode 100644 index d7fa473eda38..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/humanoid/humanoid_warp_env_cfg.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from __future__ import annotations - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg -from isaaclab.envs import DirectRLEnvCfg -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim import SimulationCfg -from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils.configclass import configclass - -from isaaclab_assets import HUMANOID_CFG - - -@configclass -class HumanoidWarpEnvCfg(DirectRLEnvCfg): - # env - episode_length_s = 15.0 - decimation = 2 - action_scale = 1.0 - action_space = 21 - observation_space = 75 - state_space = 0 - - solver_cfg = MJWarpSolverCfg( - njmax=80, - nconmax=25, - cone="pyramidal", - integrator="implicitfast", - update_data_interval=2, - impratio=1, - ) - newton_cfg = NewtonCfg( - solver_cfg=solver_cfg, - num_substeps=2, - debug_mode=False, - use_cuda_graph=True, - ) - - # simulation - sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=decimation, physics=newton_cfg) - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg( - friction_combine_mode="average", - restitution_combine_mode="average", - static_friction=1.0, - dynamic_friction=1.0, - restitution=0.0, - ), - debug_vis=False, - ) - - # scene - scene: InteractiveSceneCfg = InteractiveSceneCfg( - num_envs=4096, env_spacing=4.0, replicate_physics=True, clone_in_fabric=True - ) - - # robot - robot: ArticulationCfg = HUMANOID_CFG.replace(prim_path="/World/envs/env_.*/Robot") - joint_gears: list = [ - 67.5000, # left_upper_arm - 67.5000, # left_upper_arm - 45.0000, # left_lower_arm - 67.5000, # lower_waist - 67.5000, # lower_waist - 67.5000, # pelvis - 45.0000, # left_thigh: x - 135.0000, # left_thigh: y - 45.0000, # left_thigh: z - 90.0000, # left_knee - 22.5, # left_foot - 22.5, # left_foot - 45.0000, # right_thigh: x - 135.0000, # right_thigh: y - 45.0000, # right_thigh: z - 90.0000, # right_knee - 22.5, # right_foot - 22.5, # right_foot - 67.5000, # right_upper_arm - 67.5000, # right_upper_arm - 45.0000, # right_lower_arm - ] - - heading_weight: float = 0.5 - up_weight: float = 0.1 - - energy_cost_scale: float = 0.05 - actions_cost_scale: float = 0.01 - alive_reward_scale: float = 2.0 - dof_vel_scale: float = 0.1 - - death_cost: float = -1.0 - termination_height: float = 0.8 - - angular_velocity_scale: float = 0.25 - contact_force_scale: float = 0.01 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/inhand_manipulation/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/inhand_manipulation/__init__.py deleted file mode 100644 index 460a30569089..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/inhand_manipulation/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/locomotion/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/locomotion/__init__.py deleted file mode 100644 index 460a30569089..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/direct/locomotion/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/__init__.py deleted file mode 100644 index 7f23883e6332..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Experimental registrations for manager-based tasks. - -We intentionally only register new Gym IDs pointing at experimental entry points. -Task definitions (configs/mdp) remain in `isaaclab_tasks` to avoid duplication. -""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/__init__.py deleted file mode 100644 index 79c13e2aa8f4..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Classic environments for control. - -These environments are based on the MuJoCo environments provided by OpenAI. - -Reference: - https://github.com/openai/gym/tree/master/gym/envs/mujoco -""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/__init__.py deleted file mode 100644 index af513e675f03..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Ant locomotion environment (experimental manager-based entry point). -""" - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.locomotion.ant import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Ant-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.ant_env_cfg:AntEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AntPPORunnerCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_manager_ppo_cfg.yaml", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_manager_ppo_cfg.yaml", - "sb3_cfg_entry_point": f"{agents.__name__}:sb3_manager_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/ant_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/ant_env_cfg.py deleted file mode 100644 index 66a9131011f3..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/ant/ant_env_cfg.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -# Ant reuses humanoid's experimental MDP (mirrors stable pattern). -from isaaclab_experimental.managers import ObservationTermCfg as ObsTerm -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import TerminationTermCfg as DoneTerm -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -import isaaclab.sim as sim_utils -from isaaclab.assets import AssetBaseCfg -from isaaclab.envs import ManagerBasedRLEnvCfg -from isaaclab.managers import EventTermCfg as EventTerm -from isaaclab.managers import ObservationGroupCfg as ObsGroup -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim import SimulationCfg -from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.classic.humanoid.mdp as mdp - -## -# Pre-defined configs -## -from isaaclab_assets.robots.ant import ANT_CFG # isort: skip - - -@configclass -class MySceneCfg(InteractiveSceneCfg): - """Configuration for the terrain scene with an ant robot.""" - - # terrain - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg( - friction_combine_mode="average", - restitution_combine_mode="average", - static_friction=1.0, - dynamic_friction=1.0, - restitution=0.0, - ), - debug_vis=False, - ) - - # robot - robot = ANT_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - - # lights - light = AssetBaseCfg( - prim_path="/World/light", - spawn=sim_utils.DistantLightCfg(color=(0.75, 0.75, 0.75), intensity=3000.0), - ) - - -## -# MDP settings -## - - -@configclass -class ActionsCfg: - """Action specifications for the MDP.""" - - joint_effort = mdp.JointEffortActionCfg(asset_name="robot", joint_names=[".*"], scale=7.5) - - -@configclass -class ObservationsCfg: - """Observation specifications for the MDP.""" - - @configclass - class PolicyCfg(ObsGroup): - """Observations for the policy.""" - - base_height = ObsTerm(func=mdp.base_pos_z) - base_lin_vel = ObsTerm(func=mdp.base_lin_vel) - base_ang_vel = ObsTerm(func=mdp.base_ang_vel) - base_yaw_roll = ObsTerm(func=mdp.base_yaw_roll) - base_angle_to_target = ObsTerm(func=mdp.base_angle_to_target, params={"target_pos": (1000.0, 0.0, 0.0)}) - base_up_proj = ObsTerm(func=mdp.base_up_proj) - base_heading_proj = ObsTerm(func=mdp.base_heading_proj, params={"target_pos": (1000.0, 0.0, 0.0)}) - joint_pos_norm = ObsTerm(func=mdp.joint_pos_limit_normalized) - joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel, scale=0.2) - actions = ObsTerm(func=mdp.last_action) - - def __post_init__(self): - self.enable_corruption = False - self.concatenate_terms = True - - # observation groups - policy: PolicyCfg = PolicyCfg() - - -@configclass -class EventCfg: - """Configuration for events.""" - - reset_base = EventTerm( - func=mdp.reset_root_state_uniform, - mode="reset", - params={"pose_range": {}, "velocity_range": {}}, - ) - - reset_robot_joints = EventTerm( - func=mdp.reset_joints_by_offset, - mode="reset", - params={ - "position_range": (-0.2, 0.2), - "velocity_range": (-0.1, 0.1), - }, - ) - - -@configclass -class RewardsCfg: - """Reward terms for the MDP.""" - - # (1) Reward for moving forward - progress = RewTerm(func=mdp.progress_reward, weight=1.0, params={"target_pos": (1000.0, 0.0, 0.0)}) - # (2) Stay alive bonus - alive = RewTerm(func=mdp.is_alive, weight=0.5) - # (3) Reward for non-upright posture - upright = RewTerm(func=mdp.upright_posture_bonus, weight=0.1, params={"threshold": 0.93}) - # (4) Reward for moving in the right direction - move_to_target = RewTerm( - func=mdp.move_to_target_bonus, weight=0.5, params={"threshold": 0.8, "target_pos": (1000.0, 0.0, 0.0)} - ) - # (5) Penalty for large action commands - action_l2 = RewTerm(func=mdp.action_l2, weight=-0.005) - # (6) Penalty for energy consumption - energy = RewTerm(func=mdp.power_consumption, weight=-0.05, params={"gear_ratio": {".*": 15.0}}) - # (7) Penalty for reaching close to joint limits - joint_pos_limits = RewTerm( - func=mdp.joint_pos_limits_penalty_ratio, weight=-0.1, params={"threshold": 0.99, "gear_ratio": {".*": 15.0}} - ) - - -@configclass -class TerminationsCfg: - """Termination terms for the MDP.""" - - # (1) Terminate if the episode length is exceeded - time_out = DoneTerm(func=mdp.time_out, time_out=True) - # (2) Terminate if the robot falls - torso_height = DoneTerm(func=mdp.root_height_below_minimum, params={"minimum_height": 0.31}) - - -## -# Environment configuration -## - - -@configclass -class AntEnvCfg(ManagerBasedRLEnvCfg): - """Configuration for the MuJoCo-style Ant walking environment.""" - - # Simulation settings - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=38, - nconmax=15, - ls_iterations=10, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - # Scene settings - scene: MySceneCfg = MySceneCfg(num_envs=4096, env_spacing=5.0, clone_in_fabric=True) - # Basic settings - observations: ObservationsCfg = ObservationsCfg() - actions: ActionsCfg = ActionsCfg() - # MDP settings - rewards: RewardsCfg = RewardsCfg() - terminations: TerminationsCfg = TerminationsCfg() - events: EventCfg = EventCfg() - - def __post_init__(self): - """Post initialization.""" - # general settings - self.decimation = 2 - self.episode_length_s = 16.0 - # simulation settings - self.sim.dt = 1 / 120.0 - self.sim.render_interval = self.decimation - # default friction material - self.sim.physics_material.static_friction = 1.0 - self.sim.physics_material.dynamic_friction = 1.0 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/__init__.py deleted file mode 100644 index 9f46551de255..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Cartpole balancing environment (experimental manager-based entry point). -""" - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.cartpole import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Cartpole-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.cartpole_env_cfg:CartpoleEnvCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_manager_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_manager_ppo_cfg:CartpolePPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_manager_ppo_cfg.yaml", - "sb3_cfg_entry_point": f"{agents.__name__}:sb3_manager_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/cartpole_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/cartpole_env_cfg.py deleted file mode 100644 index ae187a593a16..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/cartpole_env_cfg.py +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import math - -from isaaclab_experimental.managers import ObservationTermCfg as ObsTerm -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import SceneEntityCfg -from isaaclab_experimental.managers import TerminationTermCfg as DoneTerm -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.envs import ManagerBasedRLEnvCfg -from isaaclab.managers import EventTermCfg as EventTerm -from isaaclab.managers import ObservationGroupCfg as ObsGroup -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.classic.cartpole.mdp as mdp - -## -# Pre-defined configs -## -from isaaclab_assets.robots.cartpole import CARTPOLE_CFG # isort:skip - - -## -# Scene definition -## - - -@configclass -class CartpoleSceneCfg(InteractiveSceneCfg): - """Configuration for a cart-pole scene.""" - - # ground plane - # ground = AssetBaseCfg( - # prim_path="/World/ground", - # spawn=sim_utils.GroundPlaneCfg(size=(100.0, 100.0)), - # ) - - # cartpole - robot: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - - # lights - dome_light = AssetBaseCfg( - prim_path="/World/DomeLight", - spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=500.0), - ) - - -## -# MDP settings -## - - -@configclass -class ActionsCfg: - """Action specifications for the MDP.""" - - joint_effort = mdp.JointEffortActionCfg(asset_name="robot", joint_names=["slider_to_cart"], scale=100.0) - - -@configclass -class ObservationsCfg: - """Observation specifications for the MDP.""" - - @configclass - class PolicyCfg(ObsGroup): - """Observations for policy group.""" - - # observation terms (order preserved) - joint_pos_rel = ObsTerm(func=mdp.joint_pos_rel) - joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel) - - def __post_init__(self) -> None: - self.enable_corruption = False - self.concatenate_terms = True - - # observation groups - policy: PolicyCfg = PolicyCfg() - - -@configclass -class EventCfg: - """Configuration for events.""" - - # reset - reset_cart_position = EventTerm( - func=mdp.reset_joints_by_offset, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]), - "position_range": (-1.0, 1.0), - "velocity_range": (-0.5, 0.5), - }, - ) - - reset_pole_position = EventTerm( - func=mdp.reset_joints_by_offset, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]), - "position_range": (-0.25 * math.pi, 0.25 * math.pi), - "velocity_range": (-0.25 * math.pi, 0.25 * math.pi), - }, - ) - - -@configclass -class RewardsCfg: - """Reward terms for the MDP.""" - - # (1) Constant running reward - alive = RewTerm(func=mdp.is_alive, weight=1.0) - # (2) Failure penalty - terminating = RewTerm(func=mdp.is_terminated, weight=-2.0) - # (3) Primary task: keep pole upright - pole_pos = RewTerm( - func=mdp.joint_pos_target_l2, - weight=-1.0, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]), "target": 0.0}, - ) - # (4) Shaping tasks: lower cart velocity - cart_vel = RewTerm( - func=mdp.joint_vel_l1, - weight=-0.01, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"])}, - ) - # (5) Shaping tasks: lower pole angular velocity - pole_vel = RewTerm( - func=mdp.joint_vel_l1, - weight=-0.005, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"])}, - ) - - -@configclass -class TerminationsCfg: - """Termination terms for the MDP.""" - - # (1) Time out - time_out = DoneTerm(func=mdp.time_out, time_out=True) - # (2) Cart out of bounds - cart_out_of_bounds = DoneTerm( - func=mdp.joint_pos_out_of_manual_limit, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]), "bounds": (-3.0, 3.0)}, - ) - - -## -# Environment configuration -## - - -@configclass -class CartpoleEnvCfg(ManagerBasedRLEnvCfg): - """Configuration for the cartpole environment.""" - - # Scene settings - scene: CartpoleSceneCfg = CartpoleSceneCfg(num_envs=4096, env_spacing=4.0, clone_in_fabric=True) - # Basic settings - observations: ObservationsCfg = ObservationsCfg() - actions: ActionsCfg = ActionsCfg() - events: EventCfg = EventCfg() - # MDP settings - rewards: RewardsCfg = RewardsCfg() - terminations: TerminationsCfg = TerminationsCfg() - # Simulation settings - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=5, - nconmax=3, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - use_cuda_graph=True, - ) - ) - - # Post initialization - def __post_init__(self) -> None: - """Post initialization.""" - # general settings - self.decimation = 2 - self.episode_length_s = 5 - # viewer settings - self.viewer.eye = (8.0, 0.0, 5.0) - # simulation settings - self.sim.dt = 1 / 120 - self.sim.render_interval = self.decimation diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/rewards.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/rewards.py deleted file mode 100644 index 2678ef03de65..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/cartpole/mdp/rewards.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import warp as wp -from isaaclab_experimental.managers import SceneEntityCfg -from isaaclab_experimental.utils.warp.utils import wrap_to_pi - -if TYPE_CHECKING: - from isaaclab.assets import Articulation - from isaaclab.envs import ManagerBasedRLEnv - - -@wp.kernel -def _joint_pos_target_l2_kernel( - joint_pos: wp.array(dtype=wp.float32, ndim=2), - joint_mask: wp.array(dtype=wp.bool), - out: wp.array(dtype=wp.float32), - target: float, -): - i = wp.tid() - s = float(0.0) - for j in range(joint_pos.shape[1]): - if joint_mask[j]: - a = wrap_to_pi(joint_pos[i, j]) - d = a - target - s += d * d - out[i] = s - - -def joint_pos_target_l2(env: ManagerBasedRLEnv, out, target: float, asset_cfg: SceneEntityCfg) -> None: - """Penalize joint position deviation from a target value. Writes into ``out``.""" - asset: Articulation = env.scene[asset_cfg.name] - assert asset.data.joint_pos.warp.shape[1] == asset_cfg.joint_mask.shape[0] - wp.launch( - kernel=_joint_pos_target_l2_kernel, - dim=env.num_envs, - inputs=[asset.data.joint_pos.warp, asset_cfg.joint_mask, out, target], - device=env.device, - ) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/__init__.py deleted file mode 100644 index 0bcd020bcd4c..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Humanoid locomotion environment (experimental manager-based entry point). -""" - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.locomotion.humanoid import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Humanoid-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.humanoid_env_cfg:HumanoidEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:HumanoidPPORunnerCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_manager_ppo_cfg.yaml", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_manager_ppo_cfg.yaml", - "sb3_cfg_entry_point": f"{agents.__name__}:sb3_manager_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/humanoid_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/humanoid_env_cfg.py deleted file mode 100644 index 9de689ffb98e..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/classic/humanoid/humanoid_env_cfg.py +++ /dev/null @@ -1,232 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_experimental.managers import ObservationTermCfg as ObsTerm -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import TerminationTermCfg as DoneTerm -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -import isaaclab.sim as sim_utils -from isaaclab.assets import AssetBaseCfg -from isaaclab.envs import ManagerBasedRLEnvCfg -from isaaclab.managers import EventTermCfg as EventTerm -from isaaclab.managers import ObservationGroupCfg as ObsGroup -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim import SimulationCfg -from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.classic.humanoid.mdp as mdp - -from isaaclab_assets.robots.humanoid import HUMANOID_CFG # isort:skip - - -## -# Scene definition -## - - -@configclass -class MySceneCfg(InteractiveSceneCfg): - """Configuration for the terrain scene with a humanoid robot.""" - - # terrain - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg(static_friction=1.0, dynamic_friction=1.0, restitution=0.0), - debug_vis=False, - ) - - # robot - robot = HUMANOID_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - - # lights - light = AssetBaseCfg( - prim_path="/World/light", - spawn=sim_utils.DistantLightCfg(color=(0.75, 0.75, 0.75), intensity=3000.0), - ) - - -## -# MDP settings -## - - -@configclass -class ActionsCfg: - """Action specifications for the MDP.""" - - joint_effort = mdp.JointEffortActionCfg( - asset_name="robot", - joint_names=[".*"], - scale={ - ".*_waist.*": 67.5, - ".*_upper_arm.*": 67.5, - "pelvis": 67.5, - ".*_lower_arm": 45.0, - ".*_thigh:0": 45.0, - ".*_thigh:1": 135.0, - ".*_thigh:2": 45.0, - ".*_shin": 90.0, - ".*_foot.*": 22.5, - }, - ) - - -@configclass -class ObservationsCfg: - """Observation specifications for the MDP.""" - - @configclass - class PolicyCfg(ObsGroup): - """Observations for the policy.""" - - base_height = ObsTerm(func=mdp.base_pos_z) - base_lin_vel = ObsTerm(func=mdp.base_lin_vel) - base_ang_vel = ObsTerm(func=mdp.base_ang_vel, scale=0.25) - base_yaw_roll = ObsTerm(func=mdp.base_yaw_roll) - base_angle_to_target = ObsTerm(func=mdp.base_angle_to_target, params={"target_pos": (1000.0, 0.0, 0.0)}) - base_up_proj = ObsTerm(func=mdp.base_up_proj) - base_heading_proj = ObsTerm(func=mdp.base_heading_proj, params={"target_pos": (1000.0, 0.0, 0.0)}) - joint_pos_norm = ObsTerm(func=mdp.joint_pos_limit_normalized) - joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel, scale=0.1) - actions = ObsTerm(func=mdp.last_action) - - def __post_init__(self): - self.enable_corruption = False - self.concatenate_terms = True - - # observation groups - policy: PolicyCfg = PolicyCfg() - - -@configclass -class EventCfg: - """Configuration for events.""" - - reset_base = EventTerm( - func=mdp.reset_root_state_uniform, - mode="reset", - params={"pose_range": {}, "velocity_range": {}}, - ) - - reset_robot_joints = EventTerm( - func=mdp.reset_joints_by_offset, - mode="reset", - params={ - "position_range": (-0.2, 0.2), - "velocity_range": (-0.1, 0.1), - }, - ) - - -@configclass -class RewardsCfg: - """Reward terms for the MDP.""" - - # (1) Reward for moving forward - progress = RewTerm(func=mdp.progress_reward, weight=1.0, params={"target_pos": (1000.0, 0.0, 0.0)}) - # (2) Stay alive bonus - alive = RewTerm(func=mdp.is_alive, weight=2.0) - # (3) Reward for non-upright posture - upright = RewTerm(func=mdp.upright_posture_bonus, weight=0.1, params={"threshold": 0.93}) - # (4) Reward for moving in the right direction - move_to_target = RewTerm( - func=mdp.move_to_target_bonus, weight=0.5, params={"threshold": 0.8, "target_pos": (1000.0, 0.0, 0.0)} - ) - # (5) Penalty for large action commands - action_l2 = RewTerm(func=mdp.action_l2, weight=-0.01) - # (6) Penalty for energy consumption - energy = RewTerm( - func=mdp.power_consumption, - weight=-0.005, - params={ - "gear_ratio": { - ".*_waist.*": 67.5, - ".*_upper_arm.*": 67.5, - "pelvis": 67.5, - ".*_lower_arm": 45.0, - ".*_thigh:0": 45.0, - ".*_thigh:1": 135.0, - ".*_thigh:2": 45.0, - ".*_shin": 90.0, - ".*_foot.*": 22.5, - } - }, - ) - # (7) Penalty for reaching close to joint limits - joint_pos_limits = RewTerm( - func=mdp.joint_pos_limits_penalty_ratio, - weight=-0.25, - params={ - "threshold": 0.98, - "gear_ratio": { - ".*_waist.*": 67.5, - ".*_upper_arm.*": 67.5, - "pelvis": 67.5, - ".*_lower_arm": 45.0, - ".*_thigh:0": 45.0, - ".*_thigh:1": 135.0, - ".*_thigh:2": 45.0, - ".*_shin": 90.0, - ".*_foot.*": 22.5, - }, - }, - ) - - -@configclass -class TerminationsCfg: - """Termination terms for the MDP.""" - - # (1) Terminate if the episode length is exceeded - time_out = DoneTerm(func=mdp.time_out, time_out=True) - # (2) Terminate if the robot falls - torso_height = DoneTerm(func=mdp.root_height_below_minimum, params={"minimum_height": 0.8}) - - -@configclass -class HumanoidEnvCfg(ManagerBasedRLEnvCfg): - """Configuration for the MuJoCo-style Humanoid walking environment.""" - - # Scene settings - scene: MySceneCfg = MySceneCfg(num_envs=4096, env_spacing=5.0, clone_in_fabric=True) - # Basic settings - observations: ObservationsCfg = ObservationsCfg() - actions: ActionsCfg = ActionsCfg() - # MDP settings - rewards: RewardsCfg = RewardsCfg() - terminations: TerminationsCfg = TerminationsCfg() - events: EventCfg = EventCfg() - - def __post_init__(self): - """Post initialization.""" - # general settings - self.decimation = 2 - self.episode_length_s = 16.0 - # simulation settings - self.sim: SimulationCfg = SimulationCfg( - dt=1 / 120.0, - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=80, - nconmax=25, - ls_iterations=15, - cone="pyramidal", - update_data_interval=2, - impratio=1, - integrator="implicitfast", - ), - num_substeps=2, - debug_mode=False, - ), - ) - # self.sim.dt = 1 / 120.0 - self.sim.render_interval = self.decimation - # default friction material - self.sim.physics_material.static_friction = 1.0 - self.sim.physics_material.dynamic_friction = 1.0 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/__init__.py deleted file mode 100644 index 0660d38f0658..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Locomotion experimental task registrations (manager-based).""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/__init__.py deleted file mode 100644 index 0857176a3fc7..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Velocity locomotion experimental task registrations (manager-based).""" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/__init__.py deleted file mode 100644 index 26f3257daef4..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Configurations for velocity-based locomotion environments.""" - -# We leave this file empty since we don't want to expose any configs in this package directly. -# We still need this file to import the "config" module in the parent package. diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/__init__.py deleted file mode 100644 index 895e4aac6f0e..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.contrib.velocity.config.a1 import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Velocity-Flat-UnitreeA1-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:UnitreeA1FlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:UnitreeA1FlatPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - "sb3_cfg_entry_point": f"{agents.__name__}:sb3_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/flat_env_cfg.py deleted file mode 100644 index 026e6443f768..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/flat_env_cfg.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import UnitreeA1RoughEnvCfg - - -@configclass -class UnitreeA1FlatEnvCfg(UnitreeA1RoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=60, - nconmax=30, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - # post init of parent - super().__post_init__() - - # override rewards - self.rewards.flat_orientation_l2.weight = -2.5 - self.rewards.feet_air_time.weight = 0.25 - - # change terrain to flat - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - # no height scan - # self.scene.height_scanner = None - # self.observations.policy.height_scan = None - # no terrain curriculum - self.curriculum.terrain_levels = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/rough_env_cfg.py deleted file mode 100644 index bc3806e1e606..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/a1/rough_env_cfg.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_experimental.managers import TerminationTermCfg as DoneTerm - -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.locomotion.velocity.mdp as mdp -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import ( - LocomotionVelocityRoughEnvCfg, - TerminationsCfg, -) - -from isaaclab_assets.robots.unitree import UNITREE_A1_CFG # isort: skip - - -class TerminationsCfg_A1(TerminationsCfg): - base_too_low = DoneTerm(func=mdp.root_height_below_minimum, params={"minimum_height": 0.2}) - - -@configclass -class UnitreeA1RoughEnvCfg(LocomotionVelocityRoughEnvCfg): - terminations: TerminationsCfg_A1 = TerminationsCfg_A1() - - def __post_init__(self): - # post init of parent - super().__post_init__() - - self.scene.robot = UNITREE_A1_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.scene.terrain.terrain_generator.sub_terrains["boxes"].grid_height_range = (0.025, 0.1) - self.scene.terrain.terrain_generator.sub_terrains["random_rough"].noise_range = (0.01, 0.06) - self.scene.terrain.terrain_generator.sub_terrains["random_rough"].noise_step = 0.01 - - # reduce action scale - self.actions.joint_pos.scale = 0.25 - - # event - self.events.push_robot = None - # TODO: TEMPORARILY DISABLED - adding this causes NaNs in the simulation - # self.events.add_base_mass.params["mass_distribution_params"] = (-1.0, 3.0) - # self.events.add_base_mass.params["asset_cfg"].body_names = "trunk" - self.events.base_external_force_torque.params["asset_cfg"].body_names = "trunk" - self.events.reset_robot_joints.params["position_range"] = (1.0, 1.0) - self.events.reset_base.params = { - "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)}, - "velocity_range": { - "x": (0.0, 0.0), - "y": (0.0, 0.0), - "z": (0.0, 0.0), - "roll": (0.0, 0.0), - "pitch": (0.0, 0.0), - "yaw": (0.0, 0.0), - }, - } - self.events.base_com = None - - # rewards - self.rewards.feet_air_time.params["sensor_cfg"].body_names = ".*_foot" - self.rewards.feet_air_time.weight = 0.01 - self.rewards.undesired_contacts.params["sensor_cfg"].body_names = ".*thigh" - self.rewards.dof_torques_l2.weight = -0.0002 - self.rewards.track_lin_vel_xy_exp.weight = 1.5 - self.rewards.track_ang_vel_z_exp.weight = 0.75 - self.rewards.dof_acc_l2.weight = -2.5e-7 - self.terminations.base_contact.params["sensor_cfg"].body_names = "trunk" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/__init__.py deleted file mode 100644 index 1619df4c54da..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.contrib.velocity.config.anymal_b import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Velocity-Flat-AnymalB-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:AnymalBFlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AnymalBFlatPPORunnerCfg", - "rsl_rl_with_symmetry_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AnymalBFlatPPORunnerWithSymmetryCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/flat_env_cfg.py deleted file mode 100644 index ba965fd1e742..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/flat_env_cfg.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import AnymalBRoughEnvCfg - - -@configclass -class AnymalBFlatEnvCfg(AnymalBRoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=75, - nconmax=15, - cone="elliptic", - impratio=100, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - # post init of parent - super().__post_init__() - - # override rewards - self.rewards.flat_orientation_l2.weight = -5.0 - self.rewards.dof_torques_l2.weight = -2.5e-5 - self.rewards.feet_air_time.weight = 0.5 - # change terrain to flat - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - # no height scan - self.scene.height_scanner = None - self.observations.policy.height_scan = None - # no terrain curriculum - self.curriculum.terrain_levels = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/rough_env_cfg.py deleted file mode 100644 index c49fe5310bd5..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_b/rough_env_cfg.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab.utils.configclass import configclass - -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg - -from isaaclab_assets import ANYMAL_B_CFG # isort: skip - - -@configclass -class AnymalBRoughEnvCfg(LocomotionVelocityRoughEnvCfg): - def __post_init__(self): - super().__post_init__() - self.scene.robot = ANYMAL_B_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.manager_call_max_mode = {"Scene": 1} diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/__init__.py deleted file mode 100644 index 8fcace681602..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.contrib.velocity.config.anymal_c import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Velocity-Flat-AnymalC-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:AnymalCFlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AnymalCFlatPPORunnerCfg", - "rsl_rl_with_symmetry_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AnymalCFlatPPORunnerWithSymmetryCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_flat_ppo_cfg.yaml", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/flat_env_cfg.py deleted file mode 100644 index 92a064490451..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/flat_env_cfg.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import AnymalCRoughEnvCfg - - -@configclass -class AnymalCFlatEnvCfg(AnymalCRoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=120, - nconmax=15, - cone="elliptic", - impratio=100, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - # post init of parent - super().__post_init__() - - # override rewards - self.rewards.flat_orientation_l2.weight = -5.0 - self.rewards.dof_torques_l2.weight = -2.5e-5 - self.rewards.feet_air_time.weight = 0.5 - # change terrain to flat - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - # no terrain curriculum - self.curriculum.terrain_levels = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/rough_env_cfg.py deleted file mode 100644 index 41a95468b30a..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_c/rough_env_cfg.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab.utils.configclass import configclass - -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg - -## -# Pre-defined configs -## -from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # isort: skip - - -@configclass -class AnymalCRoughEnvCfg(LocomotionVelocityRoughEnvCfg): - def __post_init__(self): - # post init of parent - super().__post_init__() - # switch robot to anymal-c - self.scene.robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.scene.robot.actuators["legs"].armature = 0.01 - self.manager_call_max_mode = {"Scene": 1} diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/__init__.py deleted file mode 100644 index f380e8e179ad..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.velocity.config.anymal_d import agents - -## -# Register Gym environments. -## - -# Rough env disabled: requires isaaclab_physx which is not yet available on dev/newton. -# The package exists on upstream/develop (commit 308400f1d35) but has not been merged. -# Re-enable once dev/newton picks up isaaclab_physx. -# gym.register( -# id="Isaac-Velocity-Rough-AnymalD-Warp-v0", -# entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", -# disable_env_checker=True, -# kwargs={ -# "env_cfg_entry_point": f"{__name__}.rough_env_cfg:AnymalDRoughEnvCfg", -# "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AnymalDRoughPPORunnerCfg", -# "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", -# }, -# ) - -gym.register( - id="Isaac-Velocity-Flat-AnymalD-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:AnymalDFlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AnymalDFlatPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/flat_env_cfg.py deleted file mode 100644 index 09c5f48c308f..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/flat_env_cfg.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import AnymalDRoughEnvCfg - - -@configclass -class AnymalDFlatEnvCfg(AnymalDRoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=60, - nconmax=25, - cone="elliptic", - impratio=100.0, - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - super().__post_init__() - self.rewards.flat_orientation_l2.weight = -5.0 - self.rewards.dof_torques_l2.weight = -2.5e-5 - self.rewards.feet_air_time.weight = 0.5 - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - self.curriculum.terrain_levels = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py deleted file mode 100644 index f23e4142232f..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab.utils.configclass import configclass - -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg - -## -# Pre-defined configs -## -from isaaclab_assets.robots.anymal import ANYMAL_D_CFG # isort: skip - - -@configclass -class AnymalDRoughEnvCfg(LocomotionVelocityRoughEnvCfg): - def __post_init__(self): - super().__post_init__() - self.scene.robot = ANYMAL_D_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.manager_call_max_mode = {"Scene": 1} diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/__init__.py deleted file mode 100644 index cd7ca934f0d7..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.velocity.config.cassie import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Velocity-Flat-Cassie-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:CassieFlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:CassieFlatPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/flat_env_cfg.py deleted file mode 100644 index cf977d7cb0a9..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/flat_env_cfg.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import CassieRoughEnvCfg - - -@configclass -class CassieFlatEnvCfg(CassieRoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=52, - nconmax=15, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - super().__post_init__() - self.rewards.flat_orientation_l2.weight = -2.5 - self.rewards.feet_air_time.weight = 5.0 - self.rewards.joint_deviation_hip.params["asset_cfg"].joint_names = ["hip_rotation_.*"] - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - self.curriculum.terrain_levels = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/rough_env_cfg.py deleted file mode 100644 index 3287dce1d209..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/cassie/rough_env_cfg.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import SceneEntityCfg - -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.locomotion.velocity.mdp as mdp -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import ( - LocomotionVelocityRoughEnvCfg, - RewardsCfg, -) - -from isaaclab_assets.robots.cassie import CASSIE_CFG # isort: skip - - -@configclass -class CassieRewardsCfg(RewardsCfg): - termination_penalty = RewTerm(func=mdp.is_terminated, weight=-200.0) - feet_air_time = RewTerm( - func=mdp.feet_air_time_positive_biped, - weight=2.5, - params={ - "sensor_cfg": SceneEntityCfg("contact_forces", body_names=".*toe"), - "command_name": "base_velocity", - "threshold": 0.3, - }, - ) - joint_deviation_hip = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.2, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=["hip_abduction_.*", "hip_rotation_.*"])}, - ) - joint_deviation_toes = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.2, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=["toe_joint_.*"])}, - ) - dof_pos_limits = RewTerm( - func=mdp.joint_pos_limits, - weight=-1.0, - params={"asset_cfg": SceneEntityCfg("robot", joint_names="toe_joint_.*")}, - ) - - -@configclass -class CassieRoughEnvCfg(LocomotionVelocityRoughEnvCfg): - rewards: CassieRewardsCfg = CassieRewardsCfg() - - def __post_init__(self): - super().__post_init__() - self.scene.robot = CASSIE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.actions.joint_pos.scale = 0.5 - self.events.push_robot = None - # TODO: TEMPORARILY DISABLED - adding this causes NaNs in the simulation - # self.events.add_base_mass = None - self.events.reset_robot_joints.params["position_range"] = (1.0, 1.0) - self.events.base_external_force_torque.params["asset_cfg"].body_names = [".*pelvis"] - self.events.reset_base.params = { - "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)}, - "velocity_range": { - "x": (0.0, 0.0), - "y": (0.0, 0.0), - "z": (0.0, 0.0), - "roll": (0.0, 0.0), - "pitch": (0.0, 0.0), - "yaw": (0.0, 0.0), - }, - } - self.events.base_com = None - self.terminations.base_contact.params["sensor_cfg"].body_names = [".*pelvis"] - self.rewards.undesired_contacts = None - self.rewards.dof_torques_l2.weight = -5.0e-6 - self.rewards.track_lin_vel_xy_exp.weight = 2.0 - self.rewards.track_ang_vel_z_exp.weight = 1.0 - self.rewards.action_rate_l2.weight *= 1.5 - self.rewards.dof_acc_l2.weight *= 1.5 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/__init__.py deleted file mode 100644 index 19c556425294..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.velocity.config.g1 import agents - -## -# Register Gym environments. -## - -# gym.register( -# id="Isaac-Velocity-Rough-G1-Warp-v0", -# entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", -# disable_env_checker=True, -# kwargs={ -# "env_cfg_entry_point": f"{__name__}.rough_env_cfg:G1RoughEnvCfg", -# "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:G1RoughPPORunnerCfg", -# "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", -# }, -# ) - - -gym.register( - id="Isaac-Velocity-Flat-G1-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:G1FlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:G1FlatPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/flat_env_cfg.py deleted file mode 100644 index 11abf5a05500..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/flat_env_cfg.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_experimental.managers import SceneEntityCfg -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import G1RoughEnvCfg - - -@configclass -class G1FlatEnvCfg(G1RoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=95, - nconmax=10, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - super().__post_init__() - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - self.curriculum.terrain_levels = None - self.rewards.track_ang_vel_z_exp.weight = 1.0 - self.rewards.lin_vel_z_l2.weight = -0.2 - self.rewards.action_rate_l2.weight = -0.005 - self.rewards.dof_acc_l2.weight = -1.0e-7 - self.rewards.feet_air_time.weight = 0.75 - self.rewards.feet_air_time.params["threshold"] = 0.4 - self.rewards.dof_torques_l2.weight = -2.0e-6 - self.rewards.dof_torques_l2.params["asset_cfg"] = SceneEntityCfg( - "robot", joint_names=[".*_hip_.*", ".*_knee_joint"] - ) - self.commands.base_velocity.ranges.lin_vel_x = (0.0, 1.0) - self.commands.base_velocity.ranges.lin_vel_y = (-0.5, 0.5) - self.commands.base_velocity.ranges.ang_vel_z = (-1.0, 1.0) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/rough_env_cfg.py deleted file mode 100644 index 7854aac087ef..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/g1/rough_env_cfg.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import SceneEntityCfg - -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.locomotion.velocity.mdp as mdp -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import ( - LocomotionVelocityRoughEnvCfg, - RewardsCfg, -) - -## -# Pre-defined configs -## -from isaaclab_assets import G1_MINIMAL_CFG # isort: skip - - -@configclass -class G1Rewards(RewardsCfg): - """Reward terms for the MDP.""" - - termination_penalty = RewTerm(func=mdp.is_terminated, weight=-200.0) - track_lin_vel_xy_exp = RewTerm( - func=mdp.track_lin_vel_xy_yaw_frame_exp, - weight=1.0, - params={"command_name": "base_velocity", "std": 0.5}, - ) - track_ang_vel_z_exp = RewTerm( - func=mdp.track_ang_vel_z_world_exp, weight=2.0, params={"command_name": "base_velocity", "std": 0.5} - ) - feet_air_time = RewTerm( - func=mdp.feet_air_time_positive_biped, - weight=0.25, - params={ - "command_name": "base_velocity", - "sensor_cfg": SceneEntityCfg("contact_forces", body_names=".*_ankle_roll_link"), - "threshold": 0.4, - }, - ) - feet_slide = RewTerm( - func=mdp.feet_slide, - weight=-0.1, - params={ - "sensor_cfg": SceneEntityCfg("contact_forces", body_names=".*_ankle_roll_link"), - "asset_cfg": SceneEntityCfg("robot", body_names=".*_ankle_roll_link"), - }, - ) - dof_pos_limits = RewTerm( - func=mdp.joint_pos_limits, - weight=-1.0, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=[".*_ankle_pitch_joint", ".*_ankle_roll_joint"])}, - ) - joint_deviation_hip = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.1, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=[".*_hip_yaw_joint", ".*_hip_roll_joint"])}, - ) - joint_deviation_arms = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.1, - params={ - "asset_cfg": SceneEntityCfg( - "robot", - joint_names=[ - ".*_shoulder_pitch_joint", - ".*_shoulder_roll_joint", - ".*_shoulder_yaw_joint", - ".*_elbow_pitch_joint", - ".*_elbow_roll_joint", - ], - ) - }, - ) - joint_deviation_fingers = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.05, - params={ - "asset_cfg": SceneEntityCfg( - "robot", - joint_names=[ - ".*_five_joint", - ".*_three_joint", - ".*_six_joint", - ".*_four_joint", - ".*_zero_joint", - ".*_one_joint", - ".*_two_joint", - ], - ) - }, - ) - joint_deviation_torso = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.1, - params={"asset_cfg": SceneEntityCfg("robot", joint_names="torso_joint")}, - ) - - -@configclass -class G1RoughEnvCfg(LocomotionVelocityRoughEnvCfg): - rewards: G1Rewards = G1Rewards() - - def __post_init__(self): - super().__post_init__() - self.scene.robot = G1_MINIMAL_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.events.push_robot = None - # TODO: TEMPORARILY DISABLED - adding this causes NaNs in the simulation - # self.events.add_base_mass = None - self.events.reset_robot_joints.params["position_range"] = (1.0, 1.0) - self.events.base_external_force_torque.params["asset_cfg"].body_names = ["torso_link"] - self.events.reset_base.params = { - "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)}, - "velocity_range": { - "x": (0.0, 0.0), - "y": (0.0, 0.0), - "z": (0.0, 0.0), - "roll": (0.0, 0.0), - "pitch": (0.0, 0.0), - "yaw": (0.0, 0.0), - }, - } - self.events.base_com = None - - # Rewards - self.rewards.lin_vel_z_l2.weight = 0.0 - self.rewards.undesired_contacts = None - self.rewards.flat_orientation_l2.weight = -1.0 - self.rewards.action_rate_l2.weight = -0.005 - self.rewards.dof_acc_l2.weight = -1.25e-7 - self.rewards.dof_acc_l2.params["asset_cfg"] = SceneEntityCfg( - "robot", joint_names=[".*_hip_.*", ".*_knee_joint"] - ) - self.rewards.dof_torques_l2.weight = -1.5e-7 - self.rewards.dof_torques_l2.params["asset_cfg"] = SceneEntityCfg( - "robot", joint_names=[".*_hip_.*", ".*_knee_joint", ".*_ankle_.*"] - ) - - # Commands - self.commands.base_velocity.ranges.lin_vel_x = (0.0, 1.0) - self.commands.base_velocity.ranges.lin_vel_y = (-0.0, 0.0) - self.commands.base_velocity.ranges.ang_vel_z = (-1.0, 1.0) - - # terminations - self.terminations.base_contact.params["sensor_cfg"].body_names = "torso_link" - - def play_mode(self): - # play-mode overrides of parent - super().play_mode() - - self.episode_length_s = 40.0 - self.commands.base_velocity.ranges.lin_vel_x = (1.0, 1.0) - self.commands.base_velocity.ranges.lin_vel_y = (0.0, 0.0) - self.commands.base_velocity.ranges.ang_vel_z = (-1.0, 1.0) - self.commands.base_velocity.ranges.heading = (0.0, 0.0) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/__init__.py deleted file mode 100644 index 819107f671b7..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.contrib.velocity.config.go1 import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Velocity-Flat-UnitreeGo1-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:UnitreeGo1FlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:UnitreeGo1FlatPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/flat_env_cfg.py deleted file mode 100644 index 9169d5ffa1dc..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/flat_env_cfg.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import UnitreeGo1RoughEnvCfg - - -@configclass -class UnitreeGo1FlatEnvCfg(UnitreeGo1RoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=60, - nconmax=25, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - super().__post_init__() - self.rewards.flat_orientation_l2.weight = -2.5 - self.rewards.feet_air_time.weight = 0.25 - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - self.curriculum.terrain_levels = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/rough_env_cfg.py deleted file mode 100644 index bc1412bafff8..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go1/rough_env_cfg.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab.utils.configclass import configclass - -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg - -from isaaclab_assets.robots.unitree import UNITREE_GO1_CFG # isort: skip - - -@configclass -class UnitreeGo1RoughEnvCfg(LocomotionVelocityRoughEnvCfg): - def __post_init__(self): - super().__post_init__() - self.scene.robot = UNITREE_GO1_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.manager_call_max_mode = {"Scene": 1} - self.scene.terrain.terrain_generator.sub_terrains["boxes"].grid_height_range = (0.025, 0.1) - self.scene.terrain.terrain_generator.sub_terrains["random_rough"].noise_range = (0.01, 0.06) - self.scene.terrain.terrain_generator.sub_terrains["random_rough"].noise_step = 0.01 - self.actions.joint_pos.scale = 0.25 - self.events.push_robot = None - self.events.base_external_force_torque.params["asset_cfg"].body_names = "trunk" - self.events.reset_robot_joints.params["position_range"] = (1.0, 1.0) - self.events.reset_base.params = { - "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)}, - "velocity_range": { - "x": (0.0, 0.0), - "y": (0.0, 0.0), - "z": (0.0, 0.0), - "roll": (0.0, 0.0), - "pitch": (0.0, 0.0), - "yaw": (0.0, 0.0), - }, - } - self.events.base_com = None - self.rewards.feet_air_time.params["sensor_cfg"].body_names = ".*_foot" - self.rewards.feet_air_time.weight = 0.01 - self.rewards.undesired_contacts = None - self.rewards.dof_torques_l2.weight = -0.0002 - self.rewards.track_lin_vel_xy_exp.weight = 1.5 - self.rewards.track_ang_vel_z_exp.weight = 0.75 - self.rewards.dof_acc_l2.weight = -2.5e-7 - self.terminations.base_contact.params["sensor_cfg"].body_names = "trunk" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/__init__.py deleted file mode 100644 index 73a45ecc104c..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.velocity.config.go2 import agents - -## -# Register Gym environments. -## - -gym.register( - id="Isaac-Velocity-Flat-UnitreeGo2-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:UnitreeGo2FlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:UnitreeGo2FlatPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/flat_env_cfg.py deleted file mode 100644 index 6e0891437cea..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/flat_env_cfg.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import UnitreeGo2RoughEnvCfg - - -@configclass -class UnitreeGo2FlatEnvCfg(UnitreeGo2RoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=65, - nconmax=35, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - super().__post_init__() - self.rewards.flat_orientation_l2.weight = -2.5 - self.rewards.feet_air_time.weight = 0.25 - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - self.curriculum.terrain_levels = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/rough_env_cfg.py deleted file mode 100644 index 7cbb4e233d14..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/go2/rough_env_cfg.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab.utils.configclass import configclass - -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg - -from isaaclab_assets.robots.unitree import UNITREE_GO2_CFG # isort: skip - - -@configclass -class UnitreeGo2RoughEnvCfg(LocomotionVelocityRoughEnvCfg): - def __post_init__(self): - super().__post_init__() - self.scene.robot = UNITREE_GO2_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.scene.terrain.terrain_generator.sub_terrains["boxes"].grid_height_range = (0.025, 0.1) - self.scene.terrain.terrain_generator.sub_terrains["random_rough"].noise_range = (0.01, 0.06) - self.scene.terrain.terrain_generator.sub_terrains["random_rough"].noise_step = 0.01 - self.actions.joint_pos.scale = 0.25 - self.events.push_robot = None - self.events.base_external_force_torque.params["asset_cfg"].body_names = "base" - self.events.reset_robot_joints.params["position_range"] = (1.0, 1.0) - self.events.reset_base.params = { - "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)}, - "velocity_range": { - "x": (0.0, 0.0), - "y": (0.0, 0.0), - "z": (0.0, 0.0), - "roll": (0.0, 0.0), - "pitch": (0.0, 0.0), - "yaw": (0.0, 0.0), - }, - } - self.events.base_com = None - self.rewards.feet_air_time.params["sensor_cfg"].body_names = ".*_foot" - self.rewards.feet_air_time.weight = 0.01 - self.rewards.undesired_contacts = None - self.rewards.dof_torques_l2.weight = -0.0002 - self.rewards.track_lin_vel_xy_exp.weight = 1.5 - self.rewards.track_ang_vel_z_exp.weight = 0.75 - self.rewards.dof_acc_l2.weight = -2.5e-7 - self.terminations.base_contact.params["sensor_cfg"].body_names = "base" diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/__init__.py deleted file mode 100644 index dadcf5430095..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.velocity.config.h1 import agents - -## -# Register Gym environments. -## - -# gym.register( -# id="Isaac-Velocity-Rough-H1-Warp-v0", -# entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", -# disable_env_checker=True, -# kwargs={ -# "env_cfg_entry_point": f"{__name__}.rough_env_cfg:H1RoughEnvCfg", -# "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:H1RoughPPORunnerCfg", -# "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", -# }, -# ) - -gym.register( - id="Isaac-Velocity-Flat-H1-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.flat_env_cfg:H1FlatEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:H1FlatPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/flat_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/flat_env_cfg.py deleted file mode 100644 index 08b009841a94..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/flat_env_cfg.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -from .rough_env_cfg import H1RoughEnvCfg - - -@configclass -class H1FlatEnvCfg(H1RoughEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=65, - nconmax=15, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - super().__post_init__() - self.scene.terrain.terrain_type = "plane" - self.scene.terrain.terrain_generator = None - self.curriculum.terrain_levels = None - self.rewards.feet_air_time.weight = 1.0 - self.rewards.feet_air_time.params["threshold"] = 0.6 diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/rough_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/rough_env_cfg.py deleted file mode 100644 index 98f783e5ac96..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/config/h1/rough_env_cfg.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import SceneEntityCfg - -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.locomotion.velocity.mdp as mdp -from isaaclab_tasks_experimental.manager_based.locomotion.velocity.velocity_env_cfg import ( - LocomotionVelocityRoughEnvCfg, - RewardsCfg, -) - -## -# Pre-defined configs -## -from isaaclab_assets import H1_MINIMAL_CFG # isort: skip - - -@configclass -class H1Rewards(RewardsCfg): - """Reward terms for the MDP.""" - - termination_penalty = RewTerm(func=mdp.is_terminated, weight=-200.0) - lin_vel_z_l2 = None - track_lin_vel_xy_exp = RewTerm( - func=mdp.track_lin_vel_xy_yaw_frame_exp, - weight=1.0, - params={"command_name": "base_velocity", "std": 0.5}, - ) - track_ang_vel_z_exp = RewTerm( - func=mdp.track_ang_vel_z_world_exp, weight=1.0, params={"command_name": "base_velocity", "std": 0.5} - ) - feet_air_time = RewTerm( - func=mdp.feet_air_time_positive_biped, - weight=0.25, - params={ - "command_name": "base_velocity", - "sensor_cfg": SceneEntityCfg("contact_forces", body_names=".*ankle_link"), - "threshold": 0.4, - }, - ) - feet_slide = RewTerm( - func=mdp.feet_slide, - weight=-0.25, - params={ - "sensor_cfg": SceneEntityCfg("contact_forces", body_names=".*ankle_link"), - "asset_cfg": SceneEntityCfg("robot", body_names=".*ankle_link"), - }, - ) - dof_pos_limits = RewTerm( - func=mdp.joint_pos_limits, weight=-1.0, params={"asset_cfg": SceneEntityCfg("robot", joint_names=".*_ankle")} - ) - joint_deviation_hip = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.2, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=[".*_hip_yaw", ".*_hip_roll"])}, - ) - joint_deviation_arms = RewTerm( - func=mdp.joint_deviation_l1, - weight=-0.2, - params={"asset_cfg": SceneEntityCfg("robot", joint_names=[".*_shoulder_.*", ".*_elbow"])}, - ) - joint_deviation_torso = RewTerm( - func=mdp.joint_deviation_l1, weight=-0.1, params={"asset_cfg": SceneEntityCfg("robot", joint_names="torso")} - ) - - -@configclass -class H1RoughEnvCfg(LocomotionVelocityRoughEnvCfg): - rewards: H1Rewards = H1Rewards() - - def __post_init__(self): - super().__post_init__() - self.scene.robot = H1_MINIMAL_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - self.events.push_robot = None - self.events.reset_robot_joints.params["position_range"] = (1.0, 1.0) - self.events.base_external_force_torque.params["asset_cfg"].body_names = [".*torso_link"] - self.events.reset_base.params = { - "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)}, - "velocity_range": { - "x": (0.0, 0.0), - "y": (0.0, 0.0), - "z": (0.0, 0.0), - "roll": (0.0, 0.0), - "pitch": (0.0, 0.0), - "yaw": (0.0, 0.0), - }, - } - self.events.base_com = None - self.rewards.undesired_contacts = None - self.rewards.flat_orientation_l2.weight = -1.0 - self.rewards.dof_torques_l2.weight = 0.0 - self.rewards.action_rate_l2.weight = -0.005 - self.rewards.dof_acc_l2.weight = -1.25e-7 - self.commands.base_velocity.ranges.lin_vel_x = (0.0, 1.0) - self.commands.base_velocity.ranges.lin_vel_y = (0.0, 0.0) - self.commands.base_velocity.ranges.ang_vel_z = (-1.0, 1.0) - self.terminations.base_contact.params["sensor_cfg"].body_names = ".*torso_link" - - def play_mode(self): - # play-mode overrides of parent - super().play_mode() - - self.episode_length_s = 40.0 - self.commands.base_velocity.ranges.lin_vel_x = (1.0, 1.0) - self.commands.base_velocity.ranges.lin_vel_y = (0.0, 0.0) - self.commands.base_velocity.ranges.ang_vel_z = (-1.0, 1.0) - self.commands.base_velocity.ranges.heading = (0.0, 0.0) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/velocity_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/velocity_env_cfg.py deleted file mode 100644 index 8e0f9f67c007..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/locomotion/velocity/velocity_env_cfg.py +++ /dev/null @@ -1,310 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import math -from dataclasses import MISSING - -from isaaclab_experimental.managers import ObservationTermCfg as ObsTerm -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import SceneEntityCfg -from isaaclab_experimental.managers import TerminationTermCfg as DoneTerm - -import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.envs import ManagerBasedRLEnvCfg -from isaaclab.managers import CurriculumTermCfg as CurrTerm -from isaaclab.managers import EventTermCfg as EventTerm -from isaaclab.managers import ObservationGroupCfg as ObsGroup -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sensors import ContactSensorCfg -from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR -from isaaclab.utils.configclass import configclass -from isaaclab.utils.noise import UniformNoiseCfg as Unoise - -import isaaclab_tasks_experimental.manager_based.locomotion.velocity.mdp as mdp - -## -# Pre-defined configs -## -from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG # isort: skip - - -## -# Scene definition -## - - -@configclass -class MySceneCfg(InteractiveSceneCfg): - """Configuration for the terrain scene with a legged robot.""" - - # ground terrain - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="generator", - terrain_generator=ROUGH_TERRAINS_CFG, - max_init_terrain_level=5, - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg( - friction_combine_mode="multiply", - restitution_combine_mode="multiply", - static_friction=1.0, - dynamic_friction=1.0, - ), - visual_material=sim_utils.MdlFileCfg( - mdl_path=f"{ISAACLAB_NUCLEUS_DIR}/Materials/TilesMarbleSpiderWhiteBrickBondHoned/TilesMarbleSpiderWhiteBrickBondHoned.mdl", - project_uvw=True, - texture_scale=(0.25, 0.25), - ), - debug_vis=False, - ) - # robots - robot: ArticulationCfg = MISSING - # sensors - contact_forces = ContactSensorCfg( - prim_path="{ENV_REGEX_NS}/Robot/.*", - filter_prim_paths_expr=[], - history_length=3, - track_air_time=True, - ) - # lights - sky_light = AssetBaseCfg( - prim_path="/World/skyLight", - spawn=sim_utils.DomeLightCfg( - intensity=750.0, - texture_file=f"{ISAAC_NUCLEUS_DIR}/Materials/Textures/Skies/PolyHaven/kloofendal_43d_clear_puresky_4k.hdr", - ), - ) - - -## -# MDP settings -## - - -@configclass -class CommandsCfg: - """Command specifications for the MDP.""" - - base_velocity = mdp.UniformVelocityCommandCfg( - asset_name="robot", - resampling_time_range=(10.0, 10.0), - rel_standing_envs=0.02, - rel_heading_envs=1.0, - heading_command=True, - heading_control_stiffness=0.5, - debug_vis=True, - ranges=mdp.UniformVelocityCommandCfg.Ranges( - lin_vel_x=(-1.0, 1.0), lin_vel_y=(-1.0, 1.0), ang_vel_z=(-1.0, 1.0), heading=(-math.pi, math.pi) - ), - ) - - -@configclass -class ActionsCfg: - """Action specifications for the MDP.""" - - joint_pos = mdp.JointPositionActionCfg(asset_name="robot", joint_names=[".*"], scale=0.5, use_default_offset=True) - - -@configclass -class PolicyCfg(ObsGroup): - """Observations for policy group.""" - - # observation terms (order preserved) - base_lin_vel = ObsTerm(func=mdp.base_lin_vel, noise=Unoise(n_min=-0.1, n_max=0.1)) - base_ang_vel = ObsTerm(func=mdp.base_ang_vel, noise=Unoise(n_min=-0.2, n_max=0.2)) - projected_gravity = ObsTerm( - func=mdp.projected_gravity, - noise=Unoise(n_min=-0.05, n_max=0.05), - ) - velocity_commands = ObsTerm(func=mdp.generated_commands, params={"command_name": "base_velocity"}) - joint_pos = ObsTerm(func=mdp.joint_pos_rel, noise=Unoise(n_min=-0.01, n_max=0.01)) - joint_vel = ObsTerm(func=mdp.joint_vel_rel, noise=Unoise(n_min=-1.5, n_max=1.5)) - actions = ObsTerm(func=mdp.last_action) - - def __post_init__(self): - self.enable_corruption = True - self.concatenate_terms = True - - -@configclass -class ObservationsCfg: - """Observation specifications for the MDP.""" - - # observation groups - policy: PolicyCfg = PolicyCfg() - - -@configclass -class EventCfg: - """Configuration for events.""" - - # FIXME(warp-migration): COM randomization in exp manager-based locomotion currently causes - # NaNs and is temporarily disabled. - # base_com = EventTerm( - # func=mdp.randomize_rigid_body_com, - # mode="startup", - # params={ - # "asset_cfg": SceneEntityCfg("robot", body_names="base"), - # "com_range": {"x": (-0.05, 0.05), "y": (-0.05, 0.05), "z": (-0.01, 0.01)}, - # }, - # ) - base_com = None - - # reset - base_external_force_torque = EventTerm( - func=mdp.apply_external_force_torque, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names="base"), - "force_range": (0.0, 0.0), - "torque_range": (-0.0, 0.0), - }, - ) - - reset_base = EventTerm( - func=mdp.reset_root_state_uniform, - mode="reset", - params={ - "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)}, - "velocity_range": { - "x": (-0.5, 0.5), - "y": (-0.5, 0.5), - "z": (-0.5, 0.5), - "roll": (-0.5, 0.5), - "pitch": (-0.5, 0.5), - "yaw": (-0.5, 0.5), - }, - }, - ) - - reset_robot_joints = EventTerm( - func=mdp.reset_joints_by_scale, - mode="reset", - params={ - "position_range": (0.5, 1.5), - "velocity_range": (0.0, 0.0), - }, - ) - - # interval - push_robot = EventTerm( - func=mdp.push_by_setting_velocity, - mode="interval", - interval_range_s=(10.0, 15.0), - params={"velocity_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5)}}, - ) - - -@configclass -class RewardsCfg: - """Reward terms for the MDP.""" - - # -- task - track_lin_vel_xy_exp = RewTerm( - func=mdp.track_lin_vel_xy_exp, weight=1.0, params={"command_name": "base_velocity", "std": math.sqrt(0.25)} - ) - track_ang_vel_z_exp = RewTerm( - func=mdp.track_ang_vel_z_exp, weight=0.5, params={"command_name": "base_velocity", "std": math.sqrt(0.25)} - ) - # -- penalties - lin_vel_z_l2 = RewTerm(func=mdp.lin_vel_z_l2, weight=-2.0) - ang_vel_xy_l2 = RewTerm(func=mdp.ang_vel_xy_l2, weight=-0.05) - dof_torques_l2 = RewTerm(func=mdp.joint_torques_l2, weight=-1.0e-5) - dof_acc_l2 = RewTerm(func=mdp.joint_acc_l2, weight=-2.5e-7) - action_rate_l2 = RewTerm(func=mdp.action_rate_l2, weight=-0.01) - feet_air_time = RewTerm( - func=mdp.feet_air_time, - weight=0.125, - params={ - "sensor_cfg": SceneEntityCfg("contact_forces", body_names=".*FOOT"), - "command_name": "base_velocity", - "threshold": 0.5, - }, - ) - undesired_contacts = RewTerm( - func=mdp.undesired_contacts, - weight=-1.0, - params={"sensor_cfg": SceneEntityCfg("contact_forces", body_names=".*THIGH"), "threshold": 1.0}, - ) - # -- optional penalties - flat_orientation_l2 = RewTerm(func=mdp.flat_orientation_l2, weight=0.0) - dof_pos_limits = RewTerm(func=mdp.joint_pos_limits, weight=0.0) - - -@configclass -class TerminationsCfg: - """Termination terms for the MDP.""" - - time_out = DoneTerm(func=mdp.time_out, time_out=True) - base_contact = DoneTerm( - func=mdp.illegal_contact, - params={"sensor_cfg": SceneEntityCfg("contact_forces", body_names="base"), "threshold": 1.0}, - ) - - -@configclass -class CurriculumCfg: - """Curriculum terms for the MDP.""" - - terrain_levels = CurrTerm(func=mdp.terrain_levels_vel) - - -## -# Environment configuration -## - - -@configclass -class LocomotionVelocityRoughEnvCfg(ManagerBasedRLEnvCfg): - """Configuration for the locomotion velocity-tracking environment.""" - - # Scene settings - scene: MySceneCfg = MySceneCfg(num_envs=4096, env_spacing=2.5, replicate_physics=True) - # Basic settings - observations: ObservationsCfg = ObservationsCfg() - actions: ActionsCfg = ActionsCfg() - commands: CommandsCfg = CommandsCfg() - # MDP settings - rewards: RewardsCfg = RewardsCfg() - terminations: TerminationsCfg = TerminationsCfg() - events: EventCfg = EventCfg() - curriculum: CurriculumCfg = CurriculumCfg() - - def __post_init__(self): - """Post initialization.""" - # general settings - self.decimation = 4 - self.episode_length_s = 20.0 - # simulation settings - self.sim.dt = 1.0 / 200.0 - self.sim.render_interval = self.decimation - self.sim.physics_material = self.scene.terrain.physics_material - # update sensor update periods - if self.scene.contact_forces is not None: - self.scene.contact_forces.update_period = self.sim.dt - # check if terrain levels curriculum is enabled - if getattr(self.curriculum, "terrain_levels", None) is not None: - if self.scene.terrain.terrain_generator is not None: - self.scene.terrain.terrain_generator.curriculum = True - else: - if self.scene.terrain.terrain_generator is not None: - self.scene.terrain.terrain_generator.curriculum = False - - def play_mode(self): - """Play-mode overrides shared by the velocity-tracking environments.""" - super().play_mode() - # spawn the robot randomly in the grid (instead of their terrain levels) - self.scene.terrain.max_init_terrain_level = None - # reduce the number of terrains to save memory - if self.scene.terrain.terrain_generator is not None: - self.scene.terrain.terrain_generator.num_rows = 5 - self.scene.terrain.terrain_generator.num_cols = 5 - self.scene.terrain.terrain_generator.curriculum = False - # remove random pushing events - self.events.base_external_force_torque = None - self.events.push_robot = None diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/__init__.py deleted file mode 100644 index 6cd56351b6e5..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from .reach import * diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/__init__.py deleted file mode 100644 index 460a30569089..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/__init__.py deleted file mode 100644 index 827015b953e6..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -# Reuse agent configs from the stable task package. -from isaaclab_tasks.core.reach.config.franka import agents - -## -# Register Gym environments. -## - -## -# Joint Position Control -## - -gym.register( - id="Isaac-Reach-Franka-Warp-v0", - entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.joint_pos_env_cfg:FrankaReachEnvCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:FrankaReachPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, -) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/joint_pos_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/joint_pos_env_cfg.py deleted file mode 100644 index 3203f9987f9f..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/franka/joint_pos_env_cfg.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import math - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.manipulation.reach.mdp as mdp -from isaaclab_tasks_experimental.manager_based.manipulation.reach.reach_env_cfg import ReachEnvCfg - -## -# Pre-defined configs -## -from isaaclab_assets import FRANKA_PANDA_CFG # isort: skip - - -## -# Environment configuration -## - - -@configclass -class FrankaReachEnvCfg(ReachEnvCfg): - sim: SimulationCfg = SimulationCfg( - dt=1 / 60, - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=50, - nconmax=20, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ), - ) - - def __post_init__(self): - # post init of parent - super().__post_init__() - - # switch robot to franka - self.scene.robot = FRANKA_PANDA_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - # override rewards - self.rewards.end_effector_position_tracking.params["asset_cfg"].body_names = ["panda_hand"] - self.rewards.end_effector_position_tracking_fine_grained.params["asset_cfg"].body_names = ["panda_hand"] - self.rewards.end_effector_orientation_tracking.params["asset_cfg"].body_names = ["panda_hand"] - - # override actions - self.actions.arm_action = mdp.JointPositionActionCfg( - asset_name="robot", joint_names=["panda_joint.*"], scale=0.5, use_default_offset=True - ) - # override command generator body - # end-effector is along z-direction - self.commands.ee_pose.body_name = "panda_hand" - self.commands.ee_pose.ranges.pitch = (math.pi, math.pi) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/__init__.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/__init__.py deleted file mode 100644 index 022224150585..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -# UR10 env disabled: USD asset has composition errors (broken asset file). -# Fails on both torch baseline and warp with: -# RuntimeError: USD stage has composition errors while loading provided stage -# Re-enable once the UR10 USD asset is fixed. - -# import gymnasium as gym -# from isaaclab_tasks.core.reach.config.ur_10 import agents - -# gym.register( -# id="Isaac-Reach-UR10-Warp-v0", -# entry_point="isaaclab_experimental.envs:ManagerBasedRLEnvWarp", -# disable_env_checker=True, -# kwargs={ -# "env_cfg_entry_point": f"{__name__}.joint_pos_env_cfg:UR10ReachEnvCfg", -# "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", -# "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:UR10ReachPPORunnerCfg", -# "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", -# }, -# ) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/joint_pos_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/joint_pos_env_cfg.py deleted file mode 100644 index 46a06f48de6a..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/config/ur_10/joint_pos_env_cfg.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import math - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - -from isaaclab.sim import SimulationCfg -from isaaclab.utils.configclass import configclass - -import isaaclab_tasks_experimental.manager_based.manipulation.reach.mdp as mdp -from isaaclab_tasks_experimental.manager_based.manipulation.reach.reach_env_cfg import ReachEnvCfg - -## -# Pre-defined configs -## -from isaaclab_assets import UR10_CFG # isort: skip - - -## -# Environment configuration -## - - -@configclass -class UR10ReachEnvCfg(ReachEnvCfg): - sim: SimulationCfg = SimulationCfg( - physics=NewtonCfg( - solver_cfg=MJWarpSolverCfg( - njmax=50, - nconmax=20, - cone="pyramidal", - impratio=1, - integrator="implicitfast", - ), - num_substeps=1, - debug_mode=False, - ) - ) - - def __post_init__(self): - # post init of parent - super().__post_init__() - - # switch robot to ur10 - self.scene.robot = UR10_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - # override events - self.events.reset_robot_joints.params["position_range"] = (0.75, 1.25) - # override rewards - self.rewards.end_effector_position_tracking.params["asset_cfg"].body_names = ["ee_link"] - self.rewards.end_effector_position_tracking_fine_grained.params["asset_cfg"].body_names = ["ee_link"] - self.rewards.end_effector_orientation_tracking.params["asset_cfg"].body_names = ["ee_link"] - # override actions - self.actions.arm_action = mdp.JointPositionActionCfg( - asset_name="robot", joint_names=[".*"], scale=0.5, use_default_offset=True - ) - # override command generator body - # end-effector is along x-direction - self.commands.ee_pose.body_name = "ee_link" - self.commands.ee_pose.ranges.pitch = (math.pi / 2, math.pi / 2) diff --git a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/reach_env_cfg.py b/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/reach_env_cfg.py deleted file mode 100644 index e83a27e44db5..000000000000 --- a/source/isaaclab_tasks_experimental/isaaclab_tasks_experimental/manager_based/manipulation/reach/reach_env_cfg.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from dataclasses import MISSING - -from isaaclab_experimental.managers import ObservationTermCfg as ObsTerm -from isaaclab_experimental.managers import RewardTermCfg as RewTerm -from isaaclab_experimental.managers import SceneEntityCfg -from isaaclab_experimental.managers import TerminationTermCfg as DoneTerm - -import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.envs import ManagerBasedRLEnvCfg -from isaaclab.managers import ActionTermCfg as ActionTerm -from isaaclab.managers import CurriculumTermCfg as CurrTerm -from isaaclab.managers import EventTermCfg as EventTerm -from isaaclab.managers import ObservationGroupCfg as ObsGroup -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.utils.configclass import configclass -from isaaclab.utils.noise import UniformNoiseCfg as Unoise - -import isaaclab_tasks_experimental.manager_based.manipulation.reach.mdp as mdp - -## -# Scene definition -## - - -@configclass -class ReachSceneCfg(InteractiveSceneCfg): - """Configuration for the scene with a robotic arm.""" - - # world - ground = AssetBaseCfg( - prim_path="/World/ground", - spawn=sim_utils.GroundPlaneCfg(), - init_state=AssetBaseCfg.InitialStateCfg(pos=(0.0, 0.0, -1.05)), - ) - - # robots - robot: ArticulationCfg = MISSING - - # lights - light = AssetBaseCfg( - prim_path="/World/light", - spawn=sim_utils.DomeLightCfg(color=(0.75, 0.75, 0.75), intensity=2500.0), - ) - - -## -# MDP settings -## - - -@configclass -class CommandsCfg: - """Command terms for the MDP.""" - - ee_pose = mdp.UniformPoseCommandCfg( - asset_name="robot", - body_name=MISSING, - resampling_time_range=(4.0, 4.0), - debug_vis=True, - ranges=mdp.UniformPoseCommandCfg.Ranges( - pos_x=(0.35, 0.65), - pos_y=(-0.2, 0.2), - pos_z=(0.15, 0.5), - roll=(0.0, 0.0), - pitch=MISSING, # depends on end-effector axis - yaw=(-3.14, 3.14), - ), - ) - - -@configclass -class ActionsCfg: - """Action specifications for the MDP.""" - - arm_action: ActionTerm = MISSING - gripper_action: ActionTerm | None = None - - -@configclass -class ObservationsCfg: - """Observation specifications for the MDP.""" - - @configclass - class PolicyCfg(ObsGroup): - """Observations for policy group.""" - - # observation terms (order preserved) - joint_pos = ObsTerm(func=mdp.joint_pos_rel, noise=Unoise(n_min=-0.01, n_max=0.01)) - joint_vel = ObsTerm(func=mdp.joint_vel_rel, noise=Unoise(n_min=-0.01, n_max=0.01)) - pose_command = ObsTerm(func=mdp.generated_commands, params={"command_name": "ee_pose"}) - actions = ObsTerm(func=mdp.last_action) - - def __post_init__(self): - self.enable_corruption = True - self.concatenate_terms = True - - # observation groups - policy: PolicyCfg = PolicyCfg() - - -@configclass -class EventCfg: - """Configuration for events.""" - - reset_base = EventTerm( - func=mdp.reset_root_state_uniform, - mode="reset", - params={"pose_range": {}, "velocity_range": {}}, - ) - - reset_robot_joints = EventTerm( - func=mdp.reset_joints_by_scale, - mode="reset", - params={ - "position_range": (0.5, 1.5), - "velocity_range": (0.0, 0.0), - }, - ) - - -@configclass -class RewardsCfg: - """Reward terms for the MDP.""" - - # task terms - end_effector_position_tracking = RewTerm( - func=mdp.position_command_error, - weight=-0.2, - params={"asset_cfg": SceneEntityCfg("robot", body_names=MISSING), "command_name": "ee_pose"}, - ) - end_effector_position_tracking_fine_grained = RewTerm( - func=mdp.position_command_error_tanh, - weight=0.1, - params={"asset_cfg": SceneEntityCfg("robot", body_names=MISSING), "std": 0.1, "command_name": "ee_pose"}, - ) - end_effector_orientation_tracking = RewTerm( - func=mdp.orientation_command_error, - weight=-0.1, - params={"asset_cfg": SceneEntityCfg("robot", body_names=MISSING), "command_name": "ee_pose"}, - ) - - # action penalty - action_rate = RewTerm(func=mdp.action_rate_l2, weight=-0.0001) - joint_vel = RewTerm( - func=mdp.joint_vel_l2, - weight=-0.0001, - params={"asset_cfg": SceneEntityCfg("robot")}, - ) - - -@configclass -class TerminationsCfg: - """Termination terms for the MDP.""" - - time_out = DoneTerm(func=mdp.time_out, time_out=True) - - -@configclass -class CurriculumCfg: - """Curriculum terms for the MDP.""" - - action_rate = CurrTerm( - func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -0.005, "num_steps": 4500} - ) - - joint_vel = CurrTerm( - func=mdp.modify_reward_weight, params={"term_name": "joint_vel", "weight": -0.001, "num_steps": 4500} - ) - - -## -# Environment configuration -## - - -@configclass -class ReachEnvCfg(ManagerBasedRLEnvCfg): - """Configuration for the reach end-effector pose tracking environment.""" - - # Scene settings - scene: ReachSceneCfg = ReachSceneCfg(num_envs=4096, env_spacing=2.5) - # Basic settings - observations: ObservationsCfg = ObservationsCfg() - actions: ActionsCfg = ActionsCfg() - commands: CommandsCfg = CommandsCfg() - # MDP settings - rewards: RewardsCfg = RewardsCfg() - terminations: TerminationsCfg = TerminationsCfg() - events: EventCfg = EventCfg() - curriculum: CurriculumCfg = CurriculumCfg() - - def __post_init__(self): - """Post initialization.""" - # general settings - self.decimation = 2 - self.sim.render_interval = self.decimation - self.episode_length_s = 12.0 - self.viewer.eye = (3.5, 3.5, 3.5) - # simulation settings - self.sim.dt = 1.0 / 60.0 From 07956338de386eea6d4092f739d243e1750ddc4f Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 23 Jul 2026 16:03:21 -0700 Subject: [PATCH 4/8] Route the shared RL CLIs through the warp frontend factory Add --frontend {torch,warp} to the shared reinforcement-learning training and play entrypoints and construct environments through the frontend factory. --- .../changelog.d/warp-manager-bridge.minor.rst | 8 ++ .../entrypoints/backends/play_rl_games.py | 21 ++++-- .../entrypoints/backends/play_rsl_rl.py | 21 ++++-- .../entrypoints/backends/play_sb3.py | 21 ++++-- .../entrypoints/backends/play_skrl.py | 21 ++++-- .../isaaclab_rl/entrypoints/common.py | 35 ++++++++- .../test/test_entrypoints_common.py | 75 +++++++++++++++++++ 7 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 source/isaaclab_rl/changelog.d/warp-manager-bridge.minor.rst diff --git a/source/isaaclab_rl/changelog.d/warp-manager-bridge.minor.rst b/source/isaaclab_rl/changelog.d/warp-manager-bridge.minor.rst new file mode 100644 index 000000000000..b1c98eb567e1 --- /dev/null +++ b/source/isaaclab_rl/changelog.d/warp-manager-bridge.minor.rst @@ -0,0 +1,8 @@ +Added +^^^^^ + +* Added ``--frontend {torch,warp}`` to the shared reinforcement-learning + training and play CLIs (all supported RL libraries) for selecting the + environment runtime; default ``torch`` is unchanged. The ``rlinf`` + integration constructs environments inside the external framework and is + not frontend-routable. diff --git a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rl_games.py b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rl_games.py index 153254dca7a5..4fa5630f0d4c 100644 --- a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rl_games.py +++ b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rl_games.py @@ -26,7 +26,13 @@ from isaaclab.utils.dict import print_dict from isaaclab.utils.seed import configure_seed -from isaaclab_rl.entrypoints.common import CHECKPOINT_SELECTORS, resolve_checkpoint_selector, resolve_play_task_name +from isaaclab_rl.entrypoints.common import ( + CHECKPOINT_SELECTORS, + add_frontend_args, + create_isaaclab_env, + resolve_checkpoint_selector, + resolve_play_task_name, +) from isaaclab_rl.rl_games import RlGamesGpuEnv, RlGamesVecEnvWrapper from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint @@ -50,6 +56,7 @@ ) parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") +add_frontend_args(parser) parser.add_argument( "--agent", type=str, default="rl_games_cfg_entry_point", help="Name of the RL agent configuration entry point." ) @@ -133,12 +140,12 @@ def main(): obs_groups = agent_cfg["params"]["env"].get("obs_groups") concate_obs_groups = agent_cfg["params"]["env"].get("concate_obs_groups", True) - env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None) - - if isinstance(env.unwrapped.cfg, DirectMARLEnvCfg): - from isaaclab.envs import multi_agent_to_single_agent - - env = multi_agent_to_single_agent(env) + env = create_isaaclab_env( + args_cli.task, + env_cfg, + args_cli, + convert_marl_to_single_agent=isinstance(env_cfg, DirectMARLEnvCfg), + ) if args_cli.video: video_kwargs = { diff --git a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rsl_rl.py b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rsl_rl.py index 1e6bcf025a42..558c7634b7e8 100644 --- a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rsl_rl.py +++ b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rsl_rl.py @@ -25,7 +25,13 @@ from isaaclab.utils.string import list_intersection, string_to_callable from isaaclab_rl.entrypoints.backends import cli_args_rsl_rl as cli_args -from isaaclab_rl.entrypoints.common import CHECKPOINT_SELECTORS, resolve_checkpoint_selector, resolve_play_task_name +from isaaclab_rl.entrypoints.common import ( + CHECKPOINT_SELECTORS, + add_frontend_args, + create_isaaclab_env, + resolve_checkpoint_selector, + resolve_play_task_name, +) from isaaclab_rl.rsl_rl import ( RslRlBaseRunnerCfg, RslRlVecEnvWrapper, @@ -55,6 +61,7 @@ ) parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") +add_frontend_args(parser) parser.add_argument( "--agent", type=str, default="rsl_rl_cfg_entry_point", help="Name of the RL agent configuration entry point." ) @@ -137,12 +144,12 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen env_cfg.log_dir = log_dir - env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None) - - if isinstance(env.unwrapped.cfg, DirectMARLEnvCfg): - from isaaclab.envs import multi_agent_to_single_agent - - env = multi_agent_to_single_agent(env) + env = create_isaaclab_env( + args_cli.task, + env_cfg, + args_cli, + convert_marl_to_single_agent=isinstance(env_cfg, DirectMARLEnvCfg), + ) if args_cli.video: video_kwargs = { diff --git a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_sb3.py b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_sb3.py index c19cf829692c..09b78f442cbc 100644 --- a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_sb3.py +++ b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_sb3.py @@ -23,7 +23,13 @@ from isaaclab.utils.dict import print_dict from isaaclab.utils.seed import configure_seed -from isaaclab_rl.entrypoints.common import CHECKPOINT_SELECTORS, resolve_checkpoint_selector, resolve_play_task_name +from isaaclab_rl.entrypoints.common import ( + CHECKPOINT_SELECTORS, + add_frontend_args, + create_isaaclab_env, + resolve_checkpoint_selector, + resolve_play_task_name, +) from isaaclab_rl.sb3 import Sb3VecEnvWrapper, process_sb3_cfg from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint @@ -47,6 +53,7 @@ ) parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") +add_frontend_args(parser) parser.add_argument( "--agent", type=str, default="sb3_cfg_entry_point", help="Name of the RL agent configuration entry point." ) @@ -123,15 +130,15 @@ def main(): env_cfg.log_dir = log_dir - env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None) + env = create_isaaclab_env( + args_cli.task, + env_cfg, + args_cli, + convert_marl_to_single_agent=isinstance(env_cfg, DirectMARLEnvCfg), + ) agent_cfg = process_sb3_cfg(agent_cfg, env.unwrapped.num_envs) - if isinstance(env.unwrapped.cfg, DirectMARLEnvCfg): - from isaaclab.envs import multi_agent_to_single_agent - - env = multi_agent_to_single_agent(env) - if args_cli.video: video_kwargs = { "video_folder": os.path.join(log_dir, "videos", "play"), diff --git a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_skrl.py b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_skrl.py index c5b4b99dd630..b8b1e530c523 100644 --- a/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_skrl.py +++ b/source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_skrl.py @@ -27,7 +27,13 @@ from isaaclab.utils.dict import print_dict from isaaclab.utils.seed import configure_seed -from isaaclab_rl.entrypoints.common import CHECKPOINT_SELECTORS, resolve_checkpoint_selector, resolve_play_task_name +from isaaclab_rl.entrypoints.common import ( + CHECKPOINT_SELECTORS, + add_frontend_args, + create_isaaclab_env, + resolve_checkpoint_selector, + resolve_play_task_name, +) from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint import isaaclab_tasks # noqa: F401 @@ -52,6 +58,7 @@ ) parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") +add_frontend_args(parser) parser.add_argument( "--agent", type=str, @@ -175,12 +182,12 @@ def main(): env_cfg.log_dir = log_dir - env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None) - - if isinstance(env.unwrapped.cfg, DirectMARLEnvCfg) and algorithm in ["ppo"]: - from isaaclab.envs import multi_agent_to_single_agent - - env = multi_agent_to_single_agent(env) + env = create_isaaclab_env( + args_cli.task, + env_cfg, + args_cli, + convert_marl_to_single_agent=isinstance(env_cfg, DirectMARLEnvCfg) and algorithm in ["ppo"], + ) try: dt = env.step_dt diff --git a/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py b/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py index 1d34bb060fdf..685f24e42b05 100644 --- a/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py +++ b/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py @@ -266,6 +266,30 @@ def resolve_play_checkpoint(checkpoint: str | None, framework: str, task: str) - return path +def add_frontend_args(parser: argparse.ArgumentParser) -> None: + """Add the environment-runtime selector argument. + + The flag is always registered so the CLI surface does not depend on + optional packages; the warp runtime itself is imported only when selected + (see :func:`create_isaaclab_env`). + + Args: + parser: The parser to add the argument to. + """ + parser.add_argument( + "--frontend", + type=str, + choices=["torch", "warp"], + default="torch", + help=( + "Runtime that constructs the environment. 'torch' uses the registered stable environment via" + " gym.make. 'warp' (experimental) adapts a manager-based task config onto the Warp runtime, or" + " dispatches a direct task to its registered Warp environment; requires isaaclab_experimental" + " and `presets=newton_mjwarp`." + ), + ) + + def add_common_train_args( parser: argparse.ArgumentParser, *, @@ -292,6 +316,7 @@ def add_common_train_args( ) parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") + add_frontend_args(parser) if include_agent: parser.add_argument("--agent", type=str, default=agent_default, help=agent_help) parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") @@ -442,7 +467,15 @@ def create_isaaclab_env( Returns: The created Gymnasium environment. """ - env = gym.make(task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None) + render_mode = "rgb_array" if args_cli.video else None + if args_cli.frontend == "torch": + env = gym.make(task, cfg=env_cfg, render_mode=render_mode) + else: + # Imported lazily: the warp frontend lives in the optional + # isaaclab_experimental package, and the torch path must work without it. + from isaaclab_experimental.envs.frontend import WarpFrontend + + env = WarpFrontend.build_env(env_cfg, task, render_mode=render_mode) if convert_marl_to_single_agent and isinstance(env.unwrapped.cfg, DirectMARLEnvCfg): from isaaclab.envs import multi_agent_to_single_agent diff --git a/source/isaaclab_rl/test/test_entrypoints_common.py b/source/isaaclab_rl/test/test_entrypoints_common.py index c0c081858dcf..b9902ec75f1b 100644 --- a/source/isaaclab_rl/test/test_entrypoints_common.py +++ b/source/isaaclab_rl/test/test_entrypoints_common.py @@ -20,6 +20,7 @@ from isaaclab_rl.entrypoints.common import ( CaptureEnvSensors, add_common_train_args, + create_isaaclab_env, dispatch_library_entrypoint, enable_cameras_for_video, resolve_play_task_name, @@ -205,6 +206,80 @@ def test_enable_cameras_for_video_enables_cameras_for_sensor_capture() -> None: assert args_cli.enable_cameras +def test_common_train_args_register_frontend_with_torch_default() -> None: + """Every RL library CLI exposes ``--frontend`` and defaults to the torch runtime.""" + parser = argparse.ArgumentParser() + add_common_train_args(parser, agent_default=None, agent_help="", include_agent=False) + + assert parser.parse_args([]).frontend == "torch" + assert parser.parse_args(["--frontend", "warp"]).frontend == "warp" + with pytest.raises(SystemExit): + parser.parse_args(["--frontend", "tensorflow"]) + + +def _repo_root() -> Path: + return Path(__file__).resolve().parents[3] + + +def test_play_entrypoints_route_through_frontend_factory() -> None: + """Every dispatched play backend constructs its env via the frontend-aware factory.""" + play_scripts = sorted( + (_repo_root() / "source" / "isaaclab_rl" / "isaaclab_rl" / "entrypoints" / "backends").glob("play_*.py") + ) + # rlinf constructs environments inside the external framework; the frontend cannot + # reach it (documented limitation). + play_scripts = [path for path in play_scripts if path.name != "play_rlinf.py"] + assert len(play_scripts) == 4, sorted(path.name for path in play_scripts) + for script in play_scripts: + source = script.read_text() + assert "create_isaaclab_env(" in source, f"{script.name} bypasses the frontend factory" + assert "gym.make(args_cli.task" not in source, f"{script.name} constructs directly via gym.make" + assert "add_frontend_args(parser)" in source, f"{script.name} does not expose --frontend" + + +def test_create_isaaclab_env_uses_registered_torch_env_by_default(monkeypatch: pytest.MonkeyPatch) -> None: + """The shared factory preserves the existing Gym path when no frontend is selected.""" + expected_env = object() + env_cfg = object() + calls: list[tuple[Any, ...]] = [] + + def fake_make(task: str, **kwargs: Any) -> Any: + calls.append((task, kwargs)) + return expected_env + + monkeypatch.setattr(_rl_common.gym, "make", fake_make) + args_cli = argparse.Namespace(video=False, frontend="torch") + + env = create_isaaclab_env("Isaac-Test", env_cfg, args_cli, convert_marl_to_single_agent=False) + + assert env is expected_env + assert len(calls) == 1 + assert calls[0][0] == "Isaac-Test" + assert calls[0][1]["cfg"] is env_cfg + assert calls[0][1]["render_mode"] is None + + +def test_create_isaaclab_env_uses_selected_warp_frontend(monkeypatch: pytest.MonkeyPatch) -> None: + """The shared factory delegates Warp selection to the experimental frontend.""" + import isaaclab_experimental.envs.frontend as frontend_module + + expected_env = object() + env_cfg = object() + calls: list[tuple[Any, ...]] = [] + + def fake_build_env(cfg: Any, task: str, **kwargs: Any) -> Any: + calls.append((cfg, task, kwargs)) + return expected_env + + monkeypatch.setattr(frontend_module.WarpFrontend, "build_env", fake_build_env) + args_cli = argparse.Namespace(video=True, frontend="warp") + + env = create_isaaclab_env("Isaac-Test", env_cfg, args_cli, convert_marl_to_single_agent=False) + + assert env is expected_env + assert calls == [(env_cfg, "Isaac-Test", {"render_mode": "rgb_array"})] + + def test_dispatch_library_entrypoint_shows_help_without_library( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: From 3fdef8b61745971f2b3f75c2b907554366d994f5 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 23 Jul 2026 16:03:23 -0700 Subject: [PATCH 5/8] Document the warp environment frontend --- .../newton/warp-environments.rst | 119 ++++++++---------- docs/source/overview/environments.rst | 75 ++--------- 2 files changed, 62 insertions(+), 132 deletions(-) diff --git a/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst b/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst index 12eafa178b05..c9a4b088143a 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst @@ -12,6 +12,11 @@ The experimental extensions introduce **warp-first** environment infrastructure support. All environment-side computation (observations, rewards, resets, actions) runs as pure Warp kernels, eliminating Python overhead and enabling CUDA graph capture for maximum throughput. +Throughout this page, **stable** refers to the standard Torch-based implementations in the +``isaaclab`` and ``isaaclab_tasks`` packages (the default runtime, used when ``--frontend warp`` +is not passed). Their Warp counterparts live in the ``isaaclab_experimental`` and +``isaaclab_tasks_experimental`` packages. + Workflows ~~~~~~~~~ @@ -31,38 +36,36 @@ Available Environments Direct Warp Environments ^^^^^^^^^^^^^^^^^^^^^^^^ -- ``Isaac-Cartpole-Direct-Warp-v0`` — Cartpole balance -- ``Isaac-Ant-Direct-Warp-v0`` — Ant locomotion -- ``Isaac-Humanoid-Direct-Warp-v0`` — Humanoid locomotion -- ``Isaac-Reorient-Cube-Allegro-Direct-Warp-v0`` — Allegro hand cube reorient - +Direct tasks share the stable task configuration: ``--frontend warp`` resolves +the warp env class by name from the mirrored experimental package +(``_direct_env:Env`` → ``_warp_env:WarpEnv``) and swaps +only the environment class. Stable tasks with a warp implementation: -Manager-Based Warp Environments -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- ``Isaac-Cartpole-Direct`` — Cartpole balance +- ``Isaac-Ant-Direct`` — Ant locomotion +- ``Isaac-Humanoid-Direct`` — Humanoid locomotion +- ``Isaac-Reorient-Cube-Allegro-Direct`` — Allegro hand cube reorient -**Classic** -- ``Isaac-Cartpole-Warp-v0`` -- ``Isaac-Ant-Warp-v0`` -- ``Isaac-Humanoid-Warp-v0`` +Manager-Based Warp Execution (``--frontend warp``) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -**Locomotion (Flat)** +Manager-based tasks do not need a parallel warp registration: the shared RL +CLI exposes ``--frontend {torch,warp}``, and ``--frontend warp`` adapts the +*stable* task configuration onto the warp runtime at build time (swapping each +MDP term for its warp twin). Select the Newton solver explicitly with +``presets=newton_mjwarp``. Stable tasks with full twin coverage: -- ``Isaac-Velocity-Flat-AnymalB-Warp-v0`` -- ``Isaac-Velocity-Flat-AnymalC-Warp-v0`` -- ``Isaac-Velocity-Flat-AnymalD-Warp-v0`` -- ``Isaac-Velocity-Flat-Cassie-Warp-v0`` -- ``Isaac-Velocity-Flat-G1-Warp-v0`` -- ``Isaac-Velocity-Flat-G1-Warp-v1`` -- ``Isaac-Velocity-Flat-H1-Warp-v0`` -- ``Isaac-Velocity-Flat-UnitreeA1-Warp-v0`` -- ``Isaac-Velocity-Flat-UnitreeGo1-Warp-v0`` -- ``Isaac-Velocity-Flat-UnitreeGo2-Warp-v0`` +- ``Isaac-Cartpole`` +- ``Isaac-Ant`` +- ``Isaac-Humanoid`` +- ``Isaac-Reach-Franka``, ``Isaac-Reach-UR10`` +- ``Isaac-Velocity-Flat-AnymalD``, ``-Cassie``, ``-G1``, ``-H1``, ``-UnitreeGo2`` -**Manipulation** - -- ``Isaac-Reach-Franka-Warp-v0`` -- ``Isaac-Reach-UR10-Warp-v0`` +A missing twin is a hard error listing the affected terms, so a partially +covered task fails at build time rather than silently changing behavior. +Rough-terrain velocity tasks remain unsupported until +:class:`~isaaclab.terrains.TerrainImporter` gains Warp APIs. Quick Start @@ -74,25 +77,25 @@ Quick Start .. code-block:: bash - # Direct workflow + # Direct workflow: stable task, warp env implementation uv run isaaclab train --rl_library rsl_rl \ - --task Isaac-Cartpole-Direct-Warp-v0 --num_envs 4096 + --task Isaac-Cartpole-Direct --frontend warp presets=newton_mjwarp --num_envs 4096 - # Manager-based workflow + # Manager-based workflow: stable task on the warp runtime uv run isaaclab train --rl_library rsl_rl \ - --task Isaac-Velocity-Flat-AnymalC-Warp-v0 --num_envs 4096 + --task Isaac-Velocity-Flat-AnymalD --frontend warp presets=newton_mjwarp --num_envs 4096 .. tab-item:: isaaclab.sh / isaaclab.bat .. code-block:: bash - # Direct workflow + # Direct workflow: stable task, warp env implementation ./isaaclab.sh train --rl_library rsl_rl \ - --task Isaac-Cartpole-Direct-Warp-v0 --num_envs 4096 + --task Isaac-Cartpole-Direct --frontend warp presets=newton_mjwarp --num_envs 4096 - # Manager-based workflow + # Manager-based workflow: stable task on the warp runtime ./isaaclab.sh train --rl_library rsl_rl \ - --task Isaac-Velocity-Flat-AnymalC-Warp-v0 --num_envs 4096 + --task Isaac-Velocity-Flat-AnymalD --frontend warp presets=newton_mjwarp --num_envs 4096 All RL libraries with warp-compatible wrappers are supported: RSL-RL, RL Games, SKRL, and Stable-Baselines3. @@ -159,16 +162,6 @@ both running on the Newton physics backend. Measured over 300 iterations with 40 - 11,458 - 7,813 - -31.83% - * - AnymalB - - Manager - - 29,188 - - 21,781 - - -25.38% - * - AnymalC - - Manager - - 30,938 - - 22,228 - - -28.15% * - AnymalD - Manager - 32,294 @@ -189,17 +182,7 @@ both running on the Newton physics backend. Measured over 300 iterations with 40 - 22,202 - 15,864 - -28.55% - * - A1 - - Manager - - 15,257 - - 9,907 - - -35.07% - * - Go1 - - Manager - - 16,515 - - 11,869 - - -28.13% - * - Go2 + * - UnitreeGo2 - Manager - 15,221 - 9,966 @@ -216,7 +199,7 @@ overhead. Reading the table above: - **Manager-based classic RL** (Cartpole, Ant) — biggest gains (-52% to -54%). Many small reward / observation terms with low compute per term, so per-launch CPU overhead dominated the stable baseline. -- **Manager-based locomotion** (Anymal, G1, H1, Cassie, Unitree) — consistent -25% to -38% +- **Manager-based locomotion** (AnymalD, G1, H1, Cassie, UnitreeGo2) — consistent -21% to -38% range. The MDP has more terms but the underlying physics step is heavier, so the relative Python savings shrink. - **Direct workflow** — gains scale with how much the env's step body was Python (Ant -51%, @@ -244,7 +227,8 @@ specific to warp envs; for Newton physics limitations see :doc:`supported-featur ``class_type`` fields resolve to ``isaaclab_physx.*`` classes that depend on ``omni.physics.tensors`` (a Kit module the warp runtime does not initialise), and several warp APIs (env-mask reset, CUDA graph capture) require the Newton articulation. Configure - the cfg with a Newton physics block (or ``physics=newton_mjwarp``). + the cfg with a Newton physics block (or the typed selector ``physics=newton_mjwarp``, + which fails loudly if the task has no Newton physics preset). **MDP coverage** @@ -282,25 +266,32 @@ to estimate the gain for your own task before committing to a migration. **Single-task A/B** +Run the same stable task id twice, differing only in ``--frontend``. Pass +``presets=newton_mjwarp`` to *both* runs so the physics backend is identical and the +measured difference isolates the frontend (manager pipeline + CUDA graph capture): + .. code-block:: bash - # Stable variant + # Torch frontend (stable managers) uv run isaaclab benchmark training \ --rl_library rsl_rl \ - --task -v0 \ + --task \ --num_envs 4096 \ --max_iterations 500 \ --benchmark_formatter summary \ - --output_path benchmarks/stable + --output_path benchmarks/torch \ + presets=newton_mjwarp - # Warp variant — same task with -Warp- suffix + # Warp frontend (warp managers, CUDA-graph captured) — same task id uv run isaaclab benchmark training \ --rl_library rsl_rl \ - --task -Warp-v0 \ + --task \ + --frontend warp \ --num_envs 4096 \ --max_iterations 500 \ --benchmark_formatter summary \ - --output_path benchmarks/warp + --output_path benchmarks/warp \ + presets=newton_mjwarp The ``summary`` formatter prints step time (min / mean / max) and total throughput. Compare "step time" between the two runs to estimate the gain per env step. @@ -308,7 +299,7 @@ The ``summary`` formatter prints step time (min / mean / max) and total throughp **Sweep across all available tasks** Run ``isaaclab benchmark training`` for each task in the stable set (cartpole, ant, humanoid, -locomotion, manipulation) and again with the ``-Warp-`` suffixed task ids, then diff the two output +locomotion, manipulation) and again with ``--frontend warp``, then diff the two output directories. **What to look at in the output** diff --git a/docs/source/overview/environments.rst b/docs/source/overview/environments.rst index b0127428729a..43889a3365f7 100644 --- a/docs/source/overview/environments.rst +++ b/docs/source/overview/environments.rst @@ -920,6 +920,13 @@ To run inference on a trained task, pass its task name to ``play.py``. The play configuration's ``play_mode`` overrides automatically, providing configurations more suitable for inferencing, including disabling runtime perturbations used for training. +.. note:: + + Warp-native environment implementations no longer register separate ``-Warp`` task ids. + The same stable tasks run on the Warp runtime by passing ``--frontend warp`` (together + with ``presets=newton_mjwarp``) to the training and play entrypoints. See + :doc:`core-concepts/physical-backends/newton/warp-environments` for details. + .. START-AUTO-GENERATED: comprehensive-environment-list .. list-table:: @@ -937,14 +944,6 @@ including disabling runtime perturbations used for training. - Direct - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``ovphysx``, ``physx`` - * - Isaac-Ant-Direct-Warp-v0 - - Direct - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - - - * - Isaac-Ant-Warp-v0 - - Manager Based - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO), **sb3** (PPO) - - * - Isaac-Cartpole - Manager Based - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO), **sb3** (PPO) @@ -965,14 +964,6 @@ including disabling runtime perturbations used for training. - Direct - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO), **sb3** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``ovphysx``, ``physx`` - * - Isaac-Cartpole-Direct-Warp-v0 - - Direct - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO), **sb3** (PPO) - - - * - Isaac-Cartpole-Warp-v0 - - Manager Based - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO), **sb3** (PPO) - - * - Isaac-Fourbar-Pole-Swingup - Manager Based - **rsl_rl** (PPO) @@ -985,14 +976,6 @@ including disabling runtime perturbations used for training. - Direct - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_mjwarp``, ``ovphysx``, ``physx`` - * - Isaac-Humanoid-Direct-Warp-v0 - - Direct - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - - - * - Isaac-Humanoid-Warp-v0 - - Manager Based - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO), **sb3** (PPO) - - * - Isaac-Lift-Cloth-Franka - Manager Based - **rsl_rl** (PPO) @@ -1045,10 +1028,6 @@ including disabling runtime perturbations used for training. - Manager Based - **rsl_rl** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``physx`` - * - Isaac-Reach-Franka-Warp-v0 - - Manager Based - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Reach-UR10 - Manager Based - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) @@ -1061,10 +1040,6 @@ including disabling runtime perturbations used for training. - Direct - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_mjwarp``, ``ovphysx``, ``physx`` - * - Isaac-Reorient-Cube-Allegro-Direct-Warp-v0 - - Direct - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Reorient-Cube-Shadow-Camera-Direct - Direct - **rl_games** (PPO), **rsl_rl** (PPO) @@ -1103,66 +1078,30 @@ including disabling runtime perturbations used for training. - Direct - **rl_games** (PPO), **skrl** (PPO, IPPO, MAPPO) - **physics=** ``newton_mjwarp``, ``physx`` - * - Isaac-Velocity-Flat-AnymalB-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO) - - - * - Isaac-Velocity-Flat-AnymalC-Warp-v0 - - Manager Based - - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Velocity-Flat-AnymalD - Manager Based - **rsl_rl** (PPO, DISTILLATION, DISTILLATION_RECURRENT, RECURRENT), **skrl** (PPO) - **physics=** ``newton_mjwarp``, ``ovphysx``, ``physx`` - * - Isaac-Velocity-Flat-AnymalD-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Velocity-Flat-Cassie - Manager Based - **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``physx`` - * - Isaac-Velocity-Flat-Cassie-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Velocity-Flat-G1 - Manager Based - **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``physx`` - * - Isaac-Velocity-Flat-G1-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Velocity-Flat-H1 - Manager Based - **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``physx`` - * - Isaac-Velocity-Flat-H1-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Velocity-Flat-Spot - Manager Based - **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``physx`` - * - Isaac-Velocity-Flat-UnitreeA1-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO), **sb3** (PPO) - - - * - Isaac-Velocity-Flat-UnitreeGo1-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Velocity-Flat-UnitreeGo2 - Manager Based - **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``newton_kamino``, ``newton_mjwarp``, ``physx`` - * - Isaac-Velocity-Flat-UnitreeGo2-Warp-v0 - - Manager Based - - **rsl_rl** (PPO), **skrl** (PPO) - - * - Isaac-Velocity-Rough-AnymalD - Manager Based - **rsl_rl** (PPO), **skrl** (PPO) From fbb5d8a8371f8b8cb9b6d55f9191a06a6551f1b0 Mon Sep 17 00:00:00 2001 From: Kelly Guo Date: Thu, 23 Jul 2026 22:16:45 -0700 Subject: [PATCH 6/8] Clarify Warp frontend resolution Document convention-based direct environment lookup and keep the optional override and migration guidance consistent. --- .../physical-backends/newton/warp-environments.rst | 10 ++++++---- docs/source/overview/environments.rst | 4 ++-- .../changelog.d/warp-manager-bridge.minor.rst | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst b/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst index c9a4b088143a..cc4731fe93a2 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/warp-environments.rst @@ -36,10 +36,12 @@ Available Environments Direct Warp Environments ^^^^^^^^^^^^^^^^^^^^^^^^ -Direct tasks share the stable task configuration: ``--frontend warp`` resolves -the warp env class by name from the mirrored experimental package -(``_direct_env:Env`` → ``_warp_env:WarpEnv``) and swaps -only the environment class. Stable tasks with a warp implementation: +Direct tasks share the stable task configuration. ``--frontend warp`` resolves +the stable ``_direct_env:Env`` entry point to the mirrored +``_warp_env:WarpEnv`` implementation and swaps only the environment +class. Registrations can use ``warp_entry_point`` as an optional override when +the implementation cannot follow this convention. Stable tasks with a Warp +implementation: - ``Isaac-Cartpole-Direct`` — Cartpole balance - ``Isaac-Ant-Direct`` — Ant locomotion diff --git a/docs/source/overview/environments.rst b/docs/source/overview/environments.rst index 43889a3365f7..f2fc8afe49fb 100644 --- a/docs/source/overview/environments.rst +++ b/docs/source/overview/environments.rst @@ -923,8 +923,8 @@ including disabling runtime perturbations used for training. .. note:: Warp-native environment implementations no longer register separate ``-Warp`` task ids. - The same stable tasks run on the Warp runtime by passing ``--frontend warp`` (together - with ``presets=newton_mjwarp``) to the training and play entrypoints. See + Run the same stable tasks on the Warp runtime by passing ``--frontend warp`` together with + ``presets=newton_mjwarp`` to the training and play entry points. See :doc:`core-concepts/physical-backends/newton/warp-environments` for details. .. START-AUTO-GENERATED: comprehensive-environment-list diff --git a/source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst b/source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst index 9b447e8af412..306ee6e078d3 100644 --- a/source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst +++ b/source/isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.minor.rst @@ -26,9 +26,10 @@ Removed ``Isaac-Cartpole-Direct-Warp-v0``, ``Isaac-Ant-Direct-Warp-v0``, ``Isaac-Humanoid-Direct-Warp-v0``, and ``Isaac-Reorient-Cube-Allegro-Direct-Warp-v0`` together with their - duplicated environment configurations; the stable registrations declare the - warp environment classes via ``warp_entry_point``. Run the stable task ids - with ``--frontend warp`` and ``presets=newton_mjwarp`` instead, e.g. + duplicated environment configurations; the frontend resolves their Warp + environment classes by the mirrored naming convention, with + ``warp_entry_point`` available as an optional override. Run the stable task + ids with ``--frontend warp`` and ``presets=newton_mjwarp`` instead, e.g. ``--task Isaac-Cartpole-Direct --frontend warp presets=newton_mjwarp``. * Removed the unregistered rough velocity warp configurations; rough-terrain warp tasks remain unsupported until :class:`~isaaclab.terrains.TerrainImporter` From 453477381b7d96fe332a7b9bdd7b7c5977a061f7 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 23 Jul 2026 23:50:04 -0700 Subject: [PATCH 7/8] Guard direct warp resolution against cfg mismatch A stable direct env class can be shared by several tasks (e.g. ReorientDirectEnv serves both the Allegro and Shadow hands via different cfgs). Name-based resolution keys on that shared env class, so it mirrored every sharing task to the single ReorientDirectWarpEnv twin -- which implements only the Allegro variant -- silently running the wrong MDP for Shadow under --frontend warp. Have _resolve_direct_warp_class verify the name-mirrored twin implements the task's cfg (via the twin's `cfg` annotation) before returning it; a mismatch is a hard error. Explicit warp_entry_point overrides stay trusted. The 4 supported direct tasks are unaffected; the 3 Shadow reorient tasks now report unsupported instead of mis-resolving. --- .../changelog.d/warp-manager-bridge.rst | 6 +- .../isaaclab_experimental/envs/frontend.py | 98 ++++++++++++++--- .../test/envs/test_frontend.py | 104 ++++++++++++++++++ 3 files changed, 193 insertions(+), 15 deletions(-) diff --git a/source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst b/source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst index a8df6918733a..015db4da61ea 100644 --- a/source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst +++ b/source/isaaclab_experimental/changelog.d/warp-manager-bridge.rst @@ -8,7 +8,11 @@ Added resolves a stable direct task's ``_direct_env:Env`` to the mirrored ``_warp_env:WarpEnv`` and constructs it with the stable cfg. No parallel registration is needed; a ``warp_entry_point`` kwarg is honored as an - optional override for classes that cannot follow the convention. + optional override for classes that cannot follow the convention. The resolved + twin is verified against the task's cfg (via the warp env's ``cfg`` annotation), + so a stable env class shared by several tasks — e.g. ``ReorientDirectEnv``, + which serves both the Allegro and Shadow hands — never silently runs one + variant's warp env for another; a mismatch is a hard error. * Added observation-noise conversion to the warp frontend: stable noise cfgs (constant/uniform/gaussian) swap to their warp-native twins during cfg adaptation; cfgs without a twin (class-based noise models, customized or diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py index 29f9aefcaa59..1439159a69d2 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py @@ -13,8 +13,12 @@ :class:`ManagerBasedRLEnvWarp`. All warp MDP twins (functions *and* classes) must exist for the chosen task — a missing twin is a hard failure, not a silent drop. -* Direct tasks: construct the warp env class the stable registration declares - via its ``warp_entry_point`` kwarg, sharing the stable cfg. +* Direct tasks: resolve the warp env class by name from the mirrored + experimental package (or an explicit ``warp_entry_point`` override) and + construct it with the stable cfg. The resolved twin is verified against the + task's cfg, so a stable env class shared by several tasks (e.g. the Allegro + and Shadow reorient hands) never silently runs one variant's warp env for + another. Twin resolution is purely mechanical: the experimental packages mirror the stable tree (``isaaclab.* ↔ isaaclab_experimental.*``, @@ -135,16 +139,24 @@ def _build_direct_env(cls, env_cfg: Any, task_id: str, **construct_kwargs: Any) """Construct a direct warp env. Direct workflows aren't cfg-adapted: a hand-written warp env class - implements the task. Preferred path: the stable registration declares - it via ``warp_entry_point`` and shares the stable cfg. Fallback: the - task itself is registered under the warp packages (warp-native cfg). + implements the task, constructed with the *stable* cfg. The class is + resolved by name from the mirrored experimental package (or an explicit + ``warp_entry_point`` override); see :meth:`_resolve_direct_warp_class`. + If name resolution finds nothing, the task itself must be a warp-native + registration. + + Several stable tasks can share one direct env class (e.g. + ``ReorientDirectEnv`` serves both the Allegro and Shadow hands via + different cfgs), so :meth:`_resolve_direct_warp_class` returns a + name-mirrored twin only when it implements ``env_cfg`` — otherwise it + raises rather than let the wrong env run silently. """ - env_class = cls._resolve_direct_warp_class(task_id) + env_class = cls._resolve_direct_warp_class(task_id, env_cfg) if env_class is None: - # No warp_entry_point: the task itself must be a warp-native registration. + # No warp twin by name: the task itself must be a warp-native registration. cls._assert_direct_warp_registration(task_id) return gym.make(task_id, cfg=env_cfg, **construct_kwargs) - # Declared warp class + stable cfg: swap only the env class. + # Name-resolved warp class that implements this cfg: swap only the env class. cls._require_newton_physics(env_cfg, type(env_cfg).__name__) return env_class(cfg=env_cfg, **construct_kwargs) @@ -316,14 +328,15 @@ def _detect_workflow(cfg: Any) -> Workflow: ) @classmethod - def _resolve_direct_warp_class(cls, task_id: str) -> type | None: - """Return the warp env class for a stable direct task, or ``None``. + def _resolve_direct_warp_class(cls, task_id: str, env_cfg: Any = None) -> type | None: + """Return the warp env class that implements a stable direct task, or ``None``. Resolution order: 1. An explicit ``warp_entry_point`` kwarg on the stable registration - (``"module.path:ClassName"``), if present. This is an override for - env classes that cannot follow the naming convention below. + (``"module.path:ClassName"``), if present. This is a deliberate + per-registration override for env classes that cannot follow the + naming convention below, and is trusted as declared. 2. Name-based mirror: the stable env entry point ``isaaclab_tasks.<...>._direct_env:Env`` resolves to ``isaaclab_tasks_experimental.<...>._warp_env:WarpEnv`` @@ -331,7 +344,13 @@ def _resolve_direct_warp_class(cls, task_id: str) -> type | None: by following the convention — no per-task registration needed. The class is constructed with the *stable* cfg, so the task needs no - parallel warp registration or duplicated configuration. + parallel warp registration or duplicated configuration. Because one + stable env class can be shared by several tasks (e.g. ``ReorientDirectEnv`` + serves the Allegro and Shadow hands via different cfgs), a name-mirrored + twin is returned only when it implements ``env_cfg``, verified via + :meth:`_assert_cfg_supported`; a mismatch is a hard error. Pass + ``env_cfg=None`` to perform the name mapping alone (used by callers that + only probe the convention, not applicability). """ try: spec = gym.spec(task_id) @@ -356,7 +375,13 @@ def _resolve_direct_warp_class(cls, task_id: str) -> type | None: f" but {class_name!r} is not defined in {module_name!r}." ) return env_class - return cls._mirror_direct_warp_class(spec.entry_point) + env_class = cls._mirror_direct_warp_class(spec.entry_point) + if env_class is not None and env_cfg is not None: + # Name resolution keys on the (possibly shared) stable env class, so + # confirm the mirrored twin actually implements this task's cfg + # before offering it as the resolution. + cls._assert_cfg_supported(env_class, env_cfg, task_id) + return env_class @classmethod def _mirror_direct_warp_class(cls, entry_point: Any) -> type | None: @@ -399,6 +424,51 @@ def _assert_direct_warp_registration(cls, task_id: str) -> None: f" registered as a warp env class." ) + @staticmethod + def _declared_stable_cfg_name(env_class: type) -> str | None: + """Return the stable cfg class name a direct warp env declares it implements. + + The contract is the env's own ``cfg`` class annotation (e.g. + ``cfg: AllegroHandEnvCfg``). It is read straight from ``__annotations__`` + rather than via :func:`typing.get_type_hints`, which eagerly resolves + *every* annotation on the class — including unrelated ``TYPE_CHECKING`` + forward references the runtime cannot import — and would raise. Returns + ``None`` when the annotation is absent or is not a bare class name, in + which case no compatibility check is enforced. + """ + annotation = getattr(env_class, "__annotations__", {}).get("cfg") + if isinstance(annotation, str) and annotation.isidentifier(): + return annotation + return None + + @classmethod + def _assert_cfg_supported(cls, env_class: type, env_cfg: Any, task_id: str) -> None: + """Guard that a name-resolved direct warp env implements the task's cfg. + + A direct warp env is constructed with the *stable* cfg, so it must + actually implement that cfg. When one stable env class is shared by + several tasks (e.g. ``ReorientDirectEnv`` serves the Allegro and Shadow + hands via different cfgs), name resolution yields a single warp twin + that may implement only one of them. The twin advertises the cfg it + supports via its ``cfg`` annotation; this passes when the task's cfg is + that class or a subclass of it, and raises otherwise so the caller never + trains a different MDP under a mismatched env. + """ + declared = cls._declared_stable_cfg_name(env_class) + if declared is None: + return # env declares no cfg contract — keep prior behavior + cfg_type_names = {base.__name__ for base in type(env_cfg).__mro__} + if declared not in cfg_type_names: + raise FrontendIncompatibleError( + f"--frontend=warp: task {task_id!r} resolves by name to warp env " + f"{env_class.__module__}.{env_class.__qualname__}, which implements config " + f"{declared!r}, but the task's config is {type(env_cfg).__name__!r}. This warp " + f"env does not implement that config, so the task has no warp implementation. " + f"(This happens when several stable tasks share one direct env class but only " + f"one has a warp twin; declare a `warp_entry_point` on the stable registration " + f"if a dedicated twin exists.)" + ) + # ------------------------------------------------------------------ # Twin resolution # ------------------------------------------------------------------ diff --git a/source/isaaclab_experimental/test/envs/test_frontend.py b/source/isaaclab_experimental/test/envs/test_frontend.py index d0dbc90fafe0..e1c73f957d85 100644 --- a/source/isaaclab_experimental/test/envs/test_frontend.py +++ b/source/isaaclab_experimental/test/envs/test_frontend.py @@ -525,6 +525,10 @@ def test_is_swap_candidate_rejects_non_callables(): # Follows the _direct_env:Env convention, so it resolves to the # mirrored _warp_env:WarpEnv with no warp_entry_point. "_Frontend-Test-NameMirror-Direct-v0": ("isaaclab_tasks.core.cartpole.cartpole_direct_env:CartpoleEnv", None), + # Real shared reorient env class: both Allegro and Shadow register it, and it + # mirrors to the real ReorientDirectWarpEnv (which declares cfg AllegroHandEnvCfg). + # Used to exercise the cfg-compatibility gate on a genuinely shared base env. + "_Frontend-Test-Reorient-Direct-v0": ("isaaclab_tasks.core.reorient.reorient_direct_env:ReorientDirectEnv", None), } @@ -589,3 +593,103 @@ def test_resolve_direct_warp_class_rejects_missing_class(): def test_resolve_direct_warp_class_rejects_unknown_task(): with pytest.raises(FrontendIncompatibleError): fe.WarpFrontend._resolve_direct_warp_class("Frontend-Test-NotRegistered-v0") + + +# ====================================================================== +# Direct cfg-compatibility guard +# ====================================================================== +# +# A stable direct env class can be shared by several tasks (e.g. ReorientDirectEnv +# serves the Allegro and Shadow hands via different cfgs). Name resolution yields a +# single warp twin that may implement only one variant, so the twin declares the cfg +# it supports via its ``cfg`` annotation and the frontend refuses a mismatched cfg +# rather than silently running the wrong MDP. + + +class _GuardDeclaredCfg: + pass + + +class _GuardSubclassCfg(_GuardDeclaredCfg): + pass + + +class _GuardUnrelatedCfg: + pass + + +class _GuardWarpEnv: + cfg: _GuardDeclaredCfg # under `from __future__ import annotations` this stores the + # identifier string "_GuardDeclaredCfg", exactly as a real warp env's `cfg: `. + + +class _GuardWarpEnvNoAnnotation: + pass + + +def test_declared_stable_cfg_name_reads_annotation(): + assert fe.WarpFrontend._declared_stable_cfg_name(_GuardWarpEnv) == "_GuardDeclaredCfg" + assert fe.WarpFrontend._declared_stable_cfg_name(_GuardWarpEnvNoAnnotation) is None + + +def test_assert_cfg_supported_accepts_exact_cfg(): + fe.WarpFrontend._assert_cfg_supported(_GuardWarpEnv, _GuardDeclaredCfg(), "_task") # no raise + + +def test_assert_cfg_supported_accepts_subclass_cfg(): + # A task cfg that IS-A the declared cfg is accepted (covers camera/variant subclasses). + fe.WarpFrontend._assert_cfg_supported(_GuardWarpEnv, _GuardSubclassCfg(), "_task") # no raise + + +def test_assert_cfg_supported_rejects_unrelated_cfg(): + # This is the Shadow-reorient-into-Allegro-twin regression: a cfg the twin does + # not implement must be a hard error, not a silent wrong-MDP run. + with pytest.raises(FrontendIncompatibleError) as exc: + fe.WarpFrontend._assert_cfg_supported(_GuardWarpEnv, _GuardUnrelatedCfg(), "_task") + msg = str(exc.value) + assert "_GuardDeclaredCfg" in msg and "_GuardUnrelatedCfg" in msg + + +def test_assert_cfg_supported_skips_when_no_annotation(): + # No cfg contract declared → nothing to enforce → never raises. + fe.WarpFrontend._assert_cfg_supported(_GuardWarpEnvNoAnnotation, _GuardUnrelatedCfg(), "_task") + + +# --- resolve correctness on the REAL shared reorient env class --------- +# +# `_Frontend-Test-Reorient-Direct-v0` uses the real ReorientDirectEnv entry point, +# so resolution mirrors to the real ReorientDirectWarpEnv (declared cfg +# AllegroHandEnvCfg). The gate keys on the cfg *class name*, so lightweight stub +# cfgs named exactly as the real ones exercise the accept/reject paths without +# constructing the heavy real configs. + + +class AllegroHandEnvCfg: # noqa: N801 - name mirrors the real cfg the twin implements + pass + + +class ShadowHandEnvCfg: # noqa: N801 - name mirrors the real Shadow cfg the twin does NOT implement + pass + + +def test_resolve_returns_twin_when_cfg_matches(): + env_class = fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-Reorient-Direct-v0", AllegroHandEnvCfg()) + assert env_class is not None + assert env_class.__name__ == "ReorientDirectWarpEnv" + + +def test_resolve_rejects_twin_when_cfg_mismatches(): + # The Shadow-reorient regression: the shared env name-mirrors to the Allegro + # twin, but the twin does not implement the Shadow cfg → hard error, not a + # silent wrong-MDP run. + with pytest.raises(FrontendIncompatibleError) as exc: + fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-Reorient-Direct-v0", ShadowHandEnvCfg()) + msg = str(exc.value) + assert "AllegroHandEnvCfg" in msg and "ShadowHandEnvCfg" in msg + + +def test_resolve_name_only_skips_cfg_check(): + # Without a cfg, resolution reports the name mapping alone (no applicability gate). + env_class = fe.WarpFrontend._resolve_direct_warp_class("_Frontend-Test-Reorient-Direct-v0") + assert env_class is not None + assert env_class.__name__ == "ReorientDirectWarpEnv" From ac3d964dc401e7b88438d0f93235a8a972aa0a69 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 23 Jul 2026 23:51:11 -0700 Subject: [PATCH 8/8] Traverse dict-typed manager groups in the warp frontend The warp reward/event managers accept a manager group expressed as a {name: term} dict as well as a configclass, but _walk_terms stopped at any non-configclass node -- so a dict group's terms were never discovered and never swapped to their warp twins, leaving torch-signature callables that fail under the warp managers. Recurse into mapping values in _walk_terms so both group forms are covered. No warp-supported task uses dict groups today; this closes the gap before one does. --- .../isaaclab_experimental/envs/frontend.py | 14 +++++++++++++- .../test/envs/test_frontend.py | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py index 1439159a69d2..467bd718bc3f 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py @@ -35,7 +35,7 @@ import importlib import importlib.util import logging -from collections.abc import Iterable, Iterator +from collections.abc import Iterable, Iterator, Mapping from enum import StrEnum from types import ModuleType from typing import Any, ClassVar @@ -659,6 +659,10 @@ def _walk_terms(node: Any, path: tuple[str, ...] = ()) -> Iterator[tuple[tuple[s ``rewards``, ``events``, ``actions``, sub-groups like ``observations.policy``, and anything nested deeper are reached transparently. + * Mapping (a group expressed as a ``{name: term}`` dict rather than a + configclass): don't yield; recurse into each value. The warp managers + accept both dict and configclass groups, so the walker must reach the + terms either way or they would never be swapped. * Anything else (plain Python data, callables, non-configclass objects): stop. No yield, no recursion. @@ -677,6 +681,14 @@ def _walk_terms(node: Any, path: tuple[str, ...] = ()) -> Iterator[tuple[tuple[s if isinstance(node, (ManagerTermBaseCfg, ActionTermCfg)): yield path, node # a term: yield and stop; never descend into params/func return + if isinstance(node, Mapping): + # A manager group can be a dict of {name: term} instead of a + # configclass; the warp managers accept both, so descend into dict + # values too or those terms would never be promoted/swapped. + for key, value in node.items(): + if value is not None: + yield from WarpFrontend._walk_terms(value, path + (str(key),)) + return if not hasattr(node, "__dataclass_fields__"): return # plain data / callables: not a cfg subtree for name, value in vars(node).items(): # instance attrs — the same view the managers iterate diff --git a/source/isaaclab_experimental/test/envs/test_frontend.py b/source/isaaclab_experimental/test/envs/test_frontend.py index e1c73f957d85..a56b7f368156 100644 --- a/source/isaaclab_experimental/test/envs/test_frontend.py +++ b/source/isaaclab_experimental/test/envs/test_frontend.py @@ -285,6 +285,15 @@ def test_walk_terms_skips_none_subtrees(): assert list(fe.WarpFrontend._walk_terms(cfg)) == [] +def test_walk_terms_descends_into_dict_groups(): + # A manager group expressed as a dict of {name: term} (rather than a configclass) + # must still have its terms discovered — the warp managers accept both forms. + cfg = _CfgFixture() + cfg.rewards = {"r1": _term(), "r2": _term()} + paths = {".".join(p) for p, _ in fe.WarpFrontend._walk_terms(cfg)} + assert paths == {"rewards.r1", "rewards.r2"} + + @configclass class _GroupWithNestedClass: """Mirrors the real ``ObservationsCfg.PolicyCfg`` nested-class pattern."""