.. _migration_isaac_lab:

Migrating from Isaac Lab
========================

.. warning::

   This guide is a work in progress. As more users migrate, we will update this
   page with additional patterns and edge cases. If something is not covered,
   please open an issue on GitHub or start a discussion:

   - Issues: https://github.com/mujocolab/mjlab/issues
   - Discussions: https://github.com/mujocolab/mjlab/discussions

TL;DR
-----

Most Isaac Lab *manager-based* task configs can be ported to ``mjlab`` with
only small changes:

- The overall **MDP structure is the same** (managers for rewards, observations,
  actions, commands, terminations, events, curriculum).
- The **environment base classes are similar**, but naming is slightly
  different.
- The biggest change is **configuration style**: Isaac Lab uses nested
  ``@configclass`` definitions; ``mjlab`` uses dictionaries of config objects.

If you are familiar with Isaac Lab's manager-based API, migration is mostly
mechanical.

Key Differences
---------------

1. Import Paths
~~~~~~~~~~~~~~~

Isaac Lab:

.. code-block:: python

   from isaaclab.envs import ManagerBasedRLEnv

mjlab:

.. code-block:: python

   from mjlab.envs import ManagerBasedRlEnvCfg

.. note::

   ``mjlab`` uses a consistent ``CamelCase`` naming convention (for example,
   ``RlEnv`` instead of ``RLEnv``).

2. Configuration Structure
~~~~~~~~~~~~~~~~~~~~~~~~~~

Isaac Lab uses nested ``@configclass`` blocks for manager terms. ``mjlab``
instead uses **plain dictionaries** mapping names to config objects, which makes
it easy to construct variants, merge configs, or generate them programmatically.
For the full context behind this design decision, see
`PR #292 <https://github.com/mujocolab/mjlab/pull/292>`_.

**Isaac Lab:**

.. code-block:: python

   @configclass
   class RewardsCfg:
       """Reward terms for the MDP."""

       motion_global_anchor_pos = RewTerm(
           func=mdp.motion_global_anchor_position_error_exp,
           weight=0.5,
           params={"command_name": "motion", "std": 0.3},
       )
       motion_global_anchor_ori = RewTerm(
           func=mdp.motion_global_anchor_orientation_error_exp,
           weight=0.5,
           params={"command_name": "motion", "std": 0.4},
       )

**mjlab:**

.. code-block:: python

   rewards = {
       "motion_global_anchor_pos": RewardTermCfg(
           func=mdp.motion_global_anchor_position_error_exp,
           weight=0.5,
           params={"command_name": "motion", "std": 0.3},
       ),
       "motion_global_anchor_ori": RewardTermCfg(
           func=mdp.motion_global_anchor_orientation_error_exp,
           weight=0.5,
           params={"command_name": "motion", "std": 0.4},
       ),
   }

   cfg = ManagerBasedRlEnvCfg(
       scene=scene,
       rewards=rewards,
       # ... other manager dictionaries:
       # observations=..., actions=..., commands=..., terminations=...,
       # events=..., curriculum=...
   )

This pattern applies to all managers:

- ``rewards``
- ``observations``
- ``actions``
- ``commands``
- ``terminations``
- ``events``
- ``curriculum``

3. Scene Configuration
~~~~~~~~~~~~~~~~~~~~~~

Scene setup is **simpler** in ``mjlab``:

- No Omniverse / USD scene graph, no ``prim_path`` management.
- Assets are pure MuJoCo (MJCF) with modifier dataclasses applied to
  ``mujoco.MjSpec``.
- Lights, materials, textures, and sensors are configured as part of
  ``SceneCfg`` and robot configs.

**Isaac Lab:**

.. code-block:: python

   from whole_body_tracking.robots.g1 import G1_ACTION_SCALE, G1_CYLINDER_CFG
   from isaaclab.scene import InteractiveSceneCfg
   from isaaclab.sensors import ContactSensorCfg
   from isaaclab.terrains import TerrainImporterCfg
   import isaaclab.sim as sim_utils
   from isaaclab.assets import ArticulationCfg, AssetBaseCfg

   @configclass
   class MySceneCfg(InteractiveSceneCfg):
       """Configuration for the terrain scene with a legged robot."""

       # ground terrain
       terrain = TerrainEntityCfg(
           prim_path="/World/ground",
           terrain_type="plane",
           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="{NVIDIA_NUCLEUS_DIR}/Materials/Base/Architecture/Shingles_01.mdl",
               project_uvw=True,
           ),
       )
       # lights
       light = AssetBaseCfg(
           prim_path="/World/light",
           spawn=sim_utils.DistantLightCfg(
               color=(0.75, 0.75, 0.75), intensity=3000.0
           ),
       )
       sky_light = AssetBaseCfg(
           prim_path="/World/skyLight",
           spawn=sim_utils.DomeLightCfg(
               color=(0.13, 0.13, 0.13), intensity=1000.0
           ),
       )
       robot = G1_CYLINDER_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")

**mjlab:**

.. code-block:: python

   from dataclasses import replace

   from mjlab.scene import SceneCfg
   from mjlab.asset_zoo.robots.unitree_g1.g1_constants import get_g1_robot_cfg
   from mjlab.utils.spec_config import ContactSensorCfg
   from mjlab.terrains import TerrainEntityCfg

   # Configure contact sensor
   self_collision_sensor = ContactSensorCfg(
       name="self_collision",
       subtree1="pelvis",
       subtree2="pelvis",
       data=("found",),
       reduce="netforce",
       num=10,  # report up to 10 contacts
   )

   # Add sensor to robot config
   g1_cfg = replace(get_g1_robot_cfg(), sensors=(self_collision_sensor,))

   # Create scene
   SCENE_CFG = SceneCfg(
       terrain=TerrainEntityCfg(terrain_type="plane"),
       entities={"robot": g1_cfg},
   )

Key changes:

- No USD ``prim_path`` or cloning; the scene is described directly in MuJoCo.
- Materials, lights, and visual properties are applied via
  ``MjSpec``-modifier dataclasses.
- See ``mjlab.utils.spec_config`` in the repository for helpers that apply
  these changes for you.
- ``asset_name`` has been unified to ``entity_name`` across all configurations.

Complete Example Comparison
---------------------------

A good way to learn the pattern is to compare concrete tasks that have already
been ported:

- Isaac Lab implementation (Beyond Mimic):

  - https://github.com/HybridRobotics/whole_body_tracking/blob/main/source/whole_body_tracking/whole_body_tracking/tasks/tracking/tracking_env_cfg.py

- mjlab implementation:

  - https://github.com/mujocolab/mjlab/blob/main/src/mjlab/tasks/tracking/tracking_env_cfg.py

You will see that:

- Manager dictionaries in ``mjlab`` mirror Isaac Lab's config classes,
- Reward, observation, command, and termination logic is almost identical,
- Scene and asset setup are simplified to pure MuJoCo.

Migration Checklist
-------------------

Use this as a quick checklist when porting a task:

1. **Base class and imports**

   - Replace Isaac Lab imports (for example,
     ``from isaaclab.envs import ManagerBasedRLEnv``) with the corresponding
     ``mjlab`` imports (for example,
     ``from mjlab.envs import ManagerBasedRlEnvCfg``).

2. **Manager configuration**

   - Convert each Isaac Lab ``@configclass`` manager (``RewardsCfg``,
     ``ObservationsCfg``, etc.) into a dictionary of config objects.
   - Pass these dictionaries into ``ManagerBasedRlEnvCfg``.

3. **Scene and assets**

   - Replace ``InteractiveSceneCfg`` with a ``SceneCfg`` instance.
   - Replace USD / ``prim_path`` logic with MuJoCo asset configs and scene
     entities (for example, a robot from ``asset_zoo``).

4. **Sensors and contact handling**

   - Convert Isaac Lab ``ContactSensorCfg`` to
     ``mjlab.utils.spec_config.ContactSensorCfg`` and attach it to the robot
     config.

5. **RL entry points**

   - Make sure your training script or entry point uses the correct task id and
     environment config (for example, via Gymnasium registration or direct
     construction, depending on how your project is structured).

Tips and Support
----------------

1. Check the examples in the repository under:

   - ``src/mjlab/tasks/``

2. If you get stuck:

   - Open an issue: https://github.com/mujocolab/mjlab/issues
   - Start a discussion: https://github.com/mujocolab/mjlab/discussions

3. Keep in mind MuJoCo vs Isaac Sim differences:

   - Some Omniverse / USD rendering features do not have direct equivalents.
   - Focus first on matching the **physics and observations**, then polish
     visuals if needed.
