diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60fe57a36a..3a4ae5c107 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: - id: pyupgrade args: ["--py310-plus"] # FIXME: This is a hack because Pytorch does not like: torch.Tensor | dict aliasing - exclude: "source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env.py" + exclude: "source/extensions/omni.isaac.lab/omni/isaac/lab/envs/types.py" - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: diff --git a/.vscode/tools/setup_vscode.py b/.vscode/tools/setup_vscode.py index 00b2010d2a..1e682059ce 100644 --- a/.vscode/tools/setup_vscode.py +++ b/.vscode/tools/setup_vscode.py @@ -22,6 +22,9 @@ """Path to the Isaac Lab directory.""" ISAACSIM_DIR = os.path.join(ISAACLAB_DIR, "_isaac_sim") """Path to the isaac-sim directory.""" +# check if ISAACSIM_DIR is valid: +if not os.path.isdir(ISAACSIM_DIR): + ISAACSIM_DIR = os.environ.get("ISAACSIM_PATH", "") def overwrite_python_analysis_extra_paths(isaaclab_settings: str) -> str: diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c2750df89c..cd902a3ba1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -24,7 +24,8 @@ Guidelines for modifications: * Hunter Hansen * James Smith * James Tigue -* **Mayank Mittal** (maintainer) +* Kelly Guo +* Mayank Mittal * Nikita Rudin * Pascal Roth diff --git a/README.md b/README.md index 7653a61abd..ee91564483 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -![Example Tasks created with Isaac Lab](docs/source/_static/tasks.jpg) +![Isaac Lab](docs/source/_static/isaaclab.jpg) --- # Isaac Lab -[![IsaacSim](https://img.shields.io/badge/IsaacSim-2023.1.1-silver.svg)](https://docs.omniverse.nvidia.com/isaacsim/latest/overview.html) +[![IsaacSim](https://img.shields.io/badge/IsaacSim-4.0-silver.svg)](https://docs.omniverse.nvidia.com/isaacsim/latest/overview.html) [![Python](https://img.shields.io/badge/python-3.10-blue.svg)](https://docs.python.org/3/whatsnew/3.10.html) [![Linux platform](https://img.shields.io/badge/platform-linux--64-orange.svg)](https://releases.ubuntu.com/20.04/) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://pre-commit.com/) @@ -54,20 +54,3 @@ or opening a question on its [forums](https://forums.developer.nvidia.com/c/agx- NVIDIA Isaac Sim is available freely under [individual license](https://www.nvidia.com/en-us/omniverse/download/). For more information about its license terms, please check [here](https://docs.omniverse.nvidia.com/app_isaacsim/common/NVIDIA_Omniverse_License_Agreement.html#software-support-supplement). The Isaac Lab framework is released under [BSD-3 License](LICENSE). The license files of its dependencies and assets are present in the [`docs/licenses`](docs/licenses) directory. - -## Citing - -If you use this framework in your work, please cite [this paper](https://arxiv.org/abs/2301.04195): - -```text -@article{mittal2023orbit, - author={Mittal, Mayank and Yu, Calvin and Yu, Qinxi and Liu, Jingzhou and Rudin, Nikita and Hoeller, David and Yuan, Jia Lin and Singh, Ritvik and Guo, Yunrong and Mazhar, Hammad and Mandlekar, Ajay and Babich, Buck and State, Gavriel and Hutter, Marco and Garg, Animesh}, - journal={IEEE Robotics and Automation Letters}, - title={Orbit: A Unified Simulation Framework for Interactive Robot Learning Environments}, - year={2023}, - volume={8}, - number={6}, - pages={3740-3747}, - doi={10.1109/LRA.2023.3270034} -} -``` diff --git a/docker/.env.base b/docker/.env.base index 28679fdcae..a78248a4e3 100644 --- a/docker/.env.base +++ b/docker/.env.base @@ -4,8 +4,8 @@ # Accept the NVIDIA Omniverse EULA by default ACCEPT_EULA=Y -# NVIDIA Isaac Sim version to use (e.g. 2023.1.1, 2023.1.0-hotfix.1) -ISAACSIM_VERSION=2023.1.1 +# NVIDIA Isaac Sim version to use (e.g. 4.0.0, 2023.1.1) +ISAACSIM_VERSION=4.0.0 # Derived from the default path in the NVIDIA provided Isaac Sim container DOCKER_ISAACSIM_ROOT_PATH=/isaac-sim # The Isaac Lab path in the container diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base index d7920e2afc..a8a17da612 100644 --- a/docker/Dockerfile.base +++ b/docker/Dockerfile.base @@ -22,7 +22,7 @@ LABEL description="Dockerfile for building and running the Isaac Lab framework i # Path to Isaac Sim root folder ARG ISAACSIM_ROOT_PATH_ARG ENV ISAACSIM_ROOT_PATH=${ISAACSIM_ROOT_PATH_ARG} -# Path to Isaac Lab directory +# Path to the Isaac Lab directory ARG ISAACLAB_PATH_ARG ENV ISAACLAB_PATH=${ISAACLAB_PATH_ARG} # Home dir of docker user, typically '/root' @@ -33,6 +33,8 @@ ENV DOCKER_USER_HOME=${DOCKER_USER_HOME_ARG} ENV LANG=C.UTF-8 ENV DEBIAN_FRONTEND=noninteractive +USER root + # Install dependencies and remove cache RUN --mount=type=cache,target=/var/cache/apt \ apt-get update && apt-get install -y --no-install-recommends \ @@ -50,12 +52,6 @@ COPY ../ ${ISAACLAB_PATH} # Set up a symbolic link between the installed Isaac Sim root folder and _isaac_sim in the Isaac Lab directory RUN ln -sf ${ISAACSIM_ROOT_PATH} ${ISAACLAB_PATH}/_isaac_sim -# Install apt dependencies for extensions that declare them in their extension.toml -RUN --mount=type=cache,target=/var/cache/apt \ - ${ISAACLAB_PATH}/isaaclab.sh --install-deps apt && \ - apt -y autoremove && apt clean autoclean && \ - rm -rf /var/lib/apt/lists/* - # for singularity usage, have to create the directories that will binded RUN mkdir -p ${ISAACSIM_ROOT_PATH}/kit/cache && \ mkdir -p ${DOCKER_USER_HOME}/.cache/ov && \ @@ -79,10 +75,11 @@ RUN touch /bin/nvidia-smi && \ # installing Isaac Lab dependencies # use pip caching to avoid reinstalling large packages RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip \ - ${ISAACLAB_PATH}/isaaclab.sh --install --extra + ${ISAACLAB_PATH}/isaaclab.sh --install # aliasing isaaclab.sh and python for convenience -RUN echo "alias isaaclab=${ISAACLAB_PATH}/isaaclab.sh" >> ${HOME}/.bashrc && \ +RUN echo "export ISAACLAB_PATH=${ISAACLAB_PATH}" >> ${HOME}/.bashrc && \ + echo "alias isaaclab=${ISAACLAB_PATH}/isaaclab.sh" >> ${HOME}/.bashrc && \ echo "alias python=${ISAACLAB_PATH}/_isaac_sim/python.sh" >> ${HOME}/.bashrc && \ echo "alias python3=${ISAACLAB_PATH}/_isaac_sim/python.sh" >> ${HOME}/.bashrc && \ echo "alias pip='${ISAACLAB_PATH}/_isaac_sim/python.sh -m pip'" >> ${HOME}/.bashrc && \ diff --git a/docker/Dockerfile.ros2 b/docker/Dockerfile.ros2 index de7a516f5c..9d4e66dd7a 100644 --- a/docker/Dockerfile.ros2 +++ b/docker/Dockerfile.ros2 @@ -22,9 +22,6 @@ RUN --mount=type=cache,target=/var/cache/apt \ ros-humble-rmw-fastrtps-cpp \ # This includes various dev tools including colcon ros-dev-tools && \ - # Install rosdeps for extensions that declare a ros_ws in - # their extension.toml - ${ISAACLAB_PATH}/isaaclab.sh --install-deps rosdep && \ apt -y autoremove && apt clean autoclean && \ rm -rf /var/lib/apt/lists/* && \ # Add sourcing of setup.bash to .bashrc diff --git a/docker/container.sh b/docker/container.sh index a29b7eb2c6..e16fad5847 100755 --- a/docker/container.sh +++ b/docker/container.sh @@ -363,7 +363,7 @@ case $mode in # make sure target directory exists ssh $CLUSTER_LOGIN "mkdir -p $CLUSTER_ISAACLAB_DIR" # Sync Isaac Lab code - echo "[INFO] Syncing ISaac Lab code..." + echo "[INFO] Syncing Isaac Lab code..." rsync -rh --exclude="*.git*" --filter=':- .dockerignore' /$SCRIPT_DIR/.. $CLUSTER_LOGIN:$CLUSTER_ISAACLAB_DIR # execute job script echo "[INFO] Executing job script..." diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 9b5f49bee4..b6441edc3d 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -128,7 +128,7 @@ volumes: isaac-carb-logs: isaac-data: isaac-docs: - # isaaclab + # isaac-lab isaac-lab-docs: isaac-lab-logs: isaac-lab-data: diff --git a/docs/conf.py b/docs/conf.py index ad48298c68..2c57b4f3a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,6 +51,7 @@ "sphinxcontrib.bibtex", "sphinx_copybutton", "sphinx_design", + "sphinx_tabs.tabs", ] # mathjax hacks @@ -159,8 +160,8 @@ # List of zero or more Sphinx-specific warning categories to be squelched (i.e., # suppressed, ignored). suppress_warnings = [ - # FIXME: *THIS IS TERRIBLE.* Generally speaking, we do want Sphinx to inform - # us about cross-referencing failures. Remove this hack entirely after Sphinx + # Generally speaking, we do want Sphinx to inform + # us about cross-referencing failures. Remove this entirely after Sphinx # resolves this open issue: # https://github.com/sphinx-doc/sphinx/issues/4961 # Squelch mostly ignorable warnings resembling: @@ -224,7 +225,7 @@ { "name": "Isaac Sim", "url": "https://developer.nvidia.com/isaac-sim", - "icon": "https://img.shields.io/badge/IsaacSim-2023.1.1-silver.svg", + "icon": "https://img.shields.io/badge/IsaacSim-4.0-silver.svg", "type": "url", }, { diff --git a/docs/index.rst b/docs/index.rst index 3b6f2cb929..8a9e0d99ae 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,10 @@ Overview ======== +.. figure:: source/_static/isaaclab.jpg + :width: 100% + :alt: H1 Humanoid example using Isaac Lab + **Isaac Lab** is a unified and modular framework for robot learning that aims to simplify common workflows in robotics research (such as RL, learning from demonstrations, and motion planning). It is built upon `NVIDIA Isaac Sim`_ to leverage the latest simulation capabilities for photo-realistic scenes, and fast @@ -11,6 +15,10 @@ and efficient simulation. The core objectives of the framework are: - **Openness**: Remain open-sourced to allow the community to contribute and extend the framework. - **Battery-included**: Include a number of environments, sensors, and tasks that are ready to use. +Key features available in Isaac Lab include fast and accurate physics simulation provided by PhysX, +tiled rendering APIs for vectorized rendering, domain randomization for improving robustness and adaptability, +and support for running in the cloud. + For more information about the framework, please refer to the `paper `_ :cite:`mittal2023orbit`. For clarifications on NVIDIA Isaac ecosystem, please check out the :doc:`/source/setup/faq` section. @@ -20,25 +28,6 @@ For more information about the framework, please refer to the `paper `_, please make sure + the Isaac Lab directory is placed under the ``/home`` directory tree when using docker. + Obtaining the Isaac Sim Container ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -253,12 +258,12 @@ versions installed on your machine. To fix this, you can try the following: * Install the latest version of docker based on the instructions in the setup section. -WebRTC and WebSocket Streaming -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +WebRTC Streaming +~~~~~~~~~~~~~~~~ When streaming the GUI from Isaac Sim, there are `several streaming clients`_ available. There is a `known issue`_ when attempting to use WebRTC streaming client on Google Chrome and Safari while running Isaac Sim inside a container. -To avoid this problem, we suggest using either the Native Streaming Client or WebSocket options, or using the +To avoid this problem, we suggest using the Native Streaming Client or using the Mozilla Firefox browser on which WebRTC works. Streaming is the only supported method for visualizing the Isaac GUI from within the container. The Omniverse Streaming Client diff --git a/docs/source/features/environments.rst b/docs/source/features/environments.rst index f744f080c4..47030b4f0f 100644 --- a/docs/source/features/environments.rst +++ b/docs/source/features/environments.rst @@ -24,20 +24,31 @@ Classic environments that are based on IsaacGymEnvs implementation of MuJoCo-sty +------------------+-----------------------------+-------------------------------------------------------------------------+ | World | Environment ID | Description | +==================+=============================+=========================================================================+ - | |humanoid| | |humanoid-link| | Move towards a direction with the MuJoCo humanoid robot | + | |humanoid| | | |humanoid-link| | Move towards a direction with the MuJoCo humanoid robot | + | | | |humanoid-direct-link| | | +------------------+-----------------------------+-------------------------------------------------------------------------+ - | |ant| | |ant-link| | Move towards a direction with the MuJoCo ant robot | + | |ant| | | |ant-link| | Move towards a direction with the MuJoCo ant robot | + | | | |ant-direct-link| | | +------------------+-----------------------------+-------------------------------------------------------------------------+ - | |cartpole| | |cartpole-link| | Move the cart to keep the pole upwards in the classic cartpole control | + | |cartpole| | | |cartpole-link| | Move the cart to keep the pole upwards in the classic cartpole control | + | | | |cartpole-direct-link| | | + | | | |cartpole-camera-rgb-link|| | + | | | |cartpole-camera-dpt-link|| | +------------------+-----------------------------+-------------------------------------------------------------------------+ .. |humanoid| image:: ../_static/tasks/classic/humanoid.jpg .. |ant| image:: ../_static/tasks/classic/ant.jpg .. |cartpole| image:: ../_static/tasks/classic/cartpole.jpg -.. |humanoid-link| replace:: `Isaac-Humanoid-v0 `__ -.. |ant-link| replace:: `Isaac-Ant-v0 `__ -.. |cartpole-link| replace:: `Isaac-Cartpole-v0 `__ +.. |humanoid-link| replace:: `Isaac-Humanoid-v0 `__ +.. |ant-link| replace:: `Isaac-Ant-v0 `__ +.. |cartpole-link| replace:: `Isaac-Cartpole-v0 `__ + +.. |humanoid-direct-link| replace:: `Isaac-Humanoid-Direct-v0 `__ +.. |ant-direct-link| replace:: `Isaac-Ant-Direct-v0 `__ +.. |cartpole-direct-link| replace:: `Isaac-Cartpole-Direct-v0 `__ +.. |cartpole-camera-rgb-link| replace:: `Isaac-Cartpole-RGB-Camera-Direct-v0 `__ +.. |cartpole-camera-dpt-link| replace:: `Isaac-Cartpole-Depth-Camera-Direct-v0 `__ Manipulation @@ -55,33 +66,42 @@ for the reach environment: .. table:: :widths: 33 37 30 - +----------------+---------------------+-----------------------------------------------------------------------------+ - | World | Environment ID | Description | - +================+=====================+=============================================================================+ - | |reach-franka| | |reach-franka-link| | Move the end-effector to a sampled target pose with the Franka robot | - +----------------+---------------------+-----------------------------------------------------------------------------+ - | |reach-ur10| | |reach-ur10-link| | Move the end-effector to a sampled target pose with the UR10 robot | - +----------------+---------------------+-----------------------------------------------------------------------------+ - | |lift-cube| | |lift-cube-link| | Pick a cube and bring it to a sampled target position with the Franka robot | - +----------------+---------------------+-----------------------------------------------------------------------------+ - | |cabi-franka| | |cabi-franka-link| | Grasp the handle of a cabinet's drawer and open it with the Franka robot | - +----------------+---------------------+-----------------------------------------------------------------------------+ - | |cube-allegro| | |cube-allegro-link| | In-hand reorientation of a cube using Allegro hand | - +----------------+---------------------+-----------------------------------------------------------------------------+ + +----------------+---------------------------+-----------------------------------------------------------------------------+ + | World | Environment ID | Description | + +================+===========================+=============================================================================+ + | |reach-franka| | |reach-franka-link| | Move the end-effector to a sampled target pose with the Franka robot | + +----------------+---------------------------+-----------------------------------------------------------------------------+ + | |reach-ur10| | |reach-ur10-link| | Move the end-effector to a sampled target pose with the UR10 robot | + +----------------+---------------------------+-----------------------------------------------------------------------------+ + | |lift-cube| | |lift-cube-link| | Pick a cube and bring it to a sampled target position with the Franka robot | + +----------------+---------------------------+-----------------------------------------------------------------------------+ + | |cabi-franka| | |cabi-franka-link| | Grasp the handle of a cabinet's drawer and open it with the Franka robot | + +----------------+---------------------------+-----------------------------------------------------------------------------+ + | |cube-allegro| | |cube-allegro-link| | In-hand reorientation of a cube using Allegro hand | + +----------------+---------------------------+-----------------------------------------------------------------------------+ + | |cube-shadow| | | |cube-shadow-link| | In-hand reorientation of a cube using Shadow hand | + | | | |cube-shadow-ff-link| | | + | | | |cube-shadow-lstm-link| | | + +----------------+---------------------------+-----------------------------------------------------------------------------+ .. |reach-franka| image:: ../_static/tasks/manipulation/franka_reach.jpg .. |reach-ur10| image:: ../_static/tasks/manipulation/ur10_reach.jpg .. |lift-cube| image:: ../_static/tasks/manipulation/franka_lift.jpg .. |cabi-franka| image:: ../_static/tasks/manipulation/franka_open_drawer.jpg .. |cube-allegro| image:: ../_static/tasks/manipulation/allegro_cube.jpg +.. |cube-shadow| image:: ../_static/tasks/manipulation/shadow_cube.jpg + +.. |reach-franka-link| replace:: `Isaac-Reach-Franka-v0 `__ +.. |reach-ur10-link| replace:: `Isaac-Reach-UR10-v0 `__ +.. |lift-cube-link| replace:: `Isaac-Lift-Cube-Franka-v0 `__ +.. |lift-cube-ik-abs-link| replace:: `Isaac-Lift-Cube-Franka-IK-Abs-v0 `__ +.. |lift-cube-ik-rel-link| replace:: `Isaac-Lift-Cube-Franka-IK-Rel-v0 `__ +.. |cabi-franka-link| replace:: `Isaac-Open-Drawer-Franka-v0 `__ +.. |cube-allegro-link| replace:: `Isaac-Repose-Cube-Allegro-v0 `__ -.. |reach-franka-link| replace:: `Isaac-Reach-Franka-v0 `__ -.. |reach-ur10-link| replace:: `Isaac-Reach-UR10-v0 `__ -.. |lift-cube-link| replace:: `Isaac-Lift-Cube-Franka-v0 `__ -.. |lift-cube-ik-abs-link| replace:: `Isaac-Lift-Cube-Franka-IK-Abs-v0 `__ -.. |lift-cube-ik-rel-link| replace:: `Isaac-Lift-Cube-Franka-IK-Rel-v0 `__ -.. |cabi-franka-link| replace:: `Isaac-Open-Drawer-Franka-v0 `__ -.. |cube-allegro-link| replace:: `Isaac-Repose-Cube-Allegro-v0 `__ +.. |cube-shadow-link| replace:: `Isaac-Shadow-Hand-Direct-v0 `__ +.. |cube-shadow-ff-link| replace:: `Isaac-Shadow-Hand-OpenAI-FF-Direct-v0 `__ +.. |cube-shadow-lstm-link| replace:: `Isaac-Shadow-Hand-OpenAI-LSTM-Direct-v0 `__ Locomotion ---------- @@ -98,9 +118,11 @@ Environments based on legged locomotion tasks. +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ | |velocity-rough-anymal-b| | |velocity-rough-anymal-b-link| | Track a velocity command on rough terrain with the Anymal B robot | +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ - | |velocity-flat-anymal-c| | |velocity-flat-anymal-c-link| | Track a velocity command on flat terrain with the Anymal C robot | + | |velocity-flat-anymal-c| | | |velocity-flat-anymal-c-link| | Track a velocity command on flat terrain with the Anymal C robot | + | | | |velocity-flat-anymal-c-direct-link| | | +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ - | |velocity-rough-anymal-c| | |velocity-rough-anymal-c-link| | Track a velocity command on rough terrain with the Anymal C robot | + | |velocity-rough-anymal-c| | | |velocity-rough-anymal-c-link| | Track a velocity command on rough terrain with the Anymal C robot | + | | | |velocity-rough-anymal-c-direct-link| | | +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ | |velocity-flat-anymal-d| | |velocity-flat-anymal-d-link| | Track a velocity command on flat terrain with the Anymal D robot | +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ @@ -118,24 +140,34 @@ Environments based on legged locomotion tasks. +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ | |velocity-rough-unitree-go2| | |velocity-rough-unitree-go2-link| | Track a velocity command on rough terrain with the Unitree Go2 robot | +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ + | |velocity-flat-h1| | |velocity-flat-h1-link| | Track a velocity command on flat terrain with the Unitree H1 robot | + +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ + | |velocity-rough-h1| | |velocity-rough-h1-link| | Track a velocity command on rough terrain with the Unitree H1 robot | + +------------------------------+----------------------------------------------+-------------------------------------------------------------------------+ + +.. |velocity-flat-anymal-b-link| replace:: `Isaac-Velocity-Flat-Anymal-B-v0 `__ +.. |velocity-rough-anymal-b-link| replace:: `Isaac-Velocity-Rough-Anymal-B-v0 `__ -.. |velocity-flat-anymal-b-link| replace:: `Isaac-Velocity-Flat-Anymal-B-v0 `__ -.. |velocity-rough-anymal-b-link| replace:: `Isaac-Velocity-Rough-Anymal-B-v0 `__ +.. |velocity-flat-anymal-c-link| replace:: `Isaac-Velocity-Flat-Anymal-C-v0 `__ +.. |velocity-rough-anymal-c-link| replace:: `Isaac-Velocity-Rough-Anymal-C-v0 `__ -.. |velocity-flat-anymal-c-link| replace:: `Isaac-Velocity-Flat-Anymal-C-v0 `__ -.. |velocity-rough-anymal-c-link| replace:: `Isaac-Velocity-Rough-Anymal-C-v0 `__ +.. |velocity-flat-anymal-c-direct-link| replace:: `Isaac-Velocity-Flat-Anymal-C-Direct-v0 `__ +.. |velocity-rough-anymal-c-direct-link| replace:: `Isaac-Velocity-Rough-Anymal-C-Direct-v0 `__ -.. |velocity-flat-anymal-d-link| replace:: `Isaac-Velocity-Flat-Anymal-D-v0 `__ -.. |velocity-rough-anymal-d-link| replace:: `Isaac-Velocity-Rough-Anymal-D-v0 `__ +.. |velocity-flat-anymal-d-link| replace:: `Isaac-Velocity-Flat-Anymal-D-v0 `__ +.. |velocity-rough-anymal-d-link| replace:: `Isaac-Velocity-Rough-Anymal-D-v0 `__ -.. |velocity-flat-unitree-a1-link| replace:: `Isaac-Velocity-Flat-Unitree-A1-v0 `__ -.. |velocity-rough-unitree-a1-link| replace:: `Isaac-Velocity-Rough-Unitree-A1-v0 `__ +.. |velocity-flat-unitree-a1-link| replace:: `Isaac-Velocity-Flat-Unitree-A1-v0 `__ +.. |velocity-rough-unitree-a1-link| replace:: `Isaac-Velocity-Rough-Unitree-A1-v0 `__ -.. |velocity-flat-unitree-go1-link| replace:: `Isaac-Velocity-Flat-Unitree-Go1-v0 `__ -.. |velocity-rough-unitree-go1-link| replace:: `Isaac-Velocity-Rough-Unitree-Go1-v0 `__ +.. |velocity-flat-unitree-go1-link| replace:: `Isaac-Velocity-Flat-Unitree-Go1-v0 `__ +.. |velocity-rough-unitree-go1-link| replace:: `Isaac-Velocity-Rough-Unitree-Go1-v0 `__ -.. |velocity-flat-unitree-go2-link| replace:: `Isaac-Velocity-Flat-Unitree-Go2-v0 `__ -.. |velocity-rough-unitree-go2-link| replace:: `Isaac-Velocity-Rough-Unitree-Go2-v0 `__ +.. |velocity-flat-unitree-go2-link| replace:: `Isaac-Velocity-Flat-Unitree-Go2-v0 `__ +.. |velocity-rough-unitree-go2-link| replace:: `Isaac-Velocity-Rough-Unitree-Go2-v0 `__ + +.. |velocity-flat-h1-link| replace:: `Isaac-Velocity-Flat-H1-v0 `__ +.. |velocity-rough-h1-link| replace:: `Isaac-Velocity-Rough-H1-v0 `__ .. |velocity-flat-anymal-b| image:: ../_static/tasks/locomotion/anymal_b_flat.jpg @@ -150,3 +182,39 @@ Environments based on legged locomotion tasks. .. |velocity-rough-unitree-go1| image:: ../_static/tasks/locomotion/go1_rough.jpg .. |velocity-flat-unitree-go2| image:: ../_static/tasks/locomotion/go2_flat.jpg .. |velocity-rough-unitree-go2| image:: ../_static/tasks/locomotion/go2_rough.jpg +.. |velocity-flat-h1| image:: ../_static/tasks/locomotion/h1_flat.jpg +.. |velocity-rough-h1| image:: ../_static/tasks/locomotion/h1_rough.jpg + +Navigation +---------- + +.. table:: + :widths: 33 37 30 + + +----------------+---------------------+-----------------------------------------------------------------------------+ + | World | Environment ID | Description | + +================+=====================+=============================================================================+ + | |anymal_c_nav| | |anymal_c_nav-link| | Navigate towards a target x-y position and heading with the ANYmal C robot. | + +----------------+---------------------+-----------------------------------------------------------------------------+ + +.. |anymal_c_nav-link| replace:: `Isaac-Navigation-Flat-Anymal-C-v0 `__ + +.. |anymal_c_nav| image:: ../_static/tasks/navigation/anymal_c_nav.jpg + + +Others +------ + +.. table:: + :widths: 33 37 30 + + +----------------+---------------------+-----------------------------------------------------------------------------+ + | World | Environment ID | Description | + +================+=====================+=============================================================================+ + | |quadcopter| | |quadcopter-link| | Fly and hover the Crazyflie copter at a goal point by applying thrust. | + +----------------+---------------------+-----------------------------------------------------------------------------+ + +.. |quadcopter-link| replace:: `Isaac-Quadcopter-Direct-v0 `__ + + +.. |quadcopter| image:: ../_static/tasks/others/quadcopter.jpg diff --git a/docs/source/features/multi_gpu.rst b/docs/source/features/multi_gpu.rst new file mode 100644 index 0000000000..33d3409286 --- /dev/null +++ b/docs/source/features/multi_gpu.rst @@ -0,0 +1,58 @@ +Multi-GPU and Multi-Node Training +================================= + +.. currentmodule:: omni.isaac.lab + +Isaac Lab supports multi-GPU and multi-node reinforcement learning on Linux. + + +Multi-GPU Training +------------------ + +For complex reinforcement learning environments, it may be desirable to scale up training across multiple GPUs. +This is possible in Isaac Lab with the ``rl_games`` RL library through the use of the +`PyTorch distributed `_ framework. +In this workflow, ``torch.distributed`` is used to launch multiple processes of training, where the number of +processes must be equal to or less than the number of GPUs available. Each process runs on +a dedicated GPU and launches its own instance of Isaac Sim and the Isaac Lab environment. +Each process collects its own rollouts during the training process and has its own copy of the policy +network. During training, gradients are aggregated across the processes and broadcasted back to the process +at the end of the epoch. + +.. image:: ../_static/multigpu.png + :align: center + :alt: Multi-GPU training paradigm + + +To train with multiple GPUs, use the following command, where ``--proc_per_node`` represents the number of available GPUs: + +.. code-block:: shell + + python -m torch.distributed.run --nnodes=1 --nproc_per_node=2 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed + + +Due to limitations of NCCL on Windows, this feature is currently supported on Linux only. + + +Multi-Node Training +------------------- + +To scale up training beyond multiple GPUs on a single machine, it is also possible to train across multiple nodes. +To train across multiple nodes/machines, it is required to launch an individual process on each node. +For the master node, use the following command, where ``--proc_per_node`` represents the number of available GPUs, and ``--nnodes`` represents the number of nodes: + +.. code-block:: shell + + python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=0 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=localhost:5555 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed + +Note that the port (``5555``) can be replaced with any other available port. + +For non-master nodes, use the following command, replacing ``--node_rank`` with the index of each machine: + +.. code-block:: shell + + python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=1 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=ip_of_master_machine:5555 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed + +For more details on multi-node training with PyTorch, please visit the `PyTorch documentation `_. As mentioned in the PyTorch documentation, "multinode training is bottlenecked by inter-node communication latencies". When this latency is high, it is possible multi-node training will perform worse than running on a single node instance. + +Due to limitations of NCCL on Windows, this feature is currently supported on Linux only. diff --git a/docs/source/features/tiled_rendering.rst b/docs/source/features/tiled_rendering.rst new file mode 100644 index 0000000000..9ef6962b75 --- /dev/null +++ b/docs/source/features/tiled_rendering.rst @@ -0,0 +1,80 @@ +Tiled Rendering and Recording +============================= + +.. currentmodule:: omni.isaac.lab + + +Tiled Rendering +--------------- + +.. note:: + + This feature is only available from Isaac Sim version 4.0.0. + +Tiled rendering APIs provide a vectorized interface for collecting data from camera sensors. +This is useful for reinforcement learning environments requiring vision in the loop. +Tiled rendering works by concatenating camera outputs from multiple cameras and rendering +one single large image instead of multiple smaller images that would have been produced +by each individual camera. This reduces the amount of time required for rendering and +provides a more efficient API for working with vision data. + +Isaac Lab provides tiled rendering APIs for RGB and depth data through the :class:`~sensors.TiledCamera` +class. Configurations for the tiled rendering APIs can be defined through the :class:`~sensors.TiledCameraCfg` +class, specifying parameters such as the regex expression for all camera paths, the transform +for the cameras, the desired data type, the type of cameras to add to the scene, and the camera +resolution. + +.. code-block:: python + + tiled_camera: TiledCameraCfg = TiledCameraCfg( + prim_path="/World/envs/env_.*/Camera", + offset=TiledCameraCfg.OffsetCfg(pos=(-7.0, 0.0, 3.0), rot=(0.9945, 0.0, 0.1045, 0.0), convention="world"), + data_types=["rgb"], + spawn=sim_utils.PinholeCameraCfg( + focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 20.0) + ), + width=80, + height=80, + ) + +To access the tiled rendering interface, a :class:`~sensors.TiledCamera` object can be created and used +to retrieve data from the cameras. + +.. code-block:: python + + tiled_camera = TiledCamera(cfg.tiled_camera) + data_type = "rgb" + data = tiled_camera.data.output[data_type] + +The returned data will be transformed into the shape (num_cameras, height, width, num_channels), which +can be used directly as observation for reinforcement learning. + +When working with rendering, make sure to add the ``--enable_cameras`` argument when launching the +environment. For example: + +.. code-block:: shell + + python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-RGB-Camera-Direct-v0 --headless --enable_cameras + + +Recording during training +------------------------- + +Isaac Lab supports recording video clips during training using the `gymnasium.wrappers.RecordVideo `_ class. + +This feature can be enabled by using the following command line arguments with the training script: + +* ``--video`` - enables video recording during training +* ``--video_length`` - length of each recorded video (in steps) +* ``--video_interval`` - interval between each video recording (in steps) + +Make sure to also add the ``--enable_cameras`` argument when running headless. +Note that enabling recording is equivalent to enabling rendering during training, which will slow down both startup and runtime performance. + +Example usage: + +.. code-block:: shell + + python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --enable_cameras --video --video_length 100 --video_interval 500 + +Recorded videos will be saved in the same directory as the training checkpoints, under ``IsaacLab/logs////videos``. diff --git a/docs/source/features/workflows.rst b/docs/source/features/workflows.rst new file mode 100644 index 0000000000..60d608a787 --- /dev/null +++ b/docs/source/features/workflows.rst @@ -0,0 +1,238 @@ +.. _feature-workflows: + + +Task Design Workflows +===================== + +.. currentmodule:: omni.isaac.lab + +Reinforcement learning environments can be implemented using two different workflows: Manager-based and Direct. +This page outlines the two workflows, explaining their benefits and usecases. + +In addition, multi-GPU and multi-node reinforcement learning support is explained, along with the tiled rendering API, +which can be used for efficient vectorized rendering across environments. + + +Manager-Based Environments +-------------------------- + +Manager-based environments promote modular implementations of reinforcement learning tasks +through the use of Managers. Each component of the task, such as rewards, observations, termination +can all be specified as individual configuration classes that are then passed to the corresponding +manager classes. Each manager is responsible for parsing the configurations and processing +the contents specified in each config class. The manager implementations are taken care of by +the base class :class:`envs.ManagerBasedRLEnv`. + +With this approach, it is simple to switch implementations of some components in the task +while leaving the remaining of the code intact. This is desirable when collaborating with others +on implementing a reinforcement learning environment, where contributors may choose to use +different combinations of configurations for the reinforcement learning components of the task. + +A class definition of a manager-based environment consists of defining a task configuration class that +inherits from :class:`envs.ManagerBasedRLEnvCfg`. This class should contain variables assigned to various +configuration classes for each of the components of the RL task, such as the ``ObservationCfg`` +or ``RewardCfg``. The entry point of the environment becomes the base class :class:`envs.ManagerBasedRLEnv`, +which will process the main task config and iterate through the individual configuration classes that are defined +in the task config class. + +An example of implementing the reward function for the Cartpole task using the manager-based implementation is as follow: + +.. code-block:: python + + @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"])}, + ) + +.. seealso:: + + We provide a more detailed tutorial for setting up a RL environment using the manager-based workflow at + `Creating a manager-based RL Environment <../tutorials/03_envs/create_rl_env.html>`_. + + +Direct Environments +------------------- + +The direct-style environment more closely aligns with traditional implementations of reinforcement learning environments, +where a single script implements the reward function, observation function, resets, and all other components +of the environment. This approach does not use the Manager classes. Instead, users are left with the freedom +to implement the APIs from the base class :class:`envs.DirectRLEnv`. For users migrating from the IsaacGymEnvs +or OmniIsaacGymEnvs framework, this workflow will have a closer implementation to the previous frameworks. + +When defining an environment following the direct-style implementation, a task configuration class inheriting from +:class:`envs.DirectRLEnvCfg` is used for defining task environment configuration variables, such as the number +of observations and actions. Adding configuration classes for the managers are not required and will not be processed +by the base class. In addition to the configuration class, the logic of the task should be defined in a new +task class that inherits from the base class :class:`envs.DirectRLEnv`. This class will then implement the main +task logics, including setting up the scene, processing the actions, computing resets, rewards, and observations. + +This approach may bring more performance benefits for the environment, as it allows implementing large chunks +of logic with optimized frameworks such as `PyTorch Jit `_ or +`Warp `_. This may be important when scaling up training for large and complex +environments. Additionally, data may be cached in class variables and reused in multiple APIs for the class. +This method provides more transparency in the implementations of the environments, as logic is defined +within the task class instead of abstracted with the use the Managers. + +An example of implementing the reward function for the Cartpole task using the Direct-style implementation is as follow: + +.. code-block:: python + + def _get_rewards(self) -> torch.Tensor: + total_reward = compute_rewards( + self.cfg.rew_scale_alive, + self.cfg.rew_scale_terminated, + self.cfg.rew_scale_pole_pos, + self.cfg.rew_scale_cart_vel, + self.cfg.rew_scale_pole_vel, + self.joint_pos[:, self._pole_dof_idx[0]], + self.joint_vel[:, self._pole_dof_idx[0]], + self.joint_pos[:, self._cart_dof_idx[0]], + self.joint_vel[:, self._cart_dof_idx[0]], + self.reset_terminated, + ) + return total_reward + + @torch.jit.script + def compute_rewards( + rew_scale_alive: float, + rew_scale_terminated: float, + rew_scale_pole_pos: float, + rew_scale_cart_vel: float, + rew_scale_pole_vel: float, + pole_pos: torch.Tensor, + pole_vel: torch.Tensor, + cart_pos: torch.Tensor, + cart_vel: torch.Tensor, + reset_terminated: torch.Tensor, + ): + rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) + rew_termination = rew_scale_terminated * reset_terminated.float() + rew_pole_pos = rew_scale_pole_pos * torch.sum(torch.square(pole_pos), dim=-1) + rew_cart_vel = rew_scale_cart_vel * torch.sum(torch.abs(cart_vel), dim=-1) + rew_pole_vel = rew_scale_pole_vel * torch.sum(torch.abs(pole_vel), dim=-1) + total_reward = rew_alive + rew_termination + rew_pole_pos + rew_cart_vel + rew_pole_vel + return total_reward + +.. seealso:: + + We provide a more detailed tutorial for setting up a RL environment using the direct workflow at + `Creating a Direct Workflow RL Environment <../tutorials/03_envs/create_direct_rl_env.html>`_. + + +Multi-GPU Training +------------------ + +For complex reinforcement learning environments, it may be desirable to scale up training across multiple GPUs. +This is possible in Isaac Lab with the ``rl_games`` RL library through the use of the +`PyTorch distributed `_ framework. +In this workflow, ``torch.distributed`` is used to launch multiple processes of training, where the number of +processes must be equal to or less than the number of GPUs available. Each process runs on +a dedicated GPU and launches its own instance of Isaac Sim and the Isaac Lab environment. +Each process collects its own rollouts during the training process and has its own copy of the policy +network. During training, gradients are aggregated across the processes and broadcasted back to the process +at the end of the epoch. + +.. image:: ../_static/multigpu.png + :align: center + :alt: Multi-GPU training paradigm + + +To train with multiple GPUs, use the following command, where ``--proc_per_node`` represents the number of available GPUs: + +.. code-block:: shell + + python -m torch.distributed.run --nnodes=1 --nproc_per_node=2 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed + + + +Multi-Node Training +------------------- + +To scale up training beyond multiple GPUs on a single machine, it is also possible to train across multiple nodes. +To train across multiple nodes/machines, it is required to launch an individual process on each node. +For the master node, use the following command, where ``--proc_per_node`` represents the number of available GPUs, and ``--nnodes`` represents the number of nodes: + +.. code-block:: shell + + python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=0 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=localhost:5555 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed + +Note that the port (``5555``) can be replaced with any other available port. + +For non-master nodes, use the following command, replacing ``--node_rank`` with the index of each machine: + +.. code-block:: shell + + python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=1 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=ip_of_master_machine:5555 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed + +For more details on multi-node training with PyTorch, please visit the `PyTorch documentation `_. As mentioned in the PyTorch documentation, "multinode training is bottlenecked by inter-node communication latencies". When this latency is high, it is possible multi-node training will perform worse than running on a single node instance. + + +Tiled Rendering +--------------- + +Tiled rendering APIs provide a vectorized interface for collecting data from camera sensors. +This is useful for reinforcement learning environments requiring vision in the loop. +Tiled rendering works by concatenating camera outputs from multiple cameras and rending +one single large image instead of multiple smaller images that would have been produced +by each individual camera. This reduces the amount of time required for rendering and +provides a more efficient API for working with vision data. + +Isaac Lab provides tiled rendering APIs for RGB and depth data through the :class:`~sensors.TiledCamera` +class. Configurations for the tiled rendering APIs can be defined through the :class:`~sensors.TiledCameraCfg` +class, specifying parameters such as the regex expression for all camera paths, the transform +for the cameras, the desired data type, the type of cameras to add to the scene, and the camera +resolution. + +.. code-block:: python + + tiled_camera: TiledCameraCfg = TiledCameraCfg( + prim_path="/World/envs/env_.*/Camera", + offset=TiledCameraCfg.OffsetCfg(pos=(-7.0, 0.0, 3.0), rot=(0.9945, 0.0, 0.1045, 0.0), convention="world"), + data_types=["rgb"], + spawn=sim_utils.PinholeCameraCfg( + focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 20.0) + ), + width=80, + height=80, + ) + +To access the tiled rendering interface, a :class:`~sensors.TiledCamera` object can be created and used +to retrieve data from the cameras. + +.. code-block:: python + + tiled_camera = TiledCamera(cfg.tiled_camera) + data_type = "rgb" + data = tiled_camera.data.output[data_type] + +The returned data will be transformed into the shape (num_cameras, height, width, num_channels), which +can be used directly as observation for reinforcement learning. + +When working with rendering, make sure to add the ``--enable_cameras`` argument when launching the +environment. For example: + +.. code-block:: shell + + python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-RGB-Camera-Direct-v0 --headless --enable_cameras diff --git a/docs/source/how-to/add_own_library.rst b/docs/source/how-to/add_own_library.rst index 3aa145376d..144ad2c074 100644 --- a/docs/source/how-to/add_own_library.rst +++ b/docs/source/how-to/add_own_library.rst @@ -1,7 +1,7 @@ Adding your own learning library ================================ -Isaac Lab comes pre-integrated with a number of libraries (such as RSL-RL , RL-Games, Stable Baselines, etc.). +Isaac Lab comes pre-integrated with a number of libraries (such as RSL-RL, RL-Games, SKRL, Stable Baselines, etc.). However, you may want to integrate your own library with Isaac Lab or use a different version of the libraries than the one installed by Isaac Lab. This is possible as long as the library is available as Python package that supports the Python version used by the underlying simulator. For instance, if you are using Isaac Sim 2023.1.1, you need diff --git a/docs/source/how-to/save_camera_output.rst b/docs/source/how-to/save_camera_output.rst index f5753f2236..94a834492a 100644 --- a/docs/source/how-to/save_camera_output.rst +++ b/docs/source/how-to/save_camera_output.rst @@ -92,7 +92,7 @@ To run the accompanying script, execute the following command: ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py --save --draw # Usage with saving only in headless mode - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py --save --headless --offscreen_render + ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py --save --headless --enable_cameras The simulation should start, and you can observe different objects falling down. An output folder will be created diff --git a/docs/source/how-to/wrap_rl_env.rst b/docs/source/how-to/wrap_rl_env.rst index 5f56af9b1b..fda1263225 100644 --- a/docs/source/how-to/wrap_rl_env.rst +++ b/docs/source/how-to/wrap_rl_env.rst @@ -10,7 +10,7 @@ Environment wrappers are a way to modify the behavior of an environment without This can be used to apply functions to modify observations or rewards, record videos, enforce time limits, etc. A detailed description of the API is available in the :class:`gymnasium.Wrapper` class. -At present, all RL environments inheriting from the :class:`~envs.RLTaskEnv` class +At present, all RL environments inheriting from the :class:`~envs.ManagerBasedRLEnv` class are compatible with :class:`gymnasium.Wrapper`, since the base class implements the :class:`gymnasium.Env` interface. In order to wrap an environment, you need to first initialize the base environment. After that, you can wrap it with as many wrappers as you want by calling ``env = wrapper(env, *args, **kwargs)`` repeatedly. @@ -97,7 +97,7 @@ for 200 steps, and saves it in the ``videos`` folder at a step interval of 1500 from omni.isaac.lab.app import AppLauncher # launch omniverse app in headless mode with off-screen rendering - app_launcher = AppLauncher(headless=True, offscreen_render=True) + app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app """Rest everything follows.""" @@ -125,9 +125,9 @@ Wrapper for learning frameworks Every learning framework has its own API for interacting with environments. For example, the `Stable-Baselines3`_ library uses the `gym.Env `_ -interface to interact with environments. However, libraries like `RL-Games`_ or `RSL-RL`_ +interface to interact with environments. However, libraries like `RL-Games`_, `RSL-RL`_ or `SKRL`_ use their own API for interfacing with a learning environments. Since there is no one-size-fits-all -solution, we do not base the :class:`~envs.RLTaskEnv` class on any particular learning framework's +solution, we do not base the :class:`~envs.ManagerBasedRLEnv` class on any particular learning framework's environment definition. Instead, we implement wrappers to make it compatible with the learning framework's environment definition. @@ -154,12 +154,13 @@ Adding new wrappers ------------------- All new wrappers should be added to the :mod:`omni.isaac.lab_tasks.utils.wrappers` module. -They should check that the underlying environment is an instance of :class:`omni.isaac.lab.envs.RLTaskEnv` +They should check that the underlying environment is an instance of :class:`omni.isaac.lab.envs.ManagerBasedRLEnv` before applying the wrapper. This can be done by using the :func:`unwrapped` property. We include a set of wrappers in this module that can be used as a reference to implement your own wrappers. If you implement a new wrapper, please consider contributing it to the framework by opening a pull request. .. _Stable-Baselines3: https://stable-baselines3.readthedocs.io/en/master/ +.. _SKRL: https://skrl.readthedocs.io .. _RL-Games: https://github.com/Denys88/rl_games .. _RSL-RL: https://github.com/leggedrobotics/rsl_rl diff --git a/docs/source/migration/index.rst b/docs/source/migration/index.rst new file mode 100644 index 0000000000..789d68da73 --- /dev/null +++ b/docs/source/migration/index.rst @@ -0,0 +1,14 @@ +Migration Guides +================ + +The following guides show the migration process from previous frameworks that are now deprecated, +including IsaacGymEnvs, OmniIsaacGymEnvs, and Orbit. + + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + migrating_from_isaacgymenvs + migrating_from_omniisaacgymenvs + migrating_from_orbit diff --git a/docs/source/migration/migrating_from_isaacgymenvs.rst b/docs/source/migration/migrating_from_isaacgymenvs.rst new file mode 100644 index 0000000000..e30f26a471 --- /dev/null +++ b/docs/source/migration/migrating_from_isaacgymenvs.rst @@ -0,0 +1,908 @@ +.. _migrating-from-isaacgymenvs: + +Migrating from IsaacGymEnvs and Isaac Gym Preview Release +========================================================= + +.. currentmodule:: omni.isaac.lab + + +IsaacGymEnvs was a reinforcement learning framework designed for the Isaac Gym Preview Release. +As both IsaacGymEnvs and the Isaac Gym Preview Release are now deprecated, the following guide walks through the key differences +between IsaacGymEnvs and Isaac Lab, as well as differences in APIs between Isaac Gym Preview Release +and Isaac Sim. + + +Task Config Setup +~~~~~~~~~~~~~~~~~ + +In IsaacGymEnvs, task config files were defined in ``.yaml`` format. With Isaac Lab, configs are now specified using a specialized +Python class :class:`~omni.isaac.lab.utils.configclass`. The :class:`~omni.isaac.lab.utils.configclass` module provides a wrapper on top of Python's ``dataclasses`` module. +Each environment should specify its own config class annotated by ``@configclass`` that inherits from :class:`~envs.DirectRLEnvCfg`, +which can include simulation parameters, environment scene parameters, robot parameters, and task-specific parameters. + +Below is an example skeleton of a task config class: + +.. code-block:: python + + from omni.isaac.lab.envs import DirectRLEnvCfg + from omni.isaac.lab.scene import InteractiveSceneCfg + from omni.isaac.lab.sim import SimulationCfg + + @configclass + class MyEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg() + # robot + robot_cfg: ArticulationCfg = ArticulationCfg() + # scene + scene: InteractiveSceneCfg = InteractiveSceneCfg() + # env + decimation = 2 + episode_length_s = 5.0 + num_actions = 1 + num_observations = 4 + num_states = 0 + # task-specific parameters + ... + +Simulation Config +----------------- + +Simulation related parameters are defined as part of the :class:`~omni.isaac.lab.sim.SimulationCfg` class, which is a :class:`~omni.isaac.lab.utils.configclass` module +that holds simulation parameters such as ``dt``, ``device``, and ``gravity``. +Each task config must have a variable named ``sim`` defined that holds the type :class:`~omni.isaac.lab.sim.SimulationCfg`. + +In Isaac Lab, the use of ``substeps`` has been replaced +by a combination of the simulation ``dt`` and the ``decimation`` parameters. For example, in IsaacGymEnvs, having ``dt=1/60`` and ``substeps=2`` +is equivalent to taking 2 simulation steps with ``dt=1/120``, but running the task step at ``1/60`` seconds. +The ``decimation`` parameter is a task parameter that controls the number of simulation steps to take for each task (or RL) step, replacing the ``controlFrequencyInv`` parameter in IsaacGymEnvs. +Thus, the same setup in Isaac Lab will become ``dt=1/120`` and ``decimation=2``. + +In Isaac Sim, physx simulation parameters such as ``num_position_iterations``, ``num_velocity_iterations``, +``contact_offset``, ``rest_offset``, ``bounce_threshold_velocity``, ``max_depenetration_velocity`` can all +be specified on a per-actor basis. These parameters have been moved from the physx simulation config +to each individual articulation and rigid body config. + +When running simulation on the GPU, buffers in PhysX require pre-allocation for computing and storing +information such as contacts, collisions and aggregate pairs. These buffers may need to be adjusted +depending on the complexity of the environment, the number of expected contacts and collisions, +and the number of actors in the environment. The :class:`~omni.isaac.lab.sim.PhysxCfg` class provides access for setting the GPU buffer dimensions. + ++--------------------------------------------------------------+-------------------------------------------------------------------+ +| | | +|.. code-block:: yaml |.. code-block:: python | +| | | +| # IsaacGymEnvs | # IsaacLab | +| sim: | sim: SimulationCfg = SimulationCfg( | +| dt: 0.0166 # 1/60 s | dt=1 / 120, | +| substeps: 2 | # decimation will be set in the task config | +| up_axis: "z" | # up axis will always be Z in isaac sim | +| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | use_gpu_pipeline=True, | +| gravity: [0.0, 0.0, -9.81] | gravity=(0.0, 0.0, -9.81), | +| physx: | physx: PhysxCfg = PhysxCfg( | +| num_threads: ${....num_threads} | # num_threads is no longer needed | +| solver_type: ${....solver_type} | solver_type=1, | +| use_gpu: ${contains:"cuda",${....sim_device}} | use_gpu=True, | +| num_position_iterations: 4 | max_position_iteration_count=4, | +| num_velocity_iterations: 0 | max_velocity_iteration_count=0, | +| contact_offset: 0.02 | # moved to actor config | +| rest_offset: 0.001 | # moved to actor config | +| bounce_threshold_velocity: 0.2 | bounce_threshold_velocity=0.2, | +| max_depenetration_velocity: 100.0 | # moved to actor config | +| default_buffer_size_multiplier: 2.0 | # default_buffer_size_multiplier is no longer needed | +| max_gpu_contact_pairs: 1048576 # 1024*1024 | gpu_max_rigid_contact_count=2**23 | +| num_subscenes: ${....num_subscenes} | # num_subscenes is no longer needed | +| contact_collection: 0 | # contact_collection is no longer needed | +| | )) | ++--------------------------------------------------------------+-------------------------------------------------------------------+ + +Scene Config +------------ + +The :class:`~omni.isaac.lab.scene.InteractiveSceneCfg` class can be used to specify parameters related to the scene, such as the number of environments +and the spacing between environments. +Each task config must have a variable named ``scene`` defined that holds the type :class:`~omni.isaac.lab.scene.InteractiveSceneCfg`. + ++--------------------------------------------------------------+-------------------------------------------------------------------+ +| | | +|.. code-block:: yaml |.. code-block:: python | +| | | +| # IsaacGymEnvs | # IsaacLab | +| env: | scene: InteractiveSceneCfg = InteractiveSceneCfg( | +| numEnvs: ${resolve_default:512,${...num_envs}} | num_envs=512, | +| envSpacing: 4.0 | env_spacing=4.0) | ++--------------------------------------------------------------+-------------------------------------------------------------------+ + +Task Config +----------- + +Each environment should specify its own config class that holds task specific parameters, such as the dimensions of the +observation and action buffers. Reward term scaling parameters can also be specified in the config class. + +The following parameters must be set for each environment config: + +.. code-block:: python + + decimation = 2 + episode_length_s = 5.0 + num_actions = 1 + num_observations = 4 + num_states = 0 + +Note that the maximum episode length parameter (now ``episode_length_s``) is in seconds instead of steps as it was in IsaacGymEnvs. To convert between +step count to seconds, use the equation: ``episode_length_s = dt * decimation * num_steps`` + + +RL Config Setup +~~~~~~~~~~~~~~~ + +RL config files for the rl_games library can continue to be defined in ``.yaml`` files in Isaac Lab. +Most of the content of the config file can be copied directly from IsaacGymEnvs. +Note that in Isaac Lab, we do not use hydra to resolve relative paths in config files. +Please replace any relative paths such as ``${....device}`` with the actual values of the parameters. + +Additionally, the observation and action clip ranges have been moved to the RL config file. +For any ``clipObservations`` and ``clipActions`` parameters that were defined in the IsaacGymEnvs task config file, +they should be moved to the RL config file in Isaac Lab. + ++--------------------------+----------------------------+ +| | | +| IsaacGymEnvs Task Config | Isaac Lab RL Config | ++--------------------------+----------------------------+ +|.. code-block:: yaml |.. code-block:: yaml | +| | | +| # IsaacGymEnvs | # IsaacLab | +| env: | params: | +| clipObservations: 5.0 | env: | +| clipActions: 1.0 | clip_observations: 5.0 | +| | clip_actions: 1.0 | ++--------------------------+----------------------------+ + +Environment Creation +~~~~~~~~~~~~~~~~~~~~ + +In IsaacGymEnvs, environment creation generally included four components: creating the sim object with ``create_sim()``, +creating the ground plane, importing the assets from MJCF or URDF files, and finally creating the environments +by looping through each environment and adding actors into the environments. + +Isaac Lab no longer requires calling the ``create_sim()`` method to retrieve the sim object. Instead, the simulation +context is retrieved automatically by the framework. It is also no longer required to use the ``sim`` as an +argument for the simulation APIs. + +In replacement of ``create_sim()``, tasks can implement the ``_setup_scene()`` method in Isaac Lab. +This method can be used for adding actors into the scene, adding ground plane, cloning the actors, and +adding any other optional objects into the scene, such as lights. + ++------------------------------------------------------------------------------+------------------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++------------------------------------------------------------------------------+------------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def create_sim(self): | def _setup_scene(self): | +| # set the up axis to be z-up | self.cartpole = Articulation(self.cfg.robot_cfg) | +| self.up_axis = self.cfg["sim"]["up_axis"] | # add ground plane | +| | spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg() | +| self.sim = super().create_sim(self.device_id, self.graphics_device_id, | # clone, filter, and replicate | +| self.physics_engine, self.sim_params) | self.scene.clone_environments(copy_from_source=False) | +| self._create_ground_plane() | self.scene.filter_collisions(global_prim_paths=[]) | +| self._create_envs(self.num_envs, self.cfg["env"]['envSpacing'], | # add articultion to scene | +| int(np.sqrt(self.num_envs))) | self.scene.articulations["cartpole"] = self.cartpole | +| | # add lights | +| | light_cfg = sim_utils.DomeLightCfg(intensity=2000.0) | +| | light_cfg.func("/World/Light", light_cfg) | ++------------------------------------------------------------------------------+------------------------------------------------------------------------+ + + +Ground Plane +------------ + +In Isaac Lab, most of the environment creation process has been simplified into configs with the :class:`~omni.isaac.lab.utils.configclass` module. + +The ground plane can be defined using the :class:`~terrains.TerrainImporterCfg` class. + +.. code-block:: python + + from omni.isaac.lab.terrains import TerrainImporterCfg + + terrain = TerrainImporterCfg( + 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, + restitution=0.0, + ), + ) + +The terrain can then be added to the scene in ``_setup_scene(self)`` by referencing the ``TerrainImporterCfg`` object: + +.. code-block::python + + def _setup_scene(self): + ... + self.cfg.terrain.num_envs = self.scene.cfg.num_envs + self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing + self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) + + +Actors +------ + +Isaac Lab and Isaac Sim both use the `USD (Universal Scene Description) `_ library for describing the scene. Assets defined in MJCF and URDF formats can be imported to USD using importer tools described in the `Importing a New Asset <../../how-to/import_new_asset.rst>`_ tutorial. + +Each Articulation and Rigid Body actor can also have its own config class. The :class:`~omni.isaac.lab.assets.ArticulationCfg` can be +used to define parameters for articulation actors, including file path, simulation parameters, actuator properties, and initial states. + +.. code-block::python + + from omni.isaac.lab.actuators import ImplicitActuatorCfg + from omni.isaac.lab.assets import ArticulationCfg + + CARTPOLE_CFG = ArticulationCfg( + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Classic/Cartpole/cartpole.usd", + rigid_props=sim_utils.RigidBodyPropertiesCfg( + rigid_body_enabled=True, + max_linear_velocity=1000.0, + max_angular_velocity=1000.0, + max_depenetration_velocity=100.0, + enable_gyroscopic_forces=True, + ), + articulation_props=sim_utils.ArticulationRootPropertiesCfg( + enabled_self_collisions=False, + solver_position_iteration_count=4, + solver_velocity_iteration_count=0, + sleep_threshold=0.005, + stabilization_threshold=0.001, + ), + ), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 2.0), joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} + ), + actuators={ + "cart_actuator": ImplicitActuatorCfg( + joint_names_expr=["slider_to_cart"], + effort_limit=400.0, + velocity_limit=100.0, + stiffness=0.0, + damping=10.0, + ), + "pole_actuator": ImplicitActuatorCfg( + joint_names_expr=["cart_to_pole"], effort_limit=400.0, velocity_limit=100.0, stiffness=0.0, damping=0.0 + ), + }, + ) + +Within the :class:`~assets.ArticulationCfg`, the ``spawn`` attribute can be used to add the robot to the scene by specifying the path to the robot file. +In addition, :class:`~omni.isaac.lab.sim.schemas.RigidBodyPropertiesCfg` can be used to specify simulation properties +for the rigid bodies in the articulation. +Similarly, :class:`~omni.isaac.lab.sim.schemas.ArticulationRootPropertiesCfg` can be used to specify simulation properties for the articulation. +Joint and dof properties are now specified as part of the ``actuators`` dictionary using :class:`~actuators.ImplicitActuatorCfg`. +Joints and dofs with the same properties can be grouped into regex expressions or provided as a list of names or expressions. + +Actors are added to the scene by simply calling ``self.cartpole = Articulation(self.cfg.robot_cfg)``, +where ``self.cfg.robot_cfg`` is an :class:`~assets.ArticulationCfg` object. Once initialized, they should also be added +to the :class:`~scene.InteractiveScene` by calling ``self.scene.articulations["cartpole"] = self.cartpole`` so that +the :class:`~scene.InteractiveScene` can traverse through actors in the scene for writing values to the simulation and resetting. + +Simulation Parameters for Actors +"""""""""""""""""""""""""""""""" + +Some simulation parameters related to Rigid Bodies and Articulations may have different +default values between Isaac Gym Preview Release and Isaac Sim. +It may be helpful to double check the USD assets to ensure that the default values are +applicable for the asset. + +For instance, the following parameters in the ``RigidBodyAPI`` could be different +between Isaac Gym Preview Release and Isaac Sim: + +.. list-table:: + :widths: 50 50 50 + :header-rows: 1 + + * - RigidBodyAPI Parameter + - Default Value in Isaac Sim + - Default Value in Isaac Gym Preview Release + * - Linear Damping + - 0.00 + - 0.00 + * - Angular Damping + - 0.05 + - 0.0 + * - Max Linear Velocity + - inf + - 1000 + * - Max Angular Velocity + - 5729.58008 (degree/s) + - 64.0 (rad/s) + * - Max Contact Impulse + - inf + - 1e32 + +Articulation parameters for the ``JointAPI`` and ``DriveAPI`` could be altered as well. Note +that the Isaac Sim UI assumes the unit of angle to be degrees. It is particularly +worth noting that the ``Damping`` and ``Stiffness`` parameters in the ``DriveAPI`` have the unit +of ``1/deg`` in the Isaac Sim UI but ``1/rad`` in Isaac Gym Preview Release. + +.. list-table:: + :widths: 50 50 50 + :header-rows: 1 + + * - Joint Parameter + - Default Value in Isaac Sim + - Default Value in Isaac Gym Preview Releases + * - Maximum Joint Velocity + - 1000000.0 (deg) + - 100.0 (rad) + + +Cloner +------ + +Isaac Sim introduced a concept of ``Cloner``, which is a class designed for replication during the scene creation process. +In IsaacGymEnvs, scenes had to be created by looping through the number of environments. +Within each iteration, actors were added to each environment and their handles had to be cached. +Isaac Lab eliminates the need for looping through the environments by using the ``Cloner`` APIs. +The scene creation process is as follow: + +#. Construct a single environment (what the scene would look like if number of environments = 1) +#. Call ``clone_environments()`` to replicate the single environment +#. Call ``filter_collisions()`` to filter out collision between environments (if required) + + +.. code-block:: python + + # construct a single environment with the Cartpole robot + self.cartpole = Articulation(self.cfg.robot_cfg) + # clone the environment + self.scene.clone_environments(copy_from_source=False) + # filter collisions + self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) + + +Accessing States from Simulation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +APIs for accessing physics states in Isaac Lab require the creation of an :class:`~assets.Articulation` or :class:`~assets.RigidObject` +object. Multiple objects can be initialized for different articulations or rigid bodies in the scene by defining +corresponding :class:`~assets.ArticulationCfg` or :class:`~assets.RigidObjectCfg` config as outlined in the section above. +This approach eliminates the need of retrieving body handles to slice states for specific bodies in the scene. + + +.. code-block:: python + + self._robot = Articulation(self.cfg.robot) + self._cabinet = Articulation(self.cfg.cabinet) + self._object = RigidObject(self.cfg.object_cfg) + + +We have also removed ``acquire`` and ``refresh`` APIs in Isaac Lab. Physics states can be directly applied or retrieved +using APIs defined for the articulations and rigid objects. + +APIs provided in Isaac Lab no longer require explicit wrapping and un-wrapping of underlying buffers. +APIs can now work with tensors directly for reading and writing data. + ++------------------------------------------------------------------+-----------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++------------------------------------------------------------------+-----------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| dof_state_tensor = self.gym.acquire_dof_state_tensor(self.sim) | self.joint_pos = self._robot.data.joint_pos | +| self.dof_state = gymtorch.wrap_tensor(dof_state_tensor) | self.joint_vel = self._robot.data.joint_vel | +| self.gym.refresh_dof_state_tensor(self.sim) | | ++------------------------------------------------------------------+-----------------------------------------------------------------+ + +Note some naming differences between APIs in Isaac Gym Preview Release and Isaac Lab. Most ``dof`` related APIs have been +named to ``joint`` in Isaac Lab. +APIs in Isaac Lab also no longer follow the explicit ``_tensors`` or ``_tensor_indexed`` suffixes in naming. +Indexed versions of APIs now happen implicitly through the optional ``indices`` parameter. + +Most APIs in Isaac Lab also provide +the option to specify an ``indices`` parameter, which can be used when reading or writing data for a subset +of environments. Note that when setting states with the ``indices`` parameter, the shape of the states buffer +should match with the dimension of the ``indices`` list. + ++---------------------------------------------------------------------------+---------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++---------------------------------------------------------------------------+---------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| env_ids_int32 = env_ids.to(dtype=torch.int32) | self._robot.write_joint_state_to_sim(joint_pos, joint_vel, | +| self.gym.set_dof_state_tensor_indexed(self.sim, | joint_ids, env_ids) | +| gymtorch.unwrap_tensor(self.dof_state), | | +| gymtorch.unwrap_tensor(env_ids_int32), len(env_ids_int32)) | | ++---------------------------------------------------------------------------+---------------------------------------------------------------+ + +Quaternion Convention +--------------------- + +Isaac Lab and Isaac Sim both adopt ``wxyz`` as the quaternion convention. However, the quaternion +convention used in Isaac Gym Preview Release was ``xyzw``. +Remember to switch all quaternions to use the ``xyzw`` convention when working indexing rotation data. +Similarly, please ensure all quaternions are in ``wxyz`` before passing them to Isaac Lab APIs. + + +Articulation Joint Order +------------------------ + +Physics simulation in Isaac Sim and Isaac Lab assumes a breadth-first +ordering for the joints in a given kinematic tree. +However, Isaac Gym Preview Release assumed a depth-first ordering for joints in the kinematic tree. +This means that indexing joints based on their ordering may be different in IsaacGymEnvs and Isaac Lab. + +In Isaac Lab, the list of joint names can be retrieved with ``Articulation.data.joint_names``, which will +also correspond to the ordering of the joints in the Articulation. + + +Creating a New Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each environment in Isaac Lab should be in its own directory following this structure: + +.. code-block:: none + + my_environment/ + - agents/ + - __init__.py + - rl_games_ppo_cfg.py + - __init__.py + my_env.py + +* ``my_environment`` is the root directory of the task. +* ``my_environment/agents`` is the directory containing all RL config files for the task. Isaac Lab supports multiple RL libraries that can each have its own individual config file. +* ``my_environment/__init__.py`` is the main file that registers the environment with the Gymnasium interface. This allows the training and inferencing scripts to find the task by its name. The content of this file should be as follow: + +.. code-block:: python + + import gymnasium as gym + + from . import agents + from .cartpole_env import CartpoleEnv, CartpoleEnvCfg + + ## + # Register Gym environments. + ## + + gym.register( + id="Isaac-Cartpole-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct_workflow.cartpole:CartpoleEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": CartpoleEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml" + }, + ) + +* ``my_environment/my_env.py`` is the main python script that implements the task logic and task config class for the environment. + + +Task Logic +~~~~~~~~~~ + +In Isaac Lab, the ``post_physics_step`` function has been moved to the framework in the base class. +Tasks are not required to implement this method, but can choose to override it if a different workflow is desired. + +By default, Isaac Lab follows the following flow in logic: + ++----------------------------------+----------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++----------------------------------+----------------------------------+ +|.. code-block:: none |.. code-block:: none | +| | | +| pre_physics_step | pre_physics_step | +| |-- apply_action | |-- _pre_physics_step(action)| +| | |-- _apply_action() | +| | | +| post_physics_step | post_physics_step | +| |-- reset_idx() | |-- _get_dones() | +| |-- compute_observation() | |-- _get_rewards() | +| |-- compute_reward() | |-- _reset_idx() | +| | |-- _get_observations() | ++----------------------------------+----------------------------------+ + +In Isaac Lab, we also separate the ``pre_physics_step`` API for processing actions from the policy with +the ``apply_action`` API, which sets the actions into the simulation. This provides more flexibility in controlling +when actions should be written to simulation when ``decimation`` is used. +``pre_physics_step`` will be called once per step before stepping simulation. +``apply_actions`` will be called ``decimation`` number of times for each RL step, once before each simulation step call. + +With this approach, resets are performed based on actions from the current step instead of the previous step. +Observations will also be computed with the correct states after resets. + +We have also performed some renamings of APIs: + +* ``create_sim(self)`` --> ``_setup_scene(self)`` +* ``pre_physics_step(self, actions)`` --> ``_pre_physics_step(self, actions)`` and ``_apply_action(self)`` +* ``reset_idx(self, env_ids)`` --> ``_reset_idx(self, env_ids)`` +* ``compute_observations(self)`` --> ``_get_observations(self)`` - ``_get_observations()`` should now return a dictionary ``{"policy": obs}`` +* ``compute_reward(self)`` --> ``_get_rewards(self)`` - ``_get_rewards()`` should now return the reward buffer +* ``post_physics_step(self)`` --> moved to the base class +* In addition, Isaac Lab requires the implementation of ``_is_done(self)``, which should return two buffers: the ``reset`` buffer and the ``time_out`` buffer. + + +Putting It All Together +~~~~~~~~~~~~~~~~~~~~~~~ + +The Cartpole environment is shown here in completion to fully show the comparison between the IsaacGymEnvs implementation and the Isaac Lab implementation. + +Task Config +----------- + ++--------------------------------------------------------+---------------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++--------------------------------------------------------+---------------------------------------------------------------------+ +|.. code-block:: yaml |.. code-block:: python | +| | | +| # used to create the object | @configclass | +| name: Cartpole | class CartpoleEnvCfg(DirectRLEnvCfg): | +| | | +| physics_engine: ${..physics_engine} | # simulation | +| | sim: SimulationCfg = SimulationCfg(dt=1 / 120) | +| # if given, will override the device setting in gym. | # robot | +| env: | robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace( | +| numEnvs: ${resolve_default:512,${...num_envs}} | prim_path="/World/envs/env_.*/Robot") | +| envSpacing: 4.0 | cart_dof_name = "slider_to_cart" | +| resetDist: 3.0 | pole_dof_name = "cart_to_pole" | +| maxEffort: 400.0 | # scene | +| | scene: InteractiveSceneCfg = InteractiveSceneCfg( | +| clipObservations: 5.0 | num_envs=4096, env_spacing=4.0, replicate_physics=True) | +| clipActions: 1.0 | # env | +| | decimation = 2 | +| asset: | episode_length_s = 5.0 | +| assetRoot: "../../assets" | action_scale = 100.0 # [N] | +| assetFileName: "urdf/cartpole.urdf" | num_actions = 1 | +| | num_observations = 4 | +| enableCameraSensors: False | num_states = 0 | +| | # reset | +| sim: | max_cart_pos = 3.0 | +| dt: 0.0166 # 1/60 s | initial_pole_angle_range = [-0.25, 0.25] | +| substeps: 2 | # reward scales | +| up_axis: "z" | rew_scale_alive = 1.0 | +| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | rew_scale_terminated = -2.0 | +| gravity: [0.0, 0.0, -9.81] | rew_scale_pole_pos = -1.0 | +| physx: | rew_scale_cart_vel = -0.01 | +| num_threads: ${....num_threads} | rew_scale_pole_vel = -0.005 | +| solver_type: ${....solver_type} | | +| use_gpu: ${contains:"cuda",${....sim_device}} | | +| num_position_iterations: 4 | | +| num_velocity_iterations: 0 | | +| contact_offset: 0.02 | | +| rest_offset: 0.001 | | +| bounce_threshold_velocity: 0.2 | | +| max_depenetration_velocity: 100.0 | | +| default_buffer_size_multiplier: 2.0 | | +| max_gpu_contact_pairs: 1048576 # 1024*1024 | | +| num_subscenes: ${....num_subscenes} | | +| contact_collection: 0 | | ++--------------------------------------------------------+---------------------------------------------------------------------+ + + + +Task Setup +---------- + +Isaac Lab no longer requires pre-initialization of buffers through the ``acquire_*`` APIs that were used in IsaacGymEnvs. +It is also no longer necessary to ``wrap`` and ``unwrap`` tensors. + ++-------------------------------------------------------------------------+-------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++-------------------------------------------------------------------------+-------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| class Cartpole(VecTask): | class CartpoleEnv(DirectRLEnv): | +| | cfg: CartpoleEnvCfg | +| def __init__(self, cfg, rl_device, sim_device, graphics_device_id, | def __init__(self, cfg: CartpoleEnvCfg, | +| headless, virtual_screen_capture, force_render): | render_mode: str | None = None, **kwargs): | +| self.cfg = cfg | | +| | super().__init__(cfg, render_mode, **kwargs) | +| self.reset_dist = self.cfg["env"]["resetDist"] | | +| | self._cart_dof_idx, _ = self.cartpole.find_joints( | +| self.max_push_effort = self.cfg["env"]["maxEffort"] | self.cfg.cart_dof_name) | +| self.max_episode_length = 500 | self._pole_dof_idx, _ = self.cartpole.find_joints( | +| | self.cfg.pole_dof_name) | +| self.cfg["env"]["numObservations"] = 4 | self.action_scale = self.cfg.action_scale | +| self.cfg["env"]["numActions"] = 1 | | +| | self.joint_pos = self.cartpole.data.joint_pos | +| super().__init__(config=self.cfg, | self.joint_vel = self.cartpole.data.joint_vel | +| rl_device=rl_device, sim_device=sim_device, | | +| graphics_device_id=graphics_device_id, headless=headless, | | +| virtual_screen_capture=virtual_screen_capture, | | +| force_render=force_render) | | +| | | +| dof_state_tensor = self.gym.acquire_dof_state_tensor(self.sim) | | +| self.dof_state = gymtorch.wrap_tensor(dof_state_tensor) | | +| self.dof_pos = self.dof_state.view( | | +| self.num_envs, self.num_dof, 2)[..., 0] | | +| self.dof_vel = self.dof_state.view( | | +| self.num_envs, self.num_dof, 2)[..., 1] | | ++-------------------------------------------------------------------------+-------------------------------------------------------------+ + + + +Scene Setup +----------- + +Scene setup is now done through the ``Cloner`` API and by specifying actor attributes in config objects. +This eliminates the need to loop through the number of environments to set up the environments and avoids +the need to set simulation parameters for actors in the task implementation. + ++------------------------------------------------------------------------+---------------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++------------------------------------------------------------------------+---------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def create_sim(self): | def _setup_scene(self): | +| # set the up axis to be z-up given that assets are y-up by default | self.cartpole = Articulation(self.cfg.robot_cfg) | +| self.up_axis = self.cfg["sim"]["up_axis"] | # add ground plane | +| | spawn_ground_plane(prim_path="/World/ground", | +| self.sim = super().create_sim(self.device_id, | cfg=GroundPlaneCfg()) | +| self.graphics_device_id, self.physics_engine, | # clone, filter, and replicate | +| self.sim_params) | self.scene.clone_environments( | +| self._create_ground_plane() | copy_from_source=False) | +| self._create_envs(self.num_envs, | self.scene.filter_collisions( | +| self.cfg["env"]['envSpacing'], | global_prim_paths=[]) | +| int(np.sqrt(self.num_envs))) | # add articultion to scene | +| | self.scene.articulations["cartpole"] = self.cartpole | +| def _create_ground_plane(self): | # add lights | +| plane_params = gymapi.PlaneParams() | light_cfg = sim_utils.DomeLightCfg( | +| # set the normal force to be z dimension | intensity=2000.0, color=(0.75, 0.75, 0.75)) | +| plane_params.normal = (gymapi.Vec3(0.0, 0.0, 1.0) | light_cfg.func("/World/Light", light_cfg) | +| if self.up_axis == 'z' | | +| else gymapi.Vec3(0.0, 1.0, 0.0)) | CARTPOLE_CFG = ArticulationCfg( | +| self.gym.add_ground(self.sim, plane_params) | spawn=sim_utils.UsdFileCfg( | +| | usd_path=f"{ISAACLAB_NUCLEUS_DIR}/.../cartpole.usd", | +| def _create_envs(self, num_envs, spacing, num_per_row): | rigid_props=sim_utils.RigidBodyPropertiesCfg( | +| # define plane on which environments are initialized | rigid_body_enabled=True, | +| lower = (gymapi.Vec3(0.5 * -spacing, -spacing, 0.0) | max_linear_velocity=1000.0, | +| if self.up_axis == 'z' | max_angular_velocity=1000.0, | +| else gymapi.Vec3(0.5 * -spacing, 0.0, -spacing)) | max_depenetration_velocity=100.0, | +| upper = gymapi.Vec3(0.5 * spacing, spacing, spacing) | enable_gyroscopic_forces=True, | +| | ), | +| asset_root = os.path.join(os.path.dirname( | articulation_props=sim_utils.ArticulationRootPropertiesCfg( | +| os.path.abspath(__file__)), "../../assets") | enabled_self_collisions=False, | +| asset_file = "urdf/cartpole.urdf" | solver_position_iteration_count=4, | +| | solver_velocity_iteration_count=0, | +| if "asset" in self.cfg["env"]: | sleep_threshold=0.005, | +| asset_root = os.path.join(os.path.dirname( | stabilization_threshold=0.001, | +| os.path.abspath(__file__)), | ), | +| self.cfg["env"]["asset"].get("assetRoot", asset_root)) | ), | +| asset_file = self.cfg["env"]["asset"].get( | init_state=ArticulationCfg.InitialStateCfg( | +| "assetFileName", asset_file) | pos=(0.0, 0.0, 2.0), | +| | joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} | +| asset_path = os.path.join(asset_root, asset_file) | ), | +| asset_root = os.path.dirname(asset_path) | actuators={ | +| asset_file = os.path.basename(asset_path) | "cart_actuator": ImplicitActuatorCfg( | +| | joint_names_expr=["slider_to_cart"], | +| asset_options = gymapi.AssetOptions() | effort_limit=400.0, | +| asset_options.fix_base_link = True | velocity_limit=100.0, | +| cartpole_asset = self.gym.load_asset(self.sim, | stiffness=0.0, | +| asset_root, asset_file, asset_options) | damping=10.0, | +| self.num_dof = self.gym.get_asset_dof_count( | ), | +| cartpole_asset) | "pole_actuator": ImplicitActuatorCfg( | +| | joint_names_expr=["cart_to_pole"], effort_limit=400.0, | +| pose = gymapi.Transform() | velocity_limit=100.0, stiffness=0.0, damping=0.0 | +| if self.up_axis == 'z': | ), | +| pose.p.z = 2.0 | }, | +| pose.r = gymapi.Quat(0.0, 0.0, 0.0, 1.0) | ) | +| else: | | +| pose.p.y = 2.0 | | +| pose.r = gymapi.Quat( | | +| -np.sqrt(2)/2, 0.0, 0.0, np.sqrt(2)/2) | | +| | | +| self.cartpole_handles = [] | | +| self.envs = [] | | +| for i in range(self.num_envs): | | +| # create env instance | | +| env_ptr = self.gym.create_env( | | +| self.sim, lower, upper, num_per_row | | +| ) | | +| cartpole_handle = self.gym.create_actor( | | +| env_ptr, cartpole_asset, pose, | | +| "cartpole", i, 1, 0) | | +| | | +| dof_props = self.gym.get_actor_dof_properties( | | +| env_ptr, cartpole_handle) | | +| dof_props['driveMode'][0] = gymapi.DOF_MODE_EFFORT | | +| dof_props['driveMode'][1] = gymapi.DOF_MODE_NONE | | +| dof_props['stiffness'][:] = 0.0 | | +| dof_props['damping'][:] = 0.0 | | +| self.gym.set_actor_dof_properties(env_ptr, c | | +| artpole_handle, dof_props) | | +| | | +| self.envs.append(env_ptr) | | +| self.cartpole_handles.append(cartpole_handle) | | ++------------------------------------------------------------------------+---------------------------------------------------------------------+ + + +Pre and Post Physics Step +------------------------- + +In IsaacGymEnvs, due to limitations of the GPU APIs, observations had stale data when environments had to perform resets. +This restriction has been eliminated in Isaac Lab, and thus, tasks follow the correct workflow of applying actions, stepping simulation, +collecting states, computing dones, calculating rewards, performing resets, and finally computing observations. +This workflow is done automatically by the framework such that a ``post_physics_step`` API is not required in the task. +However, individual tasks can override the ``step()`` API to control the workflow. + ++------------------------------------------------------------------+-------------------------------------------------------------+ +| IsaacGymEnvs | IsaacLab | ++------------------------------------------------------------------+-------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def pre_physics_step(self, actions): | def _pre_physics_step(self, actions: torch.Tensor) -> None: | +| actions_tensor = torch.zeros( | self.actions = self.action_scale * actions | +| self.num_envs * self.num_dof, | | +| device=self.device, dtype=torch.float) | def _apply_action(self) -> None: | +| actions_tensor[::self.num_dof] = actions.to( | self.cartpole.set_joint_effort_target( | +| self.device).squeeze() * self.max_push_effort | self.actions, joint_ids=self._cart_dof_idx) | +| forces = gymtorch.unwrap_tensor(actions_tensor) | | +| self.gym.set_dof_actuation_force_tensor( | | +| self.sim, forces) | | +| | | +| def post_physics_step(self): | | +| self.progress_buf += 1 | | +| | | +| env_ids = self.reset_buf.nonzero( | | +| as_tuple=False).squeeze(-1) | | +| if len(env_ids) > 0: | | +| self.reset_idx(env_ids) | | +| | | +| self.compute_observations() | | +| self.compute_reward() | | ++------------------------------------------------------------------+-------------------------------------------------------------+ + + +Dones and Resets +---------------- + +In Isaac Lab, ``dones`` are computed in the ``_get_dones()`` method and should return two variables: ``resets`` and ``time_out``. +Tracking of the ``progress_buf`` has been moved to the base class and is now automatically incremented and reset by the framework. +The ``progress_buf`` variable has also been renamed to ``episode_length_buf``. + ++-----------------------------------------------------------------------+---------------------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++-----------------------------------------------------------------------+---------------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def reset_idx(self, env_ids): | def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: | +| positions = 0.2 * (torch.rand((len(env_ids), self.num_dof), | self.joint_pos = self.cartpole.data.joint_pos | +| device=self.device) - 0.5) | self.joint_vel = self.cartpole.data.joint_vel | +| velocities = 0.5 * (torch.rand((len(env_ids), self.num_dof), | | +| device=self.device) - 0.5) | time_out = self.episode_length_buf >= self.max_episode_length - 1 | +| | out_of_bounds = torch.any(torch.abs( | +| self.dof_pos[env_ids, :] = positions[:] | self.joint_pos[:, self._pole_dof_idx] > self.cfg.max_cart_pos), | +| self.dof_vel[env_ids, :] = velocities[:] | dim=1) | +| | out_of_bounds = out_of_bounds | torch.any( | +| env_ids_int32 = env_ids.to(dtype=torch.int32) | torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2, | +| self.gym.set_dof_state_tensor_indexed(self.sim, | dim=1) | +| gymtorch.unwrap_tensor(self.dof_state), | return out_of_bounds, time_out | +| gymtorch.unwrap_tensor(env_ids_int32), len(env_ids_int32)) | | +| self.reset_buf[env_ids] = 0 | def _reset_idx(self, env_ids: Sequence[int] | None): | +| self.progress_buf[env_ids] = 0 | if env_ids is None: | +| | env_ids = self.cartpole._ALL_INDICES | +| | super()._reset_idx(env_ids) | +| | | +| | joint_pos = self.cartpole.data.default_joint_pos[env_ids] | +| | joint_pos[:, self._pole_dof_idx] += sample_uniform( | +| | self.cfg.initial_pole_angle_range[0] * math.pi, | +| | self.cfg.initial_pole_angle_range[1] * math.pi, | +| | joint_pos[:, self._pole_dof_idx].shape, | +| | joint_pos.device, | +| | ) | +| | joint_vel = self.cartpole.data.default_joint_vel[env_ids] | +| | | +| | default_root_state = self.cartpole.data.default_root_state[env_ids] | +| | default_root_state[:, :3] += self.scene.env_origins[env_ids] | +| | | +| | self.joint_pos[env_ids] = joint_pos | +| | | +| | self.cartpole.write_root_pose_to_sim( | +| | default_root_state[:, :7], env_ids) | +| | self.cartpole.write_root_velocity_to_sim( | +| | default_root_state[:, 7:], env_ids) | +| | self.cartpole.write_joint_state_to_sim( | +| | joint_pos, joint_vel, None, env_ids) | ++-----------------------------------------------------------------------+---------------------------------------------------------------------------+ + + +Observations +------------ + +In Isaac Lab, the ``_get_observations()`` API should now return a dictionary containing the ``policy`` key with the observation +buffer as the value. +For asymmetric policies, the dictionary should also include a ``critic`` key that holds the state buffer. + ++--------------------------------------------------------------------------+---------------------------------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++--------------------------------------------------------------------------+---------------------------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def compute_observations(self, env_ids=None): | def _get_observations(self) -> dict: | +| if env_ids is None: | obs = torch.cat( | +| env_ids = np.arange(self.num_envs) | ( | +| | self.joint_pos[:, self._pole_dof_idx[0]], | +| self.gym.refresh_dof_state_tensor(self.sim) | self.joint_vel[:, self._pole_dof_idx[0]], | +| | self.joint_pos[:, self._cart_dof_idx[0]], | +| self.obs_buf[env_ids, 0] = self.dof_pos[env_ids, 0] | self.joint_vel[:, self._cart_dof_idx[0]], | +| self.obs_buf[env_ids, 1] = self.dof_vel[env_ids, 0] | ), | +| self.obs_buf[env_ids, 2] = self.dof_pos[env_ids, 1] | dim=-1, | +| self.obs_buf[env_ids, 3] = self.dof_vel[env_ids, 1] | ) | +| | observations = {"policy": obs} | +| return self.obs_buf | return observations | ++--------------------------------------------------------------------------+---------------------------------------------------------------------------------------+ + + +Rewards +------- + +In Isaac Lab, the reward method ``_get_rewards`` should return the reward buffer as a return value. +Similar to IsaacGymEnvs, computations in the reward function can also be performed using pytorch jit +by adding the ``@torch.jit.script`` annotation. + ++--------------------------------------------------------------------------+----------------------------------------------------------------------------------------+ +| IsaacGymEnvs | Isaac Lab | ++--------------------------------------------------------------------------+----------------------------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def compute_reward(self): | def _get_rewards(self) -> torch.Tensor: | +| # retrieve environment observations from buffer | total_reward = compute_rewards( | +| pole_angle = self.obs_buf[:, 2] | self.cfg.rew_scale_alive, | +| pole_vel = self.obs_buf[:, 3] | self.cfg.rew_scale_terminated, | +| cart_vel = self.obs_buf[:, 1] | self.cfg.rew_scale_pole_pos, | +| cart_pos = self.obs_buf[:, 0] | self.cfg.rew_scale_cart_vel, | +| | self.cfg.rew_scale_pole_vel, | +| self.rew_buf[:], self.reset_buf[:] = compute_cartpole_reward( | self.joint_pos[:, self._pole_dof_idx[0]], | +| pole_angle, pole_vel, cart_vel, cart_pos, | self.joint_vel[:, self._pole_dof_idx[0]], | +| self.reset_dist, self.reset_buf, | self.joint_pos[:, self._cart_dof_idx[0]], | +| self.progress_buf, self.max_episode_length | self.joint_vel[:, self._cart_dof_idx[0]], | +| ) | self.reset_terminated, | +| | ) | +| @torch.jit.script | return total_reward | +| def compute_cartpole_reward(pole_angle, pole_vel, | | +| cart_vel, cart_pos, | @torch.jit.script | +| reset_dist, reset_buf, | def compute_rewards( | +| progress_buf, max_episode_length): | rew_scale_alive: float, | +| | rew_scale_terminated: float, | +| reward = (1.0 - pole_angle * pole_angle - | rew_scale_pole_pos: float, | +| 0.01 * torch.abs(cart_vel) - | rew_scale_cart_vel: float, | +| 0.005 * torch.abs(pole_vel)) | rew_scale_pole_vel: float, | +| | pole_pos: torch.Tensor, | +| # adjust reward for reset agents | pole_vel: torch.Tensor, | +| reward = torch.where(torch.abs(cart_pos) > reset_dist, | cart_pos: torch.Tensor, | +| torch.ones_like(reward) * -2.0, reward) | cart_vel: torch.Tensor, | +| reward = torch.where(torch.abs(pole_angle) > np.pi / 2, | reset_terminated: torch.Tensor, | +| torch.ones_like(reward) * -2.0, reward) | ): | +| | rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) | +| reset = torch.where(torch.abs(cart_pos) > reset_dist, | rew_termination = rew_scale_terminated * reset_terminated.float() | +| torch.ones_like(reset_buf), reset_buf) | rew_pole_pos = rew_scale_pole_pos * torch.sum( | +| reset = torch.where(torch.abs(pole_angle) > np.pi / 2, | torch.square(pole_pos), dim=-1) | +| torch.ones_like(reset_buf), reset_buf) | rew_cart_vel = rew_scale_cart_vel * torch.sum( | +| reset = torch.where(progress_buf >= max_episode_length - 1, | torch.abs(cart_vel), dim=-1) | +| torch.ones_like(reset_buf), reset) | rew_pole_vel = rew_scale_pole_vel * torch.sum( | +| | torch.abs(pole_vel), dim=-1) | +| | total_reward = (rew_alive + rew_termination | +| | + rew_pole_pos + rew_cart_vel + rew_pole_vel) | +| | return total_reward | ++--------------------------------------------------------------------------+----------------------------------------------------------------------------------------+ + + + +Launching Training +~~~~~~~~~~~~~~~~~~ + +To launch a training in Isaac Lab, use the command: + +.. code-block:: bash + + python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 --headless + +Launching Inferencing +~~~~~~~~~~~~~~~~~~~~~ + +To launch inferencing in Isaac Lab, use the command: + +.. code-block:: bash + + python source/standalone/workflows/rl_games/play.py --task=Isaac-Cartpole-Direct-v0 --num_envs=25 --checkpoint= diff --git a/docs/source/migration/migrating_from_omniisaacgymenvs.rst b/docs/source/migration/migrating_from_omniisaacgymenvs.rst new file mode 100644 index 0000000000..8e03b88b30 --- /dev/null +++ b/docs/source/migration/migrating_from_omniisaacgymenvs.rst @@ -0,0 +1,976 @@ +.. _migrating-from-omniisaacgymenvs: + +Migrating from OmniIsaacGymEnvs +=============================== + +.. currentmodule:: omni.isaac.lab + + +OmniIsaacGymEnvs was a reinforcement learning framework using the Isaac Sim platform. +Features from OmniIsaacGymEnvs have been integrated into the Isaac Lab framework. +We have updated OmniIsaacGymEnvs to Isaac Sim version 4.0.0 to support the migration process +to Isaac Lab. Moving forward, OmniIsaacGymEnvs will be deprecated and future development +will continue in Isaac Lab. + + +Task Config Setup +~~~~~~~~~~~~~~~~~ + +In OmniIsaacGymEnvs, task config files were defined in ``.yaml`` format. With Isaac Lab, configs are now specified using a specialized +Python class :class:`~omni.isaac.lab.utils.configclass`. The :class:`~omni.isaac.lab.utils.configclass` module provides a wrapper on top of Python's ``dataclasses`` module. +Each environment should specify its own config class annotated by ``@configclass`` that inherits from :class:`~envs.DirectRLEnvCfg`, +which can include simulation parameters, environment scene parameters, robot parameters, and task-specific parameters. + +Below is an example skeleton of a task config class: + +.. code-block:: python + + from omni.isaac.lab.envs import DirectRLEnvCfg + from omni.isaac.lab.scene import InteractiveSceneCfg + from omni.isaac.lab.sim import SimulationCfg + + @configclass + class MyEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg() + # robot + robot_cfg: ArticulationCfg = ArticulationCfg() + # scene + scene: InteractiveSceneCfg = InteractiveSceneCfg() + # env + decimation = 2 + episode_length_s = 5.0 + num_actions = 1 + num_observations = 4 + num_states = 0 + # task-specific parameters + ... + +Simulation Config +----------------- + +Simulation related parameters are defined as part of the :class:`~omni.isaac.lab.sim.SimulationCfg` class, which is a :class:`~omni.isaac.lab.utils.configclass` module +that holds simulation parameters such as ``dt``, ``device``, and ``gravity``. +Each task config must have a variable named ``sim`` defined that holds the type :class:`~omni.isaac.lab.sim.SimulationCfg`. + +Simulation parameters for articulations and rigid bodies such as ``num_position_iterations``, ``num_velocity_iterations``, +``contact_offset``, ``rest_offset``, ``bounce_threshold_velocity``, ``max_depenetration_velocity`` can all +be specified on a per-actor basis in the config class for each individual articulation and rigid body. + +When running simulation on the GPU, buffers in PhysX require pre-allocation for computing and storing +information such as contacts, collisions and aggregate pairs. These buffers may need to be adjusted +depending on the complexity of the environment, the number of expected contacts and collisions, +and the number of actors in the environment. The :class:`~omni.isaac.lab.sim.PhysxCfg` class provides access for setting the GPU buffer dimensions. + ++--------------------------------------------------------------+-------------------------------------------------------------------+ +| | | +|.. code-block:: yaml |.. code-block:: python | +| | | +| # OmniIsaacGymEnvs | # IsaacLab | +| sim: | sim: SimulationCfg = SimulationCfg( | +| dt: 0.0083 # 1/120 s | dt=1 / 120, | +| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | use_gpu_pipeline=True, | +| use_fabric: True | use_fabric=True, | +| enable_scene_query_support: False | enable_scene_query_support=False, | +| disable_contact_processing: False | disable_contact_processing=False, | +| gravity: [0.0, 0.0, -9.81] | gravity=(0.0, 0.0, -9.81), | +| | | +| default_physics_material: | physics_material=RigidBodyMaterialCfg( | +| static_friction: 1.0 | static_friction=1.0, | +| dynamic_friction: 1.0 | dynamic_friction=1.0, | +| restitution: 0.0 | restitution=0.0 | +| | ) | +| physx: | physx: PhysxCfg = PhysxCfg( | +| worker_thread_count: ${....num_threads} | # worker_thread_count is no longer needed | +| solver_type: ${....solver_type} | solver_type=1, | +| use_gpu: ${contains:"cuda",${....sim_device}} | use_gpu=True, | +| solver_position_iteration_count: 4 | max_position_iteration_count=4, | +| solver_velocity_iteration_count: 0 | max_velocity_iteration_count=0, | +| contact_offset: 0.02 | # moved to actor config | +| rest_offset: 0.001 | # moved to actor config | +| bounce_threshold_velocity: 0.2 | bounce_threshold_velocity=0.2, | +| friction_offset_threshold: 0.04 | friction_offset_threshold=0.04, | +| friction_correlation_distance: 0.025 | friction_correlation_distance=0.025, | +| enable_sleeping: True | # enable_sleeping is no longer needed | +| enable_stabilization: True | enable_stabilization=True, | +| max_depenetration_velocity: 100.0 | # moved to RigidBodyPropertiesCfg | +| | | +| gpu_max_rigid_contact_count: 524288 | gpu_max_rigid_contact_count=2**23, | +| gpu_max_rigid_patch_count: 81920 | gpu_max_rigid_patch_count=5 * 2**15, | +| gpu_found_lost_pairs_capacity: 1024 | gpu_found_lost_pairs_capacity=2**21, | +| gpu_found_lost_aggregate_pairs_capacity: 262144 | gpu_found_lost_aggregate_pairs_capacity=2**25, | +| gpu_total_aggregate_pairs_capacity: 1024 | gpu_total_aggregate_pairs_capacity=2**21, | +| gpu_heap_capacity: 67108864 | gpu_heap_capacity=2**26, | +| gpu_temp_buffer_capacity: 16777216 | gpu_temp_buffer_capacity=2**24, | +| gpu_max_num_partitions: 8 | gpu_max_num_partitions=8, | +| gpu_max_soft_body_contacts: 1048576 | gpu_max_soft_body_contacts=2**20, | +| gpu_max_particle_contacts: 1048576 | gpu_max_particle_contacts=2**20, | +| | ) | +| | ) | ++--------------------------------------------------------------+-------------------------------------------------------------------+ + +Parameters such as ``add_ground_plane`` and ``add_distant_light`` are now part of the task logic when creating the scene. +``enable_cameras`` is now a command line argument ``--enable_cameras`` that can be passed directly to the training script. + + +Scene Config +------------ + +The :class:`~omni.isaac.lab.scene.InteractiveSceneCfg` class can be used to specify parameters related to the scene, such as the number of environments +and the spacing between environments. +Each task config must have a variable named ``scene`` defined that holds the type :class:`~omni.isaac.lab.scene.InteractiveSceneCfg`. + ++--------------------------------------------------------------+-------------------------------------------------------------------+ +| | | +|.. code-block:: yaml |.. code-block:: python | +| | | +| # OmniIsaacGymEnvs | # IsaacLab | +| env: | scene: InteractiveSceneCfg = InteractiveSceneCfg( | +| numEnvs: ${resolve_default:512,${...num_envs}} | num_envs=512, | +| envSpacing: 4.0 | env_spacing=4.0) | ++--------------------------------------------------------------+-------------------------------------------------------------------+ + +Task Config +----------- + +Each environment should specify its own config class that holds task specific parameters, such as the dimensions of the +observation and action buffers. Reward term scaling parameters can also be specified in the config class. + +In Isaac Lab, the ``controlFrequencyInv`` parameter has been renamed to ``decimation``, +which must be specified as a parameter in the config class. + +In addition, the maximum episode length parameter (now ``episode_length_s``) is in seconds instead of steps as it was in OmniIsaacGymEnvs. +To convert between step count to seconds, use the equation: ``episode_length_s = dt * decimation * num_steps``. + +The following parameters must be set for each environment config: + +.. code-block:: python + + decimation = 2 + episode_length_s = 5.0 + num_actions = 1 + num_observations = 4 + num_states = 0 + + +RL Config Setup +~~~~~~~~~~~~~~~ + +RL config files for the rl_games library can continue to be defined in ``.yaml`` files in Isaac Lab. +Most of the content of the config file can be copied directly from OmniIsaacGymEnvs. +Note that in Isaac Lab, we do not use hydra to resolve relative paths in config files. +Please replace any relative paths such as ``${....device}`` with the actual values of the parameters. + +Additionally, the observation and action clip ranges have been moved to the RL config file. +For any ``clipObservations`` and ``clipActions`` parameters that were defined in the IsaacGymEnvs task config file, +they should be moved to the RL config file in Isaac Lab. + ++--------------------------+----------------------------+ +| | | +| IsaacGymEnvs Task Config | Isaac Lab RL Config | ++--------------------------+----------------------------+ +|.. code-block:: yaml |.. code-block:: yaml | +| | | +| # OmniIsaacGymEnvs | # IsaacLab | +| env: | params: | +| clipObservations: 5.0 | env: | +| clipActions: 1.0 | clip_observations: 5.0 | +| | clip_actions: 1.0 | ++--------------------------+----------------------------+ + +Environment Creation +~~~~~~~~~~~~~~~~~~~~ + +In OmniIsaacGymEnvs, environment creation generally happened in the ``set_up_scene()`` API, +which involved creating the initial environment, cloning the environment, filtering collisions, +adding the ground plane and lights, and creating the ``View`` classes for the actors. + +Similar functionality is performed in Isaac Lab in the ``_setup_scene()`` API. +The main difference is that the base class ``_setup_scene()`` no longer performs operations for +cloning the environment and adding ground plane and lights. Instead, these operations +should now be implemented in individual tasks' ``_setup_scene`` implementations to provide more +flexibility around the scene setup process. + +Also note that by defining an ``Articulation`` or ``RigidObject`` object, the actors will be +added to the scene by parsing the ``spawn`` parameter in the actor config and a ``View`` class +will automatically be created for the actor. This avoids the need to separately define an +``ArticulationView`` or ``RigidPrimView`` object for the actors. + + ++------------------------------------------------------------------------------+------------------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++------------------------------------------------------------------------------+------------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def set_up_scene(self, scene) -> None: | def _setup_scene(self): | +| self.get_cartpole() | self.cartpole = Articulation(self.cfg.robot_cfg) | +| super().set_up_scene(scene) | # add ground plane | +| | spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg() | +| self._cartpoles = ArticulationView( | # clone, filter, and replicate | +| prim_paths_expr="/World/envs/.*/Cartpole", | self.scene.clone_environments(copy_from_source=False) | +| name="cartpole_view", reset_xform_properties=False | self.scene.filter_collisions(global_prim_paths=[]) | +| ) | # add articultion to scene | +| scene.add(self._cartpoles) | self.scene.articulations["cartpole"] = self.cartpole | +| | # add lights | +| | light_cfg = sim_utils.DomeLightCfg(intensity=2000.0) | +| | light_cfg.func("/World/Light", light_cfg) | ++------------------------------------------------------------------------------+------------------------------------------------------------------------+ + + +Ground Plane +------------ + +In addition to the above example, more sophisticated ground planes can be defined using the :class:`~terrains.TerrainImporterCfg` class. + +.. code-block:: python + + from omni.isaac.lab.terrains import TerrainImporterCfg + + terrain = TerrainImporterCfg( + 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, + restitution=0.0, + ), + ) + +The terrain can then be added to the scene in ``_setup_scene(self)`` by referencing the ``TerrainImporterCfg`` object: + +.. code-block::python + + def _setup_scene(self): + ... + self.cfg.terrain.num_envs = self.scene.cfg.num_envs + self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing + self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) + + +Actors +------ + +In Isaac Lab, each Articulation and Rigid Body actor can have its own config class. +The :class:`~omni.isaac.lab.assets.ArticulationCfg` can be +used to define parameters for articulation actors, including file path, simulation parameters, actuator properties, and initial states. + +.. code-block::python + + from omni.isaac.lab.actuators import ImplicitActuatorCfg + from omni.isaac.lab.assets import ArticulationCfg + + CARTPOLE_CFG = ArticulationCfg( + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Classic/Cartpole/cartpole.usd", + rigid_props=sim_utils.RigidBodyPropertiesCfg( + rigid_body_enabled=True, + max_linear_velocity=1000.0, + max_angular_velocity=1000.0, + max_depenetration_velocity=100.0, + enable_gyroscopic_forces=True, + ), + articulation_props=sim_utils.ArticulationRootPropertiesCfg( + enabled_self_collisions=False, + solver_position_iteration_count=4, + solver_velocity_iteration_count=0, + sleep_threshold=0.005, + stabilization_threshold=0.001, + ), + ), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 2.0), joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} + ), + actuators={ + "cart_actuator": ImplicitActuatorCfg( + joint_names_expr=["slider_to_cart"], + effort_limit=400.0, + velocity_limit=100.0, + stiffness=0.0, + damping=10.0, + ), + "pole_actuator": ImplicitActuatorCfg( + joint_names_expr=["cart_to_pole"], effort_limit=400.0, velocity_limit=100.0, stiffness=0.0, damping=0.0 + ), + }, + ) + +Within the :class:`~assets.ArticulationCfg`, the ``spawn`` attribute can be used to add the robot to the scene by specifying the path to the robot file. +In addition, :class:`~omni.isaac.lab.sim.schemas.RigidBodyPropertiesCfg` can be used to specify simulation properties +for the rigid bodies in the articulation. +Similarly, :class:`~omni.isaac.lab.sim.schemas.ArticulationRootPropertiesCfg` can be used to specify simulation properties for the articulation. +Joint and dof properties are now specified as part of the ``actuators`` dictionary using :class:`~actuators.ImplicitActuatorCfg`. +Joints and dofs with the same properties can be grouped into regex expressions or provided as a list of names or expressions. + +Actors are added to the scene by simply calling ``self.cartpole = Articulation(self.cfg.robot_cfg)``, +where ``self.cfg.robot_cfg`` is an :class:`~assets.ArticulationCfg` object. Once initialized, they should also be added +to the :class:`~scene.InteractiveScene` by calling ``self.scene.articulations["cartpole"] = self.cartpole`` so that +the :class:`~scene.InteractiveScene` can traverse through actors in the scene for writing values to the simulation and resetting. + + +Accessing States from Simulation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +APIs for accessing physics states in Isaac Lab require the creation of an :class:`~assets.Articulation` or :class:`~assets.RigidObject` +object. Multiple objects can be initialized for different articulations or rigid bodies in the scene by defining +corresponding :class:`~assets.ArticulationCfg` or :class:`~assets.RigidObjectCfg` config, as outlined in the section above. +This replaces the previously used ``ArticulationView`` and ``RigidPrimView`` classes used in OmniIsaacGymEnvs. + +However, functionality between the classes are similar: + ++------------------------------------------------------------------+-----------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++------------------------------------------------------------------+-----------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| dof_pos = self._cartpoles.get_joint_positions(clone=False) | self.joint_pos = self._robot.data.joint_pos | +| dof_vel = self._cartpoles.get_joint_velocities(clone=False) | self.joint_vel = self._robot.data.joint_vel | ++------------------------------------------------------------------+-----------------------------------------------------------------+ + +In Isaac Lab, :class:`~assets.Articulation` and :class:`~assets.RigidObject` classes both have a ``data`` class. +The data classes (:class:`~assets.ArticulationData` and :class:`~assets.RigidObjectData`) contain +buffers that hold the states for the articulation and rigid objects and provide +a more performant way of retrieving states from the actors. + +Apart from some renamings of APIs, setting states for actors can also be performed similarly between OmniIsaacGymEnvs and Isaac Lab. + ++---------------------------------------------------------------------------+---------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++---------------------------------------------------------------------------+---------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| indices = env_ids.to(dtype=torch.int32) | self._robot.write_joint_state_to_sim(joint_pos, joint_vel, | +| self._cartpoles.set_joint_positions(dof_pos, indices=indices) | joint_ids, env_ids) | +| self._cartpoles.set_joint_velocities(dof_vel, indices=indices) | | ++---------------------------------------------------------------------------+---------------------------------------------------------------+ + +In Isaac Lab, ``root_pose`` and ``root_velocity`` have been combined into single buffers and no longer split between +``root_position``, ``root_orientation``, ``root_linear_velocity`` and ``root_angular_velocity``. + +.. code-block::python + + self.cartpole.write_root_pose_to_sim(default_root_state[:, :7], env_ids) + self.cartpole.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) + + +Creating a New Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each environment in Isaac Lab should be in its own directory following this structure: + +.. code-block:: none + + my_environment/ + - agents/ + - __init__.py + - rl_games_ppo_cfg.py + - __init__.py + my_env.py + +* ``my_environment`` is the root directory of the task. +* ``my_environment/agents`` is the directory containing all RL config files for the task. Isaac Lab supports multiple RL libraries that can each have its own individual config file. +* ``my_environment/__init__.py`` is the main file that registers the environment with the Gymnasium interface. This allows the training and inferencing scripts to find the task by its name. The content of this file should be as follow: + +.. code-block:: python + + import gymnasium as gym + + from . import agents + from .cartpole_env import CartpoleEnv, CartpoleEnvCfg + + ## + # Register Gym environments. + ## + + gym.register( + id="Isaac-Cartpole-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct_workflow.cartpole:CartpoleEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": CartpoleEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml" + }, + ) + +* ``my_environment/my_env.py`` is the main python script that implements the task logic and task config class for the environment. + + +Task Logic +~~~~~~~~~~ + +The ``post_reset`` API in OmniIsaacGymEnvs is no longer required in Isaac Lab. +Everything that was previously done in ``post_reset`` can be done in the ``__init__`` method after +executing the base class's ``__init__``. At this point, simulation has already started. + +In OmniIsaacGymEnvs, due to limitations of the GPU APIs, resets could not be performed based on states of the current step. +Instead, resets have to be performed at the beginning of the next time step. +This restriction has been eliminated in Isaac Lab, and thus, tasks follow the correct workflow of applying actions, stepping simulation, +collecting states, computing dones, calculating rewards, performing resets, and finally computing observations. +This workflow is done automatically by the framework such that a ``post_physics_step`` API is not required in the task. +However, individual tasks can override the ``step()`` API to control the workflow. + +In Isaac Lab, we also separate the ``pre_physics_step`` API for processing actions from the policy with +the ``apply_action`` API, which sets the actions into the simulation. This provides more flexibility in controlling +when actions should be written to simulation when ``decimation`` is used. +``pre_physics_step`` will be called once per step before stepping simulation. +``apply_actions`` will be called ``decimation`` number of times for each RL step, once before each simulation step call. + +The ordering of the calls are as follow: + ++----------------------------------+----------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++----------------------------------+----------------------------------+ +|.. code-block:: none |.. code-block:: none | +| | | +| pre_physics_step | pre_physics_step | +| |-- reset_idx() | |-- _pre_physics_step(action)| +| |-- apply_action | |-- _apply_action() | +| | | +| post_physics_step | post_physics_step | +| |-- get_observations() | |-- _get_dones() | +| |-- calculate_metrics() | |-- _get_rewards() | +| |-- is_done() | |-- _reset_idx() | +| | |-- _get_observations() | ++----------------------------------+----------------------------------+ + +With this approach, resets are performed based on actions from the current step instead of the previous step. +Observations will also be computed with the correct states after resets. + +We have also performed some renamings of APIs: + +* ``set_up_scene(self, scene)`` --> ``_setup_scene(self)`` +* ``post_reset(self)`` --> ``__init__(...)`` +* ``pre_physics_step(self, actions)`` --> ``_pre_physics_step(self, actions)`` and ``_apply_action(self)`` +* ``reset_idx(self, env_ids)`` --> ``_reset_idx(self, env_ids)`` +* ``get_observations(self)`` --> ``_get_observations(self)`` - ``_get_observations()`` should now return a dictionary ``{"policy": obs}`` +* ``calculate_metrics(self)`` --> ``_get_rewards(self)`` - ``_get_rewards()`` should now return the reward buffer +* ``is_done(self)`` --> ``_get_dones(self)`` - ``_get_dones()`` should now return 2 buffers: ``reset`` and ``time_out`` buffers + + + +Putting It All Together +~~~~~~~~~~~~~~~~~~~~~~~ + +The Cartpole environment is shown here in completion to fully show the comparison between the OmniIsaacGymEnvs implementation and the Isaac Lab implementation. + +Task Config +----------- + +Task config in Isaac Lab can be split into the main task configuration class and individual config objects for the actors. + ++-----------------------------------------------------------------+-----------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++-----------------------------------------------------------------+-----------------------------------------------------------------+ +|.. code-block:: yaml |.. code-block:: python | +| | | +| # used to create the object | @configclass | +| | class CartpoleEnvCfg(DirectRLEnvCfg): | +| name: Cartpole | | +| | # simulation | +| physics_engine: ${..physics_engine} | sim: SimulationCfg = SimulationCfg(dt=1 / 120) | +| | # robot | +| # if given, will override the device setting in gym. | robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace( | +| env: | prim_path="/World/envs/env_.*/Robot") | +| | cart_dof_name = "slider_to_cart" | +| numEnvs: ${resolve_default:512,${...num_envs}} | pole_dof_name = "cart_to_pole" | +| envSpacing: 4.0 | # scene | +| resetDist: 3.0 | scene: InteractiveSceneCfg = InteractiveSceneCfg( | +| maxEffort: 400.0 | num_envs=4096, env_spacing=4.0, replicate_physics=True) | +| | # env | +| clipObservations: 5.0 | decimation = 2 | +| clipActions: 1.0 | episode_length_s = 5.0 | +| controlFrequencyInv: 2 # 60 Hz | action_scale = 100.0 # [N] | +| | num_actions = 1 | +| sim: | num_observations = 4 | +| | num_states = 0 | +| dt: 0.0083 # 1/120 s | # reset | +| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | max_cart_pos = 3.0 | +| gravity: [0.0, 0.0, -9.81] | initial_pole_angle_range = [-0.25, 0.25] | +| add_ground_plane: True | # reward scales | +| add_distant_light: False | rew_scale_alive = 1.0 | +| use_fabric: True | rew_scale_terminated = -2.0 | +| enable_scene_query_support: False | rew_scale_pole_pos = -1.0 | +| disable_contact_processing: False | rew_scale_cart_vel = -0.01 | +| | rew_scale_pole_vel = -0.005 | +| enable_cameras: False | | +| | | +| default_physics_material: | CARTPOLE_CFG = ArticulationCfg( | +| static_friction: 1.0 | spawn=sim_utils.UsdFileCfg( | +| dynamic_friction: 1.0 | usd_path=f"{ISAACLAB_NUCLEUS_DIR}/.../cartpole.usd", | +| restitution: 0.0 | rigid_props=sim_utils.RigidBodyPropertiesCfg( | +| | rigid_body_enabled=True, | +| physx: | max_linear_velocity=1000.0, | +| worker_thread_count: ${....num_threads} | max_angular_velocity=1000.0, | +| solver_type: ${....solver_type} | max_depenetration_velocity=100.0, | +| use_gpu: ${eq:${....sim_device},"gpu"} # set to False to... | enable_gyroscopic_forces=True, | +| solver_position_iteration_count: 4 | ), | +| solver_velocity_iteration_count: 0 | articulation_props=sim_utils.ArticulationRootPropertiesCfg( | +| contact_offset: 0.02 | enabled_self_collisions=False, | +| rest_offset: 0.001 | solver_position_iteration_count=4, | +| bounce_threshold_velocity: 0.2 | solver_velocity_iteration_count=0, | +| friction_offset_threshold: 0.04 | sleep_threshold=0.005, | +| friction_correlation_distance: 0.025 | stabilization_threshold=0.001, | +| enable_sleeping: True | ), | +| enable_stabilization: True | ), | +| max_depenetration_velocity: 100.0 | init_state=ArticulationCfg.InitialStateCfg( | +| | pos=(0.0, 0.0, 2.0), | +| # GPU buffers | joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} | +| gpu_max_rigid_contact_count: 524288 | ), | +| gpu_max_rigid_patch_count: 81920 | actuators={ | +| gpu_found_lost_pairs_capacity: 1024 | "cart_actuator": ImplicitActuatorCfg( | +| gpu_found_lost_aggregate_pairs_capacity: 262144 | joint_names_expr=["slider_to_cart"], | +| gpu_total_aggregate_pairs_capacity: 1024 | effort_limit=400.0, | +| gpu_max_soft_body_contacts: 1048576 | velocity_limit=100.0, | +| gpu_max_particle_contacts: 1048576 | stiffness=0.0, | +| gpu_heap_capacity: 67108864 | damping=10.0, | +| gpu_temp_buffer_capacity: 16777216 | ), | +| gpu_max_num_partitions: 8 | "pole_actuator": ImplicitActuatorCfg( | +| | joint_names_expr=["cart_to_pole"], effort_limit=400.0, | +| Cartpole: | velocity_limit=100.0, stiffness=0.0, damping=0.0 | +| override_usd_defaults: False | ), | +| enable_self_collisions: False | }, | +| enable_gyroscopic_forces: True | ) | +| solver_position_iteration_count: 4 | | +| solver_velocity_iteration_count: 0 | | +| sleep_threshold: 0.005 | | +| stabilization_threshold: 0.001 | | +| density: -1 | | +| max_depenetration_velocity: 100.0 | | +| contact_offset: 0.02 | | +| rest_offset: 0.001 | | ++-----------------------------------------------------------------+-----------------------------------------------------------------+ + + + +Task Setup +---------- + +The ``post_reset`` API in OmniIsaacGymEnvs is no longer required in Isaac Lab. +Everything that was previously done in ``post_reset`` can be done in the ``__init__`` method after +executing the base class's ``__init__``. At this point, simulation has already started. + ++-------------------------------------------------------------------------+-------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++-------------------------------------------------------------------------+-------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| class CartpoleTask(RLTask): | class CartpoleEnv(DirectRLEnv): | +| | cfg: CartpoleEnvCfg | +| def __init__(self, name, sim_config, env, offset=None) -> None: | def __init__(self, cfg: CartpoleEnvCfg, | +| | render_mode: str | None = None, **kwargs): | +| self.update_config(sim_config) | super().__init__(cfg, render_mode, **kwargs) | +| self._max_episode_length = 500 | | +| | | +| self._num_observations = 4 | self._cart_dof_idx, _ = self.cartpole.find_joints( | +| self._num_actions = 1 | self.cfg.cart_dof_name) | +| | self._pole_dof_idx, _ = self.cartpole.find_joints( | +| RLTask.__init__(self, name, env) | self.cfg.pole_dof_name) | +| | self.action_scale=self.cfg.action_scale | +| def update_config(self, sim_config): | | +| self._sim_config = sim_config | self.joint_pos = self.cartpole.data.joint_pos | +| self._cfg = sim_config.config | self.joint_vel = self.cartpole.data.joint_vel | +| self._task_cfg = sim_config. | | +| task_config | | +| | | +| self._num_envs = self._task_cfg["env"]["numEnvs"] | | +| self._env_spacing = self._task_cfg["env"]["envSpacing"] | | +| self._cartpole_positions = torch.tensor([0.0, 0.0, 2.0]) | | +| | | +| self._reset_dist = self._task_cfg["env"]["resetDist"] | | +| self._max_push_effort = self._task_cfg["env"]["maxEffort"] | | +| | | +| | | +| def post_reset(self): | | +| self._cart_dof_idx = self._cartpoles.get_dof_index( | | +| "cartJoint") | | +| self._pole_dof_idx = self._cartpoles.get_dof_index( | | +| "poleJoint") | | +| # randomize all envs | | +| indices = torch.arange( | | +| self._cartpoles.count, dtype=torch.int64, | | +| device=self._device) | | +| self.reset_idx(indices) | | ++-------------------------------------------------------------------------+-------------------------------------------------------------+ + + + +Scene Setup +----------- + +``set_up_scene`` in OmniIsaacGymEnvs has been replaced by ``_setup_scene``. +In Isaac Lab, cloning and collision filtering have been provided as APIs for the task class to call when necessary. +Similarly, adding ground plane and lights should also be taken care of in the task class. +Adding actors to the scene has been replaced by ``self.scene.articulations["cartpole"] = self.cartpole``. + ++-----------------------------------------------------------+----------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++-----------------------------------------------------------+----------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def set_up_scene(self, scene) -> None: | def _setup_scene(self): | +| | self.cartpole = Articulation(self.cfg.robot_cfg) | +| self.get_cartpole() | # add ground plane | +| super().set_up_scene(scene) | spawn_ground_plane(prim_path="/World/ground", | +| self._cartpoles = ArticulationView( | cfg=GroundPlaneCfg()) | +| prim_paths_expr="/World/envs/.*/Cartpole", | # clone, filter, and replicate | +| name="cartpole_view", | self.scene.clone_environments( | +| reset_xform_properties=False | copy_from_source=False) | +| ) | self.scene.filter_collisions( | +| scene.add(self._cartpoles) | global_prim_paths=[]) | +| return | # add articultion to scene | +| | self.scene.articulations["cartpole"] = self.cartpole | +| def get_cartpole(self): | | +| cartpole = Cartpole( | # add lights | +| prim_path=self.default_zero_env_path+"/Cartpole", | light_cfg = sim_utils.DomeLightCfg( | +| name="Cartpole", | intensity=2000.0, color=(0.75, 0.75, 0.75)) | +| translation=self._cartpole_positions | light_cfg.func("/World/Light", light_cfg) | +| ) | | +| # applies articulation settings from the | | +| # task configuration yaml file | | +| self._sim_config.apply_articulation_settings( | | +| "Cartpole", get_prim_at_path(cartpole.prim_path), | | +| self._sim_config.parse_actor_config("Cartpole") | | +| ) | | ++-----------------------------------------------------------+----------------------------------------------------------+ + + +Pre-Physics Step +---------------- + +Note that resets are no longer performed in the ``pre_physics_step`` API. +In addition, the separation of ``_pre_physics_step`` and ``_apply_action`` allow for more flexibility +in processing the action buffer and setting actions into simulation. + ++------------------------------------------------------------------+-------------------------------------------------------------+ +| OmniIsaacGymEnvs | IsaacLab | ++------------------------------------------------------------------+-------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def pre_physics_step(self, actions) -> None: | def _pre_physics_step(self, | +| if not self.world.is_playing(): | actions: torch.Tensor) -> None: | +| return | self.actions = self.action_scale * actions | +| | | +| reset_env_ids = self.reset_buf.nonzero( | def _apply_action(self) -> None: | +| as_tuple=False).squeeze(-1) | self.cartpole.set_joint_effort_target( | +| if len(reset_env_ids) > 0: | self.actions, joint_ids=self._cart_dof_idx) | +| self.reset_idx(reset_env_ids) | | +| | | +| actions = actions.to(self._device) | | +| | | +| forces = torch.zeros((self._cartpoles.count, | | +| self._cartpoles.num_dof), | | +| dtype=torch.float32, device=self._device) | | +| forces[:, self._cart_dof_idx] = | | +| self._max_push_effort * actions[:, 0] | | +| | | +| indices = torch.arange(self._cartpoles.count, | | +| dtype=torch.int32, device=self._device) | | +| self._cartpoles.set_joint_efforts( | | +| forces, indices=indices) | | ++------------------------------------------------------------------+-------------------------------------------------------------+ + + +Dones and Resets +---------------- + +In Isaac Lab, ``dones`` are computed in the ``_get_dones()`` method and should return two variables: ``resets`` and ``time_out``. +``_reset_idx()`` is also called after stepping simulation instead of before, as it was done in OmniIsaacGymEnvs. +``progress_buf`` has been renamed to ``episode_length_buf`` in Isaac Lab and +bookkeeping is now done automatically by the framework. Task implementations should no longer increment and reset the ``episode_length_buf`` buffer. + ++------------------------------------------------------------------+--------------------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++------------------------------------------------------------------+--------------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def is_done(self) -> None: | def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: | +| resets = torch.where( | self.joint_pos = self.cartpole.data.joint_pos | +| torch.abs(self.cart_pos) > self._reset_dist, 1, 0) | self.joint_vel = self.cartpole.data.joint_vel | +| resets = torch.where( | | +| torch.abs(self.pole_pos) > math.pi / 2, 1, resets) | time_out = self.episode_length_buf >= self.max_episode_length - 1 | +| resets = torch.where( | out_of_bounds = torch.any(torch.abs( | +| self.progress_buf >= self._max_episode_length, 1, resets) | self.joint_pos[:, self._pole_dof_idx] > self.cfg.max_cart_pos), | +| self.reset_buf[:] = resets | dim=1) | +| | out_of_bounds = out_of_bounds | torch.any( | +| | torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2, | +| | dim=1) | +| | return out_of_bounds, time_out | +| | | +| def reset_idx(self, env_ids): | def _reset_idx(self, env_ids: Sequence[int] | None): | +| num_resets = len(env_ids) | if env_ids is None: | +| | env_ids = self.cartpole._ALL_INDICES | +| # randomize DOF positions | super()._reset_idx(env_ids) | +| dof_pos = torch.zeros((num_resets, self._cartpoles.num_dof), | | +| device=self._device) | joint_pos = self.cartpole.data.default_joint_pos[env_ids] | +| dof_pos[:, self._cart_dof_idx] = 1.0 * ( | joint_pos[:, self._pole_dof_idx] += sample_uniform( | +| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | self.cfg.initial_pole_angle_range[0] * math.pi, | +| dof_pos[:, self._pole_dof_idx] = 0.125 * math.pi * ( | self.cfg.initial_pole_angle_range[1] * math.pi, | +| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | joint_pos[:, self._pole_dof_idx].shape, | +| | joint_pos.device, | +| # randomize DOF velocities | ) | +| dof_vel = torch.zeros((num_resets, self._cartpoles.num_dof), | joint_vel = self.cartpole.data.default_joint_vel[env_ids] | +| device=self._device) | | +| dof_vel[:, self._cart_dof_idx] = 0.5 * ( | default_root_state = self.cartpole.data.default_root_state[env_ids] | +| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | default_root_state[:, :3] += self.scene.env_origins[env_ids] | +| dof_vel[:, self._pole_dof_idx] = 0.25 * math.pi * ( | | +| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | self.joint_pos[env_ids] = joint_pos | +| | self.joint_vel[env_ids] = joint_vel | +| # apply resets | | +| indices = env_ids.to(dtype=torch.int32) | self.cartpole.write_root_pose_to_sim( | +| self._cartpoles.set_joint_positions(dof_pos, indices=indices) | default_root_state[:, :7], env_ids) | +| self._cartpoles.set_joint_velocities(dof_vel, indices=indices) | self.cartpole.write_root_velocity_to_sim( | +| | default_root_state[:, 7:], env_ids) | +| # bookkeeping | self.cartpole.write_joint_state_to_sim( | +| self.reset_buf[env_ids] = 0 | joint_pos, joint_vel, None, env_ids) | +| self.progress_buf[env_ids] = 0 | | +| | | +| | | ++------------------------------------------------------------------+--------------------------------------------------------------------------+ + + +Rewards +------- + +In Isaac Lab, rewards are implemented in the ``_get_rewards`` API and should return the reward buffer instead of assigning +it directly to ``self.rew_buf``. Computation in the reward function can also be performed using pytorch jit +through defining functions with the ``@torch.jit.script`` annotation. + ++-------------------------------------------------------+-----------------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++-------------------------------------------------------+-----------------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: python | +| | | +| def calculate_metrics(self) -> None: | def _get_rewards(self) -> torch.Tensor: | +| reward = (1.0 - self.pole_pos * self.pole_pos | total_reward = compute_rewards( | +| - 0.01 * torch.abs(self.cart_vel) - 0.005 | self.cfg.rew_scale_alive, | +| * torch.abs(self.pole_vel)) | self.cfg.rew_scale_terminated, | +| reward = torch.where( | self.cfg.rew_scale_pole_pos, | +| torch.abs(self.cart_pos) > self._reset_dist, | self.cfg.rew_scale_cart_vel, | +| torch.ones_like(reward) * -2.0, reward) | self.cfg.rew_scale_pole_vel, | +| reward = torch.where( | self.joint_pos[:, self._pole_dof_idx[0]], | +| torch.abs(self.pole_pos) > np.pi / 2, | self.joint_vel[:, self._pole_dof_idx[0]], | +| torch.ones_like(reward) * -2.0, reward) | self.joint_pos[:, self._cart_dof_idx[0]], | +| | self.joint_vel[:, self._cart_dof_idx[0]], | +| self.rew_buf[:] = reward | self.reset_terminated, | +| | ) | +| | return total_reward | +| | | +| | @torch.jit.script | +| | def compute_rewards( | +| | rew_scale_alive: float, | +| | rew_scale_terminated: float, | +| | rew_scale_pole_pos: float, | +| | rew_scale_cart_vel: float, | +| | rew_scale_pole_vel: float, | +| | pole_pos: torch.Tensor, | +| | pole_vel: torch.Tensor, | +| | cart_pos: torch.Tensor, | +| | cart_vel: torch.Tensor, | +| | reset_terminated: torch.Tensor, | +| | ): | +| | rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) | +| | rew_termination = rew_scale_terminated * reset_terminated.float() | +| | rew_pole_pos = rew_scale_pole_pos * torch.sum( | +| | torch.square(pole_pos), dim=-1) | +| | rew_cart_vel = rew_scale_cart_vel * torch.sum( | +| | torch.abs(cart_vel), dim=-1) | +| | rew_pole_vel = rew_scale_pole_vel * torch.sum( | +| | torch.abs(pole_vel), dim=-1) | +| | total_reward = (rew_alive + rew_termination | +| | + rew_pole_pos + rew_cart_vel + rew_pole_vel) | +| | return total_reward | ++-------------------------------------------------------+-----------------------------------------------------------------------+ + + +Observations +------------ + +In Isaac Lab, the ``_get_observations()`` API must return a dictionary with the key ``policy`` that has the observation buffer as the value. +When working with asymmetric actor-critic states, the states for the critic should have the key ``critic`` and be returned +with the observation buffer in the same dictionary. + ++------------------------------------------------------------------+-------------------------------------------------------------+ +| OmniIsaacGymEnvs | Isaac Lab | ++------------------------------------------------------------------+-------------------------------------------------------------+ +|.. code-block:: python |.. code-block:: | +| | | +| def get_observations(self) -> dict: | def _get_observations(self) -> dict: | +| dof_pos = self._cartpoles.get_joint_positions(clone=False) | obs = torch.cat( | +| dof_vel = self._cartpoles.get_joint_velocities(clone=False) | ( | +| | self.joint_pos[:, self._pole_dof_idx[0]], | +| self.cart_pos = dof_pos[:, self._cart_dof_idx] | self.joint_vel[:, self._pole_dof_idx[0]], | +| self.cart_vel = dof_vel[:, self._cart_dof_idx] | self.joint_pos[:, self._cart_dof_idx[0]], | +| self.pole_pos = dof_pos[:, self._pole_dof_idx] | self.joint_vel[:, self._cart_dof_idx[0]], | +| self.pole_vel = dof_vel[:, self._pole_dof_idx] | ), | +| self.obs_buf[:, 0] = self.cart_pos | dim=-1, | +| self.obs_buf[:, 1] = self.cart_vel | ) | +| self.obs_buf[:, 2] = self.pole_pos | observations = {"policy": obs} | +| self.obs_buf[:, 3] = self.pole_vel | return observations | +| | | +| observations = {self._cartpoles.name: | | +| {"obs_buf": self.obs_buf}} | | +| return observations | | ++------------------------------------------------------------------+-------------------------------------------------------------+ + + +Domain Randomization +~~~~~~~~~~~~~~~~~~~~ + +In OmniIsaacGymEnvs, domain randomization was specified through the task ``.yaml`` config file. +In Isaac Lab, the domain randomization configuration uses the :class:`~omni.isaac.lab.utils.configclass` module +to specify a configuration class consisting of :class:`~managers.EventTermCfg` variables. + +Below is an example of a configuration class for domain randomization: + +.. code-block:: python + + @configclass + class EventCfg: + robot_physics_material = EventTerm( + func=mdp.randomize_rigid_body_material, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=".*"), + "static_friction_range": (0.7, 1.3), + "dynamic_friction_range": (1.0, 1.0), + "restitution_range": (1.0, 1.0), + "num_buckets": 250, + }, + ) + robot_joint_stiffness_and_damping = EventTerm( + func=mdp.randomize_actuator_gains, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), + "stiffness_distribution_params": (0.75, 1.5), + "damping_distribution_params": (0.3, 3.0), + "operation": "scale", + "distribution": "log_uniform", + }, + ) + reset_gravity = EventTerm( + func=mdp.randomize_physics_scene_gravity, + mode="interval", + is_global_time=True, + interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt) + params={ + "gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]), + "operation": "add", + "distribution": "gaussian", + }, + ) + +Each ``EventTerm`` object is of the :class:`~managers.EventTermCfg` class and takes in a ``func`` parameter +for specifying the function to call during randomization, a ``mode`` parameter, which can be ``startup``, +``reset`` or ``interval``. THe ``params`` dictionary should provide the necessary arguments to the +function that is specified in the ``func`` parameter. +Functions specified as ``func`` for the ``EventTerm`` can be found in the :class:`~envs.mdp.events` module. + +Note that as part of the ``"asset_cfg": SceneEntityCfg("robot", body_names=".*")`` parameter, the name of +the actor ``"robot"`` is provided, along with the body or joint names specified as a regex expression, +which will be the actors and bodies/joints that will have randomization applied. + +One difference with OmniIsaacGymEnvs is that ``interval`` randomization is now specified as seconds instead of +steps. When ``mode="interval"``, the ``interval_range_s`` parameter must also be provided, which specifies +the range of seconds for which randomization should be applied. This range will then be randomized to +determine a specific time in seconds when the next randomization will occur for the term. +To convert between steps to seconds, use the equation ``time_s = num_steps * (decimation * dt)``. + +Similar to OmniIsaacGymEnvs, randomization APIs are available for randomizing articulation properties, +such as joint stiffness and damping, joint limits, rigid body materials, fixed tendon properties, +as well as rigid body properties, such as mass and rigid body materials. Randomization of the +physics scene gravity is also supported. Note that randomization of scale is current not supported +in Isaac Lab. To randomize scale, please set up the scene in a way where each environment holds the actor +at a different scale. + +Once the ``configclass`` for the randomization terms have been set up, the class must be added +to the base config class for the task and be assigned to the variable ``events``. + +.. code-block:: python + + @configclass + class MyTaskConfig: + events: EventCfg = EventCfg() + + +Action and Observation Noise +---------------------------- + +Actions and observation noise can also be added using the :class:`~utils.configclass` module. +Action and observation noise configs must be added to the main task config using the +``action_noise_model`` and ``observation_noise_model`` variables: + +.. code-block:: python + + @configclass + class MyTaskConfig: + # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset + action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), + ) + # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset + observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), + ) + + +:class:`~.utils.noise.NoiseModelWithAdditiveBiasCfg` can be used to sample both uncorrelated noise +per step as well as correlated noise that is re-sampled at reset time. +The ``noise_cfg`` term specifies the Gaussian distribution that will be sampled at each +step for all environments. This noise will be added to the corresponding actions and +observations buffers at every step. +The ``bias_noise_cfg`` term specifies the Gaussian distribution for the correlated noise +that will be sampled at reset time for the environments being reset. The same noise +will be applied each step for the remaining of the episode for the environments and +resampled at the next reset. + +This replaces the following setup in OmniIsaacGymEnvs: + +.. code-block:: yaml + + domain_randomization: + randomize: True + randomization_params: + observations: + on_reset: + operation: "additive" + distribution: "gaussian" + distribution_parameters: [0, .0001] + on_interval: + frequency_interval: 1 + operation: "additive" + distribution: "gaussian" + distribution_parameters: [0, .002] + actions: + on_reset: + operation: "additive" + distribution: "gaussian" + distribution_parameters: [0, 0.015] + on_interval: + frequency_interval: 1 + operation: "additive" + distribution: "gaussian" + distribution_parameters: [0., 0.05] + + +Launching Training +~~~~~~~~~~~~~~~~~~ + +To launch a training in Isaac Lab, use the command: + +.. code-block:: bash + + python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 --headless + +Launching Inferencing +~~~~~~~~~~~~~~~~~~~~~ + +To launch inferencing in Isaac Lab, use the command: + +.. code-block:: bash + + python source/standalone/workflows/rl_games/play.py --task=Isaac-Cartpole-Direct-v0 --num_envs=25 --checkpoint= diff --git a/docs/source/migration/migrating_from_orbit.rst b/docs/source/migration/migrating_from_orbit.rst new file mode 100644 index 0000000000..883391681c --- /dev/null +++ b/docs/source/migration/migrating_from_orbit.rst @@ -0,0 +1,127 @@ +.. _migrating-from-orbit: + +Migrating from Orbit +==================== + +.. currentmodule:: omni.isaac.lab + +Since Orbit was used as basis for Isaac Lab, migrating from Orbit to Isaac Lab is straightforward. +The following sections describe the changes that need to be made to your code to migrate from Orbit to Isaac Lab. + +Updates to scripts +~~~~~~~~~~~~~~~~~~ + +The script ``orbit.sh`` has been renamed to ``isaaclab.sh``. + + +Updates to extensions +~~~~~~~~~~~~~~~~~~~~~ + +The extensions ``omni.isaac.orbit``, ``omni.isaac.orbit_tasks``, and ``omni.isaac.orbit_assets`` have been renamed +to ``omni.isaac.lab``, ``omni.isaac.lab_tasks``, and ``omni.isaac.lab_assets``, respectively. Thus, the new folder structure looks like this: + +- ``source/extensions/omni.isaac.lab/omni/isaac/lab`` +- ``source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks`` +- ``source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets`` + +The high level imports have to be updated as well: + ++-------------------------------------+-----------------------------------+ +| Orbit | Isaac Lab | ++=====================================+===================================+ +| ``from omni.isaac.orbit...`` | ``from omni.isaac.lab...`` | ++-------------------------------------+-----------------------------------+ +| ``from omni.isaac.orbit_tasks...`` | ``from omni.isaac.lab_tasks...`` | ++-------------------------------------+-----------------------------------+ +| ``from omni.isaac.orbit_assets...`` | ``from omni.isaac.lab_assets...`` | ++-------------------------------------+-----------------------------------+ + + +Updates to class names +~~~~~~~~~~~~~~~~~~~~~~ + +In Isaac Lab, we introduced the concept of task design workflows (see :ref:`feature-workflows`). The Orbit code is using +the manager-based workflow and the environment specific class names have been updated to reflect this change: + ++------------------------+---------------------------------------------------------+ +| Orbit | Isaac Lab | ++========================+=========================================================+ +| ``BaseEnv`` | :class:`omni.isaac.lab.envs.ManagerBasedEnv` | ++------------------------+---------------------------------------------------------+ +| ``BaseEnvCfg`` | :class:`omni.isaac.lab.envs.ManagerBasedEnvCfg` | ++------------------------+---------------------------------------------------------+ +| ``RLTaskEnv`` | :class:`omni.isaac.lab.envs.ManagerBasedRLEnv` | ++------------------------+---------------------------------------------------------+ +| ``RLTaskEnvCfg`` | :class:`omni.isaac.lab.envs.ManagerBasedRLEnvCfg` | ++------------------------+---------------------------------------------------------+ +| ``RLTaskEnvWindow`` | :class:`omni.isaac.lab.envs.ui.ManagerBasedRLEnvWindow` | ++------------------------+---------------------------------------------------------+ + + +Updates to the tasks folder structure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To support the manager-based and direct workflows, we have added two folders in the tasks extension: + +- ``source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based`` +- ``source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct`` + +The tasks from Orbit can now be found under the ``manager_based`` folder. +This change must also be reflected in the imports for your tasks. For example, + +.. code-block:: python + + from omni.isaac.orbit_tasks.locomotion.velocity.velocity_env_cfg ... + +should now be + +.. code-block:: python + + from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg ... + + +Other Breaking changes +~~~~~~~~~~~~~~~~~~~~~~ + +Offscreen rendering +------------------- + +The input argument ``--offscreen_render`` given to :class:`omni.isaac.lab.app.AppLauncher` and the environment variable ``OFFSCREEN_RENDER`` +have been renamed to ``--enable_cameras`` and ``ENABLE_CAMERAS`` respectively. + + +Event term distribution configuration +------------------------------------- + +Some of the event functions in `events.py `_ +accepted a ``distribution`` parameter and a ``range`` to sample from. In an effort to support arbitrary distributions, +we have renamed the input argument ``AAA_range`` to ``AAA_distribution_params`` for these functions. +Therefore, event term configurations whose functions have a ``distribution`` argument should be updated. For example, + +.. code-block:: python + :emphasize-lines: 6 + + add_base_mass = EventTerm( + func=mdp.randomize_rigid_body_mass, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names="base"), + "mass_range": (-5.0, 5.0), + "operation": "add", + }, + ) + +should now be + +.. code-block:: python + :emphasize-lines: 6 + + add_base_mass = EventTerm( + func=mdp.randomize_rigid_body_mass, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names="base"), + "mass_distribution_params": (-5.0, 5.0), + "operation": "add", + }, + ) diff --git a/docs/source/refs/issues.rst b/docs/source/refs/issues.rst index 1ea785b0aa..4a760e6d85 100644 --- a/docs/source/refs/issues.rst +++ b/docs/source/refs/issues.rst @@ -54,6 +54,16 @@ environment stepping commences. For more information, please refer to the `PhysX Determinism documentation`_. +In addition, due to floating point precision, states across different environments in the simulation +may be non-deterministic when the same set of actions are applied to the same initial +states. This occurs as environments are placed further apart from the world origin at (0, 0, 0). +As actors get placed at different origins in the world, floating point errors may build up +and result in slight variance in results even when starting from the same initial states. One +possible workaround for this issue is to place all actors/environments at the world origin +at (0, 0, 0) and filter out collisions between the environments. Note that this may induce +a performance degradation of around 15-50%, depending on the complexity of actors and +environment. + Blank initial frames from the camera ------------------------------------ @@ -90,3 +100,18 @@ are stored in the instanceable asset's USD file and not in its stage reference's .. _instanceable assets: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_gym_instanceable_assets.html .. _Omniverse Isaac Sim documentation: https://docs.omniverse.nvidia.com/isaacsim/latest/known_issues.html .. _PhysX Determinism documentation: https://nvidia-omniverse.github.io/PhysX/physx/5.3.1/docs/BestPractices.html#determinism + + +Exiting the process +------------------- + +When exiting a process with ``Ctrl+C``, occasionally the below error may appear: + +.. code-block:: bash + + [Error] [omni.physx.plugin] Subscribtion cannot be changed during the event call. + +This is due to the termination occurring in the middle of a physics event call and +should not affect the functionality of Isaac Lab. It is safe to ignore the error +message and continue with terminating the process. On Windows systems, please use +``Ctrl+Break`` or ``Ctrl+fn+B`` to terminate the process. diff --git a/docs/source/setup/developer.rst b/docs/source/setup/developer.rst index 21d28a442e..3a8d2e6c4f 100644 --- a/docs/source/setup/developer.rst +++ b/docs/source/setup/developer.rst @@ -30,7 +30,7 @@ and include the following files: To setup the IDE, please follow these instructions: -1. Open the ``IsaacLab`` directory on Visual Studio Code IDE +1. Open the ``Isaac Lab`` directory on Visual Studio Code IDE 2. Run VSCode `Tasks `__, by pressing ``Ctrl+Shift+P``, selecting ``Tasks: Run Task`` and running the ``setup_python_env`` in the drop down menu. diff --git a/docs/source/setup/faq.rst b/docs/source/setup/faq.rst index 207701db9e..d403215b2a 100644 --- a/docs/source/setup/faq.rst +++ b/docs/source/setup/faq.rst @@ -28,6 +28,8 @@ Isaac Sim. that aims to unite complex 3D workflows. Isaac Sim leverages the latest advances in graphics and physics simulation to provide a high-fidelity simulation environment for robotics. It supports ROS/ROS2, various sensor simulation, tools for domain randomization and synthetic data creation. +Tiled rendering support in Isaac Sim allows for vectorized rendering across environments, along with +support for running in the cloud using `Isaac Automator`_. Overall, it is a powerful tool for roboticists and is a huge step forward in the field of robotics simulation. @@ -38,7 +40,7 @@ a starting point to understand what is possible with the simulators for robot le can be used for benchmarking but are not designed for developing and testing custom environments and algorithms. This is where Isaac Lab comes in. -Isaac Lab :cite:`mittal2023orbit` is built on top of Isaac Sim to provide a unified and flexible framework +Isaac Lab is built on top of Isaac Sim to provide a unified and flexible framework for robot learning that exploits latest simulation technologies. It is designed to be modular and extensible, and aims to simplify common workflows in robotics research (such as RL, learning from demonstrations, and motion planning). While it includes some pre-built environments, sensors, and tasks, its main goal is to @@ -47,6 +49,10 @@ and robot learning algorithms. It not only inherits the capabilities of Isaac Si of new features that pertain to robot learning research. For example, including actuator dynamics in the simulation, procedural terrain generation, and support to collect data from human demonstrations. +Isaac Lab replaces the previous `IsaacGymEnvs`_, `OmniIsaacGymEnvs`_ and `Orbit`_ frameworks and will +be the single robot learning framework for Isaac Sim. Previously released frameworks are deprecated +and we encourage users to follow our `migration guides`_ to transition over to Isaac Lab. + Why should I use Isaac Lab? --------------------------- @@ -76,3 +82,6 @@ to Isaac Lab, please reach out to us. .. _Isaac Gym: https://developer.nvidia.com/isaac-gym .. _IsaacGymEnvs: https://github.com/NVIDIA-Omniverse/IsaacGymEnvs .. _OmniIsaacGymEnvs: https://github.com/NVIDIA-Omniverse/OmniIsaacGymEnvs +.. _Orbit: https://isaac-orbit.github.io/orbit +.. _Isaac Automator: https://github.com/isaac-sim/IsaacAutomator +.. _migration guides: ../migration/index.html diff --git a/docs/source/setup/installation.rst b/docs/source/setup/installation.rst deleted file mode 100644 index 2d840a4c51..0000000000 --- a/docs/source/setup/installation.rst +++ /dev/null @@ -1,266 +0,0 @@ -Installation Guide -=================== - -.. image:: https://img.shields.io/badge/IsaacSim-2023.1.1-silver.svg - :target: https://developer.nvidia.com/isaac-sim - :alt: IsaacSim 2023.1.1 - -.. image:: https://img.shields.io/badge/python-3.10-blue.svg - :target: https://www.python.org/downloads/release/python-31013/ - :alt: Python 3.10 - -.. image:: https://img.shields.io/badge/platform-linux--64-orange.svg - :target: https://releases.ubuntu.com/20.04/ - :alt: Ubuntu 20.04 - - -Installing Isaac Sim --------------------- - - -.. caution:: - - We have dropped support for Isaac Sim versions 2023.1.0 and below. We recommend using the latest - Isaac Sim 2023.1.1 release. - - For more information, please refer to the - `Isaac Sim release notes `__. - -Downloading pre-built binaries -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Please follow the Isaac Sim -`documentation `__ -to install the latest Isaac Sim release. - -To check the minimum system requirements,refer to the documentation -`here `__. - -.. note:: - We have tested Isaac Lab with Isaac Sim 2023.1.1 release on Ubuntu - 20.04LTS with NVIDIA driver 525.147. - -Configuring the environment variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Isaac Sim is shipped with its own Python interpreter which bundles in -the extensions released with it. To simplify the setup, we recommend -using the same Python interpreter. Alternately, it is possible to setup -a virtual environment following the instructions -`here `__. - -Please locate the `Python executable in Isaac -Sim `__ -by navigating to Isaac Sim root folder. In the remaining of the -documentation, we will refer to its path as ``ISAACSIM_PYTHON_EXE``. - -.. note:: - - On Linux systems, by default, this should be the executable ``python.sh`` in the directory - ``${HOME}/.local/share/ov/pkg/isaac_sim-*``, with ``*`` corresponding to the Isaac Sim version. - -To avoid the overhead of finding and locating the Isaac Sim installation -directory every time, we recommend exporting the following environment -variables to your terminal for the remaining of the installation instructions: - -.. code:: bash - - # Isaac Sim root directory - export ISAACSIM_PATH="${HOME}/.local/share/ov/pkg/isaac_sim-2023.1.0-hotfix.1" - # Isaac Sim python executable - export ISAACSIM_PYTHON_EXE="${ISAACSIM_PATH}/python.sh" - -For more information on common paths, please check the Isaac Sim -`documentation `__. - -Running the simulator -~~~~~~~~~~~~~~~~~~~~~ - -Once Isaac Sim is installed successfully, make sure that the simulator runs on your -system. For this, we encourage the user to try some of the introductory -tutorials on their `website `__. - -For completeness, we specify the commands here to check that everything is configured correctly. -On a new terminal (**Ctrl+Alt+T**), run the following: - -- Check that the simulator runs as expected: - - .. code:: bash - - # note: you can pass the argument "--help" to see all arguments possible. - ${ISAACSIM_PATH}/isaac-sim.sh - -- Check that the simulator runs from a standalone python script: - - .. code:: bash - - # checks that python path is set correctly - ${ISAACSIM_PYTHON_EXE} -c "print('Isaac Sim configuration is now complete.')" - # checks that Isaac Sim can be launched from python - ${ISAACSIM_PYTHON_EXE} ${ISAACSIM_PATH}/standalone_examples/api/omni.isaac.core/add_cubes.py - -.. attention:: - - If you have been using a previous version of Isaac Sim, you - need to run the following command for the *first* time after - installation to remove all the old user data and cached variables: - - .. code:: bash - - ${ISAACSIM_PATH}/isaac-sim.sh --reset-user - -If the simulator does not run or crashes while following the above -instructions, it means that something is incorrectly configured. To -debug and troubleshoot, please check Isaac Sim -`documentation `__ -and the -`forums `__. - - -Installing Isaac Lab --------------------- - -Organizing the workspace -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. note:: - - We recommend making a `fork `_ of the ``Isaac Lab`` repository to contribute - to the project. This is not mandatory to use the framework. If you - make a fork, please replace ``isaac-sim`` with your username - in the following instructions. - - If you are not familiar with git, we recommend following the `git - tutorial `__. - -- Clone the ``Isaac Lab`` repository into your workspace: - - .. code:: bash - - # Option 1: With SSH - git clone git@github.com:isaac-sim/IsaacLab.git - # Option 2: With HTTPS - git clone https://github.com/isaac-sim/IsaacLab.git - -- Set up a symbolic link between the installed Isaac Sim root folder - and ``_isaac_sim`` in the ``IsaacLab``` directory. This makes it convenient - to index the python modules and look for extensions shipped with - Isaac Sim. - - .. code:: bash - - # enter the cloned repository - cd IsaacLab - # create a symbolic link - ln -s ${ISAACSIM_PATH} _isaac_sim - -We provide a helper executable `isaaclab.sh `_ that provides -utilities to manage extensions: - -.. code:: text - - ./isaaclab.sh --help - - usage: isaaclab.sh [-h] [-i] [-e] [-f] [-p] [-s] [-t] [-o] [-v] [-d] [-c] -- Utility to manage Isaac Lab. - - optional arguments: - -h, --help Display the help content. - -i, --install Install the extensions inside Isaac Lab. - -e, --extra [LIB] Install learning frameworks (rl_games, rsl_rl, sb3) as extra dependencies. Default is 'all'. - -f, --format Run pre-commit to format the code and check lints. - -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). - -s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim. - -t, --test Run all python unittest tests. - -o, --docker Run the docker container helper script (docker/container.sh). - -v, --vscode Generate the VSCode settings file from template. - -d, --docs Build the documentation from source using sphinx. - -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. - -Setting up the environment -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. attention:: - This step is optional. If you are using the bundled python with Isaac Sim, you can skip this step. - -The executable ``isaaclab.sh`` automatically fetches the python bundled with Isaac -Sim, using ``./isaaclab.sh -p`` command (unless inside a virtual environment). This executable -behaves like a python executable, and can be used to run any python script or -module with the simulator. For more information, please refer to the -`documentation `__. - -Although using a virtual environment is optional, we recommend using ``conda``. To install -``conda``, please follow the instructions `here `__. -In case you want to use ``conda`` to create a virtual environment, you can -use the following command: - -.. code:: bash - - # Option 1: Default name for conda environment is 'isaaclab' - ./isaaclab.sh --conda # or "./isaaclab.sh -c" - # Option 2: Custom name for conda environment - ./isaaclab.sh --conda my_env # or "./isaaclab.sh -c my_env" - -If you are using ``conda`` to create a virtual environment, make sure to -activate the environment before running any scripts. For example: - -.. code:: bash - - conda activate isaaclab # or "conda activate my_env" - -Once you are in the virtual environment, you do not need to use ``./isaaclab.sh -p`` -to run python scripts. You can use the default python executable in your environment -by running ``python`` or ``python3``. However, for the rest of the documentation, -we will assume that you are using ``./isaaclab.sh -p`` to run python scripts. This command -is equivalent to running ``python`` or ``python3`` in your virtual environment. - -Building extensions -~~~~~~~~~~~~~~~~~~~ - -To build all the extensions, run the following commands: - -- Install dependencies using ``apt`` (on Ubuntu): - - .. code:: bash - - sudo apt install cmake build-essential - -- Run the install command that iterates over all the extensions in - ``source/extensions`` directory and installs them using pip - (with ``--editable`` flag): - - .. code:: bash - - ./isaaclab.sh --install # or "./isaaclab.sh -i" - -- For installing all other dependencies (such as learning - frameworks), execute: - - .. code:: bash - - # Option 1: Install all dependencies - ./isaaclab.sh --extra # or "./isaaclab.sh -e" - # Option 2: Install only a subset of dependencies - # note: valid options are 'rl_games', 'rsl_rl', 'sb3', 'robomimic', 'all' - ./isaaclab.sh --extra rsl_rl # or "./isaaclab.sh -e rsl_r" - - -Verifying the installation -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To verify that the installation was successful, run the following command from the -top of the repository: - -.. code:: bash - - # Option 1: Using the isaaclab.sh executable - # note: this works for both the bundled python and the virtual environment - ./isaaclab.sh -p source/standalone/tutorials/00_sim/create_empty.py - - # Option 2: Using python in your virtual environment - python source/standalone/tutorials/00_sim/create_empty.py - -The above command should launch the simulator and display a window with a black -ground plane. You can exit the script by pressing ``Ctrl+C`` on your terminal or -by pressing the ``STOP`` button on the simulator window. - -If you see this, then the installation was successful! |:tada:| diff --git a/docs/source/setup/installation/binaries_installation.rst b/docs/source/setup/installation/binaries_installation.rst new file mode 100644 index 0000000000..3f34282ae5 --- /dev/null +++ b/docs/source/setup/installation/binaries_installation.rst @@ -0,0 +1,230 @@ +Installation using Isaac Sim Binaries +===================================== + + +Installing Isaac Sim +-------------------- + +Downloading pre-built binaries +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Please follow the Isaac Sim +`documentation `__ +to install the latest Isaac Sim release. + +To check the minimum system requirements,refer to the documentation +`here `__. + +.. note:: + We have tested Isaac Lab with Isaac Sim 4.0 release on Ubuntu + 20.04LTS with NVIDIA driver 525.147. + + .. tabs:: + + .. tab:: Linux + + On Linux systems, by default, Isaac Sim is installed in the directory + ``${HOME}/.local/share/ov/pkg/isaac_sim-*``, with ``*`` corresponding to the Isaac Sim version. + + .. tab:: Windows + + On Windows systems, by default,Isaac Sim is installed in the directory + ``C:\Users\user\AppData\Local\ov\pkg\isaac_sim-*``, with ``*`` corresponding to the Isaac Sim version. + + +Installing Isaac Lab +-------------------- + +Cloning Isaac Lab +~~~~~~~~~~~~~~~~~ + +.. note:: + + We recommend making a `fork `_ of the Isaac Lab repository to contribute + to the project but this is not mandatory to use the framework. If you + make a fork, please replace ``isaac-sim`` with your username + in the following instructions. + +Clone the Isaac Lab repository into your workspace: + +.. code:: bash + + # Option 1: With SSH + git clone git@github.com:isaac-sim/IsaacLab.git + # Option 2: With HTTPS + git clone https://github.com/isaac-sim/IsaacLab.git + + +.. note:: + We provide a helper executable `isaaclab.sh `_ that provides + utilities to manage extensions: + + .. tabs:: + + .. tab:: Linux + + .. code:: text + + ./isaaclab.sh --help + + usage: isaaclab.sh [-h] [-i] [-f] [-p] [-s] [-t] [-o] [-v] [-d] [-c] -- Utility to manage Isaac Lab. + + optional arguments: + -h, --help Display the help content. + -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl-games, rsl-rl, sb3, skrl) as extra dependencies. Default is 'all'. + -f, --format Run pre-commit to format the code and check lints. + -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). + -s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim. + -t, --test Run all python unittest tests. + -o, --docker Run the docker container helper script (docker/container.sh). + -v, --vscode Generate the VSCode settings file from template. + -d, --docs Build the documentation from source using sphinx. + -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. + + .. tab:: Windows + + .. code:: text + + isaaclab.bat --help + + usage: isaaclab.bat [-h] [-i] [-f] [-p] [-s] [-v] [-d] [-c] -- Utility to manage Isaac Lab. + + optional arguments: + -h, --help Display the help content. + -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl-games, rsl-rl, sb3, skrl) as extra dependencies. Default is 'all'. + -f, --format Run pre-commit to format the code and check lints. + -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). + -s, --sim Run the simulator executable (isaac-sim.bat) provided by Isaac Sim. + -t, --test Run all python unittest tests. + -v, --vscode Generate the VSCode settings file from template. + -d, --docs Build the documentation from source using sphinx. + -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. + + +Creating the Isaac Sim Symbolic Link +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Set up a symbolic link between the installed Isaac Sim root folder +and ``_isaac_sim`` in the Isaac Lab directory. This makes it convenient +to index the python modules and look for extensions shipped with Isaac Sim. + +.. tabs:: + + .. tab:: Linux + + .. code:: bash + + # enter the cloned repository + cd IsaacLab + # create a symbolic link + ln -s path_to_isaac_sim _isaac_sim + # For example: ln -s /home/nvidia/.local/share/ov/pkg/isaac-sim-4.0.0 _isaac_sim + + .. tab:: Windows + + .. code:: batch + + :: enter the cloned repository + cd IsaacLab + :: create a symbolic link - requires launching Command Prompt with Administrator access + mklink /D _isaac_sim path_to_isaac_sim + # For example: mklink /D _isaac_sim C:/Users/nvidia/AppData/Local/ov/pkg/isaac-sim-4.0.0 + + +Setting up the conda environment (optional) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attention:: + This step is optional. If you are using the bundled python with Isaac Sim, you can skip this step. + +The executable ``isaaclab.sh`` automatically fetches the python bundled with Isaac +Sim, using ``./isaaclab.sh -p`` command (unless inside a virtual environment). This executable +behaves like a python executable, and can be used to run any python script or +module with the simulator. For more information, please refer to the +`documentation `__. + +Although using a virtual environment is optional, we recommend using ``conda``. To install +``conda``, please follow the instructions `here `__. +In case you want to use ``conda`` to create a virtual environment, you can +use the following command: + +.. tabs:: + + .. tab:: Linux + + .. code:: bash + + # Option 1: Default name for conda environment is 'isaaclab' + ./isaaclab.sh --conda # or "./isaaclab.sh -c" + # Option 2: Custom name for conda environment + ./isaaclab.sh --conda my_env # or "./isaaclab.sh -c my_env" + + .. tab:: Windows + + .. code:: batch + + :: Option 1: Default name for conda environment is 'isaaclab' + isaaclab.bat --conda :: or "isaaclab.bat -c" + :: Option 2: Custom name for conda environment + isaaclab.bat --conda my_env :: or "isaaclab.bat -c my_env" + + +If you are using ``conda`` to create a virtual environment, make sure to +activate the environment before running any scripts. For example: + +.. code:: bash + + conda activate isaaclab # or "conda activate my_env" + +Once you are in the virtual environment, you do not need to use ``./isaaclab.sh -p`` / ``isaaclab.bat -p`` +to run python scripts. You can use the default python executable in your environment +by running ``python`` or ``python3``. However, for the rest of the documentation, +we will assume that you are using ``./isaaclab.sh -p`` / ``isaaclab.bat -p`` to run python scripts. This command +is equivalent to running ``python`` or ``python3`` in your virtual environment. + + +Installation +~~~~~~~~~~~~ + +- Install dependencies using ``apt`` (on Ubuntu): + + .. code:: bash + + sudo apt install cmake build-essential + +- Run the install command that iterates over all the extensions in ``source/extensions`` directory and installs them + using pip (with ``--editable`` flag): + +.. tabs:: + + .. tab:: Linux + + .. code:: bash + + ./isaaclab.sh --install # or "./isaaclab.sh -i" + + .. tab:: Windows + + .. code:: bash + + isaaclab.bat --install :: or "isaaclab.bat -i" + +.. note:: + By default, this will install all the learning frameworks. If you want to install only a specific framework, you can + pass the name of the framework as an argument. For example, to install only the ``rl_games`` framework, you can run + + .. tabs:: + + .. tab:: Linux + + .. code:: bash + + ./isaaclab.sh --install rl_games + + .. tab:: Windows + + .. code:: bash + + isaaclab.bat --install rl_games :: or "isaaclab.bat -i" + + The valid options are ``rl_games``, ``rsl_rl``, ``sb3``, ``skrl``, ``robomimic``, ``none``. diff --git a/docs/source/setup/installation/cloud_installation.rst b/docs/source/setup/installation/cloud_installation.rst new file mode 100644 index 0000000000..cf68f18131 --- /dev/null +++ b/docs/source/setup/installation/cloud_installation.rst @@ -0,0 +1,108 @@ +Running Isaac Lab in the Cloud +============================== + +Isaac Lab can be run in various cloud infrastructures with the use of `Isaac Automator `__. +Isaac Automator allows for quick deployment of Isaac Sim and Isaac Lab onto the public clouds (AWS, GCP, Azure, and Alibaba Cloud are currently supported). + +The result is a fully configured remote desktop cloud workstation, which can be used for development and testing of Isaac Lab within minutes and on a budget. Isaac Automator supports variety of GPU instances and stop-start functionality to save on cloud costs and a variety of tools to aid the workflow (like uploading and downloading data, autorun, deployment management, etc). + + +Installing Isaac Automator +-------------------------- + +To use Isaac Automator, first clone the repo: + +.. code-block:: bash + + git clone https://github.com/isaac-sim/IsaacAutomator.git + +Isaac Automator requires having ``docker`` pre-installed on the system. + +* To install Docker, please follow the instructions for your operating system on the `Docker website`_. +* Follow the post-installation steps for Docker on the `post-installation steps`_ page. These steps allow you to run + Docker without using ``sudo``. + +Isaac Automator also requires obtaining a NGC API key. + +* Get access to the `Isaac Sim container`_ by joining the NVIDIA Developer Program credentials. +* Generate your `NGC API key`_ to access locked container images from NVIDIA GPU Cloud (NGC). + + * This step requires you to create an NGC account if you do not already have one. + * Once you have your generated API key, you need to log in to NGC + from the terminal. + + .. code:: bash + + docker login nvcr.io + + * For the username, enter ``$oauthtoken`` exactly as shown. It is a special username that is used to + authenticate with NGC. + + .. code:: text + + Username: $oauthtoken + Password: + + +Running Isaac Automator +----------------------- + +To run Isaac Automator, first build the Isaac Automator container: + +.. code-block:: bash + + ./build + +Next, run the deployed script for your preferred cloud: + +.. code-block:: bash + + # AWS + ./deploy-aws + # Azure + ./deploy-azure + # GCP + ./deploy-gcp + # Alibaba Cloud + ./deploy-alicloud + +Follow the prompts for entering information regarding the environment setup and credentials. +Once successful, instructions for connecting to the cloud instance will be available in the terminal. +Connections can be made using SSH, noVCN, or NoMachine. + +For details on the credentials and setup required for each cloud, please visit the +`Isaac Automator `__ +page for more instructions. + + +Running Isaac Lab on the Cloud +------------------------------ + +Once connected to the cloud instance, the desktop will have an icon showing ``isaaclab.sh``. +Launch the ``isaaclab.sh`` executable, which will open a new Terminal. Within the terminal, +Isaac Lab commands can be executed in the same way as running locally. + +For example: + +.. code-block:: bash + + ./isaaclab.sh -p source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 + + +Destroying a Development +------------------------- + +To save costs, deployments can be destroyed when not being used. +This can be done from within the Automator container, which can be entered with command ``./run``. + +To destroy a deployment, run: + +.. code:: bash + + ./destroy + + +.. _`Docker website`: https://docs.docker.com/desktop/install/linux-install/ +.. _`post-installation steps`: https://docs.docker.com/engine/install/linux-postinstall/ +.. _`Isaac Sim container`: https://catalog.ngc.nvidia.com/orgs/nvidia/containers/isaac-sim +.. _`NGC API key`: https://docs.nvidia.com/ngc/gpu-cloud/ngc-user-guide/index.html#generating-api-key diff --git a/docs/source/setup/installation/index.rst b/docs/source/setup/installation/index.rst new file mode 100644 index 0000000000..074fe38598 --- /dev/null +++ b/docs/source/setup/installation/index.rst @@ -0,0 +1,45 @@ +Installation Guide +=================== + +.. image:: https://img.shields.io/badge/IsaacSim-4.0-silver.svg + :target: https://developer.nvidia.com/isaac-sim + :alt: IsaacSim 4.0 + +.. image:: https://img.shields.io/badge/python-3.10-blue.svg + :target: https://www.python.org/downloads/release/python-31013/ + :alt: Python 3.10 + +.. image:: https://img.shields.io/badge/platform-linux--64-orange.svg + :target: https://releases.ubuntu.com/20.04/ + :alt: Ubuntu 20.04 + +.. image:: https://img.shields.io/badge/platform-windows--64-orange.svg + :target: https://www.microsoft.com/en-ca/windows/windows-11 + :alt: Windows 11 + +.. caution:: + + We have dropped support for Isaac Sim versions 2023.1.0 and below. We recommend using the latest + Isaac Sim 4.0 release to benefit from the latest features and improvements. + + For more information, please refer to the + `Isaac Sim release notes `__. + +.. note:: + + We recommend system requirements with at least 32GB RAM and 16GB VRAM for Isaac Lab. + For the full list of system requirements for Isaac Sim, please refer to the + `Isaac Sim system requirements `_. + +As an experimental feature in Isaac Sim 4.0 release, Isaac Sim can also be installed through pip. +This simplifies the installation +process by avoiding the need to download the Omniverse Launcher and installing Isaac Sim through +the launcher. Therefore, there are two ways to install Isaac Lab: + +.. toctree:: + :maxdepth: 2 + + Installation using Isaac Sim pip (experimental) + binaries_installation + verifying_installation + cloud_installation diff --git a/docs/source/setup/installation/pip_installation.rst b/docs/source/setup/installation/pip_installation.rst new file mode 100644 index 0000000000..0a3bda2144 --- /dev/null +++ b/docs/source/setup/installation/pip_installation.rst @@ -0,0 +1,174 @@ +Installation using Isaac Sim pip +================================ + + +Installing Isaac Sim +-------------------- + +.. note:: + + Installing Isaac Sim from pip is currently an experimental feature. + If errors occur, please report them to the + `Isaac Sim Forums `_ + and install Isaac Sim from pre-built binaries. + + +- To use the pip installation approach for Isaac Sim, we recommend first creating a virtual environment. + Ensure that the python version of the virtual environment is **Python 3.10**. + + .. tabs:: + + .. tab:: Conda + + .. code-block:: bash + + conda create -n isaaclab python=3.10 + conda activate isaaclab + + .. tab:: Virtual environment (venv) + + .. code-block:: bash + + python3.10 -m venv isaaclab + # on Linux + source isaaclab/bin/activate + # on Windows + isaaclab\Scripts\activate + + +- Next, install a CUDA-enabled PyTorch 2.2.2 build based on the CUDA version available on your system. + + .. tabs:: + + .. tab:: CUDA 11 + + .. code-block:: bash + + pip install torch==2.2.2 --index-url https://download.pytorch.org/whl/cu118 + + .. tab:: CUDA 12 + + .. code-block:: bash + + pip install torch==2.2.2 --index-url https://download.pytorch.org/whl/cu121 + + +- Then, install the Isaac Sim packages necessary for running Isaac Lab: + + .. code-block:: bash + + pip install isaacsim-rl isaacsim-replicator --index-url https://pypi.nvidia.com/ + + +Installing Isaac Lab +-------------------- + +Cloning Isaac Lab +~~~~~~~~~~~~~~~~~ + +.. note:: + + We recommend making a `fork `_ of the Isaac Lab repository to contribute + to the project but this is not mandatory to use the framework. If you + make a fork, please replace ``isaac-sim`` with your username + in the following instructions. + +Clone the Isaac Lab repository into your workspace: + +.. code:: bash + + # Option 1: With SSH + git clone git@github.com:isaac-sim/IsaacLab.git + # Option 2: With HTTPS + git clone https://github.com/isaac-sim/IsaacLab.git + +.. note:: + We provide a helper executable `isaaclab.sh `_ that provides + utilities to manage extensions: + + .. tabs:: + + .. tab:: Linux + + .. code:: text + + ./isaaclab.sh --help + + usage: isaaclab.sh [-h] [-i] [-f] [-p] [-s] [-t] [-o] [-v] [-d] [-c] -- Utility to manage Isaac Lab. + + optional arguments: + -h, --help Display the help content. + -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl_games, rsl_rl, sb3, skrl) as extra dependencies. Default is 'all'. + -f, --format Run pre-commit to format the code and check lints. + -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). + -s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim. + -t, --test Run all python unittest tests. + -o, --docker Run the docker container helper script (docker/container.sh). + -v, --vscode Generate the VSCode settings file from template. + -d, --docs Build the documentation from source using sphinx. + -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. + + .. tab:: Windows + + .. code:: text + + isaaclab.bat --help + + usage: isaaclab.bat [-h] [-i] [-f] [-p] [-s] [-v] [-d] [-c] -- Utility to manage Isaac Lab. + + optional arguments: + -h, --help Display the help content. + -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl_games, rsl_rl, sb3, skrl) as extra dependencies. Default is 'all'. + -f, --format Run pre-commit to format the code and check lints. + -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). + -s, --sim Run the simulator executable (isaac-sim.bat) provided by Isaac Sim. + -t, --test Run all python unittest tests. + -v, --vscode Generate the VSCode settings file from template. + -d, --docs Build the documentation from source using sphinx. + -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. + +Installation +~~~~~~~~~~~~ + +- Install dependencies using ``apt`` (on Ubuntu): + + .. code:: bash + + sudo apt install cmake build-essential + +- Run the install command that iterates over all the extensions in ``source/extensions`` directory and installs them + using pip (with ``--editable`` flag): + +.. tabs:: + + .. tab:: Linux + + .. code:: bash + + ./isaaclab.sh --install # or "./isaaclab.sh -i" + + .. tab:: Windows + + .. code:: bash + + isaaclab.bat --install :: or "isaaclab.bat -i" + +.. note:: + By default, this will install all the learning frameworks. If you want to install only a specific framework, you can + pass the name of the framework as an argument. For example, to install only the ``rl_games`` framework, you can run + + .. tabs:: + + .. tab:: Linux + + .. code:: bash + + ./isaaclab.sh --install rl_games + + .. tab:: Windows + + .. code:: bash + + isaaclab.bat --install rl_games :: or "isaaclab.bat -i" + + The valid options are ``rl_games``, ``rsl_rl``, ``sb3``, ``skrl``, ``robomimic``, ``none``. diff --git a/docs/source/setup/installation/verifying_installation.rst b/docs/source/setup/installation/verifying_installation.rst new file mode 100644 index 0000000000..41565fae2a --- /dev/null +++ b/docs/source/setup/installation/verifying_installation.rst @@ -0,0 +1,195 @@ +Verifying the Installation +========================== + + +Verifying the Isaac Sim installation +------------------------------------ + +Isaac Sim installed from pip +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Make sure that your virtual environment is activated (if applicable) + + +- Check that the simulator runs as expected: + + .. code:: bash + + # note: you can pass the argument "--help" to see all arguments possible. + isaacsim + + By default, this will launch an empty mini Kit window. + +- To run with a specific experience file, run: + + .. code:: bash + + # experience files can be absolute path, or relative path searched in isaacsim/apps or omni/apps + isaacsim omni.isaac.sim.python.kit + + +.. attention:: + + When running Isaac Sim for the first time, all dependent extensions will be pulled from the registry. + This process can take upwards of 10 minutes and is required on the first run of each experience file. + Once the extensions are pulled, consecutive runs using the same experience file will use the cached extensions. + + In addition, the first run will prompt users to accept the Nvidia Omniverse License Agreement. + To accept the EULA, reply ``Yes`` when prompted with the below message: + + .. code:: bash + + By installing or using Isaac Sim, I agree to the terms of NVIDIA OMNIVERSE LICENSE AGREEMENT (EULA) + in https://docs.omniverse.nvidia.com/isaacsim/latest/common/NVIDIA_Omniverse_License_Agreement.html + + Do you accept the EULA? (Yes/No): Yes + + +If the simulator does not run or crashes while following the above +instructions, it means that something is incorrectly configured. To +debug and troubleshoot, please check Isaac Sim +`documentation `__ +and the +`forums `__. + + +Isaac Sim installed from binaries +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To avoid the overhead of finding and locating the Isaac Sim installation +directory every time, we recommend exporting the following environment +variables to your terminal for the remaining of the installation instructions: + +.. tabs:: + + .. tab:: Linux + + .. code:: bash + + # Isaac Sim root directory + export ISAACSIM_PATH="${HOME}/.local/share/ov/pkg/isaac_sim-4.0" + # Isaac Sim python executable + export ISAACSIM_PYTHON_EXE="${ISAACSIM_PATH}/python.sh" + + .. tab:: Windows + + .. code:: batch + + :: Isaac Sim root directory + set ISAACSIM_PATH="C:\Users\user\AppData\Local\ov\pkg\isaac_sim-4.0" + :: Isaac Sim python executable + set ISAACSIM_PYTHON_EXE="%ISAACSIM_PATH%\python.bat" + + +For more information on common paths, please check the Isaac Sim +`documentation `__. + + +- Check that the simulator runs as expected: + + .. tabs:: + + .. tab:: Linux + + .. code:: bash + + # note: you can pass the argument "--help" to see all arguments possible. + ${ISAACSIM_PATH}/isaac-sim.sh + + .. tab:: Windows + + .. code:: batch + + :: note: you can pass the argument "--help" to see all arguments possible. + %ISAACSIM_PATH%\isaac-sim.bat + + +- Check that the simulator runs from a standalone python script: + + .. tabs:: + + .. tab:: Linux + + .. code:: bash + + # checks that python path is set correctly + ${ISAACSIM_PYTHON_EXE} -c "print('Isaac Sim configuration is now complete.')" + # checks that Isaac Sim can be launched from python + ${ISAACSIM_PYTHON_EXE} ${ISAACSIM_PATH}/standalone_examples/api/omni.isaac.core/add_cubes.py + + .. tab:: Windows + + .. code:: batch + + :: checks that python path is set correctly + %ISAACSIM_PYTHON_EXE% -c "print('Isaac Sim configuration is now complete.')" + :: checks that Isaac Sim can be launched from python + %ISAACSIM_PYTHON_EXE% %ISAACSIM_PATH%\standalone_examples\api\omni.isaac.core\add_cubes.py + + +.. attention:: + + If you have been using a previous version of Isaac Sim, you + need to run the following command for the *first* time after + installation to remove all the old user data and cached variables: + + .. tabs:: + + .. tab:: Linux + + .. code:: bash + + ${ISAACSIM_PATH}/isaac-sim.sh --reset-user + + .. tab:: Windows + + .. code:: batch + + %ISAACSIM_PATH%\isaac-sim.bat --reset-user + + +If the simulator does not run or crashes while following the above +instructions, it means that something is incorrectly configured. To +debug and troubleshoot, please check Isaac Sim +`documentation `__ +and the +`forums `__. + + +Verifying the Isaac Lab installation +------------------------------------ + +To verify that the installation was successful, run the following command from the +top of the repository: + +.. tabs:: + + .. tab:: Linux + + .. code:: bash + + # Option 1: Using the isaaclab.sh executable + # note: this works for both the bundled python and the virtual environment + ./isaaclab.sh -p source/standalone/tutorials/00_sim/create_empty.py + + # Option 2: Using python in your virtual environment + python source/standalone/tutorials/00_sim/create_empty.py + + .. tab:: Windows + + .. code:: batch + + :: Option 1: Using the isaaclab.bat executable + :: note: this works for both the bundled python and the virtual environment + isaaclab.bat -p source\standalone\tutorials\00_sim\create_empty.py + + :: Option 2: Using python in your virtual environment + python source\standalone\tutorials\00_sim\create_empty.py + + +The above command should launch the simulator and display a window with a black +ground plane. You can exit the script by pressing ``Ctrl+C`` on your terminal. +On Windows machines, please terminate the process from Command Prompt using +``Ctrl+Break`` or ``Ctrl+fn+B``. + +If you see this, then the installation was successful! |:tada:| diff --git a/docs/source/setup/sample.rst b/docs/source/setup/sample.rst index 6b255b396b..19c721c188 100644 --- a/docs/source/setup/sample.rst +++ b/docs/source/setup/sample.rst @@ -141,8 +141,10 @@ format. .. code:: bash + # install the dependencies + sudo apt install cmake build-essential # install python module (for robomimic) - ./isaaclab.sh -e robomimic + ./isaaclab.sh -i robomimic # split data ./isaaclab.sh -p source/standalone//workflows/robomimic/tools/split_train_val.py logs/robomimic/Isaac-Lift-Cube-Franka-IK-Rel-v0/hdf_dataset.hdf5 --ratio 0.2 @@ -172,7 +174,7 @@ from the environments into the respective libraries function argument and return .. code:: bash # install python module (for stable-baselines3) - ./isaaclab.sh -e sb3 + ./isaaclab.sh -i sb3 # run script for training # note: we enable cpu flag since SB3 doesn't optimize for GPU anyway ./isaaclab.sh -p source/standalone/workflows/sb3/train.py --task Isaac-Cartpole-v0 --headless --cpu @@ -185,7 +187,7 @@ from the environments into the respective libraries function argument and return .. code:: bash # install python module (for skrl) - ./isaaclab.sh -e skrl + ./isaaclab.sh -i skrl # run script for training ./isaaclab.sh -p source/standalone/workflows/skrl/train.py --task Isaac-Reach-Franka-v0 --headless # run script for playing with 32 environments @@ -197,7 +199,7 @@ from the environments into the respective libraries function argument and return .. code:: bash # install python module (for rl-games) - ./isaaclab.sh -e rl_games + ./isaaclab.sh -i rl_games # run script for training ./isaaclab.sh -p source/standalone/workflows/rl_games/train.py --task Isaac-Ant-v0 --headless # run script for playing with 32 environments @@ -209,7 +211,7 @@ from the environments into the respective libraries function argument and return .. code:: bash # install python module (for rsl-rl) - ./isaaclab.sh -e rsl_rl + ./isaaclab.sh -i rsl_rl # run script for training ./isaaclab.sh -p source/standalone/workflows/rsl_rl/train.py --task Isaac-Reach-Franka-v0 --headless # run script for playing with 32 environments diff --git a/docs/source/tutorials/00_sim/launch_app.rst b/docs/source/tutorials/00_sim/launch_app.rst index e9c4ad1f07..b6aab2dd82 100644 --- a/docs/source/tutorials/00_sim/launch_app.rst +++ b/docs/source/tutorials/00_sim/launch_app.rst @@ -75,8 +75,8 @@ custom arguments and those from :class:`~app.AppLauncher`. [INFO] Using python from: /isaac-sim/python.sh [INFO][AppLauncher]: The argument 'width' will be used to configure the SimulationApp. [INFO][AppLauncher]: The argument 'height' will be used to configure the SimulationApp. - usage: launch_app.py [-h] [--size SIZE] [--width WIDTH] [--height HEIGHT] [--headless] [--livestream {0,1,2,3}] - [--offscreen_render] [--verbose] [--experience EXPERIENCE] + usage: launch_app.py [-h] [--size SIZE] [--width WIDTH] [--height HEIGHT] [--headless] [--livestream {0,1,2}] + [--enable_cameras] [--verbose] [--experience EXPERIENCE] Tutorial on running IsaacSim via the AppLauncher. @@ -88,9 +88,9 @@ custom arguments and those from :class:`~app.AppLauncher`. app_launcher arguments: --headless Force display off at all times. - --livestream {0,1,2,3} + --livestream {0,1,2} Force enable livestreaming. Mapping corresponds to that for the "LIVESTREAM" environment variable. - --offscreen_render Enable offscreen rendering when running without a GUI. + --enable_cameras Enable cameras when running without a GUI. --verbose Enable verbose terminal logging from the SimulationApp. --experience EXPERIENCE The experience file to load when launching the SimulationApp. @@ -123,7 +123,7 @@ In the case where these arguments are provided from the CLI, they will override as we will demonstrate later in this tutorial. These arguments can be used with any script that starts the simulation using :class:`~app.AppLauncher`, -with one exception, ``--offscreen_render``. This setting sets the rendering pipeline to use the +with one exception, ``--enable_cameras``. This setting sets the rendering pipeline to use the offscreen renderer. However, this setting is only compatible with the :class:`omni.isaac.lab.sim.SimulationContext`. It will not work with Isaac Sim's :class:`omni.isaac.core.simulation_context.SimulationContext` class. For more information on this flag, please see the :class:`~app.AppLauncher` API documentation. diff --git a/docs/source/tutorials/00_sim/spawn_prims.rst b/docs/source/tutorials/00_sim/spawn_prims.rst index 2c8128ddcf..e7e81616cd 100644 --- a/docs/source/tutorials/00_sim/spawn_prims.rst +++ b/docs/source/tutorials/00_sim/spawn_prims.rst @@ -46,7 +46,7 @@ A collection of these prims, with their attributes and relationships, is called as a container for all prims in a scene. When we say we are designing a scene, we are actually designing a USD stage. While working with direct USD APIs provides a lot of flexibility, it can be cumbersome to learn and use. To make it -easier to design scenes, Isaac Lab builds on top of the USD APIs to provide a configuration-drive interface to spawn prims +easier to design scenes, Isaac Lab builds on top of the USD APIs to provide a configuration-driven interface to spawn prims into a scene. These are included in the :mod:`sim.spawners` module. When spawning prims into the scene, each prim requires a configuration class instance that defines the prim's attributes diff --git a/docs/source/tutorials/03_envs/create_base_env.rst b/docs/source/tutorials/03_envs/create_base_env.rst index 8a8c61e863..4349d6d122 100644 --- a/docs/source/tutorials/03_envs/create_base_env.rst +++ b/docs/source/tutorials/03_envs/create_base_env.rst @@ -1,24 +1,25 @@ .. _tutorial-create-base-env: -Creating a Base Environment -=========================== +Creating a Manager-Based Base Environment +========================================= .. currentmodule:: omni.isaac.lab Environments bring together different aspects of the simulation such as the scene, observations and actions spaces, reset events etc. to create a -coherent interface for various applications. In Isaac Lab, environments are -implemented as :class:`envs.BaseEnv` and :class:`envs.RLTaskEnv` classes. -The two classes are very similar, but :class:`envs.RLTaskEnv` is useful for +coherent interface for various applications. In Isaac Lab, manager-based environments are +implemented as :class:`envs.ManagerBasedEnv` and :class:`envs.ManagerBasedRLEnv` classes. +The two classes are very similar, but :class:`envs.ManagerBasedRLEnv` is useful for reinforcement learning tasks and contains rewards, terminations, curriculum -and command generation. The :class:`envs.BaseEnv` class is useful for +and command generation. The :class:`envs.ManagerBasedEnv` class is useful for traditional robot control and doesn't contain rewards and terminations. -In this tutorial, we will look at the base class :class:`envs.BaseEnv` and its -corresponding configuration class :class:`envs.BaseEnvCfg`. We will use the +In this tutorial, we will look at the base class :class:`envs.ManagerBasedEnv` and its +corresponding configuration class :class:`envs.ManagerBasedEnvCfg` for the manager-based workflow. +We will use the cartpole environment from earlier to illustrate the different components -in creating a new :class:`envs.BaseEnv` environment. +in creating a new :class:`envs.ManagerBasedEnv` environment. The Code @@ -39,7 +40,7 @@ directory. The Code Explained ~~~~~~~~~~~~~~~~~~ -The base class :class:`envs.BaseEnv` wraps around many intricacies of the simulation interaction +The base class :class:`envs.ManagerBasedEnv` wraps around many intricacies of the simulation interaction and provides a simple interface for the user to run the simulation and interact with it. It is composed of the following components: @@ -51,7 +52,7 @@ is composed of the following components: By configuring these components, the user can create different variations of the same environment with minimal effort. In this tutorial, we will go through the different components of the -:class:`envs.BaseEnv` class and how to configure them to create a new environment. +:class:`envs.ManagerBasedEnv` class and how to configure them to create a new environment. Designing the scene ------------------- @@ -126,7 +127,7 @@ takes in the :attr:`managers.EventTermCfg.func` that specifies the function or c class that performs the event. Additionally, it expects the **mode** of the event. The mode specifies when the event term should be applied. -It is possible to specify your own mode. For this, you'll need to adapt the :class:`~envs.BaseEnv` class. +It is possible to specify your own mode. For this, you'll need to adapt the :class:`~envs.ManagerBasedEnv` class. However, out of the box, Isaac Lab provides three commonly used modes: * ``"startup"`` - Event that takes place only once at environment startup. @@ -145,13 +146,13 @@ Tying it all together --------------------- Having defined the scene and manager configurations, we can now define the environment configuration -through the :class:`envs.BaseEnvCfg` class. This class takes in the scene, action, observation and +through the :class:`envs.ManagerBasedEnvCfg` class. This class takes in the scene, action, observation and event configurations. -In addition to these, it also takes in the :attr:`envs.BaseEnvCfg.sim` which defines the simulation +In addition to these, it also takes in the :attr:`envs.ManagerBasedEnvCfg.sim` which defines the simulation parameters such as the timestep, gravity, etc. This is initialized to the default values, but can be modified as needed. We recommend doing so by defining the :meth:`__post_init__` method in the -:class:`envs.BaseEnvCfg` class, which is called after the configuration is initialized. +:class:`envs.ManagerBasedEnvCfg` class, which is called after the configuration is initialized. .. literalinclude:: ../../../../source/standalone/tutorials/03_envs/create_cartpole_base_env.py :language: python @@ -162,12 +163,12 @@ Running the simulation Lastly, we revisit the simulation execution loop. This is now much simpler since we have abstracted away most of the details into the environment configuration. We only need to -call the :meth:`envs.BaseEnv.reset` method to reset the environment and :meth:`envs.BaseEnv.step` +call the :meth:`envs.ManagerBasedEnv.reset` method to reset the environment and :meth:`envs.ManagerBasedEnv.step` method to step the environment. Both these functions return the observation and an info dictionary which may contain additional information provided by the environment. These can be used by an agent for decision-making. -The :class:`envs.BaseEnv` class does not have any notion of terminations since that concept is +The :class:`envs.ManagerBasedEnv` class does not have any notion of terminations since that concept is specific for episodic tasks. Thus, the user is responsible for defining the termination condition for the environment. In this tutorial, we reset the simulation at regular intervals. @@ -213,5 +214,5 @@ directory. For completeness, they can be run using the following commands: ./isaaclab.sh -p source/standalone/tutorials/03_envs/create_quadruped_base_env.py --num_envs 32 -In the following tutorial, we will look at the :class:`envs.RLTaskEnv` class and how to use it +In the following tutorial, we will look at the :class:`envs.ManagerBasedRLEnv` class and how to use it to create a Markovian Decision Process (MDP). diff --git a/docs/source/tutorials/03_envs/create_direct_rl_env.rst b/docs/source/tutorials/03_envs/create_direct_rl_env.rst new file mode 100644 index 0000000000..192bc7b684 --- /dev/null +++ b/docs/source/tutorials/03_envs/create_direct_rl_env.rst @@ -0,0 +1,334 @@ +.. _tutorial-create-oige-rl-env: + + +Creating a Direct Workflow RL Environment +========================================= + +.. currentmodule:: omni.isaac.lab + +In addition to the :class:`envs.ManagerBasedRLEnv` class, which encourages the use of configuration classes +for more modular environments, the :class:`~omni.isaac.lab.envs.DirectRLEnv` class allows for more direct control +in the scripting of environment. + +Instead of using Manager classes for defining rewards and observations, the direct workflow tasks +implement the full reward and observation functions directly in the task script. +This allows for more control in the implementation of the methods, such as using pytorch jit +features, and provides a less abstracted framework that makes it easier to find the various +pieces of code. + +In this tutorial, we will configure the cartpole environment using the direct workflow implementation to create a task +for balancing the pole upright. We will learn how to specify the task using by implementing functions +for scene creation, actions, resets, rewards and observations. + + +The Code +~~~~~~~~ + +For this tutorial, we use the cartpole environment defined in ``omni.isaac.lab_tasks.direct.cartpole`` module. + +.. dropdown:: Code for cartpole_env.py + :icon: code + + .. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py + :language: python + :linenos: + + +The Code Explained +~~~~~~~~~~~~~~~~~~ + +Similar to the manager-based environments, a configuration class is defined for the task to hold settings +for the simulation parameters, the scene, the actors, and the task. With the direct workflow implementation, +the :class:`envs.DirectRLEnvCfg` class is used as the base class for configurations. +Since the direct workflow implementation does not use Action and Observation managers, the task +config should define the number of actions and observations for the environment. + +.. code-block:: python + + @configclass + class CartpoleEnvCfg(DirectRLEnvCfg): + ... + num_actions = 1 + num_observations = 4 + num_states = 0 + +The config class can also be used to define task-specific attributes, such as scaling for reward terms +and thresholds for reset conditions. + +.. code-block:: python + + @configclass + class CartpoleEnvCfg(DirectRLEnvCfg): + ... + # reset + max_cart_pos = 3.0 + initial_pole_angle_range = [-0.25, 0.25] + + # 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 + +When creating a new environment, the code should define a new class that inherits from :class:`~omni.isaac.lab.envs.DirectRLEnv`. + +.. code-block:: python + + class CartpoleEnv(DirectRLEnv): + cfg: CartpoleEnvCfg + + def __init__(self, cfg: CartpoleEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) + +The class can also hold class variables that are accessible by all functions in the class, +including functions for applying actions, computing resets, rewards, and observations. + +Scene Creation +-------------- + +In contrast to manager-based environments where the scene creation is taken care of by the framework, +the direct workflow implementation provides flexibility for users to implement their own scene creation +function. This includes adding actors into the stage, cloning the environments, filtering collisions +between the environments, adding the actors into the scene, and adding any additional props to the +scene, such as ground plane and lights. These operations should be implemented in the +``_setup_scene(self)`` method. + +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py + :language: python + :pyobject: CartpoleEnv._setup_scene + +Defining Rewards +---------------- + +Reward function should be defined in the ``_get_rewards(self)`` API, which returns the reward +buffer as a return value. Within this function, the task is free to implement the logic of +the reward function. In this example, we implement a Pytorch jitted function that computes +the various components of the reward function. + +.. code-block:: python + + def _get_rewards(self) -> torch.Tensor: + total_reward = compute_rewards( + self.cfg.rew_scale_alive, + self.cfg.rew_scale_terminated, + self.cfg.rew_scale_pole_pos, + self.cfg.rew_scale_cart_vel, + self.cfg.rew_scale_pole_vel, + self.joint_pos[:, self._pole_dof_idx[0]], + self.joint_vel[:, self._pole_dof_idx[0]], + self.joint_pos[:, self._cart_dof_idx[0]], + self.joint_vel[:, self._cart_dof_idx[0]], + self.reset_terminated, + ) + return total_reward + + @torch.jit.script + def compute_rewards( + rew_scale_alive: float, + rew_scale_terminated: float, + rew_scale_pole_pos: float, + rew_scale_cart_vel: float, + rew_scale_pole_vel: float, + pole_pos: torch.Tensor, + pole_vel: torch.Tensor, + cart_pos: torch.Tensor, + cart_vel: torch.Tensor, + reset_terminated: torch.Tensor, + ): + rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) + rew_termination = rew_scale_terminated * reset_terminated.float() + rew_pole_pos = rew_scale_pole_pos * torch.sum(torch.square(pole_pos), dim=-1) + rew_cart_vel = rew_scale_cart_vel * torch.sum(torch.abs(cart_vel), dim=-1) + rew_pole_vel = rew_scale_pole_vel * torch.sum(torch.abs(pole_vel), dim=-1) + total_reward = rew_alive + rew_termination + rew_pole_pos + rew_cart_vel + rew_pole_vel + return total_reward + + +Defining Observations +--------------------- + +The observation buffer should be computed in the ``_get_observations(self)`` function, +which constructs the observation buffer for the environment. At the end of this API, +a dictionary should be returned that contains ``policy`` as the key, and the full +observation buffer as the value. For asymmetric policies, the dictionary should also +include the key ``critic`` and the states buffer as the value. + +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py + :language: python + :pyobject: CartpoleEnv._get_observations + +Computing Dones and Performing Resets +------------------------------------- + +Populating the ``dones`` buffer should be done in the ``_get_dones(self)`` method. +This method is free to implement logic that computes which environments would need to be reset +and which environments have reached the episode length limit. Both results should be +returned by the ``_get_dones(self)`` function, in the form of a tuple of boolean tensors. + +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py + :language: python + :pyobject: CartpoleEnv._get_dones + +Once the indices for environments requiring reset have been computed, the ``_reset_idx(self, env_ids)`` +function performs the reset operations on those environments. Within this function, new states +for the environments requiring reset should be set directly into simulation. + +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py + :language: python + :pyobject: CartpoleEnv._reset_idx + +Applying Actions +---------------- + +There are two APIs that are designed for working with actions. The ``_pre_physics_step(self, actions)`` takes in actions +from the policy as an argument and is called once per RL step, prior to taking any physics steps. This function can +be used to process the actions buffer from the policy and cache the data in a class variable for the environment. + +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py + :language: python + :pyobject: CartpoleEnv._pre_physics_step + +The ``_apply_action(self)`` API is called ``decimation`` number of times for each RL step, prior to taking +each physics step. This provides more flexibility for environments where actions should be applied +for each physics step. + +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py + :language: python + :pyobject: CartpoleEnv._apply_action + + +The Code Execution +~~~~~~~~~~~~~~~~~~ + +To run training for the direct workflow Cartpole environment, we can use the following command: + +.. code-block:: bash + + ./isaaclab.sh -p source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 + +All direct workflow tasks have the suffix ``-Direct`` added to the task name to differentiate the implementation style. + + +Domain Randomization +~~~~~~~~~~~~~~~~~~~~ + +In the direct workflow, domain randomization configuration uses the :class:`~omni.isaac.lab.utils.configclass` module +to specify a configuration class consisting of :class:`~managers.EventTermCfg` variables. + +Below is an example of a configuration class for domain randomization: + +.. code-block:: python + + @configclass + class EventCfg: + robot_physics_material = EventTerm( + func=mdp.randomize_rigid_body_material, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=".*"), + "static_friction_range": (0.7, 1.3), + "dynamic_friction_range": (1.0, 1.0), + "restitution_range": (1.0, 1.0), + "num_buckets": 250, + }, + ) + robot_joint_stiffness_and_damping = EventTerm( + func=mdp.randomize_actuator_gains, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), + "stiffness_distribution_params": (0.75, 1.5), + "damping_distribution_params": (0.3, 3.0), + "operation": "scale", + "distribution": "log_uniform", + }, + ) + reset_gravity = EventTerm( + func=mdp.randomize_physics_scene_gravity, + mode="interval", + is_global_time=True, + interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt) + params={ + "gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]), + "operation": "add", + "distribution": "gaussian", + }, + ) + +Each ``EventTerm`` object is of the :class:`~managers.EventTermCfg` class and takes in a ``func`` parameter +for specifying the function to call during randomization, a ``mode`` parameter, which can be ``startup``, +``reset`` or ``interval``. THe ``params`` dictionary should provide the necessary arguments to the +function that is specified in the ``func`` parameter. +Functions specified as ``func`` for the ``EventTerm`` can be found in the :class:`~envs.mdp.events` module. + +Note that as part of the ``"asset_cfg": SceneEntityCfg("robot", body_names=".*")`` parameter, the name of +the actor ``"robot"`` is provided, along with the body or joint names specified as a regex expression, +which will be the actors and bodies/joints that will have randomization applied. + +Once the ``configclass`` for the randomization terms have been set up, the class must be added +to the base config class for the task and be assigned to the variable ``events``. + +.. code-block:: python + + @configclass + class MyTaskConfig: + events: EventCfg = EventCfg() + + +Action and Observation Noise +---------------------------- + +Actions and observation noise can also be added using the :class:`~utils.configclass` module. +Action and observation noise configs must be added to the main task config using the +``action_noise_model`` and ``observation_noise_model`` variables: + +.. code-block:: python + + @configclass + class MyTaskConfig: + + # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset + action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), + ) + + # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset + observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), + ) + + +:class:`~.utils.noise.NoiseModelWithAdditiveBiasCfg` can be used to sample both uncorrelated noise +per step as well as correlated noise that is re-sampled at reset time. + +The ``noise_cfg`` term specifies the Gaussian distribution that will be sampled at each +step for all environments. This noise will be added to the corresponding actions and +observations buffers at every step. + +The ``bias_noise_cfg`` term specifies the Gaussian distribution for the correlated noise +that will be sampled at reset time for the environments being reset. The same noise +will be applied each step for the remaining of the episode for the environments and +resampled at the next reset. + +If only per-step noise is desired, :class:`~utils.noise.GaussianNoiseCfg` can be used +to specify an additive Gaussian distribution that adds the sampled noise to the input buffer. + +.. code-block:: python + + @configclass + class MyTaskConfig: + action_noise_model: GaussianNoiseCfg = GaussianNoiseCfg(mean=0.0, std=0.05, operation="add") + + + + +In this tutorial, we learnt how to create a direct workflow task environment for reinforcement learning. We do this +by extending the base environment to include the scene setup, actions, dones, reset, reward and observaion functions. + +While it is possible to manually create an instance of :class:`~omni.isaac.lab.envs.DirectRLEnv` class for a desired task, +this is not scalable as it requires specialized scripts for each task. Thus, we exploit the +:meth:`gymnasium.make` function to create the environment with the gym interface. We will learn how to do this +in the next tutorial. diff --git a/docs/source/tutorials/03_envs/create_rl_env.rst b/docs/source/tutorials/03_envs/create_rl_env.rst index 3f5d120037..c359bab76c 100644 --- a/docs/source/tutorials/03_envs/create_rl_env.rst +++ b/docs/source/tutorials/03_envs/create_rl_env.rst @@ -1,27 +1,27 @@ .. _tutorial-create-rl-env: -Creating an RL Environment -========================== +Creating a Manager-Based RL Environment +======================================= .. currentmodule:: omni.isaac.lab -Having learnt how to create a base environment in :ref:`tutorial-create-base-env`, we will now look at how to create a +Having learnt how to create a base environment in :ref:`tutorial-create-base-env`, we will now look at how to create a manager-based task environment for reinforcement learning. The base environment is designed as an sense-act environment where the agent can send commands to the environment and receive observations from the environment. This minimal interface is sufficient for many applications such as traditional motion planning and controls. However, many applications require a task-specification which often serves as the learning objective for the agent. For instance, in a navigation task, the agent may be required to -reach a goal location. To this end, we use the :class:`envs.RLTaskEnv` class which extends the base environment +reach a goal location. To this end, we use the :class:`envs.ManagerBasedRLEnv` class which extends the base environment to include a task specification. -Similar to other components in Isaac Lab, instead of directly modifying the base class :class:`RLTaskEnv`, we -encourage users to simply implement a configuration :class:`RLTaskEnvCfg` for their task environment. +Similar to other components in Isaac Lab, instead of directly modifying the base class :class:`envs.ManagerBasedRLEnv`, we +encourage users to simply implement a configuration :class:`envs.ManagerBasedRLEnvCfg` for their task environment. This practice allows us to separate the task specification from the environment implementation, making it easier to reuse components of the same environment for different tasks. -In this tutorial, we will configure the cartpole environment using the :class:`RLTaskEnvCfg` to create a task +In this tutorial, we will configure the cartpole environment using the :class:`envs.ManagerBasedRLEnvCfg` to create a manager-based task for balancing the pole upright. We will learn how to specify the task using reward terms, termination criteria, curriculum and commands. @@ -29,20 +29,20 @@ curriculum and commands. The Code ~~~~~~~~ -For this tutorial, we use the cartpole environment defined in ``omni.isaac.lab_tasks.classic.cartpole`` module. +For this tutorial, we use the cartpole environment defined in ``omni.isaac.lab_tasks.manager_based.classic.cartpole`` module. .. dropdown:: Code for cartpole_env_cfg.py :icon: code - .. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py + .. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py :language: python :emphasize-lines: 63-68, 124-149, 152-162, 165-169, 187-192 :linenos: The script for running the environment ``run_cartpole_rl_env.py`` is present in the -``source/standalone/tutorials/03_envs`` directory. The script is similar to the +``isaaclab/source/standalone/tutorials/03_envs`` directory. The script is similar to the ``cartpole_base_env.py`` script in the previous tutorial, except that it uses the -:class:`envs.RLTaskEnv` instead of the :class:`envs.BaseEnv`. +:class:`envs.ManagerBasedRLEnv` instead of the :class:`envs.ManagerBasedEnv`. .. dropdown:: Code for run_cartpole_rl_env.py :icon: code @@ -63,7 +63,7 @@ will focus only on the RL components of the environment. In Isaac Lab, we provide various implementations of different terms in the :mod:`envs.mdp` module. We will use some of these terms in this tutorial, but users are free to define their own terms as well. These are usually placed in their task-specific sub-package -(for instance, in :mod:`omni.isaac.lab_tasks.classic.cartpole.mdp`). +(for instance, in :mod:`omni.isaac.lab_tasks.manager_based.classic.cartpole.mdp`). Defining rewards @@ -83,7 +83,7 @@ For the cartpole task, we will use the following reward terms: * **Cart Velocity Reward**: Encourage the agent to keep the cart velocity as small as possible. * **Pole Velocity Reward**: Encourage the agent to keep the pole velocity as small as possible. -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py :language: python :pyobject: RewardsCfg @@ -106,7 +106,7 @@ The flag :attr:`managers.TerminationsCfg.time_out` specifies whether the term is or terminated term. These are used to indicate the two types of terminations as described in `Gymnasium's documentation `_. -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py :language: python :pyobject: TerminationsCfg @@ -121,7 +121,7 @@ For this simple task, we do not use any commands. This is specified by using a c :class:`envs.mdp.NullCommandCfg` configuration. However, you can see an example of command definitions in the locomotion or manipulation tasks. -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py :language: python :pyobject: CommandsCfg @@ -136,18 +136,18 @@ In this tutorial we don't implement a curriculum for simplicity, but you can see curriculum definition in the other locomotion or manipulation tasks. We use a simple pass-through curriculum to define a curriculum manager that does not modify the environment. -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py :language: python :pyobject: CurriculumCfg Tying it all together --------------------- -With all the above components defined, we can now create the :class:`RLTaskEnvCfg` configuration for the -cartpole environment. This is similar to the :class:`BaseEnvCfg` defined in :ref:`tutorial-create-base-env`, +With all the above components defined, we can now create the :class:`ManagerBasedRLEnvCfg` configuration for the +cartpole environment. This is similar to the :class:`ManagerBasedEnvCfg` defined in :ref:`tutorial-create-base-env`, only with the added RL components explained in the above sections. -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py :language: python :pyobject: CartpoleEnvCfg @@ -155,8 +155,8 @@ Running the simulation loop --------------------------- Coming back to the ``run_cartpole_rl_env.py`` script, the simulation loop is similar to the previous tutorial. -The only difference is that we create an instance of :class:`envs.RLTaskEnv` instead of the -:class:`envs.BaseEnv`. Consequently, now the :meth:`envs.RLTaskEnv.step` method returns additional signals +The only difference is that we create an instance of :class:`envs.ManagerBasedRLEnv` instead of the +:class:`envs.ManagerBasedEnv`. Consequently, now the :meth:`envs.ManagerBasedRLEnv.step` method returns additional signals such as the reward and termination status. The information dictionary also maintains logging of quantities such as the reward contribution from individual terms, the termination status of each term, the episode length etc. @@ -185,10 +185,10 @@ where you started the simulation. In this tutorial, we learnt how to create a task environment for reinforcement learning. We do this by extending the base environment to include the rewards, terminations, commands and curriculum terms. -We also learnt how to use the :class:`envs.RLTaskEnv` class to run the environment and receive various +We also learnt how to use the :class:`envs.ManagerBasedRLEnv` class to run the environment and receive various signals from it. -While it is possible to manually create an instance of :class:`envs.RLTaskEnv` class for a desired task, +While it is possible to manually create an instance of :class:`envs.ManagerBasedRLEnv` class for a desired task, this is not scalable as it requires specialized scripts for each task. Thus, we exploit the :meth:`gymnasium.make` function to create the environment with the gym interface. We will learn how to do this in the next tutorial. diff --git a/docs/source/tutorials/03_envs/index.rst b/docs/source/tutorials/03_envs/index.rst index de28430144..e4185044c7 100644 --- a/docs/source/tutorials/03_envs/index.rst +++ b/docs/source/tutorials/03_envs/index.rst @@ -1,8 +1,9 @@ Designing an Environment ======================== -The following tutorials introduce the concept of environments: :class:`~omni.isaac.lab.envs.BaseEnv` -and its derivative :class:`~omni.isaac.lab.envs.RLTaskEnv`. These environments bring-in together +The following tutorials introduce the concept of manager-based environments: :class:`~omni.isaac.lab.envs.ManagerBasedEnv` +and its derivative :class:`~omni.isaac.lab.envs.ManagerBasedRLEnv`, as well as the direct workflow base class +:class:`~omni.isaac.lab.envs.DirectRLEnv`. These environments bring-in together different aspects of the framework to create a simulation environment for agent interaction. .. toctree:: @@ -11,5 +12,6 @@ different aspects of the framework to create a simulation environment for agent create_base_env create_rl_env + create_direct_rl_env register_rl_env_gym run_rl_training diff --git a/docs/source/tutorials/03_envs/register_rl_env_gym.rst b/docs/source/tutorials/03_envs/register_rl_env_gym.rst index 675e066043..e30e70a734 100644 --- a/docs/source/tutorials/03_envs/register_rl_env_gym.rst +++ b/docs/source/tutorials/03_envs/register_rl_env_gym.rst @@ -13,7 +13,7 @@ class. .. literalinclude:: ../../../../source/standalone/tutorials/03_envs/run_cartpole_rl_env.py :language: python :start-at: # create environment configuration - :end-at: env = RLTaskEnv(cfg=env_cfg) + :end-at: env = ManagerBasedRLEnv(cfg=env_cfg) While straightforward, this approach is not scalable as we have a large suite of environments. In this tutorial, we will show how to use the :meth:`gymnasium.register` method to register @@ -46,21 +46,34 @@ The tutorial corresponds to the ``random_agent.py`` script in the ``source/stand The Code Explained ~~~~~~~~~~~~~~~~~~ -The :class:`envs.RLTaskEnv` class inherits from the :class:`gymnasium.Env` class to follow -a standard interface. However, unlike the traditional Gym environments, the :class:`envs.RLTaskEnv` +The :class:`envs.ManagerBasedRLEnv` class inherits from the :class:`gymnasium.Env` class to follow +a standard interface. However, unlike the traditional Gym environments, the :class:`envs.ManagerBasedRLEnv` implements a *vectorized* environment. This means that multiple environment instances are running simultaneously in the same process, and all the data is returned in a batched fashion. +Similarly, the :class:`envs.DirectRLEnv` class also inherits from the :class:`gymnasium.Env` class +for the direct workflow. + Using the gym registry ---------------------- To register an environment, we use the :meth:`gymnasium.register` method. This method takes in the environment name, the entry point to the environment class, and the entry point to the -environment configuration class. For the cartpole environment, the following shows the registration -call in the ``omni.isaac.lab_tasks.classic.cartpole`` sub-package: +environment configuration class. -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/__init__.py +.. note:: + The ``gymnasium`` registry is a global registry. Hence, it is important to ensure that the + environment names are unique. Otherwise, the registry will throw an error when registering + the environment. + +Manager-Based Environments +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For manager-based environments, the following shows the registration +call for the cartpole environment in the ``omni.isaac.lab_tasks.manager_based.classic.cartpole`` sub-package: + +.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/__init__.py :language: python :lines: 10- :emphasize-lines: 11, 12, 15 @@ -75,7 +88,56 @@ and difficult to read. The ``entry_point`` argument is the entry point to the environment class. The entry point is a string of the form ``:``. In the case of the cartpole environment, the entry point is -``omni.isaac.lab.envs:RLTaskEnv``. The entry point is used to import the environment class +``omni.isaac.lab.envs:ManagerBasedRLEnv``. The entry point is used to import the environment class +when creating the environment instance. + +The ``env_cfg_entry_point`` argument specifies the default configuration for the environment. The default +configuration is loaded using the :meth:`omni.isaac.lab_tasks.utils.parse_env_cfg` function. +It is then passed to the :meth:`gymnasium.make` function to create the environment instance. +The configuration entry point can be both a YAML file or a python configuration class. + +Direct Environemtns +^^^^^^^^^^^^^^^^^^^ + +For direct-based environments, the following shows the registration call for the cartpole environment +in the ``omni.isaac.lab_tasks.direct.cartpole`` sub-package: + +.. code-block:: python + + import gymnasium as gym + + from . import agents + from .cartpole_env import CartpoleEnv, CartpoleEnvCfg + + ## + # Register Gym environments. + ## + + gym.register( + id="Isaac-Cartpole-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.cartpole:CartpoleEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": CartpoleEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.CartpolePPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + "sb3_cfg_entry_point": f"{agents.__name__}:sb3_ppo_cfg.yaml", + }, + ) + +The ``id`` argument is the name of the environment. As a convention, we name all the environments +with the prefix ``Isaac-`` to make it easier to search for them in the registry. +For direct environments, we also add the suffix ``-Direct``. The name of the +environment is typically followed by the name of the task, and then the name of the robot. +For instance, for legged locomotion with ANYmal C on flat terrain, the environment is called +``Isaac-Velocity-Flat-Anymal-C-Direct-v0``. The version number ``v`` is typically used to specify different +variations of the same environment. Otherwise, the names of the environments can become too long +and difficult to read. + +The ``entry_point`` argument is the entry point to the environment class. The entry point is a string +of the form ``:``. In the case of the cartpole environment, the entry point is +``omni.isaac.lab_tasks.direct.cartpole:CartpoleEnv``. The entry point is used to import the environment class when creating the environment instance. The ``env_cfg_entry_point`` argument specifies the default configuration for the environment. The default @@ -83,10 +145,6 @@ configuration is loaded using the :meth:`omni.isaac.lab_tasks.utils.parse_env_cf It is then passed to the :meth:`gymnasium.make` function to create the environment instance. The configuration entry point can be both a YAML file or a python configuration class. -.. note:: - The ``gymnasium`` registry is a global registry. Hence, it is important to ensure that the - environment names are unique. Otherwise, the registry will throw an error when registering - the environment. Creating the environment ------------------------ diff --git a/docs/source/tutorials/03_envs/run_rl_training.rst b/docs/source/tutorials/03_envs/run_rl_training.rst index e73b1c6630..ce99d99413 100644 --- a/docs/source/tutorials/03_envs/run_rl_training.rst +++ b/docs/source/tutorials/03_envs/run_rl_training.rst @@ -7,7 +7,7 @@ In the previous tutorials, we covered how to define an RL task environment, regi it into the ``gym`` registry, and interact with it using a random agent. We now move on to the next step: training an RL agent to solve the task. -Although the :class:`envs.RLTaskEnv` conforms to the :class:`gymnasium.Env` interface, +Although the :class:`envs.ManagerBasedRLEnv` conforms to the :class:`gymnasium.Env` interface, it is not exactly a ``gym`` environment. The input and outputs of the environment are not numpy arrays, but rather based on torch tensors with the first dimension being the number of environment instances. @@ -15,8 +15,8 @@ number of environment instances. Additionally, most RL libraries expect their own variation of an environment interface. For example, `Stable-Baselines3`_ expects the environment to conform to its `VecEnv API`_ which expects a list of numpy arrays instead of a single tensor. Similarly, -`RSL-RL`_ and `RL-Games`_ expect a different interface. Since there is no one-size-fits-all -solution, we do not base the :class:`envs.RLTaskEnv` on any particular learning library. +`RSL-RL`_, `RL-Games`_ and `SKRL`_ expect a different interface. Since there is no one-size-fits-all +solution, we do not base the :class:`envs.ManagerBasedRLEnv` on any particular learning library. Instead, we implement wrappers to convert the environment into the expected interface. These are specified in the :mod:`omni.isaac.lab_tasks.utils.wrappers` module. @@ -93,13 +93,13 @@ Headless execution with off-screen render """"""""""""""""""""""""""""""""""""""""" Since the above command does not render the simulation, it is not possible to visualize the agent's -behavior during training. To visualize the agent's behavior, we pass the ``--offscreen_render`` which +behavior during training. To visualize the agent's behavior, we pass the ``--enable_cameras`` which enables off-screen rendering. Additionally, we pass the flag ``--video`` which records a video of the agent's behavior during training. .. code-block:: bash - ./isaaclab.sh -p source/standalone/workflows/sb3/train.py --task Isaac-Cartpole-v0 --num_envs 64 --headless --offscreen_render --video + ./isaaclab.sh -p source/standalone/workflows/sb3/train.py --task Isaac-Cartpole-v0 --num_envs 64 --headless --enable_cameras --video The videos are saved to the ``logs/sb3/Isaac-Cartpole-v0//videos`` directory. You can open these videos using any video player. @@ -151,3 +151,4 @@ directory. You can also specify a specific checkpoint by passing the ``--checkpo .. _`stable_baselines3.common.vec_env.VecNormalize`: https://stable-baselines3.readthedocs.io/en/master/guide/vec_envs.html#vecnormalize .. _RL-Games: https://github.com/Denys88/rl_games .. _RSL-RL: https://github.com/leggedrobotics/rsl_rl +.. _SKRL: https://skrl.readthedocs.io diff --git a/isaaclab.bat b/isaaclab.bat new file mode 100644 index 0000000000..129fb04f1f --- /dev/null +++ b/isaaclab.bat @@ -0,0 +1,519 @@ +@echo off +setlocal EnableExtensions EnableDelayedExpansion + +rem Copyright (c) 2022-2024, The Isaac Lab Project Developers. +rem All rights reserved. +rem +rem SPDX-License-Identifier: BSD-3-Clause + +rem Configurations +set "ISAACLAB_PATH=%~dp0" +goto main + +rem Helper functions + +rem extract the python from isaacsim +:extract_python_exe +rem Check if IsaacSim directory manually specified +rem Note: for manually build isaacsim, this: _build/linux-x86_64/release +if not "%ISAACSIM_PATH%"=="" ( + rem Use local build + set build_path=%ISAACSIM_PATH% +) else ( + rem Use TeamCity build + set build_path=%ISAACLAB_PATH%\_isaac_sim +) +rem check if using conda +if not "%CONDA_PREFIX%"=="" ( + rem use conda python + set python_exe=%CONDA_PREFIX%\python +) else ( + rem check if isaacsim is installed + pip show isaacsim-rl > nul 2>&1 + if errorlevel 1 ( + rem use python from kit if Isaac Sim not installed from pip + set python_exe=%build_path%\python.bat + ) else ( + rem use current python if Isaac Sim is installed from pip + set "python_exe=" + for /f "delims=" %%i in ('where python') do ( + if not defined python_exe ( + set "python_exe=%%i" + ) + ) + ) +) +rem check if there is a python path available +if "%python_exe%"=="" ( + echo [ERROR] No python executable found at path: %build_path% + exit /b 1 +) +goto :eof + + +rem extract the simulator exe from isaacsim +:extract_isaacsim_exe +rem Check if IsaacSim directory manually specified +rem Note: for manually build isaacsim, this: _build\linux-x86_64\release +if not "%ISAACSIM_PATH%"=="" ( + rem Use local build + set build_path=%ISAACSIM_PATH% +) else ( + rem Use TeamCity build + set build_path=%ISAACLAB_PATH%\_isaac_sim +) +rem python executable to use +set isaacsim_exe=%build_path%\isaac-sim.bat +rem check if there is a python path available +if not exist "%isaacsim_exe%" ( + echo [ERROR] No isaac-sim executable found at path: %build_path% + exit /b 1 +) +goto :eof + + +rem check if input directory is a python extension and install the module +:install_isaaclab_extension +echo %ext_folder% +rem retrieve the python executable +call :extract_python_exe +rem if the directory contains setup.py then install the python module +if exist "%ext_folder%\setup.py" ( + echo module: %ext_folder% + call !python_exe! -m pip install --editable %ext_folder% +) +goto :eof + + +rem setup anaconda environment for Isaac Lab +:setup_conda_env +rem get environment name from input +set env_name=%conda_env_name% +rem check if conda is installed +where conda >nul 2>nul +if errorlevel 1 ( + echo [ERROR] Conda could not be found. Please install conda and try again. + exit /b 1 +) +rem check if Isaac Sim directory manually specified +rem Note: for manually build Isaac Sim, this: _build\windows-x86_64\release +if not "%ISAACSIM_PATH%"=="" ( + rem Use local build + set "build_path=%ISAACSIM_PATH%" +) else ( + rem Use TeamCity build + set "build_path=%ISAACLAB_PATH%\_isaac_sim" +) +rem check if the environment exists +call conda env list | findstr /c:"%env_name%" >nul +if %errorlevel% equ 0 ( + echo [INFO] Conda environment named '%env_name%' already exists. +) else ( + echo [INFO] Creating conda environment named '%env_name%'... + call conda env create --name %env_name% -f %build_path%\environment.yml +) +rem cache current paths for later +set "cache_pythonpath=%PYTHONPATH%" +set "cache_ld_library_path=%LD_LIBRARY_PATH%" +rem clear any existing files +echo %CONDA_PREFIX% +del "%CONDA_PREFIX%\etc\conda\activate.d\setenv.bat" 2>nul +del "%CONDA_PREFIX%\etc\conda\deactivate.d\unsetenv.bat" 2>nul +rem activate the environment +call conda activate %env_name% +rem setup directories to load isaac-sim variables +mkdir "%CONDA_PREFIX%\etc\conda\activate.d" 2>nul +mkdir "%CONDA_PREFIX%\etc\conda\deactivate.d" 2>nul +rem add variables to environment during activation +( + echo @echo off + rem for isaac-sim + echo set CARB_APP_PATH=%build_path%\kit + echo set EXP_PATH=%build_path%\apps + echo set ISAAC_PATH=%build_path% + echo set PYTHONPATH=%PYTHONPATH%;%build_path%\site + echo set "RESOURCE_NAME=IsaacSim" + echo doskey isaaclab=isaaclab.bat $* +) > "%CONDA_PREFIX%\etc\conda\activate.d\env_vars.bat" +( + echo $env:CARB_APP_PATH="%build_path%\kit" + echo $env:EXP_PATH="%build_path%\apps" + echo $env:ISAAC_PATH="%build_path%" + echo $env:PYTHONPATH="%PYTHONPATH%;%build_path%\site" + echo $env:RESOURCE_NAME="IsaacSim" +) > "%CONDA_PREFIX%\etc\conda\activate.d\env_vars.ps1" + +rem reactivate the environment to load the variables +call conda activate %env_name% +rem remove variables from environment during deactivation +( + echo @echo off + echo rem for isaac-sim + echo set "CARB_APP_PATH=" + echo set "EXP_PATH=" + echo set "ISAAC_PATH=" + echo set "RESOURCE_NAME=" + echo doskey isaaclab = + echo. + echo rem restore paths + echo set "PYTHONPATH=%cache_pythonpath%" + echo set "LD_LIBRARY_PATH=%cache_ld_library_path%" +) > "%CONDA_PREFIX%\etc\conda\deactivate.d\unsetenv_vars.bat" +( + echo $env:CARB_APP_PATH="" + echo $env:EXP_PATH="" + echo $env:ISAAC_PATH="" + echo $env:RESOURCE_NAME="" + echo $env:PYTHONPATH="%cache_pythonpath%" + echo $env:LD_LIBRARY_PATH="%cache_pythonpath%" +) > "%CONDA_PREFIX%\etc\conda\deactivate.d\unsetenv_vars.ps1" + +rem install some extra dependencies +echo [INFO] Installing extra dependencies (this might take a few minutes)... +call conda install -c conda-forge -y importlib_metadata >nul 2>&1 +rem deactivate the environment +call conda deactivate +rem add information to the user about alias +echo [INFO] Added 'isaaclab' alias to conda environment for 'isaaclab.bat' script. +echo [INFO] Created conda environment named '%env_name%'. +echo. +echo 1. To activate the environment, run: conda activate %env_name% +echo 2. To install Isaac Lab extensions, run: isaaclab -i +echo 4. To perform formatting, run: isaaclab -f +echo 5. To deactivate the environment, run: conda deactivate +echo. +goto :eof + + +rem Update the vscode settings from template and Isaac Sim settings +:update_vscode_settings +echo [INFO] Setting up vscode settings... +rem Retrieve the python executable +call :extract_python_exe python_exe +rem Path to setup_vscode.py +set "setup_vscode_script=%ISAACLAB_PATH%\.vscode\tools\setup_vscode.py" +rem Check if the file exists before attempting to run it +if exist "%setup_vscode_script%" ( + call !python_exe! "%setup_vscode_script%" +) else ( + echo [WARNING] setup_vscode.py not found. Aborting vscode settings setup. +) +goto :eof + + +rem Print the usage description +:print_help +echo. +echo usage: %~nx0 [-h] [-i] [-f] [-p] [-s] [-v] [-d] [-c] -- Utility to manage extensions in Isaac Lab. +echo. +echo optional arguments: +echo -h, --help Display the help content. +echo -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks as extra dependencies. Default is 'all'. +echo -f, --format Run pre-commit to format the code and check lints. +echo -p, --python Run the python executable (python.bat) provided by Isaac Sim. +echo -s, --sim Run the simulator executable (isaac-sim.bat) provided by Isaac Sim. +echo -t, --test Run all python unittest tests. +echo -v, --vscode Generate the VSCode settings file from template. +echo -d, --docs Build the documentation from source using sphinx. +echo -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. +echo. +goto :eof + + +rem Main +:main + +rem check argument provided +if "%~1"=="" ( + echo [Error] No arguments provided. + call :print_help + exit /b 1 +) + +rem pass the arguments +:loop +if "%~1"=="" goto :end +set "arg=%~1" + +rem read the key +if "%arg%"=="-i" ( + rem install the python packages in omni.isaac.rl/source directory + echo [INFO] Installing extensions inside the Isaac Lab repository... + call :extract_python_exe + for /d %%d in ("%ISAACLAB_PATH%\source\extensions\*") do ( + set ext_folder="%%d" + call :install_isaaclab_extension + ) + call !python_exe! -m pip show isaacsim-rl > nul 2>&1 + rem if not installing from pip, set up VScode + if errorlevel 1 ( + rem setup vscode settings + call :update_vscode_settings + ) + rem install the python packages for supported reinforcement learning frameworks + echo [INFO] Installing extra requirements such as learning frameworks... + if "%~2"=="" ( + echo [INFO] Installing all rl-frameworks. + set framework_name=all + ) else if "%~2"=="none" ( + echo [INFO] No rl-framework will be installed. + set framework_name=none + shift + ) else ( + echo [INFO] Installing rl-framework: %2. + set framework_name=%2 + shift + ) + rem install the rl-frameworks specified + !python_exe! -m pip install -e %ISAACLAB_PATH%\source\extensions\omni.isaac.lab_tasks[!framework_name!] + shift +) else if "%arg%"=="--install" ( + rem install the python packages in omni.isaac.rl/source directory + echo [INFO] Installing extensions inside the Isaac Lab repository... + call :extract_python_exe + for /d %%d in ("%ISAACLAB_PATH%\source\extensions\*") do ( + set ext_folder="%%d" + call :install_isaaclab_extension + ) + call !python_exe! -m pip show isaacsim-rl > nul 2>&1 + rem if not installing from pip, set up VScode + if errorlevel 1 ( + rem setup vscode settings + call :update_vscode_settings + ) + rem install the python packages for supported reinforcement learning frameworks + echo [INFO] Installing extra requirements such as learning frameworks... + if "%~2"=="" ( + echo [INFO] Installing all rl-frameworks. + set framework_name=all + ) else if "%~2"=="none" ( + echo [INFO] No rl-framework will be installed. + set framework_name=none + shift + ) else ( + echo [INFO] Installing rl-framework: %2. + set framework_name=%2 + shift + ) + rem install the rl-frameworks specified + !python_exe! -m pip install -e %ISAACLAB_PATH%\source\extensions\omni.isaac.lab_tasks[!framework_name!] + shift +) else if "%arg%"=="-c" ( + rem use default name if not provided + if not "%~2"=="" ( + echo [INFO] Using conda environment name: %2 + set conda_env_name=%2 + shift + ) else ( + echo [INFO] Using default conda environment name: isaaclab + set conda_env_name=isaaclab + ) + call :setup_conda_env %conda_env_name% + shift +) else if "%arg%"=="--conda" ( + rem use default name if not provided + if not "%~2"=="" ( + echo [INFO] Using conda environment name: %2 + set conda_env_name=%2 + shift + ) else ( + echo [INFO] Using default conda environment name: isaaclab + set conda_env_name=isaaclab + ) + call :setup_conda_env %conda_env_name% + shift +) else if "%arg%"=="-f" ( + rem reset the python path to avoid conflicts with pre-commit + rem this is needed because the pre-commit hooks are installed in a separate virtual environment + rem and it uses the system python to run the hooks + if not "%CONDA_DEFAULT_ENV%"=="" ( + set cache_pythonpath=%PYTHONPATH% + set PYTHONPATH= + ) + + rem run the formatter over the repository + rem check if pre-commit is installed + pip show pre-commit > nul 2>&1 + if errorlevel 1 ( + echo [INFO] Installing pre-commit... + pip install pre-commit + ) + + rem always execute inside the Isaac Lab directory + echo [INFO] Formatting the repository... + pushd %ISAACLAB_PATH% + call python -m pre_commit run --all-files + popd >nul + + rem set the python path back to the original value + if not "%CONDA_DEFAULT_ENV%"=="" ( + set PYTHONPATH=%cache_pythonpath% + ) + goto :end +) else if "%arg%"=="--format" ( + rem reset the python path to avoid conflicts with pre-commit + rem this is needed because the pre-commit hooks are installed in a separate virtual environment + rem and it uses the system python to run the hooks + if not "%CONDA_DEFAULT_ENV%"=="" ( + set cache_pythonpath=%PYTHONPATH% + set PYTHONPATH= + ) + + rem run the formatter over the repository + rem check if pre-commit is installed + pip show pre-commit > nul 2>&1 + if errorlevel 1 ( + echo [INFO] Installing pre-commit... + pip install pre-commit + ) + + rem always execute inside the Isaac Lab directory + echo [INFO] Formatting the repository... + pushd %ISAACLAB_PATH% + call python -m pre_commit run --all-files + popd >nul + + rem set the python path back to the original value + if not "%CONDA_DEFAULT_ENV%"=="" ( + set PYTHONPATH=%cache_pythonpath% + ) + goto :end +) else if "%arg%"=="-p" ( + rem run the python provided by Isaac Sim + call :extract_python_exe + echo [INFO] Using python from: !python_exe! + REM Loop through all arguments - mimic shift + set "allArgs=" + for %%a in (%*) do ( + REM Append each argument to the variable, skip the first one + if defined skip ( + set "allArgs=!allArgs! %%a" + ) else ( + set "skip=1" + ) + ) + !python_exe! !allArgs! + goto :end +) else if "%arg%"=="--python" ( + rem run the python provided by Isaac Sim + call :extract_python_exe + echo [INFO] Using python from: !python_exe! + REM Loop through all arguments - mimic shift + set "allArgs=" + for %%a in (%*) do ( + REM Append each argument to the variable, skip the first one + if defined skip ( + set "allArgs=!allArgs! %%a" + ) else ( + set "skip=1" + ) + ) + !python_exe! !allArgs! + goto :end +) else if "%arg%"=="-s" ( + rem run the simulator exe provided by isaacsim + call :extract_isaacsim_exe + echo [INFO] Running isaac-sim from: %isaacsim_exe% + set "allArgs=" + for %%a in (%*) do ( + REM Append each argument to the variable, skip the first one + if defined skip ( + set "allArgs=!allArgs! %%a" + ) else ( + set "skip=1" + ) + ) + !isaacsim_exe! --ext-folder %ISAACLAB_PATH%\source\extensions !allArgs1 + goto :end +) else if "%arg%"=="--sim" ( + rem run the simulator exe provided by Isaac Sim + call :extract_isaacsim_exe + echo [INFO] Running isaac-sim from: %isaacsim_exe% + set "allArgs=" + for %%a in (%*) do ( + REM Append each argument to the variable, skip the first one + if defined skip ( + set "allArgs=!allArgs! %%a" + ) else ( + set "skip=1" + ) + ) + !isaacsim_exe! --ext-folder %ISAACLAB_PATH%\source\extensions !allArgs1 + goto :end +) else if "%arg%"=="-t" ( + rem run the python provided by Isaac Sim + call :extract_python_exe + set "allArgs=" + for %%a in (%*) do ( + REM Append each argument to the variable, skip the first one + if defined skip ( + set "allArgs=!allArgs! %%a" + ) else ( + set "skip=1" + ) + ) + !python_exe! tools\run_all_tests.py !allArgs! + goto :end +) else if "%arg%"=="--test" ( + rem run the python provided by Isaac Sim + call :extract_python_exe + set "allArgs=" + for %%a in (%*) do ( + REM Append each argument to the variable, skip the first one + if defined skip ( + set "allArgs=!allArgs! %%a" + ) else ( + set "skip=1" + ) + ) + !python_exe! tools\run_all_tests.py !allArgs! + goto :end +) else if "%arg%"=="-v" ( + rem update the vscode settings + call :update_vscode_settings + shift + goto :end +) else if "%arg%"=="--vscode" ( + rem update the vscode settings + call :update_vscode_settings + shift + goto :end +) else if "%arg%"=="-d" ( + rem build the documentation + echo [INFO] Building documentation... + call :extract_python_exe + pushd %ISAACLAB_PATH%\docs + call !python_exe! -m pip install -r requirements.txt >nul + call !python_exe! -m sphinx -b html -d _build\doctrees . _build\html + echo [INFO] To open documentation on default browser, run: + echo xdg-open "%ISAACLAB_PATH%\docs\_build\html\index.html" + popd >nul + shift + goto :end +) else if "%arg%"=="--docs" ( + rem build the documentation + echo [INFO] Building documentation... + call :extract_python_exe + pushd %ISAACLAB_PATH%\docs + call !python_exe! -m pip install -r requirements.txt >nul + call !python_exe! -m sphinx -b html -d _build\doctrees . _build\html + echo [INFO] To open documentation on default browser, run: + echo xdg-open "%ISAACLAB_PATH%\docs\_build\html\index.html" + popd >nul + shift + goto :end +) else if "%arg%"=="-h" ( + call :print_help + goto :end +) else if "%arg%"=="--help" ( + call :print_help + goto :end +) else ( + echo Invalid argument provided: %arg% + call :print_help + exit /b 1 +) +goto loop + +:end +exit /b 0 diff --git a/isaaclab.sh b/isaaclab.sh index dc9b95781c..8ebcbb22bb 100755 --- a/isaaclab.sh +++ b/isaaclab.sh @@ -39,8 +39,13 @@ extract_python_exe() { # use conda python local python_exe=${CONDA_PREFIX}/bin/python else - # use python from kit - local python_exe=${build_path}/python.sh + if pip show isaacsim-rl > /dev/null 2>&1; then + # use current python executable + python_exe=$(which python) + else + # use python from kit + local python_exe=${build_path}/python.sh + fi fi # check if there is a python path available if [ ! -f "${python_exe}" ]; then @@ -85,16 +90,6 @@ install_isaaclab_extension() { fi } -install_extension_deps() { - # retrieve the python executable - set -e - path="$1" - cmd="$2" - python_exe=$(extract_python_exe) - echo -e "\t Installing deps for module: $1" - ${python_exe} ${ISAACLAB_PATH}/tools/install_deps.py ${cmd} $1 -} - # setup anaconda environment for Isaac Lab setup_conda_env() { # get environment name from input @@ -176,7 +171,6 @@ setup_conda_env() { echo -e "[INFO] Created conda environment named '${env_name}'.\n" echo -e "\t\t1. To activate the environment, run: conda activate ${env_name}" echo -e "\t\t2. To install Isaac Lab extensions, run: isaaclab -i" - echo -e "\t\t3. To install learning-related dependencies, run: isaaclab -e" echo -e "\t\t4. To perform formatting, run: isaaclab -f" echo -e "\t\t5. To deactivate the environment, run: conda deactivate" echo -e "\n" @@ -199,12 +193,10 @@ update_vscode_settings() { # print the usage description print_help () { - echo -e "\nusage: $(basename "$0") [-h] [-i] [-e] [-f] [-p] [-s] [-t] [-o] [-v] [-d] [-c] -- Utility to manage Isaac Lab." + echo -e "\nusage: $(basename "$0") [-h] [-i] [-f] [-p] [-s] [-t] [-o] [-v] [-d] [-c] -- Utility to manage Isaac Lab." echo -e "\noptional arguments:" echo -e "\t-h, --help Display the help content." - echo -e "\t-i, --install Install the extensions inside Isaac Lab." - echo -e "\t-e, --extra [LIB] Install learning frameworks (rl_games, rsl_rl, sb3) as extra dependencies. Default is 'all'." - echo -e "\t--install-deps [dep_type] Install dependencies for extensions (apt, rosdep, all) from each extension.toml. Default is 'all'." + echo -e "\t-i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks as extra dependencies. Default is 'all'." echo -e "\t-f, --format Run pre-commit to format the code and check lints." echo -e "\t-p, --python Run the python executable provided by Isaac Sim or virtual environment (if active)." echo -e "\t-s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim." @@ -235,6 +227,7 @@ while [[ $# -gt 0 ]]; do -i|--install) # install the python packages in IsaacLab/source directory echo "[INFO] Installing extensions inside the Isaac Lab repository..." + python_exe=$(extract_python_exe) # recursively look into directories and install them # this does not check dependencies between extensions export -f extract_python_exe @@ -244,17 +237,19 @@ while [[ $# -gt 0 ]]; do # unset local variables unset install_isaaclab_extension # setup vscode settings - update_vscode_settings - shift # past argument - ;; - -e|--extra) + if ! ${python_exe} -m pip show isaacsim-rl &>/dev/null; then + update_vscode_settings + fi # install the python packages for supported reinforcement learning frameworks echo "[INFO] Installing extra requirements such as learning frameworks..." - python_exe=$(extract_python_exe) # check if specified which rl-framework to install if [ -z "$2" ]; then echo "[INFO] Installing all rl-frameworks..." framework_name="all" + elif [ "$2" = "none" ]; then + echo "[INFO] No rl-framework will be installed." + framework_name="none" + shift # past argument else echo "[INFO] Installing rl-framework: $2" framework_name=$2 @@ -264,30 +259,6 @@ while [[ $# -gt 0 ]]; do ${python_exe} -m pip install -e ${ISAACLAB_PATH}/source/extensions/omni.isaac.lab_tasks["${framework_name}"] shift # past argument ;; - --install-deps) - # install the deps for extensions in source/extensions directory - if [ -z "$2" ]; then - dep_type="all" - else - dep_type=$2 - shift # past argument - fi - echo "[INFO] Installing ${dep_type} dependencies for extensions inside the Isaac Lab repository..." - # recursively look into directories and install - # all extension dependencies - export -f extract_python_exe - export -f install_extension_deps - # check if dep_type is installed, if not "all" - if [ "$dep_type" = "all" ] || command -v "$dep_type" &>/dev/null; then - find -L "${ISAACLAB_PATH}/source/extensions" -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -I {} bash -c 'install_extension_deps "$1" "$2"' _ {} "${dep_type}" - else - echo "[ERROR] Not installing ${dep_type} deps, ${dep_type} not a known command" - exit 1 - fi - # unset local variables - unset install_extension_deps - shift # past argument - ;; -c|--conda) # use default name if not provided if [ -z "$2" ]; then diff --git a/source/apps/isaaclab.backwards.compatible.kit b/source/apps/isaaclab.backwards.compatible.kit new file mode 100644 index 0000000000..be3e9e9756 --- /dev/null +++ b/source/apps/isaaclab.backwards.compatible.kit @@ -0,0 +1,479 @@ +# This experience must be used to run Isaac Lab with Isaac Sim 2023.1.1 + +[package] +title = "Isaac Lab Python" +description = "An app for running Isaac Lab with Isaac Sim 2023.1.1" +version = "2023.1.1" + +# That makes it browsable in UI with "experience" filter +keywords = ["experience", "app", "orbit", "python"] + +################# +# Basic Kit App # +################# +[settings] +# Note: This path was adapted to be respective to the kit-exe file location +app.versionFile = "${exe-path}/VERSION" +app.folder = "${exe-path}/" +app.name = "Isaac-Sim" +app.version = "2023.1.1" + +# set the default ros bridge to disable on startup +isaac.startup.ros_bridge_extension = "" + +################################## +# Omniverse related dependencies # +################################## +[dependencies] + +# The Main UI App +"omni.kit.uiapp" = {} +"omni.kit.renderer.core" = {} + +# Livestream - OV Streaming Client +"omni.kit.livestream.native" = {version = "2.4.0", exact = true} +"omni.kit.streamsdk.plugins" = {version = "2.5.2", exact = true} + +# Status Bar +"omni.kit.window.status_bar" = {} +"omni.stats" = {} +"omni.kit.telemetry" = {} + +# Kit Menu +"omni.kit.menu.utils" = {} +"omni.kit.menu.file" = {} +"omni.kit.menu.edit" = {} +"omni.kit.menu.create" = {} +"omni.kit.menu.common" = {} +"omni.kit.menu.stage" = {} + +"omni.kit.window.file" = {} +"omni.kit.context_menu" = {} + +"omni.kit.selection" = {} +"omni.kit.stage_templates" = {} + +# PhysX +"omni.physx.bundle" = {} +"omni.physx.tensors" = {} + +# "omni.kit.search.service" = {} +"omni.kit.primitive.mesh" = {} + +# Create Windows +"omni.kit.window.title" = {} +"omni.kit.widget.live" = {} +"omni.kit.window.stage" = {} +"omni.kit.widget.layers" = {} +"omni.kit.window.cursor" = {} +"omni.kit.window.toolbar" = {} +"omni.kit.window.commands" = {} + +# New Viewport, load the default bundle of extensions +"omni.kit.viewport.bundle" = {} +"omni.kit.viewport.menubar.lighting" = {} +# Load the rendering extensions +# "omni.renderer" = { tag = "rtx" } +# Load the RTX rendering bundle +"omni.kit.viewport.rtx" = {} +# Load the Storm rendering bundle +"omni.kit.viewport.pxr" = {} + +# Needed for Fabric delegate +"omni.resourcemonitor" = {} + +# Additional Viewport features (legacy grid etc, HUD GPU stats) +"omni.kit.viewport.legacy_gizmos" = {} +"omni.kit.viewport.ready" = {} +"omni.hydra.engine.stats" = {} + +"omni.rtx.settings.core" = {} # this is the new Render Settings 2.0 + +# "omni.kit.window.movie_capture" = { } + +"omni.kit.profiler.window" = {} + +"omni.kit.stage_column.variant" = {} +"omni.kit.stage_column.payload" = {} + +# Viewport Widgets and Collaboration +# "omni.kit.viewport_widgets_manager" = {} +"omni.kit.collaboration.channel_manager" = {} + +# "omni.kit.widgets.custom" = {} + +# utils window +"omni.kit.widget.filebrowser" = {} +"omni.kit.window.filepicker" = {} +"omni.kit.window.content_browser" = {} + +"omni.kit.window.stats" = { order = 1000 } + +"omni.kit.window.script_editor" = {} +"omni.kit.window.console" = {} + +"omni.kit.window.extensions" = {} + +# browsers +"omni.kit.browser.sample" = {} +# "omni.kit.browser.asset" = {} +# "omni.kit.browser.asset_store" = {} +# "omni.kit.browser.asset_provider.local" = {} +# "omni.kit.browser.asset_provider.sketchfab" = {} +# "omni.kit.browser.asset_provider.turbosquid" = {} +# "omni.kit.browser.asset_provider.actorcore" = {} + +# "omni.kit.window.environment" = {} # potentially increases startup times + +# Material +# "omni.kit.window.material" = { } +# "omni.kit.graph.delegate.default" = { } +# "omni.kit.window.material_graph" = { } + +# "omni.kit.window.usd_paths" = {} +# "omni.kit.window.preferences" = { order = 1000 } # so the menu is in the correct place + +# "omni.kit.renderer.capture" = {} +# "omni.kit.thumbnails.usd" = {} +# "omni.kit.thumbnails.images" = {} + +# bring all the property Widgets and Window +"omni.kit.window.property" = {} +"omni.kit.property.bundle" = {} +"omni.kit.property.layer" = {} + +# Manipulator +"omni.kit.manipulator.prim" = {} +"omni.kit.manipulator.transform" = {} +"omni.kit.manipulator.viewport" = {} +# "omni.kit.manipulator.tool.mesh_snap" = {} + +# Animation +# Needed to properly load navigation mesh +# "omni.anim.graph.schema" = {} +# "omni.anim.navigation.schema" = {} + +# OmniGraph +# "omni.graph.bundle.action" = {} +# "omni.graph.window.action" = {} +# "omni.graph.window.generic" = {} +# "omni.graph.visualization.nodes" = {} + +# Scene Visualization +"omni.usd.schema.scene.visualization" = {} +# "omni.scene.visualization.bundle" = {} + +# Hotkeys +"omni.kit.hotkeys.window" = {} + +# Needed for omni.kit.viewport.ready.viewport_ready +"omni.activity.profiler" = {} +"omni.activity.pump" = {} + +"omni.kit.widget.cache_indicator" = {} + + +[settings] +renderer.active = "rtx" +exts."omni.kit.viewport.menubar.camera".expand = true # Expand the extra-camera settings by default +exts."omni.kit.window.file".useNewFilePicker = true +exts."omni.kit.tool.asset_importer".useNewFilePicker = true +exts."omni.kit.tool.collect".useNewFilePicker = true +exts."omni.kit.widget.layers".useNewFilePicker = true +exts."omni.kit.renderer.core".imgui.enableMips = true +exts."omni.kit.browser.material".enabled = false +exts."omni.kit.browser.asset".visible_after_startup = false +exts."omni.kit.window.material".load_after_startup = true +exts."omni.kit.widget.cloud_share".require_access_code = false +exts."omni.kit.pipapi".installCheckIgnoreVersion = true +exts."omni.kit.viewport.window".startup.windowName="Viewport" # Rename from Viewport Next +exts."omni.kit.menu.utils".logDeprecated = false + +# app.content.emptyStageOnStart = false +app.file.ignoreUnsavedOnExit = true # prevents save dialog when exiting + +# deprecate support for old kit.ui.menu +app.menu.legacy_mode = false +# use omni.ui.Menu for the MenuBar +app.menu.compatibility_mode = false +# Setting the port for the embedded http server +exts."omni.services.transport.server.http".port = 8211 + +# default viewport is fill +app.runLoops.rendering_0.fillResolution = false +exts."omni.kit.window.viewport".blockingGetViewportDrawable = false + +exts."omni.kit.test".includeTests.1 = "*isaac*" + +[settings.app.settings] +persistent = false +dev_build = false +fabricDefaultStageFrameHistoryCount = 3 # needed for omni.syntheticdata TODO105 Still True? + +[settings.app.window] +title = "Isaac Sim Python" +hideUi = false +_iconSize = 256 +iconPath = "${exe-path}/../exts/omni.isaac.app.setup/data/nvidia-omniverse-isaacsim.ico" + +# Fonts +[setting.app.font] +file = "${fonts}/OpenSans-SemiBold.ttf" +size = 16 + +[settings.exts.'omni.kit.window.extensions'] +# List extensions here we want to show as featured when extension manager is opened +featuredExts = [] + +[settings.app.python] +# These disable the kit app from also printing out python output, which gets confusing +interceptSysStdOutput = false +logSysStdOutput = false + +[settings] +# MGPU is always on, you can turn it from the settings, and force this off to save even more resource if you +# only want to use a single GPU on your MGPU system +# False for Isaac Sim +renderer.multiGpu.enabled = true +renderer.multiGpu.autoEnable = true +'rtx-transient'.resourcemanager.enableTextureStreaming = true +# app.hydra.aperture.conform = 4 # in 105.1 pixels are square by default +app.hydraEngine.waitIdle = false +rtx.newDenoiser.enabled = true + +# Enable Iray and pxr by setting this to "rtx,iray,pxr" +renderer.enabled = "rtx" + +# Disable the simulation output window popup +physics.autoPopupSimulationOutputWindow=false + +### async rendering settings +omni.replicator.asyncRendering = false +app.asyncRendering = false +app.asyncRenderingLowLatency = false + +### Render thread settings +app.runLoops.main.rateLimitEnabled = false +app.runLoops.main.rateLimitFrequency = 120 +app.runLoops.main.rateLimitUsePrecisionSleep = true +app.runLoops.main.syncToPresent = false +app.runLoops.present.rateLimitFrequency = 120 +app.runLoops.present.rateLimitUsePrecisionSleep = true +app.runLoops.rendering_0.rateLimitFrequency = 120 +app.runLoops.rendering_0.rateLimitUsePrecisionSleep = true +app.runLoops.rendering_0.syncToPresent = false +app.runLoops.rendering_1.rateLimitFrequency = 120 +app.runLoops.rendering_1.rateLimitUsePrecisionSleep = true +app.runLoops.rendering_1.syncToPresent = false +app.runLoopsGlobal.syncToPresent = false +app.vsync = false +exts.omni.kit.renderer.core.present.enabled = false +exts.omni.kit.renderer.core.present.presentAfterRendering = false +persistent.app.viewport.defaults.tickRate = 120 +rtx-transient.dlssg.enabled = false + +privacy.externalBuild = true + +# hide NonToggleable Exts +exts."omni.kit.window.extensions".hideNonToggleableExts = true +exts."omni.kit.window.extensions".showFeatureOnly = false + +# Hang Detector +################################ +# app.hangDetector.enabled = false +# app.hangDetector.timeout = 120 + +############ +# Browsers # +############ +exts."omni.kit.browser.material".folders = [ + "Base::http://omniverse-content-production.s3-us-west-2.amazonaws.com/Materials/Base", + "vMaterials::http://omniverse-content-production.s3.us-west-2.amazonaws.com/Materials/vMaterials_2/", + "Twinbru Fabrics::https://twinbru.s3.eu-west-1.amazonaws.com/omniverse/Twinbru Fabrics/" +] + +exts."omni.kit.window.environment".folders = [ + "https://omniverse-content-production.s3.us-west-2.amazonaws.com/Assets/Skies/2022_1/Skies", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Scenes/Templates", +] + +exts."omni.kit.browser.sample".folders = [ + "http://omniverse-content-production.s3-us-west-2.amazonaws.com//Samples" +] + +exts."omni.kit.browser.asset".folders = [ + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Vegetation", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/ArchVis/Commercial", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/ArchVis/Industrial", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/ArchVis/Residential", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Equipment", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Safety", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Shipping", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Storage", +] + +exts."omni.kit.browser.texture".folders = [ + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Vegetation", +] + +############################ +# Content Browser Settings # +############################ +[settings.exts."omni.kit.window.content_browser"] +enable_thumbnail_generation_images = false # temp fix to avoid leaking python processes + +####################### +# Extensions Settings # +####################### +[settings.exts."omni.kit.registry.nucleus"] +registries = [ + { name = "kit/default", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/shared" }, + { name = "kit/sdk", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/sdk/${kit_version_short}/${kit_git_hash}" }, + { name = "kit/community", url = "https://dw290v42wisod.cloudfront.net/exts/kit/community" }, +] + +[settings.app.extensions] +skipPublishVerification = false +registryEnabled = true + +[settings.exts."omni.kit.window.modifier.titlebar"] +titleFormatString = " Isaac Sim {version:${exe-path}/../SHORT_VERSION,font_color=0x909090,font_size=16} {separator} {file, board=true}" +showFileFullPath = true +icon.file = "${exe-path}/../exts/omni.isaac.app.setup/data/nvidia-omniverse-isaacsim.ico" +icon.size = 256 +defaultFont.name = "Arial" +defaultFont.size = 16 +defaultFont.color = 0xD0D0D0 +separator.color = 0x00B976 +separator.width = 1 +windowBorder.color = 0x0F0F0F +windowBorder.width = 2 +colors.caption = 0x0F0F0F +colors.client = 0x0F0F0F +respondOnMouseUp = true +changeWindowRegion = true + +# Register extension folder from this repo in kit +[settings.app.exts] +folders = [ + "${exe-path}/exts", # kit extensions + "${exe-path}/extscore", # kit core extensions + "${exe-path}/../exts", # isaac extensions + "${exe-path}/../extscache", # isaac cache extensions + "${exe-path}/../extsPhysics", # isaac physics extensions, + "${app}", # needed to find other app files + "${app}/../extensions", # needed to find extensions in Isaac Lab +] + +[settings.crashreporter.data] +experience = "Isaac Sim Python" + +###################### +# Isaac Sim Settings # +###################### +[settings.app.renderer] +skipWhileMinimized = false +sleepMsOnFocus = 0 +sleepMsOutOfFocus = 0 +resolution.width=1280 +resolution.height=720 + +[settings.app.livestream] +proto = "ws" +allowResize = true +outDirectory = "${data}" + +# default camera position in meters +[settings.app.viewport] +defaultCamPos.x = 5 +defaultCamPos.y = 5 +defaultCamPos.z = 5 + +################ +# RTX Settings # +################ +[settings.rtx] +translucency.worldEps = 0.005 +raytracing.fractionalCutoutOpacity = false +hydra.enableSemanticSchema = true +# descriptorSets=60000 +# reservedDescriptors=500000 +# sceneDb.maxInstances=1000000 + +# Enable this for static scenes, improves visual quality +# directLighting.sampledLighting.enabled = true + +[settings.persistent] +app.file.recentFiles = [] +app.stage.upAxis = "Z" +app.stage.movePrimInPlace = false +app.stage.instanceableOnCreatingReference = false +app.stage.materialStrength = "weakerThanDescendants" + +app.transform.gizmoUseSRT = true +app.viewport.grid.scale = 1.0 +app.viewport.pickingMode = "kind:model.ALL" +app.viewport.camMoveVelocity = 0.05 # 5 m/s +app.viewport.gizmo.scale = 0.01 # scaled to meters +app.viewport.previewOnPeek = false +app.viewport.snapToSurface = false +app.viewport.displayOptions = 31887 # Disable Frame Rate and Resolution by default +app.window.uiStyle = "NvidiaDark" +app.primCreation.DefaultXformOpType = "Scale, Orient, Translate" +app.primCreation.DefaultXformOpOrder="xformOp:translate, xformOp:orient, xformOp:scale" +app.primCreation.typedDefaults.camera.clippingRange = [0.01, 10000000.0] +simulation.minFrameRate = 15 +simulation.defaultMetersPerUnit = 1.0 +omnigraph.updateToUsd = false +omnigraph.useSchemaPrims = true +omnigraph.disablePrimNodes = true +physics.updateToUsd = true +physics.updateVelocitiesToUsd = true +physics.useFastCache = false +physics.visualizationDisplayJoints = false +physics.visualizationSimulationOutput = false +omni.replicator.captureOnPlay = true +omnihydra.useSceneGraphInstancing = true + +renderer.startupMessageDisplayed = true # hides the IOMMU popup window + +# Make Detail panel visible by default +app.omniverse.content_browser.options_menu.show_details = true +app.omniverse.filepicker.options_menu.show_details = true + + +[settings.ngx] +enabled=true # Enable this for DLSS + +######################## +# Isaac Sim Extensions # +######################## +[dependencies] +"omni.isaac.core" = {} +"omni.isaac.core_archive" = {} +"omni.pip.compute" = {} +"omni.pip.cloud" = {} +"omni.isaac.kit" = {} +"omni.isaac.ml_archive" = {} +"omni.kit.loop-isaac" = {} +"omni.isaac.utils" = {} +"omni.kit.property.isaac" = {} + +"omni.isaac.cloner" = {} +"omni.isaac.debug_draw" = {} + + +# linux only extensions +[dependencies."filter:platform"."linux-x86_64"] +# "omni.isaac.ocs2" = {} + +############################ +# Non-Isaac Sim Extensions # +############################ +[dependencies] +"omni.syntheticdata" = {} +"semantics.schema.editor" = {} +"semantics.schema.property" = {} +"omni.replicator.core" = {} +# "omni.importer.mjcf" = {} +# "omni.importer.urdf" = {} diff --git a/source/apps/isaaclab.python.headless.kit b/source/apps/isaaclab.python.headless.kit index cca93cae37..fefdc87354 100644 --- a/source/apps/isaaclab.python.headless.kit +++ b/source/apps/isaaclab.python.headless.kit @@ -3,55 +3,45 @@ ## [package] -title = "Isaac Sim Python - Minimal (headless)" -description = "A minimal app for running standalone scripts headlessly." -version = "2023.1.1" +title = "Isaac Lab Python Headless" +description = "An app for running Isaac Lab headlessly" +version = "1.0.0" # That makes it browsable in UI with "experience" filter keywords = ["experience", "app", "isaaclab", "python", "headless"] -################# -# Basic Kit App # -################# [settings] -# Note: This path was adapted to be respective to the kit-exe file location app.versionFile = "${exe-path}/VERSION" app.folder = "${exe-path}/" app.name = "Isaac-Sim" -app.version = "2023.1.1" - -# set the default ros bridge to disable on startup -isaac.startup.ros_bridge_extension = "" +app.version = "4.0.0" ################################## # Omniverse related dependencies # ################################## [dependencies] "omni.kit.window.title" = {} -"omni.kit.window.console" = {} "omni.physx" = {} "omni.physx.tensors" = {} "omni.physx.fabric" = {} "omni.warp.core" = {} "usdrt.scenegraph" = {} - -# "omni.kit.mainwindow" = {} -# "omni.kit.telemetry" = {} +"omni.kit.primitive.mesh" = {} +"omni.kit.mainwindow" = {} +"omni.kit.telemetry" = {} [settings] -# Basic Kit App +renderer.active = "rtx" + app.content.emptyStageOnStart = false -# deprecate support for old kit.ui.menu -app.menu.legacy_mode = false -# use omni.ui.Menu for the MenuBar -app.menu.compatibility_mode = false # Setting the port for the embedded http server exts."omni.services.transport.server.http".port = 8211 # default viewport is fill app.runLoops.rendering_0.fillResolution = false +exts."omni.kit.window.viewport".blockingGetViewportDrawable = false # Fix PlayButtonGroup error exts."omni.kit.widget.toolbar".PlayButton.enabled = false @@ -62,17 +52,27 @@ dev_build = false fabricDefaultStageFrameHistoryCount = 3 # needed for omni.syntheticdata TODO105 still true? [settings.app.window] -title = "Isaac Sim Python" +title = "Isaac Sim" hideUi = false _iconSize = 256 -# Note: This path was adapted to be respective to the kit folder location -iconPath = "${exe-path}/../exts/omni.isaac.app.setup/data/nvidia-omniverse-isaacsim.ico" +iconPath = "${omni.isaac.app.setup}/data/omni.isaac.sim.png" + +# width = 1700 +# height = 900 +# x = -1 +# y = -1 # Fonts [setting.app.font] file = "${fonts}/OpenSans-SemiBold.ttf" size = 16 +# [setting.app.runLoops] +# main.rateLimitEnabled = false +# main.rateLimitFrequency = 60 +# main.rateLimitUseBusyLoop = false +# rendering_0.rateLimitEnabled = false + [settings.exts.'omni.kit.window.extensions'] # List extensions here we want to show as featured when extension manager is opened featuredExts = [] @@ -98,11 +98,21 @@ omni.replicator.asyncRendering = false # Enable Iray and pxr by setting this to "rtx,iray,pxr" renderer.enabled = "rtx" -# Disable the simulation output window popup -physics.autoPopupSimulationOutputWindow=false +# Avoid warning on shutdown from audio context +app.audio.enabled = false -# Disable IOMMU Enabled pop-up message on warmup (OM-100381) -persistent.renderer.startupMessageDisplayed = true +# Enable Vulkan - avoids torch+cu12 error on windows +app.vulkan = true + +# Basic Kit App +################################ +app.versionFile = "${app}/../VERSION" +app.name = "Isaac-Sim" +app.version = "4.0.0" + +# hide NonToggleable Exts +exts."omni.kit.window.extensions".hideNonToggleableExts = true +exts."omni.kit.window.extensions".showFeatureOnly = false # Hang Detector ################################ @@ -110,14 +120,16 @@ persistent.renderer.startupMessageDisplayed = true # app.hangDetector.timeout = 120 -####################### -# Extensions Settings # -####################### +# set the default ros bridge to disable on startup +isaac.startup.ros_bridge_extension = "" + +# Extensions +############################### [settings.exts."omni.kit.registry.nucleus"] registries = [ - { name = "kit/default", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/shared" }, - { name = "kit/sdk", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/sdk/${kit_version_short}/${kit_git_hash}" }, - { name = "kit/community", url = "https://dw290v42wisod.cloudfront.net/exts/kit/community" }, + { name = "kit/default", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/shared" }, + { name = "kit/sdk", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/sdk/${kit_version_short}/${kit_git_hash}" }, + { name = "kit/community", url = "https://dw290v42wisod.cloudfront.net/exts/kit/community" }, ] [settings.app.extensions] @@ -125,9 +137,9 @@ skipPublishVerification = false registryEnabled = true [settings.exts."omni.kit.window.modifier.titlebar"] -titleFormatString = " Isaac Sim {version:${exe-path}/../SHORT_VERSION,font_color=0x909090,font_size=16} {separator} {file, board=true}" +titleFormatString = " Isaac Sim {version:${app}/../SHORT_VERSION,font_color=0x909090,font_size=16} {separator} {file, board=true}" showFileFullPath = true -icon.file = "${exe-path}/../exts/omni.isaac.app.setup/data/nvidia-omniverse-isaacsim.ico" +icon.file = "${app}/../exts/omni.isaac.app.setup/data/nvidia-omniverse-isaacsim.ico" icon.size = 256 defaultFont.name = "Arial" defaultFont.size = 16 @@ -142,11 +154,10 @@ respondOnMouseUp = true changeWindowRegion = true [settings.crashreporter.data] -experience = "Isaac Sim Python Minimal" +experience = "Isaac Sim" -###################### -# Isaac Sim Settings # -###################### +# Isaac Sim Settings +############################### [settings.app.renderer] skipWhileMinimized = false sleepMsOnFocus = 0 @@ -163,6 +174,7 @@ defaultCamPos.z = 5 [settings.rtx] raytracing.fractionalCutoutOpacity = false hydra.enableSemanticSchema = true +mdltranslator.mdlDistilling = false # descriptorSets=60000 # reservedDescriptors=500000 # sceneDb.maxInstances=1000000 @@ -193,24 +205,29 @@ simulation.defaultMetersPerUnit = 1.0 omnigraph.updateToUsd = false omnigraph.useSchemaPrims = true omnigraph.disablePrimNodes = true -physics.updateToUsd = false -physics.updateVelocitiesToUsd = false -physics.useFastCache = false -physics.visualizationDisplayJoints = false omni.replicator.captureOnPlay = true omnihydra.useSceneGraphInstancing = true - renderer.startupMessageDisplayed = true # hides the IOMMU popup window -# Make Detail panel invisible by default -app.omniverse.content_browser.options_menu.show_details = false -app.omniverse.filepicker.options_menu.show_details = false +# Make Detail panel visible by default +app.omniverse.content_browser.options_menu.show_details = true +app.omniverse.filepicker.options_menu.show_details = true [settings.physics] updateToUsd = false +updateParticlesToUsd = false updateVelocitiesToUsd = false updateForceSensorsToUsd = false outputVelocitiesLocalSpace = false +useFastCache = false +visualizationDisplayJoints = false +fabricUpdateTransformations = false +fabricUpdateVelocities = false +fabricUpdateForceSensors = false +fabricUpdateJointStates = false + +# Performance improvement +resourcemonitor.timeBetweenQueries = 100 # Register extension folder from this repo in kit [settings.app.exts] @@ -219,7 +236,10 @@ folders = [ "${exe-path}/extscore", # kit core extensions "${exe-path}/../exts", # isaac extensions "${exe-path}/../extscache", # isaac cache extensions - "${exe-path}/../extsPhysics", # isaac physics extensions, + "${exe-path}/../extsPhysics", # isaac physics extensions + "${exe-path}/../isaacsim/exts", # isaac extensions for pip + "${exe-path}/../isaacsim/extscache", # isaac cache extensions for pip + "${exe-path}/../isaacsim/extsPhysics", # isaac physics extensions for pip "${app}", # needed to find other app files "${app}/../extensions", # needed to find extensions in Isaac Lab ] @@ -232,11 +252,6 @@ enabled=true # Enable this for DLSS ######################## [dependencies] "omni.isaac.core" = {} -"omni.isaac.core_archive" = {} -"omni.pip.compute" = {} -"omni.pip.cloud" = {} "omni.isaac.cloner" = {} "omni.isaac.kit" = {} -"omni.isaac.ml_archive" = {} "omni.kit.loop-isaac" = {} -"omni.isaac.cloner" = {} diff --git a/source/apps/isaaclab.python.headless.rendering.kit b/source/apps/isaaclab.python.headless.rendering.kit new file mode 100644 index 0000000000..b88b446b72 --- /dev/null +++ b/source/apps/isaaclab.python.headless.rendering.kit @@ -0,0 +1,117 @@ +## +# Adapted from: https://github.com/NVIDIA-Omniverse/OmniIsaacGymEnvs/blob/main/apps/omni.isaac.sim.python.gym.camera.kit +# +# This app file designed specifically towards vision-based RL tasks. It provides necessary settings to enable +# multiple cameras to be rendered each frame. Additional settings are also applied to increase performance when +# rendering cameras across multiple environments. +## + +[package] +title = "Isaac Lab Python Headless Camera" +description = "An app for running Isaac Lab headlessly with rendering enabled" +version = "1.0.0" + +# That makes it browsable in UI with "experience" filter +keywords = ["experience", "app", "isaaclab", "python", "camera", "minimal"] + +[dependencies] +# Isaac Lab minimal app +"isaaclab.python.headless" = {} +"omni.replicator.core" = {} +"omni.replicator.isaac" = {} + +# Rendering +"omni.kit.material.library" = {} +"omni.kit.viewport.rtx" = {} + +[settings] + +# Basic Kit App +################################ +# Note: This path was adapted to be respective to the kit-exe file location +app.versionFile = "${exe-path}/VERSION" +app.folder = "${exe-path}/" +app.name = "Isaac-Sim" +app.version = "4.0.0" + +# set the default ros bridge to disable on startup +isaac.startup.ros_bridge_extension = "" + +# Increase available descriptors to support more simultaneous cameras +rtx.descriptorSets=30000 + +# Enable new denoiser to reduce motion blur artifacts +rtx.newDenoiser.enabled=true + +# Disable present thread to improve performance +exts."omni.renderer.core".present.enabled=false + +# Disabling these settings reduces renderer VRAM usage and improves rendering performance, but at some quality cost +rtx.raytracing.cached.enabled = false +rtx.raytracing.lightcache.spatialCache.enabled = false +rtx.ambientOcclusion.enabled = false +rtx-transient.dlssg.enabled = false + +rtx.sceneDb.ambientLightIntensity = 1.0 +rtx.directLighting.sampledLighting.enabled = true + +# Force synchronous rendering to improve training results +omni.replicator.asyncRendering = false + +app.renderer.waitIdle=true +app.hydraEngine.waitIdle=true + +app.audio.enabled = false + +# Enable Vulkan - avoids torch+cu12 error on windows +app.vulkan = true + +[settings.exts."omni.kit.registry.nucleus"] +registries = [ + { name = "kit/default", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/shared" }, + { name = "kit/sdk", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/sdk/${kit_version_short}/${kit_git_hash}" }, + { name = "kit/community", url = "https://dw290v42wisod.cloudfront.net/exts/kit/community" }, +] + +[settings.app.python] +# These disable the kit app from also printing out python output, which gets confusing +interceptSysStdOutput = false +logSysStdOutput = false + +[settings.app.renderer] +skipWhileMinimized = false +sleepMsOnFocus = 0 +sleepMsOutOfFocus = 0 + +[settings.physics] +updateToUsd = false +updateParticlesToUsd = false +updateVelocitiesToUsd = false +updateForceSensorsToUsd = false +outputVelocitiesLocalSpace = false +useFastCache = false +visualizationDisplayJoints = false +fabricUpdateTransformations = false +fabricUpdateVelocities = false +fabricUpdateForceSensors = false +fabricUpdateJointStates = false + +# Register extension folder from this repo in kit +[settings.app.exts] +folders = [ + "${exe-path}/exts", # kit extensions + "${exe-path}/extscore", # kit core extensions + "${exe-path}/../exts", # isaac extensions + "${exe-path}/../extscache", # isaac cache extensions + "${exe-path}/../extsPhysics", # isaac physics extensions + "${exe-path}/../isaacsim/exts", # isaac extensions for pip + "${exe-path}/../isaacsim/extscache", # isaac cache extensions for pip + "${exe-path}/../isaacsim/extsPhysics", # isaac physics extensions for pip + "${app}", # needed to find other app files + "${app}/../extensions", # needed to find extensions in Isaac Lab +] + +# Isaac Sim Extensions +############################### +[dependencies] +#"omni.isaac.app.setup" = { order = 1000 } # we are running that at the end diff --git a/source/apps/isaaclab.python.kit b/source/apps/isaaclab.python.kit index 697ffadd3a..ba053af63a 100644 --- a/source/apps/isaaclab.python.kit +++ b/source/apps/isaaclab.python.kit @@ -3,177 +3,102 @@ ## [package] -title = "Isaac Sim Python" -description = "A trimmed down app for use with python standalone scripts." -version = "2023.1.1" +title = "Isaac Lab Python" +description = "An app for running Isaac Lab" +version = "1.0.0" # That makes it browsable in UI with "experience" filter -keywords = ["experience", "app", "isaaclab", "python"] - -################# -# Basic Kit App # -################# -[settings] -# Note: This path was adapted to be respective to the kit-exe file location -app.versionFile = "${exe-path}/VERSION" -app.folder = "${exe-path}/" -app.name = "Isaac-Sim" -app.version = "2023.1.1" +keywords = ["experience", "app", "usd"] -# set the default ros bridge to disable on startup -isaac.startup.ros_bridge_extension = "" - -################################## -# Omniverse related dependencies # -################################## [dependencies] +# Isaac Sim extensions +"omni.isaac.cloner" = {} +"omni.isaac.core" = {} +"omni.isaac.core_nodes" = {} +"omni.isaac.debug_draw" = {} +"omni.isaac.kit" = {} +"omni.isaac.menu" = {} +"omni.isaac.nucleus" = {} +"omni.isaac.range_sensor" = {} +"omni.isaac.sensor" = {} +"omni.isaac.utils" = {} +"omni.kit.property.isaac" = {} +"omni.replicator.isaac" = {} -# The Main UI App -"omni.kit.uiapp" = {} -"omni.kit.renderer.core" = {} - -# Livestream - OV Streaming Client -"omni.kit.livestream.native" = {version = "2.4.0", exact = true} -"omni.kit.streamsdk.plugins" = {version = "2.5.2", exact = true} - -# Status Bar -"omni.kit.window.status_bar" = {} -"omni.stats" = {} -"omni.kit.telemetry" = {} - -# Kit Menu -"omni.kit.menu.utils" = {} -"omni.kit.menu.file" = {} -"omni.kit.menu.edit" = {} -"omni.kit.menu.create" = {} +# Kit extensions +"omni.graph.bundle.action" = {} +"omni.graph.visualization.nodes" = {} +"omni.graph.window.action" = {} +"omni.graph.window.generic" = {} +"omni.hydra.engine.stats" = {} +"omni.kit.context_menu" = {} +"omni.kit.hotkeys.window" = {} +"omni.kit.loop-isaac" = {} "omni.kit.menu.common" = {} +"omni.kit.menu.create" = {} +"omni.kit.menu.edit" = {} +"omni.kit.menu.file" = {} "omni.kit.menu.stage" = {} - -"omni.kit.window.file" = {} -"omni.kit.context_menu" = {} - -"omni.kit.selection" = {} -"omni.kit.stage_templates" = {} - -# PhysX -"omni.physx.bundle" = {} -"omni.physx.tensors" = {} - -# "omni.kit.search.service" = {} +"omni.kit.menu.utils" = {} "omni.kit.primitive.mesh" = {} - -# Create Windows -"omni.kit.window.title" = {} -"omni.kit.widget.live" = {} -"omni.kit.window.stage" = {} -"omni.kit.widget.layers" = {} -"omni.kit.window.cursor" = {} -"omni.kit.window.toolbar" = {} -"omni.kit.window.commands" = {} - -# New Viewport, load the default bundle of extensions -"omni.kit.viewport.bundle" = {} -"omni.kit.viewport.menubar.lighting" = {} -# Load the rendering extensions -# "omni.renderer" = { tag = "rtx" } -# Load the RTX rendering bundle -"omni.kit.viewport.rtx" = {} -# Load the Storm rendering bundle -"omni.kit.viewport.pxr" = {} - -# Needed for Fabric delegate -"omni.resourcemonitor" = {} - -# Additional Viewport features (legacy grid etc, HUD GPU stats) -"omni.kit.viewport.legacy_gizmos" = {} -"omni.kit.viewport.ready" = {} -"omni.hydra.engine.stats" = {} - -"omni.rtx.settings.core" = {} # this is the new Render Settings 2.0 - -# "omni.kit.window.movie_capture" = { } - "omni.kit.profiler.window" = {} - -"omni.kit.stage_column.variant" = {} +"omni.kit.property.bundle" = {} +"omni.kit.property.layer" = {} +"omni.kit.renderer.core" = {} +"omni.kit.selection" = {} "omni.kit.stage_column.payload" = {} +"omni.kit.stage_column.variant" = {} +"omni.kit.stage_templates" = {} +"omni.kit.stagerecorder.core" = {} +"omni.kit.telemetry" = {} +"omni.kit.uiapp" = {} -# Viewport Widgets and Collaboration -# "omni.kit.viewport_widgets_manager" = {} -"omni.kit.collaboration.channel_manager" = {} - -# "omni.kit.widgets.custom" = {} +"omni.kit.viewport.window" = {} +"omni.kit.manipulator.camera" = {} +"omni.kit.manipulator.prim" = {} +"omni.kit.manipulator.selection" = {} +"omni.kit.window.drop_support" = {} +"omni.kit.viewport.menubar.settings" = {} +"omni.kit.viewport.menubar.render" = {} +"omni.kit.viewport.menubar.camera" = {} +"omni.kit.viewport.menubar.display" = {} -# utils window +"omni.kit.viewport.rtx" = {} +"omni.kit.widget.cache_indicator" = {} +"omni.kit.widget.extended_searchfield" = {} "omni.kit.widget.filebrowser" = {} -"omni.kit.window.filepicker" = {} -"omni.kit.window.content_browser" = {} - -"omni.kit.window.stats" = { order = 1000 } +"omni.kit.widget.layers" = {} +"omni.kit.widget.live" = {} +"omni.kit.widget.timeline" = {} -"omni.kit.window.script_editor" = {} +"omni.kit.window.commands" = {} "omni.kit.window.console" = {} - +"omni.kit.window.content_browser" = {} +"omni.kit.window.cursor" = {} "omni.kit.window.extensions" = {} - -# browsers -"omni.kit.browser.sample" = {} -# "omni.kit.browser.asset" = {} -# "omni.kit.browser.asset_store" = {} -# "omni.kit.browser.asset_provider.local" = {} -# "omni.kit.browser.asset_provider.sketchfab" = {} -# "omni.kit.browser.asset_provider.turbosquid" = {} -# "omni.kit.browser.asset_provider.actorcore" = {} - -# "omni.kit.window.environment" = {} # potentially increases startup times - -# Material -# "omni.kit.window.material" = { } -# "omni.kit.graph.delegate.default" = { } -# "omni.kit.window.material_graph" = { } - -# "omni.kit.window.usd_paths" = {} -# "omni.kit.window.preferences" = { order = 1000 } # so the menu is in the correct place - -# "omni.kit.renderer.capture" = {} -# "omni.kit.thumbnails.usd" = {} -# "omni.kit.thumbnails.images" = {} - -# bring all the property Widgets and Window +"omni.kit.window.file" = {} +"omni.kit.window.filepicker" = {} "omni.kit.window.property" = {} -"omni.kit.property.bundle" = {} -"omni.kit.property.layer" = {} +"omni.kit.window.script_editor" = {} +"omni.kit.window.stage" = {} +"omni.kit.window.stats" = {order = 1000} +"omni.kit.window.status_bar" = {} +"omni.kit.window.title" = {} +"omni.kit.window.toolbar" = {} -# Manipulator -"omni.kit.manipulator.prim" = {} -"omni.kit.manipulator.transform" = {} -"omni.kit.manipulator.viewport" = {} -# "omni.kit.manipulator.tool.mesh_snap" = {} - -# Animation -# Needed to properly load navigation mesh -# "omni.anim.graph.schema" = {} -# "omni.anim.navigation.schema" = {} - -# OmniGraph -# "omni.graph.bundle.action" = {} -# "omni.graph.window.action" = {} -# "omni.graph.window.generic" = {} -# "omni.graph.visualization.nodes" = {} - -# Scene Visualization +"omni.physx.bundle" = {} +# "omni.physx.fabric" = {} +"omni.physx.tensors" = {} +"omni.replicator.core" = {} +"omni.replicator.replicator_yaml" = {} +"omni.resourcemonitor" = {} +"omni.rtx.settings.core" = {} +"omni.stats" = {} +"omni.syntheticdata" = {} "omni.usd.schema.scene.visualization" = {} -# "omni.scene.visualization.bundle" = {} - -# Hotkeys -"omni.kit.hotkeys.window" = {} - -# Needed for omni.kit.viewport.ready.viewport_ready -"omni.activity.profiler" = {} -"omni.activity.pump" = {} - -"omni.kit.widget.cache_indicator" = {} - +"omni.warp" = {} +"semantics.schema.editor" = {} +"semantics.schema.property" = {} [settings] renderer.active = "rtx" @@ -183,9 +108,6 @@ exts."omni.kit.tool.asset_importer".useNewFilePicker = true exts."omni.kit.tool.collect".useNewFilePicker = true exts."omni.kit.widget.layers".useNewFilePicker = true exts."omni.kit.renderer.core".imgui.enableMips = true -exts."omni.kit.browser.material".enabled = false -exts."omni.kit.browser.asset".visible_after_startup = false -exts."omni.kit.window.material".load_after_startup = true exts."omni.kit.widget.cloud_share".require_access_code = false exts."omni.kit.pipapi".installCheckIgnoreVersion = true exts."omni.kit.viewport.window".startup.windowName="Viewport" # Rename from Viewport Next @@ -205,10 +127,15 @@ exts."omni.services.transport.server.http".port = 8211 app.runLoops.rendering_0.fillResolution = false exts."omni.kit.window.viewport".blockingGetViewportDrawable = false -exts."omni.kit.test".includeTests.1 = "*isaac*" +exts."omni.kit.test".includeTests = [ "*isaac*" ] + +[settings.app.python] +# These disable the kit app from also printing out python output, which gets confusing +interceptSysStdOutput = false +logSysStdOutput = false [settings.app.settings] -persistent = false +persistent = true dev_build = false fabricDefaultStageFrameHistoryCount = 3 # needed for omni.syntheticdata TODO105 Still True? @@ -216,28 +143,37 @@ fabricDefaultStageFrameHistoryCount = 3 # needed for omni.syntheticdata TODO105 title = "Isaac Sim Python" hideUi = false _iconSize = 256 -iconPath = "${exe-path}/../exts/omni.isaac.app.setup/data/nvidia-omniverse-isaacsim.ico" +iconPath = "${omni.isaac.kit}/data/omni.isaac.sim.png" + +# width = 1700 +# height = 900 +# x = -1 +# y = -1 # Fonts [setting.app.font] file = "${fonts}/OpenSans-SemiBold.ttf" size = 16 +# [setting.app.runLoops] +# main.rateLimitEnabled = false +# main.rateLimitFrequency = 60 +# main.rateLimitUseBusyLoop = false +# rendering_0.rateLimitEnabled = false + [settings.exts.'omni.kit.window.extensions'] # List extensions here we want to show as featured when extension manager is opened featuredExts = [] -[settings.app.python] -# These disable the kit app from also printing out python output, which gets confusing -interceptSysStdOutput = false -logSysStdOutput = false [settings] # MGPU is always on, you can turn it from the settings, and force this off to save even more resource if you # only want to use a single GPU on your MGPU system -# False for Isaac Sim renderer.multiGpu.enabled = true renderer.multiGpu.autoEnable = true +# This setting forces all GPUs to copy their render results to the main GPU. +# This legacy setting should not be needed anymore +app.gatherRenderResults = false 'rtx-transient'.resourcemanager.enableTextureStreaming = true # app.hydra.aperture.conform = 4 # in 105.1 pixels are square by default app.hydraEngine.waitIdle = false @@ -246,9 +182,6 @@ rtx.newDenoiser.enabled = true # Enable Iray and pxr by setting this to "rtx,iray,pxr" renderer.enabled = "rtx" -# Disable the simulation output window popup -physics.autoPopupSimulationOutputWindow=false - ### async rendering settings omni.replicator.asyncRendering = false app.asyncRendering = false @@ -274,7 +207,17 @@ exts.omni.kit.renderer.core.present.presentAfterRendering = false persistent.app.viewport.defaults.tickRate = 120 rtx-transient.dlssg.enabled = false -privacy.externalBuild = true +app.audio.enabled = false + +# Enable Vulkan - avoids torch+cu12 error on windows +app.vulkan = true + +# Basic Kit App +################################ +app.versionFile = "${exe-path}/VERSION" +app.folder = "${exe-path}/" +app.name = "Isaac-Sim" +app.version = "4.0.0" # hide NonToggleable Exts exts."omni.kit.window.extensions".hideNonToggleableExts = true @@ -285,53 +228,23 @@ exts."omni.kit.window.extensions".showFeatureOnly = false # app.hangDetector.enabled = false # app.hangDetector.timeout = 120 -############ -# Browsers # -############ -exts."omni.kit.browser.material".folders = [ - "Base::http://omniverse-content-production.s3-us-west-2.amazonaws.com/Materials/Base", - "vMaterials::http://omniverse-content-production.s3.us-west-2.amazonaws.com/Materials/vMaterials_2/", - "Twinbru Fabrics::https://twinbru.s3.eu-west-1.amazonaws.com/omniverse/Twinbru Fabrics/" -] - -exts."omni.kit.window.environment".folders = [ - "https://omniverse-content-production.s3.us-west-2.amazonaws.com/Assets/Skies/2022_1/Skies", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Scenes/Templates", -] - -exts."omni.kit.browser.sample".folders = [ - "http://omniverse-content-production.s3-us-west-2.amazonaws.com//Samples" -] - -exts."omni.kit.browser.asset".folders = [ - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Vegetation", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/ArchVis/Commercial", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/ArchVis/Industrial", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/ArchVis/Residential", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Equipment", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Safety", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Shipping", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/DigitalTwin/Assets/Warehouse/Storage", -] - -exts."omni.kit.browser.texture".folders = [ - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Vegetation", -] +# RTX Settings +############################### +[settings.rtx] +translucency.worldEps = 0.005 -############################ -# Content Browser Settings # -############################ +# Content Browser +############################### [settings.exts."omni.kit.window.content_browser"] enable_thumbnail_generation_images = false # temp fix to avoid leaking python processes -####################### -# Extensions Settings # -####################### +# Extensions +############################### [settings.exts."omni.kit.registry.nucleus"] registries = [ - { name = "kit/default", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/shared" }, - { name = "kit/sdk", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/sdk/${kit_version_short}/${kit_git_hash}" }, - { name = "kit/community", url = "https://dw290v42wisod.cloudfront.net/exts/kit/community" }, + { name = "kit/default", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/shared" }, + { name = "kit/sdk", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/sdk/${kit_version_short}/${kit_git_hash}" }, + { name = "kit/community", url = "https://dw290v42wisod.cloudfront.net/exts/kit/community" }, ] [settings.app.extensions] @@ -355,6 +268,7 @@ colors.client = 0x0F0F0F respondOnMouseUp = true changeWindowRegion = true + # Register extension folder from this repo in kit [settings.app.exts] folders = [ @@ -362,7 +276,10 @@ folders = [ "${exe-path}/extscore", # kit core extensions "${exe-path}/../exts", # isaac extensions "${exe-path}/../extscache", # isaac cache extensions - "${exe-path}/../extsPhysics", # isaac physics extensions, + "${exe-path}/../extsPhysics", # isaac physics extensions + "${exe-path}/../isaacsim/exts", # isaac extensions for pip + "${exe-path}/../isaacsim/extscache", # isaac cache extensions for pip + "${exe-path}/../isaacsim/extsPhysics", # isaac physics extensions for pip "${app}", # needed to find other app files "${app}/../extensions", # needed to find extensions in Isaac Lab ] @@ -370,9 +287,8 @@ folders = [ [settings.crashreporter.data] experience = "Isaac Sim Python" -###################### -# Isaac Sim Settings # -###################### +# Isaac Sim Settings +############################### [settings.app.renderer] skipWhileMinimized = false sleepMsOnFocus = 0 @@ -391,17 +307,13 @@ defaultCamPos.x = 5 defaultCamPos.y = 5 defaultCamPos.z = 5 -################ -# RTX Settings # -################ [settings.rtx] -translucency.worldEps = 0.005 raytracing.fractionalCutoutOpacity = false hydra.enableSemanticSchema = true +mdltranslator.mdlDistilling = false # descriptorSets=60000 # reservedDescriptors=500000 # sceneDb.maxInstances=1000000 - # Enable this for static scenes, improves visual quality # directLighting.sampledLighting.enabled = true @@ -429,13 +341,8 @@ simulation.defaultMetersPerUnit = 1.0 omnigraph.updateToUsd = false omnigraph.useSchemaPrims = true omnigraph.disablePrimNodes = true -physics.updateToUsd = true -physics.updateVelocitiesToUsd = true -physics.useFastCache = false -physics.visualizationDisplayJoints = false -physics.visualizationSimulationOutput = false omni.replicator.captureOnPlay = true -omnihydra.useSceneGraphInstancing = true +exts."omni.anim.navigation.core".navMesh.viewNavMesh = false renderer.startupMessageDisplayed = true # hides the IOMMU popup window @@ -443,39 +350,24 @@ renderer.startupMessageDisplayed = true # hides the IOMMU popup window app.omniverse.content_browser.options_menu.show_details = true app.omniverse.filepicker.options_menu.show_details = true +# Performance improvement +resourcemonitor.timeBetweenQueries = 100 [settings.ngx] enabled=true # Enable this for DLSS -######################## -# Isaac Sim Extensions # -######################## -[dependencies] -"omni.isaac.core" = {} -"omni.isaac.core_archive" = {} -"omni.pip.compute" = {} -"omni.pip.cloud" = {} -"omni.isaac.kit" = {} -"omni.isaac.ml_archive" = {} -"omni.kit.loop-isaac" = {} -"omni.isaac.utils" = {} -"omni.kit.property.isaac" = {} - -"omni.isaac.cloner" = {} -"omni.isaac.debug_draw" = {} - - -# linux only extensions -[dependencies."filter:platform"."linux-x86_64"] -# "omni.isaac.ocs2" = {} - -############################ -# Non-Isaac Sim Extensions # -############################ -[dependencies] -"omni.syntheticdata" = {} -"semantics.schema.editor" = {} -"semantics.schema.property" = {} -"omni.replicator.core" = {} -# "omni.importer.mjcf" = {} -# "omni.importer.urdf" = {} +[settings.physics] +autoPopupSimulationOutputWindow=false +updateToUsd = false +updateVelocitiesToUsd = false +updateParticlesToUsd = false +updateVelocitiesToUsd = false +updateForceSensorsToUsd = false +outputVelocitiesLocalSpace = false +useFastCache = false +visualizationDisplayJoints = false +visualizationSimulationOutput = false +fabricUpdateTransformations = false +fabricUpdateVelocities = false +fabricUpdateForceSensors = false +fabricUpdateJointStates = false diff --git a/source/apps/isaaclab.python.headless.multicam.kit b/source/apps/isaaclab.python.rendering.kit similarity index 74% rename from source/apps/isaaclab.python.headless.multicam.kit rename to source/apps/isaaclab.python.rendering.kit index 9eae93543a..38dcc67d92 100644 --- a/source/apps/isaaclab.python.headless.multicam.kit +++ b/source/apps/isaaclab.python.rendering.kit @@ -7,41 +7,22 @@ ## [package] -title = "Isaac Sim Python - Minimal (efficient rendering)" -description = "A minimal app for running standalone scripts with efficient camera rendering settings." -version = "2023.1.1" +title = "Isaac Lab Python Camera" +description = "An app for running Isaac Lab with rendering enabled" +version = "1.0.0" # That makes it browsable in UI with "experience" filter keywords = ["experience", "app", "isaaclab", "python", "camera", "minimal"] [dependencies] # Isaac Lab minimal app -"isaaclab.python.headless" = {} +"isaaclab.python" = {} # PhysX "omni.kit.property.physx" = {} -"omni.kit.property.bundle" = {} # Rendering "omni.kit.material.library" = {} -"omni.kit.viewport.rtx" = {} -"omni.kit.viewport.bundle" = {} - -# Windows -"omni.kit.window.file" = {} -"omni.kit.window.status_bar" = {} -"omni.kit.window.title" = {} -"omni.kit.window.extensions" = {} -"omni.kit.window.toolbar" = {} -"omni.kit.window.stage" = {} - -# Menus -"omni.kit.menu.utils" = {} -"omni.kit.menu.file" = {} -"omni.kit.menu.edit" = {} -"omni.kit.menu.create" = {} -"omni.kit.menu.common" = {} -"omni.kit.menu.stage" = {} [settings] @@ -51,7 +32,7 @@ keywords = ["experience", "app", "isaaclab", "python", "camera", "minimal"] app.versionFile = "${exe-path}/VERSION" app.folder = "${exe-path}/" app.name = "Isaac-Sim" -app.version = "2023.1.1" +app.version = "4.0.0" # set the default ros bridge to disable on startup isaac.startup.ros_bridge_extension = "" @@ -80,6 +61,22 @@ omni.replicator.asyncRendering = false app.renderer.waitIdle=true app.hydraEngine.waitIdle=true +app.audio.enabled = false + +[settings.physics] +updateToUsd = false +updateParticlesToUsd = false +updateVelocitiesToUsd = false +updateForceSensorsToUsd = false +outputVelocitiesLocalSpace = false +useFastCache = false +visualizationDisplayJoints = false +fabricUpdateTransformations = false +fabricUpdateVelocities = false +fabricUpdateForceSensors = false +fabricUpdateJointStates = false + + [settings.exts."omni.kit.registry.nucleus"] registries = [ { name = "kit/default", url = "https://ovextensionsprod.blob.core.windows.net/exts/kit/prod/shared" }, @@ -87,6 +84,11 @@ registries = [ { name = "kit/community", url = "https://dw290v42wisod.cloudfront.net/exts/kit/community" }, ] +[settings.app.python] +# These disable the kit app from also printing out python output, which gets confusing +interceptSysStdOutput = false +logSysStdOutput = false + [settings.app.renderer] skipWhileMinimized = false sleepMsOnFocus = 0 @@ -99,7 +101,10 @@ folders = [ "${exe-path}/extscore", # kit core extensions "${exe-path}/../exts", # isaac extensions "${exe-path}/../extscache", # isaac cache extensions - "${exe-path}/../extsPhysics", # isaac physics extensions, + "${exe-path}/../extsPhysics", # isaac physics extensions + "${exe-path}/../isaacsim/exts", # isaac extensions for pip + "${exe-path}/../isaacsim/extscache", # isaac cache extensions for pip + "${exe-path}/../isaacsim/extsPhysics", # isaac physics extensions for pip "${app}", # needed to find other app files "${app}/../extensions", # needed to find extensions in Isaac Lab ] diff --git a/source/extensions/omni.isaac.lab/config/extension.toml b/source/extensions/omni.isaac.lab/config/extension.toml index c83446ccb9..2aba04811d 100644 --- a/source/extensions/omni.isaac.lab/config/extension.toml +++ b/source/extensions/omni.isaac.lab/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.16.5" +version = "0.17.11" # Description title = "Isaac Lab framework for Robot Learning" diff --git a/source/extensions/omni.isaac.lab/docs/CHANGELOG.rst b/source/extensions/omni.isaac.lab/docs/CHANGELOG.rst index 82e6702537..c4f97a7621 100644 --- a/source/extensions/omni.isaac.lab/docs/CHANGELOG.rst +++ b/source/extensions/omni.isaac.lab/docs/CHANGELOG.rst @@ -1,8 +1,9 @@ Changelog --------- -0.16.5 (2024-05-22) -~~~~~~~~~~~~~~~~~~~ + +0.17.11 (2024-05-30) +~~~~~~~~~~~~~~~~~~~~ Fixed ^^^^^ @@ -12,8 +13,8 @@ Fixed :meth:`omni.isaac.lab.sensor.ContactSensor._debug_vis_callback` is called which references it. -0.16.4 (2024-05-15) -~~~~~~~~~~~~~~~~~~~ +0.17.10 (2024-05-30) +~~~~~~~~~~~~~~~~~~~~ Fixed ^^^^^ @@ -23,7 +24,7 @@ Fixed compound objects were reflected across all instances generated from the same ``default_factory`` method. -0.16.3 (2024-05-13) +0.17.9 (2024-05-30) ~~~~~~~~~~~~~~~~~~~ Added @@ -33,7 +34,139 @@ Added variants when loading assets from USD files. -0.16.2 (2024-04-26) +0.17.8 (2024-05-28) +~~~~~~~~~~~~~~~~~~~ + +Fixed +^^^^^ + +* Implemented the reset methods in the action terms to avoid returning outdated data. + + +0.17.7 (2024-05-28) +~~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Added debug visualization utilities in the :class:`omni.isaac.lab.managers.ActionManager` class. + + +0.17.6 (2024-05-27) +~~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Added ``wp.init()`` call in Warp utils. + + +0.17.5 (2024-05-22) +~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Websocket livestreaming is no longer supported. Valid livestream options are {0, 1, 2}. +* WebRTC livestream is now set with livestream=2. + + +0.17.4 (2024-05-17) +~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Modified the noise functions to also support add, scale, and abs operations on the data. Added aliases + to ensure backward compatibility with the previous functions. + + * Added :attr:`omni.isaac.lab.utils.noise.NoiseCfg.operation` for the different operations. + * Renamed ``constant_bias_noise`` to :func:`omni.isaac.lab.utils.noise.constant_noise`. + * Renamed ``additive_uniform_noise`` to :func:`omni.isaac.lab.utils.noise.uniform_noise`. + * Renamed ``additive_gaussian_noise`` to :func:`omni.isaac.lab.utils.noise.gaussian_noise`. + + +0.17.3 (2024-05-15) +~~~~~~~~~~~~~~~~~~~ + +Fixed +^^^^^ + +* Set ``hide_ui`` flag in the app launcher for livestream. +* Fix native client livestream extensions. + + +0.17.2 (2024-05-09) +~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Renamed ``_range`` to ``distribution_params`` in ``events.py`` for methods that defined a distribution. +* Apply additive/scaling randomization noise on default data instead of current data. +* Changed material bucketing logic to prevent exceeding 64k materials. + +Fixed +^^^^^ + +* Fixed broadcasting issues with indexing when environment and joint IDs are provided. +* Fixed incorrect tensor dimensions when setting a subset of environments. + +Added +^^^^^ + +* Added support for randomization of fixed tendon parameters. +* Added support for randomization of dof limits. +* Added support for randomization of gravity. +* Added support for Gaussian sampling. +* Added default buffers to Articulation/Rigid object data classes for randomization. + + +0.17.1 (2024-05-10) +~~~~~~~~~~~~~~~~~~~ + +Fixed +^^^^^ + +* Added attribute :attr:`omni.isaac.lab.sim.converters.UrdfConverterCfg.override_joint_dynamics` to properly parse + joint dynamics in :class:`omni.isaac.lab.sim.converters.UrdfConverter`. + + +0.17.0 (2024-05-07) +~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Renamed ``BaseEnv`` to :class:`omni.isaac.lab.envs.ManagerBasedEnv`. +* Renamed ``base_env.py`` to ``manager_based_env.py``. +* Renamed ``BaseEnvCfg`` to :class:`omni.isaac.lab.envs.ManagerBasedEnvCfg`. +* Renamed ``RLTaskEnv`` to :class:`omni.isaac.lab.envs.ManagerBasedRLEnv`. +* Renamed ``rl_task_env.py`` to ``manager_based_rl_env.py``. +* Renamed ``RLTaskEnvCfg`` to :class:`omni.isaac.lab.envs.ManagerBasedRLEnvCfg`. +* Renamed ``rl_task_env_cfg.py`` to ``rl_env_cfg.py``. +* Renamed ``OIGEEnv`` to :class:`omni.isaac.lab.envs.DirectRLEnv`. +* Renamed ``oige_env.py`` to ``direct_rl_env.py``. +* Renamed ``RLTaskEnvWindow`` to :class:`omni.isaac.lab.envs.ui.ManagerBasedRLEnvWindow`. +* Renamed ``rl_task_env_window.py`` to ``manager_based_rl_env_window.py``. +* Renamed all references of ``BaseEnv``, ``BaseEnvCfg``, ``RLTaskEnv``, ``RLTaskEnvCfg``, ``OIGEEnv``, and ``RLTaskEnvWindow``. + +Added +^^^^^ + +* Added direct workflow base class :class:`omni.isaac.lab.envs.DirectRLEnv`. + + +0.16.4 (2024-05-06) +~~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Added :class:`omni.isaac.lab.sensors.TiledCamera` to support tiled rendering with RGB and depth. + + +0.16.3 (2024-04-26) ~~~~~~~~~~~~~~~~~~~ Fixed @@ -44,6 +177,16 @@ Fixed regex expressions instead of glob expressions. +0.16.2 (2024-04-25) +~~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Simplified the installation procedure, isaaclab -e is no longer needed +* Updated torch dependency to 2.2.2 + + 0.16.1 (2024-04-20) ~~~~~~~~~~~~~~~~~~~ @@ -92,6 +235,16 @@ Deprecated operations (add, scale, or set) and sampling distributions. +0.15.13 (2024-04-16) +~~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Improved startup performance by enabling rendering-based extensions only when necessary and caching of nucleus directory. +* Renamed the flag ``OFFSCREEN_RENDER`` or ``--offscreen_render`` to ``ENABLE_CAMERAS`` or ``--enable_cameras`` respectively. + + 0.15.12 (2024-04-16) ~~~~~~~~~~~~~~~~~~~~ @@ -1283,9 +1436,9 @@ Added Added ^^^^^ -* Created :class:`omni.issac.lab.sim.converters.asset_converter.AssetConverter` to serve as a base +* Created :class:`omni.isaac.lab.sim.converters.asset_converter.AssetConverter` to serve as a base class for all asset converters. -* Added :class:`omni.issac.lab.sim.converters.mesh_converter.MeshConverter` to handle loading and conversion +* Added :class:`omni.isaac.lab.sim.converters.mesh_converter.MeshConverter` to handle loading and conversion of mesh files (OBJ, STL and FBX) into USD format. * Added script ``convert_mesh.py`` to ``source/tools`` to allow users to convert a mesh to USD via command line arguments. @@ -1567,7 +1720,7 @@ Added Changed ^^^^^^^ -* Adapted all the sensor classes to follow a structure similar to the :class:`omni.issac.lab.assets.AssetBase`. +* Adapted all the sensor classes to follow a structure similar to the :class:`omni.isaac.lab.assets.AssetBase`. Hence, the spawning and initialization of sensors manually by the users is avoided. * Removed the :meth:`debug_vis` function since that this functionality is handled by a render callback automatically (based on the passed configuration for the :class:`omni.isaac.lab.sensors.SensorBaseCfg.debug_vis` flag). diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/app/app_launcher.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/app/app_launcher.py index ffab12492d..b1b39434c8 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/app/app_launcher.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/app/app_launcher.py @@ -13,6 +13,7 @@ """ import argparse +import contextlib import faulthandler import os import re @@ -20,6 +21,9 @@ import sys from typing import Any, Literal +with contextlib.suppress(ModuleNotFoundError): + import isaacsim # noqa: F401 + from omni.isaac.kit import SimulationApp @@ -98,10 +102,15 @@ def __init__(self, launcher_args: argparse.Namespace | dict | None = None, **kwa # Define config members that are read from env-vars or keyword args self._headless: bool # 0: GUI, 1: Headless - self._livestream: Literal[0, 1, 2, 3] # 0: Disabled, 1: Native, 2: Websocket, 3: WebRTC + self._livestream: Literal[0, 1, 2] # 0: Disabled, 1: Native, 2: WebRTC self._offscreen_render: bool # 0: Disabled, 1: Enabled self._sim_experience_file: str # Experience file to load + # Exposed to train scripts + self.device_id: int # device ID for GPU simulation (defaults to 0) + self.local_rank: int # local rank of GPUs in the current node + self.global_rank: int # global rank for multi-node training + # Integrate env-vars and input keyword args into simulation app config self._config_resolution(launcher_args) # Create SimulationApp, passing the resolved self._config to it for initialization @@ -150,19 +159,23 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: * ``headless`` (bool): If True, the app will be launched in headless (no-gui) mode. The values map the same as that for the ``HEADLESS`` environment variable. If False, then headless mode is determined by the ``HEADLESS`` environment variable. - * ``livestream`` (int): If one of {0, 1, 2, 3}, then livestreaming and headless mode is enabled. The values + * ``livestream`` (int): If one of {0, 1, 2}, then livestreaming and headless mode is enabled. The values map the same as that for the ``LIVESTREAM`` environment variable. If :obj:`-1`, then livestreaming is determined by the ``LIVESTREAM`` environment variable. - * ``offscreen_render`` (bool): If True, the app will be launched in offscreen-render mode. The values - map the same as that for the ``OFFSCREEN_RENDER`` environment variable. If False, then offscreen-render - mode is determined by the ``OFFSCREEN_RENDER`` environment variable. + * ``enable_cameras`` (bool): If True, the app will enable camera sensors and render them, even when in + headless mode. This flag must be set to True if the environments contains any camera sensors. + The values map the same as that for the ``ENABLE_CAMERAS`` environment variable. + If False, then enable_cameras mode is determined by the ``ENABLE_CAMERAS`` environment variable. + * ``device_id`` (int): If specified, simulation will run on the specified GPU device. * ``experience`` (str): The experience file to load when launching the SimulationApp. If a relative path is provided, it is resolved relative to the ``apps`` folder in Isaac Sim and Isaac Lab (in that order). If provided as an empty string, the experience file is determined based on the headless flag: - * If headless is True, the experience file is set to ``isaaclab.python.headless.kit``. - * If headless is False, the experience file is set to ``isaaclab.python.kit``. + * If headless and enable_cameras are True, the experience file is set to ``isaaclab.python.headless.rendering.kit``. + * If headless is False and enable_cameras is True, the experience file is set to ``isaaclab.python.rendering.kit``. + * If headless is False and enable_cameras is False, the experience file is set to ``isaaclab.python.kit``. + * If headless is True and enable_cameras is False, the experience file is set to ``isaaclab.python.headless.kit``. Args: parser: An argument parser instance to be extended with the AppLauncher specific options. @@ -213,14 +226,20 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: "--livestream", type=int, default=AppLauncher._APPLAUNCHER_CFG_INFO["livestream"][1], - choices={0, 1, 2, 3}, + choices={0, 1, 2}, help="Force enable livestreaming. Mapping corresponds to that for the `LIVESTREAM` environment variable.", ) arg_group.add_argument( - "--offscreen_render", + "--enable_cameras", action="store_true", - default=AppLauncher._APPLAUNCHER_CFG_INFO["offscreen_render"][1], - help="Enable offscreen rendering when running without a GUI.", + default=AppLauncher._APPLAUNCHER_CFG_INFO["enable_cameras"][1], + help="Enable camera sensors and relevant extension dependencies.", + ) + arg_group.add_argument( + "--device_id", + type=int, + default=AppLauncher._APPLAUNCHER_CFG_INFO["device_id"][1], + help="GPU device ID used for running simulation.", ) arg_group.add_argument( "--verbose", # Note: This is read by SimulationApp through sys.argv @@ -251,7 +270,8 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: _APPLAUNCHER_CFG_INFO: dict[str, tuple[list[type], Any]] = { "headless": ([bool], False), "livestream": ([int], -1), - "offscreen_render": ([bool], False), + "enable_cameras": ([bool], False), + "device_id": ([int], 0), "experience": ([str], ""), } """A dictionary of arguments added manually by the :meth:`AppLauncher.add_app_launcher_args` method. @@ -267,6 +287,7 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: # it is ambiguous where the default types are None _SIM_APP_CFG_TYPES: dict[str, list[type]] = { "headless": [bool], + "hide_ui": [bool, type(None)], "active_gpu": [int, type(None)], "physics_gpu": [int], "multi_gpu": [bool], @@ -352,7 +373,7 @@ def _config_resolution(self, launcher_args: dict): # livestream_env = int(os.environ.get("LIVESTREAM", 0)) livestream_arg = launcher_args.pop("livestream", AppLauncher._APPLAUNCHER_CFG_INFO["livestream"][1]) - livestream_valid_vals = {0, 1, 2, 3} + livestream_valid_vals = {0, 1, 2} # Value checking on LIVESTREAM if livestream_env not in livestream_valid_vals: raise ValueError( @@ -393,7 +414,7 @@ def _config_resolution(self, launcher_args: dict): # Note: Headless is always true when livestreaming if headless_arg is True: self._headless = headless_arg - elif self._livestream in {1, 2, 3}: + elif self._livestream in {1, 2}: # we are always headless on the host machine self._headless = True # inform who has toggled the headless flag @@ -413,24 +434,43 @@ def _config_resolution(self, launcher_args: dict): # Headless needs to be passed to the SimulationApp so we keep it here launcher_args["headless"] = self._headless - # --OFFSCREEN_RENDER logic-- + # --enable_cameras logic-- # - # off-screen rendering - offscreen_render_env = int(os.environ.get("OFFSCREEN_RENDER", 0)) - offscreen_render_arg = launcher_args.pop( - "offscreen_render", AppLauncher._APPLAUNCHER_CFG_INFO["offscreen_render"][1] - ) - offscreen_render_valid_vals = {0, 1} - if offscreen_render_env not in offscreen_render_valid_vals: + enable_cameras_env = int(os.environ.get("ENABLE_CAMERAS", 0)) + enable_cameras_arg = launcher_args.pop("enable_cameras", AppLauncher._APPLAUNCHER_CFG_INFO["enable_cameras"][1]) + enable_cameras_valid_vals = {0, 1} + if enable_cameras_env not in enable_cameras_valid_vals: raise ValueError( - f"Invalid value for environment variable `OFFSCREEN_RENDER`: {offscreen_render_env} ." - f"Expected: {offscreen_render_valid_vals} ." + f"Invalid value for environment variable `ENABLE_CAMERAS`: {enable_cameras_env} ." + f"Expected: {enable_cameras_valid_vals} ." ) - # We allow offscreen_render kwarg to supersede OFFSCREEN_RENDER envvar - if offscreen_render_arg is True: - self._offscreen_render = offscreen_render_arg + # We allow enable_cameras kwarg to supersede ENABLE_CAMERAS envvar + if enable_cameras_arg is True: + self._enable_cameras = enable_cameras_arg else: - self._offscreen_render = bool(offscreen_render_env) + self._enable_cameras = bool(enable_cameras_env) + self._offscreen_render = False + if self._enable_cameras and self._headless: + self._offscreen_render = True + # hide_ui flag + launcher_args["hide_ui"] = False + if self._headless and not self._livestream: + launcher_args["hide_ui"] = True + + # --simulation GPU device logic -- + self.device_id = launcher_args.pop("device_id", AppLauncher._APPLAUNCHER_CFG_INFO["device_id"][1]) + if "distributed" in launcher_args: + distributed_train = launcher_args["distributed"] + # local rank (GPU id) in a current multi-gpu mode + self.local_rank = int(os.getenv("LOCAL_RANK", "0")) + # global rank (GPU id) in multi-gpu multi-node mode + self.global_rank = int(os.getenv("RANK", "0")) + if distributed_train: + self.device_id = self.local_rank + launcher_args["multi_gpu"] = False + # set physics and rendering device + launcher_args["physics_gpu"] = self.device_id + launcher_args["active_gpu"] = self.device_id # Check if input keywords contain an 'experience' file setting # Note: since experience is taken as a separate argument by Simulation App, we store it separately @@ -438,10 +478,17 @@ def _config_resolution(self, launcher_args: dict): # If nothing is provided resolve the experience file based on the headless flag kit_app_exp_path = os.environ["EXP_PATH"] - isaaclab_app_exp_path = os.path.join(os.environ["ISAACLAB_PATH"], "source", "apps") + isaaclab_app_exp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), *[".."] * 6, "apps") if self._sim_experience_file == "": - # check if the headless flag is set - if self._headless and not self._livestream: + # check if the headless flag is setS + if self._enable_cameras: + if self._headless and not self._livestream: + self._sim_experience_file = os.path.join( + isaaclab_app_exp_path, "isaaclab.python.headless.rendering.kit" + ) + else: + self._sim_experience_file = os.path.join(isaaclab_app_exp_path, "isaaclab.python.rendering.kit") + elif self._headless and not self._livestream: self._sim_experience_file = os.path.join(isaaclab_app_exp_path, "isaaclab.python.headless.kit") else: self._sim_experience_file = os.path.join(isaaclab_app_exp_path, "isaaclab.python.kit") @@ -491,10 +538,16 @@ def _create_app(self): for key, value in hacked_modules.items(): sys.modules[key] = value + def _rendering_enabled(self): + # Indicates whether rendering is required by the app. + # Extensions required for rendering bring startup and simulation costs, so we do not enable them if not required. + return not self._headless or self._livestream >= 1 or self._enable_cameras + def _load_extensions(self): """Load correct extensions based on AppLauncher's resolved config member variables.""" # These have to be loaded after SimulationApp is initialized import carb + import omni.physx.bindings._physx as physx_impl from omni.isaac.core.utils.extensions import enable_extension # Retrieve carb settings for modification @@ -514,18 +567,15 @@ def _load_extensions(self): if self._livestream == 1: # Enable Native Livestream extension # Default App: Streaming Client from the Omniverse Launcher - enable_extension("omni.kit.livestream.native") - enable_extension("omni.services.streaming.manager") + enable_extension("omni.kit.streamsdk.plugins-3.2.1") + enable_extension("omni.kit.livestream.core-3.2.0") + enable_extension("omni.kit.livestream.native-4.1.0") elif self._livestream == 2: - # Enable WebSocket Livestream extension - # Default URL: http://localhost:8211/streaming/client/ - enable_extension("omni.services.streamclient.websocket") - elif self._livestream == 3: # Enable WebRTC Livestream extension # Default URL: http://localhost:8211/streaming/webrtc-client/ enable_extension("omni.services.streamclient.webrtc") else: - raise ValueError(f"Invalid value for livestream: {self._livestream}. Expected: 1, 2, 3 .") + raise ValueError(f"Invalid value for livestream: {self._livestream}. Expected: 1, 2 .") else: carb_settings_iface.set_bool("/app/livestream/enabled", False) @@ -539,45 +589,23 @@ def _load_extensions(self): # for example: the `Camera` sensor class carb_settings_iface.set_bool("/isaaclab/render/rtx_sensors", False) - # enable extensions for off-screen rendering - # Depending on the app file, some extensions might not be available in it. - # Thus, we manually enable these extensions to make sure they are available. - # note: enabling extensions is order-sensitive. please do not change the order! - if self._offscreen_render or not self._headless or self._livestream >= 1: - # extension to enable UI buttons (otherwise we get attribute errors) - enable_extension("omni.kit.window.toolbar") - - if self._offscreen_render or not self._headless: - # extension to make RTX realtime and path-traced renderers - enable_extension("omni.kit.viewport.rtx") - # extension to make HydraDelegate renderers - enable_extension("omni.kit.viewport.pxr") - # enable viewport extension if full rendering is enabled - enable_extension("omni.kit.viewport.bundle") - # extension for window status bar - enable_extension("omni.kit.window.status_bar") - # enable replicator extension - # note: moved here since it requires to have the viewport extension to be enabled first. - enable_extension("omni.replicator.core") - # enable UI tools - # note: we need to always import this even with headless to make - # the module for orbit.envs.ui work - enable_extension("omni.isaac.ui") - # enable animation recording extension - if not self._headless or self._livestream >= 1: - enable_extension("omni.kit.stagerecorder.core") + # set fabric update flag to disable updating transforms when rendering is disabled + carb_settings_iface.set_bool("/physics/fabricUpdateTransformations", self._rendering_enabled()) # set the nucleus directory manually to the latest published Nucleus # note: this is done to ensure prior versions of Isaac Sim still use the latest assets carb_settings_iface.set_string( "/persistent/isaac/asset_root/default", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/2023.1.1", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/4.0", ) carb_settings_iface.set_string( "/persistent/isaac/asset_root/nvidia", - "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/2023.1.1", + "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/4.0", ) + # disable physics backwards compatibility check + carb_settings_iface.set_int(physx_impl.SETTING_BACKWARD_COMPATIBILITY, 0) + def _hide_stop_button(self): """Hide the stop button in the toolbar. diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation.py index eb550dfe4e..65ea74ad0b 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation.py @@ -15,9 +15,10 @@ from typing import TYPE_CHECKING import carb +import omni.isaac.core.utils.stage as stage_utils import omni.physics.tensors.impl.api as physx from omni.isaac.core.utils.types import ArticulationActions -from pxr import UsdPhysics +from pxr import PhysxSchema, UsdPhysics import omni.isaac.lab.sim as sim_utils import omni.isaac.lab.utils.math as math_utils @@ -218,7 +219,8 @@ def update(self, dt: float): # -- joint states self._data.joint_pos[:] = self.root_physx_view.get_dof_positions() self._data.joint_vel[:] = self.root_physx_view.get_dof_velocities() - self._data.joint_acc[:] = (self._data.joint_vel - self._previous_joint_vel) / dt + if dt > 0.0: + self._data.joint_acc[:] = (self._data.joint_vel - self._previous_joint_vel) / dt # -- update common data # note: these are computed in the base class @@ -249,6 +251,29 @@ def find_joints( # find joints return string_utils.resolve_matching_names(name_keys, joint_subset, preserve_order) + def find_fixed_tendons( + self, name_keys: str | Sequence[str], tendon_subsets: list[str] | None = None, preserve_order: bool = False + ) -> tuple[list[int], list[str]]: + """Find fixed tendons in the articulation based on the name keys. + + Please see the :func:`omni.isaac.orbit.utils.string.resolve_matching_names` function for more information + on the name matching. + + Args: + name_keys: A regular expression or a list of regular expressions to match the joint names with fixed tendons. + tendon_subsets: A subset of joints with fixed tendons to search for. Defaults to None, which means all joints + in the articulation are searched. + preserve_order: Whether to preserve the order of the name keys in the output. Defaults to False. + + Returns: + A tuple of lists containing the tendon indices and names. + """ + if tendon_subsets is None: + # tendons follow the joint names they are attached to + tendon_subsets = self.fixed_tendon_names + # find tendons + return string_utils.resolve_matching_names(name_keys, tendon_subsets, preserve_order) + """ Operations - Setters. """ @@ -320,6 +345,8 @@ def write_joint_state_to_sim( physx_env_ids = self._ALL_INDICES if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set into internal buffers self._data.joint_pos[env_ids, joint_ids] = position self._data.joint_vel[env_ids, joint_ids] = velocity @@ -350,6 +377,8 @@ def write_joint_stiffness_to_sim( physx_env_ids = self._ALL_INDICES if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set into internal buffers self._data.joint_stiffness[env_ids, joint_ids] = stiffness # set into simulation @@ -378,6 +407,8 @@ def write_joint_damping_to_sim( physx_env_ids = self._ALL_INDICES if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set into internal buffers self._data.joint_damping[env_ids, joint_ids] = damping # set into simulation @@ -404,6 +435,8 @@ def write_joint_effort_limit_to_sim( physx_env_ids = self._ALL_INDICES if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # move tensor to cpu if needed if isinstance(limits, torch.Tensor): limits = limits.cpu() @@ -433,6 +466,8 @@ def write_joint_armature_to_sim( physx_env_ids = self._ALL_INDICES if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set into internal buffers self._data.joint_armature[env_ids, joint_ids] = armature # set into simulation @@ -458,11 +493,41 @@ def write_joint_friction_to_sim( physx_env_ids = self._ALL_INDICES if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set into internal buffers self._data.joint_friction[env_ids, joint_ids] = joint_friction # set into simulation self.root_physx_view.set_dof_friction_coefficients(self._data.joint_friction.cpu(), indices=physx_env_ids.cpu()) + def write_joint_limits_to_sim( + self, + limits: torch.Tensor | float, + joint_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Write joint limits into the simulation. + + Args: + limits: Joint limits. Shape is (len(env_ids), len(joint_ids), 2). + joint_ids: The joint indices to set the limits for. Defaults to None (all joints). + env_ids: The environment indices to set the limits for. Defaults to None (all environments). + """ + # note: This function isn't setting the values for actuator models. (#128) + # resolve indices + physx_env_ids = env_ids + if env_ids is None: + env_ids = slice(None) + physx_env_ids = self._ALL_INDICES + if joint_ids is None: + joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] + # set into internal buffers + self._data.joint_limits[env_ids, joint_ids] = limits + # set into simulation + self.root_physx_view.set_dof_limits(self._data.joint_limits.cpu(), indices=physx_env_ids.cpu()) + """ Operations - State. """ @@ -486,6 +551,8 @@ def set_joint_position_target( env_ids = slice(None) if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set targets self._data.joint_pos_target[env_ids, joint_ids] = target @@ -508,6 +575,8 @@ def set_joint_velocity_target( env_ids = slice(None) if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set targets self._data.joint_vel_target[env_ids, joint_ids] = target @@ -530,9 +599,202 @@ def set_joint_effort_target( env_ids = slice(None) if joint_ids is None: joint_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] # set targets self._data.joint_effort_target[env_ids, joint_ids] = target + def set_fixed_tendon_stiffness( + self, + stiffness: torch.Tensor, + fixed_tendon_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Set fixed tendon stiffness into internal buffers. + + .. note:: + This function does not apply the tendon stiffness to the simulation. It only fills the buffers with + the desired values. To apply the tendon stiffness, call the :meth:`write_fixed_tendon_properties_to_sim` function. + + Args: + stiffness: Fixed tendon stiffness. Shape is (len(env_ids), len(fixed_tendon_ids)). + fixed_tendon_ids: The tendon indices to set the stiffness for. Defaults to None (all fixed tendons). + env_ids: The environment indices to set the stiffness for. Defaults to None (all environments). + """ + # resolve indices + if env_ids is None: + env_ids = slice(None) + if fixed_tendon_ids is None: + fixed_tendon_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] + # set stiffness + self._data.fixed_tendon_stiffness[env_ids, fixed_tendon_ids] = stiffness + + def set_fixed_tendon_damping( + self, + damping: torch.Tensor, + fixed_tendon_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Set fixed tendon damping into internal buffers. + + .. note:: + This function does not apply the tendon damping to the simulation. It only fills the buffers with + the desired values. To apply the tendon damping, call the :meth:`write_fixed_tendon_properties_to_sim` function. + + Args: + damping: Fixed tendon damping. Shape is (len(env_ids), len(fixed_tendon_ids)). + fixed_tendon_ids: The tendon indices to set the damping for. Defaults to None (all fixed tendons). + env_ids: The environment indices to set the damping for. Defaults to None (all environments). + """ + # resolve indices + if env_ids is None: + env_ids = slice(None) + if fixed_tendon_ids is None: + fixed_tendon_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] + # set damping + self._data.fixed_tendon_damping[env_ids, fixed_tendon_ids] = damping + + def set_fixed_tendon_limit_stiffness( + self, + limit_stiffness: torch.Tensor, + fixed_tendon_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Set fixed tendon limit stiffness efforts into internal buffers. + + .. note:: + This function does not apply the tendon limit stiffness to the simulation. It only fills the buffers with + the desired values. To apply the tendon limit stiffness, call the :meth:`write_fixed_tendon_properties_to_sim` function. + + Args: + limit_stiffness: Fixed tendon limit stiffness. Shape is (len(env_ids), len(fixed_tendon_ids)). + fixed_tendon_ids: The tendon indices to set the limit stiffness for. Defaults to None (all fixed tendons). + env_ids: The environment indices to set the limit stiffness for. Defaults to None (all environments). + """ + # resolve indices + if env_ids is None: + env_ids = slice(None) + if fixed_tendon_ids is None: + fixed_tendon_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] + # set limit_stiffness + self._data.fixed_tendon_limit_stiffness[env_ids, fixed_tendon_ids] = limit_stiffness + + def set_fixed_tendon_limit( + self, + limit: torch.Tensor, + fixed_tendon_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Set fixed tendon limit efforts into internal buffers. + + .. note:: + This function does not apply the tendon limit to the simulation. It only fills the buffers with + the desired values. To apply the tendon limit, call the :meth:`write_fixed_tendon_properties_to_sim` function. + + Args: + limit: Fixed tendon limit. Shape is (len(env_ids), len(fixed_tendon_ids)). + fixed_tendon_ids: The tendon indices to set the limit for. Defaults to None (all fixed tendons). + env_ids: The environment indices to set the limit for. Defaults to None (all environments). + """ + # resolve indices + if env_ids is None: + env_ids = slice(None) + if fixed_tendon_ids is None: + fixed_tendon_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] + # set limit + self._data.fixed_tendon_limit[env_ids, fixed_tendon_ids] = limit + + def set_fixed_tendon_rest_length( + self, + rest_length: torch.Tensor, + fixed_tendon_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Set fixed tendon rest length efforts into internal buffers. + + .. note:: + This function does not apply the tendon rest length to the simulation. It only fills the buffers with + the desired values. To apply the tendon rest length, call the :meth:`write_fixed_tendon_properties_to_sim` function. + + Args: + rest_length: Fixed tendon rest length. Shape is (len(env_ids), len(fixed_tendon_ids)). + fixed_tendon_ids: The tendon indices to set the rest length for. Defaults to None (all fixed tendons). + env_ids: The environment indices to set the rest length for. Defaults to None (all environments). + """ + # resolve indices + if env_ids is None: + env_ids = slice(None) + if fixed_tendon_ids is None: + fixed_tendon_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] + # set rest_length + self._data.fixed_tendon_rest_length[env_ids, fixed_tendon_ids] = rest_length + + def set_fixed_tendon_offset( + self, + offset: torch.Tensor, + fixed_tendon_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Set fixed tendon offset efforts into internal buffers. + + .. note:: + This function does not apply the tendon offset to the simulation. It only fills the buffers with + the desired values. To apply the tendon offset, call the :meth:`write_fixed_tendon_properties_to_sim` function. + + Args: + offset: Fixed tendon offset. Shape is (len(env_ids), len(fixed_tendon_ids)). + fixed_tendon_ids: The tendon indices to set the offset for. Defaults to None (all fixed tendons). + env_ids: The environment indices to set the offset for. Defaults to None (all environments). + """ + # resolve indices + if env_ids is None: + env_ids = slice(None) + if fixed_tendon_ids is None: + fixed_tendon_ids = slice(None) + elif env_ids != slice(None): + env_ids = env_ids[:, None] + # set offset + self._data.fixed_tendon_offset[env_ids, fixed_tendon_ids] = offset + + def write_fixed_tendon_properties_to_sim( + self, + fixed_tendon_ids: Sequence[int] | slice | None = None, + env_ids: Sequence[int] | None = None, + ): + """Write fixed tendon properties into the simulation. + + Args: + fixed_tendon_ids: The fixed tendon indices to set the limits for. Defaults to None (all fixed tendons). + env_ids: The environment indices to set the limits for. Defaults to None (all environments). + """ + # resolve indices + physx_env_ids = env_ids + if env_ids is None: + physx_env_ids = self._ALL_INDICES + if fixed_tendon_ids is None: + fixed_tendon_ids = slice(None) + + # set into simulation + self.root_physx_view.set_fixed_tendon_properties( + self._data.fixed_tendon_stiffness, + self._data.fixed_tendon_damping, + self._data.fixed_tendon_limit_stiffness, + self._data.fixed_tendon_limit, + self._data.fixed_tendon_rest_length, + self._data.fixed_tendon_offset, + indices=physx_env_ids, + ) + """ Internal helper. """ @@ -604,8 +866,11 @@ def _initialize_impl(self): # process configuration self._process_cfg() self._process_actuators_cfg() + self._process_fixed_tendons() # validate configuration self._validate_cfg() + # update the robot data + self.update(0.0) # log joint information self._log_articulation_joint_info() @@ -632,13 +897,58 @@ def _create_buffers(self): self._data.joint_damping = torch.zeros_like(self._data.joint_pos) self._data.joint_armature = torch.zeros_like(self._data.joint_pos) self._data.joint_friction = torch.zeros_like(self._data.joint_pos) + self._data.joint_limits = torch.zeros(self.num_instances, self.num_joints, 2, device=self.device) # -- joint commands (explicit) self._data.computed_torque = torch.zeros_like(self._data.joint_pos) self._data.applied_torque = torch.zeros_like(self._data.joint_pos) + # -- tendons + if self.num_fixed_tendons > 0: + self._data.fixed_tendon_stiffness = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.fixed_tendon_damping = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.fixed_tendon_limit_stiffness = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.fixed_tendon_limit = torch.zeros( + self.num_instances, self.num_fixed_tendons, 2, device=self.device + ) + self._data.fixed_tendon_rest_length = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.fixed_tendon_offset = torch.zeros(self.num_instances, self.num_fixed_tendons, device=self.device) + # -- other data self._data.soft_joint_pos_limits = torch.zeros(self.num_instances, self.num_joints, 2, device=self.device) self._data.soft_joint_vel_limits = torch.zeros(self.num_instances, self.num_joints, device=self.device) self._data.gear_ratio = torch.ones(self.num_instances, self.num_joints, device=self.device) + # -- initialize default buffers + self._data.default_joint_stiffness = torch.zeros(self.num_instances, self.num_joints, device=self.device) + self._data.default_joint_damping = torch.zeros(self.num_instances, self.num_joints, device=self.device) + self._data.default_joint_armature = torch.zeros(self.num_instances, self.num_joints, device=self.device) + self._data.default_joint_friction = torch.zeros(self.num_instances, self.num_joints, device=self.device) + self._data.default_joint_limits = torch.zeros(self.num_instances, self.num_joints, 2, device=self.device) + if self.num_fixed_tendons > 0: + self._data.default_fixed_tendon_stiffness = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.default_fixed_tendon_damping = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.default_fixed_tendon_limit_stiffness = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.default_fixed_tendon_limit = torch.zeros( + self.num_instances, self.num_fixed_tendons, 2, device=self.device + ) + self._data.default_fixed_tendon_rest_length = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) + self._data.default_fixed_tendon_offset = torch.zeros( + self.num_instances, self.num_fixed_tendons, device=self.device + ) # soft joint position limits (recommended not to be too close to limits). joint_pos_limits = self.root_physx_view.get_dof_limits() @@ -670,6 +980,9 @@ def _process_cfg(self): ) self._data.default_joint_vel[:, indices_list] = torch.tensor(values_list, device=self.device) + self._data.default_joint_limits = self.root_physx_view.get_dof_limits().to(device=self.device).clone() + self._data.joint_limits = self._data.default_joint_limits.clone() + """ Internal helpers -- Actuators. """ @@ -738,6 +1051,14 @@ def _process_actuators_cfg(self): self.write_joint_armature_to_sim(actuator.armature, joint_ids=actuator.joint_indices) self.write_joint_friction_to_sim(actuator.friction, joint_ids=actuator.joint_indices) + # set the default joint parameters based on the changes from the actuators + self._data.default_joint_stiffness = self.root_physx_view.get_dof_stiffnesses().to(device=self.device).clone() + self._data.default_joint_damping = self.root_physx_view.get_dof_dampings().to(device=self.device).clone() + self._data.default_joint_armature = self.root_physx_view.get_dof_armatures().to(device=self.device).clone() + self._data.default_joint_friction = ( + self.root_physx_view.get_dof_friction_coefficients().to(device=self.device).clone() + ) + # perform some sanity checks to ensure actuators are prepared correctly total_act_joints = sum(actuator.num_joints for actuator in self.actuators.values()) if total_act_joints != (self.num_joints - self.num_fixed_tendons): @@ -746,6 +1067,28 @@ def _process_actuators_cfg(self): f" joints available: {total_act_joints} != {self.num_joints}." ) + def _process_fixed_tendons(self): + """Process fixed tendons.""" + self.fixed_tendon_names = list() + if self.num_fixed_tendons > 0: + stage = stage_utils.get_current_stage() + for j in range(self.num_joints): + usd_joint_path = self.root_physx_view.dof_paths[0][j] + # check whether joint has tendons - tendon name follows the joint name it is attached to + joint = UsdPhysics.Joint.Get(stage, usd_joint_path) + if joint.GetPrim().HasAPI(PhysxSchema.PhysxTendonAxisRootAPI): + joint_name = usd_joint_path.split("/")[-1] + self.fixed_tendon_names.append(joint_name) + + self._data.default_fixed_tendon_stiffness = self.root_physx_view.get_fixed_tendon_stiffnesses().clone() + self._data.default_fixed_tendon_damping = self.root_physx_view.get_fixed_tendon_dampings().clone() + self._data.default_fixed_tendon_limit_stiffness = ( + self.root_physx_view.get_fixed_tendon_limit_stiffnesses().clone() + ) + self._data.default_fixed_tendon_limit = self.root_physx_view.get_fixed_tendon_limits().clone() + self._data.default_fixed_tendon_rest_length = self.root_physx_view.get_fixed_tendon_rest_lengths().clone() + self._data.default_fixed_tendon_offset = self.root_physx_view.get_fixed_tendon_offsets().clone() + def _apply_actuator_model(self): """Processes joint commands for the articulation by forwarding them to the actuators. diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation_data.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation_data.py index 5eb5922adc..d273253228 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation_data.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/articulation/articulation_data.py @@ -71,6 +71,10 @@ class ArticulationData(RigidObjectData): which are then set into the simulation. """ + ## + # Joint properties. + ## + joint_stiffness: torch.Tensor = None """Joint stiffness provided to simulation. Shape is (num_instances, num_joints).""" @@ -83,6 +87,28 @@ class ArticulationData(RigidObjectData): joint_friction: torch.Tensor = None """Joint friction provided to simulation. Shape is (num_instances, num_joints).""" + joint_limits: torch.Tensor = None + """Joint limits provided to simulation. Shape is (num_instances, num_joints, 2).""" + + ## + # Default joint properties + ## + + default_joint_stiffness: torch.Tensor = None + """Default joint stiffness of all joints. Shape is (num_instances, num_joints).""" + + default_joint_damping: torch.Tensor = None + """Default joint damping of all joints. Shape is (num_instances, num_joints).""" + + default_joint_armature: torch.Tensor = None + """Default joint armature of all joints. Shape is (num_instances, num_joints).""" + + default_joint_friction: torch.Tensor = None + """Default joint friction of all joints. Shape is (num_instances, num_joints).""" + + default_joint_limits: torch.Tensor = None + """Default joint limits of all joints. Shape is (num_instances, num_joints, 2).""" + ## # Joint commands -- Explicit actuators. ## @@ -106,6 +132,50 @@ class ArticulationData(RigidObjectData): Note: The torques are zero for implicit actuator models. """ + ## + # Fixed tendon properties. + ## + + fixed_tendon_stiffness: torch.Tensor = None + """Fixed tendon stiffness provided to simulation. Shape is (num_instances, num_fixed_tendons).""" + + fixed_tendon_damping: torch.Tensor = None + """Fixed tendon damping provided to simulation. Shape is (num_instances, num_fixed_tendons).""" + + fixed_tendon_limit_stiffness: torch.Tensor = None + """Fixed tendon limit stiffness provided to simulation. Shape is (num_instances, num_fixed_tendons).""" + + fixed_tendon_rest_length: torch.Tensor = None + """Fixed tendon rest length provided to simulation. Shape is (num_instances, num_fixed_tendons).""" + + fixed_tendon_offset: torch.Tensor = None + """Fixed tendon offset provided to simulation. Shape is (num_instances, num_fixed_tendons).""" + + fixed_tendon_limit: torch.Tensor = None + """Fixed tendon limits provided to simulation. Shape is (num_instances, num_fixed_tendons, 2).""" + + ## + # Default fixed tendon properties + ## + + default_fixed_tendon_stiffness: torch.Tensor = None + """Default tendon stiffness of all tendons. Shape is (num_instances, num_fixed_tendons).""" + + default_fixed_tendon_damping: torch.Tensor = None + """Default tendon damping of all tendons. Shape is (num_instances, num_fixed_tendons).""" + + default_fixed_tendon_limit_stiffness: torch.Tensor = None + """Default tendon limit stiffness of all tendons. Shape is (num_instances, num_fixed_tendons).""" + + default_fixed_tendon_rest_length: torch.Tensor = None + """Default tendon rest length of all tendons. Shape is (num_instances, num_fixed_tendons).""" + + default_fixed_tendon_offset: torch.Tensor = None + """Default tendon offset of all tendons. Shape is (num_instances, num_fixed_tendons).""" + + default_fixed_tendon_limit: torch.Tensor = None + """Default tendon limits of all tendons. Shape is (num_instances, num_fixed_tendons, 2).""" + ## # Other Data. ## diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object.py index 677d0eb679..27f65d3ae9 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object.py @@ -333,6 +333,8 @@ def _initialize_impl(self): self._create_buffers() # process configuration self._process_cfg() + # update the rigid body data + self.update(0.0) def _create_buffers(self): """Create buffers for storing data.""" @@ -369,6 +371,9 @@ def _create_buffers(self): # -- used to compute body accelerations numerically self._last_body_vel_w = torch.zeros(self.num_instances, self.num_bodies, 6, device=self.device) + # mass + self._data.default_mass = self.root_physx_view.get_masses().clone() + def _process_cfg(self): """Post processing of configuration parameters.""" # default state @@ -391,7 +396,8 @@ def _update_common_data(self, dt: float): override the update function without having to worry about updating the common data. """ # -- body acceleration - self._data.body_acc_w[:] = (self._data.body_state_w[..., 7:] - self._last_body_vel_w) / dt + if dt > 0.0: + self._data.body_acc_w[:] = (self._data.body_state_w[..., 7:] - self._last_body_vel_w) / dt self._last_body_vel_w[:] = self._data.body_state_w[..., 7:] # -- root state in body frame self._data.root_vel_b[:, 0:3] = math_utils.quat_rotate_inverse( diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object_data.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object_data.py index 47fbc3c9d3..f48d6db3b5 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object_data.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/assets/rigid_object/rigid_object_data.py @@ -57,6 +57,13 @@ class RigidObjectData: This quantity is computed based on the rigid body state from the last step. """ + ## + # Default rigid body properties + ## + + default_mass: torch.Tensor = None + """ Default mass provided by simulation. Shape is (num_instances, num_bodies).""" + """ Properties """ diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/__init__.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/__init__.py index 3d7ff8604f..3c0e8935f7 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/__init__.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/__init__.py @@ -13,16 +13,26 @@ Based on these, there are two types of environments: -* :class:`BaseEnv`: The base environment which only provides the agent with the +* :class:`ManagerBasedEnv`: The manager-based workflow base environment which + only provides the agent with the current observations and executes the actions provided by the agent. -* :class:`RLTaskEnv`: The RL task environment which besides the functionality of +* :class:`ManagerBasedRLEnv`: The manager-based workflow RL task environment which + besides the functionality of the base environment also provides additional Markov Decision Process (MDP) related information such as the current reward, done flag, and information. +In addition, RL task environments can use the direct workflow implementation: + +* :class:`DirectRLEnv`: The direct workflow RL task environment which provides implementations + for implementing scene setup, computing dones, performing resets, and computing + reward and observation. + """ from . import mdp, ui -from .base_env import BaseEnv, VecEnvObs -from .base_env_cfg import BaseEnvCfg, ViewerCfg -from .rl_task_env import RLTaskEnv, VecEnvStepReturn -from .rl_task_env_cfg import RLTaskEnvCfg +from .base_env_cfg import ManagerBasedEnvCfg, ViewerCfg +from .direct_rl_env import DirectRLEnv +from .manager_based_env import ManagerBasedEnv +from .manager_based_rl_env import ManagerBasedRLEnv +from .rl_env_cfg import DirectRLEnvCfg, ManagerBasedRLEnvCfg +from .types import VecEnvObs, VecEnvStepReturn diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env_cfg.py index d8ba91251d..aad6cc1ddb 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env_cfg.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env_cfg.py @@ -76,7 +76,7 @@ class DefaultEventManagerCfg: @configclass -class BaseEnvCfg: +class ManagerBasedEnvCfg: """Base configuration of the environment.""" # simulation settings diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/direct_rl_env.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/direct_rl_env.py new file mode 100644 index 0000000000..14a9eafe8b --- /dev/null +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/direct_rl_env.py @@ -0,0 +1,524 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import builtins +import gymnasium as gym +import inspect +import math +import numpy as np +import torch +import weakref +from abc import abstractmethod +from collections.abc import Sequence +from typing import Any, ClassVar + +import omni.isaac.core.utils.torch as torch_utils +import omni.kit.app +from omni.isaac.version import get_version + +from omni.isaac.lab.envs.types import VecEnvObs, VecEnvStepReturn +from omni.isaac.lab.managers import EventManager +from omni.isaac.lab.scene import InteractiveScene +from omni.isaac.lab.sim import SimulationContext +from omni.isaac.lab.utils.noise import NoiseModel +from omni.isaac.lab.utils.timer import Timer + +from .rl_env_cfg import DirectRLEnvCfg +from .ui import ViewportCameraController + + +class DirectRLEnv(gym.Env): + """The superclass for the direct workflow reinforcement learning-based environments. + + This class implements the core functionality for reinforcement learning-based + environments. It is designed to be used with any RL library. The class is designed + to be used with vectorized environments, i.e., the environment is expected to be run + in parallel with multiple sub-environments. + + While the environment itself is implemented as a vectorized environment, we do not + inherit from :class:`gym.vector.VectorEnv`. This is mainly because the class adds + various methods (for wait and asynchronous updates) which are not required. + Additionally, each RL library typically has its own definition for a vectorized + environment. Thus, to reduce complexity, we directly use the :class:`gym.Env` over + here and leave it up to library-defined wrappers to take care of wrapping this + environment for their agents. + + Note: + For vectorized environments, it is recommended to **only** call the :meth:`reset` + method once before the first call to :meth:`step`, i.e. after the environment is created. + After that, the :meth:`step` function handles the reset of terminated sub-environments. + This is because the simulator does not support resetting individual sub-environments + in a vectorized environment. + + """ + + is_vector_env: ClassVar[bool] = True + """Whether the environment is a vectorized environment.""" + metadata: ClassVar[dict[str, Any]] = { + "render_modes": [None, "human", "rgb_array"], + "isaac_sim_version": get_version(), + } + """Metadata for the environment.""" + + def __init__(self, cfg: DirectRLEnvCfg, render_mode: str | None = None, **kwargs): + """Initialize the environment. + + Args: + cfg: The configuration object for the environment. + + Raises: + RuntimeError: If a simulation context already exists. The environment must always create one + since it configures the simulation context and controls the simulation. + """ + # store inputs to class + self.cfg = cfg + # store the render mode + self.render_mode = render_mode + # initialize internal variables + self._is_closed = False + + # create a simulation context to control the simulator + if SimulationContext.instance() is None: + self.sim: SimulationContext = SimulationContext(self.cfg.sim) + else: + raise RuntimeError("Simulation context already exists. Cannot create a new one.") + + # print useful information + print("[INFO]: Base environment:") + print(f"\tEnvironment device : {self.device}") + print(f"\tPhysics step-size : {self.physics_dt}") + print(f"\tRendering step-size : {self.physics_dt * self.cfg.sim.substeps}") + print(f"\tEnvironment step-size : {self.step_dt}") + print(f"\tPhysics GPU pipeline : {self.cfg.sim.use_gpu_pipeline}") + print(f"\tPhysics GPU simulation: {self.cfg.sim.physx.use_gpu}") + + # generate scene + with Timer("[INFO]: Time taken for scene creation"): + self.scene = InteractiveScene(self.cfg.scene) + self._setup_scene() + 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. + if self.sim.render_mode >= self.sim.RenderMode.PARTIAL_RENDERING: + self.viewport_camera_controller = ViewportCameraController(self, self.cfg.viewer) + else: + self.viewport_camera_controller = None + + # play the simulator to activate physics handles + # note: this activates the physics simulation view that exposes TensorAPIs + # note: when started in extension mode, first call sim.reset_async() and then initialize the managers + if builtins.ISAAC_LAUNCHED_FROM_TERMINAL is False: + print("[INFO]: Starting the simulation. This may take a few seconds. Please wait...") + with Timer("[INFO]: Time taken for simulation start"): + self.sim.reset() + + # -- event manager used for randomization + if self.cfg.events: + self.event_manager = EventManager(self.cfg.events, self) + print("[INFO] Event Manager: ", self.event_manager) + + # make sure torch is running on the correct device + if "cuda" in self.device: + torch.cuda.set_device(self.device) + + # check if debug visualization is has been implemented by the environment + source_code = inspect.getsource(self._set_debug_vis_impl) + self.has_debug_vis_implementation = "NotImplementedError" not in source_code + self._debug_vis_handle = None + + # extend UI elements + # we need to do this here after all the managers are initialized + # this is because they dictate the sensors and commands right now + if self.sim.has_gui() and self.cfg.ui_window_class_type is not None: + self._window = self.cfg.ui_window_class_type(self, window_name="IsaacLab") + else: + # if no window, then we don't need to store the window + self._window = None + + # allocate dictionary to store metrics + self.extras = {} + + # initialize data and constants + # -- counter for curriculum + self.common_step_counter = 0 + # -- init buffers + self.episode_length_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.long) + self.reset_terminated = torch.zeros(self.num_envs, device=self.device, dtype=torch.bool) + self.reset_time_outs = torch.zeros_like(self.reset_terminated) + self.reset_buf = torch.zeros(self.num_envs, dtype=torch.bool, device=self.sim.device) + self.actions = torch.zeros(self.num_envs, self.cfg.num_actions, device=self.sim.device) + # setup the action and observation spaces for Gym + self._configure_gym_env_spaces() + + # -- noise cfg for adding action and observation noise + if self.cfg.action_noise_model: + self._action_noise_model: NoiseModel = self.cfg.action_noise_model.class_type( + self.num_envs, self.cfg.action_noise_model, self.device + ) + if self.cfg.observation_noise_model: + self._observation_noise_model: NoiseModel = self.cfg.observation_noise_model.class_type( + self.num_envs, self.cfg.observation_noise_model, self.device + ) + # perform events at the start of the simulation + if self.cfg.events: + if "startup" in self.event_manager.available_modes: + self.event_manager.apply(mode="startup") + + # print the environment information + print("[INFO]: Completed setting up the environment...") + + def __del__(self): + """Cleanup for the environment.""" + self.close() + + """ + Properties. + """ + + @property + def num_envs(self) -> int: + """The number of instances of the environment that are running.""" + return self.scene.num_envs + + @property + def physics_dt(self) -> float: + """The physics time-step (in s). + + This is the lowest time-decimation at which the simulation is happening. + """ + return self.cfg.sim.dt + + @property + def step_dt(self) -> float: + """The environment stepping time-step (in s). + + This is the time-step at which the environment steps forward. + """ + return self.cfg.sim.dt * self.cfg.decimation + + @property + def device(self): + """The device on which the environment is running.""" + return self.sim.device + + @property + def max_episode_length_s(self) -> float: + """Maximum episode length in seconds.""" + return self.cfg.episode_length_s + + @property + def max_episode_length(self): + """The maximum episode length in steps adjusted from s.""" + return math.ceil(self.max_episode_length_s / (self.cfg.sim.dt * self.cfg.decimation)) + + """ + Operations. + """ + + def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[VecEnvObs, dict]: + """Resets all the environments and returns observations. + + Args: + seed: The seed to use for randomization. Defaults to None, in which case the seed is not set. + options: Additional information to specify how the environment is reset. Defaults to None. + + Note: + This argument is used for compatibility with Gymnasium environment definition. + + Returns: + A tuple containing the observations and extras. + """ + # set the seed + if seed is not None: + self.seed(seed) + # reset state of scene + indices = torch.arange(self.num_envs, dtype=torch.int64, device=self.device) + self._reset_idx(indices) + + obs = self._get_observations() + # return observations + return obs, self.extras + + def step(self, action: torch.Tensor) -> VecEnvStepReturn: + """Execute one time-step of the environment's dynamics. + + The environment steps forward at a fixed time-step, while the physics simulation is + decimated at a lower time-step. This is to ensure that the simulation is stable. These two + time-steps can be configured independently using the :attr:`DirectRLEnvCfg.decimation` (number of + simulation steps per environment step) and the :attr:`DirectRLEnvCfg.physics_dt` (physics time-step). + Based on these parameters, the environment time-step is computed as the product of the two. + + Args: + action: The actions to apply on the environment. Shape is (num_envs, action_dim). + + Returns: + A tuple containing the observations and extras. + """ + + # add action noise + if self.cfg.action_noise_model: + action = self._action_noise_model.apply(action.clone()) + + # process actions + self._pre_physics_step(action) + # perform physics stepping + for _ in range(self.cfg.decimation): + # set actions into buffers + self._apply_action() + # set actions into simulator + self.scene.write_data_to_sim() + # simulate + self.sim.step(render=False) + # update buffers at sim dt + self.scene.update(dt=self.physics_dt) + # perform rendering if gui is enabled + if self.sim.has_gui() or self.sim.has_rtx_sensors(): + self.sim.render() + + # post-step: + # -- update env counters (used for curriculum generation) + self.episode_length_buf += 1 # step in current episode (per env) + self.common_step_counter += 1 # total step (common for all envs) + + self.reset_terminated[:], self.reset_time_outs[:] = self._get_dones() + self.reset_buf = self.reset_terminated | self.reset_time_outs + self.reward_buf = self._get_rewards() + + # -- reset envs that terminated/timed-out and log the episode information + reset_env_ids = self.reset_buf.nonzero(as_tuple=False).squeeze(-1) + if len(reset_env_ids) > 0: + self._reset_idx(reset_env_ids) + + # post-step: step interval event + if self.cfg.events: + if "interval" in self.event_manager.available_modes: + self.event_manager.apply(mode="interval", dt=self.step_dt) + + # update observations + self.obs_buf = self._get_observations() + + # add observation noise + if self.cfg.observation_noise_model: + self.obs_buf["policy"] = self._observation_noise_model.apply(self.obs_buf["policy"]) + + # return observations, rewards, resets and extras + return self.obs_buf, self.reward_buf, self.reset_terminated, self.reset_time_outs, self.extras + + @staticmethod + def seed(seed: int = -1) -> int: + """Set the seed for the environment. + + Args: + seed: The seed for random generator. Defaults to -1. + + Returns: + The seed used for random generator. + """ + # set seed for replicator + try: + import omni.replicator.core as rep + + rep.set_global_seed(seed) + except ModuleNotFoundError: + pass + # set seed for torch and other libraries + return torch_utils.set_seed(seed) + + def render(self, recompute: bool = False) -> np.ndarray | None: + """Run rendering without stepping through the physics. + + By convention, if mode is: + + - **human**: Render to the current display and return nothing. Usually for human consumption. + - **rgb_array**: Return an numpy.ndarray with shape (x, y, 3), representing RGB values for an + x-by-y pixel image, suitable for turning into a video. + + Args: + recompute: Whether to force a render even if the simulator has already rendered the scene. + Defaults to False. + + Returns: + The rendered image as a numpy array if mode is "rgb_array". Otherwise, returns None. + + Raises: + RuntimeError: If mode is set to "rgb_data" and simulation render mode does not support it. + In this case, the simulation render mode must be set to ``RenderMode.PARTIAL_RENDERING`` + or ``RenderMode.FULL_RENDERING``. + NotImplementedError: If an unsupported rendering mode is specified. + """ + # run a rendering step of the simulator + # if we have rtx sensors, we do not need to render again sin + if not self.sim.has_rtx_sensors() and not recompute: + self.sim.render() + # decide the rendering mode + 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 + if self.sim.render_mode.value < self.sim.RenderMode.PARTIAL_RENDERING.value: + raise RuntimeError( + f"Cannot render '{self.render_mode}' when the simulation render mode is" + f" '{self.sim.render_mode.name}'. Please set the simulation render mode to:" + f"'{self.sim.RenderMode.PARTIAL_RENDERING.name}' or '{self.sim.RenderMode.FULL_RENDERING.name}'." + " If running headless, make sure --enable_cameras is set." + ) + # create the annotator if it does not exist + if not hasattr(self, "_rgb_annotator"): + import omni.replicator.core as rep + + # create render product + self._render_product = rep.create.render_product( + self.cfg.viewer.cam_prim_path, self.cfg.viewer.resolution + ) + # create rgb annotator -- used to read data from the render product + self._rgb_annotator = rep.AnnotatorRegistry.get_annotator("rgb", device="cpu") + self._rgb_annotator.attach([self._render_product]) + # obtain the rgb data + rgb_data = self._rgb_annotator.get_data() + # convert to numpy array + rgb_data = np.frombuffer(rgb_data, dtype=np.uint8).reshape(*rgb_data.shape) + # return the rgb data + # note: initially the renerer is warming up and returns empty data + if rgb_data.size == 0: + return np.zeros((self.cfg.viewer.resolution[1], self.cfg.viewer.resolution[0], 3), dtype=np.uint8) + else: + return rgb_data[:, :, :3] + else: + raise NotImplementedError( + f"Render mode '{self.render_mode}' is not supported. Please use: {self.metadata['render_modes']}." + ) + + def close(self): + """Cleanup for the environment.""" + if not self._is_closed: + if self.cfg.events: + del self.event_manager + del self.scene + if self.viewport_camera_controller is not None: + del self.viewport_camera_controller + # clear callbacks and instance + self.sim.clear_all_callbacks() + self.sim.clear_instance() + # destroy the window + if self._window is not None: + self._window = None + # update closing status + self._is_closed = True + + def set_debug_vis(self, debug_vis: bool) -> bool: + """Toggles the environment debug visualization. + + Args: + debug_vis: Whether to visualize the environment debug visualization. + + Returns: + Whether the debug visualization was successfully set. False if the environment + does not support debug visualization. + """ + # check if debug visualization is supported + if not self.has_debug_vis_implementation: + return False + # toggle debug visualization objects + self._set_debug_vis_impl(debug_vis) + # toggle debug visualization handles + if debug_vis: + # create a subscriber for the post update event if it doesn't exist + if self._debug_vis_handle is None: + app_interface = omni.kit.app.get_app_interface() + self._debug_vis_handle = app_interface.get_post_update_event_stream().create_subscription_to_pop( + lambda event, obj=weakref.proxy(self): obj._debug_vis_callback(event) + ) + else: + # remove the subscriber if it exists + if self._debug_vis_handle is not None: + self._debug_vis_handle.unsubscribe() + self._debug_vis_handle = None + # return success + return True + + """ + Helper functions. + """ + + def _configure_gym_env_spaces(self): + """Configure the action and observation spaces for the Gym environment.""" + # observation space (unbounded since we don't impose any limits) + self.num_actions = self.cfg.num_actions + self.num_observations = self.cfg.num_observations + self.num_states = self.cfg.num_states + + # set up spaces + self.single_observation_space = gym.spaces.Dict() + self.single_observation_space["policy"] = gym.spaces.Box( + low=-np.inf, high=np.inf, shape=(self.num_observations,) + ) + self.single_action_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.num_actions,)) + + # batch the spaces for vectorized environments + self.observation_space = gym.vector.utils.batch_space(self.single_observation_space["policy"], self.num_envs) + self.action_space = gym.vector.utils.batch_space(self.single_action_space, self.num_envs) + + if self.num_states > 0: + self.single_observation_space["critic"] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.num_states,)) + self.state_space = gym.vector.utils.batch_space(self.single_observation_space["critic"], self.num_envs) + + def _reset_idx(self, env_ids: Sequence[int]): + """Reset environments based on specified indices. + + Args: + env_ids: List of environment ids which must be reset + """ + self.scene.reset(env_ids) + # apply events such as randomizations for environments that need a reset + if self.cfg.events: + if "reset" in self.event_manager.available_modes: + self.event_manager.apply(env_ids=env_ids, mode="reset") + if self.cfg.action_noise_model: + self._action_noise_model.reset(env_ids) + if self.cfg.observation_noise_model: + self._observation_noise_model.reset(env_ids) + # reset the episode length buffer + self.episode_length_buf[env_ids] = 0 + + # this can be done through configs as well + def _setup_scene(self): + pass + + def _set_debug_vis_impl(self, debug_vis: bool): + """Set debug visualization into visualization objects. + + This function is responsible for creating the visualization objects if they don't exist + and input ``debug_vis`` is True. If the visualization objects exist, the function should + set their visibility into the stage. + """ + raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") + + @abstractmethod + def _pre_physics_step(self, actions: torch.Tensor): + return NotImplementedError + + @abstractmethod + def _apply_action(self): + return NotImplementedError + + @abstractmethod + def _get_observations(self) -> VecEnvObs: + return NotImplementedError + + def _get_states(self) -> VecEnvObs | None: + return None + + @abstractmethod + def _get_rewards(self) -> torch.Tensor: + return NotImplementedError + + @abstractmethod + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + return NotImplementedError diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_env.py similarity index 88% rename from source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env.py rename to source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_env.py index 0493977ed6..03178e4d06 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/base_env.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_env.py @@ -7,45 +7,23 @@ import torch import warnings from collections.abc import Sequence -from typing import Any, Dict +from typing import Any import carb import omni.isaac.core.utils.torch as torch_utils +from omni.isaac.lab.envs.types import VecEnvObs from omni.isaac.lab.managers import ActionManager, EventManager, ObservationManager from omni.isaac.lab.scene import InteractiveScene from omni.isaac.lab.sim import SimulationContext from omni.isaac.lab.utils.timer import Timer -from .base_env_cfg import BaseEnvCfg +from .base_env_cfg import ManagerBasedEnvCfg from .ui import ViewportCameraController -VecEnvObs = Dict[str, torch.Tensor | Dict[str, torch.Tensor]] -"""Observation returned by the environment. -The observations are stored in a dictionary. The keys are the group to which the observations belong. -This is useful for various setups such as reinforcement learning with asymmetric actor-critic or -multi-agent learning. For non-learning paradigms, this may include observations for different components -of a system. - -Within each group, the observations can be stored either as a dictionary with keys as the names of each -observation term in the group, or a single tensor obtained from concatenating all the observation terms. -For example, for asymmetric actor-critic, the observation for the actor and the critic can be accessed -using the keys ``"policy"`` and ``"critic"`` respectively. - -Note: - By default, most learning frameworks deal with default and privileged observations in different ways. - This handling must be taken care of by the wrapper around the :class:`RLTaskEnv` instance. - - For included frameworks (RSL-RL, RL-Games, skrl), the observations must have the key "policy". In case, - the key "critic" is also present, then the critic observations are taken from the "critic" group. - Otherwise, they are the same as the "policy" group. - -""" - - -class BaseEnv: - """The base environment encapsulates the simulation scene and the environment managers. +class ManagerBasedEnv: + """The base environment encapsulates the simulation scene and the environment managers for the manager-based workflow. While a simulation scene or world comprises of different components such as the robots, objects, and sensors (cameras, lidars, etc.), the environment is a higher level abstraction @@ -76,13 +54,13 @@ class BaseEnv: The environment steps forward in time at a fixed time-step. The physics simulation is decimated at a lower time-step. This is to ensure that the simulation is stable. These two time-steps can be configured - independently using the :attr:`BaseEnvCfg.decimation` (number of simulation steps per environment step) - and the :attr:`BaseEnvCfg.sim.dt` (physics time-step) parameters. Based on these parameters, the + independently using the :attr:`ManagerBasedEnvCfg.decimation` (number of simulation steps per environment step) + and the :attr:`ManagerBasedEnvCfg.sim.dt` (physics time-step) parameters. Based on these parameters, the environment time-step is computed as the product of the two. The two time-steps can be obtained by querying the :attr:`physics_dt` and the :attr:`step_dt` properties respectively. """ - def __init__(self, cfg: BaseEnvCfg): + def __init__(self, cfg: ManagerBasedEnvCfg): """Initialize the environment. Args: @@ -138,6 +116,10 @@ def __init__(self, cfg: BaseEnvCfg): # add timeline event to load managers self.load_managers() + # make sure torch is running on the correct device + if "cuda" in self.device: + torch.cuda.set_device(self.device) + # extend UI elements # we need to do this here after all the managers are initialized # this is because they dictate the sensors and commands right now @@ -257,8 +239,8 @@ def step(self, action: torch.Tensor) -> tuple[VecEnvObs, dict]: The environment steps forward at a fixed time-step, while the physics simulation is decimated at a lower time-step. This is to ensure that the simulation is stable. These two - time-steps can be configured independently using the :attr:`BaseEnvCfg.decimation` (number of - simulation steps per environment step) and the :attr:`BaseEnvCfg.physics_dt` (physics time-step). + time-steps can be configured independently using the :attr:`ManagerBasedEnvCfg.decimation` (number of + simulation steps per environment step) and the :attr:`ManagerBasedEnvCfg.physics_dt` (physics time-step). Based on these parameters, the environment time-step is computed as the product of the two. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_task_env.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_rl_env.py similarity index 92% rename from source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_task_env.py rename to source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_rl_env.py index 3663cd12b2..1d9557de15 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_task_env.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_rl_env.py @@ -17,26 +17,15 @@ from omni.isaac.lab.managers import CommandManager, CurriculumManager, RewardManager, TerminationManager -from .base_env import BaseEnv, VecEnvObs -from .rl_task_env_cfg import RLTaskEnvCfg +from .manager_based_env import ManagerBasedEnv +from .rl_env_cfg import ManagerBasedRLEnvCfg +from .types import VecEnvStepReturn -VecEnvStepReturn = tuple[VecEnvObs, torch.Tensor, torch.Tensor, torch.Tensor, dict] -"""The environment signals processed at the end of each step. -The tuple contains batched information for each sub-environment. The information is stored in the following order: +class ManagerBasedRLEnv(ManagerBasedEnv, gym.Env): + """The superclass for the manager-based workflow reinforcement learning-based environments. -1. **Observations**: The observations from the environment. -2. **Rewards**: The rewards from the environment. -3. **Terminated Dones**: Whether the environment reached a terminal state, such as task success or robot falling etc. -4. **Timeout Dones**: Whether the environment reached a timeout state, such as end of max episode length. -5. **Extras**: A dictionary containing additional information from the environment. -""" - - -class RLTaskEnv(BaseEnv, gym.Env): - """The superclass for reinforcement learning-based environments. - - This class inherits from :class:`BaseEnv` and implements the core functionality for + This class inherits from :class:`ManagerBasedEnv` and implements the core functionality for reinforcement learning-based environments. It is designed to be used with any RL library. The class is designed to be used with vectorized environments, i.e., the environment is expected to be run in parallel with multiple sub-environments. The @@ -71,10 +60,10 @@ class RLTaskEnv(BaseEnv, gym.Env): } """Metadata for the environment.""" - cfg: RLTaskEnvCfg + cfg: ManagerBasedRLEnvCfg """Configuration for the environment.""" - def __init__(self, cfg: RLTaskEnvCfg, render_mode: str | None = None, **kwargs): + def __init__(self, cfg: ManagerBasedRLEnvCfg, render_mode: str | None = None, **kwargs): """Initialize the environment. Args: @@ -145,7 +134,7 @@ def load_managers(self): def step(self, action: torch.Tensor) -> VecEnvStepReturn: """Execute one time-step of the environment's dynamics and reset terminated environments. - Unlike the :class:`BaseEnv.step` class, the function performs the following operations: + Unlike the :class:`ManagerBasedEnv.step` class, the function performs the following operations: 1. Process the actions. 2. Perform physics stepping. @@ -240,6 +229,7 @@ def render(self, recompute: bool = False) -> np.ndarray | None: f"Cannot render '{self.render_mode}' when the simulation render mode is" f" '{self.sim.render_mode.name}'. Please set the simulation render mode to:" f"'{self.sim.RenderMode.PARTIAL_RENDERING.name}' or '{self.sim.RenderMode.FULL_RENDERING.name}'." + " If running headless, make sure --enable_cameras is set." ) # create the annotator if it does not exist if not hasattr(self, "_rgb_annotator"): diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/binary_joint_actions.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/binary_joint_actions.py index 4e24d365e2..73b2353add 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/binary_joint_actions.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/binary_joint_actions.py @@ -6,6 +6,7 @@ from __future__ import annotations import torch +from collections.abc import Sequence from typing import TYPE_CHECKING import carb @@ -15,7 +16,7 @@ from omni.isaac.lab.managers.action_manager import ActionTerm if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from . import actions_cfg @@ -43,7 +44,7 @@ class BinaryJointAction(ActionTerm): _asset: Articulation """The articulation asset on which the action term is applied.""" - def __init__(self, cfg: actions_cfg.BinaryJointActionCfg, env: BaseEnv) -> None: + def __init__(self, cfg: actions_cfg.BinaryJointActionCfg, env: ManagerBasedEnv) -> None: # initialize the action term super().__init__(cfg, env) @@ -115,6 +116,9 @@ def process_actions(self, actions: torch.Tensor): # compute the command self._processed_actions = torch.where(binary_mask, self._close_command, self._open_command) + def reset(self, env_ids: Sequence[int] | None = None) -> None: + self._raw_actions[env_ids] = 0.0 + class BinaryJointPositionAction(BinaryJointAction): """Binary joint action that sets the binary action into joint position targets.""" diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions.py index a3a9d12440..1dc6bc1746 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions.py @@ -6,6 +6,7 @@ from __future__ import annotations import torch +from collections.abc import Sequence from typing import TYPE_CHECKING import carb @@ -15,7 +16,7 @@ from omni.isaac.lab.managers.action_manager import ActionTerm if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from . import actions_cfg @@ -50,7 +51,7 @@ class JointAction(ActionTerm): _offset: torch.Tensor | float """The offset applied to the input action.""" - def __init__(self, cfg: actions_cfg.JointActionCfg, env: BaseEnv) -> None: + def __init__(self, cfg: actions_cfg.JointActionCfg, env: ManagerBasedEnv) -> None: # initialize the action term super().__init__(cfg, env) @@ -118,6 +119,9 @@ def process_actions(self, actions: torch.Tensor): # apply the affine transformations self._processed_actions = self._raw_actions * self._scale + self._offset + def reset(self, env_ids: Sequence[int] | None = None) -> None: + self._raw_actions[env_ids] = 0.0 + class JointPositionAction(JointAction): """Joint action term that applies the processed actions to the articulation's joints as position commands.""" @@ -125,7 +129,7 @@ class JointPositionAction(JointAction): cfg: actions_cfg.JointPositionActionCfg """The configuration of the action term.""" - def __init__(self, cfg: actions_cfg.JointPositionActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.JointPositionActionCfg, env: ManagerBasedEnv): # initialize the action term super().__init__(cfg, env) # use default joint positions as offset @@ -156,7 +160,7 @@ class RelativeJointPositionAction(JointAction): cfg: actions_cfg.RelativeJointPositionActionCfg """The configuration of the action term.""" - def __init__(self, cfg: actions_cfg.RelativeJointPositionActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.RelativeJointPositionActionCfg, env: ManagerBasedEnv): # initialize the action term super().__init__(cfg, env) # use zero offset for relative position @@ -176,7 +180,7 @@ class JointVelocityAction(JointAction): cfg: actions_cfg.JointVelocityActionCfg """The configuration of the action term.""" - def __init__(self, cfg: actions_cfg.JointVelocityActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.JointVelocityActionCfg, env: ManagerBasedEnv): # initialize the action term super().__init__(cfg, env) # use default joint velocity as offset @@ -194,7 +198,7 @@ class JointEffortAction(JointAction): cfg: actions_cfg.JointEffortActionCfg """The configuration of the action term.""" - def __init__(self, cfg: actions_cfg.JointEffortActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.JointEffortActionCfg, env: ManagerBasedEnv): super().__init__(cfg, env) def apply_actions(self): diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions_to_limits.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions_to_limits.py index 4baaebc1a7..81f4a82184 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions_to_limits.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/joint_actions_to_limits.py @@ -17,7 +17,7 @@ from omni.isaac.lab.managers.action_manager import ActionTerm if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from . import actions_cfg @@ -45,7 +45,7 @@ class JointPositionToLimitsAction(ActionTerm): _scale: torch.Tensor | float """The scaling factor applied to the input action.""" - def __init__(self, cfg: actions_cfg.JointPositionToLimitsActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.JointPositionToLimitsActionCfg, env: ManagerBasedEnv): # initialize the action term super().__init__(cfg, env) @@ -119,6 +119,9 @@ def apply_actions(self): # set position targets self._asset.set_joint_position_target(self.processed_actions, joint_ids=self._joint_ids) + def reset(self, env_ids: Sequence[int] | None = None) -> None: + self._raw_actions[env_ids] = 0.0 + class EMAJointPositionToLimitsAction(JointPositionToLimitsAction): r"""Joint action term that applies exponential moving average (EMA) over the processed actions as the @@ -145,7 +148,7 @@ class EMAJointPositionToLimitsAction(JointPositionToLimitsAction): cfg: actions_cfg.EMAJointPositionToLimitsActionCfg """The configuration of the action term.""" - def __init__(self, cfg: actions_cfg.EMAJointPositionToLimitsActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.EMAJointPositionToLimitsActionCfg, env: ManagerBasedEnv): # initialize the action term super().__init__(cfg, env) @@ -180,6 +183,7 @@ def reset(self, env_ids: Sequence[int] | None = None) -> None: # check if specific environment ids are provided if env_ids is None: env_ids = slice(None) + super().reset(env_ids) # reset history to current joint positions self._prev_applied_actions[env_ids, :] = self._asset.data.joint_pos[env_ids, self._joint_ids] diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/non_holonomic_actions.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/non_holonomic_actions.py index 542adc4161..6344ad256a 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/non_holonomic_actions.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/non_holonomic_actions.py @@ -6,6 +6,7 @@ from __future__ import annotations import torch +from collections.abc import Sequence from typing import TYPE_CHECKING import carb @@ -15,7 +16,7 @@ from omni.isaac.lab.utils.math import euler_xyz_from_quat if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from . import actions_cfg @@ -59,7 +60,7 @@ class NonHolonomicAction(ActionTerm): _offset: torch.Tensor """The offset applied to the input action. Shape is (1, 2).""" - def __init__(self, cfg: actions_cfg.NonHolonomicActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.NonHolonomicActionCfg, env: ManagerBasedEnv): # initialize the action term super().__init__(cfg, env) @@ -139,3 +140,6 @@ def apply_actions(self): self._joint_vel_command[:, 2] = self.processed_actions[:, 1] # yaw # set the joint velocity targets self._asset.set_joint_velocity_target(self._joint_vel_command, joint_ids=self._joint_ids) + + def reset(self, env_ids: Sequence[int] | None = None) -> None: + self._raw_actions[env_ids] = 0.0 diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/task_space_actions.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/task_space_actions.py index 10c227a65a..02a384052d 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/task_space_actions.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/actions/task_space_actions.py @@ -6,6 +6,7 @@ from __future__ import annotations import torch +from collections.abc import Sequence from typing import TYPE_CHECKING import carb @@ -16,7 +17,7 @@ from omni.isaac.lab.managers.action_manager import ActionTerm if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from . import actions_cfg @@ -42,7 +43,7 @@ class DifferentialInverseKinematicsAction(ActionTerm): _scale: torch.Tensor """The scaling factor applied to the input action. Shape is (1, action_dim).""" - def __init__(self, cfg: actions_cfg.DifferentialInverseKinematicsActionCfg, env: BaseEnv): + def __init__(self, cfg: actions_cfg.DifferentialInverseKinematicsActionCfg, env: ManagerBasedEnv): # initialize the action term super().__init__(cfg, env) @@ -140,6 +141,9 @@ def apply_actions(self): # set the joint position command self._asset.set_joint_position_target(joint_pos_des, self._joint_ids) + def reset(self, env_ids: Sequence[int] | None = None) -> None: + self._raw_actions[env_ids] = 0.0 + """ Helper functions. """ diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_2d_command.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_2d_command.py index df9f3bd83f..9b5e91491b 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_2d_command.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_2d_command.py @@ -19,7 +19,7 @@ from omni.isaac.lab.utils.math import quat_from_euler_xyz, quat_rotate_inverse, wrap_to_pi, yaw_quat if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from .commands_cfg import TerrainBasedPose2dCommandCfg, UniformPose2dCommandCfg @@ -37,7 +37,7 @@ class UniformPose2dCommand(CommandTerm): cfg: UniformPose2dCommandCfg """Configuration for the command generator.""" - def __init__(self, cfg: UniformPose2dCommandCfg, env: BaseEnv): + def __init__(self, cfg: UniformPose2dCommandCfg, env: ManagerBasedEnv): """Initialize the command generator class. Args: @@ -159,7 +159,7 @@ class TerrainBasedPose2dCommand(UniformPose2dCommand): cfg: TerrainBasedPose2dCommandCfg """Configuration for the command generator.""" - def __init__(self, cfg: TerrainBasedPose2dCommandCfg, env: BaseEnv): + def __init__(self, cfg: TerrainBasedPose2dCommandCfg, env: ManagerBasedEnv): # initialize the base class super().__init__(cfg, env) diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_command.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_command.py index e997f84a66..3a7078d175 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_command.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/pose_command.py @@ -18,7 +18,7 @@ from omni.isaac.lab.utils.math import combine_frame_transforms, compute_pose_error, quat_from_euler_xyz, quat_unique if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from .commands_cfg import UniformPoseCommandCfg @@ -45,7 +45,7 @@ class UniformPoseCommand(CommandTerm): cfg: UniformPoseCommandCfg """Configuration for the command generator.""" - def __init__(self, cfg: UniformPoseCommandCfg, env: BaseEnv): + def __init__(self, cfg: UniformPoseCommandCfg, env: ManagerBasedEnv): """Initialize the command generator class. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/velocity_command.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/velocity_command.py index 50b8b10c57..c4a99da1e6 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/velocity_command.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/commands/velocity_command.py @@ -18,7 +18,7 @@ from omni.isaac.lab.markers.config import BLUE_ARROW_X_MARKER_CFG, GREEN_ARROW_X_MARKER_CFG if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv from .commands_cfg import NormalVelocityCommandCfg, UniformVelocityCommandCfg, UserVelocityCommandCfg @@ -44,8 +44,8 @@ class UniformVelocityCommand(CommandTerm): cfg: UniformVelocityCommandCfg """The configuration of the command generator.""" - def __init__(self, cfg: UniformVelocityCommandCfg, env: BaseEnv): - """Initialize the command generator. + def __init__(self, cfg: UniformVelocityCommandCfg, env: ManagerBasedEnv): + """Initialize the command generator. Args: cfg: The configuration of the command generator. diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/curriculums.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/curriculums.py index adbb2063f3..e548ccfa6e 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/curriculums.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/curriculums.py @@ -15,10 +15,10 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv -def modify_reward_weight(env: RLTaskEnv, env_ids: Sequence[int], term_name: str, weight: float, num_steps: int): +def modify_reward_weight(env: ManagerBasedRLEnv, env_ids: Sequence[int], term_name: str, weight: float, num_steps: int): """Curriculum that modifies a reward weight a given number of steps. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/events.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/events.py index 3551daf974..c2236b3314 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/events.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/events.py @@ -20,6 +20,7 @@ import carb +import omni.isaac.lab.sim as sim_utils import omni.isaac.lab.utils.math as math_utils from omni.isaac.lab.actuators import ImplicitActuator from omni.isaac.lab.assets import Articulation, RigidObject @@ -27,11 +28,11 @@ from omni.isaac.lab.terrains import TerrainImporter if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv def randomize_rigid_body_material( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor | None, static_friction_range: tuple[float, float], dynamic_friction_range: tuple[float, float], @@ -63,31 +64,39 @@ def randomize_rigid_body_material( # extract the used quantities (to enable type-hinting) asset: RigidObject | Articulation = env.scene[asset_cfg.name] + if not isinstance(asset, (RigidObject, Articulation)): + raise ValueError( + f"Randomization term 'randomize_rigid_body_material' not supported for asset: '{asset_cfg.name}'" + f" with type: '{type(asset)}'." + ) + # resolve environment ids if env_ids is None: env_ids = torch.arange(env.scene.num_envs, device="cpu") + else: + env_ids = env_ids.cpu() + + # retrieve material buffer + materials = asset.root_physx_view.get_material_properties() # sample material properties from the given ranges - material_buckets = torch.zeros(num_buckets, 3) - material_buckets[:, 0].uniform_(*static_friction_range) - material_buckets[:, 1].uniform_(*dynamic_friction_range) - material_buckets[:, 2].uniform_(*restitution_range) - - # create random material assignments based on the total number of shapes: num_assets x num_shapes - # note: not optimal since it creates assignments for all the shapes but only a subset is used in the body indices case. - material_ids = torch.randint(0, num_buckets, (len(env_ids), asset.root_physx_view.max_shapes)) - - if asset_cfg.body_ids == slice(None) or isinstance(asset, RigidObject): - # get the current materials of the bodies - materials = asset.root_physx_view.get_material_properties() - # assign the new materials - # material ids are of shape: num_env_ids x num_shapes - # material_buckets are of shape: num_buckets x 3 - materials[env_ids] = material_buckets[material_ids] - - # set the material properties into the physics simulation - asset.root_physx_view.set_material_properties(materials, env_ids) - elif isinstance(asset, Articulation): + material_samples = torch.zeros(materials[env_ids].shape) + material_samples[..., 0].uniform_(*static_friction_range) + material_samples[..., 1].uniform_(*dynamic_friction_range) + material_samples[..., 2].uniform_(*restitution_range) + + # create uniform range tensor for bucketing + lo = torch.tensor([static_friction_range[0], dynamic_friction_range[0], restitution_range[0]], device="cpu") + hi = torch.tensor([static_friction_range[1], dynamic_friction_range[1], restitution_range[1]], device="cpu") + + # to avoid 64k material limit in physx, we bucket materials by binning randomized material properties + # into buckets based on the number of buckets specified + for d in range(3): + buckets = torch.tensor([(hi[d] - lo[d]) * i / num_buckets + lo[d] for i in range(num_buckets)], device="cpu") + material_samples[..., d] = buckets[torch.searchsorted(buckets, material_samples[..., d].contiguous()) - 1] + + # update material buffer with new samples + if isinstance(asset, Articulation) and asset_cfg.body_ids != slice(None): # obtain number of shapes per body (needed for indexing the material properties correctly) # note: this is a workaround since the Articulation does not provide a direct way to obtain the number of shapes # per body. We use the physics simulation view to obtain the number of shapes per body. @@ -96,9 +105,6 @@ def randomize_rigid_body_material( link_physx_view = asset._physics_sim_view.create_rigid_body_view(link_path) # type: ignore num_shapes_per_body.append(link_physx_view.max_shapes) - # get the current materials of the bodies - materials = asset.root_physx_view.get_material_properties() - # sample material properties from the given ranges for body_id in asset_cfg.body_ids: # start index of shape @@ -108,19 +114,19 @@ def randomize_rigid_body_material( # assign the new materials # material ids are of shape: num_env_ids x num_shapes # material_buckets are of shape: num_buckets x 3 - materials[env_ids, start_idx:end_idx] = material_buckets[material_ids[:, start_idx:end_idx]] - - # set the material properties into the physics simulation - asset.root_physx_view.set_material_properties(materials, env_ids) + materials[env_ids, start_idx:end_idx] = material_samples[:, start_idx:end_idx] else: - raise ValueError( - f"Randomization term 'randomize_rigid_body_material' not supported for asset: '{asset_cfg.name}'" - f" with type: '{type(asset)}' and body_ids: '{asset_cfg.body_ids}'." - ) + materials[env_ids] = material_samples + + # apply to simulation + asset.root_physx_view.set_material_properties(materials, env_ids) def add_body_mass( - env: BaseEnv, env_ids: torch.Tensor | None, mass_range: tuple[float, float], asset_cfg: SceneEntityCfg + env: ManagerBasedEnv, + env_ids: torch.Tensor | None, + mass_distribution_params: tuple[float, float], + asset_cfg: SceneEntityCfg, ): """Randomize the mass of the bodies by adding a random value sampled from the given range. @@ -137,21 +143,23 @@ def add_body_mass( carb.log_warn(msg) # call the new function - randomize_rigid_body_mass(env, env_ids, asset_cfg, mass_range, operation="add", distribution="uniform") + randomize_rigid_body_mass( + env, env_ids, asset_cfg, mass_distribution_params, operation="add", distribution="uniform" + ) def randomize_rigid_body_mass( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor | None, asset_cfg: SceneEntityCfg, - mass_range: tuple[float, float], + mass_distribution_params: tuple[float, float], operation: Literal["add", "scale", "abs"], - distribution: Literal["uniform", "log_uniform"] = "uniform", + distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", ): """Randomize the mass of the bodies by adding, scaling, or setting random values. This function allows randomizing the mass of the bodies of the asset. The function samples random values from the - given ranges and adds, scales, or sets the values into the physics simulation based on the operation. + given distribution parameters and adds, scales, or sets the values into the physics simulation based on the operation. .. tip:: This function uses CPU tensors to assign the body masses. It is recommended to use this function @@ -160,6 +168,12 @@ def randomize_rigid_body_mass( # extract the used quantities (to enable type-hinting) asset: RigidObject | Articulation = env.scene[asset_cfg.name] + # resolve environment ids + if env_ids is None: + env_ids = torch.arange(env.scene.num_envs, device="cpu") + else: + env_ids = env_ids.cpu() + # resolve body indices if asset_cfg.body_ids == slice(None): body_ids = torch.arange(asset.num_bodies, dtype=torch.int, device="cpu") @@ -168,36 +182,69 @@ def randomize_rigid_body_mass( # get the current masses of the bodies (num_assets, num_bodies) masses = asset.root_physx_view.get_masses() + # apply randomization on default values + masses[env_ids[:, None], body_ids] = asset.data.default_mass[env_ids[:, None], body_ids].clone() # sample from the given range # note: we modify the masses in-place for all environments # however, the setter takes care that only the masses of the specified environments are modified masses = _randomize_prop_by_op( - masses, mass_range, env_ids, body_ids, operation=operation, distribution=distribution + masses, mass_distribution_params, env_ids, body_ids, operation=operation, distribution=distribution ) - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device="cpu") # set the mass into the physics simulation asset.root_physx_view.set_masses(masses, env_ids) +def randomize_physics_scene_gravity( + env: ManagerBasedEnv, + env_ids: torch.Tensor | None, + gravity_distribution_params: tuple[list[float], list[float]], + operation: Literal["add", "scale", "abs"], + distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", +): + """Randomize gravity by adding, scaling, or setting random values. + + This function allows randomizing gravity of the physics scene. The function samples random values from the + given distribution parameters and adds, scales, or sets the values into the physics simulation based on the operation. + + .. attention:: + This function applied the same gravity for all the environments. + + .. tip:: + This function uses CPU tensors to assign gravity. + """ + # get the current gravity + gravity = torch.tensor(env.sim.cfg.gravity, device="cpu").unsqueeze(0) + dist_param_0 = torch.tensor(gravity_distribution_params[0], device="cpu") + dist_param_1 = torch.tensor(gravity_distribution_params[1], device="cpu") + gravity = _randomize_prop_by_op( + gravity, + (dist_param_0, dist_param_1), + None, + slice(None), + operation=operation, + distribution=distribution, + )[0] + # set the gravity into the physics simulation + sim_utils.SimulationContext.instance().physics_sim_view.set_gravity(carb.Float3(gravity[0], gravity[1], gravity[2])) + + def randomize_actuator_gains( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor | None, asset_cfg: SceneEntityCfg, - stiffness_range: tuple[float, float] | None = None, - damping_range: tuple[float, float] | None = None, + stiffness_distribution_params: tuple[float, float] | None = None, + damping_distribution_params: tuple[float, float] | None = None, operation: Literal["add", "scale", "abs"] = "abs", - distribution: Literal["uniform", "log_uniform"] = "uniform", + distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", ): """Randomize the actuator gains in an articulation by adding, scaling, or setting random values. This function allows randomizing the actuator stiffness and damping gains. - The function samples random values from the given ranges and applies the operation to the joint properties. - It then sets the values into the actuator models. If the ranges are not provided for a particular property, + The function samples random values from the given distribution parameters and applies the operation to the joint properties. + It then sets the values into the actuator models. If the distribution parameters are not provided for a particular property, the function does not modify the property. .. tip:: @@ -211,6 +258,10 @@ def randomize_actuator_gains( # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] + # resolve environment ids + if env_ids is None: + env_ids = torch.arange(env.scene.num_envs, device=asset.device) + # resolve joint indices if asset_cfg.joint_ids == slice(None): joint_ids_list = range(asset.num_joints) @@ -234,37 +285,39 @@ def randomize_actuator_gains( # sample joint properties from the given ranges and set into the physics simulation # -- stiffness - if stiffness_range is not None: - stiffness = asset.root_physx_view.get_dof_stiffnesses().to(asset.device) + if stiffness_distribution_params is not None: + stiffness = asset.data.default_joint_stiffness.to(asset.device).clone() stiffness = _randomize_prop_by_op( - stiffness, stiffness_range, env_ids, joint_ids, operation=operation, distribution=distribution - ) + stiffness, stiffness_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution + )[env_ids][:, joint_ids] asset.write_joint_stiffness_to_sim(stiffness, joint_ids=joint_ids, env_ids=env_ids) # -- damping - if damping_range is not None: - damping = asset.root_physx_view.get_dof_dampings().to(asset.device) + if damping_distribution_params is not None: + damping = asset.data.default_joint_damping.to(asset.device).clone() damping = _randomize_prop_by_op( - damping, damping_range, env_ids, joint_ids, operation=operation, distribution=distribution - ) + damping, damping_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution + )[env_ids][:, joint_ids] asset.write_joint_damping_to_sim(damping, joint_ids=joint_ids, env_ids=env_ids) def randomize_joint_parameters( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor | None, asset_cfg: SceneEntityCfg, - friction_range: tuple[float, float] | None = None, - armature_range: tuple[float, float] | None = None, + friction_distribution_params: tuple[float, float] | None = None, + armature_distribution_params: tuple[float, float] | None = None, + lower_limit_distribution_params: tuple[float, float] | None = None, + upper_limit_distribution_params: tuple[float, float] | None = None, operation: Literal["add", "scale", "abs"] = "abs", - distribution: Literal["uniform", "log_uniform"] = "uniform", + distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", ): """Randomize the joint parameters of an articulation by adding, scaling, or setting random values. - This function allows randomizing the joint parameters (friction and armature) of the asset. These correspond - to the physics engine joint properties that affect the joint behavior. + This function allows randomizing the joint parameters of the asset. + These correspond to the physics engine joint properties that affect the joint behavior. - The function samples random values from the given ranges and applies the operation to the joint properties. - It then sets the values into the physics simulation. If the ranges are not provided for a + The function samples random values from the given distribution parameters and applies the operation to the joint properties. + It then sets the values into the physics simulation. If the distribution parameters are not provided for a particular property, the function does not modify the property. .. tip:: @@ -274,6 +327,10 @@ def randomize_joint_parameters( # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] + # resolve environment ids + if env_ids is None: + env_ids = torch.arange(env.scene.num_envs, device=asset.device) + # resolve joint indices if asset_cfg.joint_ids == slice(None): joint_ids = slice(None) # for optimization purposes @@ -282,23 +339,190 @@ def randomize_joint_parameters( # sample joint properties from the given ranges and set into the physics simulation # -- friction - if friction_range is not None: - friction = asset.root_physx_view.get_dof_friction_coefficients().to(asset.device) + if friction_distribution_params is not None: + friction = asset.data.default_joint_friction.to(asset.device).clone() friction = _randomize_prop_by_op( - friction, friction_range, env_ids, joint_ids, operation=operation, distribution=distribution - ) + friction, friction_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution + )[env_ids][:, joint_ids] asset.write_joint_friction_to_sim(friction, joint_ids=joint_ids, env_ids=env_ids) # -- armature - if armature_range is not None: - armature = asset.root_physx_view.get_dof_armatures().to(asset.device) + if armature_distribution_params is not None: + armature = asset.data.default_joint_armature.to(asset.device).clone() armature = _randomize_prop_by_op( - armature, armature_range, env_ids, joint_ids, operation=operation, distribution=distribution - ) + armature, armature_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution + )[env_ids][:, joint_ids] asset.write_joint_armature_to_sim(armature, joint_ids=joint_ids, env_ids=env_ids) + # -- dof limits + if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: + dof_limits = asset.data.default_joint_limits.to(asset.device).clone() + if lower_limit_distribution_params is not None: + lower_limits = dof_limits[..., 0] + lower_limits = _randomize_prop_by_op( + lower_limits, + lower_limit_distribution_params, + env_ids, + joint_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, joint_ids] + dof_limits[env_ids[:, None], joint_ids, 0] = lower_limits + if upper_limit_distribution_params is not None: + upper_limits = dof_limits[..., 1] + upper_limits = _randomize_prop_by_op( + upper_limits, + upper_limit_distribution_params, + env_ids, + joint_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, joint_ids] + dof_limits[env_ids[:, None], joint_ids, 1] = upper_limits + if (dof_limits[env_ids[:, None], joint_ids, 0] > dof_limits[env_ids[:, None], joint_ids, 1]).any(): + raise ValueError( + "Randomization term 'randomize_joint_parameters' is setting lower joint limits that are greater than" + " upper joint limits." + ) + + asset.write_joint_limits_to_sim(dof_limits[env_ids][:, joint_ids], joint_ids=joint_ids, env_ids=env_ids) + + +def randomize_fixed_tendon_parameters( + env: ManagerBasedEnv, + env_ids: torch.Tensor | None, + asset_cfg: SceneEntityCfg, + stiffness_distribution_params: tuple[float, float] | None = None, + damping_distribution_params: tuple[float, float] | None = None, + limit_stiffness_distribution_params: tuple[float, float] | None = None, + lower_limit_distribution_params: tuple[float, float] | None = None, + upper_limit_distribution_params: tuple[float, float] | None = None, + rest_length_distribution_params: tuple[float, float] | None = None, + offset_distribution_params: tuple[float, float] | None = None, + operation: Literal["add", "scale", "abs"] = "abs", + distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", +): + """Randomize the fixed tendon parameters of an articulation by adding, scaling, or setting random values. + + This function allows randomizing the fixed tendon parameters of the asset. + These correspond to the physics engine tendon properties that affect the joint behavior. + + The function samples random values from the given distribution parameters and applies the operation to the tendon properties. + It then sets the values into the physics simulation. If the distribution parameters are not provided for a + particular property, the function does not modify the property. + + """ + # extract the used quantities (to enable type-hinting) + asset: Articulation = env.scene[asset_cfg.name] + + # resolve environment ids + if env_ids is None: + env_ids = torch.arange(env.scene.num_envs, device=asset.device) + + # resolve joint indices + if asset_cfg.fixed_tendon_ids == slice(None): + fixed_tendon_ids = slice(None) # for optimization purposes + else: + fixed_tendon_ids = torch.tensor(asset_cfg.fixed_tendon_ids, dtype=torch.int, device=asset.device) + + # sample tendon properties from the given ranges and set into the physics simulation + # -- stiffness + if stiffness_distribution_params is not None: + stiffness = asset.data.default_fixed_tendon_stiffness.clone() + stiffness = _randomize_prop_by_op( + stiffness, + stiffness_distribution_params, + env_ids, + fixed_tendon_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, fixed_tendon_ids] + asset.set_fixed_tendon_stiffness(stiffness, fixed_tendon_ids, env_ids) + # -- damping + if damping_distribution_params is not None: + damping = asset.data.default_fixed_tendon_damping.clone() + damping = _randomize_prop_by_op( + damping, + damping_distribution_params, + env_ids, + fixed_tendon_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, fixed_tendon_ids] + asset.set_fixed_tendon_damping(damping, fixed_tendon_ids, env_ids) + # -- limit stiffness + if limit_stiffness_distribution_params is not None: + limit_stiffness = asset.data.default_fixed_tendon_limit_stiffness.clone() + limit_stiffness = _randomize_prop_by_op( + limit_stiffness, + limit_stiffness_distribution_params, + env_ids, + fixed_tendon_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, fixed_tendon_ids] + asset.set_fixed_tendon_limit_stiffness(limit_stiffness, fixed_tendon_ids, env_ids) + # -- limits + if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: + limit = asset.data.default_fixed_tendon_limit.clone() + # -- lower limit + if lower_limit_distribution_params is not None: + lower_limit = limit[..., 0] + lower_limit = _randomize_prop_by_op( + lower_limit, + lower_limit_distribution_params, + env_ids, + fixed_tendon_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, fixed_tendon_ids] + limit[env_ids[:, None], fixed_tendon_ids, 0] = lower_limit + # -- upper limit + if upper_limit_distribution_params is not None: + upper_limit = limit[..., 1] + upper_limit = _randomize_prop_by_op( + upper_limit, + upper_limit_distribution_params, + env_ids, + fixed_tendon_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, fixed_tendon_ids] + limit[env_ids[:, None], fixed_tendon_ids, 1] = upper_limit + if (limit[env_ids[:, None], fixed_tendon_ids, 0] > limit[env_ids[:, None], fixed_tendon_ids, 1]).any(): + raise ValueError( + "Randomization term 'randomize_fixed_tendon_parameters' is setting lower tendon limits that are greater" + " than upper tendon limits." + ) + asset.set_fixed_tendon_limit(limit, fixed_tendon_ids, env_ids) + # -- rest length + if rest_length_distribution_params is not None: + rest_length = asset.data.default_fixed_tendon_rest_length.clone() + rest_length = _randomize_prop_by_op( + rest_length, + rest_length_distribution_params, + env_ids, + fixed_tendon_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, fixed_tendon_ids] + asset.set_fixed_tendon_rest_length(rest_length, fixed_tendon_ids, env_ids) + # -- offset + if offset_distribution_params is not None: + offset = asset.data.default_fixed_tendon_offset.clone() + offset = _randomize_prop_by_op( + offset, + offset_distribution_params, + env_ids, + fixed_tendon_ids, + operation=operation, + distribution=distribution, + )[env_ids][:, fixed_tendon_ids] + asset.set_fixed_tendon_offset(offset, fixed_tendon_ids, env_ids) + + asset.write_fixed_tendon_properties_to_sim(fixed_tendon_ids, env_ids) def apply_external_force_torque( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, force_range: tuple[float, float], torque_range: tuple[float, float], @@ -329,7 +553,7 @@ def apply_external_force_torque( def push_by_setting_velocity( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, velocity_range: dict[str, tuple[float, float]], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), @@ -357,7 +581,7 @@ def push_by_setting_velocity( def reset_root_state_uniform( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, pose_range: dict[str, tuple[float, float]], velocity_range: dict[str, tuple[float, float]], @@ -402,7 +626,7 @@ def reset_root_state_uniform( def reset_root_state_with_random_orientation( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, pose_range: dict[str, tuple[float, float]], velocity_range: dict[str, tuple[float, float]], @@ -454,7 +678,7 @@ def reset_root_state_with_random_orientation( def reset_root_state_from_terrain( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, pose_range: dict[str, tuple[float, float]], velocity_range: dict[str, tuple[float, float]], @@ -521,7 +745,7 @@ def reset_root_state_from_terrain( def reset_joints_by_scale( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, position_range: tuple[float, float], velocity_range: tuple[float, float], @@ -554,7 +778,7 @@ def reset_joints_by_scale( def reset_joints_by_offset( - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, position_range: tuple[float, float], velocity_range: tuple[float, float], @@ -587,7 +811,7 @@ def reset_joints_by_offset( asset.write_joint_state_to_sim(joint_pos, joint_vel, env_ids=env_ids) -def reset_scene_to_default(env: BaseEnv, env_ids: torch.Tensor): +def reset_scene_to_default(env: ManagerBasedEnv, env_ids: torch.Tensor): """Reset the scene to the default state specified in the scene configuration.""" # rigid bodies for rigid_object in env.scene.rigid_objects.values(): @@ -617,17 +841,17 @@ def reset_scene_to_default(env: BaseEnv, env_ids: torch.Tensor): def _randomize_prop_by_op( data: torch.Tensor, - sample_range: tuple[float, float], + distribution_parameters: tuple[float, float], dim_0_ids: torch.Tensor | None, dim_1_ids: torch.Tensor | slice, operation: Literal["add", "scale", "abs"], - distribution: Literal["uniform", "log_uniform"], + distribution: Literal["uniform", "log_uniform", "gaussian"], ) -> torch.Tensor: """Perform data randomization based on the given operation and distribution. Args: data: The data tensor to be randomized. Shape is (dim_0, dim_1). - sample_range: The range to sample the random values from. + distribution_parameters: The parameters for the distribution to sample values from. dim_0_ids: The indices of the first dimension to randomize. dim_1_ids: The indices of the second dimension to randomize. operation: The operation to perform on the data. Options: 'add', 'scale', 'abs'. @@ -646,6 +870,7 @@ def _randomize_prop_by_op( dim_0_ids = slice(None) else: n_dim_0 = len(dim_0_ids) + dim_0_ids = dim_0_ids[:, None] # -- dim 1 if isinstance(dim_1_ids, slice): n_dim_1 = data.shape[1] @@ -657,18 +882,20 @@ def _randomize_prop_by_op( dist_fn = math_utils.sample_uniform elif distribution == "log_uniform": dist_fn = math_utils.sample_log_uniform + elif distribution == "gaussian": + dist_fn = math_utils.sample_gaussian else: raise NotImplementedError( f"Unknown distribution: '{distribution}' for joint properties randomization." - " Please use 'uniform' or 'log_uniform'." + " Please use 'uniform', 'log_uniform', 'gaussian'." ) # perform the operation if operation == "add": - data[dim_0_ids, dim_1_ids] += dist_fn(*sample_range, (n_dim_0, n_dim_1), device=data.device) + data[dim_0_ids, dim_1_ids] += dist_fn(*distribution_parameters, (n_dim_0, n_dim_1), device=data.device) elif operation == "scale": - data[dim_0_ids, dim_1_ids] *= dist_fn(*sample_range, (n_dim_0, n_dim_1), device=data.device) + data[dim_0_ids, dim_1_ids] *= dist_fn(*distribution_parameters, (n_dim_0, n_dim_1), device=data.device) elif operation == "abs": - data[dim_0_ids, dim_1_ids] = dist_fn(*sample_range, (n_dim_0, n_dim_1), device=data.device) + data[dim_0_ids, dim_1_ids] = dist_fn(*distribution_parameters, (n_dim_0, n_dim_1), device=data.device) else: raise NotImplementedError( f"Unknown operation: '{operation}' for property randomization. Please use 'add', 'scale', or 'abs'." diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/observations.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/observations.py index 4573e14f87..d59299183f 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/observations.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/observations.py @@ -20,42 +20,42 @@ from omni.isaac.lab.sensors import RayCaster if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv, RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedRLEnv """ Root state. """ -def base_pos_z(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def base_pos_z(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Root height in the simulation world frame.""" # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] return asset.data.root_pos_w[:, 2].unsqueeze(-1) -def base_lin_vel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def base_lin_vel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Root linear velocity in the asset's root frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] return asset.data.root_lin_vel_b -def base_ang_vel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def base_ang_vel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Root angular velocity in the asset's root frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] return asset.data.root_ang_vel_b -def projected_gravity(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def projected_gravity(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Gravity projection on the asset's root frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] return asset.data.projected_gravity_b -def root_pos_w(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def root_pos_w(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Asset root position in the environment frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] @@ -63,7 +63,7 @@ def root_pos_w(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") def root_quat_w( - env: BaseEnv, make_quat_unique: bool = False, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedEnv, make_quat_unique: bool = False, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Asset root orientation (w, x, y, z) in the environment frame. @@ -79,14 +79,14 @@ def root_quat_w( return math_utils.quat_unique(quat) if make_quat_unique else quat -def root_lin_vel_w(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def root_lin_vel_w(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Asset root linear velocity in the environment frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] return asset.data.root_lin_vel_w -def root_ang_vel_w(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def root_ang_vel_w(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Asset root angular velocity in the environment frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] @@ -98,7 +98,7 @@ def root_ang_vel_w(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("rob """ -def joint_pos(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_pos(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """The joint positions of the asset. Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their positions returned. @@ -108,7 +108,7 @@ def joint_pos(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) return asset.data.joint_pos[:, asset_cfg.joint_ids] -def joint_pos_rel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_pos_rel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """The joint positions of the asset w.r.t. the default joint positions. Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their positions returned. @@ -118,7 +118,9 @@ def joint_pos_rel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robo return asset.data.joint_pos[:, asset_cfg.joint_ids] - asset.data.default_joint_pos[:, asset_cfg.joint_ids] -def joint_pos_limit_normalized(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_pos_limit_normalized( + env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") +) -> torch.Tensor: """The joint positions of the asset normalized with the asset's joint limits. Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their normalized positions returned. @@ -132,7 +134,7 @@ def joint_pos_limit_normalized(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEn ) -def joint_vel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")): +def joint_vel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")): """The joint velocities of the asset. Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their velocities returned. @@ -142,7 +144,7 @@ def joint_vel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) return asset.data.joint_vel[:, asset_cfg.joint_ids] -def joint_vel_rel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")): +def joint_vel_rel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")): """The joint velocities of the asset w.r.t. the default joint velocities. Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their velocities returned. @@ -157,7 +159,7 @@ def joint_vel_rel(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robo """ -def height_scan(env: BaseEnv, sensor_cfg: SceneEntityCfg, offset: float = 0.5) -> torch.Tensor: +def height_scan(env: ManagerBasedEnv, sensor_cfg: SceneEntityCfg, offset: float = 0.5) -> torch.Tensor: """Height scan from the given sensor w.r.t. the sensor's frame. The provided offset (Defaults to 0.5) is subtracted from the returned values. @@ -168,7 +170,7 @@ def height_scan(env: BaseEnv, sensor_cfg: SceneEntityCfg, offset: float = 0.5) - return sensor.data.pos_w[:, 2].unsqueeze(1) - sensor.data.ray_hits_w[..., 2] - offset -def body_incoming_wrench(env: BaseEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def body_incoming_wrench(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Incoming spatial wrench on bodies of an articulation in the simulation world frame. This is the 6-D wrench (force and torque) applied to the body link by the incoming joint force. @@ -185,7 +187,7 @@ def body_incoming_wrench(env: BaseEnv, asset_cfg: SceneEntityCfg) -> torch.Tenso """ -def last_action(env: BaseEnv, action_name: str | None = None) -> torch.Tensor: +def last_action(env: ManagerBasedEnv, action_name: str | None = None) -> torch.Tensor: """The last input action to the environment. The name of the action term for which the action is required. If None, the @@ -202,6 +204,6 @@ def last_action(env: BaseEnv, action_name: str | None = None) -> torch.Tensor: """ -def generated_commands(env: RLTaskEnv, command_name: str) -> torch.Tensor: +def generated_commands(env: ManagerBasedRLEnv, command_name: str) -> torch.Tensor: """The generated command from command term in the command manager with the given name.""" return env.command_manager.get_command(command_name) diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/rewards.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/rewards.py index b5474eed0d..e244b167fc 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/rewards.py @@ -21,19 +21,19 @@ from omni.isaac.lab.sensors import ContactSensor if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv """ General. """ -def is_alive(env: RLTaskEnv) -> torch.Tensor: +def is_alive(env: ManagerBasedRLEnv) -> torch.Tensor: """Reward for being alive.""" return (~env.termination_manager.terminated).float() -def is_terminated(env: RLTaskEnv) -> torch.Tensor: +def is_terminated(env: ManagerBasedRLEnv) -> torch.Tensor: """Penalize terminated episodes that don't correspond to episodic timeouts.""" return env.termination_manager.terminated.float() @@ -51,14 +51,14 @@ class is_terminated_term(ManagerTermBase): if two termination terms are active, the reward is 2. """ - def __init__(self, cfg: RewardTermCfg, env: RLTaskEnv): + def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): # initialize the base class super().__init__(cfg, env) # find and store the termination terms term_keys = cfg.params.get("term_keys", ".*") self._term_names = env.termination_manager.find_terms(term_keys) - def __call__(self, env: RLTaskEnv, term_keys: str | list[str] = ".*") -> torch.Tensor: + def __call__(self, env: ManagerBasedRLEnv, term_keys: str | list[str] = ".*") -> torch.Tensor: # Return the unweighted reward for the termination terms reset_buf = torch.zeros(env.num_envs, device=env.device) for term in self._term_names: @@ -73,21 +73,21 @@ def __call__(self, env: RLTaskEnv, term_keys: str | list[str] = ".*") -> torch.T """ -def lin_vel_z_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def lin_vel_z_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize z-axis base linear velocity using L2-kernel.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] return torch.square(asset.data.root_lin_vel_b[:, 2]) -def ang_vel_xy_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def ang_vel_xy_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize xy-axis base angular velocity using L2-kernel.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] return torch.sum(torch.square(asset.data.root_ang_vel_b[:, :2]), dim=1) -def flat_orientation_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def flat_orientation_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize non-flat base orientation using L2-kernel. This is computed by penalizing the xy-components of the projected gravity vector. @@ -98,7 +98,7 @@ def flat_orientation_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityC def base_height_l2( - env: RLTaskEnv, target_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, target_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Penalize asset height from its target using L2-kernel. @@ -111,7 +111,7 @@ def base_height_l2( return torch.square(asset.data.root_pos_w[:, 2] - target_height) -def body_lin_acc_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def body_lin_acc_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize the linear acceleration of bodies using L2-kernel.""" asset: Articulation = env.scene[asset_cfg.name] return torch.sum(torch.norm(asset.data.body_lin_acc_w[:, asset_cfg.body_ids, :], dim=-1), dim=1) @@ -122,7 +122,7 @@ def body_lin_acc_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg(" """ -def joint_torques_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_torques_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize joint torques applied on the articulation using L2-kernel. NOTE: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their joint torques contribute to the L2 norm. @@ -132,14 +132,14 @@ def joint_torques_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg( return torch.sum(torch.square(asset.data.applied_torque[:, asset_cfg.joint_ids]), dim=1) -def joint_vel_l1(env: RLTaskEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def joint_vel_l1(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Penalize joint velocities on the articulation using an L1-kernel.""" # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] return torch.sum(torch.abs(asset.data.joint_vel[:, asset_cfg.joint_ids]), dim=1) -def joint_vel_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_vel_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize joint velocities on the articulation using L1-kernel. NOTE: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their joint velocities contribute to the L1 norm. @@ -149,7 +149,7 @@ def joint_vel_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("rob return torch.sum(torch.square(asset.data.joint_vel[:, asset_cfg.joint_ids]), dim=1) -def joint_acc_l2(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_acc_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize joint accelerations on the articulation using L2-kernel. NOTE: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their joint accelerations contribute to the L2 norm. @@ -168,7 +168,7 @@ def joint_deviation_l1(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) return torch.sum(torch.abs(angle), dim=1) -def joint_pos_limits(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_pos_limits(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize joint positions if they cross the soft limits. This is computed as a sum of the absolute value of the difference between the joint position and the soft limits. @@ -186,7 +186,7 @@ def joint_pos_limits(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg( def joint_vel_limits( - env: RLTaskEnv, soft_ratio: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, soft_ratio: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Penalize joint velocities if they cross the soft limits. @@ -212,7 +212,7 @@ def joint_vel_limits( """ -def applied_torque_limits(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def applied_torque_limits(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Penalize applied torques if they cross the limits. This is computed as a sum of the absolute value of the difference between the applied torques and the limits. @@ -231,12 +231,12 @@ def applied_torque_limits(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntit return torch.sum(out_of_limits, dim=1) -def action_rate_l2(env: RLTaskEnv) -> torch.Tensor: +def action_rate_l2(env: ManagerBasedRLEnv) -> torch.Tensor: """Penalize the rate of change of the actions using L2-kernel.""" return torch.sum(torch.square(env.action_manager.action - env.action_manager.prev_action), dim=1) -def action_l2(env: RLTaskEnv) -> torch.Tensor: +def action_l2(env: ManagerBasedRLEnv) -> torch.Tensor: """Penalize the actions using L2-kernel.""" return torch.sum(torch.square(env.action_manager.action), dim=1) @@ -246,7 +246,7 @@ def action_l2(env: RLTaskEnv) -> torch.Tensor: """ -def undesired_contacts(env: RLTaskEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: +def undesired_contacts(env: ManagerBasedRLEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: """Penalize undesired contacts as the number of violations that are above a threshold.""" # extract the used quantities (to enable type-hinting) contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name] @@ -257,7 +257,7 @@ def undesired_contacts(env: RLTaskEnv, threshold: float, sensor_cfg: SceneEntity return torch.sum(is_contact, dim=1) -def contact_forces(env: RLTaskEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: +def contact_forces(env: ManagerBasedRLEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: """Penalize contact forces as the amount of violations of the net contact force.""" # extract the used quantities (to enable type-hinting) contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name] @@ -274,7 +274,7 @@ def contact_forces(env: RLTaskEnv, threshold: float, sensor_cfg: SceneEntityCfg) def track_lin_vel_xy_exp( - env: RLTaskEnv, std: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, std: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Reward tracking of linear velocity commands (xy axes) using exponential kernel.""" # extract the used quantities (to enable type-hinting) @@ -288,7 +288,7 @@ def track_lin_vel_xy_exp( def track_ang_vel_z_exp( - env: RLTaskEnv, std: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, std: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Reward tracking of angular velocity commands (yaw) using exponential kernel.""" # extract the used quantities (to enable type-hinting) diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/terminations.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/terminations.py index ae123b7893..a91bb39581 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/terminations.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/mdp/terminations.py @@ -19,7 +19,7 @@ from omni.isaac.lab.sensors import ContactSensor if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv from omni.isaac.lab.managers.command_manager import CommandTerm """ @@ -27,12 +27,12 @@ """ -def time_out(env: RLTaskEnv) -> torch.Tensor: +def time_out(env: ManagerBasedRLEnv) -> torch.Tensor: """Terminate the episode when the episode length exceeds the maximum episode length.""" return env.episode_length_buf >= env.max_episode_length -def command_resample(env: RLTaskEnv, command_name: str, num_resamples: int = 1) -> torch.Tensor: +def command_resample(env: ManagerBasedRLEnv, command_name: str, num_resamples: int = 1) -> torch.Tensor: """Terminate the episode based on the total number of times commands have been re-sampled. This makes the maximum episode length fluid in nature as it depends on how the commands are @@ -48,7 +48,7 @@ def command_resample(env: RLTaskEnv, command_name: str, num_resamples: int = 1) def bad_orientation( - env: RLTaskEnv, limit_angle: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, limit_angle: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Terminate when the asset's orientation is too far from the desired orientation limits. @@ -60,7 +60,7 @@ def bad_orientation( def root_height_below_minimum( - env: RLTaskEnv, minimum_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, minimum_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Terminate when the asset's root height is below the minimum height. @@ -77,7 +77,7 @@ def root_height_below_minimum( """ -def joint_pos_out_of_limit(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_pos_out_of_limit(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Terminate when the asset's joint positions are outside of the soft joint limits.""" # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] @@ -88,7 +88,7 @@ def joint_pos_out_of_limit(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEnti def joint_pos_out_of_manual_limit( - env: RLTaskEnv, bounds: tuple[float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, bounds: tuple[float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Terminate when the asset's joint positions are outside of the configured bounds. @@ -105,7 +105,7 @@ def joint_pos_out_of_manual_limit( return torch.logical_or(out_of_upper_limits, out_of_lower_limits) -def joint_vel_out_of_limit(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_vel_out_of_limit(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Terminate when the asset's joint velocities are outside of the soft joint limits.""" # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] @@ -115,7 +115,7 @@ def joint_vel_out_of_limit(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEnti def joint_vel_out_of_manual_limit( - env: RLTaskEnv, max_velocity: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, max_velocity: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Terminate when the asset's joint velocities are outside the provided limits.""" # extract the used quantities (to enable type-hinting) @@ -124,7 +124,9 @@ def joint_vel_out_of_manual_limit( return torch.any(torch.abs(asset.data.joint_vel[:, asset_cfg.joint_ids]) > max_velocity, dim=1) -def joint_effort_out_of_limit(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def joint_effort_out_of_limit( + env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") +) -> torch.Tensor: """Terminate when effort applied on the asset's joints are outside of the soft joint limits. In the actuators, the applied torque are the efforts applied on the joints. These are computed by clipping @@ -145,7 +147,7 @@ def joint_effort_out_of_limit(env: RLTaskEnv, asset_cfg: SceneEntityCfg = SceneE """ -def illegal_contact(env: RLTaskEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: +def illegal_contact(env: ManagerBasedRLEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: """Terminate when the contact force on the sensor exceeds the force threshold.""" # extract the used quantities (to enable type-hinting) contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name] diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_env_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_env_cfg.py new file mode 100644 index 0000000000..34e0d79a68 --- /dev/null +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_env_cfg.py @@ -0,0 +1,184 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from dataclasses import MISSING + +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.noise.noise_cfg import NoiseModelCfg + +from .base_env_cfg import ManagerBasedEnvCfg, ViewerCfg +from .ui import BaseEnvWindow, ManagerBasedRLEnvWindow + + +@configclass +class ManagerBasedRLEnvCfg(ManagerBasedEnvCfg): + """Configuration for a reinforcement learning environment with the manager-based workflow.""" + + # ui settings + ui_window_class_type: type | None = ManagerBasedRLEnvWindow + + # general settings + is_finite_horizon: bool = False + """Whether the learning task is treated as a finite or infinite horizon problem for the agent. + Defaults to False, which means the task is treated as an infinite horizon problem. + + This flag handles the subtleties of finite and infinite horizon tasks: + + * **Finite horizon**: no penalty or bootstrapping value is required by the the agent for + running out of time. However, the environment still needs to terminate the episode after the + time limit is reached. + * **Infinite horizon**: the agent needs to bootstrap the value of the state at the end of the episode. + This is done by sending a time-limit (or truncated) done signal to the agent, which triggers this + bootstrapping calculation. + + If True, then the environment is treated as a finite horizon problem and no time-out (or truncated) done signal + is sent to the agent. If False, then the environment is treated as an infinite horizon problem and a time-out + (or truncated) done signal is sent to the agent. + + Note: + The base :class:`ManagerBasedRLEnv` class does not use this flag directly. It is used by the environment + wrappers to determine what type of done signal to send to the corresponding learning agent. + """ + + episode_length_s: float = MISSING + """Duration of an episode (in seconds). + + Based on the decimation rate and physics time step, the episode length is calculated as: + + .. code-block:: python + + episode_length_steps = ceil(episode_length_s / (decimation_rate * physics_time_step)) + + For example, if the decimation rate is 10, the physics time step is 0.01, and the episode length is 10 seconds, + then the episode length in steps is 100. + """ + + # environment settings + rewards: object = MISSING + """Reward settings. + + Please refer to the :class:`omni.isaac.lab.managers.RewardManager` class for more details. + """ + + terminations: object = MISSING + """Termination settings. + + Please refer to the :class:`omni.isaac.lab.managers.TerminationManager` class for more details. + """ + + curriculum: object = MISSING + """Curriculum settings. + + Please refer to the :class:`omni.isaac.lab.managers.CurriculumManager` class for more details. + """ + + commands: object = MISSING + """Command settings. + + Please refer to the :class:`omni.isaac.lab.managers.CommandManager` class for more details. + """ + + +@configclass +class DirectRLEnvCfg(ManagerBasedEnvCfg): + """Configuration for a reinforcement learning environment with the direct workflow.""" + + # simulation settings + viewer: ViewerCfg = ViewerCfg() + """Viewer configuration. Default is ViewerCfg().""" + sim: SimulationCfg = SimulationCfg() + """Physics simulation configuration. Default is SimulationCfg().""" + + # ui settings + ui_window_class_type: type | None = BaseEnvWindow + """The class type of the UI window. Default is None. + + If None, then no UI window is created. + + Note: + If you want to make your own UI window, you can create a class that inherits from + from :class:`omni.isaac.lab.envs.ui.base_env_window.BaseEnvWindow`. Then, you can set + this attribute to your class type. + """ + + # general settings + decimation: int = MISSING + """Number of control action updates @ sim dt per policy dt. + + For instance, if the simulation dt is 0.01s and the policy dt is 0.1s, then the decimation is 10. + This means that the control action is updated every 10 simulation steps. + """ + + # environment settings + scene: InteractiveSceneCfg = MISSING + """Scene settings. + + Please refer to the :class:`omni.isaac.lab.scene.InteractiveSceneCfg` class for more details. + """ + + # general settings + is_finite_horizon: bool = False + """Whether the learning task is treated as a finite or infinite horizon problem for the agent. + Defaults to False, which means the task is treated as an infinite horizon problem. + + This flag handles the subtleties of finite and infinite horizon tasks: + + * **Finite horizon**: no penalty or bootstrapping value is required by the the agent for + running out of time. However, the environment still needs to terminate the episode after the + time limit is reached. + * **Infinite horizon**: the agent needs to bootstrap the value of the state at the end of the episode. + This is done by sending a time-limit (or truncated) done signal to the agent, which triggers this + bootstrapping calculation. + + If True, then the environment is treated as a finite horizon problem and no time-out (or truncated) done signal + is sent to the agent. If False, then the environment is treated as an infinite horizon problem and a time-out + (or truncated) done signal is sent to the agent. + + Note: + The base :class:`ManagerBasedRLEnv` class does not use this flag directly. It is used by the environment + wrappers to determine what type of done signal to send to the corresponding learning agent. + """ + + episode_length_s: float = MISSING + """Duration of an episode (in seconds). + + Based on the decimation rate and physics time step, the episode length is calculated as: + + .. code-block:: python + + episode_length_steps = ceil(episode_length_s / (decimation_rate * physics_time_step)) + + For example, if the decimation rate is 10, the physics time step is 0.01, and the episode length is 10 seconds, + then the episode length in steps is 100. + """ + + num_observations: int = MISSING + """The size of the observation for each environment.""" + + num_states: int = 0 + """The size of the state-space for each environment. Default is 0. + + This is used for asymmetric actor-critic and defines the observation space for the critic. + """ + + num_actions: int = MISSING + """The size of the action space for each environment.""" + + events: object = None + """Settings for specifying domain randomization terms during training. + Please refer to the :class:`omni.isaac.lab.managers.EventManager` class for more details. + """ + + action_noise_model: NoiseModelCfg | None = None + """Settings for adding noise to the action buffer. + Please refer to the :class:`omni.isaac.lab.utils.noise.NoiseModel` class for more details. + """ + + observation_noise_model: NoiseModelCfg | None = None + """Settings for adding noise to the observation buffer. + Please refer to the :class:`omni.isaac.lab.utils.noise.NoiseModel` class for more details. + """ diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_task_env_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_task_env_cfg.py deleted file mode 100644 index 3fca6c7957..0000000000 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/rl_task_env_cfg.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2022-2024, The Isaac Lab Project Developers. -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -from dataclasses import MISSING - -from omni.isaac.lab.utils import configclass - -from .base_env_cfg import BaseEnvCfg -from .ui import RLTaskEnvWindow - - -@configclass -class RLTaskEnvCfg(BaseEnvCfg): - """Configuration for a reinforcement learning environment.""" - - # ui settings - ui_window_class_type: type | None = RLTaskEnvWindow - - # general settings - is_finite_horizon: bool = False - """Whether the learning task is treated as a finite or infinite horizon problem for the agent. - Defaults to False, which means the task is treated as an infinite horizon problem. - - This flag handles the subtleties of finite and infinite horizon tasks: - - * **Finite horizon**: no penalty or bootstrapping value is required by the the agent for - running out of time. However, the environment still needs to terminate the episode after the - time limit is reached. - * **Infinite horizon**: the agent needs to bootstrap the value of the state at the end of the episode. - This is done by sending a time-limit (or truncated) done signal to the agent, which triggers this - bootstrapping calculation. - - If True, then the environment is treated as a finite horizon problem and no time-out (or truncated) done signal - is sent to the agent. If False, then the environment is treated as an infinite horizon problem and a time-out - (or truncated) done signal is sent to the agent. - - Note: - The base :class:`RLTaskEnv` class does not use this flag directly. It is used by the environment - wrappers to determine what type of done signal to send to the corresponding learning agent. - """ - - episode_length_s: float = MISSING - """Duration of an episode (in seconds). - - Based on the decimation rate and physics time step, the episode length is calculated as: - - .. code-block:: python - - episode_length_steps = ceil(episode_length_s / (decimation_rate * physics_time_step)) - - For example, if the decimation rate is 10, the physics time step is 0.01, and the episode length is 10 seconds, - then the episode length in steps is 100. - """ - - # environment settings - rewards: object = MISSING - """Reward settings. - - Please refer to the :class:`omni.isaac.lab.managers.RewardManager` class for more details. - """ - - terminations: object = MISSING - """Termination settings. - - Please refer to the :class:`omni.isaac.lab.managers.TerminationManager` class for more details. - """ - - curriculum: object = MISSING - """Curriculum settings. - - Please refer to the :class:`omni.isaac.lab.managers.CurriculumManager` class for more details. - """ - - commands: object = MISSING - """Command settings. - - Please refer to the :class:`omni.isaac.lab.managers.CommandManager` class for more details. - """ diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/types.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/types.py new file mode 100644 index 0000000000..faa182be40 --- /dev/null +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/types.py @@ -0,0 +1,44 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import torch +from typing import Dict + +VecEnvObs = Dict[str, torch.Tensor | Dict[str, torch.Tensor]] +"""Observation returned by the environment. + +The observations are stored in a dictionary. The keys are the group to which the observations belong. +This is useful for various setups such as reinforcement learning with asymmetric actor-critic or +multi-agent learning. For non-learning paradigms, this may include observations for different components +of a system. + +Within each group, the observations can be stored either as a dictionary with keys as the names of each +observation term in the group, or a single tensor obtained from concatenating all the observation terms. +For example, for asymmetric actor-critic, the observation for the actor and the critic can be accessed +using the keys ``"policy"`` and ``"critic"`` respectively. + +Note: + By default, most learning frameworks deal with default and privileged observations in different ways. + This handling must be taken care of by the wrapper around the :class:`ManagerBasedRLEnv` instance. + + For included frameworks (RSL-RL, RL-Games, skrl), the observations must have the key "policy". In case, + the key "critic" is also present, then the critic observations are taken from the "critic" group. + Otherwise, they are the same as the "policy" group. + +""" + +VecEnvStepReturn = tuple[VecEnvObs, torch.Tensor, torch.Tensor, torch.Tensor, Dict] +"""The environment signals processed at the end of each step. + +The tuple contains batched information for each sub-environment. The information is stored in the following order: + +1. **Observations**: The observations from the environment. +2. **Rewards**: The rewards from the environment. +3. **Terminated Dones**: Whether the environment reached a terminal state, such as task success or robot falling etc. +4. **Timeout Dones**: Whether the environment reached a timeout state, such as end of max episode length. +5. **Extras**: A dictionary containing additional information from the environment. +""" diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/__init__.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/__init__.py index a987ba15ca..1411a7dd08 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/__init__.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/__init__.py @@ -11,5 +11,5 @@ """ from .base_env_window import BaseEnvWindow -from .rl_task_env_window import RLTaskEnvWindow +from .manager_based_rl_env_window import ManagerBasedRLEnvWindow from .viewport_camera_controller import ViewportCameraController diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/base_env_window.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/base_env_window.py index 26216aca39..a37a8723ec 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/base_env_window.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/base_env_window.py @@ -11,16 +11,15 @@ from datetime import datetime from typing import TYPE_CHECKING -import omni.isaac.ui.ui_utils as ui_utils import omni.kit.app import omni.kit.commands -import omni.ui import omni.usd -from omni.kit.window.extensions import SimpleCheckBox from pxr import PhysxSchema, Sdf, Usd, UsdGeom, UsdPhysics if TYPE_CHECKING: - from ..base_env import BaseEnv + import omni.ui + + from ..manager_based_env import ManagerBasedEnv class BaseEnvWindow: @@ -40,7 +39,7 @@ class BaseEnvWindow: """ - def __init__(self, env: BaseEnv, window_name: str = "IsaacLab"): + def __init__(self, env: ManagerBasedEnv, window_name: str = "IsaacLab"): """Initialize the window. Args: @@ -102,7 +101,7 @@ def _build_sim_frame(self): width=omni.ui.Fraction(1), height=0, collapsed=False, - style=ui_utils.get_style(), + style=omni.isaac.ui.ui_utils.get_style(), horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED, vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON, ) @@ -119,7 +118,7 @@ def _build_sim_frame(self): "tooltip": "Select a rendering mode\n" + self.env.sim.RenderMode.__doc__, "on_clicked_fn": lambda value: self.env.sim.set_render_mode(self.env.sim.RenderMode[value]), } - self.ui_window_elements["render_dropdown"] = ui_utils.dropdown_builder(**render_mode_cfg) + self.ui_window_elements["render_dropdown"] = omni.isaac.ui.ui_utils.dropdown_builder(**render_mode_cfg) # create animation recording box record_animate_cfg = { @@ -130,7 +129,9 @@ def _build_sim_frame(self): "tooltip": "Record the animation of the scene. Only effective if fabric is disabled.", "on_clicked_fn": lambda value: self._toggle_recording_animation_fn(value), } - self.ui_window_elements["record_animation"] = ui_utils.state_btn_builder(**record_animate_cfg) + self.ui_window_elements["record_animation"] = omni.isaac.ui.ui_utils.state_btn_builder( + **record_animate_cfg + ) # disable the button if fabric is not enabled self.ui_window_elements["record_animation"].enabled = not self.env.sim.is_fabric_enabled() @@ -142,7 +143,7 @@ def _build_viewer_frame(self): width=omni.ui.Fraction(1), height=0, collapsed=False, - style=ui_utils.get_style(), + style=omni.isaac.ui.ui_utils.get_style(), horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED, vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON, ) @@ -160,7 +161,7 @@ def _build_viewer_frame(self): "max": self.env.num_envs, "tooltip": "The environment index to follow. Only effective if follow mode is not 'World'.", } - self.ui_window_elements["viewer_env_index"] = ui_utils.int_builder(**viewport_origin_cfg) + self.ui_window_elements["viewer_env_index"] = omni.isaac.ui.ui_utils.int_builder(**viewport_origin_cfg) # create a number slider to move to environment origin self.ui_window_elements["viewer_env_index"].add_value_changed_fn(self._set_viewer_env_index_fn) @@ -173,17 +174,17 @@ def _build_viewer_frame(self): "tooltip": "Select the viewport camera following mode.", "on_clicked_fn": self._set_viewer_origin_type_fn, } - self.ui_window_elements["viewer_follow"] = ui_utils.dropdown_builder(**viewer_follow_cfg) + self.ui_window_elements["viewer_follow"] = omni.isaac.ui.ui_utils.dropdown_builder(**viewer_follow_cfg) # add viewer default eye and lookat locations - self.ui_window_elements["viewer_eye"] = ui_utils.xyz_builder( + self.ui_window_elements["viewer_eye"] = omni.isaac.ui.ui_utils.xyz_builder( label="Camera Eye", tooltip="Modify the XYZ location of the viewer eye.", default_val=self.env.cfg.viewer.eye, step=0.1, on_value_changed_fn=[self._set_viewer_location_fn] * 3, ) - self.ui_window_elements["viewer_lookat"] = ui_utils.xyz_builder( + self.ui_window_elements["viewer_lookat"] = omni.isaac.ui.ui_utils.xyz_builder( label="Camera Target", tooltip="Modify the XYZ location of the viewer target.", default_val=self.env.cfg.viewer.lookat, @@ -199,13 +200,16 @@ def _build_debug_vis_frame(self): that has it implemented. If the element does not have a debug visualization implemented, a label is created instead. """ + # import omni.isaac.ui.ui_utils as ui_utils + # import omni.ui + # create collapsable frame for debug visualization self.ui_window_elements["debug_frame"] = omni.ui.CollapsableFrame( title="Scene Debug Visualization", width=omni.ui.Fraction(1), height=0, collapsed=False, - style=ui_utils.get_style(), + style=omni.isaac.ui.ui_utils.get_style(), horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED, vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON, ) @@ -360,6 +364,8 @@ def _set_viewer_env_index_fn(self, model: omni.ui.SimpleIntModel): def _create_debug_vis_ui_element(self, name: str, elem: object): """Create a checkbox for toggling debug visualization for the given element.""" + from omni.kit.window.extensions import SimpleCheckBox + with omni.ui.HStack(): # create the UI element text = ( @@ -369,7 +375,7 @@ def _create_debug_vis_ui_element(self, name: str, elem: object): ) omni.ui.Label( name.replace("_", " ").title(), - width=ui_utils.LABEL_WIDTH - 12, + width=omni.isaac.ui.ui_utils.LABEL_WIDTH - 12, alignment=omni.ui.Alignment.LEFT_CENTER, tooltip=text, ) @@ -379,7 +385,7 @@ def _create_debug_vis_ui_element(self, name: str, elem: object): checked=elem.cfg.debug_vis, on_checked_fn=lambda value, e=weakref.proxy(elem): e.set_debug_vis(value), ) - ui_utils.add_line_rect_flourish() + omni.isaac.ui.ui_utils.add_line_rect_flourish() async def _dock_window(self, window_title: str): """Docks the custom UI window to the property window.""" diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/rl_task_env_window.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/manager_based_rl_env_window.py similarity index 78% rename from source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/rl_task_env_window.py rename to source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/manager_based_rl_env_window.py index 0e1cafc012..3e149e1fef 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/rl_task_env_window.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/manager_based_rl_env_window.py @@ -10,17 +10,17 @@ from .base_env_window import BaseEnvWindow if TYPE_CHECKING: - from ..rl_task_env import RLTaskEnv + from ..manager_based_rl_env import ManagerBasedRLEnv -class RLTaskEnvWindow(BaseEnvWindow): +class ManagerBasedRLEnvWindow(BaseEnvWindow): """Window manager for the RL environment. On top of the basic environment window, this class adds controls for the RL environment. This includes visualization of the command manager. """ - def __init__(self, env: RLTaskEnv, window_name: str = "IsaacLab"): + def __init__(self, env: ManagerBasedRLEnv, window_name: str = "IsaacLab"): """Initialize the window. Args: @@ -34,5 +34,5 @@ def __init__(self, env: RLTaskEnv, window_name: str = "IsaacLab"): with self.ui_window_elements["main_vstack"]: with self.ui_window_elements["debug_frame"]: with self.ui_window_elements["debug_vstack"]: - # add command manager visualization self._create_debug_vis_ui_element("commands", self.env.command_manager) + self._create_debug_vis_ui_element("actions", self.env.action_manager) diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/viewport_camera_controller.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/viewport_camera_controller.py index 3f776ebba3..a1fef9f445 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/viewport_camera_controller.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/viewport_camera_controller.py @@ -16,7 +16,7 @@ import omni.timeline if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv, ViewerCfg + from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedEnv, ViewerCfg class ViewportCameraController: @@ -34,7 +34,7 @@ class ViewportCameraController: root position. For this, it registers a callback to the post update event stream from the simulation app. """ - def __init__(self, env: BaseEnv, cfg: ViewerCfg): + def __init__(self, env: ManagerBasedEnv | DirectRLEnv, cfg: ViewerCfg): """Initialize the ViewportCameraController. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/action_manager.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/action_manager.py index d9ab610540..56a7ff92dc 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/action_manager.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/action_manager.py @@ -7,19 +7,23 @@ from __future__ import annotations +import inspect import torch +import weakref from abc import abstractmethod from collections.abc import Sequence from prettytable import PrettyTable from typing import TYPE_CHECKING +import omni.kit.app + from omni.isaac.lab.assets import AssetBase from .manager_base import ManagerBase, ManagerTermBase from .manager_term_cfg import ActionTermCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv class ActionTerm(ManagerTermBase): @@ -35,7 +39,7 @@ class ActionTerm(ManagerTermBase): responsible for applying the processed actions to the asset managed by the term. """ - def __init__(self, cfg: ActionTermCfg, env: BaseEnv): + def __init__(self, cfg: ActionTermCfg, env: ManagerBasedEnv): """Initialize the action term. Args: @@ -47,6 +51,17 @@ def __init__(self, cfg: ActionTermCfg, env: BaseEnv): # parse config to obtain asset to which the term is applied self._asset: AssetBase = self._env.scene[self.cfg.asset_name] + # add handle for debug visualization (this is set to a valid handle inside set_debug_vis) + self._debug_vis_handle = None + # set initial state of debug visualization + self.set_debug_vis(self.cfg.debug_vis) + + def __del__(self): + """Unsubscribe from the callbacks.""" + if self._debug_vis_handle: + self._debug_vis_handle.unsubscribe() + self._debug_vis_handle = None + """ Properties. """ @@ -69,10 +84,46 @@ def processed_actions(self) -> torch.Tensor: """The actions computed by the term after applying any processing.""" raise NotImplementedError + @property + def has_debug_vis_implementation(self) -> bool: + """Whether the action term has a debug visualization implemented.""" + # check if function raises NotImplementedError + source_code = inspect.getsource(self._set_debug_vis_impl) + return "NotImplementedError" not in source_code + """ Operations. """ + def set_debug_vis(self, debug_vis: bool) -> bool: + """Sets whether to visualize the action term data. + Args: + debug_vis: Whether to visualize the action term data. + Returns: + Whether the debug visualization was successfully set. False if the action term does + not support debug visualization. + """ + # check if debug visualization is supported + if not self.has_debug_vis_implementation: + return False + # toggle debug visualization objects + self._set_debug_vis_impl(debug_vis) + # toggle debug visualization handles + if debug_vis: + # create a subscriber for the post update event if it doesn't exist + if self._debug_vis_handle is None: + app_interface = omni.kit.app.get_app_interface() + self._debug_vis_handle = app_interface.get_post_update_event_stream().create_subscription_to_pop( + lambda event, obj=weakref.proxy(self): obj._debug_vis_callback(event) + ) + else: + # remove the subscriber if it exists + if self._debug_vis_handle is not None: + self._debug_vis_handle.unsubscribe() + self._debug_vis_handle = None + # return success + return True + @abstractmethod def process_actions(self, actions: torch.Tensor): """Processes the actions sent to the environment. @@ -94,6 +145,20 @@ def apply_actions(self): """ raise NotImplementedError + def _set_debug_vis_impl(self, debug_vis: bool): + """Set debug visualization into visualization objects. + This function is responsible for creating the visualization objects if they don't exist + and input ``debug_vis`` is True. If the visualization objects exist, the function should + set their visibility into the stage. + """ + raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") + + def _debug_vis_callback(self, event): + """Callback for debug visualization. + This function calls the visualization objects and sets the data to visualize into them. + """ + raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") + class ActionManager(ManagerBase): """Manager for processing and applying actions for a given world. @@ -110,7 +175,7 @@ class ActionManager(ManagerBase): scene (such as robots). It should be called before every simulation step. """ - def __init__(self, cfg: object, env: BaseEnv): + def __init__(self, cfg: object, env: ManagerBasedEnv): """Initialize the action manager. Args: @@ -122,6 +187,10 @@ def __init__(self, cfg: object, env: BaseEnv): self._action = torch.zeros((self.num_envs, self.total_action_dim), device=self.device) self._prev_action = torch.zeros_like(self._action) + self.cfg.debug_vis = False + for term in self._terms.values(): + self.cfg.debug_vis |= term.cfg.debug_vis + def __str__(self) -> str: """Returns: A string representation for action manager.""" msg = f" contains {len(self._term_names)} active terms.\n" @@ -171,10 +240,30 @@ def prev_action(self) -> torch.Tensor: """The previous actions sent to the environment. Shape is (num_envs, total_action_dim).""" return self._prev_action + @property + def has_debug_vis_implementation(self) -> bool: + """Whether the command terms have debug visualization implemented.""" + # check if function raises NotImplementedError + has_debug_vis = False + for term in self._terms.values(): + has_debug_vis |= term.has_debug_vis_implementation + return has_debug_vis + """ Operations. """ + def set_debug_vis(self, debug_vis: bool) -> bool: + """Sets whether to visualize the action data. + Args: + debug_vis: Whether to visualize the action data. + Returns: + Whether the debug visualization was successfully set. False if the action + does not support debug visualization. + """ + for term in self._terms.values(): + term.set_debug_vis(debug_vis) + def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: """Resets the action history. diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/command_manager.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/command_manager.py index 82310bf3e0..5cf7e929ac 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/command_manager.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/command_manager.py @@ -21,7 +21,7 @@ from .manager_term_cfg import CommandTermCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv class CommandTerm(ManagerTermBase): @@ -37,7 +37,7 @@ class CommandTerm(ManagerTermBase): that can be used to visualize the command in the simulator. """ - def __init__(self, cfg: CommandTermCfg, env: RLTaskEnv): + def __init__(self, cfg: CommandTermCfg, env: ManagerBasedRLEnv): """Initialize the command generator class. Args: @@ -176,11 +176,12 @@ def _resample(self, env_ids: Sequence[int]): env_ids: The list of environment IDs to resample. """ # resample the time left before resampling - self.time_left[env_ids] = self.time_left[env_ids].uniform_(*self.cfg.resampling_time_range) - # increment the command counter - self.command_counter[env_ids] += 1 - # resample the command - self._resample_command(env_ids) + if len(env_ids) != 0: + self.time_left[env_ids] = self.time_left[env_ids].uniform_(*self.cfg.resampling_time_range) + # increment the command counter + self.command_counter[env_ids] += 1 + # resample the command + self._resample_command(env_ids) """ Implementation specific functions. @@ -232,10 +233,10 @@ class CommandManager(ManagerBase): :class:`CommandTermCfg` class. """ - _env: RLTaskEnv + _env: ManagerBasedRLEnv """The environment instance.""" - def __init__(self, cfg: object, env: RLTaskEnv): + def __init__(self, cfg: object, env: ManagerBasedRLEnv): """Initialize the command manager. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/curriculum_manager.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/curriculum_manager.py index 79d190cd5e..b9bef068bf 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/curriculum_manager.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/curriculum_manager.py @@ -16,7 +16,7 @@ from .manager_term_cfg import CurriculumTermCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv class CurriculumManager(ManagerBase): @@ -30,10 +30,10 @@ class CurriculumManager(ManagerBase): parameters. Each curriculum term should instantiate the :class:`CurriculumTermCfg` class. """ - _env: RLTaskEnv + _env: ManagerBasedRLEnv """The environment instance.""" - def __init__(self, cfg: object, env: RLTaskEnv): + def __init__(self, cfg: object, env: ManagerBasedRLEnv): """Initialize the manager. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/event_manager.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/event_manager.py index a712d6a39d..dcf5dda18f 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/event_manager.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/event_manager.py @@ -19,7 +19,7 @@ from .manager_term_cfg import EventTermCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv class EventManager(ManagerBase): @@ -53,10 +53,10 @@ class EventManager(ManagerBase): """ - _env: RLTaskEnv + _env: ManagerBasedRLEnv """The environment instance.""" - def __init__(self, cfg: object, env: RLTaskEnv): + def __init__(self, cfg: object, env: ManagerBasedRLEnv): """Initialize the event manager. Args: @@ -150,15 +150,28 @@ def apply(self, mode: str, env_ids: Sequence[int] | None = None, dt: float | Non f"Event mode '{mode}' requires the time step of the environment" " to be passed to the event manager." ) - # extract time left for this term - time_left = self._interval_mode_time_left[index] - # update the time left for each environment - time_left -= dt - # check if the interval has passed - env_ids = (time_left <= 0.0).nonzero().flatten() - if len(env_ids) > 0: - lower, upper = term_cfg.interval_range_s - time_left[env_ids] = torch.rand(len(env_ids), device=self.device) * (upper - lower) + lower + if term_cfg.is_global_time: + # extract time left for this term + time_left = self._interval_mode_time_global[index] + # update the time left for each environment + time_left -= dt + # check if the interval has passed + if time_left <= 0.0: + lower, upper = term_cfg.interval_range_s + self._interval_mode_time_global[index] = torch.rand(1) * (upper - lower) + lower + else: + # no need to call func to sample + continue + else: + # extract time left for this term + time_left = self._interval_mode_time_left[index] + # update the time left for each environment + time_left -= dt + # check if the interval has passed + env_ids = (time_left <= 0.0).nonzero().flatten() + if len(env_ids) > 0: + lower, upper = term_cfg.interval_range_s + time_left[env_ids] = torch.rand(len(env_ids), device=self.device) * (upper - lower) + lower # call the event term term_cfg.func(self._env, env_ids, **term_cfg.params) @@ -220,6 +233,8 @@ def _prepare_terms(self): self._mode_class_term_cfgs: dict[str, list[EventTermCfg]] = dict() # buffer to store the time left for each environment for "interval" mode self._interval_mode_time_left: list[torch.Tensor] = list() + # global timer for "interval" mode for global properties + self._interval_mode_time_global: list[torch.Tensor] = list() # check if config is dict already if isinstance(self.cfg, dict): @@ -248,6 +263,7 @@ def _prepare_terms(self): # add term name and parameters self._mode_term_names[term_cfg.mode].append(term_name) self._mode_term_cfgs[term_cfg.mode].append(term_cfg) + # check if the term is a class if isinstance(term_cfg.func, ManagerTermBase): self._mode_class_term_cfgs[term_cfg.mode].append(term_cfg) @@ -258,10 +274,17 @@ def _prepare_terms(self): raise ValueError( f"Event term '{term_name}' has mode 'interval' but 'interval_range_s' is not specified." ) - # sample the time left for each environment - lower, upper = term_cfg.interval_range_s - time_left = torch.rand(self.num_envs, device=self.device) * (upper - lower) + lower - self._interval_mode_time_left.append(time_left) + + # sample the time left for global + if term_cfg.is_global_time: + lower, upper = term_cfg.interval_range_s + time_left = torch.rand(1) * (upper - lower) + lower + self._interval_mode_time_global.append(time_left) + else: + # sample the time left for each environment + lower, upper = term_cfg.interval_range_s + time_left = torch.rand(self.num_envs, device=self.device) * (upper - lower) + lower + self._interval_mode_time_left.append(time_left) class RandomizationManager(EventManager): @@ -272,7 +295,7 @@ class RandomizationManager(EventManager): renamed to EventManager as it is more general purpose. The RandomizationManager will be removed in v0.4.0. """ - def __init__(self, cfg: object, env: RLTaskEnv): + def __init__(self, cfg: object, env: ManagerBasedRLEnv): """Initialize the randomization manager. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_base.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_base.py index dcda346d1a..bbf39d165f 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_base.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_base.py @@ -20,7 +20,7 @@ from .scene_entity_cfg import SceneEntityCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv class ManagerTermBase(ABC): @@ -52,7 +52,7 @@ class MyManagerCfg: """ - def __init__(self, cfg: ManagerTermBaseCfg, env: BaseEnv): + def __init__(self, cfg: ManagerTermBaseCfg, env: ManagerBasedEnv): """Initialize the manager term. Args: @@ -116,7 +116,7 @@ def __call__(self, *args) -> Any: class ManagerBase(ABC): """Base class for all managers.""" - def __init__(self, cfg: object, env: BaseEnv): + def __init__(self, cfg: object, env: ManagerBasedEnv): """Initialize the manager. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_term_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_term_cfg.py index 41eef7d73e..8d6fdf3c7e 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_term_cfg.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/manager_term_cfg.py @@ -73,6 +73,9 @@ class ActionTermCfg: class for more details. """ + debug_vis: bool = False + """Whether to visualize debug information. Defaults to False.""" + ## # Command manager. @@ -197,6 +200,16 @@ class EventTermCfg(ManagerTermBaseCfg): This is only used if the mode is ``"interval"``. """ + is_global_time: bool = False + """ Whether randomization should be tracked on a per-environment basis. + + If True, the same time for the interval is tracked for all the environments instead of + tracking the time per-environment. + + Note: + This is only used if the mode is ``"interval"``. + """ + @configclass class RandomizationTermCfg(EventTermCfg): diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/observation_manager.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/observation_manager.py index 1ba5907ee6..10c502d16e 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/observation_manager.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/observation_manager.py @@ -16,7 +16,7 @@ from .manager_term_cfg import ObservationGroupCfg, ObservationTermCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv class ObservationManager(ManagerBase): @@ -31,7 +31,7 @@ class ObservationManager(ManagerBase): observation term should instantiate the :class:`ObservationTermCfg` class. """ - def __init__(self, cfg: object, env: BaseEnv): + def __init__(self, cfg: object, env: ManagerBasedEnv): """Initialize observation manager. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/reward_manager.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/reward_manager.py index 22e85f3419..fc71943efb 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/reward_manager.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/reward_manager.py @@ -16,7 +16,7 @@ from .manager_term_cfg import RewardTermCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv class RewardManager(ManagerBase): @@ -37,10 +37,10 @@ class RewardManager(ManagerBase): """ - _env: RLTaskEnv + _env: ManagerBasedRLEnv """The environment instance.""" - def __init__(self, cfg: object, env: RLTaskEnv): + def __init__(self, cfg: object, env: ManagerBasedRLEnv): """Initialize the reward manager. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/scene_entity_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/scene_entity_cfg.py index 54a6faefb6..17095c6615 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/scene_entity_cfg.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/scene_entity_cfg.py @@ -44,6 +44,23 @@ class for more details. manager. """ + fixed_tendon_names: str | list[str] | None = None + """The names of the fixed tendons from the scene entity. Defaults to None. + + The names can be either joint names or a regular expression matching the joint names. + + These are converted to fixed tendon indices on initialization of the manager and passed to the term + function as a list of fixed tendon indices under :attr:`fixed_tendon_ids`. + """ + + fixed_tendon_ids: list[int] | slice = slice(None) + """The indices of the fixed tendons from the asset required by the term. Defaults to slice(None), which means + all the fixed tendons in the asset (if present). + + If :attr:`fixed_tendon_names` is specified, this is filled in automatically on initialization of the + manager. + """ + body_names: str | list[str] | None = None """The names of the bodies from the asset required by the term. Defaults to None. @@ -87,12 +104,23 @@ def resolve(self, scene: InteractiveScene): Raises: ValueError: If the scene entity is not found. ValueError: If both ``joint_names`` and ``joint_ids`` are specified and are not consistent. + ValueError: If both ``fixed_tendon_names`` and ``fixed_tendon_ids`` are specified and are not consistent. ValueError: If both ``body_names`` and ``body_ids`` are specified and are not consistent. """ # check if the entity is valid if self.name not in scene.keys(): raise ValueError(f"The scene entity '{self.name}' does not exist. Available entities: {scene.keys()}.") + # convert joint names to indices based on regex + self._resolve_joint_names(scene) + + # convert fixed tendon names to indices based on regex + self._resolve_fixed_tendon_names(scene) + + # convert body names to indices based on regex + self._resolve_body_names(scene) + + def _resolve_joint_names(self, scene: InteractiveScene): # convert joint names to indices based on regex if self.joint_names is not None or self.joint_ids != slice(None): entity: Articulation = scene[self.name] @@ -126,6 +154,48 @@ def resolve(self, scene: InteractiveScene): self.joint_ids = [self.joint_ids] self.joint_names = [entity.joint_names[i] for i in self.joint_ids] + def _resolve_fixed_tendon_names(self, scene: InteractiveScene): + # convert tendon names to indices based on regex + if self.fixed_tendon_names is not None or self.fixed_tendon_ids != slice(None): + entity: Articulation = scene[self.name] + # -- if both are not their default values, check if they are valid + if self.fixed_tendon_names is not None and self.fixed_tendon_ids != slice(None): + if isinstance(self.fixed_tendon_names, str): + self.fixed_tendon_names = [self.fixed_tendon_names] + if isinstance(self.fixed_tendon_ids, int): + self.fixed_tendon_ids = [self.fixed_tendon_ids] + fixed_tendon_ids, _ = entity.find_fixed_tendons( + self.fixed_tendon_names, preserve_order=self.preserve_order + ) + fixed_tendon_names = [entity.fixed_tendon_names[i] for i in self.fixed_tendon_ids] + if fixed_tendon_ids != self.fixed_tendon_ids or fixed_tendon_names != self.fixed_tendon_names: + raise ValueError( + "Both 'fixed_tendon_names' and 'fixed_tendon_ids' are specified, and are not consistent." + f"\n\tfrom joint names: {self.fixed_tendon_names} [{fixed_tendon_ids}]" + f"\n\tfrom joint ids: {fixed_tendon_names} [{self.fixed_tendon_ids}]" + "\nHint: Use either 'fixed_tendon_names' or 'fixed_tendon_ids' to avoid confusion." + ) + # -- from fixed tendon names to fixed tendon indices + elif self.fixed_tendon_names is not None: + if isinstance(self.fixed_tendon_names, str): + self.fixed_tendon_names = [self.fixed_tendon_names] + self.fixed_tendon_ids, _ = entity.find_fixed_tendons( + self.fixed_tendon_names, preserve_order=self.preserve_order + ) + # performance optimization (slice offers faster indexing than list of indices) + # only all fixed tendon in the entity order are selected + if ( + len(self.fixed_tendon_ids) == entity.num_fixed_tendons + and self.fixed_tendon_names == entity.fixed_tendon_names + ): + self.fixed_tendon_ids = slice(None) + # -- from fixed tendon indices to fixed tendon names + elif self.fixed_tendon_ids != slice(None): + if isinstance(self.fixed_tendon_ids, int): + self.fixed_tendon_ids = [self.fixed_tendon_ids] + self.fixed_tendon_names = [entity.fixed_tendon_names[i] for i in self.fixed_tendon_ids] + + def _resolve_body_names(self, scene: InteractiveScene): # convert body names to indices based on regex if self.body_names is not None or self.body_ids != slice(None): entity: RigidObject = scene[self.name] diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/termination_manager.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/termination_manager.py index b6a3730f06..a7c440be1a 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/termination_manager.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/managers/termination_manager.py @@ -16,7 +16,7 @@ from .manager_term_cfg import TerminationTermCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv class TerminationManager(ManagerBase): @@ -43,10 +43,10 @@ class TerminationManager(ManagerBase): configuration :attr:`TerminationTermCfg.time_out` decides whether the term is a timeout or a termination term. """ - _env: RLTaskEnv + _env: ManagerBasedRLEnv """The environment instance.""" - def __init__(self, cfg: object, env: RLTaskEnv): + def __init__(self, cfg: object, env: ManagerBasedRLEnv): """Initializes the termination manager. Args: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/scene/interactive_scene.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/scene/interactive_scene.py index 1ee3adcb1c..802944541d 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/scene/interactive_scene.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/scene/interactive_scene.py @@ -83,6 +83,8 @@ def __init__(self, cfg: InteractiveSceneCfg): self._extras = dict() # obtain the current stage self.stage = omni.usd.get_context().get_stage() + # physics scene path + self._physics_scene_path = None # prepare cloner for environment replication self.cloner = GridCloner(spacing=self.cfg.env_spacing) self.cloner.define_base_env(self.env_ns) @@ -97,30 +99,54 @@ def __init__(self, cfg: InteractiveSceneCfg): copy_from_source=True, ) self._default_env_origins = torch.tensor(env_origins, device=self.device, dtype=torch.float32) - # add entities from config - self._add_entities_from_cfg() - # replicate physics if we have more than one environment - # this is done to make scene initialization faster at play time - if self.cfg.replicate_physics and self.cfg.num_envs > 1: - self.cloner.replicate_physics( - source_prim_path=self.env_prim_paths[0], - prim_paths=self.env_prim_paths, - base_env_path=self.env_ns, - root_path=self.env_regex_ns.replace(".*", ""), - ) + self._global_prim_paths = list() + if self._is_scene_setup_from_cfg(): + # add entities from config + self._add_entities_from_cfg() + # replicate physics if we have more than one environment + # this is done to make scene initialization faster at play time + if self.cfg.replicate_physics and self.cfg.num_envs > 1: + self.cloner.replicate_physics( + source_prim_path=self.env_prim_paths[0], + prim_paths=self.env_prim_paths, + base_env_path=self.env_ns, + root_path=self.env_regex_ns.replace(".*", ""), + ) + self.filter_collisions(self._global_prim_paths) + + def clone_environments(self, copy_from_source: bool = False): + """Creates clones of the environment ``/World/envs/env_0``. + + Args: + copy_from_source: (bool): If set to False, clones inherit from /World/envs/env_0 and mirror its changes. + If True, clones are independent copies of the source prim and won't reflect its changes (start-up time + may increase). Defaults to False. + """ + self.cloner.clone( + source_prim_path=self.env_prim_paths[0], + prim_paths=self.env_prim_paths, + replicate_physics=self.cfg.replicate_physics, + copy_from_source=copy_from_source, + ) + + def filter_collisions(self, global_prim_paths: list[str] = []): + """Filter environments collisions. + + Disables collisions between the environments in ``/World/envs/env_.*`` and enables collisions with the prims + in global prim paths (e.g. ground plane). + + Args: + global_prim_paths: The global prim paths to enable collisions with. + """ # obtain the current physics scene - physics_scene_prim_path = None - for prim in self.stage.Traverse(): - if prim.HasAPI(PhysxSchema.PhysxSceneAPI): - physics_scene_prim_path = prim.GetPrimPath() - carb.log_info(f"Physics scene prim path: {physics_scene_prim_path}") - break + physics_scene_prim_path = self.physics_scene_path + # filter collisions within each environment instance self.cloner.filter_collisions( physics_scene_prim_path, "/World/collisions", self.env_prim_paths, - global_paths=self._global_prim_paths, + global_paths=global_prim_paths, ) def __str__(self) -> str: @@ -137,6 +163,17 @@ def __str__(self) -> str: Properties. """ + @property + def physics_scene_path(self): + """Search the stage for the physics scene""" + if self._physics_scene_path is None: + for prim in self.stage.Traverse(): + if prim.HasAPI(PhysxSchema.PhysxSceneAPI): + self._physics_scene_path = prim.GetPrimPath() + carb.log_info(f"Physics scene prim path: {self._physics_scene_path}") + break + return self._physics_scene_path + @property def physics_dt(self) -> float: """The physics timestep of the scene.""" @@ -318,6 +355,12 @@ def __getitem__(self, key: str) -> Any: Internal methods. """ + def _is_scene_setup_from_cfg(self): + return any( + not (asset_name in InteractiveSceneCfg.__dataclass_fields__ or asset_cfg is None) + for asset_name, asset_cfg in self.cfg.__dict__.items() + ) + def _add_entities_from_cfg(self): """Add scene entities from the config.""" # store paths that are in global collision filter diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/__init__.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/__init__.py index 56c52bd060..7ab4577dbd 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/__init__.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/__init__.py @@ -6,6 +6,7 @@ """Sub-module for camera wrapper around USD camera prim.""" from .camera import Camera -from .camera_cfg import CameraCfg +from .camera_cfg import CameraCfg, TiledCameraCfg from .camera_data import CameraData +from .tiled_camera import TiledCamera from .utils import * # noqa: F401, F403 diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera.py index b0da0c814e..69c10f7c8e 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera.py @@ -256,6 +256,8 @@ def set_intrinsic_matrices( # layer (default cameras are on session layer), and this is the simplest # way to set the property on the right layer. omni.usd.set_prop_val(param_attr(), param_value) + # update the internal buffers + self._update_intrinsic_matrices(env_ids) """ Operations - Set pose. @@ -336,6 +338,10 @@ def set_world_poses_from_view( """ def reset(self, env_ids: Sequence[int] | None = None): + if not self._is_initialized: + raise RuntimeError( + "Camera could not be initialized. Please ensure --enable_cameras is used to enable rendering." + ) # reset the timestamps super().reset(env_ids) # resolve None @@ -345,7 +351,6 @@ def reset(self, env_ids: Sequence[int] | None = None): # reset the data # note: this recomputation is useful if one performs events such as randomizations on the camera poses. self._update_poses(env_ids) - self._update_intrinsic_matrices(env_ids) # Reset the frame count self._frame[env_ids] = 0 @@ -361,8 +366,14 @@ def _initialize_impl(self): Raises: RuntimeError: If the number of camera prims in the view does not match the number of environments. + RuntimeError: If replicator was not found. """ - import omni.replicator.core as rep + try: + import omni.replicator.core as rep + except ModuleNotFoundError: + raise RuntimeError( + "Replicator was not found for rendering. Please use --enable_cameras to enable rendering." + ) from omni.syntheticdata.scripts.SyntheticData import SyntheticData # Initialize parent class @@ -447,12 +458,11 @@ def _initialize_impl(self): # Create internal buffers self._create_buffers() + self._update_intrinsic_matrices(self._ALL_INDICES) def _update_buffers_impl(self, env_ids: Sequence[int]): # Increment frame count self._frame[env_ids] += 1 - # -- intrinsic matrix - self._update_intrinsic_matrices(env_ids) # -- pose self._update_poses(env_ids) # -- read the data from annotator registry diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera_cfg.py index ca8367224f..866396de0b 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera_cfg.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/camera_cfg.py @@ -11,6 +11,7 @@ from ..sensor_base_cfg import SensorBaseCfg from .camera import Camera +from .tiled_camera import TiledCamera @configclass @@ -106,3 +107,64 @@ class OffsetCfg: If True, instance segmentation is converted to an image where instance IDs are mapped to colors. and returned as a ``uint8`` 4-channel array. If False, the output is returned as a ``int32`` array. """ + + +@configclass +class TiledCameraCfg(SensorBaseCfg): + """Configuration for a tiled rendering camera sensor.""" + + @configclass + class OffsetCfg: + """The offset pose of the sensor's frame from the sensor's parent frame.""" + + pos: tuple[float, float, float] = (0.0, 0.0, 0.0) + """Translation w.r.t. the parent frame. Defaults to (0.0, 0.0, 0.0).""" + + rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) + """Quaternion rotation (w, x, y, z) w.r.t. the parent frame. Defaults to (1.0, 0.0, 0.0, 0.0).""" + + convention: Literal["opengl", "ros", "world"] = "ros" + """The convention in which the frame offset is applied. Defaults to "ros". + + - ``"opengl"`` - forward axis: ``-Z`` - up axis: ``+Y`` - Offset is applied in the OpenGL (Usd.Camera) convention. + - ``"ros"`` - forward axis: ``+Z`` - up axis: ``-Y`` - Offset is applied in the ROS convention. + - ``"world"`` - forward axis: ``+X`` - up axis: ``+Z`` - Offset is applied in the World Frame convention. + + """ + + class_type: type = TiledCamera + + offset: OffsetCfg = OffsetCfg() + """The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity. + + Note: + The parent frame is the frame the sensor attaches to. For example, the parent frame of a + camera at path ``/World/envs/env_0/Robot/Camera`` is ``/World/envs/env_0/Robot``. + """ + + spawn: PinholeCameraCfg | FisheyeCameraCfg | None = MISSING + """Spawn configuration for the asset. + + If None, then the prim is not spawned by the asset. Instead, it is assumed that the + asset is already present in the scene. + """ + + data_types: list[str] = ["rgb"] + """List of sensor names/types to enable for the camera. Defaults to ["rgb"]. + + Please refer to the :class:`Camera` class for a list of available data types. + """ + + width: int = MISSING + """Width of the image in pixels.""" + + height: int = MISSING + """Height of the image in pixels.""" + + return_latest_camera_pose: bool = False + """Whether to return the latest camera pose when fetching the camera's data. Defaults to False. + + If True, the latest camera pose is returned in the camera's data which will slow down performance + due to the use of :class:`XformPrimView`. + If False, the pose of the camera during initialization is returned. + """ diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/tiled_camera.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/tiled_camera.py new file mode 100644 index 0000000000..1b15469253 --- /dev/null +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/tiled_camera.py @@ -0,0 +1,247 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import math +import numpy as np +import torch +from collections.abc import Sequence +from tensordict import TensorDict +from typing import TYPE_CHECKING, Any + +import omni.usd +import warp as wp +from omni.isaac.core.prims import XFormPrimView +from pxr import UsdGeom + +from omni.isaac.lab.utils.warp.kernels import reshape_tiled_image + +from ..sensor_base import SensorBase +from .camera import Camera + +if TYPE_CHECKING: + from .camera_cfg import TiledCameraCfg + + +class TiledCamera(Camera): + r"""The tiled rendering camera sensor for acquiring RGB and depth data. + + This class wraps over the `UsdGeom Camera`_ for providing a consistent API for acquiring visual data. + It ensures that the camera follows the ROS convention for the coordinate system. + + The following sensor types are supported: + + - ``"rgb"``: A rendered color image. + - ``"depth"``: An image containing the distance to camera optical center. + + .. _USDGeom Camera: https://graphics.pixar.com/usd/docs/api/class_usd_geom_camera.html + + """ + + cfg: TiledCameraCfg + """The configuration parameters.""" + + SUPPORTED_TYPES: set[str] = {"rgb", "depth"} + """The set of sensor types that are supported.""" + + def __init__(self, cfg: TiledCameraCfg): + """Initializes the tiled camera sensor. + + Args: + cfg: The configuration parameters. + + Raises: + RuntimeError: If no camera prim is found at the given path. + ValueError: If the provided data types are not supported by the camera. + """ + super().__init__(cfg) + + def __del__(self): + """Unsubscribes from callbacks and detach from the replicator registry.""" + SensorBase.__del__(self) + self._annotator.detach(self.render_product_paths) + + def __str__(self) -> str: + """Returns: A string containing information about the instance.""" + # message for class + return ( + f"Tiled Camera @ '{self.cfg.prim_path}': \n" + f"\tdata types : {self.data.output.sorted_keys} \n" + f"\tupdate period (s): {self.cfg.update_period}\n" + f"\tshape : {self.image_shape}\n" + f"\tnumber of sensors : {self._view.count}" + ) + + """ + Operations + """ + + def reset(self, env_ids: Sequence[int] | None = None): + if not self._is_initialized: + raise RuntimeError( + "TiledCamera could not be initialized. Please ensure --enable_cameras is used to enable rendering." + ) + # reset the timestamps + SensorBase.reset(self, env_ids) + if env_ids is None: + env_ids = self._ALL_INDICES + # Reset the frame count + self._frame[env_ids] = 0 + + """ + Implementation. + """ + + def _initialize_impl(self): + """Initializes the sensor handles and internal buffers. + + This function creates handles and registers the provided data types with the replicator registry to + be able to access the data from the sensor. It also initializes the internal buffers to store the data. + + Raises: + RuntimeError: If the number of camera prims in the view does not match the number of environments. + RuntimeError: If replicator was not found. + """ + try: + import omni.replicator.core as rep + except ModuleNotFoundError: + raise RuntimeError( + "Replicator was not found for rendering. Please use --enable_cameras to enable rendering." + ) + + # Initialize parent class + SensorBase._initialize_impl(self) + # Create a view for the sensor + self._view = XFormPrimView(self.cfg.prim_path, reset_xform_properties=False) + self._view.initialize() + # Check that sizes are correct + if self._view.count != self._num_envs: + raise RuntimeError( + f"Number of camera prims in the view ({self._view.count}) does not match" + f" the number of environments ({self._num_envs})." + ) + + # Create all env_ids buffer + self._ALL_INDICES = torch.arange(self._view.count, device=self._device, dtype=torch.long) + # Create frame count buffer + self._frame = torch.zeros(self._view.count, device=self._device, dtype=torch.long) + + # Obtain current stage + stage = omni.usd.get_context().get_stage() + # Convert all encapsulated prims to Camera + for cam_prim_path in self._view.prim_paths: + # Get camera prim + cam_prim = stage.GetPrimAtPath(cam_prim_path) + # Check if prim is a camera + if not cam_prim.IsA(UsdGeom.Camera): + raise RuntimeError(f"Prim at path '{cam_prim_path}' is not a Camera.") + # Add to list + sensor_prim = UsdGeom.Camera(cam_prim) + self._sensor_prims.append(sensor_prim) + + rep.orchestrator._orchestrator._is_started = True + sensor = rep.create.tiled_sensor( + cameras=self._view.prim_paths, + camera_resolution=[self.image_shape[1], self.image_shape[0]], + tiled_resolution=self._tiled_image_shape(), + output_types=self.cfg.data_types, + ) + render_prod_path = rep.create.render_product(camera=sensor, resolution=self._tiled_image_shape()) + if not isinstance(render_prod_path, str): + render_prod_path = render_prod_path.path + self._render_product_paths = [render_prod_path] + self._annotator = rep.AnnotatorRegistry.get_annotator("RtxSensorGpu", device=self.device, do_array_copy=False) + self._annotator.attach(self._render_product_paths) + # Create internal buffers + self._create_buffers() + + def _create_annotator_data(self): + raise RuntimeError("Annotator data is not available for the tiled camera sensor.") + + def _process_annotator_output(self, name: str, output: Any) -> tuple[torch.tensor, dict | None]: + raise RuntimeError("Annotator data is not available for the tiled camera sensor.") + + def _update_buffers_impl(self, env_ids: Sequence[int]): + # Increment frame count + self._frame[env_ids] += 1 + # Extract the flattened image buffer + tiled_data_buffer = self._annotator.get_data() + if isinstance(tiled_data_buffer, np.ndarray): + tiled_data_buffer = wp.array(tiled_data_buffer, device=self.device) + else: + tiled_data_buffer = tiled_data_buffer.to(device=self.device) + # The offset is needed when the buffer contains rgb and depth (the buffer has RGB data first and then depth) + offset = self._data.output["rgb"].numel() if "rgb" in self.cfg.data_types else 0 + for data_type in self.cfg.data_types: + wp.launch( + kernel=reshape_tiled_image, + dim=(self._view.count, self.cfg.height, self.cfg.width), + inputs=[ + tiled_data_buffer, + wp.from_torch(self._data.output[data_type]), # zero-copy alias + *list(self._data.output[data_type].shape[1:]), # height, width, num_channels + self._tiling_grid_shape()[0], # num_tiles_x + offset if data_type == "depth" else 0, + ], + device=self.device, + ) + + """ + Private Helpers + """ + + def _tiled_image_shape(self) -> tuple[int, int]: + """A tuple containing the dimension of the tiled image.""" + cols, rows = self._tiling_grid_shape() + return (self.cfg.width * cols, self.cfg.height * rows) + + def _tiling_grid_shape(self) -> tuple[int, int]: + """A tuple containing the tiling grid dimension.""" + cols = round(math.sqrt(self._view.count)) + rows = math.ceil(self._view.count / cols) + return (cols, rows) + + def _check_supported_data_types(self, cfg: TiledCameraCfg): + """Checks if the data types are supported by the camera.""" + if not set(cfg.data_types).issubset(TiledCamera.SUPPORTED_TYPES): + raise ValueError( + f"The TiledCamera class only supports the following types {TiledCamera.SUPPORTED_TYPES} but the" + f" following where provided: {cfg.data_types}" + ) + + def _create_buffers(self): + """Create buffers for storing data.""" + # create the data object + # -- pose of the cameras + self._data.pos_w = torch.zeros((self._view.count, 3), device=self._device) + self._data.quat_w_world = torch.zeros((self._view.count, 4), device=self._device) + self._update_poses(self._ALL_INDICES) + # -- intrinsic matrix + self._data.intrinsic_matrices = torch.zeros((self._view.count, 3, 3), device=self._device) + self._update_intrinsic_matrices(self._ALL_INDICES) + self._data.image_shape = self.image_shape + # -- output data + data_dict = dict() + if "rgb" in self.cfg.data_types: + data_dict["rgb"] = torch.zeros( + (self._view.count, self.cfg.height, self.cfg.width, 3), device=self.device + ).contiguous() + if "depth" in self.cfg.data_types: + data_dict["depth"] = torch.zeros( + (self._view.count, self.cfg.height, self.cfg.width, 1), device=self.device + ).contiguous() + self._data.output = TensorDict(data_dict, batch_size=self._view.count, device=self.device) + + """ + Internal simulation callbacks. + """ + + def _invalidate_initialize_callback(self, event): + """Invalidates the scene elements.""" + # call parent + super()._invalidate_initialize_callback(event) + # set all existing views to None to invalidate them + self._view = None diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/utils.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/utils.py index be28904b9d..4e8c4e63c1 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/utils.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sensors/camera/utils.py @@ -397,3 +397,17 @@ def create_rotation_matrix_from_view( x_axis = torch.where(is_close, replacement, x_axis) R = torch.cat((x_axis[:, None, :], y_axis[:, None, :], z_axis[:, None, :]), dim=1) return R.transpose(1, 2) + + +def save_images_to_file(images: torch.Tensor, file_path: str): + """Save images to file. + + Args: + images: A tensor of shape (N, H, W, C) containing the images. + file_path: The path to save the images to. + """ + from torchvision.utils import make_grid, save_image + + save_image( + make_grid(torch.swapaxes(images.unsqueeze(1), 1, -1).squeeze(-1), nrow=round(images.shape[0] ** 0.5)), file_path + ) diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/mesh_converter.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/mesh_converter.py index b86f313868..84aadaca43 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/mesh_converter.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/mesh_converter.py @@ -10,7 +10,7 @@ import omni.kit.commands import omni.usd from omni.isaac.core.utils.extensions import enable_extension -from pxr import Gf, Usd, UsdGeom, UsdPhysics, UsdUtils +from pxr import Usd, UsdGeom, UsdPhysics, UsdUtils from omni.isaac.lab.sim.converters.asset_converter_base import AssetConverterBase from omni.isaac.lab.sim.converters.mesh_converter_cfg import MeshConverterCfg @@ -82,98 +82,50 @@ def _convert_asset(self, cfg: MeshConverterCfg): mesh_file_format = mesh_file_format.lower() # Convert USD - status = asyncio.get_event_loop().run_until_complete( - self._convert_mesh_to_usd(in_file=cfg.asset_path, out_file=self.usd_path) + asyncio.get_event_loop().run_until_complete( + self._convert_mesh_to_usd( + in_file=cfg.asset_path, out_file=self.usd_path, prim_path=f"/{mesh_file_basename}" + ) ) - if not status: - raise RuntimeError(f"Failed to convert asset: {cfg.asset_path}! Please check the logs for more details.") - # Open converted USD stage # note: This opens a new stage and does not use the stage created earlier by the user # create a new stage stage = Usd.Stage.Open(self.usd_path) # add USD to stage cache stage_id = UsdUtils.StageCache.Get().Insert(stage) - # need to make kwargs for compatibility with 2023 - stage_kwargs = {"stage": stage} - stage_or_context_kwargs = {"stage_or_context": stage} - # FIXME: we need to hack this into command because Kit 105 has a bug. - from omni.usd.commands import MovePrimCommand - - MovePrimCommand._selection = None # type: ignore - # Set stage up-axis to Z - # note: later we need to rotate the mesh so that it is Z-up in the world - UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z) - - # Move all meshes to underneath a new Xform so that we can make it instanceable later if requested - # Get the default prim (which is the root prim) -- "/World" - old_xform_prim = stage.GetDefaultPrim() - # Create a path called "/{mesh_file_basename}/geometry" and move the mesh to it - new_xform_prim = stage.DefinePrim(f"/{mesh_file_basename}", "Xform") - geom_undef_prim = stage.DefinePrim(f"{new_xform_prim.GetPath()}/geometry") - # Move Looks to underneath new Xform - omni.kit.commands.execute( - "MovePrim", - path_from=f"{old_xform_prim.GetPath()}/Looks", - path_to=f"{geom_undef_prim.GetPath()}/Looks", - destructive=True, - **stage_or_context_kwargs, - ) + # Get the default prim (which is the root prim) -- "/{mesh_file_basename}" + xform_prim = stage.GetDefaultPrim() + geom_prim = stage.GetPrimAtPath(f"/{mesh_file_basename}/geometry") # Move all meshes to underneath new Xform - for child_mesh_prim in old_xform_prim.GetChildren(): - # Get mesh prim path - old_child_mesh_prim_path = child_mesh_prim.GetPath().pathString - new_child_mesh_prim_path = f"{geom_undef_prim.GetPath()}/{old_child_mesh_prim_path.split('/')[-1]}" - # Move mesh to underneath new Xform - omni.kit.commands.execute( - "MovePrim", - path_from=old_child_mesh_prim_path, - path_to=new_child_mesh_prim_path, - destructive=True, - **stage_or_context_kwargs, - ) - # Apply default Xform rotation to mesh - omni.kit.commands.execute( - "CreateDefaultXformOnPrimCommand", - prim_path=new_child_mesh_prim_path, - **stage_kwargs, - ) - # Get new mesh prim - child_mesh_prim = stage.GetPrimAtPath(new_child_mesh_prim_path) - # Rotate mesh so that it is Z-up in the world - attr_rotate = child_mesh_prim.GetAttribute("xformOp:orient") - attr_rotate.Set(Gf.Quatd(0.5, 0.5, 0.5, 0.5)) - # Apply collider properties to mesh - if cfg.collision_props is not None: - # -- Collision approximation to mesh - # TODO: https://github.com/isaac-sim/IsaacLab/issues/163 Move this to a new Schema - mesh_collision_api = UsdPhysics.MeshCollisionAPI.Apply(child_mesh_prim) - mesh_collision_api.GetApproximationAttr().Set(cfg.collision_approximation) - # -- Collider properties such as offset, scale, etc. - schemas.define_collision_properties( - prim_path=child_mesh_prim.GetPath(), cfg=cfg.collision_props, stage=stage - ) + for child_mesh_prim in geom_prim.GetChildren(): + if child_mesh_prim.GetTypeName() == "Mesh": + # Apply collider properties to mesh + if cfg.collision_props is not None: + # -- Collision approximation to mesh + # TODO: https://github.com/isaac-orbit/orbit/issues/163 Move this to a new Schema + mesh_collision_api = UsdPhysics.MeshCollisionAPI.Apply(child_mesh_prim) + mesh_collision_api.GetApproximationAttr().Set(cfg.collision_approximation) + # -- Collider properties such as offset, scale, etc. + schemas.define_collision_properties( + prim_path=child_mesh_prim.GetPath(), cfg=cfg.collision_props, stage=stage + ) # Delete the old Xform and make the new Xform the default prim - stage.SetDefaultPrim(new_xform_prim) - omni.kit.commands.execute("DeletePrims", paths=[old_xform_prim.GetPath().pathString], stage=stage) - + stage.SetDefaultPrim(xform_prim) # Handle instanceable # Create a new Xform prim that will be the prototype prim if cfg.make_instanceable: # Export Xform to a file so we can reference it from all instances export_prim_to_file( path=os.path.join(self.usd_dir, self.usd_instanceable_meshes_path), - source_prim_path=geom_undef_prim.GetPath(), + source_prim_path=geom_prim.GetPath(), stage=stage, ) # Delete the original prim that will now be a reference - geom_undef_prim_path = geom_undef_prim.GetPath().pathString - omni.kit.commands.execute("DeletePrims", paths=[geom_undef_prim_path], stage=stage) + geom_prim_path = geom_prim.GetPath().pathString + omni.kit.commands.execute("DeletePrims", paths=[geom_prim_path], stage=stage) # Update references to exported Xform and make it instanceable - geom_undef_prim = stage.DefinePrim(geom_undef_prim_path) - geom_undef_prim.GetReferences().AddReference( - self.usd_instanceable_meshes_path, primPath=geom_undef_prim_path - ) + geom_undef_prim = stage.DefinePrim(geom_prim_path) + geom_undef_prim.GetReferences().AddReference(self.usd_instanceable_meshes_path, primPath=geom_prim_path) geom_undef_prim.SetInstanceable(True) # Apply mass and rigid body properties after everything else @@ -181,10 +133,10 @@ def _convert_asset(self, cfg: MeshConverterCfg): # asset unintentionally share the same rigid body properties # apply mass properties if cfg.mass_props is not None: - schemas.define_mass_properties(prim_path=new_xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) + schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) # apply rigid body properties if cfg.rigid_props is not None: - schemas.define_rigid_body_properties(prim_path=new_xform_prim.GetPath(), cfg=cfg.rigid_props, stage=stage) + schemas.define_rigid_body_properties(prim_path=xform_prim.GetPath(), cfg=cfg.rigid_props, stage=stage) # Save changes to USD stage stage.Save() @@ -196,7 +148,9 @@ def _convert_asset(self, cfg: MeshConverterCfg): """ @staticmethod - async def _convert_mesh_to_usd(in_file: str, out_file: str, load_materials: bool = True) -> bool: + async def _convert_mesh_to_usd( + in_file: str, out_file: str, prim_path: str = "/World", load_materials: bool = True + ) -> bool: """Convert mesh from supported file types to USD. This function uses the Omniverse Asset Converter extension to convert a mesh file to USD. @@ -208,13 +162,14 @@ async def _convert_mesh_to_usd(in_file: str, out_file: str, load_materials: bool The asset hierarchy is arranged as follows: .. code-block:: none - /World (default prim) - |- /Looks - |- /Mesh + prim_path (default prim) + |- /geometry/Looks + |- /geometry/mesh Args: in_file: The file to convert. out_file: The path to store the output file. + prim_path: The prim path of the mesh. load_materials: Set to True to enable attaching materials defined in the input file to the generated USD mesh. Defaults to True. @@ -222,10 +177,11 @@ async def _convert_mesh_to_usd(in_file: str, out_file: str, load_materials: bool True if the conversion succeeds. """ enable_extension("omni.kit.asset_converter") - enable_extension("omni.isaac.unit_converter") + enable_extension("omni.usd.metrics.assembler") import omni.kit.asset_converter - from omni.isaac.unit_converter.unit_conversion_utils import set_stage_meters_per_unit + import omni.usd + from omni.metrics.assembler.core import get_metrics_assembler_interface # Create converter context converter_context = omni.kit.asset_converter.AssetConverterContext() @@ -246,7 +202,8 @@ async def _convert_mesh_to_usd(in_file: str, out_file: str, load_materials: bool # Create converter task instance = omni.kit.asset_converter.get_instance() - task = instance.create_converter_task(in_file, out_file, None, converter_context) + out_file_non_metric = out_file.replace(".usd", "_non_metric.usd") + task = instance.create_converter_task(in_file, out_file_non_metric, None, converter_context) # Start conversion task and wait for it to finish success = True while True: @@ -256,11 +213,18 @@ async def _convert_mesh_to_usd(in_file: str, out_file: str, load_materials: bool else: break - # Open converted USD stage - stage = Usd.Stage.Open(out_file) - # Set stage units to 1.0 - set_stage_meters_per_unit(stage, 1.0) - # Save changes to USD stage - stage.Save() - + temp_stage = Usd.Stage.CreateInMemory() + UsdGeom.SetStageUpAxis(temp_stage, UsdGeom.Tokens.z) + UsdGeom.SetStageMetersPerUnit(temp_stage, 1.0) + UsdPhysics.SetStageKilogramsPerUnit(temp_stage, 1.0) + + base_prim = temp_stage.DefinePrim(prim_path, "Xform") + prim = temp_stage.DefinePrim(f"{prim_path}/geometry", "Xform") + prim.GetReferences().AddReference(out_file_non_metric) + cache = UsdUtils.StageCache.Get() + cache.Insert(temp_stage) + stage_id = cache.GetId(temp_stage).ToLongInt() + get_metrics_assembler_interface().resolve_stage(stage_id) + temp_stage.SetDefaultPrim(base_prim) + temp_stage.Export(out_file) return success diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter.py index 96e8163a38..5267b1221e 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter.py @@ -10,6 +10,7 @@ import omni.kit.commands import omni.usd from omni.isaac.core.utils.extensions import enable_extension +from omni.isaac.version import get_version from pxr import Usd from .asset_converter_base import AssetConverterBase @@ -155,5 +156,8 @@ def _get_urdf_import_config(self, cfg: UrdfConverterCfg) -> omni.importer.urdf.I import_config.set_default_drive_strength(cfg.default_drive_stiffness) # default derivative gains import_config.set_default_position_drive_damping(cfg.default_drive_damping) + if get_version()[2] == "4": + # override joint dynamics parsed from urdf + import_config.set_override_joint_dynamics(cfg.override_joint_dynamics) return import_config diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter_cfg.py index 89f8940abf..06e13b3d01 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter_cfg.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/converters/urdf_converter_cfg.py @@ -48,6 +48,9 @@ class UrdfConverterCfg(AssetConverterBaseCfg): * ``"velocity"``: The joint stiff is set to zero and damping is based on the URDF file or provided configuration. """ + override_joint_dynamics: bool = False + """Override the joint dynamics parsed from the URDF file. Defaults to False.""" + default_drive_stiffness: float = 0.0 """The default stiffness of the joint drive. Defaults to 0.0.""" @@ -55,6 +58,6 @@ class UrdfConverterCfg(AssetConverterBaseCfg): """The default damping of the joint drive. Defaults to 0.0. Note: - If set to zero, the values parsed from the URDF joint tag ``""`` are used. + If ``override_joint_dynamics`` is True, the values parsed from the URDF joint tag ``""`` are used. Otherwise, it is overridden by the configured value. """ diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/simulation_context.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/simulation_context.py index 9c9c224d4f..499aa19c69 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/simulation_context.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/simulation_context.py @@ -146,7 +146,6 @@ def __init__(self, cfg: SimulationCfg | None = None): self._offscreen_render = bool(carb_settings_iface.get("/isaaclab/render/offscreen")) # flag for whether any GUI will be rendered (local, livestreamed or viewport) self._has_gui = self._local_gui or self._livestream_gui - # store the default render mode if not self._has_gui and not self._offscreen_render: # set default render mode diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights.py index 77d7ebb987..722be9215b 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights.py @@ -70,7 +70,10 @@ def spawn_light( else: raise ValueError(f"Unsupported texture attribute: '{attr_name}'.") else: - prim_prop_name = f"inputs:{attr_name}" + if attr_name == "visible_in_primary_ray": + prim_prop_name = attr_name + else: + prim_prop_name = f"inputs:{attr_name}" # set the attribute safe_set_attribute_on_usd_prim(prim, prim_prop_name, value, camel_case=True) # return the prim diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights_cfg.py index 9f4cd47a33..feabce71a2 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights_cfg.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/sim/spawners/lights/lights_cfg.py @@ -136,6 +136,12 @@ class DomeLightCfg(LightCfg): * ``"cubeMapVerticalCross"``: A cube map with faces laid out as a vertical cross. """ + visible_in_primary_ray: bool = True + """Whether the dome light is visible in the primary ray. Defaults to True. + + If true, the texture in the sky is visible, otherwise the sky is black. + """ + @configclass class CylinderLightCfg(LightCfg): diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/assets.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/assets.py index 3ff0795fdf..95467e10a6 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/assets.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/assets.py @@ -20,9 +20,12 @@ import carb import omni.client -import omni.isaac.core.utils.nucleus as nucleus_utils -# get assets root path +try: + import omni.isaac.nucleus as nucleus_utils +except ModuleNotFoundError: + import omni.isaac.core.utils.nucleus as nucleus_utils + # note: we check only once at the start of the module to prevent multiple checks on the Nucleus Server NUCLEUS_ASSET_ROOT_DIR = nucleus_utils.get_assets_root_path() """Path to the root directory on the Nucleus Server. @@ -31,8 +34,7 @@ will be set to None. The path is resolved using the following steps: 1. Based on simulation parameter: ``/persistent/isaac/asset_root/default``. -2. Iterating over all the connected Nucleus Servers and checking for the first server that has the - the connected status. +2. Iterating over all the connected Nucleus Servers and checking for the first server that has the the connected status. 3. Based on simulation parameter: ``/persistent/isaac/asset_root/cloud``. """ diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/dict.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/dict.py index ba0cb9c777..94070f45af 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/dict.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/dict.py @@ -222,9 +222,6 @@ def update_dict(orig_dict: dict, new_dict: collections.abc.Mapping) -> dict: This function mimics the dict.update() function. However, it works for nested dictionaries as well. - Reference: - https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth - Args: orig_dict: The original dictionary to insert items to. new_dict: The new dictionary to insert items from. diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/math.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/math.py index ff6ca3babf..c3722ba6f2 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/math.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/math.py @@ -1239,6 +1239,28 @@ def sample_log_uniform( return torch.exp(sample_uniform(torch.log(lower), torch.log(upper), size, device)) +def sample_gaussian( + mean: torch.Tensor | float, std: torch.Tensor | float, size: int | tuple[int, ...], device: str +) -> torch.Tensor: + """Sample using gaussian distribution. + + Args: + mean: Mean of the gaussian. + std: Std of the gaussian. + size: The shape of the tensor. + device: Device to create tensor on. + + Returns: + Sampled tensor. + """ + if isinstance(mean, float): + if isinstance(size, int): + size = (size,) + return torch.normal(mean=mean, std=std, size=size).to(device=device) + else: + return torch.normal(mean=mean, std=std).to(device=device) + + def sample_cylinder( radius: float, h_range: tuple[float, float], size: int | tuple[int, ...], device: str ) -> torch.Tensor: diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/__init__.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/__init__.py index 5baf998590..c06f5ea960 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/__init__.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/__init__.py @@ -26,4 +26,14 @@ """ from .noise_cfg import NoiseCfg # noqa: F401 -from .noise_cfg import AdditiveGaussianNoiseCfg, AdditiveUniformNoiseCfg, ConstantBiasNoiseCfg +from .noise_cfg import ( + AdditiveGaussianNoiseCfg, + AdditiveUniformNoiseCfg, + ConstantBiasNoiseCfg, + ConstantNoiseCfg, + GaussianNoiseCfg, + NoiseModelCfg, + NoiseModelWithAdditiveBiasCfg, + UniformNoiseCfg, +) +from .noise_model import NoiseModel, NoiseModelWithAdditiveBias, constant_noise, gaussian_noise, uniform_noise diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_cfg.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_cfg.py index f9e34947ee..7e85685590 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_cfg.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_cfg.py @@ -8,6 +8,7 @@ import torch from collections.abc import Callable from dataclasses import MISSING +from typing import Literal from omni.isaac.lab.utils import configclass @@ -24,37 +25,71 @@ class NoiseCfg: Note: The shape of the input and output tensors must be the same. """ + operation: Literal["add", "scale", "abs"] = "add" + """The operation to apply the noise on the data. Defaults to "add".""" @configclass -class AdditiveUniformNoiseCfg(NoiseCfg): +class ConstantNoiseCfg(NoiseCfg): + """Configuration for an additive constant noise term.""" + + func = noise_model.constant_noise + + bias: torch.Tensor | float = 0.0 + """The bias to add. Defaults to 0.0.""" + + +# Backward compatibility +ConstantBiasNoiseCfg = ConstantNoiseCfg + + +@configclass +class UniformNoiseCfg(NoiseCfg): """Configuration for a additive uniform noise term.""" - func = noise_model.additive_uniform_noise + func = noise_model.uniform_noise - n_min: float = -1.0 + n_min: torch.Tensor | float = -1.0 """The minimum value of the noise. Defaults to -1.0.""" - n_max: float = 1.0 + n_max: torch.Tensor | float = 1.0 """The maximum value of the noise. Defaults to 1.0.""" +# Backward compatibility +AdditiveUniformNoiseCfg = UniformNoiseCfg + + @configclass -class AdditiveGaussianNoiseCfg(NoiseCfg): - """Configuration for a additive gaussian noise term.""" +class GaussianNoiseCfg(NoiseCfg): + """Configuration for an additive gaussian noise term.""" - func = noise_model.additive_gaussian_noise + func = noise_model.gaussian_noise - mean: float = 0.0 + mean: torch.Tensor | float = 0.0 """The mean of the noise. Defaults to 0.0.""" - std: float = 1.0 + std: torch.Tensor | float = 1.0 """The standard deviation of the noise. Defaults to 1.0.""" +# Backward compatibility +AdditiveGaussianNoiseCfg = GaussianNoiseCfg + + @configclass -class ConstantBiasNoiseCfg(NoiseCfg): - """Configuration for a constant bias noise term.""" +class NoiseModelCfg: + """Configuration for a noise model.""" - func = noise_model.constant_bias_noise + class_type: type = noise_model.NoiseModel + """The class type of the noise model.""" - bias: float = 0.0 - """The bias to add. Defaults to 0.0.""" + noise_cfg: NoiseCfg = MISSING + """The noise configuration to use.""" + + +@configclass +class NoiseModelWithAdditiveBiasCfg(NoiseModelCfg): + """Configuration for an additive gaussian noise with bias model.""" + + class_type: type = noise_model.NoiseModelWithAdditiveBias + + bias_noise_cfg: NoiseCfg = MISSING diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_model.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_model.py index 22ed561f47..ebe077ddce 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_model.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/noise/noise_model.py @@ -6,22 +6,109 @@ from __future__ import annotations import torch +from collections.abc import Sequence from typing import TYPE_CHECKING if TYPE_CHECKING: from . import noise_cfg -def constant_bias_noise(data: torch.Tensor, cfg: noise_cfg.ConstantBiasNoiseCfg) -> torch.Tensor: - """Add a constant noise.""" - return data + cfg.bias +def constant_noise(data: torch.Tensor, cfg: noise_cfg.ConstantNoiseCfg) -> torch.Tensor: + """Constant noise.""" + if cfg.operation == "add": + return data + cfg.bias + elif cfg.operation == "scale": + return data * cfg.bias + elif cfg.operation == "abs": + return torch.zeros_like(data) + cfg.bias + else: + raise ValueError(f"Unknown operation in noise: {cfg.operation}") -def additive_uniform_noise(data: torch.Tensor, cfg: noise_cfg.UniformNoiseCfg) -> torch.Tensor: - """Adds a noise sampled from a uniform distribution.""" - return data + torch.rand_like(data) * (cfg.n_max - cfg.n_min) + cfg.n_min +def uniform_noise(data: torch.Tensor, cfg: noise_cfg.UniformNoiseCfg) -> torch.Tensor: + """Uniform noise.""" + if cfg.operation == "add": + return data + torch.rand_like(data) * (cfg.n_max - cfg.n_min) + cfg.n_min + elif cfg.operation == "scale": + return data * (torch.rand_like(data) * (cfg.n_max - cfg.n_min) + cfg.n_min) + elif cfg.operation == "abs": + return torch.rand_like(data) * (cfg.n_max - cfg.n_min) + cfg.n_min + else: + raise ValueError(f"Unknown operation in noise: {cfg.operation}") -def additive_gaussian_noise(data: torch.Tensor, cfg: noise_cfg.GaussianNoiseCfg) -> torch.Tensor: - """Adds a noise sampled from a gaussian distribution.""" - return data + cfg.mean + cfg.std * torch.randn_like(data) +def gaussian_noise(data: torch.Tensor, cfg: noise_cfg.GaussianNoiseCfg) -> torch.Tensor: + """Gaussian noise.""" + if cfg.operation == "add": + return data + cfg.mean + cfg.std * torch.randn_like(data) + elif cfg.operation == "scale": + return data * (cfg.mean + cfg.std * torch.randn_like(data)) + elif cfg.operation == "abs": + return cfg.mean + cfg.std * torch.randn_like(data) + else: + raise ValueError(f"Unknown operation in noise: {cfg.operation}") + + +class NoiseModel: + """Base class for noise models.""" + + def __init__(self, num_envs: int, noise_model_cfg: noise_cfg.NoiseModelCfg): + """Initialize the noise model. + + Args: + num_envs: The number of environments. + noise_model_cfg: The noise configuration to use. + """ + self._num_envs = num_envs + self._noise_model_cfg = noise_model_cfg + + def apply(self, data: torch.Tensor) -> torch.Tensor: + r"""Apply the noise to the data. + + Args: + data: The data to apply the noise to, which is a tensor of shape (num_envs, \*data_shape). + """ + return self._noise_model_cfg.noise_cfg.func(data, self._noise_model_cfg.noise_cfg) + + def reset(self, env_ids: Sequence[int]): + """Reset the noise model. + + This method can be implemented by derived classes to reset the noise model. + This is useful when implementing temporal noise models such as random walk. + + Args: + env_ids: The environment ids to reset the noise model for. + """ + pass + + +class NoiseModelWithAdditiveBias(NoiseModel): + """Noise model with an additive bias. + + The bias term is sampled from a the specified distribution on reset. + + """ + + def __init__(self, num_envs: int, noise_model_cfg: noise_cfg.NoiseModelWithAdditiveBiasCfg, device: str): + super().__init__(num_envs, noise_model_cfg) + self._device = device + self._bias_noise_cfg = noise_model_cfg.bias_noise_cfg + self._bias = torch.zeros((num_envs, 1), device=self._device) + + def apply(self, data: torch.Tensor) -> torch.Tensor: + r"""Apply the noise + bias. + + Args: + data: The data to apply the noise to, which is a tensor of shape (num_envs, \*data_shape). + """ + return super().apply(data) + self._bias + + def reset(self, env_ids: Sequence[int]): + """Reset the noise model. + + This method resets the bias term for the specified environments. + + Args: + env_ids: The environment ids to reset the noise model for. + """ + self._bias[env_ids] = self._bias_noise_cfg.func(self._bias[env_ids], self._bias_noise_cfg) diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/kernels.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/kernels.py index abb678828d..c482256930 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/kernels.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/kernels.py @@ -71,3 +71,37 @@ def raycast_mesh_kernel( ray_normal[tid] = n if return_face_id == 1: ray_face_id[tid] = f + + +@wp.kernel +def reshape_tiled_image( + tiled_image_buffer: wp.array(dtype=float), + batched_image: wp.array(dtype=float, ndim=4), + image_height: int, + image_width: int, + num_channels: int, + num_tiles_x: int, + offset: int, +): + """Reshape a tiled image (height*width*num_channels*num_cameras,) to a batch of images (num_cameras, height, width, num_channels). + + Args: + tiled_image_buffer: The input image buffer. Shape is (height*width*num_channels*num_cameras,). + batched_image: The output image. Shape is (num_cameras, height, width, num_channels). + image_width: The width of the image. + image_height: The height of the image. + num_channels: The number of channels in the image. + num_tiles_x: The number of tiles in x direction. + offset: The offset in the image buffer. This is used when multiple image types are concatenated in the buffer. + """ + camera_id, height_id, width_id = wp.tid() + tile_x_id = camera_id % num_tiles_x + tile_y_id = camera_id // num_tiles_x + pixel_start = ( + offset + + num_channels * num_tiles_x * image_width * (image_height * tile_y_id + height_id) + + num_channels * tile_x_id * image_width + + num_channels * width_id + ) + for i in range(num_channels): + batched_image[camera_id, height_id, width_id, i] = tiled_image_buffer[pixel_start + i] diff --git a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/ops.py b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/ops.py index 2aca585139..5c128fa1ea 100644 --- a/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/ops.py +++ b/source/extensions/omni.isaac.lab/omni/isaac/lab/utils/warp/ops.py @@ -13,6 +13,8 @@ import warp as wp +wp.init() + from . import kernels diff --git a/source/extensions/omni.isaac.lab/pyproject.toml b/source/extensions/omni.isaac.lab/pyproject.toml new file mode 100644 index 0000000000..d90ac3536f --- /dev/null +++ b/source/extensions/omni.isaac.lab/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "toml"] +build-backend = "setuptools.build_meta" diff --git a/source/extensions/omni.isaac.lab/setup.py b/source/extensions/omni.isaac.lab/setup.py index d5732566da..567acd74ed 100644 --- a/source/extensions/omni.isaac.lab/setup.py +++ b/source/extensions/omni.isaac.lab/setup.py @@ -19,9 +19,10 @@ INSTALL_REQUIRES = [ # generic "numpy", - "torch==2.0.1", + "torch>=2.2.2", "prettytable==3.3.0", "tensordict", + "toml", # devices "hidapi", # gym @@ -48,7 +49,7 @@ classifiers=[ "Natural Language :: English", "Programming Language :: Python :: 3.10", - "Isaac Sim :: 2023.1.0-hotfix.1", + "Isaac Sim :: 4.0.0", "Isaac Sim :: 2023.1.1", ], zip_safe=False, diff --git a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_app.py b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_app.py deleted file mode 100644 index 78f43ce8e5..0000000000 --- a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_app.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2022-2024, The Isaac Lab Project Developers. -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script shows the issue with launching Isaac Sim application in headless mode. - -On launching the application in headless mode, the application does not exit gracefully. -There are multiple warnings and errors that are printed on the console. - -``` -_isaac_sim/python.sh source/extensions/omni.isaac.lab/test/deps/isaacsim/check_app.py -``` - -Output: - -``` -[10.948s] Simulation App Startup Complete -[11.471s] Simulation App Shutting Down -...... [Warning] [carb] [Plugin: omni.spectree.delegate.plugin] Module /media/vulcan/packman-repo/chk/kit-sdk/105.1+release.129498.98d86eae.tc.linux-x86_64.release/exts/omni.usd_resolver/bin/libomni.spectree.delegate.plugin.so remained loaded after unload request -...... [Warning] [omni.core.ITypeFactory] Module /media/vulcan/packman-repo/chk/kit-sdk/105.1+release.129498.98d86eae.tc.linux-x86_64.release/exts/omni.graph.action/bin/libomni.graph.action.plugin.so remained loaded after unload request. -...... [Warning] [omni.core.ITypeFactory] Module /media/vulcan/packman-repo/chk/kit-sdk/105.1+release.129498.98d86eae.tc.linux-x86_64.release/exts/omni.activity.core/bin/libomni.activity.core.plugin.so remained loaded after unload request. -``` - -""" - -from omni.isaac.kit import SimulationApp - -if __name__ == "__main__": - app = SimulationApp({"headless": True}) - app.close() diff --git a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_camera.py b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_camera.py index 003bbda68e..cd8f3953c2 100644 --- a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_camera.py +++ b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_camera.py @@ -21,7 +21,7 @@ import argparse -# omni-isaac-lab +# omni.isaac.lab from omni.isaac.lab.app import AppLauncher # add argparse arguments @@ -45,7 +45,10 @@ import os import random -import omni.isaac.core.utils.nucleus as nucleus_utils +try: + import omni.isaac.nucleus as nucleus_utils +except ModuleNotFoundError: + import omni.isaac.core.utils.nucleus as nucleus_utils import omni.isaac.core.utils.prims as prim_utils import omni.replicator.core as rep from omni.isaac.core.articulations import ArticulationView @@ -69,7 +72,7 @@ def main(): - """Runs a camera sensor from Isaac Lab.""" + """Runs a camera sensor from isaaclab.""" # Load kit helper world = World(physics_dt=0.005, rendering_dt=0.005, backend="torch", device="cpu") diff --git a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_floating_base_made_fixed.py b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_floating_base_made_fixed.py index 08eb18a282..9a18cd75fc 100644 --- a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_floating_base_made_fixed.py +++ b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_floating_base_made_fixed.py @@ -53,8 +53,8 @@ ISAAC_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac" """Path to the `Isaac` directory on the NVIDIA Nucleus Server.""" -ISAACLAB_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac/IsaacLab" -"""Path to the `Isaac/IsaacLab` directory on the NVIDIA Nucleus Server.""" +ISAACLAB_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac/Samples/Orbit" +"""Path to the `Isaac/Samples/Orbit` directory on the NVIDIA Nucleus Server.""" """ diff --git a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_legged_robot_clone.py b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_legged_robot_clone.py index 15b90bdd0b..5307d22485 100644 --- a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_legged_robot_clone.py +++ b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_legged_robot_clone.py @@ -42,7 +42,11 @@ import torch import carb -import omni.isaac.core.utils.nucleus as nucleus_utils + +try: + import omni.isaac.nucleus as nucleus_utils +except ModuleNotFoundError: + import omni.isaac.core.utils.nucleus as nucleus_utils import omni.isaac.core.utils.prims as prim_utils from omni.isaac.cloner import GridCloner from omni.isaac.core.articulations import ArticulationView @@ -63,8 +67,8 @@ ISAAC_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac" """Path to the `Isaac` directory on the NVIDIA Nucleus Server.""" -ISAACLAB_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac/IsaacLab" -"""Path to the `Isaac/IsaacLab` directory on the NVIDIA Nucleus Server.""" +ISAACLAB_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac/Samples/Orbit" +"""Path to the `Isaac/Samples/Orbit` directory on the NVIDIA Nucleus Server.""" """ diff --git a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_rep_texture_randomizer.py b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_rep_texture_randomizer.py index d8833d47cf..fc035a4257 100644 --- a/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_rep_texture_randomizer.py +++ b/source/extensions/omni.isaac.lab/test/deps/isaacsim/check_rep_texture_randomizer.py @@ -23,7 +23,7 @@ import argparse -# omni-isaac-lab +# omni.isaac.lab from omni.isaac.lab.app import AppLauncher # add argparse arguments diff --git a/source/extensions/omni.isaac.lab/test/envs/check_base_env_anymal_locomotion.py b/source/extensions/omni.isaac.lab/test/envs/check_base_env_anymal_locomotion.py index da511942cb..be21016e4a 100644 --- a/source/extensions/omni.isaac.lab/test/envs/check_base_env_anymal_locomotion.py +++ b/source/extensions/omni.isaac.lab/test/envs/check_base_env_anymal_locomotion.py @@ -33,13 +33,12 @@ """Rest everything follows.""" -import os import torch import omni.isaac.lab.envs.mdp as mdp import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import BaseEnv, BaseEnvCfg +from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -48,7 +47,7 @@ from omni.isaac.lab.sensors import RayCasterCfg, patterns from omni.isaac.lab.terrains import TerrainImporterCfg from omni.isaac.lab.utils import configclass -from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR, check_file_path, read_file +from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR, NVIDIA_NUCLEUS_DIR, check_file_path, read_file from omni.isaac.lab.utils.noise import AdditiveUniformNoiseCfg as Unoise ## @@ -78,6 +77,11 @@ class MySceneCfg(InteractiveSceneCfg): 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, ) @@ -95,9 +99,13 @@ class MySceneCfg(InteractiveSceneCfg): ) # 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( + intensity=900.0, + texture_file=f"{NVIDIA_NUCLEUS_DIR}/Assets/Skies/Cloudy/kloofendal_48d_partly_cloudy_4k.hdr", + visible_in_primary_ray=False, + ), ) @@ -106,7 +114,7 @@ class MySceneCfg(InteractiveSceneCfg): ## -def constant_commands(env: BaseEnv) -> torch.Tensor: +def constant_commands(env: ManagerBasedEnv) -> torch.Tensor: """The generated command from the command generator.""" return torch.tensor([[1, 0, 0]], device=env.device).repeat(env.num_envs, 1) @@ -179,7 +187,7 @@ class EventCfg: @configclass -class QuadrupedEnvCfg(BaseEnvCfg): +class QuadrupedEnvCfg(ManagerBasedEnvCfg): """Configuration for the locomotion velocity-tracking environment.""" # Scene settings @@ -206,11 +214,11 @@ def main(): """Main function.""" # setup base environment - env = BaseEnv(cfg=QuadrupedEnvCfg()) + env = ManagerBasedEnv(cfg=QuadrupedEnvCfg()) obs, _ = env.reset() # load level policy - policy_path = os.path.join(ISAACLAB_NUCLEUS_DIR, "Policies", "ANYmal-C", "policy.pt") + policy_path = ISAACLAB_NUCLEUS_DIR + "/Policies/ANYmal-C/HeightScan/policy.pt" # check if policy file exists if not check_file_path(policy_path): diff --git a/source/extensions/omni.isaac.lab/test/envs/check_base_env_floating_cube.py b/source/extensions/omni.isaac.lab/test/envs/check_base_env_floating_cube.py index 419a091efb..4282eb5e8c 100644 --- a/source/extensions/omni.isaac.lab/test/envs/check_base_env_floating_cube.py +++ b/source/extensions/omni.isaac.lab/test/envs/check_base_env_floating_cube.py @@ -35,7 +35,7 @@ import omni.isaac.lab.envs.mdp as mdp import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import AssetBaseCfg, RigidObject, RigidObjectCfg -from omni.isaac.lab.envs import BaseEnv, BaseEnvCfg +from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -88,7 +88,7 @@ class CubeActionTerm(ActionTerm): _asset: RigidObject """The articulation asset on which the action term is applied.""" - def __init__(self, cfg: ActionTermCfg, env: BaseEnv): + def __init__(self, cfg: ActionTermCfg, env: ManagerBasedEnv): # call super constructor super().__init__(cfg, env) # create buffers @@ -147,7 +147,7 @@ class CubeActionTermCfg(ActionTermCfg): ## -def base_position(env: BaseEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def base_position(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Root linear velocity in the asset's root frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] @@ -210,7 +210,7 @@ class EventCfg: @configclass -class CubeEnvCfg(BaseEnvCfg): +class CubeEnvCfg(ManagerBasedEnvCfg): """Configuration for the locomotion velocity-tracking environment.""" # Scene settings @@ -233,7 +233,7 @@ def main(): """Main function.""" # setup base environment - env = BaseEnv(cfg=CubeEnvCfg()) + env = ManagerBasedEnv(cfg=CubeEnvCfg()) # setup target position commands target_position = torch.rand(env.num_envs, 3, device=env.device) * 2 diff --git a/source/extensions/omni.isaac.lab/test/envs/test_base_env.py b/source/extensions/omni.isaac.lab/test/envs/test_base_env.py index 010f270b54..5588fb590b 100644 --- a/source/extensions/omni.isaac.lab/test/envs/test_base_env.py +++ b/source/extensions/omni.isaac.lab/test/envs/test_base_env.py @@ -10,7 +10,7 @@ """Launch Isaac Sim Simulator first.""" -from omni.isaac.lab.app import AppLauncher +from omni.isaac.lab.app import AppLauncher, run_tests # Can set this to False to see the GUI for debugging HEADLESS = True @@ -26,7 +26,7 @@ import omni.usd -from omni.isaac.lab.envs import BaseEnv, BaseEnvCfg +from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedEnvCfg from omni.isaac.lab.scene import InteractiveSceneCfg from omni.isaac.lab.utils import configclass @@ -49,7 +49,7 @@ def get_empty_base_env_cfg(device: str = "cuda:0", num_envs: int = 1, env_spacin """Generate base environment config based on device""" @configclass - class EmptyEnvCfg(BaseEnvCfg): + class EmptyEnvCfg(ManagerBasedEnvCfg): """Configuration for the empty test environment.""" # Scene settings @@ -82,7 +82,7 @@ def test_initialization(self): # create a new stage omni.usd.get_context().new_stage() # create environment - env = BaseEnv(cfg=get_empty_base_env_cfg(device=device)) + env = ManagerBasedEnv(cfg=get_empty_base_env_cfg(device=device)) # check size of action manager terms self.assertEqual(env.action_manager.total_action_dim, 0) self.assertEqual(len(env.action_manager.active_terms), 0) @@ -99,3 +99,7 @@ def test_initialization(self): obs, ext = env.step(action=act) # close the environment env.close() + + +if __name__ == "__main__": + run_tests() diff --git a/source/extensions/omni.isaac.lab/test/envs/test_null_command_term.py b/source/extensions/omni.isaac.lab/test/envs/test_null_command_term.py index 0a3783aa82..791d91932a 100644 --- a/source/extensions/omni.isaac.lab/test/envs/test_null_command_term.py +++ b/source/extensions/omni.isaac.lab/test/envs/test_null_command_term.py @@ -23,7 +23,7 @@ class TestNullCommandTerm(unittest.TestCase): """Test cases for null command generator.""" def setUp(self) -> None: - self.env = namedtuple("RLTaskEnv", ["num_envs", "dt", "device"])(20, 0.1, "cpu") + self.env = namedtuple("ManagerBasedRLEnv", ["num_envs", "dt", "device"])(20, 0.1, "cpu") def test_str(self): """Test the string representation of the command manager.""" diff --git a/source/extensions/omni.isaac.lab/test/managers/test_observation_manager.py b/source/extensions/omni.isaac.lab/test/managers/test_observation_manager.py index 8197473376..36a447d204 100644 --- a/source/extensions/omni.isaac.lab/test/managers/test_observation_manager.py +++ b/source/extensions/omni.isaac.lab/test/managers/test_observation_manager.py @@ -94,7 +94,7 @@ def setUp(self) -> None: self.num_envs = 20 self.device = "cuda:0" # create dummy environment - self.env = namedtuple("BaseEnv", ["num_envs", "device", "data"])( + self.env = namedtuple("ManagerBasedEnv", ["num_envs", "device", "data"])( self.num_envs, self.device, MyDataClass(self.num_envs, self.device) ) diff --git a/source/extensions/omni.isaac.lab/test/managers/test_reward_manager.py b/source/extensions/omni.isaac.lab/test/managers/test_reward_manager.py index 1bdc67e997..381886776f 100644 --- a/source/extensions/omni.isaac.lab/test/managers/test_reward_manager.py +++ b/source/extensions/omni.isaac.lab/test/managers/test_reward_manager.py @@ -39,7 +39,7 @@ class TestRewardManager(unittest.TestCase): """Test cases for various situations with reward manager.""" def setUp(self) -> None: - self.env = namedtuple("RLTaskEnv", ["num_envs", "dt", "device"])(20, 0.1, "cpu") + self.env = namedtuple("ManagerBasedRLEnv", ["num_envs", "dt", "device"])(20, 0.1, "cpu") def test_str(self): """Test the string representation of the reward manager.""" diff --git a/source/extensions/omni.isaac.lab/test/performance/test_kit_startup_performance.py b/source/extensions/omni.isaac.lab/test/performance/test_kit_startup_performance.py new file mode 100644 index 0000000000..2aa0c6b071 --- /dev/null +++ b/source/extensions/omni.isaac.lab/test/performance/test_kit_startup_performance.py @@ -0,0 +1,32 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# ignore private usage of variables warning +# pyright: reportPrivateUsage=none + +from __future__ import annotations + +import time +import unittest + +from omni.isaac.lab.app import run_tests + + +class TestKitStartUpPerformance(unittest.TestCase): + """Test kit startup performance.""" + + def test_kit_start_up_time(self): + """Test kit start-up time.""" + from omni.isaac.lab.app import AppLauncher + + start_time = time.time() + self.app_launcher = AppLauncher(headless=True).app + end_time = time.time() + elapsed_time = end_time - start_time + self.assertLessEqual(elapsed_time, 8.0) + + +if __name__ == "__main__": + run_tests() diff --git a/source/extensions/omni.isaac.lab/test/performance/test_robot_load_performance.py b/source/extensions/omni.isaac.lab/test/performance/test_robot_load_performance.py new file mode 100644 index 0000000000..56e842079e --- /dev/null +++ b/source/extensions/omni.isaac.lab/test/performance/test_robot_load_performance.py @@ -0,0 +1,64 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# ignore private usage of variables warning +# pyright: reportPrivateUsage=none + +from __future__ import annotations + +from omni.isaac.lab.app import AppLauncher, run_tests + +# launch omniverse app +simulation_app = AppLauncher(headless=True).app + +import unittest + +import omni +from omni.isaac.cloner import GridCloner + +from omni.isaac.lab_assets import ANYMAL_D_CFG, CARTPOLE_CFG + +from omni.isaac.lab.assets import Articulation +from omni.isaac.lab.sim import build_simulation_context +from omni.isaac.lab.utils.timer import Timer + + +class TestRobotLoadPerformance(unittest.TestCase): + """Test robot load performance.""" + + """ + Tests + """ + + def test_robot_load_performance(self): + """Test robot load time.""" + test_configs = { + "Cartpole": {"robot_cfg": CARTPOLE_CFG, "expected_load_time": 10.0}, + "Anymal_D": {"robot_cfg": ANYMAL_D_CFG, "expected_load_time": 40.0}, + } + for test_config in test_configs.items(): + for device in ("cuda:0", "cpu"): + with self.subTest(test_config=test_config, device=device): + with build_simulation_context(device=device) as sim: + cloner = GridCloner(spacing=2) + target_paths = cloner.generate_paths("/World/Robots", 4096) + omni.usd.get_context().get_stage().DefinePrim(target_paths[0], "Xform") + _ = cloner.clone( + source_prim_path=target_paths[0], + prim_paths=target_paths, + replicate_physics=False, + copy_from_source=True, + ) + with Timer(f"{test_config[0]} load time for device {device}") as timer: + robot = Articulation( # noqa: F841 + test_config[1]["robot_cfg"].replace(prim_path="/World/Robots_.*/Robot") + ) + sim.reset() + elapsed_time = timer.time_elapsed + self.assertLessEqual(elapsed_time, test_config[1]["expected_load_time"]) + + +if __name__ == "__main__": + run_tests() diff --git a/source/extensions/omni.isaac.lab/test/sensors/test_camera.py b/source/extensions/omni.isaac.lab/test/sensors/test_camera.py index 0a28e6d9e3..5492d145dc 100644 --- a/source/extensions/omni.isaac.lab/test/sensors/test_camera.py +++ b/source/extensions/omni.isaac.lab/test/sensors/test_camera.py @@ -11,7 +11,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch omniverse app -app_launcher = AppLauncher(headless=True, offscreen_render=True) +app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app """Rest everything follows.""" @@ -443,13 +443,22 @@ def test_throughput(self): # Save images with Timer(f"Time taken for writing data with shape {camera.image_shape} "): # Pack data back into replicator format to save them using its writer - rep_output = dict() - camera_data = convert_dict_to_backend(camera.data.output[0].to_dict(), backend="numpy") - for key, data, info in zip(camera_data.keys(), camera_data.values(), camera.data.info[0].values()): - if info is not None: - rep_output[key] = {"data": data, "info": info} - else: - rep_output[key] = data + if self.sim.get_version()[0] == 4: + rep_output = {"annotators": {}} + camera_data = convert_dict_to_backend(camera.data.output[0].to_dict(), backend="numpy") + for key, data, info in zip(camera_data.keys(), camera_data.values(), camera.data.info[0].values()): + if info is not None: + rep_output["annotators"][key] = {"render_product": {"data": data, **info}} + else: + rep_output["annotators"][key] = {"render_product": {"data": data}} + else: + rep_output = dict() + camera_data = convert_dict_to_backend(camera.data.output[0].to_dict(), backend="numpy") + for key, data, info in zip(camera_data.keys(), camera_data.values(), camera.data.info[0].values()): + if info is not None: + rep_output[key] = {"data": data, "info": info} + else: + rep_output[key] = data # Save images rep_output["trigger_outputs"] = {"on_time": camera.frame[0]} rep_writer.write(rep_output) diff --git a/source/extensions/omni.isaac.lab/test/sensors/test_ray_caster_camera.py b/source/extensions/omni.isaac.lab/test/sensors/test_ray_caster_camera.py index c38d6f122c..6ea21b121d 100644 --- a/source/extensions/omni.isaac.lab/test/sensors/test_ray_caster_camera.py +++ b/source/extensions/omni.isaac.lab/test/sensors/test_ray_caster_camera.py @@ -11,7 +11,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch omniverse app -app_launcher = AppLauncher(headless=True, offscreen_render=True) +app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app """Rest everything follows.""" @@ -44,7 +44,7 @@ class TestWarpCamera(unittest.TestCase): - """Test for Isaac Lab camera sensor""" + """Test for isaaclab camera sensor""" """ Test Setup and Teardown @@ -344,13 +344,22 @@ def test_throughput(self): # Save images with Timer(f"Time taken for writing data with shape {camera.image_shape} "): # Pack data back into replicator format to save them using its writer - rep_output = dict() - camera_data = convert_dict_to_backend(camera.data.output[0].to_dict(), backend="numpy") - for key, data, info in zip(camera_data.keys(), camera_data.values(), camera.data.info[0].values()): - if info is not None: - rep_output[key] = {"data": data, "info": info} - else: - rep_output[key] = data + if self.sim.get_version()[0] == 4: + rep_output = {"annotators": {}} + camera_data = convert_dict_to_backend(camera.data.output[0].to_dict(), backend="numpy") + for key, data, info in zip(camera_data.keys(), camera_data.values(), camera.data.info[0].values()): + if info is not None: + rep_output["annotators"][key] = {"render_product": {"data": data, **info}} + else: + rep_output["annotators"][key] = {"render_product": {"data": data}} + else: + rep_output = dict() + camera_data = convert_dict_to_backend(camera.data.output[0].to_dict(), backend="numpy") + for key, data, info in zip(camera_data.keys(), camera_data.values(), camera.data.info[0].values()): + if info is not None: + rep_output[key] = {"data": data, "info": info} + else: + rep_output[key] = data # Save images rep_output["trigger_outputs"] = {"on_time": camera.frame[0]} rep_writer.write(rep_output) diff --git a/source/extensions/omni.isaac.lab/test/sensors/test_tiled_camera.py b/source/extensions/omni.isaac.lab/test/sensors/test_tiled_camera.py new file mode 100644 index 0000000000..f672a3ad4b --- /dev/null +++ b/source/extensions/omni.isaac.lab/test/sensors/test_tiled_camera.py @@ -0,0 +1,343 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# ignore private usage of variables warning +# pyright: reportPrivateUsage=none + +"""Launch Isaac Sim Simulator first.""" + +from omni.isaac.lab.app import AppLauncher, run_tests + +# launch omniverse app +app_launcher = AppLauncher(headless=True, enable_cameras=True) +simulation_app = app_launcher.app + +"""Rest everything follows.""" + +import copy +import numpy as np +import random +import unittest + +import omni.isaac.core.utils.prims as prim_utils +import omni.isaac.core.utils.stage as stage_utils +import omni.replicator.core as rep +from omni.isaac.core.prims import GeometryPrim, RigidPrim +from pxr import Gf, UsdGeom + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.sensors.camera import TiledCamera, TiledCameraCfg +from omni.isaac.lab.utils.timer import Timer + + +class TestTiledCamera(unittest.TestCase): + """Test for USD tiled Camera sensor.""" + + def setUp(self): + """Create a blank new stage for each test.""" + self.camera_cfg = TiledCameraCfg( + height=128, + width=256, + offset=TiledCameraCfg.OffsetCfg(pos=(0.0, 0.0, 4.0), rot=(0.0, 0.0, 1.0, 0.0), convention="ros"), + prim_path="/World/Camera", + update_period=0, + data_types=["rgb", "depth"], + spawn=sim_utils.PinholeCameraCfg( + focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) + ), + ) + # Create a new stage + stage_utils.create_new_stage() + # Simulation time-step + self.dt = 0.01 + # Load kit helper + sim_cfg = sim_utils.SimulationCfg(dt=self.dt) + self.sim: sim_utils.SimulationContext = sim_utils.SimulationContext(sim_cfg) + # populate scene + self._populate_scene() + # load stage + stage_utils.update_stage() + + def tearDown(self): + """Stops simulator after each test.""" + # close all the opened viewport from before. + rep.vp_manager.destroy_hydra_textures("Replicator") + # stop simulation + # note: cannot use self.sim.stop() since it does one render step after stopping!! This doesn't make sense :( + self.sim._timeline.stop() + # clear the stage + self.sim.clear_all_callbacks() + self.sim.clear_instance() + + """ + Tests + """ + + def test_single_camera_init(self): + """Test single camera initialization.""" + # Create camera + camera = TiledCamera(self.camera_cfg) + # Check simulation parameter is set correctly + self.assertTrue(self.sim.has_rtx_sensors()) + # Play sim + self.sim.reset() + # Check if camera is initialized + self.assertTrue(camera._is_initialized) + # Check if camera prim is set correctly and that it is a camera prim + self.assertEqual(camera._sensor_prims[0].GetPath().pathString, self.camera_cfg.prim_path) + self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera) + + # Simulate for a few steps + # note: This is a workaround to ensure that the textures are loaded. + # Check "Known Issues" section in the documentation for more details. + for _ in range(5): + self.sim.step() + + # Check buffers that exists and have correct shapes + self.assertEqual(camera.data.pos_w.shape, (1, 3)) + self.assertEqual(camera.data.quat_w_ros.shape, (1, 4)) + self.assertEqual(camera.data.quat_w_world.shape, (1, 4)) + self.assertEqual(camera.data.quat_w_opengl.shape, (1, 4)) + self.assertEqual(camera.data.intrinsic_matrices.shape, (1, 3, 3)) + self.assertEqual(camera.data.image_shape, (self.camera_cfg.height, self.camera_cfg.width)) + + # Simulate physics + for _ in range(10): + # perform rendering + self.sim.step() + # update camera + camera.update(self.dt) + # check image data + for im_type, im_data in camera.data.output.to_dict().items(): + if im_type == "rgb": + self.assertEqual(im_data.shape, (1, self.camera_cfg.height, self.camera_cfg.width, 3)) + else: + self.assertEqual(im_data.shape, (1, self.camera_cfg.height, self.camera_cfg.width, 1)) + self.assertGreater(im_data.mean().item(), 0.0) + del camera + + def test_multi_camera_init(self): + """Test multi-camera initialization.""" + + prim_utils.create_prim("/World/Origin_00", "Xform") + prim_utils.create_prim("/World/Origin_01", "Xform") + + # Create camera + camera_cfg = copy.deepcopy(self.camera_cfg) + camera_cfg.prim_path = "/World/Origin_.*/CameraSensor" + camera = TiledCamera(camera_cfg) + # Check simulation parameter is set correctly + self.assertTrue(self.sim.has_rtx_sensors()) + # Play sim + self.sim.reset() + # Check if camera is initialized + self.assertTrue(camera._is_initialized) + # Check if camera prim is set correctly and that it is a camera prim + self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_01/CameraSensor") + self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera) + + # Simulate for a few steps + # note: This is a workaround to ensure that the textures are loaded. + # Check "Known Issues" section in the documentation for more details. + for _ in range(5): + self.sim.step() + + # Check buffers that exists and have correct shapes + self.assertEqual(camera.data.pos_w.shape, (2, 3)) + self.assertEqual(camera.data.quat_w_ros.shape, (2, 4)) + self.assertEqual(camera.data.quat_w_world.shape, (2, 4)) + self.assertEqual(camera.data.quat_w_opengl.shape, (2, 4)) + self.assertEqual(camera.data.intrinsic_matrices.shape, (2, 3, 3)) + self.assertEqual(camera.data.image_shape, (self.camera_cfg.height, self.camera_cfg.width)) + + # Simulate physics + for _ in range(10): + # perform rendering + self.sim.step() + # update camera + camera.update(self.dt) + # check image data + for im_type, im_data in camera.data.output.to_dict().items(): + if im_type == "rgb": + self.assertEqual(im_data.shape, (2, self.camera_cfg.height, self.camera_cfg.width, 3)) + else: + self.assertEqual(im_data.shape, (2, self.camera_cfg.height, self.camera_cfg.width, 1)) + self.assertGreater(im_data[0].mean().item(), 0.0) + self.assertGreater(im_data[1].mean().item(), 0.0) + del camera + + def test_rgb_only_camera(self): + """Test initialization with only RGB.""" + + prim_utils.create_prim("/World/Origin_00", "Xform") + prim_utils.create_prim("/World/Origin_01", "Xform") + + # Create camera + camera_cfg = copy.deepcopy(self.camera_cfg) + camera_cfg.data_types = ["rgb"] + camera_cfg.prim_path = "/World/Origin_.*/CameraSensor" + camera = TiledCamera(camera_cfg) + # Check simulation parameter is set correctly + self.assertTrue(self.sim.has_rtx_sensors()) + # Play sim + self.sim.reset() + # Check if camera is initialized + self.assertTrue(camera._is_initialized) + # Check if camera prim is set correctly and that it is a camera prim + self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_01/CameraSensor") + self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera) + self.assertListEqual(list(camera.data.output.keys()), ["rgb"]) + # Simulate for a few steps + # note: This is a workaround to ensure that the textures are loaded. + # Check "Known Issues" section in the documentation for more details. + for _ in range(5): + self.sim.step() + + # Check buffers that exists and have correct shapes + self.assertEqual(camera.data.pos_w.shape, (2, 3)) + self.assertEqual(camera.data.quat_w_ros.shape, (2, 4)) + self.assertEqual(camera.data.quat_w_world.shape, (2, 4)) + self.assertEqual(camera.data.quat_w_opengl.shape, (2, 4)) + self.assertEqual(camera.data.intrinsic_matrices.shape, (2, 3, 3)) + self.assertEqual(camera.data.image_shape, (self.camera_cfg.height, self.camera_cfg.width)) + + # Simulate physics + for _ in range(10): + # perform rendering + self.sim.step() + # update camera + camera.update(self.dt) + # check image data + for _, im_data in camera.data.output.to_dict().items(): + self.assertEqual(im_data.shape, (2, self.camera_cfg.height, self.camera_cfg.width, 3)) + self.assertGreater(im_data[0].mean().item(), 0.0) + self.assertGreater(im_data[1].mean().item(), 0.0) + del camera + + def test_depth_only_camera(self): + """Test initialization with only depth.""" + + prim_utils.create_prim("/World/Origin_00", "Xform") + prim_utils.create_prim("/World/Origin_01", "Xform") + + # Create camera + camera_cfg = copy.deepcopy(self.camera_cfg) + camera_cfg.data_types = ["depth"] + camera_cfg.prim_path = "/World/Origin_.*/CameraSensor" + camera = TiledCamera(camera_cfg) + # Check simulation parameter is set correctly + self.assertTrue(self.sim.has_rtx_sensors()) + # Play sim + self.sim.reset() + # Check if camera is initialized + self.assertTrue(camera._is_initialized) + # Check if camera prim is set correctly and that it is a camera prim + self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_01/CameraSensor") + self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera) + self.assertListEqual(list(camera.data.output.keys()), ["depth"]) + + # Simulate for a few steps + # note: This is a workaround to ensure that the textures are loaded. + # Check "Known Issues" section in the documentation for more details. + for _ in range(5): + self.sim.step() + + # Check buffers that exists and have correct shapes + self.assertEqual(camera.data.pos_w.shape, (2, 3)) + self.assertEqual(camera.data.quat_w_ros.shape, (2, 4)) + self.assertEqual(camera.data.quat_w_world.shape, (2, 4)) + self.assertEqual(camera.data.quat_w_opengl.shape, (2, 4)) + self.assertEqual(camera.data.intrinsic_matrices.shape, (2, 3, 3)) + self.assertEqual(camera.data.image_shape, (self.camera_cfg.height, self.camera_cfg.width)) + + # Simulate physics + for _ in range(10): + # perform rendering + self.sim.step() + # update camera + camera.update(self.dt) + # check image data + for _, im_data in camera.data.output.to_dict().items(): + self.assertEqual(im_data.shape, (2, self.camera_cfg.height, self.camera_cfg.width, 1)) + self.assertGreater(im_data[0].mean().item(), 0.0) + self.assertGreater(im_data[1].mean().item(), 0.0) + del camera + + def test_throughput(self): + """Test tiled camera throughput.""" + + # create camera + camera_cfg = copy.deepcopy(self.camera_cfg) + camera_cfg.height = 480 + camera_cfg.width = 640 + camera = TiledCamera(camera_cfg) + + # Play simulator + self.sim.reset() + + # Simulate for a few steps + # note: This is a workaround to ensure that the textures are loaded. + # Check "Known Issues" section in the documentation for more details. + for _ in range(5): + self.sim.step() + + # Simulate physics + for _ in range(5): + # perform rendering + self.sim.step() + # update camera + with Timer(f"Time taken for updating camera with shape {camera.image_shape}"): + camera.update(self.dt) + # Check image data + for im_type, im_data in camera.data.output.to_dict().items(): + if im_type == "rgb": + self.assertEqual(im_data.shape, (1, camera_cfg.height, camera_cfg.width, 3)) + else: + self.assertEqual(im_data.shape, (1, camera_cfg.height, camera_cfg.width, 1)) + self.assertGreater(im_data.mean().item(), 0.0) + del camera + + """ + Helper functions. + """ + + @staticmethod + def _populate_scene(): + """Add prims to the scene.""" + # Ground-plane + cfg = sim_utils.GroundPlaneCfg() + cfg.func("/World/defaultGroundPlane", cfg) + # Lights + cfg = sim_utils.SphereLightCfg() + cfg.func("/World/Light/GreySphere", cfg, translation=(4.5, 3.5, 10.0)) + cfg.func("/World/Light/WhiteSphere", cfg, translation=(-4.5, 3.5, 10.0)) + # Random objects + random.seed(0) + for i in range(10): + # sample random position + position = np.random.rand(3) - np.asarray([0.05, 0.05, -1.0]) + position *= np.asarray([1.5, 1.5, 0.5]) + # create prim + prim_type = random.choice(["Cube", "Sphere", "Cylinder"]) + prim = prim_utils.create_prim( + f"/World/Objects/Obj_{i:02d}", + prim_type, + translation=position, + scale=(0.25, 0.25, 0.25), + semantic_label=prim_type, + ) + # cast to geom prim + geom_prim = getattr(UsdGeom, prim_type)(prim) + # set random color + color = Gf.Vec3f(random.random(), random.random(), random.random()) + geom_prim.CreateDisplayColorAttr() + geom_prim.GetDisplayColorAttr().Set([color]) + # add rigid properties + GeometryPrim(f"/World/Objects/Obj_{i:02d}", collision=True) + RigidPrim(f"/World/Objects/Obj_{i:02d}", mass=5.0) + + +if __name__ == "__main__": + run_tests() diff --git a/source/extensions/omni.isaac.lab/test/sim/test_simulation_context.py b/source/extensions/omni.isaac.lab/test/sim/test_simulation_context.py index e4c141a134..b9cb1a26dd 100644 --- a/source/extensions/omni.isaac.lab/test/sim/test_simulation_context.py +++ b/source/extensions/omni.isaac.lab/test/sim/test_simulation_context.py @@ -8,7 +8,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch omniverse app -simulation_app = AppLauncher(headless=True, experience="omni.isaac.sim.python.gym.headless.kit").app +simulation_app = AppLauncher(headless=True).app """Rest everything follows.""" @@ -75,7 +75,7 @@ def test_sim_version(self): sim = SimulationContext() version = sim.get_version() self.assertTrue(len(version) > 0) - self.assertTrue(version[0] >= 2023) + self.assertTrue(version[0] >= 4) def test_carb_setting(self): """Test setting carb settings.""" diff --git a/source/extensions/omni.isaac.lab/test/sim/test_spawn_lights.py b/source/extensions/omni.isaac.lab/test/sim/test_spawn_lights.py index 6949f36eef..27f44c0a8f 100644 --- a/source/extensions/omni.isaac.lab/test/sim/test_spawn_lights.py +++ b/source/extensions/omni.isaac.lab/test/sim/test_spawn_lights.py @@ -149,7 +149,10 @@ def _validate_properties_on_prim(self, prim_path: str, cfg: sim_utils.LightCfg): raise ValueError(f"Unknown texture attribute: '{attr_name}'") else: # convert attribute name in prim to cfg name - prim_prop_name = f"inputs:{to_camel_case(attr_name, to='cC')}" + if attr_name == "visible_in_primary_ray": + prim_prop_name = f"{to_camel_case(attr_name, to='cC')}" + else: + prim_prop_name = f"inputs:{to_camel_case(attr_name, to='cC')}" # configured value configured_value = prim.GetAttribute(prim_prop_name).Get() # validate the values diff --git a/source/extensions/omni.isaac.lab/test/sim/test_urdf_converter.py b/source/extensions/omni.isaac.lab/test/sim/test_urdf_converter.py index d503d9dcef..d5c7ee70f6 100644 --- a/source/extensions/omni.isaac.lab/test/sim/test_urdf_converter.py +++ b/source/extensions/omni.isaac.lab/test/sim/test_urdf_converter.py @@ -39,7 +39,8 @@ def setUp(self): extension_path = get_extension_path_from_name("omni.importer.urdf") # default configuration self.config = UrdfConverterCfg( - asset_path=f"{extension_path}/data/urdf/robots/franka_description/robots/panda_arm_hand.urdf", fix_base=True + asset_path=f"{extension_path}/data/urdf/robots/franka_description/robots/panda_arm_hand.urdf", + fix_base=True, ) # Simulation time-step self.dt = 0.01 @@ -107,9 +108,11 @@ def test_config_drive_type(self): os.makedirs(output_dir, exist_ok=True) # change the config + self.config.force_usd_conversion = True self.config.default_drive_type = "position" self.config.default_drive_stiffness = 400.0 self.config.default_drive_damping = 40.0 + self.config.override_joint_dynamics = True self.config.usd_dir = output_dir urdf_converter = UrdfConverter(self.config) # check the drive type of the robot diff --git a/source/extensions/omni.isaac.lab/test/terrains/check_terrain_importer.py b/source/extensions/omni.isaac.lab/test/terrains/check_terrain_importer.py index 38dc84ef0f..4016530822 100644 --- a/source/extensions/omni.isaac.lab/test/terrains/check_terrain_importer.py +++ b/source/extensions/omni.isaac.lab/test/terrains/check_terrain_importer.py @@ -30,7 +30,7 @@ import argparse -# omni-isaac-lab +# omni.isaac.lab from omni.isaac.lab.app import AppLauncher # add argparse arguments @@ -81,7 +81,7 @@ def main(): - """Generates a terrain from Isaac Lab.""" + """Generates a terrain from isaaclab.""" # Load kit helper sim_params = { diff --git a/source/extensions/omni.isaac.lab_assets/config/extension.toml b/source/extensions/omni.isaac.lab_assets/config/extension.toml index 7e48b07463..f036213f45 100644 --- a/source/extensions/omni.isaac.lab_assets/config/extension.toml +++ b/source/extensions/omni.isaac.lab_assets/config/extension.toml @@ -1,6 +1,6 @@ [package] # Semantic Versioning is used: https://semver.org/ -version = "0.1.2" +version = "0.1.3" # Description title = "Isaac Lab Assets" diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/__init__.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/__init__.py index 3ac6e54110..f9ac841ed1 100644 --- a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/__init__.py +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/__init__.py @@ -27,10 +27,13 @@ ## from .allegro import * +from .ant import * from .anymal import * from .cartpole import * from .franka import * +from .humanoid import * from .kinova import * +from .quadcopter import * from .ridgeback_franka import * from .sawyer import * from .shadow_hand import * diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/ant.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/ant.py new file mode 100644 index 0000000000..c7eddbc378 --- /dev/null +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/ant.py @@ -0,0 +1,55 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Configuration for the Mujoco Ant robot.""" + +from __future__ import annotations + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.actuators import ImplicitActuatorCfg +from omni.isaac.lab.assets import ArticulationCfg +from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR + +## +# Configuration +## + +ANT_CFG = ArticulationCfg( + prim_path="{ENV_REGEX_NS}/Robot", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Ant/ant_instanceable.usd", + rigid_props=sim_utils.RigidBodyPropertiesCfg( + disable_gravity=False, + max_depenetration_velocity=10.0, + enable_gyroscopic_forces=True, + ), + articulation_props=sim_utils.ArticulationRootPropertiesCfg( + enabled_self_collisions=False, + solver_position_iteration_count=4, + solver_velocity_iteration_count=0, + sleep_threshold=0.005, + stabilization_threshold=0.001, + ), + copy_from_source=False, + ), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 0.5), + joint_pos={ + ".*_leg": 0.0, + "front_left_foot": 0.785398, # 45 degrees + "front_right_foot": -0.785398, + "left_back_foot": -0.785398, + "right_back_foot": 0.785398, + }, + ), + actuators={ + "body": ImplicitActuatorCfg( + joint_names_expr=[".*"], + stiffness=0.0, + damping=0.0, + ), + }, +) +"""Configuration for the Mujoco Ant robot.""" diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py index 51aa7009f8..48d428beda 100644 --- a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py @@ -11,6 +11,10 @@ from omni.isaac.lab.assets import ArticulationCfg from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR +## +# Configuration +## + CARTPOLE_CFG = ArticulationCfg( spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Classic/Cartpole/cartpole.usd", @@ -45,3 +49,4 @@ ), }, ) +"""Configuration for a simple Cartpole robot.""" diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cassie.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cassie.py index d0e7dbc9e0..5378928a57 100644 --- a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cassie.py +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cassie.py @@ -21,7 +21,6 @@ # Configuration ## - CASSIE_CFG = ArticulationCfg( spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Agility/Cassie/cassie.usd", diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/humanoid.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/humanoid.py new file mode 100644 index 0000000000..220a42665b --- /dev/null +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/humanoid.py @@ -0,0 +1,69 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Configuration for the Mujoco Humanoid robot.""" + +from __future__ import annotations + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.actuators import ImplicitActuatorCfg +from omni.isaac.lab.assets import ArticulationCfg +from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR + +## +# Configuration +## + +HUMANOID_CFG = ArticulationCfg( + prim_path="{ENV_REGEX_NS}/Robot", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Humanoid/humanoid_instanceable.usd", + rigid_props=sim_utils.RigidBodyPropertiesCfg( + disable_gravity=None, + max_depenetration_velocity=10.0, + enable_gyroscopic_forces=True, + ), + articulation_props=sim_utils.ArticulationRootPropertiesCfg( + enabled_self_collisions=True, + solver_position_iteration_count=4, + solver_velocity_iteration_count=0, + sleep_threshold=0.005, + stabilization_threshold=0.001, + ), + copy_from_source=False, + ), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 1.34), + joint_pos={".*": 0.0}, + ), + actuators={ + "body": ImplicitActuatorCfg( + joint_names_expr=[".*"], + stiffness={ + ".*_waist.*": 20.0, + ".*_upper_arm.*": 10.0, + "pelvis": 10.0, + ".*_lower_arm": 2.0, + ".*_thigh:0": 10.0, + ".*_thigh:1": 20.0, + ".*_thigh:2": 10.0, + ".*_shin": 5.0, + ".*_foot.*": 2.0, + }, + damping={ + ".*_waist.*": 5.0, + ".*_upper_arm.*": 5.0, + "pelvis": 5.0, + ".*_lower_arm": 1.0, + ".*_thigh:0": 5.0, + ".*_thigh:1": 5.0, + ".*_thigh:2": 5.0, + ".*_shin": 0.1, + ".*_foot.*": 1.0, + }, + ), + }, +) +"""Configuration for the Mujoco Humanoid robot.""" diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/kinova.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/kinova.py index 7e6b411d53..04b781a279 100644 --- a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/kinova.py +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/kinova.py @@ -23,7 +23,6 @@ # Configuration ## - KINOVA_JACO2_N7S300_CFG = ArticulationCfg( spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Kinova/Jaco2/J2N7S300/j2n7s300_instanceable.usd", diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/quadcopter.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/quadcopter.py new file mode 100644 index 0000000000..64ec9121c8 --- /dev/null +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/quadcopter.py @@ -0,0 +1,57 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Configuration for the quadcopters""" + +from __future__ import annotations + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.actuators import ImplicitActuatorCfg +from omni.isaac.lab.assets import ArticulationCfg +from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR + +## +# Configuration +## + +CRAZYFLIE_CFG = ArticulationCfg( + prim_path="{ENV_REGEX_NS}/Robot", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Crazyflie/cf2x.usd", + rigid_props=sim_utils.RigidBodyPropertiesCfg( + disable_gravity=False, + max_depenetration_velocity=10.0, + enable_gyroscopic_forces=True, + ), + articulation_props=sim_utils.ArticulationRootPropertiesCfg( + enabled_self_collisions=False, + solver_position_iteration_count=4, + solver_velocity_iteration_count=0, + sleep_threshold=0.005, + stabilization_threshold=0.001, + ), + copy_from_source=False, + ), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 0.5), + joint_pos={ + ".*": 0.0, + }, + joint_vel={ + "m1_joint": 200.0, + "m2_joint": -200.0, + "m3_joint": 200.0, + "m4_joint": -200.0, + }, + ), + actuators={ + "dummy": ImplicitActuatorCfg( + joint_names_expr=[".*"], + stiffness=0.0, + damping=0.0, + ), + }, +) +"""Configuration for the Crazyflie quadcopter.""" diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/ridgeback_franka.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/ridgeback_franka.py index 8ff4df2a7a..2bc9240ada 100644 --- a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/ridgeback_franka.py +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/ridgeback_franka.py @@ -17,6 +17,10 @@ from omni.isaac.lab.assets.articulation import ArticulationCfg from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR +## +# Configuration +## + RIDGEBACK_FRANKA_PANDA_CFG = ArticulationCfg( spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Clearpath/RidgebackFranka/ridgeback_franka.usd", diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/sawyer.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/sawyer.py index 9c39278ed4..9cec7ebbb3 100644 --- a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/sawyer.py +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/sawyer.py @@ -21,7 +21,6 @@ # Configuration ## - SAWYER_CFG = ArticulationCfg( spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/RethinkRobotics/sawyer_instanceable.usd", diff --git a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/unitree.py b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/unitree.py index 1e4b418e3e..8533010a84 100644 --- a/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/unitree.py +++ b/source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/unitree.py @@ -10,12 +10,13 @@ * :obj:`UNITREE_A1_CFG`: Unitree A1 robot with DC motor model for the legs * :obj:`UNITREE_GO1_CFG`: Unitree Go1 robot with actuator net model for the legs * :obj:`UNITREE_GO2_CFG`: Unitree Go2 robot with DC motor model for the legs +* :obj:`H1_CFG`: H1 humanoid robot Reference: https://github.com/unitreerobotics/unitree_ros """ import omni.isaac.lab.sim as sim_utils -from omni.isaac.lab.actuators import ActuatorNetMLPCfg, DCMotorCfg +from omni.isaac.lab.actuators import ActuatorNetMLPCfg, DCMotorCfg, ImplicitActuatorCfg from omni.isaac.lab.assets.articulation import ArticulationCfg from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR @@ -173,3 +174,94 @@ }, ) """Configuration of Unitree Go2 using DC-Motor actuator model.""" + + +H1_CFG = ArticulationCfg( + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Unitree/H1/h1.usd", + activate_contact_sensors=True, + rigid_props=sim_utils.RigidBodyPropertiesCfg( + disable_gravity=False, + retain_accelerations=False, + linear_damping=0.0, + angular_damping=0.0, + max_linear_velocity=1000.0, + max_angular_velocity=1000.0, + max_depenetration_velocity=1.0, + ), + articulation_props=sim_utils.ArticulationRootPropertiesCfg( + enabled_self_collisions=False, solver_position_iteration_count=4, solver_velocity_iteration_count=4 + ), + ), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 1.05), + joint_pos={ + ".*_hip_yaw": 0.0, + ".*_hip_roll": 0.0, + ".*_hip_pitch": -0.28, # -16 degrees + ".*_knee": 0.79, # 45 degrees + ".*_ankle": -0.52, # -30 degrees + "torso": 0.0, + ".*_shoulder_pitch": 0.28, + ".*_shoulder_roll": 0.0, + ".*_shoulder_yaw": 0.0, + ".*_elbow": 0.52, + }, + joint_vel={".*": 0.0}, + ), + soft_joint_pos_limit_factor=0.9, + actuators={ + "legs": ImplicitActuatorCfg( + joint_names_expr=[".*_hip_yaw", ".*_hip_roll", ".*_hip_pitch", ".*_knee", "torso"], + effort_limit=300, + velocity_limit=100.0, + stiffness={ + ".*_hip_yaw": 150.0, + ".*_hip_roll": 150.0, + ".*_hip_pitch": 200.0, + ".*_knee": 200.0, + "torso": 200.0, + }, + damping={ + ".*_hip_yaw": 5.0, + ".*_hip_roll": 5.0, + ".*_hip_pitch": 5.0, + ".*_knee": 5.0, + "torso": 5.0, + }, + ), + "feet": ImplicitActuatorCfg( + joint_names_expr=[".*_ankle"], + effort_limit=100, + velocity_limit=100.0, + stiffness={".*_ankle": 20.0}, + damping={".*_ankle": 4.0}, + ), + "arms": ImplicitActuatorCfg( + joint_names_expr=[".*_shoulder_pitch", ".*_shoulder_roll", ".*_shoulder_yaw", ".*_elbow"], + effort_limit=300, + velocity_limit=100.0, + stiffness={ + ".*_shoulder_pitch": 40.0, + ".*_shoulder_roll": 40.0, + ".*_shoulder_yaw": 40.0, + ".*_elbow": 40.0, + }, + damping={ + ".*_shoulder_pitch": 10.0, + ".*_shoulder_roll": 10.0, + ".*_shoulder_yaw": 10.0, + ".*_elbow": 10.0, + }, + ), + }, +) +"""Configuration for the Unitree H1 Humanoid robot.""" + + +H1_MINIMAL_CFG = H1_CFG.copy() +H1_MINIMAL_CFG.spawn.usd_path = f"{ISAACLAB_NUCLEUS_DIR}/Robots/Unitree/H1/h1_minimal.usd" +"""Configuration for the Unitree H1 Humanoid robot with fewer collision meshes. + +This configuration removes most collision meshes to speed up simulation. +""" diff --git a/source/extensions/omni.isaac.lab_assets/pyproject.toml b/source/extensions/omni.isaac.lab_assets/pyproject.toml new file mode 100644 index 0000000000..d90ac3536f --- /dev/null +++ b/source/extensions/omni.isaac.lab_assets/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "toml"] +build-backend = "setuptools.build_meta" diff --git a/source/extensions/omni.isaac.lab_assets/setup.py b/source/extensions/omni.isaac.lab_assets/setup.py index 776526c89a..dcd0f0d647 100644 --- a/source/extensions/omni.isaac.lab_assets/setup.py +++ b/source/extensions/omni.isaac.lab_assets/setup.py @@ -30,7 +30,7 @@ classifiers=[ "Natural Language :: English", "Programming Language :: Python :: 3.10", - "Isaac Sim :: 2023.1.0-hotfix.1", + "Isaac Sim :: 4.0.0", "Isaac Sim :: 2023.1.1", ], zip_safe=False, diff --git a/source/extensions/omni.isaac.lab_assets/test/test_valid_configs.py b/source/extensions/omni.isaac.lab_assets/test/test_valid_configs.py index 093050b566..c875efb809 100644 --- a/source/extensions/omni.isaac.lab_assets/test/test_valid_configs.py +++ b/source/extensions/omni.isaac.lab_assets/test/test_valid_configs.py @@ -54,7 +54,7 @@ def test_asset_configs(self): with self.subTest(asset_name=asset_name, device=device): with build_simulation_context(device=device, auto_add_lighting=True) as sim: # print the asset name - print(">>> Testing entities:", asset_name) + print(f">>> Testing entity {asset_name} on device {device}") # name the prim path entity_cfg.prim_path = "/World/asset" # create the asset / sensors diff --git a/source/extensions/omni.isaac.lab_tasks/config/extension.toml b/source/extensions/omni.isaac.lab_tasks/config/extension.toml index 30699a9153..6f88544864 100644 --- a/source/extensions/omni.isaac.lab_tasks/config/extension.toml +++ b/source/extensions/omni.isaac.lab_tasks/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.6.1" +version = "0.7.5" # Description title = "Isaac Lab Environments" diff --git a/source/extensions/omni.isaac.lab_tasks/docs/CHANGELOG.rst b/source/extensions/omni.isaac.lab_tasks/docs/CHANGELOG.rst index e3f979d7a4..04cf0e35ab 100644 --- a/source/extensions/omni.isaac.lab_tasks/docs/CHANGELOG.rst +++ b/source/extensions/omni.isaac.lab_tasks/docs/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog --------- -0.6.2 (2024-05-31) +0.7.5 (2024-05-31) ~~~~~~~~~~~~~~~~~~ Added @@ -13,6 +13,66 @@ Added when used for inference. +0.7.5 (2024-05-28) +~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Added a new environment ``Isaac-Navigation-Flat-Anymal-C-v0`` to navigate towards a target position on flat terrain. + + +0.7.4 (2024-05-21) +~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Set default device for RSL RL and SB3 configs to "cuda:0". + +0.7.3 (2024-05-21) +~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Introduce ``--max_iterations`` argument to training scripts for specifying number of training iterations. + +0.7.2 (2024-05-13) +~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Add Shadow Hand environments: ``Isaac-Shadow-Hand-Direct-v0``, ``Isaac-Shadow-Hand-OpenAI-FF-Direct-v0``, ``Isaac-Shadow-Hand-OpenAI-LSTM-Direct-v0``. + + +0.7.1 (2024-05-09) +~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Added the skrl agent configurations for the config and direct workflow tasks + + +0.7.0 (2024-05-07) +~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Renamed all references of ``BaseEnv``, ``RLTaskEnv``, and ``OIGEEnv`` to :class:`omni.isaac.lab.envs.ManagerBasedEnv`, :class:`omni.isaac.lab.envs.ManagerBasedRLEnv`, and :class:`omni.isaac.lab.envs.DirectRLEnv`. +* Split environments into ``manager_based`` and ``direct`` folders. + +Added +^^^^^ + +* Added direct workflow environments: + * ``Isaac-Cartpole-Direct-v0``, ``Isaac-Cartpole-Camera-Direct-v0``, ``Isaac-Ant-Direct-v0``, ``Isaac-Humanoid-Direct-v0``. + * ``Isaac-Velocity-Flat-Anymal-C-Direct-v0``, ``Isaac-Velocity-Rough-Anymal-C-Direct-v0``, ``Isaac-Quadcopter-Direct-v0``. + + 0.6.1 (2024-04-16) ~~~~~~~~~~~~~~~~~~ @@ -36,7 +96,7 @@ Fixed ^^^^^ * Fixed logging of extra information for RL-Games wrapper. It expected the extra information to be under the - key ``"episode"``, but Orbit used the key ``"log"``. The wrapper now remaps the key to ``"episode"``. + key ``"episode"``, but Isaac Lab used the key ``"log"``. The wrapper now remaps the key to ``"episode"``. 0.5.7 (2024-02-28) @@ -74,7 +134,7 @@ Fixed Added ^^^^^ -* Added a check for the flag :attr:`omni.isaac.lab.envs.RLTaskEnvCfg.is_finite_horizon` +* Added a check for the flag :attr:`omni.isaac.lab.envs.ManagerBasedRLEnvCfg.is_finite_horizon` in the RSL-RL and RL-Games wrappers to handle the finite horizon tasks properly. Earlier, the wrappers were always assuming the tasks to be infinite horizon tasks and returning a time-out signals when the episode length was reached. @@ -107,8 +167,8 @@ Fixed Fixed ^^^^^ -* Fixed the wrappers to different learning frameworks to use the new :class:`omni.isaac.lab_tasks.RLTaskEnv` class. - The :class:`RLTaskEnv` class inherits from the :class:`gymnasium.Env` class (Gym 0.29.0). +* Fixed the wrappers to different learning frameworks to use the new :class:`omni.isaac.lab_tasks.ManagerBasedRLEnv` class. + The :class:`ManagerBasedRLEnv` class inherits from the :class:`gymnasium.Env` class (Gym 0.29.0). * Fixed the registration of tasks in the Gym registry based on Gym 0.29.0 API. Changed @@ -154,7 +214,7 @@ Changed * Moved the base environment definition to the :class:`omni.isaac.lab.envs.RLEnv` class. The :class:`RLEnv` contains RL-specific managers such as the reward, termination, randomization and curriculum managers. These are all configured using the :class:`omni.isaac.lab.envs.RLEnvConfig` class. The :class:`RLEnv` class - inherits from the :class:`omni.isaac.lab.envs.BaseEnv` and ``gym.Env`` classes. + inherits from the :class:`omni.isaac.lab.envs.ManagerBasedEnv` and ``gym.Env`` classes. Fixed ^^^^^ diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/__init__.py new file mode 100644 index 0000000000..9f7914bac2 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Direct workflow environments. +""" + +import gymnasium as gym diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/__init__.py new file mode 100644 index 0000000000..4fcb158c65 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/__init__.py @@ -0,0 +1,29 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Ant locomotion environment. +""" + +import gymnasium as gym + +from . import agents +from .ant_env import AntEnv, AntEnvCfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Ant-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.ant:AntEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": AntEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.AntPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/rl_games_ppo_cfg.yaml new file mode 100644 index 0000000000..7c1c514c29 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/rl_games_ppo_cfg.yaml @@ -0,0 +1,76 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [256, 128, 64] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: ant_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: True + normalize_input: True + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.6 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 3e-4 + lr_schedule: adaptive + schedule_type: legacy + kl_threshold: 0.008 + score_to_win: 20000 + max_epochs: 500 + save_best_after: 100 + save_frequency: 50 + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 16 + minibatch_size: 32768 + mini_epochs: 4 + critic_coef: 2 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/rsl_rl_ppo_cfg.py new file mode 100644 index 0000000000..3d40a875ef --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/rsl_rl_ppo_cfg.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class AntPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 32 + max_iterations = 1000 + save_interval = 50 + experiment_name = "ant_direct" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[400, 200, 100], + critic_hidden_dims=[400, 200, 100], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.0, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=5.0e-4, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..3d1364f0a8 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [256, 128, 64] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "tanh" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [256, 128, 64] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 16 + learning_epochs: 8 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 3.e-4 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.008 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.0 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 0.6 + # logging and checkpoint + experiment: + directory: "ant_direct" + experiment_name: "" + write_interval: 40 + checkpoint_interval: 400 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 8000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/ant_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/ant_env.py new file mode 100644 index 0000000000..71d5546778 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/ant/ant_env.py @@ -0,0 +1,73 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from omni.isaac.lab_assets.ant import ANT_CFG + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import ArticulationCfg +from omni.isaac.lab.envs import DirectRLEnvCfg +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.terrains import TerrainImporterCfg +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.direct.locomotion.locomotion_env import LocomotionEnv + + +@configclass +class AntEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg(dt=1 / 120) + 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) + + # robot + robot: ArticulationCfg = ANT_CFG.replace(prim_path="/World/envs/env_.*/Robot") + joint_gears: list = [15, 15, 15, 15, 15, 15, 15, 15] + + # env + episode_length_s = 15.0 + decimation = 2 + action_scale = 0.5 + num_actions = 8 + num_observations = 36 + num_states = 0 + + 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 + + +class AntEnv(LocomotionEnv): + cfg: AntEnvCfg + + def __init__(self, cfg: AntEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/__init__.py new file mode 100644 index 0000000000..dcb5a3135e --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/__init__.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Ant locomotion environment. +""" + +import gymnasium as gym + +from . import agents +from .anymal_c_env import AnymalCEnv, AnymalCFlatEnvCfg, AnymalCRoughEnvCfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Velocity-Flat-Anymal-C-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.anymal_c:AnymalCEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": AnymalCFlatEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_flat_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.AnymalCFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", + }, +) + +gym.register( + id="Isaac-Velocity-Rough-Anymal-C-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.anymal_c:AnymalCEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": AnymalCRoughEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_rough_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.AnymalCRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/__init__.py new file mode 100644 index 0000000000..441ab975b9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_ppo_cfg diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rl_games_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rl_games_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..4bb7435f09 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rl_games_flat_ppo_cfg.yaml @@ -0,0 +1,76 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [128, 128, 128] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: anymal_c_flat_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: True + normalize_input: False + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.6 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 1e-3 + lr_schedule: adaptive + schedule_type: legacy + kl_threshold: 0.01 + score_to_win: 20000 + max_epochs: 1500 + save_best_after: 100 + save_frequency: 50 + grad_norm: 1.0 + entropy_coef: 0.005 + truncate_grads: True + e_clip: 0.2 + horizon_length: 24 + minibatch_size: 24576 + mini_epochs: 5 + critic_coef: 2.0 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rl_games_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rl_games_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..1995015eb7 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rl_games_rough_ppo_cfg.yaml @@ -0,0 +1,76 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [512, 256, 128] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: anymal_c_rough_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: True + normalize_input: False + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.6 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 1e-3 + lr_schedule: adaptive + schedule_type: legacy + kl_threshold: 0.01 + score_to_win: 20000 + max_epochs: 1500 + save_best_after: 100 + save_frequency: 50 + grad_norm: 1.0 + entropy_coef: 0.005 + truncate_grads: True + e_clip: 0.2 + horizon_length: 24 + minibatch_size: 24576 + mini_epochs: 5 + critic_coef: 2.0 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rsl_rl_ppo_cfg.py new file mode 100644 index 0000000000..d7a7fb9c5f --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/rsl_rl_ppo_cfg.py @@ -0,0 +1,70 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class AnymalCFlatPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 24 + max_iterations = 500 + save_interval = 50 + experiment_name = "anymal_c_flat_direct" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[128, 128, 128], + critic_hidden_dims=[128, 128, 128], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.005, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=1.0e-3, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) + + +@configclass +class AnymalCRoughPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 24 + max_iterations = 1500 + save_interval = 50 + experiment_name = "anymal_c_rough_direct" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[512, 256, 128], + critic_hidden_dims=[512, 256, 128], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.005, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=1.0e-3, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/agents/skrl_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/skrl_flat_ppo_cfg.yaml similarity index 66% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/agents/skrl_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/skrl_flat_ppo_cfg.yaml index c8240de34d..8f8516d6ca 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/agents/skrl_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/skrl_flat_ppo_cfg.yaml @@ -1,33 +1,33 @@ seed: 42 # Models are instantiated using skrl's model instantiator utility -# https://skrl.readthedocs.io/en/develop/modules/skrl.utils.model_instantiators.html +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models: separate: False - policy: # see skrl.utils.model_instantiators.gaussian_model for parameter details - clip_actions: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True clip_log_std: True initial_log_std: 0 min_log_std: -20.0 max_log_std: 2.0 input_shape: "Shape.STATES" hiddens: [128, 128, 128] - hidden_activation: ["elu", "elu", "elu"] + hidden_activation: ["elu", "elu"] output_shape: "Shape.ACTIONS" - output_activation: "tanh" + output_activation: "" output_scale: 1.0 - value: # see skrl.utils.model_instantiators.deterministic_model for parameter details + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details clip_actions: False input_shape: "Shape.STATES" hiddens: [128, 128, 128] - hidden_activation: ["elu", "elu", "elu"] + hidden_activation: ["elu", "elu"] output_shape: "Shape.ONE" output_activation: "" output_scale: 1.0 # PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) -# https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent: rollouts: 24 learning_epochs: 5 @@ -48,19 +48,20 @@ agent: ratio_clip: 0.2 value_clip: 0.2 clip_predicted_values: True - entropy_loss_scale: 0.0 + entropy_loss_scale: 0.005 value_loss_scale: 1.0 kl_threshold: 0 - rewards_shaper_scale: 1.0 + rewards_shaper_scale: 0.6 # logging and checkpoint experiment: - directory: "anymal" + directory: "anymal_c_flat_direct" experiment_name: "" write_interval: 60 checkpoint_interval: 600 # Sequential trainer -# https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.sequential.html +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html trainer: timesteps: 12000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..f3ef3924ea --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.005 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 0.6 + # logging and checkpoint + experiment: + directory: "anymal_c_rough_direct" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/anymal_c_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/anymal_c_env.py new file mode 100644 index 0000000000..4a7a750007 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/anymal_c/anymal_c_env.py @@ -0,0 +1,308 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import torch + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import Articulation, ArticulationCfg +from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sensors import ContactSensor, ContactSensorCfg, RayCaster, RayCasterCfg, patterns +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.terrains import TerrainImporterCfg +from omni.isaac.lab.utils import configclass + +## +# Pre-defined configs +## +from omni.isaac.lab_assets.anymal import ANYMAL_C_CFG # isort: skip +from omni.isaac.lab.terrains.config.rough import ROUGH_TERRAINS_CFG # isort: skip + + +@configclass +class AnymalCFlatEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg( + dt=1 / 200, + disable_contact_processing=True, + physics_material=sim_utils.RigidBodyMaterialCfg( + friction_combine_mode="multiply", + restitution_combine_mode="multiply", + static_friction=1.0, + dynamic_friction=1.0, + restitution=0.0, + ), + ) + terrain = TerrainImporterCfg( + 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, + restitution=0.0, + ), + debug_vis=False, + ) + + # scene + scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=4096, env_spacing=4.0, replicate_physics=True) + + # robot + robot: ArticulationCfg = ANYMAL_C_CFG.replace(prim_path="/World/envs/env_.*/Robot") + contact_sensor: ContactSensorCfg = ContactSensorCfg( + prim_path="/World/envs/env_.*/Robot/.*", history_length=3, update_period=0.005, track_air_time=True + ) + + # env + episode_length_s = 20.0 + decimation = 4 + action_scale = 0.5 + num_actions = 12 + num_observations = 48 + num_states = 0 + + # reward scales + lin_vel_reward_scale = 1.0 + yaw_rate_reward_scale = 0.5 + z_vel_reward_scale = -2.0 + ang_vel_reward_scale = -0.05 + joint_torque_reward_scale = -2.5e-5 + joint_accel_reward_scale = -2.5e-7 + action_rate_reward_scale = -0.01 + feet_air_time_reward_scale = 0.5 + undersired_contact_reward_scale = -1.0 + flat_orientation_reward_scale = -5.0 + + +@configclass +class AnymalCRoughEnvCfg(AnymalCFlatEnvCfg): + # env + num_observations = 235 + + terrain = TerrainImporterCfg( + prim_path="/World/ground", + terrain_type="generator", + terrain_generator=ROUGH_TERRAINS_CFG, + max_init_terrain_level=9, + 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, + ), + debug_vis=False, + ) + + # we add a height scanner for perceptive locomotion + height_scanner = RayCasterCfg( + prim_path="/World/envs/env_.*/Robot/base", + offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), + attach_yaw_only=True, + pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), + debug_vis=False, + mesh_prim_paths=["/World/ground"], + ) + + # reward scales (override from flat config) + flat_orientation_reward_scale = 0.0 + + +class AnymalCEnv(DirectRLEnv): + cfg: AnymalCFlatEnvCfg | AnymalCRoughEnvCfg + + def __init__(self, cfg: AnymalCFlatEnvCfg | AnymalCRoughEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) + + # Joint position command (deviation from default joint positions) + self._actions = torch.zeros(self.num_envs, self.cfg.num_actions, device=self.device) + self._previous_actions = torch.zeros(self.num_envs, self.cfg.num_actions, device=self.device) + + # X/Y linear velocity and yaw angular velocity commands + self._commands = torch.zeros(self.num_envs, 3, device=self.device) + + # Logging + self._episode_sums = { + key: torch.zeros(self.num_envs, dtype=torch.float, device=self.device) + for key in [ + "track_lin_vel_xy_exp", + "track_ang_vel_z_exp", + "lin_vel_z_l2", + "ang_vel_xy_l2", + "dof_torques_l2", + "dof_acc_l2", + "action_rate_l2", + "feet_air_time", + "undesired_contacts", + "flat_orientation_l2", + ] + } + # Get specific body indices + self._base_id, _ = self._contact_sensor.find_bodies("base") + self._feet_ids, _ = self._contact_sensor.find_bodies(".*FOOT") + self._underisred_contact_body_ids, _ = self._contact_sensor.find_bodies(".*THIGH") + + # Randomize robot friction + env_ids = self._robot._ALL_INDICES + mat_props = self._robot.root_physx_view.get_material_properties() + mat_props[:, :, :2].uniform_(0.6, 0.8) + self._robot.root_physx_view.set_material_properties(mat_props, env_ids.cpu()) + + # Randomize base mass + base_id, _ = self._robot.find_bodies("base") + masses = self._robot.root_physx_view.get_masses() + masses[:, base_id] += torch.zeros_like(masses[:, base_id]).uniform_(-5.0, 5.0) + self._robot.root_physx_view.set_masses(masses, env_ids.cpu()) + + def _setup_scene(self): + self._robot = Articulation(self.cfg.robot) + self.scene.articulations["robot"] = self._robot + self._contact_sensor = ContactSensor(self.cfg.contact_sensor) + self.scene.sensors["contact_sensor"] = self._contact_sensor + if isinstance(self.cfg, AnymalCRoughEnvCfg): + # we add a height scanner for perceptive locomotion + self._height_scanner = RayCaster(self.cfg.height_scanner) + self.scene.sensors["height_scanner"] = self._height_scanner + self.cfg.terrain.num_envs = self.scene.cfg.num_envs + self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing + self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) + # clone, filter, and replicate + self.scene.clone_environments(copy_from_source=False) + self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) + # add lights + light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) + light_cfg.func("/World/Light", light_cfg) + + def _pre_physics_step(self, actions: torch.Tensor): + self._actions = actions.clone() + self._processed_actions = self.cfg.action_scale * self._actions + self._robot.data.default_joint_pos + + def _apply_action(self): + self._robot.set_joint_position_target(self._processed_actions) + + def _get_observations(self) -> dict: + self._previous_actions = self._actions.clone() + height_data = None + if isinstance(self.cfg, AnymalCRoughEnvCfg): + height_data = ( + self._height_scanner.data.pos_w[:, 2].unsqueeze(1) - self._height_scanner.data.ray_hits_w[..., 2] - 0.5 + ).clip(-1.0, 1.0) + obs = torch.cat( + [ + tensor + for tensor in ( + self._robot.data.root_lin_vel_b, + self._robot.data.root_ang_vel_b, + self._robot.data.projected_gravity_b, + self._commands, + self._robot.data.joint_pos - self._robot.data.default_joint_pos, + self._robot.data.joint_vel, + height_data, + self._actions, + ) + if tensor is not None + ], + dim=-1, + ) + observations = {"policy": obs} + return observations + + def _get_rewards(self) -> torch.Tensor: + # linear velocity tracking + lin_vel_error = torch.sum(torch.square(self._commands[:, :2] - self._robot.data.root_lin_vel_b[:, :2]), dim=1) + lin_vel_error_mapped = torch.exp(-lin_vel_error / 0.25) + # yaw rate tracking + yaw_rate_error = torch.square(self._commands[:, 2] - self._robot.data.root_ang_vel_b[:, 2]) + yaw_rate_error_mapped = torch.exp(-yaw_rate_error / 0.25) + # z velocity tracking + z_vel_error = torch.square(self._robot.data.root_lin_vel_b[:, 2]) + # angular velocity x/y + ang_vel_error = torch.sum(torch.square(self._robot.data.root_ang_vel_b[:, :2]), dim=1) + # joint torques + joint_torques = torch.sum(torch.square(self._robot.data.applied_torque), dim=1) + # joint acceleration + joint_accel = torch.sum(torch.square(self._robot.data.joint_acc), dim=1) + # action rate + action_rate = torch.sum(torch.square(self._actions - self._previous_actions), dim=1) + # feet air time + first_contact = self._contact_sensor.compute_first_contact(self.step_dt)[:, self._feet_ids] + last_air_time = self._contact_sensor.data.last_air_time[:, self._feet_ids] + air_time = torch.sum((last_air_time - 0.5) * first_contact, dim=1) * ( + torch.norm(self._commands[:, :2], dim=1) > 0.1 + ) + # undersired contacts + net_contact_forces = self._contact_sensor.data.net_forces_w_history + is_contact = ( + torch.max(torch.norm(net_contact_forces[:, :, self._underisred_contact_body_ids], dim=-1), dim=1)[0] > 1.0 + ) + contacts = torch.sum(is_contact, dim=1) + # flat orientation + flat_orientation = torch.sum(torch.square(self._robot.data.projected_gravity_b[:, :2]), dim=1) + + rewards = { + "track_lin_vel_xy_exp": lin_vel_error_mapped * self.cfg.lin_vel_reward_scale * self.step_dt, + "track_ang_vel_z_exp": yaw_rate_error_mapped * self.cfg.yaw_rate_reward_scale * self.step_dt, + "lin_vel_z_l2": z_vel_error * self.cfg.z_vel_reward_scale * self.step_dt, + "ang_vel_xy_l2": ang_vel_error * self.cfg.ang_vel_reward_scale * self.step_dt, + "dof_torques_l2": joint_torques * self.cfg.joint_torque_reward_scale * self.step_dt, + "dof_acc_l2": joint_accel * self.cfg.joint_accel_reward_scale * self.step_dt, + "action_rate_l2": action_rate * self.cfg.action_rate_reward_scale * self.step_dt, + "feet_air_time": air_time * self.cfg.feet_air_time_reward_scale * self.step_dt, + "undesired_contacts": contacts * self.cfg.undersired_contact_reward_scale * self.step_dt, + "flat_orientation_l2": flat_orientation * self.cfg.flat_orientation_reward_scale * self.step_dt, + } + reward = torch.sum(torch.stack(list(rewards.values())), dim=0) + # Logging + for key, value in rewards.items(): + self._episode_sums[key] += value + return reward + + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + time_out = self.episode_length_buf >= self.max_episode_length - 1 + net_contact_forces = self._contact_sensor.data.net_forces_w_history + died = torch.any(torch.max(torch.norm(net_contact_forces[:, :, self._base_id], dim=-1), dim=1)[0] > 1.0, dim=1) + return died, time_out + + def _reset_idx(self, env_ids: torch.Tensor | None): + if env_ids is None or len(env_ids) == self.num_envs: + env_ids = self._robot._ALL_INDICES + self._robot.reset(env_ids) + super()._reset_idx(env_ids) + if len(env_ids) == self.num_envs: + # Spread out the resets to avoid spikes in training when many environments reset at a similar time + self.episode_length_buf[:] = torch.randint_like(self.episode_length_buf, high=int(self.max_episode_length)) + self._actions[env_ids] = 0.0 + self._previous_actions[env_ids] = 0.0 + # Sample new commands + self._commands[env_ids] = torch.zeros_like(self._commands[env_ids]).uniform_(-1.0, 1.0) + # Reset robot state + joint_pos = self._robot.data.default_joint_pos[env_ids] + joint_vel = self._robot.data.default_joint_vel[env_ids] + default_root_state = self._robot.data.default_root_state[env_ids] + default_root_state[:, :3] += self._terrain.env_origins[env_ids] + self._robot.write_root_pose_to_sim(default_root_state[:, :7], env_ids) + self._robot.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) + self._robot.write_joint_state_to_sim(joint_pos, joint_vel, None, env_ids) + # Logging + extras = dict() + for key in self._episode_sums.keys(): + episodic_sum_avg = torch.mean(self._episode_sums[key][env_ids]) + extras["Episode Reward/" + key] = episodic_sum_avg / self.max_episode_length_s + self._episode_sums[key][env_ids] = 0.0 + self.extras["log"] = dict() + self.extras["log"].update(extras) + extras = dict() + extras["Episode Termination/base_contact"] = torch.count_nonzero(self.reset_terminated[env_ids]).item() + extras["Episode Termination/time_out"] = torch.count_nonzero(self.reset_time_outs[env_ids]).item() + self.extras["log"].update(extras) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/__init__.py new file mode 100644 index 0000000000..e1328e43d0 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/__init__.py @@ -0,0 +1,51 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Cartpole balancing environment. +""" + +import gymnasium as gym + +from . import agents +from .cartpole_camera_env import CartpoleCameraEnv, CartpoleDepthCameraEnvCfg, CartpoleRGBCameraEnvCfg +from .cartpole_env import CartpoleEnv, CartpoleEnvCfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Cartpole-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.cartpole:CartpoleEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": CartpoleEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.CartpolePPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + "sb3_cfg_entry_point": f"{agents.__name__}:sb3_ppo_cfg.yaml", + }, +) + +gym.register( + id="Isaac-Cartpole-RGB-Camera-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.cartpole:CartpoleCameraEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": CartpoleRGBCameraEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_camera_ppo_cfg.yaml", + }, +) + +gym.register( + id="Isaac-Cartpole-Depth-Camera-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.cartpole:CartpoleCameraEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": CartpoleDepthCameraEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_camera_ppo_cfg.yaml", + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rl_games_camera_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rl_games_camera_ppo_cfg.yaml new file mode 100644 index 0000000000..ee5ac79b43 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rl_games_camera_ppo_cfg.yaml @@ -0,0 +1,95 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + # added to the wrapper + clip_observations: 5.0 + # can make custom wrapper? + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + # doesn't have this fine grained control but made it close + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + cnn: + type: conv2d + activation: relu + initializer: + name: default + regularizer: + name: None + convs: + - filters: 32 + kernel_size: 8 + strides: 4 + padding: 0 + - filters: 64 + kernel_size: 4 + strides: 2 + padding: 0 + - filters: 64 + kernel_size: 3 + strides: 1 + padding: 0 + + mlp: + units: [512] + activation: elu + initializer: + name: default + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: cartpole_camera_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: False + normalize_input: False + normalize_value: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 1.0 + normalize_advantage: True + gamma: 0.99 + tau : 0.95 + learning_rate: 1e-4 + lr_schedule: adaptive + kl_threshold: 0.008 + score_to_win: 20000 + max_epochs: 500 + save_best_after: 50 + save_frequency: 25 + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 64 + minibatch_size: 2048 + mini_epochs: 4 + critic_coef: 2 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rl_games_ppo_cfg.yaml new file mode 100644 index 0000000000..e78116e70c --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rl_games_ppo_cfg.yaml @@ -0,0 +1,78 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + # added to the wrapper + clip_observations: 5.0 + # can make custom wrapper? + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + # doesn't have this fine grained control but made it close + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [32, 32] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: cartpole_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: False + normalize_input: True + normalize_value: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.1 + normalize_advantage: True + gamma: 0.99 + tau : 0.95 + learning_rate: 5e-4 + lr_schedule: adaptive + kl_threshold: 0.008 + score_to_win: 20000 + max_epochs: 150 + save_best_after: 50 + save_frequency: 25 + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 32 + minibatch_size: 16384 + mini_epochs: 8 + critic_coef: 4 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rsl_rl_ppo_cfg.py new file mode 100644 index 0000000000..bc1427aec6 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/rsl_rl_ppo_cfg.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class CartpolePPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 16 + max_iterations = 150 + save_interval = 50 + experiment_name = "cartpole_direct" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[32, 32], + critic_hidden_dims=[32, 32], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.005, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=1.0e-3, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/sb3_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/sb3_ppo_cfg.yaml similarity index 96% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/sb3_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/sb3_ppo_cfg.yaml index 1ddfde6ed1..5856f35f8e 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/sb3_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/sb3_ppo_cfg.yaml @@ -18,3 +18,4 @@ policy_kwargs: "dict( )" vf_coef: 1.0 max_grad_norm: 1.0 +device: "cuda:0" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..b710916346 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [32, 32] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "tanh" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [32, 32] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 16 + learning_epochs: 8 + mini_batches: 1 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 3.e-4 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.008 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.0 + value_loss_scale: 2.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "cartpole_direct" + experiment_name: "" + write_interval: 16 + checkpoint_interval: 80 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 1600 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_camera_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_camera_env.py new file mode 100644 index 0000000000..c7c7db029a --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_camera_env.py @@ -0,0 +1,245 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import gymnasium as gym +import math +import numpy as np +import torch +from collections.abc import Sequence + +from omni.isaac.lab_assets.cartpole import CARTPOLE_CFG + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import Articulation, ArticulationCfg +from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg, ViewerCfg +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sensors import TiledCamera, TiledCameraCfg +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.sim.spawners.from_files import GroundPlaneCfg, spawn_ground_plane +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.math import sample_uniform + + +@configclass +class CartpoleRGBCameraEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg(dt=1 / 120) + + # 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" + + # camera + tiled_camera: TiledCameraCfg = TiledCameraCfg( + prim_path="/World/envs/env_.*/Camera", + offset=TiledCameraCfg.OffsetCfg(pos=(-7.0, 0.0, 3.0), rot=(0.9945, 0.0, 0.1045, 0.0), convention="world"), + data_types=["rgb"], + spawn=sim_utils.PinholeCameraCfg( + focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 20.0) + ), + width=80, + height=80, + ) + + # change viewer settings + viewer = ViewerCfg(eye=(20.0, 20.0, 20.0)) + + # scene + scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=256, env_spacing=20.0, replicate_physics=True) + + # env + decimation = 2 + episode_length_s = 5.0 + action_scale = 100.0 # [N] + num_actions = 1 + num_channels = 3 + num_observations = num_channels * tiled_camera.height * tiled_camera.width + num_states = 0 + + # reset + max_cart_pos = 3.0 # the cart is reset if it exceeds that position [m] + initial_pole_angle_range = [-0.125, 0.125] # the range in which the pole angle is sampled from on reset [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 + + +class CartpoleDepthCameraEnvCfg(CartpoleRGBCameraEnvCfg): + # camera + tiled_camera: TiledCameraCfg = TiledCameraCfg( + prim_path="/World/envs/env_.*/Camera", + offset=TiledCameraCfg.OffsetCfg(pos=(-7.0, 0.0, 3.0), rot=(0.9945, 0.0, 0.1045, 0.0), convention="world"), + data_types=["depth"], + spawn=sim_utils.PinholeCameraCfg( + focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 20.0) + ), + width=80, + height=80, + ) + + # env + num_channels = 1 + num_observations = num_channels * tiled_camera.height * tiled_camera.width + + +class CartpoleCameraEnv(DirectRLEnv): + + cfg: CartpoleRGBCameraEnvCfg | CartpoleDepthCameraEnvCfg + + def __init__( + self, cfg: CartpoleRGBCameraEnvCfg | CartpoleDepthCameraEnvCfg, render_mode: str | None = None, **kwargs + ): + super().__init__(cfg, render_mode, **kwargs) + + self._cart_dof_idx, _ = self._cartpole.find_joints(self.cfg.cart_dof_name) + self._pole_dof_idx, _ = self._cartpole.find_joints(self.cfg.pole_dof_name) + self.action_scale = self.cfg.action_scale + + self.joint_pos = self._cartpole.data.joint_pos + self.joint_vel = self._cartpole.data.joint_vel + + if len(self.cfg.tiled_camera.data_types) != 1: + raise ValueError( + "The Cartpole camera environment only supports one image type at a time but the following were" + f" provided: {self.cfg.tiled_camera.data_types}" + ) + + def close(self): + """Cleanup for the environment.""" + super().close() + + def _configure_gym_env_spaces(self): + """Configure the action and observation spaces for the Gym environment.""" + # observation space (unbounded since we don't impose any limits) + self.num_actions = self.cfg.num_actions + self.num_observations = self.cfg.num_observations + self.num_states = self.cfg.num_states + + # set up spaces + self.single_observation_space = gym.spaces.Dict() + self.single_observation_space["policy"] = gym.spaces.Box( + low=-np.inf, + high=np.inf, + shape=(self.cfg.tiled_camera.height, self.cfg.tiled_camera.width, self.cfg.num_channels), + ) + if self.num_states > 0: + self.single_observation_space["critic"] = gym.spaces.Box( + low=-np.inf, + high=np.inf, + shape=(self.cfg.tiled_camera.height, self.cfg.tiled_camera.width, self.cfg.num_channels), + ) + self.single_action_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.num_actions,)) + + # batch the spaces for vectorized environments + self.observation_space = gym.vector.utils.batch_space(self.single_observation_space, self.num_envs) + self.action_space = gym.vector.utils.batch_space(self.single_action_space, self.num_envs) + + # RL specifics + self.actions = torch.zeros(self.num_envs, self.num_actions, device=self.sim.device) + + def _setup_scene(self): + """Setup the scene with the cartpole and camera.""" + self._cartpole = Articulation(self.cfg.robot_cfg) + self._tiled_camera = TiledCamera(self.cfg.tiled_camera) + # add ground plane + spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg(size=(500, 500))) + # clone, filter, and replicate + self.scene.clone_environments(copy_from_source=False) + self.scene.filter_collisions(global_prim_paths=[]) + + # add articultion and sensors to scene + self.scene.articulations["cartpole"] = self._cartpole + self.scene.sensors["tiled_camera"] = self._tiled_camera + # add lights + light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) + light_cfg.func("/World/Light", light_cfg) + + def _pre_physics_step(self, actions: torch.Tensor) -> None: + self.actions = self.action_scale * actions.clone() + + def _apply_action(self) -> None: + self._cartpole.set_joint_effort_target(self.actions, joint_ids=self._cart_dof_idx) + + def _get_observations(self) -> dict: + data_type = "rgb" if "rgb" in self.cfg.tiled_camera.data_types else "depth" + observations = {"policy": self._tiled_camera.data.output[data_type].clone()} + return observations + + def _get_rewards(self) -> torch.Tensor: + total_reward = compute_rewards( + self.cfg.rew_scale_alive, + self.cfg.rew_scale_terminated, + self.cfg.rew_scale_pole_pos, + self.cfg.rew_scale_cart_vel, + self.cfg.rew_scale_pole_vel, + self.joint_pos[:, self._pole_dof_idx[0]], + self.joint_vel[:, self._pole_dof_idx[0]], + self.joint_pos[:, self._cart_dof_idx[0]], + self.joint_vel[:, self._cart_dof_idx[0]], + self.reset_terminated, + ) + return total_reward + + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + self.joint_pos = self._cartpole.data.joint_pos + self.joint_vel = self._cartpole.data.joint_vel + + time_out = self.episode_length_buf >= self.max_episode_length - 1 + out_of_bounds = torch.any(torch.abs(self.joint_pos[:, self._cart_dof_idx]) > self.cfg.max_cart_pos, dim=1) + out_of_bounds = out_of_bounds | torch.any(torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2, dim=1) + return out_of_bounds, time_out + + def _reset_idx(self, env_ids: Sequence[int] | None): + if env_ids is None: + env_ids = self._cartpole._ALL_INDICES + super()._reset_idx(env_ids) + + joint_pos = self._cartpole.data.default_joint_pos[env_ids] + joint_pos[:, self._pole_dof_idx] += sample_uniform( + self.cfg.initial_pole_angle_range[0] * math.pi, + self.cfg.initial_pole_angle_range[1] * math.pi, + joint_pos[:, self._pole_dof_idx].shape, + joint_pos.device, + ) + joint_vel = self._cartpole.data.default_joint_vel[env_ids] + + default_root_state = self._cartpole.data.default_root_state[env_ids] + default_root_state[:, :3] += self.scene.env_origins[env_ids] + + self.joint_pos[env_ids] = joint_pos + self.joint_vel[env_ids] = joint_vel + + self._cartpole.write_root_pose_to_sim(default_root_state[:, :7], env_ids) + self._cartpole.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) + self._cartpole.write_joint_state_to_sim(joint_pos, joint_vel, None, env_ids) + + +@torch.jit.script +def compute_rewards( + rew_scale_alive: float, + rew_scale_terminated: float, + rew_scale_pole_pos: float, + rew_scale_cart_vel: float, + rew_scale_pole_vel: float, + pole_pos: torch.Tensor, + pole_vel: torch.Tensor, + cart_pos: torch.Tensor, + cart_vel: torch.Tensor, + reset_terminated: torch.Tensor, +): + rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) + rew_termination = rew_scale_terminated * reset_terminated.float() + rew_pole_pos = rew_scale_pole_pos * torch.sum(torch.square(pole_pos).unsqueeze(dim=1), dim=-1) + rew_cart_vel = rew_scale_cart_vel * torch.sum(torch.abs(cart_vel).unsqueeze(dim=1), dim=-1) + rew_pole_vel = rew_scale_pole_vel * torch.sum(torch.abs(pole_vel).unsqueeze(dim=1), dim=-1) + total_reward = rew_alive + rew_termination + rew_pole_pos + rew_cart_vel + rew_pole_vel + return total_reward diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py new file mode 100644 index 0000000000..f6ae6e36fb --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py @@ -0,0 +1,170 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import math +import torch +from collections.abc import Sequence + +from omni.isaac.lab_assets.cartpole import CARTPOLE_CFG + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import Articulation, ArticulationCfg +from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.sim.spawners.from_files import GroundPlaneCfg, spawn_ground_plane +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.math import sample_uniform + + +@configclass +class CartpoleEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg(dt=1 / 120) + + # 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) + + # env + decimation = 2 + episode_length_s = 5.0 + action_scale = 100.0 # [N] + num_actions = 1 + num_observations = 4 + num_states = 0 + + # 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 [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 + + +class CartpoleEnv(DirectRLEnv): + cfg: CartpoleEnvCfg + + def __init__(self, cfg: CartpoleEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) + + self._cart_dof_idx, _ = self.cartpole.find_joints(self.cfg.cart_dof_name) + self._pole_dof_idx, _ = self.cartpole.find_joints(self.cfg.pole_dof_name) + self.action_scale = self.cfg.action_scale + + self.joint_pos = self.cartpole.data.joint_pos + self.joint_vel = self.cartpole.data.joint_vel + + def _setup_scene(self): + self.cartpole = Articulation(self.cfg.robot_cfg) + # add ground plane + spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg()) + # clone, filter, and replicate + self.scene.clone_environments(copy_from_source=False) + self.scene.filter_collisions(global_prim_paths=[]) + # add articultion 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) + + def _pre_physics_step(self, actions: torch.Tensor) -> None: + self.actions = self.action_scale * actions.clone() + + def _apply_action(self) -> None: + self.cartpole.set_joint_effort_target(self.actions, joint_ids=self._cart_dof_idx) + + def _get_observations(self) -> dict: + obs = torch.cat( + ( + self.joint_pos[:, self._pole_dof_idx[0]].unsqueeze(dim=1), + self.joint_vel[:, self._pole_dof_idx[0]].unsqueeze(dim=1), + self.joint_pos[:, self._cart_dof_idx[0]].unsqueeze(dim=1), + self.joint_vel[:, self._cart_dof_idx[0]].unsqueeze(dim=1), + ), + dim=-1, + ) + observations = {"policy": obs} + return observations + + def _get_rewards(self) -> torch.Tensor: + total_reward = compute_rewards( + self.cfg.rew_scale_alive, + self.cfg.rew_scale_terminated, + self.cfg.rew_scale_pole_pos, + self.cfg.rew_scale_cart_vel, + self.cfg.rew_scale_pole_vel, + self.joint_pos[:, self._pole_dof_idx[0]], + self.joint_vel[:, self._pole_dof_idx[0]], + self.joint_pos[:, self._cart_dof_idx[0]], + self.joint_vel[:, self._cart_dof_idx[0]], + self.reset_terminated, + ) + return total_reward + + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + self.joint_pos = self.cartpole.data.joint_pos + self.joint_vel = self.cartpole.data.joint_vel + + time_out = self.episode_length_buf >= self.max_episode_length - 1 + out_of_bounds = torch.any(torch.abs(self.joint_pos[:, self._cart_dof_idx]) > self.cfg.max_cart_pos, dim=1) + out_of_bounds = out_of_bounds | torch.any(torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2, dim=1) + return out_of_bounds, time_out + + def _reset_idx(self, env_ids: Sequence[int] | None): + if env_ids is None: + env_ids = self.cartpole._ALL_INDICES + super()._reset_idx(env_ids) + + joint_pos = self.cartpole.data.default_joint_pos[env_ids] + joint_pos[:, self._pole_dof_idx] += sample_uniform( + self.cfg.initial_pole_angle_range[0] * math.pi, + self.cfg.initial_pole_angle_range[1] * math.pi, + joint_pos[:, self._pole_dof_idx].shape, + joint_pos.device, + ) + joint_vel = self.cartpole.data.default_joint_vel[env_ids] + + default_root_state = self.cartpole.data.default_root_state[env_ids] + default_root_state[:, :3] += self.scene.env_origins[env_ids] + + self.joint_pos[env_ids] = joint_pos + self.joint_vel[env_ids] = joint_vel + + self.cartpole.write_root_pose_to_sim(default_root_state[:, :7], env_ids) + self.cartpole.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) + self.cartpole.write_joint_state_to_sim(joint_pos, joint_vel, None, env_ids) + + +@torch.jit.script +def compute_rewards( + rew_scale_alive: float, + rew_scale_terminated: float, + rew_scale_pole_pos: float, + rew_scale_cart_vel: float, + rew_scale_pole_vel: float, + pole_pos: torch.Tensor, + pole_vel: torch.Tensor, + cart_pos: torch.Tensor, + cart_vel: torch.Tensor, + reset_terminated: torch.Tensor, +): + rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) + rew_termination = rew_scale_terminated * reset_terminated.float() + rew_pole_pos = rew_scale_pole_pos * torch.sum(torch.square(pole_pos).unsqueeze(dim=1), dim=-1) + rew_cart_vel = rew_scale_cart_vel * torch.sum(torch.abs(cart_vel).unsqueeze(dim=1), dim=-1) + rew_pole_vel = rew_scale_pole_vel * torch.sum(torch.abs(pole_vel).unsqueeze(dim=1), dim=-1) + total_reward = rew_alive + rew_termination + rew_pole_pos + rew_cart_vel + rew_pole_vel + return total_reward diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/__init__.py new file mode 100644 index 0000000000..9cb1ff9b30 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/__init__.py @@ -0,0 +1,28 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +""" +Franka-Cabinet environment. +""" + +import gymnasium as gym + +from . import agents +from .franka_cabinet_env import FrankaCabinetEnv, FrankaCabinetEnvCfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Franka-Cabinet-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.franka_cabinet:FrankaCabinetEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": FrankaCabinetEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.FrankaCabinetPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/__init__.py new file mode 100644 index 0000000000..441ab975b9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_ppo_cfg diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/rl_games_ppo_cfg.yaml new file mode 100644 index 0000000000..dd9cb448ae --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/rl_games_ppo_cfg.yaml @@ -0,0 +1,75 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [256, 128, 64] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: franka_cabinet_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: False + normalize_input: True + normalize_value: True + # value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.01 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 5e-4 + lr_schedule: adaptive + kl_threshold: 0.008 + score_to_win: 100000000 + max_epochs: 1500 + save_best_after: 200 + save_frequency: 100 + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 16 + minibatch_size: 8192 + mini_epochs: 8 + critic_coef: 4 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/rsl_rl_ppo_cfg.py new file mode 100644 index 0000000000..b3cffb229f --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/rsl_rl_ppo_cfg.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class FrankaCabinetPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 16 + max_iterations = 1500 + save_interval = 50 + experiment_name = "franka_cabinet_direct" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[256, 128, 64], + critic_hidden_dims=[256, 128, 64], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.0, + num_learning_epochs=8, + num_mini_batches=8, + learning_rate=5.0e-4, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.008, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..99a9473db3 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/develop/api/utils/model_instantiators.html +models: + separate: False + policy: # see 'skrl.utils.model_instantiators.torch.gaussian_model' for parameter details + clip_actions: False + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [256, 128, 64] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see 'skrl.utils.model_instantiators.torch.deterministic_model' for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [256, 128, 64] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 16 + learning_epochs: 8 + mini_batches: 8 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 5.e-4 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.008 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.0 + value_loss_scale: 2.0 + kl_threshold: 0 + rewards_shaper_scale: 0.01 + # logging and checkpoint + experiment: + directory: "franka_cabinet_direct" + experiment_name: "" + write_interval: 120 + checkpoint_interval: 1200 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 24000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/franka_cabinet_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/franka_cabinet_env.py new file mode 100644 index 0000000000..9802e2a6af --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/franka_cabinet/franka_cabinet_env.py @@ -0,0 +1,522 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import torch + +from omni.isaac.core.utils.stage import get_current_stage +from omni.isaac.core.utils.torch.transformations import tf_combine, tf_inverse, tf_vector +from pxr import UsdGeom + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.actuators.actuator_cfg import ImplicitActuatorCfg +from omni.isaac.lab.assets import Articulation, ArticulationCfg +from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.terrains import TerrainImporterCfg +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR +from omni.isaac.lab.utils.math import sample_uniform + + +@configclass +class FrankaCabinetEnvCfg(DirectRLEnvCfg): + + # env + episode_length_s = 8.3333 # 500 timesteps + decimation = 2 + num_actions = 9 + num_observations = 23 + num_states = 0 + + action_scale = 7.5 + dof_velocity_scale = 0.1 + + # simulation + sim: SimulationCfg = SimulationCfg( + dt=1 / 120, + disable_contact_processing=True, + physics_material=sim_utils.RigidBodyMaterialCfg( + friction_combine_mode="multiply", + restitution_combine_mode="multiply", + static_friction=1.0, + dynamic_friction=1.0, + restitution=0.0, + ), + ) + + # scene + scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=4096, env_spacing=3.0, replicate_physics=True) + + # robot + robot = ArticulationCfg( + prim_path="/World/envs/env_.*/Robot", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Franka/franka_instanceable.usd", + activate_contact_sensors=False, + rigid_props=sim_utils.RigidBodyPropertiesCfg( + disable_gravity=False, + max_depenetration_velocity=5.0, + ), + articulation_props=sim_utils.ArticulationRootPropertiesCfg( + enabled_self_collisions=False, solver_position_iteration_count=12, solver_velocity_iteration_count=1 + ), + ), + init_state=ArticulationCfg.InitialStateCfg( + joint_pos={ + "panda_joint1": 1.157, + "panda_joint2": -1.066, + "panda_joint3": -0.155, + "panda_joint4": -2.239, + "panda_joint5": -1.841, + "panda_joint6": 1.003, + "panda_joint7": 0.469, + "panda_finger_joint.*": 0.035, + }, + pos=(1.0, 0.0, 0.0), + rot=(0.0, 0.0, 0.0, 1.0), + ), + actuators={ + "panda_shoulder": ImplicitActuatorCfg( + joint_names_expr=["panda_joint[1-4]"], + effort_limit=87.0, + velocity_limit=2.175, + stiffness=80.0, + damping=4.0, + ), + "panda_forearm": ImplicitActuatorCfg( + joint_names_expr=["panda_joint[5-7]"], + effort_limit=12.0, + velocity_limit=2.61, + stiffness=80.0, + damping=4.0, + ), + "panda_hand": ImplicitActuatorCfg( + joint_names_expr=["panda_finger_joint.*"], + effort_limit=200.0, + velocity_limit=0.2, + stiffness=2e3, + damping=1e2, + ), + }, + ) + + # cabinet + cabinet = ArticulationCfg( + prim_path="/World/envs/env_.*/Cabinet", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Sektion_Cabinet/sektion_cabinet_instanceable.usd", + activate_contact_sensors=False, + ), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0, 0.4), + rot=(0.1, 0.0, 0.0, 0.0), + joint_pos={ + "door_left_joint": 0.0, + "door_right_joint": 0.0, + "drawer_bottom_joint": 0.0, + "drawer_top_joint": 0.0, + }, + ), + actuators={ + "drawers": ImplicitActuatorCfg( + joint_names_expr=["drawer_top_joint", "drawer_bottom_joint"], + effort_limit=87.0, + velocity_limit=100.0, + stiffness=10.0, + damping=1.0, + ), + "doors": ImplicitActuatorCfg( + joint_names_expr=["door_left_joint", "door_right_joint"], + effort_limit=87.0, + velocity_limit=100.0, + stiffness=10.0, + damping=2.5, + ), + }, + ) + + # ground plane + terrain = TerrainImporterCfg( + 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, + restitution=0.0, + ), + ) + + # reward scales + dist_reward_scale = 2.0 + rot_reward_scale = 0.5 + around_handle_reward_scale = 0.0 + open_reward_scale = 7.5 + action_penalty_scale = 0.01 + finger_dist_reward_scale = 0.0 + finger_close_reward_scale = 10.0 + + +class FrankaCabinetEnv(DirectRLEnv): + # pre-physics step calls + # |-- _pre_physics_step(action) + # |-- _apply_action() + # post-physics step calls + # |-- _get_dones() + # |-- _get_rewards() + # |-- _reset_idx(env_ids) + # |-- _get_observations() + + cfg: FrankaCabinetEnvCfg + + def __init__(self, cfg: FrankaCabinetEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) + + def get_env_local_pose(env_pos: torch.Tensor, xformable: UsdGeom.Xformable, device: torch.device): + """Compute pose in env-local coordinates""" + world_transform = xformable.ComputeLocalToWorldTransform(0) + world_pos = world_transform.ExtractTranslation() + world_quat = world_transform.ExtractRotationQuat() + + px = world_pos[0] - env_pos[0] + py = world_pos[1] - env_pos[1] + pz = world_pos[2] - env_pos[2] + qx = world_quat.imaginary[0] + qy = world_quat.imaginary[1] + qz = world_quat.imaginary[2] + qw = world_quat.real + + return torch.tensor([px, py, pz, qw, qx, qy, qz], device=device) + + self.dt = self.cfg.sim.dt * self.cfg.decimation + + # create auxiliary variables for computing applied action, observations and rewards + self.robot_dof_lower_limits = self._robot.data.soft_joint_pos_limits[0, :, 0].to(device=self.device) + self.robot_dof_upper_limits = self._robot.data.soft_joint_pos_limits[0, :, 1].to(device=self.device) + + self.robot_dof_speed_scales = torch.ones_like(self.robot_dof_lower_limits) + self.robot_dof_speed_scales[self._robot.find_joints("panda_finger_joint1")[0]] = 0.1 + self.robot_dof_speed_scales[self._robot.find_joints("panda_finger_joint2")[0]] = 0.1 + + self.robot_dof_targets = torch.zeros((self.num_envs, self._robot.num_joints), device=self.device) + + stage = get_current_stage() + hand_pose = get_env_local_pose( + self.scene.env_origins[0], + UsdGeom.Xformable(stage.GetPrimAtPath("/World/envs/env_0/Robot/panda_link7")), + self.device, + ) + lfinger_pose = get_env_local_pose( + self.scene.env_origins[0], + UsdGeom.Xformable(stage.GetPrimAtPath("/World/envs/env_0/Robot/panda_leftfinger")), + self.device, + ) + rfinger_pose = get_env_local_pose( + self.scene.env_origins[0], + UsdGeom.Xformable(stage.GetPrimAtPath("/World/envs/env_0/Robot/panda_rightfinger")), + self.device, + ) + + finger_pose = torch.zeros(7, device=self.device) + finger_pose[0:3] = (lfinger_pose[0:3] + rfinger_pose[0:3]) / 2.0 + finger_pose[3:7] = lfinger_pose[3:7] + hand_pose_inv_rot, hand_pose_inv_pos = tf_inverse(hand_pose[3:7], hand_pose[0:3]) + + robot_local_grasp_pose_rot, robot_local_pose_pos = tf_combine( + hand_pose_inv_rot, hand_pose_inv_pos, finger_pose[3:7], finger_pose[0:3] + ) + robot_local_pose_pos += torch.tensor([0, 0.04, 0], device=self.device) + self.robot_local_grasp_pos = robot_local_pose_pos.repeat((self.num_envs, 1)) + self.robot_local_grasp_rot = robot_local_grasp_pose_rot.repeat((self.num_envs, 1)) + + drawer_local_grasp_pose = torch.tensor([0.3, 0.01, 0.0, 1.0, 0.0, 0.0, 0.0], device=self.device) + self.drawer_local_grasp_pos = drawer_local_grasp_pose[0:3].repeat((self.num_envs, 1)) + self.drawer_local_grasp_rot = drawer_local_grasp_pose[3:7].repeat((self.num_envs, 1)) + + self.gripper_forward_axis = torch.tensor([0, 0, 1], device=self.device, dtype=torch.float32).repeat( + (self.num_envs, 1) + ) + self.drawer_inward_axis = torch.tensor([-1, 0, 0], device=self.device, dtype=torch.float32).repeat( + (self.num_envs, 1) + ) + self.gripper_up_axis = torch.tensor([0, 1, 0], device=self.device, dtype=torch.float32).repeat( + (self.num_envs, 1) + ) + self.drawer_up_axis = torch.tensor([0, 0, 1], device=self.device, dtype=torch.float32).repeat( + (self.num_envs, 1) + ) + + self.hand_link_idx = self._robot.find_bodies("panda_link7")[0][0] + self.left_finger_link_idx = self._robot.find_bodies("panda_leftfinger")[0][0] + self.right_finger_link_idx = self._robot.find_bodies("panda_rightfinger")[0][0] + self.drawer_link_idx = self._cabinet.find_bodies("drawer_top")[0][0] + + self.robot_grasp_rot = torch.zeros((self.num_envs, 4), device=self.device) + self.robot_grasp_pos = torch.zeros((self.num_envs, 3), device=self.device) + self.drawer_grasp_rot = torch.zeros((self.num_envs, 4), device=self.device) + self.drawer_grasp_pos = torch.zeros((self.num_envs, 3), device=self.device) + + def _setup_scene(self): + self._robot = Articulation(self.cfg.robot) + self._cabinet = Articulation(self.cfg.cabinet) + self.scene.articulations["robot"] = self._robot + self.scene.articulations["cabinet"] = self._cabinet + + self.cfg.terrain.num_envs = self.scene.cfg.num_envs + self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing + self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) + + # clone, filter, and replicate + self.scene.clone_environments(copy_from_source=False) + self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) + + # add lights + light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) + light_cfg.func("/World/Light", light_cfg) + + # pre-physics step calls + + def _pre_physics_step(self, actions: torch.Tensor): + self.actions = actions.clone().clamp(-1.0, 1.0) + targets = self.robot_dof_targets + self.robot_dof_speed_scales * self.dt * self.actions * self.cfg.action_scale + self.robot_dof_targets[:] = torch.clamp(targets, self.robot_dof_lower_limits, self.robot_dof_upper_limits) + + def _apply_action(self): + self._robot.set_joint_position_target(self.robot_dof_targets) + + # post-physics step calls + + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + terminated = self._cabinet.data.joint_pos[:, 3] > 0.39 + truncated = self.episode_length_buf >= self.max_episode_length - 1 + return terminated, truncated + + def _get_rewards(self) -> torch.Tensor: + # Refresh the intermediate values after the physics steps + self._compute_intermediate_values() + robot_left_finger_pos = self._robot.data.body_pos_w[:, self.left_finger_link_idx] + robot_right_finger_pos = self._robot.data.body_pos_w[:, self.right_finger_link_idx] + + return self._compute_rewards( + self.actions, + self._cabinet.data.joint_pos, + self.robot_grasp_pos, + self.drawer_grasp_pos, + self.robot_grasp_rot, + self.drawer_grasp_rot, + robot_left_finger_pos, + robot_right_finger_pos, + self.gripper_forward_axis, + self.drawer_inward_axis, + self.gripper_up_axis, + self.drawer_up_axis, + self.num_envs, + self.cfg.dist_reward_scale, + self.cfg.rot_reward_scale, + self.cfg.around_handle_reward_scale, + self.cfg.open_reward_scale, + self.cfg.finger_dist_reward_scale, + self.cfg.action_penalty_scale, + self._robot.data.joint_pos, + self.cfg.finger_close_reward_scale, + ) + + def _reset_idx(self, env_ids: torch.Tensor | None): + super()._reset_idx(env_ids) + # robot state + joint_pos = self._robot.data.default_joint_pos[env_ids] + sample_uniform( + -0.125, + 0.125, + (len(env_ids), self._robot.num_joints), + self.device, + ) + joint_pos = torch.clamp(joint_pos, self.robot_dof_lower_limits, self.robot_dof_upper_limits) + joint_vel = torch.zeros_like(joint_pos) + self._robot.set_joint_position_target(joint_pos, env_ids=env_ids) + self._robot.write_joint_state_to_sim(joint_pos, joint_vel, env_ids=env_ids) + + # cabinet state + zeros = torch.zeros((len(env_ids), self._cabinet.num_joints), device=self.device) + self._cabinet.write_joint_state_to_sim(zeros, zeros, env_ids=env_ids) + + # Need to refresh the intermediate values so that _get_observations() can use the latest values + self._compute_intermediate_values(env_ids) + + def _get_observations(self) -> dict: + dof_pos_scaled = ( + 2.0 + * (self._robot.data.joint_pos - self.robot_dof_lower_limits) + / (self.robot_dof_upper_limits - self.robot_dof_lower_limits) + - 1.0 + ) + to_target = self.drawer_grasp_pos - self.robot_grasp_pos + + obs = torch.cat( + ( + dof_pos_scaled, + self._robot.data.joint_vel * self.cfg.dof_velocity_scale, + to_target, + self._cabinet.data.joint_pos[:, 3].unsqueeze(-1), + self._cabinet.data.joint_vel[:, 3].unsqueeze(-1), + ), + dim=-1, + ) + return {"policy": torch.clamp(obs, -5.0, 5.0)} + + # auxiliary methods + + def _compute_intermediate_values(self, env_ids: torch.Tensor | None = None): + if env_ids is None: + env_ids = self._robot._ALL_INDICES + + hand_pos = self._robot.data.body_pos_w[env_ids, self.hand_link_idx] + hand_rot = self._robot.data.body_quat_w[env_ids, self.hand_link_idx] + drawer_pos = self._cabinet.data.body_pos_w[env_ids, self.drawer_link_idx] + drawer_rot = self._cabinet.data.body_quat_w[env_ids, self.drawer_link_idx] + ( + self.robot_grasp_rot[env_ids], + self.robot_grasp_pos[env_ids], + self.drawer_grasp_rot[env_ids], + self.drawer_grasp_pos[env_ids], + ) = self._compute_grasp_transforms( + hand_rot, + hand_pos, + self.robot_local_grasp_rot[env_ids], + self.robot_local_grasp_pos[env_ids], + drawer_rot, + drawer_pos, + self.drawer_local_grasp_rot[env_ids], + self.drawer_local_grasp_pos[env_ids], + ) + + def _compute_rewards( + self, + actions, + cabinet_dof_pos, + franka_grasp_pos, + drawer_grasp_pos, + franka_grasp_rot, + drawer_grasp_rot, + franka_lfinger_pos, + franka_rfinger_pos, + gripper_forward_axis, + drawer_inward_axis, + gripper_up_axis, + drawer_up_axis, + num_envs, + dist_reward_scale, + rot_reward_scale, + around_handle_reward_scale, + open_reward_scale, + finger_dist_reward_scale, + action_penalty_scale, + joint_positions, + finger_close_reward_scale, + ): + # distance from hand to the drawer + d = torch.norm(franka_grasp_pos - drawer_grasp_pos, p=2, dim=-1) + dist_reward = 1.0 / (1.0 + d**2) + dist_reward *= dist_reward + dist_reward = torch.where(d <= 0.02, dist_reward * 2, dist_reward) + + axis1 = tf_vector(franka_grasp_rot, gripper_forward_axis) + axis2 = tf_vector(drawer_grasp_rot, drawer_inward_axis) + axis3 = tf_vector(franka_grasp_rot, gripper_up_axis) + axis4 = tf_vector(drawer_grasp_rot, drawer_up_axis) + + dot1 = ( + torch.bmm(axis1.view(num_envs, 1, 3), axis2.view(num_envs, 3, 1)).squeeze(-1).squeeze(-1) + ) # alignment of forward axis for gripper + dot2 = ( + torch.bmm(axis3.view(num_envs, 1, 3), axis4.view(num_envs, 3, 1)).squeeze(-1).squeeze(-1) + ) # alignment of up axis for gripper + # reward for matching the orientation of the hand to the drawer (fingers wrapped) + rot_reward = 0.5 * (torch.sign(dot1) * dot1**2 + torch.sign(dot2) * dot2**2) + + # bonus if left finger is above the drawer handle and right below + around_handle_reward = torch.zeros_like(rot_reward) + around_handle_reward = torch.where( + franka_lfinger_pos[:, 2] > drawer_grasp_pos[:, 2], + torch.where( + franka_rfinger_pos[:, 2] < drawer_grasp_pos[:, 2], around_handle_reward + 0.5, around_handle_reward + ), + around_handle_reward, + ) + # reward for distance of each finger from the drawer + finger_dist_reward = torch.zeros_like(rot_reward) + lfinger_dist = torch.abs(franka_lfinger_pos[:, 2] - drawer_grasp_pos[:, 2]) + rfinger_dist = torch.abs(franka_rfinger_pos[:, 2] - drawer_grasp_pos[:, 2]) + finger_dist_reward = torch.where( + franka_lfinger_pos[:, 2] > drawer_grasp_pos[:, 2], + torch.where( + franka_rfinger_pos[:, 2] < drawer_grasp_pos[:, 2], + (0.04 - lfinger_dist) + (0.04 - rfinger_dist), + finger_dist_reward, + ), + finger_dist_reward, + ) + + finger_close_reward = torch.zeros_like(rot_reward) + finger_close_reward = torch.where( + d <= 0.03, (0.04 - joint_positions[:, 7]) + (0.04 - joint_positions[:, 8]), finger_close_reward + ) + + # regularization on the actions (summed for each environment) + action_penalty = torch.sum(actions**2, dim=-1) + + # how far the cabinet has been opened out + open_reward = cabinet_dof_pos[:, 3] * around_handle_reward + cabinet_dof_pos[:, 3] # drawer_top_joint + + rewards = ( + dist_reward_scale * dist_reward + + rot_reward_scale * rot_reward + + around_handle_reward_scale * around_handle_reward + + open_reward_scale * open_reward + + finger_dist_reward_scale * finger_dist_reward + - action_penalty_scale * action_penalty + + finger_close_reward * finger_close_reward_scale + ) + + self.extras["log"] = { + "dist_reward": (dist_reward_scale * dist_reward).mean(), + "rot_reward": (rot_reward_scale * rot_reward).mean(), + "around_handle_reward": (around_handle_reward_scale * around_handle_reward).mean(), + "open_reward": (open_reward_scale * open_reward).mean(), + "finger_dist_reward": (finger_dist_reward_scale * finger_dist_reward).mean(), + "action_penalty": (action_penalty_scale * action_penalty).mean(), + "finger_close_reward": (finger_close_reward * finger_close_reward_scale).mean(), + } + + # bonus for opening drawer properly + rewards = torch.where(cabinet_dof_pos[:, 3] > 0.01, rewards + 0.5, rewards) + rewards = torch.where(cabinet_dof_pos[:, 3] > 0.2, rewards + around_handle_reward, rewards) + rewards = torch.where(cabinet_dof_pos[:, 3] > 0.39, rewards + (2.0 * around_handle_reward), rewards) + + return rewards + + def _compute_grasp_transforms( + self, + hand_rot, + hand_pos, + franka_local_grasp_rot, + franka_local_grasp_pos, + drawer_rot, + drawer_pos, + drawer_local_grasp_rot, + drawer_local_grasp_pos, + ): + global_franka_rot, global_franka_pos = tf_combine( + hand_rot, hand_pos, franka_local_grasp_rot, franka_local_grasp_pos + ) + global_drawer_rot, global_drawer_pos = tf_combine( + drawer_rot, drawer_pos, drawer_local_grasp_rot, drawer_local_grasp_pos + ) + + return global_franka_rot, global_franka_pos, global_drawer_rot, global_drawer_pos diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/__init__.py new file mode 100644 index 0000000000..9ef64fc171 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/__init__.py @@ -0,0 +1,29 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Humanoid locomotion environment. +""" + +import gymnasium as gym + +from . import agents +from .humanoid_env import HumanoidEnv, HumanoidEnvCfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Humanoid-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.humanoid:HumanoidEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": HumanoidEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.HumanoidPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/__init__.py new file mode 100644 index 0000000000..441ab975b9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_ppo_cfg diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/rl_games_ppo_cfg.yaml new file mode 100644 index 0000000000..0b54af26e5 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/rl_games_ppo_cfg.yaml @@ -0,0 +1,75 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [400, 200, 100] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: humanoid_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: True + normalize_input: True + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.01 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 5e-4 + lr_schedule: adaptive + kl_threshold: 0.008 + score_to_win: 20000 + max_epochs: 1000 + save_best_after: 100 + save_frequency: 50 + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 32 + minibatch_size: 32768 + mini_epochs: 5 + critic_coef: 4 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/rsl_rl_ppo_cfg.py new file mode 100644 index 0000000000..50e11839c4 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/rsl_rl_ppo_cfg.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class HumanoidPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 32 + max_iterations = 1000 + save_interval = 50 + experiment_name = "humanoid_direct" + empirical_normalization = True + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[400, 200, 100], + critic_hidden_dims=[400, 200, 100], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.0, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=1.0e-4, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.008, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..559ed6d659 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [400, 200, 100] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "tanh" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [400, 200, 100] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 32 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-4 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.008 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.0 + value_loss_scale: 2.0 + kl_threshold: 0 + rewards_shaper_scale: 0.01 + # logging and checkpoint + experiment: + directory: "humanoid_direct" + experiment_name: "" + write_interval: 160 + checkpoint_interval: 1600 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 32000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/humanoid_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/humanoid_env.py new file mode 100644 index 0000000000..9320847770 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/humanoid/humanoid_env.py @@ -0,0 +1,95 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from omni.isaac.lab_assets import HUMANOID_CFG + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import ArticulationCfg +from omni.isaac.lab.envs import DirectRLEnvCfg +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.terrains import TerrainImporterCfg +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.direct.locomotion.locomotion_env import LocomotionEnv + + +@configclass +class HumanoidEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg(dt=1 / 120) + 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) + + # robot + robot: ArticulationCfg = HUMANOID_CFG.replace(prim_path="/World/envs/env_.*/Robot") + joint_gears: list = [ + 67.5000, # lower_waist + 67.5000, # lower_waist + 67.5000, # right_upper_arm + 67.5000, # right_upper_arm + 67.5000, # left_upper_arm + 67.5000, # left_upper_arm + 67.5000, # pelvis + 45.0000, # right_lower_arm + 45.0000, # left_lower_arm + 45.0000, # right_thigh: x + 135.0000, # right_thigh: y + 45.0000, # right_thigh: z + 45.0000, # left_thigh: x + 135.0000, # left_thigh: y + 45.0000, # left_thigh: z + 90.0000, # right_knee + 90.0000, # left_knee + 22.5, # right_foot + 22.5, # right_foot + 22.5, # left_foot + 22.5, # left_foot + ] + + # env + episode_length_s = 15.0 + decimation = 2 + action_scale = 1.0 + num_actions = 21 + num_observations = 75 + num_states = 0 + + 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 + + +class HumanoidEnv(LocomotionEnv): + cfg: HumanoidEnvCfg + + def __init__(self, cfg: HumanoidEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/locomotion/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/locomotion/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/locomotion/locomotion_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/locomotion/locomotion_env.py new file mode 100644 index 0000000000..c5c41a3c48 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/locomotion/locomotion_env.py @@ -0,0 +1,278 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import torch + +import omni.isaac.core.utils.torch as torch_utils +from omni.isaac.core.utils.torch.rotations import compute_heading_and_up, compute_rot, quat_conjugate + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import Articulation +from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg + + +def normalize_angle(x): + return torch.atan2(torch.sin(x), torch.cos(x)) + + +class LocomotionEnv(DirectRLEnv): + cfg: DirectRLEnvCfg + + 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 = torch.tensor(self.cfg.joint_gears, dtype=torch.float32, device=self.sim.device) + self.motor_effort_ratio = torch.ones_like(self.joint_gears, device=self.sim.device) + self._joint_dof_idx, _ = self.robot.find_joints(".*") + + self.potentials = torch.zeros(self.num_envs, dtype=torch.float32, device=self.sim.device) + self.prev_potentials = torch.zeros_like(self.potentials) + self.targets = torch.tensor([1000, 0, 0], dtype=torch.float32, device=self.sim.device).repeat( + (self.num_envs, 1) + ) + self.targets += self.scene.env_origins + self.start_rotation = torch.tensor([1, 0, 0, 0], device=self.sim.device, dtype=torch.float32) + self.up_vec = torch.tensor([0, 0, 1], dtype=torch.float32, device=self.sim.device).repeat((self.num_envs, 1)) + self.heading_vec = torch.tensor([1, 0, 0], dtype=torch.float32, device=self.sim.device).repeat( + (self.num_envs, 1) + ) + self.inv_start_rot = quat_conjugate(self.start_rotation).repeat((self.num_envs, 1)) + self.basis_vec0 = self.heading_vec.clone() + self.basis_vec1 = self.up_vec.clone() + + def _setup_scene(self): + self.robot = Articulation(self.cfg.robot) + # add ground plane + self.cfg.terrain.num_envs = self.scene.cfg.num_envs + self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing + self.terrain = self.cfg.terrain.class_type(self.cfg.terrain) + # clone, filter, and replicate + self.scene.clone_environments(copy_from_source=False) + self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) + # add articultion to scene + self.scene.articulations["robot"] = self.robot + # add lights + light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) + light_cfg.func("/World/Light", light_cfg) + + def _pre_physics_step(self, actions: torch.Tensor): + self.actions = actions.clone() + + def _apply_action(self): + forces = self.action_scale * self.joint_gears * self.actions + self.robot.set_joint_effort_target(forces, joint_ids=self._joint_dof_idx) + + def _compute_intermediate_values(self): + self.torso_position, self.torso_rotation = self.robot.data.root_pos_w, self.robot.data.root_quat_w + self.velocity, self.ang_velocity = self.robot.data.root_lin_vel_w, self.robot.data.root_ang_vel_w + self.dof_pos, self.dof_vel = self.robot.data.joint_pos, self.robot.data.joint_vel + + ( + self.up_proj, + self.heading_proj, + self.up_vec, + self.heading_vec, + self.vel_loc, + self.angvel_loc, + self.roll, + self.pitch, + self.yaw, + self.angle_to_target, + self.dof_pos_scaled, + self.prev_potentials, + self.potentials, + ) = compute_intermediate_values( + self.targets, + self.torso_position, + self.torso_rotation, + self.velocity, + self.ang_velocity, + self.dof_pos, + self.robot.data.soft_joint_pos_limits[0, :, 0], + self.robot.data.soft_joint_pos_limits[0, :, 1], + self.inv_start_rot, + self.basis_vec0, + self.basis_vec1, + self.potentials, + self.prev_potentials, + self.cfg.sim.dt, + ) + + def _get_observations(self) -> dict: + obs = torch.cat( + ( + self.torso_position[:, 2].view(-1, 1), + self.vel_loc, + self.angvel_loc * self.cfg.angular_velocity_scale, + normalize_angle(self.yaw).unsqueeze(-1), + normalize_angle(self.roll).unsqueeze(-1), + normalize_angle(self.angle_to_target).unsqueeze(-1), + self.up_proj.unsqueeze(-1), + self.heading_proj.unsqueeze(-1), + self.dof_pos_scaled, + self.dof_vel * self.cfg.dof_vel_scale, + self.actions, + ), + dim=-1, + ) + observations = {"policy": obs} + return observations + + def _get_rewards(self) -> torch.Tensor: + total_reward = compute_rewards( + self.actions, + self.reset_terminated, + self.cfg.up_weight, + self.cfg.heading_weight, + self.heading_proj, + self.up_proj, + self.dof_vel, + self.dof_pos_scaled, + self.potentials, + self.prev_potentials, + self.cfg.actions_cost_scale, + self.cfg.energy_cost_scale, + self.cfg.dof_vel_scale, + self.cfg.death_cost, + self.cfg.alive_reward_scale, + self.motor_effort_ratio, + ) + return total_reward + + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + self._compute_intermediate_values() + time_out = self.episode_length_buf >= self.max_episode_length - 1 + died = self.torso_position[:, 2] < self.cfg.termination_height + return died, time_out + + def _reset_idx(self, env_ids: torch.Tensor | None): + if env_ids is None or len(env_ids) == self.num_envs: + env_ids = self.robot._ALL_INDICES + self.robot.reset(env_ids) + super()._reset_idx(env_ids) + + joint_pos = self.robot.data.default_joint_pos[env_ids] + joint_vel = self.robot.data.default_joint_vel[env_ids] + default_root_state = self.robot.data.default_root_state[env_ids] + default_root_state[:, :3] += self.scene.env_origins[env_ids] + + self.robot.write_root_pose_to_sim(default_root_state[:, :7], env_ids) + self.robot.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) + self.robot.write_joint_state_to_sim(joint_pos, joint_vel, None, env_ids) + + to_target = self.targets[env_ids] - default_root_state[:, :3] + to_target[:, 2] = 0.0 + self.potentials[env_ids] = -torch.norm(to_target, p=2, dim=-1) / self.cfg.sim.dt + + self._compute_intermediate_values() + + +@torch.jit.script +def compute_rewards( + actions: torch.Tensor, + reset_terminated: torch.Tensor, + up_weight: float, + heading_weight: float, + heading_proj: torch.Tensor, + up_proj: torch.Tensor, + dof_vel: torch.Tensor, + dof_pos_scaled: torch.Tensor, + potentials: torch.Tensor, + prev_potentials: torch.Tensor, + actions_cost_scale: float, + energy_cost_scale: float, + dof_vel_scale: float, + death_cost: float, + alive_reward_scale: float, + motor_effort_ratio: torch.Tensor, +): + heading_weight_tensor = torch.ones_like(heading_proj) * heading_weight + heading_reward = torch.where(heading_proj > 0.8, heading_weight_tensor, heading_weight * heading_proj / 0.8) + + # aligning up axis of robot and environment + up_reward = torch.zeros_like(heading_reward) + up_reward = torch.where(up_proj > 0.93, up_reward + up_weight, up_reward) + + # energy penalty for movement + actions_cost = torch.sum(actions**2, dim=-1) + electricity_cost = torch.sum( + torch.abs(actions * dof_vel * dof_vel_scale) * motor_effort_ratio.unsqueeze(0), + dim=-1, + ) + + # dof at limit cost + dof_at_limit_cost = torch.sum(dof_pos_scaled > 0.98, dim=-1) + + # reward for duration of staying alive + alive_reward = torch.ones_like(potentials) * alive_reward_scale + progress_reward = potentials - prev_potentials + + total_reward = ( + progress_reward + + alive_reward + + up_reward + + heading_reward + - actions_cost_scale * actions_cost + - energy_cost_scale * electricity_cost + - dof_at_limit_cost + ) + # adjust reward for fallen agents + total_reward = torch.where(reset_terminated, torch.ones_like(total_reward) * death_cost, total_reward) + return total_reward + + +@torch.jit.script +def compute_intermediate_values( + targets: torch.Tensor, + torso_position: torch.Tensor, + torso_rotation: torch.Tensor, + velocity: torch.Tensor, + ang_velocity: torch.Tensor, + dof_pos: torch.Tensor, + dof_lower_limits: torch.Tensor, + dof_upper_limits: torch.Tensor, + inv_start_rot: torch.Tensor, + basis_vec0: torch.Tensor, + basis_vec1: torch.Tensor, + potentials: torch.Tensor, + prev_potentials: torch.Tensor, + dt: float, +): + to_target = targets - torso_position + to_target[:, 2] = 0.0 + + torso_quat, up_proj, heading_proj, up_vec, heading_vec = compute_heading_and_up( + torso_rotation, inv_start_rot, to_target, basis_vec0, basis_vec1, 2 + ) + + vel_loc, angvel_loc, roll, pitch, yaw, angle_to_target = compute_rot( + torso_quat, velocity, ang_velocity, targets, torso_position + ) + + dof_pos_scaled = torch_utils.maths.unscale(dof_pos, dof_lower_limits, dof_upper_limits) + + to_target = targets - torso_position + to_target[:, 2] = 0.0 + prev_potentials[:] = potentials + potentials = -torch.norm(to_target, p=2, dim=-1) / dt + + return ( + up_proj, + heading_proj, + up_vec, + heading_vec, + vel_loc, + angvel_loc, + roll, + pitch, + yaw, + angle_to_target, + dof_pos_scaled, + prev_potentials, + potentials, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/__init__.py new file mode 100644 index 0000000000..ea6d6a0bde --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/__init__.py @@ -0,0 +1,29 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Quacopter environment. +""" + +import gymnasium as gym + +from . import agents +from .quadcopter_env import QuadcopterEnv, QuadcopterEnvCfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Quadcopter-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.quadcopter:QuadcopterEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": QuadcopterEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.QuadcopterPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/__init__.py new file mode 100644 index 0000000000..441ab975b9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_ppo_cfg diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/rl_games_ppo_cfg.yaml new file mode 100644 index 0000000000..193ed86f17 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/rl_games_ppo_cfg.yaml @@ -0,0 +1,76 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [64, 64] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: quadcopter_direct + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: False + normalize_input: True + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.01 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 5e-4 + lr_schedule: adaptive + schedule_type: legacy + kl_threshold: 0.016 + score_to_win: 20000 + max_epochs: 200 + save_best_after: 100 + save_frequency: 25 + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 24 + minibatch_size: 24576 + mini_epochs: 5 + critic_coef: 2 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/rsl_rl_ppo_cfg.py new file mode 100644 index 0000000000..63075a6ef8 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/rsl_rl_ppo_cfg.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class QuadcopterPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 24 + max_iterations = 200 + save_interval = 50 + experiment_name = "quadcopter_direct" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[64, 64], + critic_hidden_dims=[64, 64], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.0, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=5.0e-4, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..d3488d99f8 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [64, 64] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "tanh" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [64, 64] + hidden_activation: ["elu", "elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 5.e-4 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.016 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.0 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 0.01 + # logging and checkpoint + experiment: + directory: "quadcopter_direct" + experiment_name: "" + write_interval: 24 + checkpoint_interval: 240 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 4800 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/quadcopter_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/quadcopter_env.py new file mode 100644 index 0000000000..91433f3d63 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/quadcopter/quadcopter_env.py @@ -0,0 +1,248 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import torch + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import Articulation, ArticulationCfg +from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg +from omni.isaac.lab.envs.ui import BaseEnvWindow +from omni.isaac.lab.markers import VisualizationMarkers +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sim import SimulationCfg +from omni.isaac.lab.terrains import TerrainImporterCfg +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.math import subtract_frame_transforms + +## +# Pre-defined configs +## +from omni.isaac.lab_assets import CRAZYFLIE_CFG # isort: skip +from omni.isaac.lab.markers import CUBOID_MARKER_CFG # isort: skip + + +class QuadcopterEnvWindow(BaseEnvWindow): + """Window manager for the Quadcopter environment.""" + + def __init__(self, env: QuadcopterEnv, window_name: str = "IsaacLab"): + """Initialize the window. + + Args: + env: The environment object. + window_name: The name of the window. Defaults to "IsaacLab". + """ + # initialize base window + super().__init__(env, window_name) + # add custom UI elements + with self.ui_window_elements["main_vstack"]: + with self.ui_window_elements["debug_frame"]: + with self.ui_window_elements["debug_vstack"]: + # add command manager visualization + self._create_debug_vis_ui_element("targets", self.env) + + +@configclass +class QuadcopterEnvCfg(DirectRLEnvCfg): + ui_window_class_type = QuadcopterEnvWindow + + # simulation + sim: SimulationCfg = SimulationCfg( + dt=1 / 100, + disable_contact_processing=True, + physics_material=sim_utils.RigidBodyMaterialCfg( + friction_combine_mode="multiply", + restitution_combine_mode="multiply", + static_friction=1.0, + dynamic_friction=1.0, + restitution=0.0, + ), + ) + terrain = TerrainImporterCfg( + 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, + restitution=0.0, + ), + debug_vis=False, + ) + + # scene + scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=4096, env_spacing=2.5, replicate_physics=True) + + # robot + robot: ArticulationCfg = CRAZYFLIE_CFG.replace(prim_path="/World/envs/env_.*/Robot") + thrust_to_weight = 1.9 + moment_scale = 0.01 + + # env + episode_length_s = 10.0 + decimation = 2 + num_actions = 4 + num_observations = 12 + num_states = 0 + debug_vis = True + + # reward scales + lin_vel_reward_scale = -0.05 + ang_vel_reward_scale = -0.01 + distance_to_goal_reward_scale = 15.0 + + +class QuadcopterEnv(DirectRLEnv): + cfg: QuadcopterEnvCfg + + def __init__(self, cfg: QuadcopterEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) + + # Total thrust and moment applied to the base of the quadcopter + self._actions = torch.zeros(self.num_envs, self.cfg.num_actions, device=self.device) + self._thrust = torch.zeros(self.num_envs, 1, 3, device=self.device) + self._moment = torch.zeros(self.num_envs, 1, 3, device=self.device) + # Goal position + self._desired_pos_w = torch.zeros(self.num_envs, 3, device=self.device) + + # Logging + self._episode_sums = { + key: torch.zeros(self.num_envs, dtype=torch.float, device=self.device) + for key in [ + "lin_vel", + "ang_vel", + "distance_to_goal", + ] + } + # Get specific body indices + self._body_id = self._robot.find_bodies("body")[0] + self._robot_mass = self._robot.root_physx_view.get_masses()[0].sum() + self._gravity_magnitude = torch.tensor(self.sim.cfg.gravity, device=self.device).norm() + self._robot_weight = (self._robot_mass * self._gravity_magnitude).item() + + # add handle for debug visualization (this is set to a valid handle inside set_debug_vis) + self.set_debug_vis(self.cfg.debug_vis) + + def _setup_scene(self): + self._robot = Articulation(self.cfg.robot) + self.scene.articulations["robot"] = self._robot + + self.cfg.terrain.num_envs = self.scene.cfg.num_envs + self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing + self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) + # clone, filter, and replicate + self.scene.clone_environments(copy_from_source=False) + self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) + # add lights + light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) + light_cfg.func("/World/Light", light_cfg) + + def _pre_physics_step(self, actions: torch.Tensor): + self._actions = actions.clone().clamp(-1.0, 1.0) + self._thrust[:, 0, 2] = self.cfg.thrust_to_weight * self._robot_weight * (self._actions[:, 0] + 1.0) / 2.0 + self._moment[:, 0, :] = self.cfg.moment_scale * self._actions[:, 1:] + + def _apply_action(self): + self._robot.set_external_force_and_torque(self._thrust, self._moment, body_ids=self._body_id) + + def _get_observations(self) -> dict: + desired_pos_b, _ = subtract_frame_transforms( + self._robot.data.root_state_w[:, :3], self._robot.data.root_state_w[:, 3:7], self._desired_pos_w + ) + obs = torch.cat( + [ + self._robot.data.root_lin_vel_b, + self._robot.data.root_ang_vel_b, + self._robot.data.projected_gravity_b, + desired_pos_b, + ], + dim=-1, + ) + observations = {"policy": obs} + return observations + + def _get_rewards(self) -> torch.Tensor: + lin_vel = torch.sum(torch.square(self._robot.data.root_lin_vel_b), dim=1) + ang_vel = torch.sum(torch.square(self._robot.data.root_ang_vel_b), dim=1) + distance_to_goal = torch.linalg.norm(self._desired_pos_w - self._robot.data.root_pos_w, dim=1) + distance_to_goal_mapped = 1 - torch.tanh(distance_to_goal / 0.8) + rewards = { + "lin_vel": lin_vel * self.cfg.lin_vel_reward_scale * self.step_dt, + "ang_vel": ang_vel * self.cfg.ang_vel_reward_scale * self.step_dt, + "distance_to_goal": distance_to_goal_mapped * self.cfg.distance_to_goal_reward_scale * self.step_dt, + } + reward = torch.sum(torch.stack(list(rewards.values())), dim=0) + # Logging + for key, value in rewards.items(): + self._episode_sums[key] += value + return reward + + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + time_out = self.episode_length_buf >= self.max_episode_length - 1 + died = torch.logical_or(self._robot.data.root_pos_w[:, 2] < 0.1, self._robot.data.root_pos_w[:, 2] > 2.0) + return died, time_out + + def _reset_idx(self, env_ids: torch.Tensor | None): + if env_ids is None or len(env_ids) == self.num_envs: + env_ids = self._robot._ALL_INDICES + + # Logging + final_distance_to_goal = torch.linalg.norm( + self._desired_pos_w[env_ids] - self._robot.data.root_pos_w[env_ids], dim=1 + ).mean() + extras = dict() + for key in self._episode_sums.keys(): + episodic_sum_avg = torch.mean(self._episode_sums[key][env_ids]) + extras["Episode Reward/" + key] = episodic_sum_avg / self.max_episode_length_s + self._episode_sums[key][env_ids] = 0.0 + self.extras["log"] = dict() + self.extras["log"].update(extras) + extras = dict() + extras["Episode Termination/died"] = torch.count_nonzero(self.reset_terminated[env_ids]).item() + extras["Episode Termination/time_out"] = torch.count_nonzero(self.reset_time_outs[env_ids]).item() + extras["Metrics/final_distance_to_goal"] = final_distance_to_goal.item() + self.extras["log"].update(extras) + + self._robot.reset(env_ids) + super()._reset_idx(env_ids) + if len(env_ids) == self.num_envs: + # Spread out the resets to avoid spikes in training when many environments reset at a similar time + self.episode_length_buf = torch.randint_like(self.episode_length_buf, high=int(self.max_episode_length)) + + self._actions[env_ids] = 0.0 + # Sample new commands + self._desired_pos_w[env_ids, :2] = torch.zeros_like(self._desired_pos_w[env_ids, :2]).uniform_(-2.0, 2.0) + self._desired_pos_w[env_ids, :2] += self._terrain.env_origins[env_ids, :2] + self._desired_pos_w[env_ids, 2] = torch.zeros_like(self._desired_pos_w[env_ids, 2]).uniform_(0.5, 1.5) + # Reset robot state + joint_pos = self._robot.data.default_joint_pos[env_ids] + joint_vel = self._robot.data.default_joint_vel[env_ids] + default_root_state = self._robot.data.default_root_state[env_ids] + default_root_state[:, :3] += self._terrain.env_origins[env_ids] + self._robot.write_root_pose_to_sim(default_root_state[:, :7], env_ids) + self._robot.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) + self._robot.write_joint_state_to_sim(joint_pos, joint_vel, None, env_ids) + + def _set_debug_vis_impl(self, debug_vis: bool): + # create markers if necessary for the first tome + if debug_vis: + if not hasattr(self, "goal_pos_visualizer"): + marker_cfg = CUBOID_MARKER_CFG.copy() + marker_cfg.markers["cuboid"].size = (0.05, 0.05, 0.05) + # -- goal pose + marker_cfg.prim_path = "/Visuals/Command/goal_position" + self.goal_pos_visualizer = VisualizationMarkers(marker_cfg) + # set their visibility to true + self.goal_pos_visualizer.set_visibility(True) + else: + if hasattr(self, "goal_pos_visualizer"): + self.goal_pos_visualizer.set_visibility(False) + + def _debug_vis_callback(self, event): + # update the markers + self.goal_pos_visualizer.visualize(self._desired_pos_w) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/__init__.py new file mode 100644 index 0000000000..ce0037a0bb --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/__init__.py @@ -0,0 +1,50 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Cartpole balancing environment. +""" + +import gymnasium as gym + +from . import agents +from .shadow_hand_env import ShadowHandEnv +from .shadow_hand_env_cfg import ShadowHandEnvCfg, ShadowHandOpenAIEnvCfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Shadow-Hand-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.shadow_hand:ShadowHandEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": ShadowHandEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.ShadowHandPPORunnerCfg, + }, +) + +gym.register( + id="Isaac-Shadow-Hand-OpenAI-FF-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.shadow_hand:ShadowHandEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": ShadowHandOpenAIEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_ff_cfg.yaml", + "rsl_rl_cfg_entry_point": agents.rsl_rl_ppo_cfg.ShadowHandAsymFFPPORunnerCfg, + }, +) + +gym.register( + id="Isaac-Shadow-Hand-OpenAI-LSTM-Direct-v0", + entry_point="omni.isaac.lab_tasks.direct.shadow_hand:ShadowHandEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": ShadowHandOpenAIEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_lstm_cfg.yaml", + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/__init__.py new file mode 100644 index 0000000000..441ab975b9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_ppo_cfg diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_cfg.yaml new file mode 100644 index 0000000000..8f18adc4bd --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_cfg.yaml @@ -0,0 +1,86 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + # added to the wrapper + clip_observations: 5.0 + # can make custom wrapper? + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + # doesn't have this fine grained control but made it close + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [512, 512, 256, 128] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: shadow_hand + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: False + normalize_input: True + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.01 + normalize_advantage: True + gamma: 0.99 + tau : 0.95 + learning_rate: 5e-4 + lr_schedule: adaptive + schedule_type: standard + kl_threshold: 0.016 + score_to_win: 100000 + max_epochs: 5000 + save_best_after: 100 + save_frequency: 200 + print_stats: True + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 16 + minibatch_size: 32768 + mini_epochs: 5 + critic_coef: 4 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 + + player: + deterministic: True + games_num: 100000 + print_stats: True diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_ff_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_ff_cfg.yaml new file mode 100644 index 0000000000..98cc0b7d09 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_ff_cfg.yaml @@ -0,0 +1,107 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + # added to the wrapper + clip_observations: 5.0 + # can make custom wrapper? + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + + space: + continuous: + mu_activation: None + sigma_activation: None + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [400, 400, 200, 100] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: shadow_hand_openai_ff + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: False + normalize_input: True + normalize_value: True + num_actors: -1 + reward_shaper: + scale_value: 0.01 + normalize_advantage: True + gamma: 0.998 + tau: 0.95 + learning_rate: 5e-4 + lr_schedule: adaptive + schedule_type: standard + kl_threshold: 0.016 + score_to_win: 100000 + max_epochs: 10000 + save_best_after: 100 + save_frequency: 200 + print_stats: True + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 16 + minibatch_size: 16384 + mini_epochs: 4 + critic_coef: 4 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 + + central_value_config: + minibatch_size: 32864 + mini_epochs: 4 + learning_rate: 5e-4 + lr_schedule: adaptive + schedule_type: standard + kl_threshold: 0.016 + clip_value: True + normalize_input: True + truncate_grads: True + + network: + name: actor_critic + central_value: True + mlp: + units: [512, 512, 256, 128] + activation: elu + d2rl: False + initializer: + name: default + regularizer: + name: None + + player: + deterministic: True + games_num: 100000 + print_stats: True diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_lstm_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_lstm_cfg.yaml new file mode 100644 index 0000000000..c404d96d9f --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rl_games_ppo_lstm_cfg.yaml @@ -0,0 +1,118 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + # added to the wrapper + clip_observations: 5.0 + # can make custom wrapper? + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + + space: + continuous: + mu_activation: None + sigma_activation: None + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [512] + activation: relu + d2rl: False + + initializer: + name: default + regularizer: + name: None + rnn: + name: lstm + units: 1024 + layers: 1 + before_mlp: True + layer_norm: True + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: shadow_hand_openai_lstm + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: False + normalize_input: True + normalize_value: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.01 + normalize_advantage: True + gamma: 0.998 + tau: 0.95 + learning_rate: 1e-4 + lr_schedule: adaptive + schedule_type: standard + kl_threshold: 0.016 + score_to_win: 100000 + max_epochs: 10000 + save_best_after: 100 + save_frequency: 200 + print_stats: True + grad_norm: 1.0 + entropy_coef: 0.0 + truncate_grads: True + e_clip: 0.2 + horizon_length: 16 + minibatch_size: 16384 + mini_epochs: 4 + critic_coef: 4 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0001 + + central_value_config: + minibatch_size: 32768 + mini_epochs: 4 + learning_rate: 1e-4 + kl_threshold: 0.016 + clip_value: True + normalize_input: True + truncate_grads: True + + network: + name: actor_critic + central_value: True + mlp: + units: [512] + activation: relu + d2rl: False + initializer: + name: default + regularizer: + name: None + rnn: + name: lstm + units: 1024 + layers: 1 + before_mlp: True + layer_norm: True + zero_rnn_on_done: False + + player: + deterministic: True + games_num: 100000 + print_stats: True diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rsl_rl_ppo_cfg.py new file mode 100644 index 0000000000..5d45964fde --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/agents/rsl_rl_ppo_cfg.py @@ -0,0 +1,70 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class ShadowHandPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 16 + max_iterations = 10000 + save_interval = 250 + experiment_name = "shadow_hand" + empirical_normalization = True + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[512, 512, 256, 128], + critic_hidden_dims=[512, 512, 256, 128], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.005, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=5.0e-4, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.016, + max_grad_norm=1.0, + ) + + +@configclass +class ShadowHandAsymFFPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 16 + max_iterations = 10000 + save_interval = 250 + experiment_name = "shadow_hand_openai_ff" + empirical_normalization = True + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[400, 400, 200, 100], + critic_hidden_dims=[512, 512, 256, 128], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.005, + num_learning_epochs=4, + num_mini_batches=4, + learning_rate=5.0e-4, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/shadow_hand_env.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/shadow_hand_env.py new file mode 100644 index 0000000000..e08afabc7a --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/shadow_hand_env.py @@ -0,0 +1,429 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + + +from __future__ import annotations + +import numpy as np +import torch +from collections.abc import Sequence + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import Articulation, RigidObject +from omni.isaac.lab.envs import DirectRLEnv +from omni.isaac.lab.markers import VisualizationMarkers +from omni.isaac.lab.sim.spawners.from_files import GroundPlaneCfg, spawn_ground_plane +from omni.isaac.lab.utils.math import quat_conjugate, quat_from_angle_axis, quat_mul, sample_uniform, saturate + +from .shadow_hand_env_cfg import ShadowHandEnvCfg + + +class ShadowHandEnv(DirectRLEnv): + cfg: ShadowHandEnvCfg + + def __init__(self, cfg: ShadowHandEnvCfg, render_mode: str | None = None, **kwargs): + super().__init__(cfg, render_mode, **kwargs) + + self.num_hand_dofs = self.hand.num_joints + + # buffers for position targets + self.hand_dof_targets = torch.zeros((self.num_envs, self.num_hand_dofs), dtype=torch.float, device=self.device) + self.prev_targets = torch.zeros((self.num_envs, self.num_hand_dofs), dtype=torch.float, device=self.device) + self.cur_targets = torch.zeros((self.num_envs, self.num_hand_dofs), dtype=torch.float, device=self.device) + + # list of actuated joints + self.actuated_dof_indices = list() + for joint_name in cfg.actuated_joint_names: + self.actuated_dof_indices.append(self.hand.joint_names.index(joint_name)) + self.actuated_dof_indices.sort() + + # finger bodies + self.finger_bodies = list() + for body_name in self.cfg.fingertip_body_names: + self.finger_bodies.append(self.hand.body_names.index(body_name)) + self.finger_bodies.sort() + self.num_fingertips = len(self.finger_bodies) + + # joint limits + joint_pos_limits = self.hand.root_physx_view.get_dof_limits().to(self.device) + self.hand_dof_lower_limits = joint_pos_limits[..., 0] + self.hand_dof_upper_limits = joint_pos_limits[..., 1] + + # track goal resets + self.reset_goal_buf = torch.zeros(self.num_envs, dtype=torch.bool, device=self.device) + # used to compare object position + self.in_hand_pos = self.object.data.default_root_state[:, 0:3].clone() + self.in_hand_pos[:, 2] -= 0.04 + # default goal positions + self.goal_rot = torch.zeros((self.num_envs, 4), dtype=torch.float, device=self.device) + self.goal_rot[:, 0] = 1.0 + self.goal_pos = torch.zeros((self.num_envs, 3), dtype=torch.float, device=self.device) + self.goal_pos[:, :] = torch.tensor([-0.2, -0.45, 0.68], device=self.device) + # initialize goal marker + self.goal_markers = VisualizationMarkers(self.cfg.goal_object_cfg) + + # track successes + self.successes = torch.zeros(self.num_envs, dtype=torch.float, device=self.device) + self.consecutive_successes = torch.zeros(1, dtype=torch.float, device=self.device) + + # unit tensors + self.x_unit_tensor = torch.tensor([1, 0, 0], dtype=torch.float, device=self.device).repeat((self.num_envs, 1)) + self.y_unit_tensor = torch.tensor([0, 1, 0], dtype=torch.float, device=self.device).repeat((self.num_envs, 1)) + self.z_unit_tensor = torch.tensor([0, 0, 1], dtype=torch.float, device=self.device).repeat((self.num_envs, 1)) + + def _setup_scene(self): + # add hand, in-hand object, and goal object + self.hand = Articulation(self.cfg.robot_cfg) + self.object = RigidObject(self.cfg.object_cfg) + # add ground plane + spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg()) + # clone and replicate (no need to filter for this environment) + self.scene.clone_environments(copy_from_source=False) + # add articultion to scene - we must register to scene to randomize with EventManager + self.scene.articulations["robot"] = self.hand + self.scene.rigid_objects["object"] = self.object + # add lights + light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)) + light_cfg.func("/World/Light", light_cfg) + + def _pre_physics_step(self, actions: torch.Tensor) -> None: + self.actions = actions.clone() + + def _apply_action(self) -> None: + self.cur_targets[:, self.actuated_dof_indices] = scale( + self.actions, + self.hand_dof_lower_limits[:, self.actuated_dof_indices], + self.hand_dof_upper_limits[:, self.actuated_dof_indices], + ) + self.cur_targets[:, self.actuated_dof_indices] = ( + self.cfg.act_moving_average * self.cur_targets[:, self.actuated_dof_indices] + + (1.0 - self.cfg.act_moving_average) * self.prev_targets[:, self.actuated_dof_indices] + ) + self.cur_targets[:, self.actuated_dof_indices] = saturate( + self.cur_targets[:, self.actuated_dof_indices], + self.hand_dof_lower_limits[:, self.actuated_dof_indices], + self.hand_dof_upper_limits[:, self.actuated_dof_indices], + ) + + self.prev_targets[:, self.actuated_dof_indices] = self.cur_targets[:, self.actuated_dof_indices] + + self.hand.set_joint_position_target( + self.cur_targets[:, self.actuated_dof_indices], joint_ids=self.actuated_dof_indices + ) + + def _get_observations(self) -> dict: + if self.cfg.asymmetric_obs: + self.fingertip_force_sensors = self.hand.root_physx_view.get_link_incoming_joint_force()[ + :, self.finger_bodies + ] + + if self.cfg.obs_type == "openai": + obs = self.compute_reduced_observations() + elif self.cfg.obs_type == "full": + obs = self.compute_full_observations() + else: + print("Unknown observations type!") + + if self.cfg.asymmetric_obs: + states = self.compute_full_state() + + observations = {"policy": obs} + if self.cfg.asymmetric_obs: + observations = {"policy": obs, "critic": states} + return observations + + def _get_rewards(self) -> torch.Tensor: + ( + total_reward, + self.reset_goal_buf, + self.successes[:], + self.consecutive_successes[:], + ) = compute_rewards( + self.reset_buf, + self.reset_goal_buf, + self.successes, + self.consecutive_successes, + self.max_episode_length, + self.object_pos, + self.object_rot, + self.in_hand_pos, + self.goal_rot, + self.cfg.dist_reward_scale, + self.cfg.rot_reward_scale, + self.cfg.rot_eps, + self.actions, + self.cfg.action_penalty_scale, + self.cfg.success_tolerance, + self.cfg.reach_goal_bonus, + self.cfg.fall_dist, + self.cfg.fall_penalty, + self.cfg.av_factor, + ) + + if "log" not in self.extras: + self.extras["log"] = dict() + self.extras["log"]["consecutive_successes"] = self.consecutive_successes.mean() + + # reset goals if the goal has been reached + goal_env_ids = self.reset_goal_buf.nonzero(as_tuple=False).squeeze(-1) + if len(goal_env_ids) > 0: + self._reset_target_pose(goal_env_ids) + + return total_reward + + def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: + self._compute_intermediate_values() + + # reset when cube has fallen + goal_dist = torch.norm(self.object_pos - self.in_hand_pos, p=2, dim=-1) + out_of_reach = goal_dist >= self.cfg.fall_dist + + if self.cfg.max_consecutive_success > 0: + # Reset progress (episode length buf) on goal envs if max_consecutive_success > 0 + rot_dist = rotation_distance(self.object_rot, self.goal_rot) + self.episode_length_buf = torch.where( + torch.abs(rot_dist) <= self.cfg.success_tolerance, + torch.zeros_like(self.episode_length_buf), + self.episode_length_buf, + ) + max_success_reached = self.successes >= self.cfg.max_consecutive_success + + time_out = self.episode_length_buf >= self.max_episode_length - 1 + if self.cfg.max_consecutive_success > 0: + time_out = time_out | max_success_reached + return out_of_reach, time_out + + def _reset_idx(self, env_ids: Sequence[int] | None): + if env_ids is None: + env_ids = self.hand._ALL_INDICES + # resets articulation and rigid body attributes + super()._reset_idx(env_ids) + + # reset goals + self._reset_target_pose(env_ids) + + # reset object + object_default_state = self.object.data.default_root_state.clone()[env_ids] + pos_noise = sample_uniform(-1.0, 1.0, (len(env_ids), 3), device=self.device) + # global object positions + object_default_state[:, 0:3] = ( + object_default_state[:, 0:3] + self.cfg.reset_position_noise * pos_noise + self.scene.env_origins[env_ids] + ) + + rot_noise = sample_uniform(-1.0, 1.0, (len(env_ids), 2), device=self.device) # noise for X and Y rotation + object_default_state[:, 3:7] = randomize_rotation( + rot_noise[:, 0], rot_noise[:, 1], self.x_unit_tensor[env_ids], self.y_unit_tensor[env_ids] + ) + + object_default_state[:, 7:] = torch.zeros_like(self.object.data.default_root_state[env_ids, 7:]) + self.object.write_root_state_to_sim(object_default_state, env_ids) + + # reset hand + delta_max = self.hand_dof_upper_limits[env_ids] - self.hand.data.default_joint_pos[env_ids] + delta_min = self.hand_dof_lower_limits[env_ids] - self.hand.data.default_joint_pos[env_ids] + + dof_pos_noise = sample_uniform(-1.0, 1.0, (len(env_ids), self.num_hand_dofs), device=self.device) + rand_delta = delta_min + (delta_max - delta_min) * 0.5 * dof_pos_noise + dof_pos = self.hand.data.default_joint_pos[env_ids] + self.cfg.reset_dof_pos_noise * rand_delta + + dof_vel_noise = sample_uniform(-1.0, 1.0, (len(env_ids), self.num_hand_dofs), device=self.device) + dof_vel = self.hand.data.default_joint_vel[env_ids] + self.cfg.reset_dof_vel_noise * dof_vel_noise + + self.prev_targets[env_ids] = dof_pos + self.cur_targets[env_ids] = dof_pos + self.hand_dof_targets[env_ids] = dof_pos + + self.hand.set_joint_position_target(dof_pos, env_ids=env_ids) + self.hand.write_joint_state_to_sim(dof_pos, dof_vel, env_ids=env_ids) + + self.successes[env_ids] = 0 + self._compute_intermediate_values() + + def _reset_target_pose(self, env_ids): + # reset goal rotation + rand_floats = sample_uniform(-1.0, 1.0, (len(env_ids), 2), device=self.device) + new_rot = randomize_rotation( + rand_floats[:, 0], rand_floats[:, 1], self.x_unit_tensor[env_ids], self.y_unit_tensor[env_ids] + ) + + # update goal pose and markers + self.goal_rot[env_ids] = new_rot + goal_pos = self.goal_pos + self.scene.env_origins + self.goal_markers.visualize(goal_pos, self.goal_rot) + + self.reset_goal_buf[env_ids] = 0 + + def _compute_intermediate_values(self): + # data for hand + self.fingertip_pos = self.hand.data.body_pos_w[:, self.finger_bodies] + self.fingertip_rot = self.hand.data.body_quat_w[:, self.finger_bodies] + self.fingertip_pos -= self.scene.env_origins.repeat((1, self.num_fingertips)).reshape( + self.num_envs, self.num_fingertips, 3 + ) + self.fingertip_velocities = self.hand.data.body_vel_w[:, self.finger_bodies] + + self.hand_dof_pos = self.hand.data.joint_pos + self.hand_dof_vel = self.hand.data.joint_vel + + # data for object + self.object_pos = self.object.data.root_pos_w - self.scene.env_origins + self.object_rot = self.object.data.root_quat_w + self.object_velocities = self.object.data.root_vel_w + self.object_linvel = self.object.data.root_lin_vel_w + self.object_angvel = self.object.data.root_ang_vel_w + + def compute_reduced_observations(self): + # Per https://arxiv.org/pdf/1808.00177.pdf Table 2 + # Fingertip positions + # Object Position, but not orientation + # Relative target orientation + obs = torch.cat( + ( + self.fingertip_pos.view(self.num_envs, self.num_fingertips * 3), # 0:15 + self.object_pos, # 15:18 + quat_mul(self.object_rot, quat_conjugate(self.goal_rot)), # 18:22 + self.actions, # 22:42 + ), + dim=-1, + ) + + return obs + + def compute_full_observations(self): + obs = torch.cat( + ( + # hand + unscale(self.hand_dof_pos, self.hand_dof_lower_limits, self.hand_dof_upper_limits), # 0:24 + self.cfg.vel_obs_scale * self.hand_dof_vel, # 24:48 + # object + self.object_pos, # 48:51 + self.object_rot, # 51:55 + self.object_linvel, # 55:58 + self.cfg.vel_obs_scale * self.object_angvel, # 58:61 + # goal + self.in_hand_pos, # 61:64 + self.goal_rot, # 64:68 + quat_mul(self.object_rot, quat_conjugate(self.goal_rot)), # 68:72 + # fingertips + self.fingertip_pos.view(self.num_envs, self.num_fingertips * 3), # 72:87 + self.fingertip_rot.view(self.num_envs, self.num_fingertips * 4), # 87:107 + self.fingertip_velocities.view(self.num_envs, self.num_fingertips * 6), # 107:137 + # actions + self.actions, # 137:157 + ), + dim=-1, + ) + return obs + + def compute_full_state(self): + states = torch.cat( + ( + # hand + unscale(self.hand_dof_pos, self.hand_dof_lower_limits, self.hand_dof_upper_limits), # 0:24 + self.cfg.vel_obs_scale * self.hand_dof_vel, # 24:48 + # object + self.object_pos, # 48:51 + self.object_rot, # 51:55 + self.object_linvel, # 55:58 + self.cfg.vel_obs_scale * self.object_angvel, # 58:61 + # goal + self.in_hand_pos, # 61:64 + self.goal_rot, # 64:68 + quat_mul(self.object_rot, quat_conjugate(self.goal_rot)), # 68:72 + # fingertips + self.fingertip_pos.view(self.num_envs, self.num_fingertips * 3), # 72:87 + self.fingertip_rot.view(self.num_envs, self.num_fingertips * 4), # 87:107 + self.fingertip_velocities.view(self.num_envs, self.num_fingertips * 6), # 107:137 + self.cfg.force_torque_obs_scale + * self.fingertip_force_sensors.view(self.num_envs, self.num_fingertips * 6), # 137:167 + # actions + self.actions, # 167:187 + ), + dim=-1, + ) + return states + + +@torch.jit.script +def scale(x, lower, upper): + return 0.5 * (x + 1.0) * (upper - lower) + lower + + +@torch.jit.script +def unscale(x, lower, upper): + return (2.0 * x - upper - lower) / (upper - lower) + + +@torch.jit.script +def randomize_rotation(rand0, rand1, x_unit_tensor, y_unit_tensor): + return quat_mul( + quat_from_angle_axis(rand0 * np.pi, x_unit_tensor), quat_from_angle_axis(rand1 * np.pi, y_unit_tensor) + ) + + +@torch.jit.script +def rotation_distance(object_rot, target_rot): + # Orientation alignment for the cube in hand and goal cube + quat_diff = quat_mul(object_rot, quat_conjugate(target_rot)) + return 2.0 * torch.asin(torch.clamp(torch.norm(quat_diff[:, 1:4], p=2, dim=-1), max=1.0)) # changed quat convention + + +@torch.jit.script +def compute_rewards( + reset_buf: torch.Tensor, + reset_goal_buf: torch.Tensor, + successes: torch.Tensor, + consecutive_successes: torch.Tensor, + max_episode_length: float, + object_pos: torch.Tensor, + object_rot: torch.Tensor, + target_pos: torch.Tensor, + target_rot: torch.Tensor, + dist_reward_scale: float, + rot_reward_scale: float, + rot_eps: float, + actions: torch.Tensor, + action_penalty_scale: float, + success_tolerance: float, + reach_goal_bonus: float, + fall_dist: float, + fall_penalty: float, + av_factor: float, +): + + goal_dist = torch.norm(object_pos - target_pos, p=2, dim=-1) + rot_dist = rotation_distance(object_rot, target_rot) + + dist_rew = goal_dist * dist_reward_scale + rot_rew = 1.0 / (torch.abs(rot_dist) + rot_eps) * rot_reward_scale + + action_penalty = torch.sum(actions**2, dim=-1) + + # Total reward is: position distance + orientation alignment + action regularization + success bonus + fall penalty + reward = dist_rew + rot_rew + action_penalty * action_penalty_scale + + # Find out which envs hit the goal and update successes count + goal_resets = torch.where(torch.abs(rot_dist) <= success_tolerance, torch.ones_like(reset_goal_buf), reset_goal_buf) + successes = successes + goal_resets + + # Success bonus: orientation is within `success_tolerance` of goal orientation + reward = torch.where(goal_resets == 1, reward + reach_goal_bonus, reward) + + # Fall penalty: distance to the goal is larger than a threshold + reward = torch.where(goal_dist >= fall_dist, reward + fall_penalty, reward) + + # Check env termination conditions, including maximum success number + resets = torch.where(goal_dist >= fall_dist, torch.ones_like(reset_buf), reset_buf) + + num_resets = torch.sum(resets) + finished_cons_successes = torch.sum(successes * resets.float()) + + cons_successes = torch.where( + num_resets > 0, + av_factor * finished_cons_successes / num_resets + (1.0 - av_factor) * consecutive_successes, + consecutive_successes, + ) + + return reward, goal_resets, successes, cons_successes diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py new file mode 100644 index 0000000000..1100004d71 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py @@ -0,0 +1,273 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + + +from omni.isaac.lab_assets.shadow_hand import SHADOW_HAND_CFG + +import omni.isaac.lab.envs.mdp as mdp +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import ArticulationCfg, RigidObjectCfg +from omni.isaac.lab.envs import DirectRLEnvCfg +from omni.isaac.lab.managers import EventTermCfg as EventTerm +from omni.isaac.lab.managers import SceneEntityCfg +from omni.isaac.lab.markers import VisualizationMarkersCfg +from omni.isaac.lab.scene import InteractiveSceneCfg +from omni.isaac.lab.sim import PhysxCfg, SimulationCfg +from omni.isaac.lab.sim.spawners.materials.physics_materials_cfg import RigidBodyMaterialCfg +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR +from omni.isaac.lab.utils.noise import GaussianNoiseCfg, NoiseModelWithAdditiveBiasCfg + + +@configclass +class EventCfg: + """Configuration for randomization.""" + + # -- robot + robot_physics_material = EventTerm( + func=mdp.randomize_rigid_body_material, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=".*"), + "static_friction_range": (0.7, 1.3), + "dynamic_friction_range": (1.0, 1.0), + "restitution_range": (1.0, 1.0), + "num_buckets": 250, + }, + ) + robot_joint_stiffness_and_damping = EventTerm( + func=mdp.randomize_actuator_gains, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), + "stiffness_distribution_params": (0.75, 1.5), + "damping_distribution_params": (0.3, 3.0), + "operation": "scale", + "distribution": "log_uniform", + }, + ) + robot_joint_limits = EventTerm( + func=mdp.randomize_joint_parameters, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), + "lower_limit_distribution_params": (0.00, 0.01), + "upper_limit_distribution_params": (0.00, 0.01), + "operation": "add", + "distribution": "gaussian", + }, + ) + robot_tendon_properties = EventTerm( + func=mdp.randomize_fixed_tendon_parameters, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", fixed_tendon_names=".*"), + "stiffness_distribution_params": (0.75, 1.5), + "damping_distribution_params": (0.3, 3.0), + "operation": "scale", + "distribution": "log_uniform", + }, + ) + + # -- object + object_physics_material = EventTerm( + func=mdp.randomize_rigid_body_material, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("object", body_names=".*"), + "static_friction_range": (0.7, 1.3), + "dynamic_friction_range": (1.0, 1.0), + "restitution_range": (1.0, 1.0), + "num_buckets": 250, + }, + ) + object_scale_mass = EventTerm( + func=mdp.randomize_rigid_body_mass, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("object"), + "mass_distribution_params": (0.5, 1.5), + "operation": "scale", + "distribution": "uniform", + }, + ) + + # -- scene + reset_gravity = EventTerm( + func=mdp.randomize_physics_scene_gravity, + mode="interval", + is_global_time=True, + interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt) + params={ + "gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]), + "operation": "add", + "distribution": "gaussian", + }, + ) + + +@configclass +class ShadowHandEnvCfg(DirectRLEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg( + dt=1 / 120, + physics_material=RigidBodyMaterialCfg( + static_friction=1.0, + dynamic_friction=1.0, + ), + physx=PhysxCfg( + bounce_threshold_velocity=0.2, + ), + ) + # robot + robot_cfg: ArticulationCfg = SHADOW_HAND_CFG.replace(prim_path="/World/envs/env_.*/Robot").replace( + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 0.5), + rot=(1.0, 0.0, 0.0, 0.0), + joint_pos={".*": 0.0}, + ) + ) + actuated_joint_names = [ + "robot0_WRJ1", + "robot0_WRJ0", + "robot0_FFJ3", + "robot0_FFJ2", + "robot0_FFJ1", + "robot0_MFJ3", + "robot0_MFJ2", + "robot0_MFJ1", + "robot0_RFJ3", + "robot0_RFJ2", + "robot0_RFJ1", + "robot0_LFJ4", + "robot0_LFJ3", + "robot0_LFJ2", + "robot0_LFJ1", + "robot0_THJ4", + "robot0_THJ3", + "robot0_THJ2", + "robot0_THJ1", + "robot0_THJ0", + ] + fingertip_body_names = [ + "robot0_ffdistal", + "robot0_mfdistal", + "robot0_rfdistal", + "robot0_lfdistal", + "robot0_thdistal", + ] + + # in-hand object + object_cfg: RigidObjectCfg = RigidObjectCfg( + prim_path="/World/envs/env_.*/object", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", + rigid_props=sim_utils.RigidBodyPropertiesCfg( + kinematic_enabled=False, + disable_gravity=False, + enable_gyroscopic_forces=True, + solver_position_iteration_count=8, + solver_velocity_iteration_count=0, + sleep_threshold=0.005, + stabilization_threshold=0.0025, + max_depenetration_velocity=1000.0, + ), + mass_props=sim_utils.MassPropertiesCfg(density=567.0), + ), + init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.39, 0.6), rot=(1.0, 0.0, 0.0, 0.0)), + ) + # 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.0, 1.0, 1.0), + ) + }, + ) + # scene + scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=8192, env_spacing=0.75, replicate_physics=True) + # env + decimation = 2 + episode_length_s = 10.0 + num_actions = 20 + num_observations = 157 # (full) + num_states = 0 + asymmetric_obs = False + obs_type = "full" + # 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.1 + max_consecutive_success = 0 + av_factor = 0.1 + act_moving_average = 1.0 + force_torque_obs_scale = 10.0 + + +@configclass +class ShadowHandOpenAIEnvCfg(ShadowHandEnvCfg): + # simulation + sim: SimulationCfg = SimulationCfg( + dt=1 / 60, + physics_material=RigidBodyMaterialCfg( + static_friction=1.0, + dynamic_friction=1.0, + ), + physx=PhysxCfg( + bounce_threshold_velocity=0.2, + gpu_max_rigid_contact_count=2**23, + gpu_max_rigid_patch_count=2**23, + ), + ) + # env + decimation = 3 + episode_length_s = 8.0 + num_actions = 20 + num_observations = 42 + num_states = 187 + asymmetric_obs = True + obs_type = "openai" + # 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 = -50 + fall_dist = 0.24 + vel_obs_scale = 0.2 + success_tolerance = 0.4 + max_consecutive_success = 50 + av_factor = 0.1 + act_moving_average = 0.3 + force_torque_obs_scale = 10.0 + # domain randomization config + events: EventCfg = EventCfg() + # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset + action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), + ) + # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset + observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/__init__.py new file mode 100644 index 0000000000..06580c1568 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Config-based workflow environments. +""" + +import gymnasium as gym diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/__init__.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/__init__.py index ed5039baab..c6683ae5fc 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/__init__.py @@ -17,7 +17,7 @@ gym.register( id="Isaac-Ant-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": ant_env_cfg.AntEnvCfg, diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/__init__.py new file mode 100644 index 0000000000..441ab975b9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_ppo_cfg diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/rl_games_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/rsl_rl_ppo_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/rsl_rl_ppo_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/rsl_rl_ppo_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/sb3_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/sb3_ppo_cfg.yaml similarity index 97% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/sb3_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/sb3_ppo_cfg.yaml index 2edff4e9fe..2c2ed974aa 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/sb3_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/sb3_ppo_cfg.yaml @@ -15,6 +15,7 @@ vf_coef: 0.5 learning_rate: !!float 3e-5 use_sde: True clip_range: 0.4 +device: "cuda:0" policy_kwargs: "dict( log_std_init=-1, ortho_init=False, diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/skrl_ppo_cfg.yaml similarity index 78% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/skrl_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/skrl_ppo_cfg.yaml index 924d1335ff..ee2d76ba3c 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/agents/skrl_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/agents/skrl_ppo_cfg.yaml @@ -1,10 +1,10 @@ seed: 42 # Models are instantiated using skrl's model instantiator utility -# https://skrl.readthedocs.io/en/develop/modules/skrl.utils.model_instantiators.html +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models: separate: False - policy: # see skrl.utils.model_instantiators.gaussian_model for parameter details + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details clip_actions: True clip_log_std: True initial_log_std: 0 @@ -16,7 +16,7 @@ models: output_shape: "Shape.ACTIONS" output_activation: "tanh" output_scale: 1.0 - value: # see skrl.utils.model_instantiators.deterministic_model for parameter details + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details clip_actions: False input_shape: "Shape.STATES" hiddens: [256, 128, 64] @@ -27,7 +27,7 @@ models: # PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) -# https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent: rollouts: 16 learning_epochs: 8 @@ -61,6 +61,7 @@ agent: # Sequential trainer -# https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.sequential.html +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html trainer: timesteps: 8000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/ant_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/ant_env_cfg.py similarity index 78% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/ant_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/ant_env_cfg.py index 1da3258918..b8b418de34 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/ant/ant_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/ant/ant_env_cfg.py @@ -4,9 +4,8 @@ # SPDX-License-Identifier: BSD-3-Clause import omni.isaac.lab.sim as sim_utils -from omni.isaac.lab.actuators import ImplicitActuatorCfg -from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.assets import AssetBaseCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -16,13 +15,13 @@ from omni.isaac.lab.scene import InteractiveSceneCfg from omni.isaac.lab.terrains import TerrainImporterCfg from omni.isaac.lab.utils import configclass -from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR -import omni.isaac.lab_tasks.classic.humanoid.mdp as mdp +import omni.isaac.lab_tasks.manager_based.classic.humanoid.mdp as mdp ## -# Scene definition +# Pre-defined configs ## +from omni.isaac.lab_assets.ant import ANT_CFG # isort: skip @configclass @@ -45,42 +44,7 @@ class MySceneCfg(InteractiveSceneCfg): ) # robot - robot = ArticulationCfg( - prim_path="{ENV_REGEX_NS}/Robot", - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Ant/ant_instanceable.usd", - rigid_props=sim_utils.RigidBodyPropertiesCfg( - disable_gravity=False, - max_depenetration_velocity=10.0, - enable_gyroscopic_forces=True, - ), - articulation_props=sim_utils.ArticulationRootPropertiesCfg( - enabled_self_collisions=False, - solver_position_iteration_count=4, - solver_velocity_iteration_count=0, - sleep_threshold=0.005, - stabilization_threshold=0.001, - ), - copy_from_source=False, - ), - init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, 0.0, 0.5), - joint_pos={ - ".*_leg": 0.0, - "front_left_foot": 0.785398, # 45 degrees - "front_right_foot": -0.785398, - "left_back_foot": -0.785398, - "right_back_foot": 0.785398, - }, - ), - actuators={ - "body": ImplicitActuatorCfg( - joint_names_expr=[".*"], - stiffness=0.0, - damping=0.0, - ), - }, - ) + robot = ANT_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") # lights light = AssetBaseCfg( @@ -207,7 +171,7 @@ class CurriculumCfg: @configclass -class AntEnvCfg(RLTaskEnvCfg): +class AntEnvCfg(ManagerBasedRLEnvCfg): """Configuration for the MuJoCo-style Ant walking environment.""" # Scene settings diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/__init__.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/__init__.py index d255d8703a..00f21df5f2 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/__init__.py @@ -18,7 +18,7 @@ gym.register( id="Isaac-Cartpole-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": CartpoleEnvCfg, diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/rl_games_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/rsl_rl_ppo_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/rsl_rl_ppo_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/rsl_rl_ppo_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/sb3_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/sb3_ppo_cfg.yaml new file mode 100644 index 0000000000..5856f35f8e --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/sb3_ppo_cfg.yaml @@ -0,0 +1,21 @@ +# Reference: https://github.com/DLR-RM/rl-baselines3-zoo/blob/master/hyperparams/ppo.yml#L32 +seed: 42 + +n_timesteps: !!float 1e6 +policy: 'MlpPolicy' +n_steps: 16 +batch_size: 4096 +gae_lambda: 0.95 +gamma: 0.99 +n_epochs: 20 +ent_coef: 0.01 +learning_rate: !!float 3e-4 +clip_range: !!float 0.2 +policy_kwargs: "dict( + activation_fn=nn.ELU, + net_arch=[32, 32], + squash_output=False, + )" +vf_coef: 1.0 +max_grad_norm: 1.0 +device: "cuda:0" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/skrl_ppo_cfg.yaml similarity index 78% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/skrl_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/skrl_ppo_cfg.yaml index 8c1b6cbb5f..6d2a5978d3 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/agents/skrl_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/agents/skrl_ppo_cfg.yaml @@ -1,10 +1,10 @@ seed: 42 # Models are instantiated using skrl's model instantiator utility -# https://skrl.readthedocs.io/en/develop/modules/skrl.utils.model_instantiators.html +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models: separate: False - policy: # see skrl.utils.model_instantiators.gaussian_model for parameter details + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details clip_actions: True clip_log_std: True initial_log_std: 0 @@ -16,7 +16,7 @@ models: output_shape: "Shape.ACTIONS" output_activation: "tanh" output_scale: 1.0 - value: # see skrl.utils.model_instantiators.deterministic_model for parameter details + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details clip_actions: False input_shape: "Shape.STATES" hiddens: [32, 32] @@ -27,7 +27,7 @@ models: # PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) -# https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent: rollouts: 16 learning_epochs: 5 @@ -61,6 +61,7 @@ agent: # Sequential trainer -# https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.sequential.html +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html trainer: timesteps: 2400 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py similarity index 97% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py index 6fd552363d..d3bea72d12 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/cartpole_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py @@ -7,7 +7,7 @@ import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -17,7 +17,7 @@ from omni.isaac.lab.scene import InteractiveSceneCfg from omni.isaac.lab.utils import configclass -import omni.isaac.lab_tasks.classic.cartpole.mdp as mdp +import omni.isaac.lab_tasks.manager_based.classic.cartpole.mdp as mdp ## # Pre-defined configs @@ -175,7 +175,7 @@ class CurriculumCfg: @configclass -class CartpoleEnvCfg(RLTaskEnvCfg): +class CartpoleEnvCfg(ManagerBasedRLEnvCfg): """Configuration for the locomotion velocity-tracking environment.""" # Scene settings diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/mdp/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/mdp/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/mdp/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/mdp/rewards.py similarity index 82% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/mdp/rewards.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/mdp/rewards.py index e5ebf1a3f4..c082a988c1 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/cartpole/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/mdp/rewards.py @@ -13,10 +13,10 @@ from omni.isaac.lab.utils.math import wrap_to_pi if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv -def joint_pos_target_l2(env: RLTaskEnv, target: float, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def joint_pos_target_l2(env: ManagerBasedRLEnv, target: float, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Penalize joint position deviation from a target value.""" # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/__init__.py similarity index 93% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/__init__.py index 5b4ac88748..36e081f157 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/__init__.py @@ -17,7 +17,7 @@ gym.register( id="Isaac-Humanoid-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": humanoid_env_cfg.HumanoidEnvCfg, diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/__init__.py new file mode 100644 index 0000000000..dadccd581c --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_ppo_cfg # noqa: F401, F403 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/rl_games_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/rsl_rl_ppo_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/rsl_rl_ppo_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/rsl_rl_ppo_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/sb3_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/sb3_ppo_cfg.yaml similarity index 90% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/sb3_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/sb3_ppo_cfg.yaml index 35bb9232e1..b6db545ff2 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/sb3_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/sb3_ppo_cfg.yaml @@ -13,8 +13,9 @@ n_epochs: 10 gae_lambda: 0.95 max_grad_norm: 1.0 vf_coef: 0.5 +device: "cuda:0" policy_kwargs: "dict( - log_std_init=-2, + log_std_init=-1, ortho_init=False, activation_fn=nn.ReLU, net_arch=dict(pi=[256, 256], vf=[256, 256]) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/skrl_ppo_cfg.yaml similarity index 78% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/skrl_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/skrl_ppo_cfg.yaml index d0a0b7a519..7d49fc7ca0 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/agents/skrl_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/agents/skrl_ppo_cfg.yaml @@ -1,10 +1,10 @@ seed: 42 # Models are instantiated using skrl's model instantiator utility -# https://skrl.readthedocs.io/en/develop/modules/skrl.utils.model_instantiators.html +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models: separate: False - policy: # see skrl.utils.model_instantiators.gaussian_model for parameter details + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details clip_actions: True clip_log_std: True initial_log_std: 0 @@ -16,7 +16,7 @@ models: output_shape: "Shape.ACTIONS" output_activation: "tanh" output_scale: 1.0 - value: # see skrl.utils.model_instantiators.deterministic_model for parameter details + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details clip_actions: False input_shape: "Shape.STATES" hiddens: [400, 200, 100] @@ -27,7 +27,7 @@ models: # PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) -# https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent: rollouts: 32 learning_epochs: 8 @@ -61,6 +61,7 @@ agent: # Sequential trainer -# https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.sequential.html +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html trainer: timesteps: 16000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/humanoid_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/humanoid_env_cfg.py similarity index 98% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/humanoid_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/humanoid_env_cfg.py index 836cc91951..085eab90cf 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/humanoid_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/humanoid_env_cfg.py @@ -6,7 +6,7 @@ import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.actuators import ImplicitActuatorCfg from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -18,7 +18,7 @@ from omni.isaac.lab.utils import configclass from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR -import omni.isaac.lab_tasks.classic.humanoid.mdp as mdp +import omni.isaac.lab_tasks.manager_based.classic.humanoid.mdp as mdp ## # Scene definition @@ -256,7 +256,7 @@ class CurriculumCfg: @configclass -class HumanoidEnvCfg(RLTaskEnvCfg): +class HumanoidEnvCfg(ManagerBasedRLEnvCfg): """Configuration for the MuJoCo-style Humanoid walking environment.""" # Scene settings diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/observations.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/observations.py similarity index 84% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/observations.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/observations.py index f1c506ae70..398535b2a4 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/observations.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/observations.py @@ -13,10 +13,10 @@ from omni.isaac.lab.managers import SceneEntityCfg if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv -def base_yaw_roll(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def base_yaw_roll(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Yaw and roll of the base in the simulation world frame.""" # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] @@ -29,7 +29,7 @@ def base_yaw_roll(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robo return torch.cat((yaw.unsqueeze(-1), roll.unsqueeze(-1)), dim=-1) -def base_up_proj(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: +def base_up_proj(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: """Projection of the base up vector onto the world up vector.""" # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] @@ -40,7 +40,7 @@ def base_up_proj(env: BaseEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot def base_heading_proj( - env: BaseEnv, target_pos: tuple[float, float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedEnv, target_pos: tuple[float, float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Projection of the base forward vector onto the world forward vector.""" # extract the used quantities (to enable type-hinting) @@ -58,7 +58,7 @@ def base_heading_proj( def base_angle_to_target( - env: BaseEnv, target_pos: tuple[float, float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedEnv, target_pos: tuple[float, float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Angle between the base forward vector and the vector to the target.""" # extract the used quantities (to enable type-hinting) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/rewards.py similarity index 89% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/rewards.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/rewards.py index f6ccb0ef61..b21be8d31f 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/classic/humanoid/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/humanoid/mdp/rewards.py @@ -16,11 +16,11 @@ from . import observations as obs if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv def upright_posture_bonus( - env: RLTaskEnv, threshold: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, threshold: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Reward for maintaining an upright posture.""" up_proj = obs.base_up_proj(env, asset_cfg).squeeze(-1) @@ -28,7 +28,7 @@ def upright_posture_bonus( def move_to_target_bonus( - env: RLTaskEnv, + env: ManagerBasedRLEnv, threshold: float, target_pos: tuple[float, float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), @@ -41,7 +41,7 @@ def move_to_target_bonus( class progress_reward(ManagerTermBase): """Reward for making progress towards the target.""" - def __init__(self, env: RLTaskEnv, cfg: RewardTermCfg): + def __init__(self, env: ManagerBasedRLEnv, cfg: RewardTermCfg): # initialize the base class super().__init__(cfg, env) # create history buffer @@ -60,7 +60,7 @@ def reset(self, env_ids: torch.Tensor): def __call__( self, - env: RLTaskEnv, + env: ManagerBasedRLEnv, target_pos: tuple[float, float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), ) -> torch.Tensor: @@ -80,7 +80,7 @@ def __call__( class joint_limits_penalty_ratio(ManagerTermBase): """Penalty for violating joint limits weighted by the gear ratio.""" - def __init__(self, env: RLTaskEnv, cfg: RewardTermCfg): + def __init__(self, env: ManagerBasedRLEnv, cfg: RewardTermCfg): # add default argument if "asset_cfg" not in cfg.params: cfg.params["asset_cfg"] = SceneEntityCfg("robot") @@ -95,7 +95,7 @@ def __init__(self, env: RLTaskEnv, cfg: RewardTermCfg): self.gear_ratio_scaled = self.gear_ratio / torch.max(self.gear_ratio) def __call__( - self, env: RLTaskEnv, threshold: float, gear_ratio: dict[str, float], asset_cfg: SceneEntityCfg + self, env: ManagerBasedRLEnv, threshold: float, gear_ratio: dict[str, float], asset_cfg: SceneEntityCfg ) -> torch.Tensor: # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] @@ -116,7 +116,7 @@ class power_consumption(ManagerTermBase): This is computed as commanded torque times the joint velocity. """ - def __init__(self, env: RLTaskEnv, cfg: RewardTermCfg): + def __init__(self, env: ManagerBasedRLEnv, cfg: RewardTermCfg): # add default argument if "asset_cfg" not in cfg.params: cfg.params["asset_cfg"] = SceneEntityCfg("robot") @@ -130,7 +130,7 @@ def __init__(self, env: RLTaskEnv, cfg: RewardTermCfg): self.gear_ratio[:, index_list] = torch.tensor(value_list, device=env.device) self.gear_ratio_scaled = self.gear_ratio / torch.max(self.gear_ratio) - def __call__(self, env: RLTaskEnv, gear_ratio: dict[str, float], asset_cfg: SceneEntityCfg) -> torch.Tensor: + def __call__(self, env: ManagerBasedRLEnv, gear_ratio: dict[str, float], asset_cfg: SceneEntityCfg) -> torch.Tensor: # extract the used quantities (to enable type-hinting) asset: Articulation = env.scene[asset_cfg.name] # return power = torque * velocity (here actions: joint torques) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/__init__.py similarity index 70% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/__init__.py index 24674e1987..fb502943d3 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/__init__.py @@ -12,40 +12,44 @@ gym.register( id="Isaac-Velocity-Flat-Anymal-B-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.AnymalBFlatEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalBFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Flat-Anymal-B-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.AnymalBFlatEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalBFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Anymal-B-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.AnymalBRoughEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalBRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Anymal-B-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.AnymalBRoughEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalBRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/skrl_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/skrl_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..8a3c4dc2a3 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/skrl_flat_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.005 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "anymal_b_flat" + experiment_name: "" + write_interval: 36 + checkpoint_interval: 360 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 7200 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..54835c25ac --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.005 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "anymal_b_rough" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/flat_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/flat_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/flat_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/rough_env_cfg.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/rough_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/rough_env_cfg.py index 98af7cb097..31c6219bed 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_b/rough_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_b/rough_env_cfg.py @@ -5,7 +5,7 @@ from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg ## # Pre-defined configs diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/__init__.py similarity index 58% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/__init__.py index 1c6346fe87..92966286e3 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/__init__.py @@ -13,41 +13,48 @@ gym.register( id="Isaac-Velocity-Flat-Anymal-C-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.AnymalCFlatEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalCFlatPPORunnerCfg, - "skrl_cfg_entry_point": "omni.isaac.lab_tasks.locomotion.velocity.anymal_c.agents:skrl_cfg.yaml", + "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", }, ) gym.register( id="Isaac-Velocity-Flat-Anymal-C-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.AnymalCFlatEnvCfg_PLAY, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_flat_ppo_cfg.yaml", "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalCFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Anymal-C-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.AnymalCRoughEnvCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_rough_ppo_cfg.yaml", "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalCRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Anymal-C-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.AnymalCRoughEnvCfg_PLAY, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_rough_ppo_cfg.yaml", "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalCRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rl_games_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rl_games_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..c472ce673c --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rl_games_flat_ppo_cfg.yaml @@ -0,0 +1,76 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [128, 128, 128] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: anymal_c_flat + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: True + normalize_input: False + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.6 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 1e-3 + lr_schedule: adaptive + schedule_type: legacy + kl_threshold: 0.01 + score_to_win: 20000 + max_epochs: 300 + save_best_after: 100 + save_frequency: 50 + grad_norm: 1.0 + entropy_coef: 0.005 + truncate_grads: True + e_clip: 0.2 + horizon_length: 24 + minibatch_size: 24576 + mini_epochs: 5 + critic_coef: 2.0 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rl_games_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rl_games_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..042799da1e --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rl_games_rough_ppo_cfg.yaml @@ -0,0 +1,76 @@ +params: + seed: 42 + + # environment wrapper clipping + env: + clip_actions: 1.0 + + algo: + name: a2c_continuous + + model: + name: continuous_a2c_logstd + + network: + name: actor_critic + separate: False + space: + continuous: + mu_activation: None + sigma_activation: None + + mu_init: + name: default + sigma_init: + name: const_initializer + val: 0 + fixed_sigma: True + mlp: + units: [512, 256, 128] + activation: elu + d2rl: False + + initializer: + name: default + regularizer: + name: None + + load_checkpoint: False # flag which sets whether to load the checkpoint + load_path: '' # path to the checkpoint to load + + config: + name: anymal_c_rough + env_name: rlgpu + device: 'cuda:0' + device_name: 'cuda:0' + multi_gpu: False + ppo: True + mixed_precision: True + normalize_input: False + normalize_value: True + value_bootstrap: True + num_actors: -1 # configured from the script (based on num_envs) + reward_shaper: + scale_value: 0.6 + normalize_advantage: True + gamma: 0.99 + tau: 0.95 + learning_rate: 1e-3 + lr_schedule: adaptive + schedule_type: legacy + kl_threshold: 0.01 + score_to_win: 20000 + max_epochs: 1500 + save_best_after: 100 + save_frequency: 50 + grad_norm: 1.0 + entropy_coef: 0.005 + truncate_grads: True + e_clip: 0.2 + horizon_length: 24 + minibatch_size: 24576 + mini_epochs: 5 + critic_coef: 2.0 + clip_value: True + seq_length: 4 + bounds_loss_coef: 0.0 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/skrl_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/skrl_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..a642556d20 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/skrl_flat_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.005 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "anymal_c_flat" + experiment_name: "" + write_interval: 36 + checkpoint_interval: 360 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 7200 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..1e33f0624b --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.005 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "anymal_c_rough" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/flat_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/flat_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/flat_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/rough_env_cfg.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/rough_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/rough_env_cfg.py index 17410ba51d..d137c4f048 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_c/rough_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_c/rough_env_cfg.py @@ -5,7 +5,7 @@ from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg ## # Pre-defined configs diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/__init__.py similarity index 70% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/__init__.py index beb3017408..9f888b9faa 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/__init__.py @@ -13,40 +13,44 @@ gym.register( id="Isaac-Velocity-Flat-Anymal-D-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.AnymalDFlatEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalDFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Flat-Anymal-D-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.AnymalDFlatEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalDFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Anymal-D-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.AnymalDRoughEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalDRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Anymal-D-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.AnymalDRoughEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AnymalDRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/skrl_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/skrl_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..4741fa51bd --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/skrl_flat_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.005 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "anymal_d_flat" + experiment_name: "" + write_interval: 36 + checkpoint_interval: 360 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 7200 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..6ec20a7b2b --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.005 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "anymal_d_rough" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/flat_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/flat_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/flat_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/rough_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py index 846d928764..0e39f8387f 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/anymal_d/rough_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py @@ -5,7 +5,7 @@ from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg ## # Pre-defined configs diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/__init__.py similarity index 69% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/__init__.py index 6f0b3aaf0d..0aed84eaef 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/__init__.py @@ -13,40 +13,44 @@ gym.register( id="Isaac-Velocity-Flat-Cassie-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.CassieFlatEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CassieFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Flat-Cassie-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.CassieFlatEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CassieFlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Cassie-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.CassieRoughEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CassieRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Cassie-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.CassieRoughEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CassieRoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/skrl_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/skrl_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..732c055dce --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/skrl_flat_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "cassie_flat" + experiment_name: "" + write_interval: 120 + checkpoint_interval: 1200 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 24000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..ee559e2cca --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "cassie_rough" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/flat_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/flat_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/flat_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/rough_env_cfg.py similarity index 94% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/rough_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/rough_env_cfg.py index 2468eb4bd6..0d7eb47218 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/cassie/rough_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/cassie/rough_env_cfg.py @@ -7,8 +7,11 @@ from omni.isaac.lab.managers import SceneEntityCfg from omni.isaac.lab.utils import configclass -import omni.isaac.lab_tasks.locomotion.velocity.mdp as mdp -from omni.isaac.lab_tasks.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg, RewardsCfg +import omni.isaac.lab_tasks.manager_based.locomotion.velocity.mdp as mdp +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import ( + LocomotionVelocityRoughEnvCfg, + RewardsCfg, +) ## # Pre-defined configs diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/__init__.py new file mode 100644 index 0000000000..ce6751d2dd --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/__init__.py @@ -0,0 +1,56 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +import gymnasium as gym + +from . import agents, flat_env_cfg, rough_env_cfg + +## +# Register Gym environments. +## + + +gym.register( + id="Isaac-Velocity-Rough-H1-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": rough_env_cfg.H1RoughEnvCfg, + "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.H1RoughPPORunnerCfg, + }, +) + + +gym.register( + id="Isaac-Velocity-Rough-H1-Play-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": rough_env_cfg.H1RoughEnvCfg_PLAY, + "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.H1RoughPPORunnerCfg, + }, +) + + +gym.register( + id="Isaac-Velocity-Flat-H1-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": flat_env_cfg.H1FlatEnvCfg, + "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.H1FlatPPORunnerCfg, + }, +) + + +gym.register( + id="Isaac-Velocity-Flat-H1-Play-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": flat_env_cfg.H1FlatEnvCfg_PLAY, + "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.H1FlatPPORunnerCfg, + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/agents/rsl_rl_cfg.py new file mode 100644 index 0000000000..8a92d05171 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/agents/rsl_rl_cfg.py @@ -0,0 +1,52 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class H1RoughPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 24 + max_iterations = 3000 + save_interval = 50 + experiment_name = "h1_rough" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[512, 256, 128], + critic_hidden_dims=[512, 256, 128], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.01, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=1.0e-3, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) + + +@configclass +class H1FlatPPORunnerCfg(H1RoughPPORunnerCfg): + def __post_init__(self): + super().__post_init__() + + self.max_iterations = 1000 + self.experiment_name = "h1_flat" + self.policy.actor_hidden_dims = [128, 128, 128] + self.policy.critic_hidden_dims = [128, 128, 128] diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/flat_env_cfg.py new file mode 100644 index 0000000000..3dd98ce78b --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/flat_env_cfg.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from .rough_env_cfg import H1RoughEnvCfg + + +@configclass +class H1FlatEnvCfg(H1RoughEnvCfg): + def __post_init__(self): + # post init of parent + super().__post_init__() + + # 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 + self.rewards.feet_air_time.weight = 1.0 + self.rewards.feet_air_time.params["threshold"] = 0.6 + + +class H1FlatEnvCfg_PLAY(H1FlatEnvCfg): + def __post_init__(self) -> None: + # post init of parent + super().__post_init__() + + # make a smaller scene for play + self.scene.num_envs = 50 + self.scene.env_spacing = 2.5 + # disable randomization for play + self.observations.policy.enable_corruption = False + # remove random pushing + self.events.base_external_force_torque = None + self.events.push_robot = None diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/rough_env_cfg.py new file mode 100644 index 0000000000..b933388efe --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/h1/rough_env_cfg.py @@ -0,0 +1,154 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.managers import RewardTermCfg as RewTerm +from omni.isaac.lab.managers import SceneEntityCfg +from omni.isaac.lab.managers import TerminationTermCfg as DoneTerm +from omni.isaac.lab.utils import configclass + +import omni.isaac.lab_tasks.manager_based.locomotion.velocity.mdp as mdp +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import ( + LocomotionVelocityRoughEnvCfg, + RewardsCfg, +) + +## +# Pre-defined configs +## +from omni.isaac.lab_assets import H1_MINIMAL_CFG # isort: skip + + +@configclass +class H1Rewards(RewardsCfg): + 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"), + }, + ) + # Penalize ankle joint limits + dof_pos_limits = RewTerm( + func=mdp.joint_pos_limits, weight=-1.0, params={"asset_cfg": SceneEntityCfg("robot", joint_names=".*_ankle")} + ) + # Penalize deviation from default of the joints that are not essential for locomotion + 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 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=".*torso_link"), "threshold": 1.0}, + ) + + +@configclass +class H1RoughEnvCfg(LocomotionVelocityRoughEnvCfg): + rewards: H1Rewards = H1Rewards() + terminations: TerminationsCfg = TerminationsCfg() + + def __post_init__(self): + # post init of parent + super().__post_init__() + # Scene + self.scene.robot = H1_MINIMAL_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") + self.scene.height_scanner.prim_path = "{ENV_REGEX_NS}/Robot/torso_link" + + # Randomization + self.events.push_robot = None + 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), + }, + } + + # Terminations + self.terminations.base_contact.params["sensor_cfg"].body_names = [".*torso_link"] + + # Rewards + 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 + + # 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) + + +@configclass +class H1RoughEnvCfg_PLAY(H1RoughEnvCfg): + def __post_init__(self): + # post init of parent + super().__post_init__() + + # make a smaller scene for play + self.scene.num_envs = 50 + self.scene.env_spacing = 2.5 + self.episode_length_s = 40.0 + # 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 + + 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) + # disable randomization for play + self.observations.policy.enable_corruption = False + # remove random pushing + self.events.base_external_force_torque = None + self.events.push_robot = None diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/__init__.py similarity index 70% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/__init__.py index d9c7110ec9..774b3b9195 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/__init__.py @@ -13,40 +13,44 @@ gym.register( id="Isaac-Velocity-Flat-Unitree-A1-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.UnitreeA1FlatEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeA1FlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Flat-Unitree-A1-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.UnitreeA1FlatEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeA1FlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Unitree-A1-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.UnitreeA1RoughEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeA1RoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Unitree-A1-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.UnitreeA1RoughEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeA1RoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/skrl_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/skrl_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..c381fbe2f9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/skrl_flat_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "unitree_a1_flat" + experiment_name: "" + write_interval: 36 + checkpoint_interval: 360 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 7200 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..14d6f26aa4 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "unitree_a1_rough" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/flat_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/flat_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/flat_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/rough_env_cfg.py similarity index 94% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/rough_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/rough_env_cfg.py index 0c0d3c064e..543dadabe9 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_a1/rough_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_a1/rough_env_cfg.py @@ -5,7 +5,7 @@ from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg ## # Pre-defined configs @@ -31,7 +31,7 @@ def __post_init__(self): # event self.events.push_robot = None - self.events.add_base_mass.params["mass_range"] = (-1.0, 3.0) + 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) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/__init__.py similarity index 70% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/__init__.py index 73d6a69338..4781b9dd9b 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/__init__.py @@ -13,40 +13,44 @@ gym.register( id="Isaac-Velocity-Flat-Unitree-Go1-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.UnitreeGo1FlatEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo1FlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Flat-Unitree-Go1-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.UnitreeGo1FlatEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo1FlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Unitree-Go1-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.UnitreeGo1RoughEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo1RoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Unitree-Go1-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.UnitreeGo1RoughEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo1RoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/skrl_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/skrl_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..88f9d421ea --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/skrl_flat_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "unitree_go1_flat" + experiment_name: "" + write_interval: 36 + checkpoint_interval: 360 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 7200 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..ab3208bcae --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "unitree_go1_rough" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/flat_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/flat_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/flat_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/rough_env_cfg.py similarity index 94% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/rough_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/rough_env_cfg.py index 452034587b..c5757602ac 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go1/rough_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go1/rough_env_cfg.py @@ -5,7 +5,7 @@ from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg ## # Pre-defined configs @@ -31,7 +31,7 @@ def __post_init__(self): # event self.events.push_robot = None - self.events.add_base_mass.params["mass_range"] = (-1.0, 3.0) + 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) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/__init__.py similarity index 75% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/__init__.py index 52df8eb3d8..c7819f7564 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/__init__.py @@ -13,41 +13,45 @@ gym.register( id="Isaac-Velocity-Flat-Unitree-Go2-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.UnitreeGo2FlatEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo2FlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Flat-Unitree-Go2-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": flat_env_cfg.UnitreeGo2FlatEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo2FlatPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_flat_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Unitree-Go2-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.UnitreeGo2RoughEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo2RoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Velocity-Rough-Unitree-Go2-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": rough_env_cfg.UnitreeGo2RoughEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.UnitreeGo2RoughPPORunnerCfg, + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/skrl_flat_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/skrl_flat_ppo_cfg.yaml new file mode 100644 index 0000000000..758f07a2a9 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/skrl_flat_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [128, 128, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "unitree_go2_flat" + experiment_name: "" + write_interval: 36 + checkpoint_interval: 360 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 7200 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/skrl_rough_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/skrl_rough_ppo_cfg.yaml new file mode 100644 index 0000000000..d3f2a74712 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/agents/skrl_rough_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: True + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.01 + value_loss_scale: 1.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "unitree_go2_rough" + experiment_name: "" + write_interval: 180 + checkpoint_interval: 1800 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 36000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/flat_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/flat_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/flat_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/flat_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/rough_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/rough_env_cfg.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/rough_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/rough_env_cfg.py index 30766e7fc8..ab8fb31846 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/config/unitree_go2/rough_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/unitree_go2/rough_env_cfg.py @@ -5,8 +5,8 @@ from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg -import omni.isaac.lab_tasks.locomotion.velocity.mdp as mdp +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg +import omni.isaac.lab_tasks.manager_based.locomotion.velocity.mdp as mdp ## # Pre-defined configs @@ -32,7 +32,7 @@ def __post_init__(self): # event self.events.push_robot = None - self.events.add_base_mass.params["mass_range"] = (-1.0, 3.0) + self.events.add_base_mass.params["mass_distribution_params"] = (-1.0, 3.0) self.events.add_base_mass.params["asset_cfg"].body_names = "base" self.events.base_external_force_torque.params["asset_cfg"].body_names = "base" self.events.reset_robot_joints.params["position_range"] = (1.0, 1.0) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/curriculums.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/curriculums.py similarity index 93% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/curriculums.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/curriculums.py index e5a8ac9729..38e7b93d10 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/curriculums.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/curriculums.py @@ -20,11 +20,11 @@ from omni.isaac.lab.terrains import TerrainImporter if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv def terrain_levels_vel( - env: RLTaskEnv, env_ids: Sequence[int], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") + env: ManagerBasedRLEnv, env_ids: Sequence[int], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: """Curriculum based on the distance the robot walked when commanded to move at a desired velocity. diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/rewards.py similarity index 55% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/rewards.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/rewards.py index b784abc2f7..b3a1756954 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/mdp/rewards.py @@ -10,12 +10,15 @@ from omni.isaac.lab.managers import SceneEntityCfg from omni.isaac.lab.sensors import ContactSensor +from omni.isaac.lab.utils.math import quat_rotate_inverse, yaw_quat if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv -def feet_air_time(env: RLTaskEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float) -> torch.Tensor: +def feet_air_time( + env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float +) -> torch.Tensor: """Reward long steps taken by the feet using L2-kernel. This function rewards the agent for taking steps that are longer than a threshold. This helps ensure @@ -55,3 +58,36 @@ def feet_air_time_positive_biped(env, command_name: str, threshold: float, senso # no reward for zero command reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1 return reward + + +def feet_slide(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: + # Penalize feet sliding + contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name] + contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0 + asset = env.scene[asset_cfg.name] + body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2] + reward = torch.sum(body_vel.norm(dim=-1) * contacts, dim=1) + return reward + + +def track_lin_vel_xy_yaw_frame_exp( + env, std: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") +) -> torch.Tensor: + """Reward tracking of linear velocity commands (xy axes) in the gravity aligned robot frame using exponential kernel.""" + # extract the used quantities (to enable type-hinting) + asset = env.scene[asset_cfg.name] + vel_yaw = quat_rotate_inverse(yaw_quat(asset.data.root_quat_w), asset.data.root_lin_vel_w[:, :3]) + lin_vel_error = torch.sum( + torch.square(env.command_manager.get_command(command_name)[:, :2] - vel_yaw[:, :2]), dim=1 + ) + return torch.exp(-lin_vel_error / std**2) + + +def track_ang_vel_z_world_exp( + env, command_name: str, std: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") +) -> torch.Tensor: + """Reward tracking of angular velocity commands (yaw) in world frame using exponential kernel.""" + # extract the used quantities (to enable type-hinting) + asset = env.scene[asset_cfg.name] + ang_vel_error = torch.square(env.command_manager.get_command(command_name)[:, 2] - asset.data.root_ang_vel_w[:, 2]) + return torch.exp(-ang_vel_error / std**2) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/velocity_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/velocity_env_cfg.py similarity index 80% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/velocity_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/velocity_env_cfg.py index 7eff57b139..26bf6476f1 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/locomotion/velocity/velocity_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/velocity_env_cfg.py @@ -8,7 +8,7 @@ import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import CurriculumTermCfg as CurrTerm from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup @@ -20,9 +20,10 @@ from omni.isaac.lab.sensors import ContactSensorCfg, RayCasterCfg, patterns from omni.isaac.lab.terrains import TerrainImporterCfg from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR from omni.isaac.lab.utils.noise import AdditiveUniformNoiseCfg as Unoise -import omni.isaac.lab_tasks.locomotion.velocity.mdp as mdp +import omni.isaac.lab_tasks.manager_based.locomotion.velocity.mdp as mdp ## # Pre-defined configs @@ -39,54 +40,46 @@ 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="{NVIDIA_NUCLEUS_DIR}/Materials/Base/Architecture/Shingles_01.mdl", - project_uvw=True, - ), - debug_vis=False, - ) - # robots - robot: ArticulationCfg = MISSING - # sensors - #height_scanner = RayCasterCfg( - # prim_path="{ENV_REGEX_NS}/Robot/base", - # offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), - # attach_yaw_only=True, - # pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), - # debug_vis=False, - # mesh_prim_paths=["/World/ground"], - #) - height_scanner = RayCasterCfg( - prim_path="{ENV_REGEX_NS}/Robot/Head_lower", - #offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), - #attach_yaw_only=True, - pattern_cfg=patterns.OmniPatternCfg(), - debug_vis=True, - mesh_prim_paths=["/World/ground"], - ) - contact_forces = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/.*", history_length=3, track_air_time=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), - ) + # 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 + height_scanner = RayCasterCfg( + prim_path="{ENV_REGEX_NS}/Robot/base", + offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), + attach_yaw_only=True, + pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), + debug_vis=False, + mesh_prim_paths=["/World/ground"], + ) + contact_forces = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/.*", history_length=3, track_air_time=True) + # lights + sky_light = AssetBaseCfg( + prim_path="/World/skyLight", + spawn=sim_utils.DomeLightCfg( + intensity=900.0, + texture_file=f"{ISAAC_NUCLEUS_DIR}/Materials/Textures/Skies/PolyHaven/kloofendal_43d_clear_puresky_4k.hdr", + ), + ) # Camera (takes alot of processing power --> might need to reduce the numof envs for training) @@ -197,11 +190,15 @@ class EventCfg: }, ) - add_base_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - mode="startup", - params={"asset_cfg": SceneEntityCfg("robot", body_names="base"), "mass_range": (-5.0, 5.0), "operation": "add"}, - ) + add_base_mass = EventTerm( + func=mdp.randomize_rigid_body_mass, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names="base"), + "mass_distribution_params": (-5.0, 5.0), + "operation": "add", + }, + ) # reset base_external_force_torque = EventTerm( @@ -308,8 +305,8 @@ class CurriculumCfg: @configclass -class LocomotionVelocityRoughEnvCfg(RLTaskEnvCfg): - """Configuration for the locomotion velocity-tracking environment.""" +class LocomotionVelocityRoughEnvCfg(ManagerBasedRLEnvCfg): + """Configuration for the locomotion velocity-tracking environment.""" # Scene settings scene: MySceneCfg = MySceneCfg(num_envs=4096, env_spacing=2.5) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/cabinet_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/cabinet_env_cfg.py similarity index 98% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/cabinet_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/cabinet_env_cfg.py index ee6e94da56..ddd28ee50e 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/cabinet_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/cabinet_env_cfg.py @@ -9,7 +9,7 @@ import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.actuators.actuator_cfg import ImplicitActuatorCfg from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -259,7 +259,7 @@ class TerminationsCfg: @configclass -class CabinetEnvCfg(RLTaskEnvCfg): +class CabinetEnvCfg(ManagerBasedRLEnvCfg): """Configuration for the cabinet environment.""" # Scene settings diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/__init__.py new file mode 100644 index 0000000000..178a87ed42 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/__init__.py @@ -0,0 +1,64 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +import gymnasium as gym + +from . import agents, ik_abs_env_cfg, ik_rel_env_cfg, joint_pos_env_cfg + +## +# Register Gym environments. +## + +## +# Joint Position Control +## + +gym.register( + id="Isaac-Open-Drawer-Franka-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + kwargs={ + "env_cfg_entry_point": joint_pos_env_cfg.FrankaCabinetEnvCfg, + "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CabinetPPORunnerCfg, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, + disable_env_checker=True, +) + +gym.register( + id="Isaac-Open-Drawer-Franka-Play-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + kwargs={ + "env_cfg_entry_point": joint_pos_env_cfg.FrankaCabinetEnvCfg_PLAY, + }, + disable_env_checker=True, +) + + +## +# Inverse Kinematics - Absolute Pose Control +## + +gym.register( + id="Isaac-Open-Drawer-Franka-IK-Abs-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + kwargs={ + "env_cfg_entry_point": ik_abs_env_cfg.FrankaCabinetEnvCfg, + }, + disable_env_checker=True, +) + +## +# Inverse Kinematics - Relative Pose Control +## + +gym.register( + id="Isaac-Open-Drawer-Franka-IK-Rel-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + kwargs={ + "env_cfg_entry_point": ik_rel_env_cfg.FrankaCabinetEnvCfg, + }, + disable_env_checker=True, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/rl_games_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..201833aad5 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: False + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [256, 128, 64] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [256, 128, 64] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 96 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 5.e-4 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.008 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 1.e-3 + value_loss_scale: 2.0 + kl_threshold: 0 + rewards_shaper_scale: 1.0 + # logging and checkpoint + experiment: + directory: "franka_open_drawer" + experiment_name: "" + write_interval: 192 + checkpoint_interval: 1920 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 38400 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/ik_abs_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/ik_abs_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/ik_abs_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/ik_abs_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/ik_rel_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/ik_rel_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/ik_rel_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/ik_rel_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/joint_pos_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/joint_pos_env_cfg.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/joint_pos_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/joint_pos_env_cfg.py index 16b278a578..730759a1d4 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/joint_pos_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/config/franka/joint_pos_env_cfg.py @@ -7,14 +7,17 @@ from omni.isaac.lab.sensors.frame_transformer.frame_transformer_cfg import OffsetCfg from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.manipulation.cabinet import mdp -from omni.isaac.lab_tasks.manipulation.cabinet.cabinet_env_cfg import CabinetEnvCfg +from omni.isaac.lab_tasks.manager_based.manipulation.cabinet import mdp + +from omni.isaac.lab_tasks.manager_based.manipulation.cabinet.cabinet_env_cfg import ( # isort: skip + FRAME_MARKER_SMALL_CFG, + CabinetEnvCfg, +) ## # Pre-defined configs ## from omni.isaac.lab_assets.franka import FRANKA_PANDA_CFG # isort: skip -from omni.isaac.lab_tasks.manipulation.cabinet.cabinet_env_cfg import FRAME_MARKER_SMALL_CFG # isort: skip @configclass diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/observations.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/observations.py similarity index 83% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/observations.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/observations.py index 78dc92bb8b..6c13b989cc 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/observations.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/observations.py @@ -13,10 +13,10 @@ from omni.isaac.lab.sensors import FrameTransformerData if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv -def rel_ee_object_distance(env: RLTaskEnv) -> torch.Tensor: +def rel_ee_object_distance(env: ManagerBasedRLEnv) -> torch.Tensor: """The distance between the end-effector and the object.""" ee_tf_data: FrameTransformerData = env.scene["ee_frame"].data object_data: ArticulationData = env.scene["object"].data @@ -24,7 +24,7 @@ def rel_ee_object_distance(env: RLTaskEnv) -> torch.Tensor: return object_data.root_pos_w - ee_tf_data.target_pos_w[..., 0, :] -def rel_ee_drawer_distance(env: RLTaskEnv) -> torch.Tensor: +def rel_ee_drawer_distance(env: ManagerBasedRLEnv) -> torch.Tensor: """The distance between the end-effector and the object.""" ee_tf_data: FrameTransformerData = env.scene["ee_frame"].data cabinet_tf_data: FrameTransformerData = env.scene["cabinet_frame"].data @@ -32,7 +32,7 @@ def rel_ee_drawer_distance(env: RLTaskEnv) -> torch.Tensor: return cabinet_tf_data.target_pos_w[..., 0, :] - ee_tf_data.target_pos_w[..., 0, :] -def fingertips_pos(env: RLTaskEnv) -> torch.Tensor: +def fingertips_pos(env: ManagerBasedRLEnv) -> torch.Tensor: """The position of the fingertips relative to the environment origins.""" ee_tf_data: FrameTransformerData = env.scene["ee_frame"].data fingertips_pos = ee_tf_data.target_pos_w[..., 1:, :] - env.scene.env_origins.unsqueeze(1) @@ -40,7 +40,7 @@ def fingertips_pos(env: RLTaskEnv) -> torch.Tensor: return fingertips_pos.view(env.num_envs, -1) -def ee_pos(env: RLTaskEnv) -> torch.Tensor: +def ee_pos(env: ManagerBasedRLEnv) -> torch.Tensor: """The position of the end-effector relative to the environment origins.""" ee_tf_data: FrameTransformerData = env.scene["ee_frame"].data ee_pos = ee_tf_data.target_pos_w[..., 0, :] - env.scene.env_origins @@ -48,7 +48,7 @@ def ee_pos(env: RLTaskEnv) -> torch.Tensor: return ee_pos -def ee_quat(env: RLTaskEnv, make_quat_unique: bool = True) -> torch.Tensor: +def ee_quat(env: ManagerBasedRLEnv, make_quat_unique: bool = True) -> torch.Tensor: """The orientation of the end-effector in the environment frame. If :attr:`make_quat_unique` is True, the quaternion is made unique by ensuring the real part is positive. diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/rewards.py similarity index 90% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/rewards.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/rewards.py index d5a479f0a3..4474eb3ec7 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/cabinet/mdp/rewards.py @@ -12,10 +12,10 @@ from omni.isaac.lab.utils.math import matrix_from_quat if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv -def approach_ee_handle(env: RLTaskEnv, threshold: float) -> torch.Tensor: +def approach_ee_handle(env: ManagerBasedRLEnv, threshold: float) -> torch.Tensor: r"""Reward the robot for reaching the drawer handle using inverse-square law. It uses a piecewise function to reward the robot for reaching the handle. @@ -40,7 +40,7 @@ def approach_ee_handle(env: RLTaskEnv, threshold: float) -> torch.Tensor: return torch.where(distance <= threshold, 2 * reward, reward) -def align_ee_handle(env: RLTaskEnv) -> torch.Tensor: +def align_ee_handle(env: ManagerBasedRLEnv) -> torch.Tensor: """Reward for aligning the end-effector with the handle. The reward is based on the alignment of the gripper with the handle. It is computed as follows: @@ -72,7 +72,7 @@ def align_ee_handle(env: RLTaskEnv) -> torch.Tensor: return 0.5 * (torch.sign(align_z) * align_z**2 + torch.sign(align_x) * align_x**2) -def align_grasp_around_handle(env: RLTaskEnv) -> torch.Tensor: +def align_grasp_around_handle(env: ManagerBasedRLEnv) -> torch.Tensor: """Bonus for correct hand orientation around the handle. The correct hand orientation is when the left finger is above the handle and the right finger is below the handle. @@ -91,7 +91,7 @@ def align_grasp_around_handle(env: RLTaskEnv) -> torch.Tensor: return is_graspable -def approach_gripper_handle(env: RLTaskEnv, offset: float = 0.04) -> torch.Tensor: +def approach_gripper_handle(env: ManagerBasedRLEnv, offset: float = 0.04) -> torch.Tensor: """Reward the robot's gripper reaching the drawer handle with the right pose. This function returns the distance of fingertips to the handle when the fingers are in a grasping orientation @@ -114,7 +114,9 @@ def approach_gripper_handle(env: RLTaskEnv, offset: float = 0.04) -> torch.Tenso return is_graspable * ((offset - lfinger_dist) + (offset - rfinger_dist)) -def grasp_handle(env: RLTaskEnv, threshold: float, open_joint_pos: float, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def grasp_handle( + env: ManagerBasedRLEnv, threshold: float, open_joint_pos: float, asset_cfg: SceneEntityCfg +) -> torch.Tensor: """Reward for closing the fingers when being close to the handle. The :attr:`threshold` is the distance from the handle at which the fingers should be closed. @@ -133,7 +135,7 @@ def grasp_handle(env: RLTaskEnv, threshold: float, open_joint_pos: float, asset_ return is_close * torch.sum(open_joint_pos - gripper_joint_pos, dim=-1) -def open_drawer_bonus(env: RLTaskEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def open_drawer_bonus(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Bonus for opening the drawer given by the joint position of the drawer. The bonus is given when the drawer is open. If the grasp is around the handle, the bonus is doubled. @@ -144,7 +146,7 @@ def open_drawer_bonus(env: RLTaskEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor return (is_graspable + 1.0) * drawer_pos -def multi_stage_open_drawer(env: RLTaskEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def multi_stage_open_drawer(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Multi-stage bonus for opening the drawer. Depending on the drawer's position, the reward is given in three stages: easy, medium, and hard. diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/__init__.py similarity index 76% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/__init__.py index ca7fe118e5..9b2045364f 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/__init__.py @@ -17,23 +17,25 @@ gym.register( id="Isaac-Repose-Cube-Allegro-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": allegro_env_cfg.AllegroCubeEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AllegroCubePPORunnerCfg, "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Repose-Cube-Allegro-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": allegro_env_cfg.AllegroCubeEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AllegroCubePPORunnerCfg, "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", }, ) @@ -43,22 +45,24 @@ gym.register( id="Isaac-Repose-Cube-Allegro-NoVelObs-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": allegro_env_cfg.AllegroCubeNoVelObsEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AllegroCubeNoVelObsPPORunnerCfg, "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", }, ) gym.register( id="Isaac-Repose-Cube-Allegro-NoVelObs-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": allegro_env_cfg.AllegroCubeNoVelObsEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.AllegroCubeNoVelObsPPORunnerCfg, "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", }, ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/rl_games_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..dfa2933421 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: False + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [512, 256, 128] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 5 + mini_batches: 4 + discount_factor: 0.998 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.002 + value_loss_scale: 2.0 + kl_threshold: 0 + rewards_shaper_scale: 0.1 + # logging and checkpoint + experiment: + directory: "allegro_cube" + experiment_name: "" + write_interval: 600 + checkpoint_interval: 6000 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 120000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/allegro_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/allegro_env_cfg.py similarity index 94% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/allegro_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/allegro_env_cfg.py index 36ec483bcd..5a63ad3aec 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/config/allegro_hand/allegro_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/config/allegro_hand/allegro_env_cfg.py @@ -5,7 +5,7 @@ from omni.isaac.lab.utils import configclass -import omni.isaac.lab_tasks.manipulation.inhand.inhand_env_cfg as inhand_env_cfg +import omni.isaac.lab_tasks.manager_based.manipulation.inhand.inhand_env_cfg as inhand_env_cfg ## # Pre-defined configs diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/inhand_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/inhand_env_cfg.py similarity index 96% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/inhand_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/inhand_env_cfg.py index 5c779a14ac..1414bd4e5d 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/inhand_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/inhand_env_cfg.py @@ -9,7 +9,7 @@ import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -23,7 +23,7 @@ from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR from omni.isaac.lab.utils.noise import AdditiveGaussianNoiseCfg as Gnoise -import omni.isaac.lab_tasks.manipulation.inhand.mdp as mdp +import omni.isaac.lab_tasks.manager_based.manipulation.inhand.mdp as mdp ## # Scene definition @@ -191,7 +191,7 @@ class EventCfg: mode="startup", params={ "asset_cfg": SceneEntityCfg("robot", body_names=".*"), - "mass_range": (0.95, 1.05), + "mass_distribution_params": (0.95, 1.05), "operation": "scale", }, ) @@ -200,8 +200,8 @@ class EventCfg: mode="startup", params={ "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), - "stiffness_range": (0.3, 3.0), # default: 3.0 - "damping_range": (0.75, 1.5), # default: 0.1 + "stiffness_distribution_params": (0.3, 3.0), # default: 3.0 + "damping_distribution_params": (0.75, 1.5), # default: 0.1 "operation": "scale", "distribution": "log_uniform", }, @@ -224,7 +224,7 @@ class EventCfg: mode="startup", params={ "asset_cfg": SceneEntityCfg("object"), - "mass_range": (0.4, 1.6), + "mass_distribution_params": (0.4, 1.6), "operation": "scale", }, ) @@ -308,7 +308,7 @@ class TerminationsCfg: @configclass -class InHandObjectEnvCfg(RLTaskEnvCfg): +class InHandObjectEnvCfg(ManagerBasedRLEnvCfg): """Configuration for the in hand reorientation environment.""" # Scene settings diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/commands/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/commands/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/commands/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/commands/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/commands/commands_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/commands/commands_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/commands/commands_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/commands/commands_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/commands/orientation_command.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/commands/orientation_command.py similarity index 98% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/commands/orientation_command.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/commands/orientation_command.py index f62ac4aa31..da6b6ba1ea 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/commands/orientation_command.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/commands/orientation_command.py @@ -17,7 +17,7 @@ from omni.isaac.lab.markers.visualization_markers import VisualizationMarkers if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv from .commands_cfg import InHandReOrientationCommandCfg @@ -41,7 +41,7 @@ class InHandReOrientationCommand(CommandTerm): cfg: InHandReOrientationCommandCfg """Configuration for the command term.""" - def __init__(self, cfg: InHandReOrientationCommandCfg, env: RLTaskEnv): + def __init__(self, cfg: InHandReOrientationCommandCfg, env: ManagerBasedRLEnv): """Initialize the command term class. Args: diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/events.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/events.py similarity index 98% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/events.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/events.py index 0dc075f2b4..a52fc2bd83 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/events.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/events.py @@ -16,7 +16,7 @@ from omni.isaac.lab.utils.math import sample_uniform if TYPE_CHECKING: - from omni.isaac.lab.envs import BaseEnv + from omni.isaac.lab.envs import ManagerBasedEnv class reset_joints_within_limits_range(ManagerTermBase): @@ -53,7 +53,7 @@ class reset_joints_within_limits_range(ManagerTermBase): """ - def __init__(self, cfg: EventTermCfg, env: BaseEnv): + def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv): # initialize the base class super().__init__(cfg, env) @@ -148,7 +148,7 @@ def __init__(self, cfg: EventTermCfg, env: BaseEnv): def __call__( self, - env: BaseEnv, + env: ManagerBasedEnv, env_ids: torch.Tensor, position_range: dict[str, tuple[float | None, float | None]], velocity_range: dict[str, tuple[float | None, float | None]], diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/observations.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/observations.py similarity index 89% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/observations.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/observations.py index d518b274cf..a7d4335842 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/observations.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/observations.py @@ -10,7 +10,7 @@ import omni.isaac.lab.utils.math as math_utils from omni.isaac.lab.assets import RigidObject -from omni.isaac.lab.envs import RLTaskEnv +from omni.isaac.lab.envs import ManagerBasedRLEnv from omni.isaac.lab.managers import SceneEntityCfg if TYPE_CHECKING: @@ -18,7 +18,7 @@ def goal_quat_diff( - env: RLTaskEnv, asset_cfg: SceneEntityCfg, command_name: str, make_quat_unique: bool + env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg, command_name: str, make_quat_unique: bool ) -> torch.Tensor: """Goal orientation relative to the asset's root frame. diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/rewards.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/rewards.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/rewards.py index e6f5ce90c4..302b85871c 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/rewards.py @@ -10,7 +10,7 @@ import omni.isaac.lab.utils.math as math_utils from omni.isaac.lab.assets import RigidObject -from omni.isaac.lab.envs import RLTaskEnv +from omni.isaac.lab.envs import ManagerBasedRLEnv from omni.isaac.lab.managers import SceneEntityCfg if TYPE_CHECKING: @@ -18,7 +18,7 @@ def success_bonus( - env: RLTaskEnv, command_name: str, object_cfg: SceneEntityCfg = SceneEntityCfg("object") + env: ManagerBasedRLEnv, command_name: str, object_cfg: SceneEntityCfg = SceneEntityCfg("object") ) -> torch.Tensor: """Bonus reward for successfully reaching the goal. @@ -45,7 +45,7 @@ def success_bonus( def track_pos_l2( - env: RLTaskEnv, command_name: str, object_cfg: SceneEntityCfg = SceneEntityCfg("object") + env: ManagerBasedRLEnv, command_name: str, object_cfg: SceneEntityCfg = SceneEntityCfg("object") ) -> torch.Tensor: """Reward for tracking the object position using the L2 norm. @@ -69,7 +69,7 @@ def track_pos_l2( def track_orientation_inv_l2( - env: RLTaskEnv, + env: ManagerBasedRLEnv, command_name: str, object_cfg: SceneEntityCfg = SceneEntityCfg("object"), rot_eps: float = 1e-3, diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/terminations.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/terminations.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/terminations.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/terminations.py index f3fb18ef17..a56df2a8b9 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/inhand/mdp/terminations.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/inhand/mdp/terminations.py @@ -8,14 +8,14 @@ import torch from typing import TYPE_CHECKING -from omni.isaac.lab.envs import RLTaskEnv +from omni.isaac.lab.envs import ManagerBasedRLEnv from omni.isaac.lab.managers import SceneEntityCfg if TYPE_CHECKING: from .commands import InHandReOrientationCommand -def max_consecutive_success(env: RLTaskEnv, num_success: int, command_name: str) -> torch.Tensor: +def max_consecutive_success(env: ManagerBasedRLEnv, num_success: int, command_name: str) -> torch.Tensor: """Check if the task has been completed consecutively for a certain number of times. Args: @@ -29,7 +29,7 @@ def max_consecutive_success(env: RLTaskEnv, num_success: int, command_name: str) def object_away_from_goal( - env: RLTaskEnv, + env: ManagerBasedRLEnv, threshold: float, command_name: str, object_cfg: SceneEntityCfg = SceneEntityCfg("object"), @@ -57,7 +57,7 @@ def object_away_from_goal( def object_away_from_robot( - env: RLTaskEnv, + env: ManagerBasedRLEnv, threshold: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), object_cfg: SceneEntityCfg = SceneEntityCfg("object"), diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/__init__.py similarity index 54% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/__init__.py index d4946cbb2a..cc007a17bc 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/__init__.py @@ -17,22 +17,24 @@ gym.register( id="Isaac-Lift-Cube-Franka-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", kwargs={ "env_cfg_entry_point": joint_pos_env_cfg.FrankaCubeLiftEnvCfg, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.LiftCubePPORunnerCfg, "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", }, disable_env_checker=True, ) gym.register( id="Isaac-Lift-Cube-Franka-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", kwargs={ "env_cfg_entry_point": joint_pos_env_cfg.FrankaCubeLiftEnvCfg_PLAY, "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.LiftCubePPORunnerCfg, "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", }, disable_env_checker=True, ) @@ -43,25 +45,13 @@ gym.register( id="Isaac-Lift-Cube-Franka-IK-Abs-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", kwargs={ "env_cfg_entry_point": ik_abs_env_cfg.FrankaCubeLiftEnvCfg, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.LiftCubePPORunnerCfg, - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", }, disable_env_checker=True, ) -gym.register( - id="Isaac-Lift-Cube-Franka-IK-Abs-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_abs_env_cfg.FrankaCubeLiftEnvCfg_PLAY, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.LiftCubePPORunnerCfg, - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, - disable_env_checker=True, -) ## # Inverse Kinematics - Relative Pose Control @@ -69,23 +59,10 @@ gym.register( id="Isaac-Lift-Cube-Franka-IK-Rel-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", kwargs={ "env_cfg_entry_point": ik_rel_env_cfg.FrankaCubeLiftEnvCfg, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.LiftCubePPORunnerCfg, - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", "robomimic_bc_cfg_entry_point": os.path.join(agents.__path__[0], "robomimic/bc.json"), }, disable_env_checker=True, ) - -gym.register( - id="Isaac-Lift-Cube-Franka-IK-Rel-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_rel_env_cfg.FrankaCubeLiftEnvCfg_PLAY, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.LiftCubePPORunnerCfg, - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, - disable_env_checker=True, -) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/__init__.py new file mode 100644 index 0000000000..b3a5a970d3 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_cfg # noqa: F401, F403 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/rl_games_ppo_cfg.yaml similarity index 88% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/rl_games_ppo_cfg.yaml index d996a8e75d..6ed68d8891 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/rl_games_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/rl_games_ppo_cfg.yaml @@ -40,7 +40,7 @@ params: load_path: '' # path to the checkpoint to load config: - name: reach + name: franka_lift env_name: rlgpu device: 'cuda:0' device_name: 'cuda:0' @@ -56,23 +56,24 @@ params: normalize_advantage: True gamma: 0.99 tau: 0.95 - learning_rate: 3e-4 + learning_rate: 1e-4 lr_schedule: adaptive schedule_type: legacy - kl_threshold: 0.008 + kl_threshold: 0.01 score_to_win: 100000000 - max_epochs: 500 + max_epochs: 1500 save_best_after: 100 save_frequency: 50 print_stats: True grad_norm: 1.0 - entropy_coef: 0.0 + entropy_coef: 0.001 truncate_grads: True e_clip: 0.2 - horizon_length: 16 - minibatch_size: 4096 #2048 + horizon_length: 24 + minibatch_size: 24576 mini_epochs: 8 critic_coef: 4 clip_value: True + clip_actions: False seq_len: 4 bounds_loss_coef: 0.0001 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/robomimic/bc.json b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/robomimic/bc.json similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/robomimic/bc.json rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/robomimic/bc.json diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/robomimic/bcq.json b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/robomimic/bcq.json similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/robomimic/bcq.json rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/robomimic/bcq.json diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/rsl_rl_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/rsl_rl_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/sb3_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/sb3_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/sb3_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/sb3_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/skrl_ppo_cfg.yaml similarity index 78% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/skrl_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/skrl_ppo_cfg.yaml index aa8abf0ba1..eef019ead0 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/agents/skrl_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/agents/skrl_ppo_cfg.yaml @@ -1,10 +1,10 @@ seed: 42 # Models are instantiated using skrl's model instantiator utility -# https://skrl.readthedocs.io/en/develop/modules/skrl.utils.model_instantiators.html +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models: separate: True - policy: # see skrl.utils.model_instantiators.gaussian_model for parameter details + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details clip_actions: False clip_log_std: True initial_log_std: 0 @@ -16,7 +16,7 @@ models: output_shape: "Shape.ACTIONS" output_activation: "" output_scale: 1.0 - value: # see skrl.utils.model_instantiators.deterministic_model for parameter details + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details clip_actions: False input_shape: "Shape.STATES" hiddens: [256, 128, 64] @@ -27,7 +27,7 @@ models: # PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) -# https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent: rollouts: 16 learning_epochs: 8 @@ -61,6 +61,7 @@ agent: # Sequential trainer -# https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.sequential.html +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html trainer: timesteps: 24000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/ik_abs_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/ik_abs_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/ik_abs_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/ik_abs_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/ik_rel_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/ik_rel_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/ik_rel_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/ik_rel_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/joint_pos_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/joint_pos_env_cfg.py similarity index 95% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/joint_pos_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/joint_pos_env_cfg.py index 9a9c592009..b4e555bb7b 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/config/franka/joint_pos_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/config/franka/joint_pos_env_cfg.py @@ -11,8 +11,8 @@ from omni.isaac.lab.utils import configclass from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR -from omni.isaac.lab_tasks.manipulation.lift import mdp -from omni.isaac.lab_tasks.manipulation.lift.lift_env_cfg import LiftEnvCfg +from omni.isaac.lab_tasks.manager_based.manipulation.lift import mdp +from omni.isaac.lab_tasks.manager_based.manipulation.lift.lift_env_cfg import LiftEnvCfg ## # Pre-defined configs diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/lift_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/lift_env_cfg.py similarity index 95% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/lift_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/lift_env_cfg.py index 0f73ba9f30..e2b86da516 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/lift_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/lift_env_cfg.py @@ -7,7 +7,7 @@ import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import CurriculumTermCfg as CurrTerm from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup @@ -137,22 +137,22 @@ class RewardsCfg: reaching_object = RewTerm(func=mdp.object_ee_distance, params={"std": 0.1}, weight=1.0) - lifting_object = RewTerm(func=mdp.object_is_lifted, params={"minimal_height": 0.06}, weight=15.0) + lifting_object = RewTerm(func=mdp.object_is_lifted, params={"minimal_height": 0.04}, weight=15.0) object_goal_tracking = RewTerm( func=mdp.object_goal_distance, - params={"std": 0.3, "minimal_height": 0.06, "command_name": "object_pose"}, + params={"std": 0.3, "minimal_height": 0.04, "command_name": "object_pose"}, weight=16.0, ) object_goal_tracking_fine_grained = RewTerm( func=mdp.object_goal_distance, - params={"std": 0.05, "minimal_height": 0.06, "command_name": "object_pose"}, + params={"std": 0.05, "minimal_height": 0.04, "command_name": "object_pose"}, weight=5.0, ) # action penalty - action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-3) + action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-4) joint_vel = RewTerm( func=mdp.joint_vel_l2, @@ -191,7 +191,7 @@ class CurriculumCfg: @configclass -class LiftEnvCfg(RLTaskEnvCfg): +class LiftEnvCfg(ManagerBasedRLEnvCfg): """Configuration for the lifting environment.""" # Scene settings diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/observations.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/observations.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/observations.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/observations.py index 4d846b82a8..3a976fa3f5 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/observations.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/observations.py @@ -13,11 +13,11 @@ from omni.isaac.lab.utils.math import subtract_frame_transforms if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv def object_position_in_robot_root_frame( - env: RLTaskEnv, + env: ManagerBasedRLEnv, robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), object_cfg: SceneEntityCfg = SceneEntityCfg("object"), ) -> torch.Tensor: diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/rewards.py similarity index 92% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/rewards.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/rewards.py index 2033e52539..334df9ea50 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/rewards.py @@ -14,11 +14,11 @@ from omni.isaac.lab.utils.math import combine_frame_transforms if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv def object_is_lifted( - env: RLTaskEnv, minimal_height: float, object_cfg: SceneEntityCfg = SceneEntityCfg("object") + env: ManagerBasedRLEnv, minimal_height: float, object_cfg: SceneEntityCfg = SceneEntityCfg("object") ) -> torch.Tensor: """Reward the agent for lifting the object above the minimal height.""" object: RigidObject = env.scene[object_cfg.name] @@ -26,7 +26,7 @@ def object_is_lifted( def object_ee_distance( - env: RLTaskEnv, + env: ManagerBasedRLEnv, std: float, object_cfg: SceneEntityCfg = SceneEntityCfg("object"), ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), @@ -46,7 +46,7 @@ def object_ee_distance( def object_goal_distance( - env: RLTaskEnv, + env: ManagerBasedRLEnv, std: float, minimal_height: float, command_name: str, diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/terminations.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/terminations.py similarity index 96% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/terminations.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/terminations.py index 32f7ea58f3..212e192336 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/lift/mdp/terminations.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/lift/mdp/terminations.py @@ -19,11 +19,11 @@ from omni.isaac.lab.utils.math import combine_frame_transforms if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv def object_reached_goal( - env: RLTaskEnv, + env: ManagerBasedRLEnv, command_name: str = "object_pose", threshold: float = 0.02, robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/__init__.py new file mode 100644 index 0000000000..c9c40208d5 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/__init__.py @@ -0,0 +1,67 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +import gymnasium as gym + +from . import agents, ik_abs_env_cfg, ik_rel_env_cfg, joint_pos_env_cfg + +## +# Register Gym environments. +## + +## +# Joint Position Control +## + +gym.register( + id="Isaac-Reach-Franka-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": 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_cfg:FrankaReachPPORunnerCfg", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, +) + +gym.register( + id="Isaac-Reach-Franka-Play-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": joint_pos_env_cfg.FrankaReachEnvCfg_PLAY, + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_cfg:FrankaReachPPORunnerCfg", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, +) + + +## +# Inverse Kinematics - Absolute Pose Control +## + +gym.register( + id="Isaac-Reach-Franka-IK-Abs-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + kwargs={ + "env_cfg_entry_point": ik_abs_env_cfg.FrankaReachEnvCfg, + }, + disable_env_checker=True, +) + +## +# Inverse Kinematics - Relative Pose Control +## + +gym.register( + id="Isaac-Reach-Franka-IK-Rel-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + kwargs={ + "env_cfg_entry_point": ik_rel_env_cfg.FrankaReachEnvCfg, + }, + disable_env_checker=True, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/__init__.py new file mode 100644 index 0000000000..c3ee657052 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/rl_games_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/rsl_rl_cfg.py similarity index 97% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/rsl_rl_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/rsl_rl_cfg.py index acfbd01d08..9d8573164e 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/rsl_rl_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/rsl_rl_cfg.py @@ -31,7 +31,7 @@ class FrankaReachPPORunnerCfg(RslRlOnPolicyRunnerCfg): value_loss_coef=1.0, use_clipped_value_loss=True, clip_param=0.2, - entropy_coef=0.01, + entropy_coef=0.001, num_learning_epochs=8, num_mini_batches=4, learning_rate=1.0e-3, diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/skrl_ppo_cfg.yaml new file mode 100644 index 0000000000..81f1dfc544 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/agents/skrl_ppo_cfg.yaml @@ -0,0 +1,67 @@ +seed: 42 + +# Models are instantiated using skrl's model instantiator utility +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html +models: + separate: False + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details + clip_actions: False + clip_log_std: True + initial_log_std: 0 + min_log_std: -20.0 + max_log_std: 2.0 + input_shape: "Shape.STATES" + hiddens: [64, 64] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ACTIONS" + output_activation: "" + output_scale: 1.0 + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details + clip_actions: False + input_shape: "Shape.STATES" + hiddens: [64, 64] + hidden_activation: ["elu", "elu"] + output_shape: "Shape.ONE" + output_activation: "" + output_scale: 1.0 + + +# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html +agent: + rollouts: 24 + learning_epochs: 8 + mini_batches: 4 + discount_factor: 0.99 + lambda: 0.95 + learning_rate: 1.e-3 + learning_rate_scheduler: "KLAdaptiveLR" + learning_rate_scheduler_kwargs: + kl_threshold: 0.01 + state_preprocessor: "RunningStandardScaler" + state_preprocessor_kwargs: null + value_preprocessor: "RunningStandardScaler" + value_preprocessor_kwargs: null + random_timesteps: 0 + learning_starts: 0 + grad_norm_clip: 1.0 + ratio_clip: 0.2 + value_clip: 0.2 + clip_predicted_values: True + entropy_loss_scale: 0.0 + value_loss_scale: 2.0 + kl_threshold: 0 + rewards_shaper_scale: 0.01 + # logging and checkpoint + experiment: + directory: "reach_franka" + experiment_name: "" + write_interval: 120 + checkpoint_interval: 1200 + + +# Sequential trainer +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html +trainer: + timesteps: 24000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/ik_abs_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/ik_abs_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/ik_abs_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/ik_abs_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/ik_rel_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/ik_rel_env_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/ik_rel_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/ik_rel_env_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/joint_pos_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/joint_pos_env_cfg.py similarity index 85% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/joint_pos_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/joint_pos_env_cfg.py index 9edd6b9b5e..9f024a7ad0 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/joint_pos_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/franka/joint_pos_env_cfg.py @@ -7,8 +7,8 @@ from omni.isaac.lab.utils import configclass -import omni.isaac.lab_tasks.manipulation.reach.mdp as mdp -from omni.isaac.lab_tasks.manipulation.reach.reach_env_cfg import ReachEnvCfg +import omni.isaac.lab_tasks.manager_based.manipulation.reach.mdp as mdp +from omni.isaac.lab_tasks.manager_based.manipulation.reach.reach_env_cfg import ReachEnvCfg ## # Pre-defined configs @@ -31,7 +31,9 @@ def __post_init__(self): 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 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/__init__.py similarity index 77% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/__init__.py index 84b0f5ad36..ca788c59d9 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/__init__.py @@ -13,22 +13,24 @@ gym.register( id="Isaac-Reach-UR10-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": 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", }, ) gym.register( id="Isaac-Reach-UR10-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ "env_cfg_entry_point": joint_pos_env_cfg.UR10ReachEnvCfg_PLAY, "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/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/agents/rl_games_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/agents/rl_games_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/agents/rl_games_ppo_cfg.yaml diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/agents/rsl_rl_ppo_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/agents/rsl_rl_ppo_cfg.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/agents/rsl_rl_ppo_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/agents/rsl_rl_ppo_cfg.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/skrl_ppo_cfg.yaml b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/agents/skrl_ppo_cfg.yaml similarity index 76% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/skrl_ppo_cfg.yaml rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/agents/skrl_ppo_cfg.yaml index e3cee43f9a..ae1600797e 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/agents/skrl_ppo_cfg.yaml +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/agents/skrl_ppo_cfg.yaml @@ -1,10 +1,10 @@ seed: 42 # Models are instantiated using skrl's model instantiator utility -# https://skrl.readthedocs.io/en/develop/modules/skrl.utils.model_instantiators.html +# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models: separate: False - policy: # see skrl.utils.model_instantiators.gaussian_model for parameter details + policy: # see skrl.utils.model_instantiators.torch.gaussian_model for parameter details clip_actions: False clip_log_std: True initial_log_std: 0 @@ -16,7 +16,7 @@ models: output_shape: "Shape.ACTIONS" output_activation: "" output_scale: 1.0 - value: # see skrl.utils.model_instantiators.deterministic_model for parameter details + value: # see skrl.utils.model_instantiators.torch.deterministic_model for parameter details clip_actions: False input_shape: "Shape.STATES" hiddens: [64, 64] @@ -27,7 +27,7 @@ models: # PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) -# https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html +# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent: rollouts: 24 learning_epochs: 8 @@ -54,13 +54,14 @@ agent: rewards_shaper_scale: 0.01 # logging and checkpoint experiment: - directory: "franka_reach" + directory: "reach_ur10" experiment_name: "" write_interval: 120 checkpoint_interval: 1200 # Sequential trainer -# https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.sequential.html +# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html trainer: timesteps: 24000 + environment_info: "log" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/joint_pos_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/joint_pos_env_cfg.py similarity index 85% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/joint_pos_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/joint_pos_env_cfg.py index 232c8f4224..bfce699301 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/ur_10/joint_pos_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/config/ur_10/joint_pos_env_cfg.py @@ -7,8 +7,8 @@ from omni.isaac.lab.utils import configclass -import omni.isaac.lab_tasks.manipulation.reach.mdp as mdp -from omni.isaac.lab_tasks.manipulation.reach.reach_env_cfg import ReachEnvCfg +import omni.isaac.lab_tasks.manager_based.manipulation.reach.mdp as mdp +from omni.isaac.lab_tasks.manager_based.manipulation.reach.reach_env_cfg import ReachEnvCfg ## # Pre-defined configs @@ -33,6 +33,7 @@ def __post_init__(self): 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( diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/mdp/__init__.py similarity index 100% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/mdp/__init__.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/mdp/__init__.py diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/mdp/rewards.py similarity index 62% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/mdp/rewards.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/mdp/rewards.py index 72724f76c3..d5ae2a57c5 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/mdp/rewards.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/mdp/rewards.py @@ -13,10 +13,10 @@ from omni.isaac.lab.utils.math import combine_frame_transforms, quat_error_magnitude, quat_mul if TYPE_CHECKING: - from omni.isaac.lab.envs import RLTaskEnv + from omni.isaac.lab.envs import ManagerBasedRLEnv -def position_command_error(env: RLTaskEnv, command_name: str, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def position_command_error(env: ManagerBasedRLEnv, command_name: str, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Penalize tracking of the position error using L2-norm. The function computes the position error between the desired position (from the command) and the @@ -33,7 +33,26 @@ def position_command_error(env: RLTaskEnv, command_name: str, asset_cfg: SceneEn return torch.norm(curr_pos_w - des_pos_w, dim=1) -def orientation_command_error(env: RLTaskEnv, command_name: str, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def position_command_error_tanh( + env: ManagerBasedRLEnv, std: float, command_name: str, asset_cfg: SceneEntityCfg +) -> torch.Tensor: + """Reward tracking of the position using the tanh kernel. + + The function computes the position error between the desired position (from the command) and the + current position of the asset's body (in world frame) and maps it with a tanh kernel. + """ + # extract the asset (to enable type hinting) + asset: RigidObject = env.scene[asset_cfg.name] + command = env.command_manager.get_command(command_name) + # obtain the desired and current positions + des_pos_b = command[:, :3] + des_pos_w, _ = combine_frame_transforms(asset.data.root_state_w[:, :3], asset.data.root_state_w[:, 3:7], des_pos_b) + curr_pos_w = asset.data.body_state_w[:, asset_cfg.body_ids[0], :3] # type: ignore + distance = torch.norm(curr_pos_w - des_pos_w, dim=1) + return 1 - torch.tanh(distance / std) + + +def orientation_command_error(env: ManagerBasedRLEnv, command_name: str, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Penalize tracking orientation error using shortest path. The function computes the orientation error between the desired orientation (from the command) and the diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/reach_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/reach_env_cfg.py similarity index 90% rename from source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/reach_env_cfg.py rename to source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/reach_env_cfg.py index 817110d1c1..b49fb5ba97 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/reach_env_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/manipulation/reach/reach_env_cfg.py @@ -7,7 +7,7 @@ import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.managers import ActionTermCfg as ActionTerm from omni.isaac.lab.managers import CurriculumTermCfg as CurrTerm from omni.isaac.lab.managers import EventTermCfg as EventTerm @@ -21,7 +21,7 @@ from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR from omni.isaac.lab.utils.noise import AdditiveUniformNoiseCfg as Unoise -import omni.isaac.lab_tasks.manipulation.reach.mdp as mdp +import omni.isaac.lab_tasks.manager_based.manipulation.reach.mdp as mdp ## # Scene definition @@ -136,9 +136,14 @@ class RewardsCfg: 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.05, + weight=-0.1, params={"asset_cfg": SceneEntityCfg("robot", body_names=MISSING), "command_name": "ee_pose"}, ) @@ -166,6 +171,10 @@ class CurriculumCfg: 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 @@ -173,7 +182,7 @@ class CurriculumCfg: @configclass -class ReachEnvCfg(RLTaskEnvCfg): +class ReachEnvCfg(ManagerBasedRLEnvCfg): """Configuration for the reach end-effector pose tracking environment.""" # Scene settings diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/__init__.py new file mode 100644 index 0000000000..d17ef98709 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Navigation environments.""" + +from .config import anymal_c diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/__init__.py new file mode 100644 index 0000000000..d6259ae03d --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Configurations for navigation environments.""" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/__init__.py new file mode 100644 index 0000000000..472493f5b3 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/__init__.py @@ -0,0 +1,32 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +import gymnasium as gym + +from . import agents, navigation_env_cfg + +## +# Register Gym environments. +## + +gym.register( + id="Isaac-Navigation-Flat-Anymal-C-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": navigation_env_cfg.NavigationEnvCfg, + "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.NavigationEnvPPORunnerCfg, + }, +) + +gym.register( + id="Isaac-Navigation-Flat-Anymal-C-Play-v0", + entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": navigation_env_cfg.NavigationEnvCfg_PLAY, + "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.NavigationEnvPPORunnerCfg, + }, +) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/agents/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/agents/__init__.py new file mode 100644 index 0000000000..b3a5a970d3 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/agents/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from . import rsl_rl_cfg # noqa: F401, F403 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/agents/rsl_rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/agents/rsl_rl_cfg.py new file mode 100644 index 0000000000..73addf6d1b --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/agents/rsl_rl_cfg.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from omni.isaac.lab.utils import configclass + +from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import ( + RslRlOnPolicyRunnerCfg, + RslRlPpoActorCriticCfg, + RslRlPpoAlgorithmCfg, +) + + +@configclass +class NavigationEnvPPORunnerCfg(RslRlOnPolicyRunnerCfg): + num_steps_per_env = 8 + max_iterations = 1500 + save_interval = 50 + experiment_name = "anymal_c_navigation" + empirical_normalization = False + policy = RslRlPpoActorCriticCfg( + init_noise_std=0.5, + actor_hidden_dims=[128, 128], + critic_hidden_dims=[128, 128], + activation="elu", + ) + algorithm = RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.005, + num_learning_epochs=5, + num_mini_batches=4, + learning_rate=1.0e-3, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/navigation_env_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/navigation_env_cfg.py new file mode 100644 index 0000000000..0fbb44ab91 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/config/anymal_c/navigation_env_cfg.py @@ -0,0 +1,164 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +import math + +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg +from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup +from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm +from omni.isaac.lab.managers import RandomizationTermCfg as RandTerm +from omni.isaac.lab.managers import RewardTermCfg as RewTerm +from omni.isaac.lab.managers import SceneEntityCfg +from omni.isaac.lab.managers import TerminationTermCfg as DoneTerm +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR + +import omni.isaac.lab_tasks.manager_based.navigation.mdp as mdp +from omni.isaac.lab_tasks.manager_based.locomotion.velocity.config.anymal_c.flat_env_cfg import AnymalCFlatEnvCfg + +LOW_LEVEL_ENV_CFG = AnymalCFlatEnvCfg() + + +@configclass +class EventCfg: + """Configuration for events.""" + + reset_base = RandTerm( + 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.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), + }, + }, + ) + + +@configclass +class ActionsCfg: + """Action terms for the MDP.""" + + pre_trained_policy_action: mdp.PreTrainedPolicyActionCfg = mdp.PreTrainedPolicyActionCfg( + asset_name="robot", + policy_path=f"{ISAACLAB_NUCLEUS_DIR}/Policies/ANYmal-C/Blind/policy.pt", + low_level_decimation=4, + low_level_actions=LOW_LEVEL_ENV_CFG.actions.joint_pos, + low_level_observations=LOW_LEVEL_ENV_CFG.observations.policy, + ) + + +@configclass +class ObservationsCfg: + """Observation specifications for the MDP.""" + + @configclass + class PolicyCfg(ObsGroup): + """Observations for policy group.""" + + # observation terms (order preserved) + base_lin_vel = ObsTerm(func=mdp.base_lin_vel) + projected_gravity = ObsTerm(func=mdp.projected_gravity) + pose_command = ObsTerm(func=mdp.generated_commands, params={"command_name": "pose_command"}) + + # observation groups + policy: PolicyCfg = PolicyCfg() + + +@configclass +class RewardsCfg: + """Reward terms for the MDP.""" + + termination_penalty = RewTerm(func=mdp.is_terminated, weight=-400.0) + position_tracking = RewTerm( + func=mdp.position_command_error_tanh, + weight=0.5, + params={"std": 2.0, "command_name": "pose_command"}, + ) + position_tracking_fine_grained = RewTerm( + func=mdp.position_command_error_tanh, + weight=0.5, + params={"std": 0.2, "command_name": "pose_command"}, + ) + orientation_tracking = RewTerm( + func=mdp.heading_command_error_abs, + weight=-0.2, + params={"command_name": "pose_command"}, + ) + + +@configclass +class CommandsCfg: + """Command terms for the MDP.""" + + pose_command = mdp.UniformPose2dCommandCfg( + asset_name="robot", + simple_heading=False, + resampling_time_range=(8.0, 8.0), + debug_vis=True, + ranges=mdp.UniformPose2dCommandCfg.Ranges(pos_x=(-3.0, 3.0), pos_y=(-3.0, 3.0), heading=(-math.pi, math.pi)), + ) + + +@configclass +class CurriculumCfg: + """Curriculum terms for the MDP.""" + + pass + + +@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 NavigationEnvCfg(ManagerBasedRLEnvCfg): + scene: SceneEntityCfg = LOW_LEVEL_ENV_CFG.scene + commands: CommandsCfg = CommandsCfg() + actions: ActionsCfg = ActionsCfg() + observations: ObservationsCfg = ObservationsCfg() + rewards: RewardsCfg = RewardsCfg() + events: EventCfg = EventCfg() + + curriculum: CurriculumCfg = CurriculumCfg() + terminations: TerminationsCfg = TerminationsCfg() + + def __post_init__(self): + """Post initialization.""" + + self.sim.dt = LOW_LEVEL_ENV_CFG.sim.dt + self.decimation = LOW_LEVEL_ENV_CFG.decimation * 10 + self.episode_length_s = self.commands.pose_command.resampling_time_range[1] + + if self.scene.height_scanner is not None: + self.scene.height_scanner.update_period = ( + self.actions.pre_trained_policy_action.low_level_decimation * self.sim.dt + ) + if self.scene.contact_forces is not None: + self.scene.contact_forces.update_period = self.sim.dt + + +class NavigationEnvCfg_PLAY(NavigationEnvCfg): + def __post_init__(self) -> None: + # post init of parent + super().__post_init__() + + # make a smaller scene for play + self.scene.num_envs = 50 + self.scene.env_spacing = 2.5 + # disable randomization for play + self.observations.policy.enable_corruption = False diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/__init__.py new file mode 100644 index 0000000000..b2e130d640 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""This sub-module contains the functions that are specific to the locomotion environments.""" + +from omni.isaac.lab.envs.mdp import * # noqa: F401, F403 + +from .pre_trained_policy_action import * # noqa: F401, F403 +from .rewards import * # noqa: F401, F403 diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/pre_trained_policy_action.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/pre_trained_policy_action.py new file mode 100644 index 0000000000..1a347ba447 --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/pre_trained_policy_action.py @@ -0,0 +1,178 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import torch +from dataclasses import MISSING +from typing import TYPE_CHECKING + +import omni.isaac.lab.utils.math as math_utils +from omni.isaac.lab.assets import Articulation +from omni.isaac.lab.managers import ActionTerm, ActionTermCfg, ObservationGroupCfg, ObservationManager +from omni.isaac.lab.markers import VisualizationMarkers +from omni.isaac.lab.markers.config import BLUE_ARROW_X_MARKER_CFG, GREEN_ARROW_X_MARKER_CFG +from omni.isaac.lab.utils import configclass +from omni.isaac.lab.utils.assets import check_file_path, read_file + +if TYPE_CHECKING: + from omni.isaac.lab.envs import ManagerBasedRLEnv + + +class PreTrainedPolicyAction(ActionTerm): + r"""Pre-trained policy action term. + + This action term infers a pre-trained policy and applies the corresponding low-level actions to the robot. + The raw actions correspond to the commands for the pre-trained policy. + + """ + + cfg: PreTrainedPolicyActionCfg + """The configuration of the action term.""" + + def __init__(self, cfg: PreTrainedPolicyActionCfg, env: ManagerBasedRLEnv) -> None: + # initialize the action term + super().__init__(cfg, env) + + self.robot: Articulation = env.scene[cfg.asset_name] + + # load policy + if not check_file_path(cfg.policy_path): + raise FileNotFoundError(f"Policy file '{cfg.policy_path}' does not exist.") + file_bytes = read_file(cfg.policy_path) + self.policy = torch.jit.load(file_bytes).to(env.device).eval() + + self._raw_actions = torch.zeros(self.num_envs, self.action_dim, device=self.device) + + # prepare low level actions + self._low_level_action_term: ActionTerm = cfg.low_level_actions.class_type(cfg.low_level_actions, env) + self.low_level_actions = torch.zeros(self.num_envs, self._low_level_action_term.action_dim, device=self.device) + + # remap some of the low level observations to internal observations + cfg.low_level_observations.actions.func = lambda dummy_env: self.low_level_actions + cfg.low_level_observations.actions.params = dict() + cfg.low_level_observations.velocity_commands.func = lambda dummy_env: self._raw_actions + cfg.low_level_observations.velocity_commands.params = dict() + + # add the low level observations to the observation manager + self._low_level_obs_manager = ObservationManager({"ll_policy": cfg.low_level_observations}, env) + + self._counter = 0 + + """ + Properties. + """ + + @property + def action_dim(self) -> int: + return 3 + + @property + def raw_actions(self) -> torch.Tensor: + return self._raw_actions + + @property + def processed_actions(self) -> torch.Tensor: + return self.raw_actions + + """ + Operations. + """ + + def process_actions(self, actions: torch.Tensor): + self._raw_actions[:] = actions + + def apply_actions(self): + if self._counter % self.cfg.low_level_decimation == 0: + low_level_obs = self._low_level_obs_manager.compute_group("ll_policy") + self.low_level_actions[:] = self.policy(low_level_obs) + self._low_level_action_term.process_actions(self.low_level_actions) + self._counter = 0 + self._low_level_action_term.apply_actions() + self._counter += 1 + + """ + Debug visualization. + """ + + def _set_debug_vis_impl(self, debug_vis: bool): + # set visibility of markers + # note: parent only deals with callbacks. not their visibility + if debug_vis: + # create markers if necessary for the first tome + if not hasattr(self, "base_vel_goal_visualizer"): + # -- goal + marker_cfg = GREEN_ARROW_X_MARKER_CFG.copy() + marker_cfg.prim_path = "/Visuals/Actions/velocity_goal" + marker_cfg.markers["arrow"].scale = (0.5, 0.5, 0.5) + self.base_vel_goal_visualizer = VisualizationMarkers(marker_cfg) + # -- current + marker_cfg = BLUE_ARROW_X_MARKER_CFG.copy() + marker_cfg.prim_path = "/Visuals/Actions/velocity_current" + marker_cfg.markers["arrow"].scale = (0.5, 0.5, 0.5) + self.base_vel_visualizer = VisualizationMarkers(marker_cfg) + # set their visibility to true + self.base_vel_goal_visualizer.set_visibility(True) + self.base_vel_visualizer.set_visibility(True) + else: + if hasattr(self, "base_vel_goal_visualizer"): + self.base_vel_goal_visualizer.set_visibility(False) + self.base_vel_visualizer.set_visibility(False) + + def _debug_vis_callback(self, event): + # get marker location + # -- base state + base_pos_w = self.robot.data.root_pos_w.clone() + base_pos_w[:, 2] += 0.5 + # -- resolve the scales and quaternions + vel_des_arrow_scale, vel_des_arrow_quat = self._resolve_xy_velocity_to_arrow(self.raw_actions[:, :2]) + vel_arrow_scale, vel_arrow_quat = self._resolve_xy_velocity_to_arrow(self.robot.data.root_lin_vel_b[:, :2]) + # display markers + self.base_vel_goal_visualizer.visualize(base_pos_w, vel_des_arrow_quat, vel_des_arrow_scale) + self.base_vel_visualizer.visualize(base_pos_w, vel_arrow_quat, vel_arrow_scale) + + """ + Internal helpers. + """ + + def _resolve_xy_velocity_to_arrow(self, xy_velocity: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: + """Converts the XY base velocity command to arrow direction rotation.""" + # obtain default scale of the marker + default_scale = self.base_vel_goal_visualizer.cfg.markers["arrow"].scale + # arrow-scale + arrow_scale = torch.tensor(default_scale, device=self.device).repeat(xy_velocity.shape[0], 1) + arrow_scale[:, 0] *= torch.linalg.norm(xy_velocity, dim=1) * 3.0 + # arrow-direction + heading_angle = torch.atan2(xy_velocity[:, 1], xy_velocity[:, 0]) + zeros = torch.zeros_like(heading_angle) + arrow_quat = math_utils.quat_from_euler_xyz(zeros, zeros, heading_angle) + # convert everything back from base to world frame + base_quat_w = self.robot.data.root_quat_w + arrow_quat = math_utils.quat_mul(base_quat_w, arrow_quat) + + return arrow_scale, arrow_quat + + +@configclass +class PreTrainedPolicyActionCfg(ActionTermCfg): + """Configuration for pre-trained policy action term. + + See :class:`PreTrainedPolicyAction` for more details. + """ + + class_type: type[ActionTerm] = PreTrainedPolicyAction + """ Class of the action term.""" + asset_name: str = MISSING + """Name of the asset in the environment for which the commands are generated.""" + policy_path: str = MISSING + """Path to the low level policy (.pt files).""" + low_level_decimation: int = 4 + """Decimation factor for the low level action term.""" + low_level_actions: ActionTermCfg = MISSING + """Low level action configuration.""" + low_level_observations: ObservationGroupCfg = MISSING + """Low level observation configuration.""" + debug_vis: bool = True + """Whether to visualize debug information. Defaults to False.""" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/rewards.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/rewards.py new file mode 100644 index 0000000000..56fd95b47e --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/navigation/mdp/rewards.py @@ -0,0 +1,27 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import torch +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from omni.isaac.lab.envs import ManagerBasedRLEnv + + +def position_command_error_tanh(env: ManagerBasedRLEnv, std: float, command_name: str) -> torch.Tensor: + """Reward position tracking with tanh kernel.""" + command = env.command_manager.get_command(command_name) + des_pos_b = command[:, :3] + distance = torch.norm(des_pos_b, dim=1) + return 1 - torch.tanh(distance / std) + + +def heading_command_error_abs(env: ManagerBasedRLEnv, command_name: str) -> torch.Tensor: + """Penalize tracking orientation error.""" + command = env.command_manager.get_command(command_name) + heading_b = command[:, 3] + return heading_b.abs() diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/__init__.py deleted file mode 100644 index e099f67636..0000000000 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/cabinet/config/franka/__init__.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2022-2024, The Isaac Lab Project Developers. -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -from . import agents, ik_abs_env_cfg, ik_rel_env_cfg, joint_pos_env_cfg - -## -# Register Gym environments. -## - -## -# Joint Position Control -## - -gym.register( - id="Isaac-Open-Drawer-Franka-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": joint_pos_env_cfg.FrankaCabinetEnvCfg, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CabinetPPORunnerCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - -gym.register( - id="Isaac-Open-Drawer-Franka-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": joint_pos_env_cfg.FrankaCabinetEnvCfg_PLAY, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CabinetPPORunnerCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - - -## -# Inverse Kinematics - Absolute Pose Control -## - -gym.register( - id="Isaac-Open-Drawer-Franka-IK-Abs-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_abs_env_cfg.FrankaCabinetEnvCfg, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CabinetPPORunnerCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - -gym.register( - id="Isaac-Open-Drawer-Franka-IK-Abs-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_abs_env_cfg.FrankaCabinetEnvCfg_PLAY, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CabinetPPORunnerCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - -## -# Inverse Kinematics - Relative Pose Control -## - -gym.register( - id="Isaac-Open-Drawer-Franka-IK-Rel-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_rel_env_cfg.FrankaCabinetEnvCfg, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CabinetPPORunnerCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - -gym.register( - id="Isaac-Open-Drawer-Franka-IK-Rel-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_rel_env_cfg.FrankaCabinetEnvCfg_PLAY, - "rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.CabinetPPORunnerCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - }, - disable_env_checker=True, -) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/__init__.py deleted file mode 100644 index f246376160..0000000000 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manipulation/reach/config/franka/__init__.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (c) 2022-2024, The Isaac Lab Project Developers. -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -import gymnasium as gym - -from . import agents, ik_abs_env_cfg, ik_rel_env_cfg, joint_pos_env_cfg - -## -# Register Gym environments. -## - -## -# Joint Position Control -## - -gym.register( - id="Isaac-Reach-Franka-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": 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_cfg:FrankaReachPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, -) - -gym.register( - id="Isaac-Reach-Franka-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": joint_pos_env_cfg.FrankaReachEnvCfg_PLAY, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_cfg:FrankaReachPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, -) - - -## -# Inverse Kinematics - Absolute Pose Control -## - -gym.register( - id="Isaac-Reach-Franka-IK-Abs-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_abs_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_cfg:FrankaReachPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - -gym.register( - id="Isaac-Reach-Franka-IK-Abs-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_abs_env_cfg.FrankaReachEnvCfg_PLAY, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_cfg:FrankaReachPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - -## -# Inverse Kinematics - Relative Pose Control -## - -gym.register( - id="Isaac-Reach-Franka-IK-Rel-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_rel_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_cfg:FrankaReachPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, - disable_env_checker=True, -) - -gym.register( - id="Isaac-Reach-Franka-IK-Rel-Play-v0", - entry_point="omni.isaac.lab.envs:RLTaskEnv", - kwargs={ - "env_cfg_entry_point": ik_rel_env_cfg.FrankaReachEnvCfg_PLAY, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_cfg:FrankaReachPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", - }, - disable_env_checker=True, -) diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/parse_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/parse_cfg.py index 8d31ea2c5a..f2074c97a2 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/parse_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/parse_cfg.py @@ -13,11 +13,11 @@ import re import yaml -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.utils import update_class_from_dict, update_dict -def load_cfg_from_registry(task_name: str, entry_point_key: str) -> dict | RLTaskEnvCfg: +def load_cfg_from_registry(task_name: str, entry_point_key: str) -> dict | ManagerBasedRLEnvCfg: """Load default configuration given its entry point from the gym registry. This function loads the configuration object from the gym registry for the given task name. @@ -98,7 +98,7 @@ def load_cfg_from_registry(task_name: str, entry_point_key: str) -> dict | RLTas def parse_env_cfg( task_name: str, use_gpu: bool | None = None, num_envs: int | None = None, use_fabric: bool | None = None -) -> dict | RLTaskEnvCfg: +) -> dict | ManagerBasedRLEnvCfg: """Parse configuration for an environment and override based on inputs. Args: @@ -178,9 +178,6 @@ def get_checkpoint_path( Returns: The path to the model checkpoint. - - Reference: - https://github.com/leggedrobotics/legged_gym/blob/master/legged_gym/utils/helpers.py#L103 """ # check if runs present in directory try: diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rl_games.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rl_games.py index 36c7af2e23..5b1c9f5cc3 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rl_games.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rl_games.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Wrapper to configure an :class:`RLTaskEnv` instance to RL-Games vectorized environment. +"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to RL-Games vectorized environment. The following example shows how to wrap an environment for RL-Games and register the environment construction for RL-Games :class:`Runner` class: @@ -41,7 +41,7 @@ from rl_games.common import env_configurations from rl_games.common.vecenv import IVecEnv -from omni.isaac.lab.envs import RLTaskEnv, VecEnvObs +from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv, VecEnvObs """ Vectorized environment wrapper. @@ -60,7 +60,7 @@ class RlGamesVecEnvWrapper(IVecEnv): observations. This dictionary contains "obs" and "states" which typically correspond to the actor and critic observations respectively. - To use asymmetric actor-critic, the environment observations from :class:`RLTaskEnv` + To use asymmetric actor-critic, the environment observations from :class:`ManagerBasedRLEnv` must have the key or group name "critic". The observation group is used to set the :attr:`num_states` (int) and :attr:`state_space` (:obj:`gym.spaces.Box`). These are used by the learning agent in RL-Games to allocate buffers in the trajectory memory. @@ -79,7 +79,7 @@ class RlGamesVecEnvWrapper(IVecEnv): https://github.com/NVIDIA-Omniverse/IsaacGymEnvs """ - def __init__(self, env: RLTaskEnv, rl_device: str, clip_obs: float, clip_actions: float): + def __init__(self, env: ManagerBasedRLEnv, rl_device: str, clip_obs: float, clip_actions: float): """Initializes the wrapper instance. Args: @@ -89,12 +89,12 @@ def __init__(self, env: RLTaskEnv, rl_device: str, clip_obs: float, clip_actions clip_actions: The clipping value for actions. Raises: - ValueError: The environment is not inherited from :class:`RLTaskEnv`. + ValueError: The environment is not inherited from :class:`ManagerBasedRLEnv`. ValueError: If specified, the privileged observations (critic) are not of type :obj:`gym.spaces.Box`. """ # check that input is valid - if not isinstance(env.unwrapped, RLTaskEnv): - raise ValueError(f"The environment must be inherited from RLTaskEnv. Environment type: {type(env)}") + if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): + raise ValueError(f"The environment must be inherited from ManagerBasedRLEnv. Environment type: {type(env)}") # initialize the wrapper self.env = env # store provided arguments @@ -143,7 +143,7 @@ def observation_space(self) -> gym.spaces.Box: " and if you are nice, please send a merge-request." ) # note: maybe should check if we are a sub-set of the actual space. don't do it right now since - # in RLTaskEnv we are setting action space as (-inf, inf). + # in ManagerBasedRLEnv we are setting action space as (-inf, inf). return gym.spaces.Box(-self._clip_obs, self._clip_obs, policy_obs_space.shape) @property @@ -159,7 +159,7 @@ def action_space(self) -> gym.Space: ) # return casted space in gym.spaces.Box (OpenAI Gym) # note: maybe should check if we are a sub-set of the actual space. don't do it right now since - # in RLTaskEnv we are setting action space as (-inf, inf). + # in ManagerBasedRLEnv we are setting action space as (-inf, inf). return gym.spaces.Box(-self._clip_actions, self._clip_actions, action_space.shape) @classmethod @@ -168,7 +168,7 @@ def class_name(cls) -> str: return cls.__name__ @property - def unwrapped(self) -> RLTaskEnv: + def unwrapped(self) -> ManagerBasedRLEnv: """Returns the base environment of the wrapper. This will be the bare :class:`gymnasium.Env` environment, underneath all layers of wrappers. @@ -205,7 +205,7 @@ def state_space(self) -> gym.spaces.Box | None: ) # return casted space in gym.spaces.Box (OpenAI Gym) # note: maybe should check if we are a sub-set of the actual space. don't do it right now since - # in RLTaskEnv we are setting action space as (-inf, inf). + # in ManagerBasedRLEnv we are setting action space as (-inf, inf). return gym.spaces.Box(-self._clip_obs, self._clip_obs, critic_obs_space.shape) def get_number_of_agents(self) -> int: diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/__init__.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/__init__.py index 138de3489e..24e0c1eff1 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/__init__.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/__init__.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Wrappers and utilities to configure an :class:`RLTaskEnv` for RSL-RL library.""" +"""Wrappers and utilities to configure an :class:`ManagerBasedRLEnv` for RSL-RL library.""" from .exporter import export_policy_as_jit, export_policy_as_onnx from .rl_cfg import RslRlOnPolicyRunnerCfg, RslRlPpoActorCriticCfg, RslRlPpoAlgorithmCfg diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/exporter.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/exporter.py index e39f7e6b6f..126c930af5 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/exporter.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/exporter.py @@ -15,15 +15,14 @@ def export_policy_as_jit(actor_critic: object, path: str, filename="policy.pt"): actor_critic: The actor-critic torch module. path: The path to the saving directory. filename: The name of exported JIT file. Defaults to "policy.pt". - - Reference: - https://github.com/leggedrobotics/legged_gym/blob/master/legged_gym/utils/helpers.py#L180 """ policy_exporter = _TorchPolicyExporter(actor_critic) policy_exporter.export(path, filename) -def export_policy_as_onnx(actor_critic: object, path: str, filename="policy.onnx", verbose=False): +def export_policy_as_onnx( + actor_critic: object, path: str, normalizer: object | None = None, filename="policy.onnx", verbose=False +): """Export policy into a Torch ONNX file. Args: @@ -44,11 +43,7 @@ def export_policy_as_onnx(actor_critic: object, path: str, filename="policy.onnx class _TorchPolicyExporter(torch.nn.Module): - """Exporter of actor-critic into JIT file. - - Reference: - https://github.com/leggedrobotics/legged_gym/blob/master/legged_gym/utils/helpers.py#L193 - """ + """Exporter of actor-critic into JIT file.""" def __init__(self, actor_critic): super().__init__() diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/rl_cfg.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/rl_cfg.py index 7c9e6e42ca..9cb309fe1e 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/rl_cfg.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/rl_cfg.py @@ -80,8 +80,8 @@ class RslRlOnPolicyRunnerCfg: seed: int = 42 """The seed for the experiment. Default is 42.""" - device: str = "cuda" - """The device for the rl-agent. Default is cuda.""" + device: str = "cuda:0" + """The device for the rl-agent. Default is cuda:0.""" num_steps_per_env: int = MISSING """The number of steps per environment per update.""" diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/vecenv_wrapper.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/vecenv_wrapper.py index 52608e75e7..6ae0601635 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/vecenv_wrapper.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/vecenv_wrapper.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Wrapper to configure an :class:`RLTaskEnv` instance to RSL-RL vectorized environment. +"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to RSL-RL vectorized environment. The following example shows how to wrap an environment for RSL-RL: @@ -21,7 +21,7 @@ from rsl_rl.env import VecEnv -from omni.isaac.lab.envs import RLTaskEnv +from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv class RslRlVecEnvWrapper(VecEnv): @@ -43,7 +43,7 @@ class RslRlVecEnvWrapper(VecEnv): https://github.com/leggedrobotics/rsl_rl/blob/master/rsl_rl/env/vec_env.py """ - def __init__(self, env: RLTaskEnv): + def __init__(self, env: ManagerBasedRLEnv): """Initializes the wrapper. Note: @@ -53,22 +53,36 @@ def __init__(self, env: RLTaskEnv): env: The environment to wrap around. Raises: - ValueError: When the environment is not an instance of :class:`RLTaskEnv`. + ValueError: When the environment is not an instance of :class:`ManagerBasedRLEnv`. """ # check that input is valid - if not isinstance(env.unwrapped, RLTaskEnv): - raise ValueError(f"The environment must be inherited from RLTaskEnv. Environment type: {type(env)}") + if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): + raise ValueError( + "The environment must be inherited from ManagerBasedRLEnv or DirectRLEnv. Environment type:" + f" {type(env)}" + ) # initialize the wrapper self.env = env # store information required by wrapper self.num_envs = self.unwrapped.num_envs self.device = self.unwrapped.device self.max_episode_length = self.unwrapped.max_episode_length - self.num_actions = self.unwrapped.action_manager.total_action_dim - self.num_obs = self.unwrapped.observation_manager.group_obs_dim["policy"][0] + if hasattr(self.unwrapped, "action_manager"): + self.num_actions = self.unwrapped.action_manager.total_action_dim + else: + self.num_actions = self.unwrapped.num_actions + if hasattr(self.unwrapped, "observation_manager"): + self.num_obs = self.unwrapped.observation_manager.group_obs_dim["policy"][0] + else: + self.num_obs = self.unwrapped.num_observations # -- privileged observations - if "critic" in self.unwrapped.observation_manager.group_obs_dim: + if ( + hasattr(self.unwrapped, "observation_manager") + and "critic" in self.unwrapped.observation_manager.group_obs_dim + ): self.num_privileged_obs = self.unwrapped.observation_manager.group_obs_dim["critic"][0] + elif hasattr(self.unwrapped, "num_states"): + self.num_privileged_obs = self.unwrapped.num_states else: self.num_privileged_obs = 0 # reset at the start since the RSL-RL runner does not call reset @@ -112,7 +126,7 @@ def class_name(cls) -> str: return cls.__name__ @property - def unwrapped(self) -> RLTaskEnv: + def unwrapped(self) -> ManagerBasedRLEnv: """Returns the base environment of the wrapper. This will be the bare :class:`gymnasium.Env` environment, underneath all layers of wrappers. @@ -125,7 +139,10 @@ def unwrapped(self) -> RLTaskEnv: def get_observations(self) -> tuple[torch.Tensor, dict]: """Returns the current observations of the environment.""" - obs_dict = self.unwrapped.observation_manager.compute() + if hasattr(self.unwrapped, "observation_manager"): + obs_dict = self.unwrapped.observation_manager.compute() + else: + obs_dict = self.unwrapped._get_observations() return obs_dict["policy"], {"observations": obs_dict} @property diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/sb3.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/sb3.py index b505a73af6..3231dff2b1 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/sb3.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/sb3.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Wrapper to configure an :class:`RLTaskEnv` instance to Stable-Baselines3 vectorized environment. +"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to Stable-Baselines3 vectorized environment. The following example shows how to wrap an environment for Stable-Baselines3: @@ -27,7 +27,7 @@ from stable_baselines3.common.utils import constant_fn from stable_baselines3.common.vec_env.base_vec_env import VecEnv, VecEnvObs, VecEnvStepReturn -from omni.isaac.lab.envs import RLTaskEnv +from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv """ Configuration Parser. @@ -85,7 +85,7 @@ class Sb3VecEnvWrapper(VecEnv): still considered a single environment instance, Stable Baselines tries to wrap around it using the :class:`DummyVecEnv`. This is only done if the environment is not inheriting from their :class:`VecEnv`. Thus, this class thinly wraps - over the environment from :class:`RLTaskEnv`. + over the environment from :class:`ManagerBasedRLEnv`. Note: While Stable-Baselines3 supports Gym 0.26+ API, their vectorized environment @@ -123,18 +123,21 @@ class Sb3VecEnvWrapper(VecEnv): """ - def __init__(self, env: RLTaskEnv): + def __init__(self, env: ManagerBasedRLEnv): """Initialize the wrapper. Args: env: The environment to wrap around. Raises: - ValueError: When the environment is not an instance of :class:`RLTaskEnv`. + ValueError: When the environment is not an instance of :class:`ManagerBasedRLEnv`. """ # check that input is valid - if not isinstance(env.unwrapped, RLTaskEnv): - raise ValueError(f"The environment must be inherited from RLTaskEnv. Environment type: {type(env)}") + if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): + raise ValueError( + "The environment must be inherited from ManagerBasedRLEnv or DirectRLEnv. Environment type:" + f" {type(env)}" + ) # initialize the wrapper self.env = env # collect common information @@ -172,7 +175,7 @@ def class_name(cls) -> str: return cls.__name__ @property - def unwrapped(self) -> RLTaskEnv: + def unwrapped(self) -> ManagerBasedRLEnv: """Returns the base environment of the wrapper. This will be the bare :class:`gymnasium.Env` environment, underneath all layers of wrappers. @@ -224,7 +227,7 @@ def step_wait(self) -> VecEnvStepReturn: # noqa: D102 reset_ids = (dones > 0).nonzero(as_tuple=False) # convert data types to numpy depending on backend - # note: RLTaskEnv uses torch backend (by default). + # note: ManagerBasedRLEnv uses torch backend (by default). obs = self._process_obs(obs_dict) rew = rew.detach().cpu().numpy() terminated = terminated.detach().cpu().numpy() @@ -284,7 +287,7 @@ def _process_obs(self, obs_dict: torch.Tensor | dict[str, torch.Tensor]) -> np.n """Convert observations into NumPy data type.""" # Sb3 doesn't support asymmetric observation spaces, so we only use "policy" obs = obs_dict["policy"] - # note: RLTaskEnv uses torch backend (by default). + # note: ManagerBasedRLEnv uses torch backend (by default). if isinstance(obs, dict): for key, value in obs.items(): obs[key] = value.detach().cpu().numpy() diff --git a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/skrl.py b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/skrl.py index dfde9e04e0..cbf7fa61ea 100644 --- a/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/skrl.py +++ b/source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/utils/wrappers/skrl.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Wrapper to configure an :class:`RLTaskEnv` instance to skrl environment. +"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to skrl environment. The following example shows how to wrap an environment for skrl: @@ -38,7 +38,7 @@ from skrl.trainers.torch.sequential import SEQUENTIAL_TRAINER_DEFAULT_CONFIG from skrl.utils.model_instantiators.torch import Shape # noqa: F401 -from omni.isaac.lab.envs import RLTaskEnv +from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv """ Configuration Parser. @@ -91,10 +91,10 @@ def update_dict(d): """ -def SkrlVecEnvWrapper(env: RLTaskEnv): +def SkrlVecEnvWrapper(env: ManagerBasedRLEnv): """Wraps around Isaac Lab environment for skrl. - This function wraps around the Isaac Lab environment. Since the :class:`RLTaskEnv` environment + This function wraps around the Isaac Lab environment. Since the :class:`ManagerBasedRLEnv` environment wrapping functionality is defined within the skrl library itself, this implementation is maintained for compatibility with the structure of the extension that contains it. Internally it calls the :func:`wrap_env` from the skrl library API. @@ -103,14 +103,16 @@ def SkrlVecEnvWrapper(env: RLTaskEnv): env: The environment to wrap around. Raises: - ValueError: When the environment is not an instance of :class:`RLTaskEnv`. + ValueError: When the environment is not an instance of :class:`ManagerBasedRLEnv`. Reference: - https://skrl.readthedocs.io/en/latest/modules/skrl.envs.wrapping.html + https://skrl.readthedocs.io/en/latest/api/envs/wrapping.html """ # check that input is valid - if not isinstance(env.unwrapped, RLTaskEnv): - raise ValueError(f"The environment must be inherited from RLTaskEnv. Environment type: {type(env)}") + if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): + raise ValueError( + f"The environment must be inherited from ManagerBasedRLEnv or DirectRLEnv. Environment type: {type(env)}" + ) # wrap and return the environment return wrap_env(env, wrapper="isaac-orbit") @@ -134,7 +136,7 @@ class SkrlSequentialLogTrainer(Trainer): * It does not close the environment at the end of the training. Reference: - https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.base_class.html + https://skrl.readthedocs.io/en/latest/api/trainers.html#base-class """ def __init__( @@ -210,8 +212,8 @@ def train(self): timesteps=self.timesteps, ) # log custom environment data - if "episode" in infos: - for k, v in infos["episode"].items(): + if "log" in infos: + for k, v in infos["log"].items(): if isinstance(v, torch.Tensor) and v.numel() == 1: self.agents.track_data(f"EpisodeInfo / {k}", v.item()) # post-interaction diff --git a/source/extensions/omni.isaac.lab_tasks/pyproject.toml b/source/extensions/omni.isaac.lab_tasks/pyproject.toml new file mode 100644 index 0000000000..d90ac3536f --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "toml"] +build-backend = "setuptools.build_meta" diff --git a/source/extensions/omni.isaac.lab_tasks/setup.py b/source/extensions/omni.isaac.lab_tasks/setup.py index 4f5781eca4..3686c63b7a 100644 --- a/source/extensions/omni.isaac.lab_tasks/setup.py +++ b/source/extensions/omni.isaac.lab_tasks/setup.py @@ -7,6 +7,7 @@ import itertools import os +import platform import toml from setuptools import setup @@ -20,11 +21,11 @@ INSTALL_REQUIRES = [ # generic "numpy", - "torch==2.0.1", + "torch>=2.2.2", "torchvision>=0.14.1", # ensure compatibility with torch 1.13.1 # 5.26.0 introduced a breaking change, so we restricted it for now. # See issue https://github.com/tensorflow/tensorboard/issues/6808 for details. - "protobuf >= 3.19.6, < 5.0.0", + "protobuf>=3.20.2, < 5.0.0", # data collection "h5py", # basic logger @@ -35,12 +36,17 @@ # Extra dependencies for RL agents EXTRAS_REQUIRE = { - "sb3": ["stable-baselines3>=2.0"], + "sb3": ["stable-baselines3>=2.1"], "skrl": ["skrl>=1.1.0"], - "rl_games": ["rl-games==1.6.1", "gym"], # rl-games still needs gym :( - "rsl_rl": ["rsl_rl@git+https://github.com/leggedrobotics/rsl_rl.git"], - "robomimic": ["robomimic@git+https://github.com/ARISE-Initiative/robomimic.git"], + "rl-games": ["rl-games==1.6.1", "gym"], # rl-games still needs gym :( + "rsl-rl": ["rsl-rl@git+https://github.com/leggedrobotics/rsl_rl.git"], + "robomimic": [], } + +# Check if the platform is Linux and add the dependency +if platform.system() == "Linux": + EXTRAS_REQUIRE["robomimic"].append("robomimic@git+https://github.com/ARISE-Initiative/robomimic.git") + # cumulation of all extra-requires EXTRAS_REQUIRE["all"] = list(itertools.chain.from_iterable(EXTRAS_REQUIRE.values())) @@ -62,7 +68,7 @@ classifiers=[ "Natural Language :: English", "Programming Language :: Python :: 3.10", - "Isaac Sim :: 2023.1.0-hotfix.1", + "Isaac Sim :: 4.0.0", "Isaac Sim :: 2023.1.1", ], zip_safe=False, diff --git a/source/extensions/omni.isaac.lab_tasks/test/test_environments.py b/source/extensions/omni.isaac.lab_tasks/test/test_environments.py index 4007bfa2b6..ae4827531a 100644 --- a/source/extensions/omni.isaac.lab_tasks/test/test_environments.py +++ b/source/extensions/omni.isaac.lab_tasks/test/test_environments.py @@ -8,7 +8,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch the simulator -app_launcher = AppLauncher(headless=True) +app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app @@ -20,7 +20,7 @@ import omni.usd -from omni.isaac.lab.envs import RLTaskEnv, RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnv, ManagerBasedRLEnvCfg import omni.isaac.lab_tasks # noqa: F401 from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg @@ -34,7 +34,7 @@ def setUpClass(cls): # acquire all Isaac environments names cls.registered_tasks = list() for task_spec in gym.registry.values(): - if "Isaac" in task_spec.id: + if "Isaac" in task_spec.id and not task_spec.id.endswith("Play-v0"): cls.registered_tasks.append(task_spec.id) # sort environments by name cls.registered_tasks.sort() @@ -84,9 +84,9 @@ def _check_random_actions(self, task_name: str, use_gpu: bool, num_envs: int, nu # create a new stage omni.usd.get_context().new_stage() # parse configuration - env_cfg: RLTaskEnvCfg = parse_env_cfg(task_name, use_gpu=use_gpu, num_envs=num_envs) + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg(task_name, use_gpu=use_gpu, num_envs=num_envs) # create environment - env: RLTaskEnv = gym.make(task_name, cfg=env_cfg) + env: ManagerBasedRLEnv = gym.make(task_name, cfg=env_cfg) # reset environment obs, _ = env.reset() diff --git a/source/extensions/omni.isaac.lab_tasks/test/test_record_video.py b/source/extensions/omni.isaac.lab_tasks/test/test_record_video.py index 39f8c3a1e3..ba0657d823 100644 --- a/source/extensions/omni.isaac.lab_tasks/test/test_record_video.py +++ b/source/extensions/omni.isaac.lab_tasks/test/test_record_video.py @@ -8,7 +8,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch the simulator -app_launcher = AppLauncher(headless=True, offscreen_render=True) +app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app """Rest everything follows.""" @@ -20,7 +20,7 @@ import omni.usd -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg import omni.isaac.lab_tasks # noqa: F401 from omni.isaac.lab_tasks.utils import parse_env_cfg @@ -60,7 +60,7 @@ def test_record_video(self): omni.usd.get_context().new_stage() # parse configuration - env_cfg: RLTaskEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) # create environment env = gym.make(task_name, cfg=env_cfg, render_mode="rgb_array") diff --git a/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rl_games_wrapper.py b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rl_games_wrapper.py index de7ec5fb40..0da9baad9a 100644 --- a/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rl_games_wrapper.py +++ b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rl_games_wrapper.py @@ -8,7 +8,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch the simulator -app_launcher = AppLauncher(headless=True) +app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app @@ -20,7 +20,7 @@ import omni.usd -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg import omni.isaac.lab_tasks # noqa: F401 from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg @@ -36,10 +36,11 @@ def setUpClass(cls): cls.registered_tasks = list() for task_spec in gym.registry.values(): if "Isaac" in task_spec.id: - cls.registered_tasks.append(task_spec.id) + cfg_entry_point = gym.spec(task_spec.id).kwargs.get("rl_games_cfg_entry_point") + if cfg_entry_point is not None: + cls.registered_tasks.append(task_spec.id) # sort environments by name cls.registered_tasks.sort() - # only pick the first four environments to test cls.registered_tasks = cls.registered_tasks[:4] # print all existing task names print(">>> All registered environments:", cls.registered_tasks) @@ -57,7 +58,7 @@ def test_random_actions(self): # create a new stage omni.usd.get_context().new_stage() # parse configuration - env_cfg: RLTaskEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) # create environment env = gym.make(task_name, cfg=env_cfg) diff --git a/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rsl_rl_wrapper.py b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rsl_rl_wrapper.py index bf5199abb0..290f6b944c 100644 --- a/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rsl_rl_wrapper.py +++ b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_rsl_rl_wrapper.py @@ -8,7 +8,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch the simulator -app_launcher = AppLauncher(headless=True) +app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app @@ -20,7 +20,7 @@ import omni.usd -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg import omni.isaac.lab_tasks # noqa: F401 from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg @@ -36,10 +36,11 @@ def setUpClass(cls): cls.registered_tasks = list() for task_spec in gym.registry.values(): if "Isaac" in task_spec.id: - cls.registered_tasks.append(task_spec.id) + cfg_entry_point = gym.spec(task_spec.id).kwargs.get("rsl_rl_cfg_entry_point") + if cfg_entry_point is not None: + cls.registered_tasks.append(task_spec.id) # sort environments by name cls.registered_tasks.sort() - # only pick the first four environments to test cls.registered_tasks = cls.registered_tasks[:4] # print all existing task names print(">>> All registered environments:", cls.registered_tasks) @@ -57,7 +58,7 @@ def test_random_actions(self): # create a new stage omni.usd.get_context().new_stage() # parse configuration - env_cfg: RLTaskEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) # create environment env = gym.make(task_name, cfg=env_cfg) @@ -93,7 +94,7 @@ def test_no_time_outs(self): # create a new stage omni.usd.get_context().new_stage() # parse configuration - env_cfg: RLTaskEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) # change to finite horizon env_cfg.is_finite_horizon = True diff --git a/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_sb3_wrapper.py b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_sb3_wrapper.py index 66ea4d019c..4f1700d7a0 100644 --- a/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_sb3_wrapper.py +++ b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_sb3_wrapper.py @@ -8,7 +8,7 @@ from omni.isaac.lab.app import AppLauncher, run_tests # launch the simulator -app_launcher = AppLauncher(headless=True) +app_launcher = AppLauncher(headless=True, enable_cameras=True) simulation_app = app_launcher.app @@ -21,7 +21,7 @@ import omni.usd -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg import omni.isaac.lab_tasks # noqa: F401 from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg @@ -29,7 +29,7 @@ class TestStableBaselines3VecEnvWrapper(unittest.TestCase): - """Test that RSL-RL VecEnv wrapper works as expected.""" + """Test that SB3 VecEnv wrapper works as expected.""" @classmethod def setUpClass(cls): @@ -37,10 +37,11 @@ def setUpClass(cls): cls.registered_tasks = list() for task_spec in gym.registry.values(): if "Isaac" in task_spec.id: - cls.registered_tasks.append(task_spec.id) + cfg_entry_point = gym.spec(task_spec.id).kwargs.get("sb3_cfg_entry_point") + if cfg_entry_point is not None: + cls.registered_tasks.append(task_spec.id) # sort environments by name cls.registered_tasks.sort() - # only pick the first four environments to test cls.registered_tasks = cls.registered_tasks[:4] # print all existing task names print(">>> All registered environments:", cls.registered_tasks) @@ -58,7 +59,7 @@ def test_random_actions(self): # create a new stage omni.usd.get_context().new_stage() # parse configuration - env_cfg: RLTaskEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) # create environment env = gym.make(task_name, cfg=env_cfg) diff --git a/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_skrl_wrapper.py b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_skrl_wrapper.py new file mode 100644 index 0000000000..12b5ef782d --- /dev/null +++ b/source/extensions/omni.isaac.lab_tasks/test/wrappers/test_skrl_wrapper.py @@ -0,0 +1,120 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Launch Isaac Sim Simulator first.""" + +from omni.isaac.lab.app import AppLauncher, run_tests + +# launch the simulator +app_launcher = AppLauncher(headless=True, enable_cameras=True) +simulation_app = app_launcher.app + + +"""Rest everything follows.""" + +import gymnasium as gym +import torch +import unittest + +import omni.usd + +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg + +import omni.isaac.lab_tasks # noqa: F401 +from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg +from omni.isaac.lab_tasks.utils.wrappers.skrl import SkrlVecEnvWrapper + + +class TestSKRLVecEnvWrapper(unittest.TestCase): + """Test that SKRL VecEnv wrapper works as expected.""" + + @classmethod + def setUpClass(cls): + # acquire all Isaac environments names + cls.registered_tasks = list() + for task_spec in gym.registry.values(): + if "Isaac" in task_spec.id: + cfg_entry_point = gym.spec(task_spec.id).kwargs.get("skrl_cfg_entry_point") + if cfg_entry_point is not None: + cls.registered_tasks.append(task_spec.id) + # sort environments by name + cls.registered_tasks.sort() + cls.registered_tasks = cls.registered_tasks[:4] + # print all existing task names + print(">>> All registered environments:", cls.registered_tasks) + + def setUp(self) -> None: + # common parameters + self.num_envs = 64 + self.use_gpu = True + + def test_random_actions(self): + """Run random actions and check environments return valid signals.""" + for task_name in self.registered_tasks: + with self.subTest(task_name=task_name): + print(f">>> Running test for environment: {task_name}") + # create a new stage + omni.usd.get_context().new_stage() + # parse configuration + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg(task_name, use_gpu=self.use_gpu, num_envs=self.num_envs) + + # create environment + env = gym.make(task_name, cfg=env_cfg) + # wrap environment + env = SkrlVecEnvWrapper(env) + + # reset environment + obs, extras = env.reset() + # check signal + self.assertTrue(self._check_valid_tensor(obs)) + self.assertTrue(self._check_valid_tensor(extras)) + + # simulate environment for 1000 steps + with torch.inference_mode(): + for _ in range(1000): + # sample actions from -1 to 1 + actions = ( + 2 * torch.rand(self.num_envs, *env.action_space.shape, device=env.unwrapped.device) - 1 + ) + # apply actions + transition = env.step(actions) + # check signals + for data in transition: + self.assertTrue(self._check_valid_tensor(data), msg=f"Invalid data: {data}") + + # close the environment + print(f">>> Closing environment: {task_name}") + env.close() + + """ + Helper functions. + """ + + @staticmethod + def _check_valid_tensor(data: torch.Tensor | dict) -> bool: + """Checks if given data does not have corrupted values. + + Args: + data: Data buffer. + + Returns: + True if the data is valid. + """ + if isinstance(data, torch.Tensor): + return not torch.any(torch.isnan(data)) + elif isinstance(data, dict): + valid_tensor = True + for value in data.values(): + if isinstance(value, dict): + valid_tensor &= TestSKRLVecEnvWrapper._check_valid_tensor(value) + elif isinstance(value, torch.Tensor): + valid_tensor &= not torch.any(torch.isnan(value)) + return valid_tensor + else: + raise ValueError(f"Input data of invalid type: {type(data)}.") + + +if __name__ == "__main__": + run_tests() diff --git a/source/standalone/demos/bipeds.py b/source/standalone/demos/bipeds.py index 29f63e4bbe..773589904e 100644 --- a/source/standalone/demos/bipeds.py +++ b/source/standalone/demos/bipeds.py @@ -11,6 +11,7 @@ """Launch Isaac Sim Simulator first.""" import argparse +import torch from omni.isaac.lab.app import AppLauncher @@ -35,6 +36,7 @@ # Pre-defined configs ## from omni.isaac.lab_assets.cassie import CASSIE_CFG # isort:skip +from omni.isaac.lab_assets import H1_CFG # isort:skip def main(): @@ -42,7 +44,7 @@ def main(): # Load kit helper sim = SimulationContext( - sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False, dt=0.005, physx=sim_utils.PhysxCfg(use_gpu=False)) + sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False, dt=0.01, physx=sim_utils.PhysxCfg(use_gpu=False)) ) # Set main camera sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) @@ -55,12 +57,14 @@ def main(): cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)) cfg.func("/World/Light", cfg) + origins = torch.tensor([ + [0.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + ]) # Robots - robot_cfg = CASSIE_CFG - robot_cfg.spawn.func("/World/Cassie/Robot_1", robot_cfg.spawn, translation=(1.5, 0.5, 0.42)) - - # create handles for the robots - robots = Articulation(robot_cfg.replace(prim_path="/World/Cassie/Robot.*")) + cassie = Articulation(CASSIE_CFG.replace(prim_path="/World/Cassie")) + h1 = Articulation(H1_CFG.replace(prim_path="/World/H1")) + robots = [cassie, h1] # Play the simulator sim.reset() @@ -79,24 +83,28 @@ def main(): # reset counters sim_time = 0.0 count = 0 - # reset dof state - joint_pos, joint_vel = robots.data.default_joint_pos, robots.data.default_joint_vel - robots.write_joint_state_to_sim(joint_pos, joint_vel) - robots.write_root_pose_to_sim(robots.data.default_root_state[:, :7]) - robots.write_root_velocity_to_sim(robots.data.default_root_state[:, 7:]) - robots.reset() + for index, robot in enumerate(robots): + # reset dof state + joint_pos, joint_vel = robot.data.default_joint_pos, robot.data.default_joint_vel + robot.write_joint_state_to_sim(joint_pos, joint_vel) + root_state = robot.data.default_root_state.clone() + root_state[:, :3] += origins[index] + robot.write_root_state_to_sim(root_state) + robot.reset() # reset command print(">>>>>>>> Reset!") # apply action to the robot - robots.set_joint_position_target(robots.data.default_joint_pos.clone()) - robots.write_data_to_sim() + for robot in robots: + robot.set_joint_position_target(robot.data.default_joint_pos.clone()) + robot.write_data_to_sim() # perform step sim.step() # update sim-time sim_time += sim_dt count += 1 # update buffers - robots.update(sim_dt) + for robot in robots: + robot.update(sim_dt) if __name__ == "__main__": diff --git a/source/standalone/demos/quadcopter.py b/source/standalone/demos/quadcopter.py new file mode 100644 index 0000000000..9b50028ed1 --- /dev/null +++ b/source/standalone/demos/quadcopter.py @@ -0,0 +1,115 @@ +# Copyright (c) 2022-2024, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +This script demonstrates how to simulate a quadcopter. + +""" + +"""Launch Isaac Sim Simulator first.""" + +import argparse +import torch + +from omni.isaac.lab.app import AppLauncher + +# add argparse arguments +parser = argparse.ArgumentParser(description="This script demonstrates how to simulate a quadcopter.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments +args_cli = parser.parse_args() + +# launch omniverse app +app_launcher = AppLauncher(args_cli) +simulation_app = app_launcher.app + +"""Rest everything follows.""" + +import omni.isaac.lab.sim as sim_utils +from omni.isaac.lab.assets import Articulation +from omni.isaac.lab.sim import SimulationContext + +## +# Pre-defined configs +## +from omni.isaac.lab_assets import CRAZYFLIE_CFG # isort:skip + + +def main(): + """Main function.""" + + # Load kit helper + sim = SimulationContext( + sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False, dt=0.005, physx=sim_utils.PhysxCfg(use_gpu=False)) + ) + # Set main camera + sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) + + # Spawn things into stage + # Ground-plane + cfg = sim_utils.GroundPlaneCfg() + cfg.func("/World/defaultGroundPlane", cfg) + # Lights + cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)) + cfg.func("/World/Light", cfg) + + # Robots + robot_cfg = CRAZYFLIE_CFG + robot_cfg.spawn.func("/World/Crazyflie/Robot_1", robot_cfg.spawn, translation=(1.5, 0.5, 0.42)) + + # create handles for the robots + robot = Articulation(robot_cfg.replace(prim_path="/World/Crazyflie/Robot.*")) + + # Play the simulator + sim.reset() + + # Fetch relevant parameters to make the quadcopter hover in place + prop_body_ids = robot.find_bodies("m.*_prop")[0] + robot_mass = robot.root_physx_view.get_masses().sum() + gravity = torch.tensor(sim.cfg.gravity, device=sim.device).norm() + + # Now we are ready! + print("[INFO]: Setup complete...") + + # Define simulation stepping + sim_dt = sim.get_physics_dt() + sim_time = 0.0 + count = 0 + # Simulate physics + while simulation_app.is_running(): + # reset + if count % 2000 == 0: + # reset counters + sim_time = 0.0 + count = 0 + # reset dof state + joint_pos, joint_vel = robot.data.default_joint_pos, robot.data.default_joint_vel + robot.write_joint_state_to_sim(joint_pos, joint_vel) + robot.write_root_pose_to_sim(robot.data.default_root_state[:, :7]) + robot.write_root_velocity_to_sim(robot.data.default_root_state[:, 7:]) + robot.reset() + # reset command + print(">>>>>>>> Reset!") + # apply action to the robot (make the robot float in place) + forces = torch.zeros(1, 4, 3, device=sim.device) + torques = torch.zeros_like(forces) + forces[..., 2] = robot_mass * gravity / 4.0 + robot.set_external_force_and_torque(forces, torques, body_ids=prop_body_ids) + robot.write_data_to_sim() + # perform step + sim.step() + # update sim-time + sim_time += sim_dt + count += 1 + # update buffers + robot.update(sim_dt) + + +if __name__ == "__main__": + # run the main function + main() + # close sim app + simulation_app.close() diff --git a/source/standalone/environments/state_machine/lift_cube_sm.py b/source/standalone/environments/state_machine/lift_cube_sm.py index 10b206f3f4..72e5ba382c 100644 --- a/source/standalone/environments/state_machine/lift_cube_sm.py +++ b/source/standalone/environments/state_machine/lift_cube_sm.py @@ -48,7 +48,7 @@ from omni.isaac.lab.assets.rigid_object.rigid_object_data import RigidObjectData import omni.isaac.lab_tasks # noqa: F401 -from omni.isaac.lab_tasks.manipulation.lift.lift_env_cfg import LiftEnvCfg +from omni.isaac.lab_tasks.manager_based.manipulation.lift.lift_env_cfg import LiftEnvCfg from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg # initialize warp diff --git a/source/standalone/environments/state_machine/open_cabinet_sm.py b/source/standalone/environments/state_machine/open_cabinet_sm.py index 69c04cde90..57d84a6ee2 100644 --- a/source/standalone/environments/state_machine/open_cabinet_sm.py +++ b/source/standalone/environments/state_machine/open_cabinet_sm.py @@ -50,7 +50,7 @@ from omni.isaac.lab.sensors import FrameTransformer import omni.isaac.lab_tasks # noqa: F401 -from omni.isaac.lab_tasks.manipulation.cabinet.cabinet_env_cfg import CabinetEnvCfg +from omni.isaac.lab_tasks.manager_based.manipulation.cabinet.cabinet_env_cfg import CabinetEnvCfg from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg # initialize warp diff --git a/source/standalone/tools/blender_obj.py b/source/standalone/tools/blender_obj.py index c50f122698..f550ab2495 100755 --- a/source/standalone/tools/blender_obj.py +++ b/source/standalone/tools/blender_obj.py @@ -25,10 +25,7 @@ def parse_cli_args(): - """Parse the input command line arguments. - - Reference: https://developer.blender.org/diffusion/B/browse/master/release/scripts/templates_py/background_job.py - """ + """Parse the input command line arguments.""" import argparse # get the args passed to blender after "--", all of which are ignored by diff --git a/source/standalone/tools/check_instanceable.py b/source/standalone/tools/check_instanceable.py index f5ab28f941..82169d822c 100644 --- a/source/standalone/tools/check_instanceable.py +++ b/source/standalone/tools/check_instanceable.py @@ -44,7 +44,6 @@ import contextlib import os -# omni-isaac-lab from omni.isaac.lab.app import AppLauncher # add argparse arguments diff --git a/source/standalone/tools/process_meshes_to_obj.py b/source/standalone/tools/process_meshes_to_obj.py index 0c0814170e..c43aadaefd 100755 --- a/source/standalone/tools/process_meshes_to_obj.py +++ b/source/standalone/tools/process_meshes_to_obj.py @@ -18,10 +18,7 @@ def parse_cli_args(): - """Parse the input command line arguments. - - Reference: https://developer.blender.org/diffusion/B/browse/master/release/scripts/templates_py/background_job.py - """ + """Parse the input command line arguments.""" # add argparse arguments parser = argparse.ArgumentParser("Utility to convert all mesh files to `.obj` in given folders.") parser.add_argument("input_dir", type=str, help="The input directory from which to load meshes.") diff --git a/source/standalone/tutorials/03_envs/create_cartpole_base_env.py b/source/standalone/tutorials/03_envs/create_cartpole_base_env.py index 06fa09c1a2..c072f90d71 100644 --- a/source/standalone/tutorials/03_envs/create_cartpole_base_env.py +++ b/source/standalone/tutorials/03_envs/create_cartpole_base_env.py @@ -34,14 +34,14 @@ import torch import omni.isaac.lab.envs.mdp as mdp -from omni.isaac.lab.envs import BaseEnv, BaseEnvCfg +from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm from omni.isaac.lab.managers import SceneEntityCfg from omni.isaac.lab.utils import configclass -from omni.isaac.lab_tasks.classic.cartpole.cartpole_env_cfg import CartpoleSceneCfg +from omni.isaac.lab_tasks.manager_based.classic.cartpole.cartpole_env_cfg import CartpoleSceneCfg @configclass @@ -81,7 +81,7 @@ class EventCfg: mode="startup", params={ "asset_cfg": SceneEntityCfg("robot", body_names=["pole"]), - "mass_range": (0.1, 0.5), + "mass_distribution_params": (0.1, 0.5), "operation": "add", }, ) @@ -109,7 +109,7 @@ class EventCfg: @configclass -class CartpoleEnvCfg(BaseEnvCfg): +class CartpoleEnvCfg(ManagerBasedEnvCfg): """Configuration for the cartpole environment.""" # Scene settings @@ -136,7 +136,7 @@ def main(): env_cfg = CartpoleEnvCfg() env_cfg.scene.num_envs = args_cli.num_envs # setup base environment - env = BaseEnv(cfg=env_cfg) + env = ManagerBasedEnv(cfg=env_cfg) # simulate physics count = 0 diff --git a/source/standalone/tutorials/03_envs/create_cube_base_env.py b/source/standalone/tutorials/03_envs/create_cube_base_env.py index 0544c263f9..92a47ed6cf 100644 --- a/source/standalone/tutorials/03_envs/create_cube_base_env.py +++ b/source/standalone/tutorials/03_envs/create_cube_base_env.py @@ -46,7 +46,7 @@ import omni.isaac.lab.envs.mdp as mdp import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import AssetBaseCfg, RigidObject, RigidObjectCfg -from omni.isaac.lab.envs import BaseEnv, BaseEnvCfg +from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedEnvCfg from omni.isaac.lab.managers import ActionTerm, ActionTermCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup @@ -81,7 +81,7 @@ class CubeActionTerm(ActionTerm): _asset: RigidObject """The articulation asset on which the action term is applied.""" - def __init__(self, cfg: CubeActionTermCfg, env: BaseEnv): + def __init__(self, cfg: CubeActionTermCfg, env: ManagerBasedEnv): # call super constructor super().__init__(cfg, env) # create buffers @@ -145,7 +145,7 @@ class CubeActionTermCfg(ActionTermCfg): ## -def base_position(env: BaseEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: +def base_position(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: """Root linear velocity in the asset's root frame.""" # extract the used quantities (to enable type-hinting) asset: RigidObject = env.scene[asset_cfg.name] @@ -243,7 +243,7 @@ class EventCfg: @configclass -class CubeEnvCfg(BaseEnvCfg): +class CubeEnvCfg(ManagerBasedEnvCfg): """Configuration for the locomotion velocity-tracking environment.""" # Scene settings @@ -266,7 +266,7 @@ def main(): """Main function.""" # setup base environment - env = BaseEnv(cfg=CubeEnvCfg()) + env = ManagerBasedEnv(cfg=CubeEnvCfg()) # setup target position commands target_position = torch.rand(env.num_envs, 3, device=env.device) * 2 diff --git a/source/standalone/tutorials/03_envs/create_quadruped_base_env.py b/source/standalone/tutorials/03_envs/create_quadruped_base_env.py index cda83ca897..56a13b52ba 100644 --- a/source/standalone/tutorials/03_envs/create_quadruped_base_env.py +++ b/source/standalone/tutorials/03_envs/create_quadruped_base_env.py @@ -39,13 +39,12 @@ """Rest everything follows.""" -import os import torch import omni.isaac.lab.envs.mdp as mdp import omni.isaac.lab.sim as sim_utils from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg -from omni.isaac.lab.envs import BaseEnv, BaseEnvCfg +from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedEnvCfg from omni.isaac.lab.managers import EventTermCfg as EventTerm from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm @@ -69,7 +68,7 @@ ## -def constant_commands(env: BaseEnv) -> torch.Tensor: +def constant_commands(env: ManagerBasedEnv) -> torch.Tensor: """The generated command from the command generator.""" return torch.tensor([[1, 0, 0]], device=env.device).repeat(env.num_envs, 1) @@ -178,7 +177,7 @@ class EventCfg: @configclass -class QuadrupedEnvCfg(BaseEnvCfg): +class QuadrupedEnvCfg(ManagerBasedEnvCfg): """Configuration for the locomotion velocity-tracking environment.""" # Scene settings @@ -205,10 +204,10 @@ def main(): """Main function.""" # setup base environment env_cfg = QuadrupedEnvCfg() - env = BaseEnv(cfg=env_cfg) + env = ManagerBasedEnv(cfg=env_cfg) # load level policy - policy_path = os.path.join(ISAACLAB_NUCLEUS_DIR, "Policies", "ANYmal-C", "policy.pt") + policy_path = ISAACLAB_NUCLEUS_DIR + "/Policies/ANYmal-C/HeightScan/policy.pt" # check if policy file exists if not check_file_path(policy_path): raise FileNotFoundError(f"Policy file '{policy_path}' does not exist.") diff --git a/source/standalone/tutorials/03_envs/run_cartpole_rl_env.py b/source/standalone/tutorials/03_envs/run_cartpole_rl_env.py index e80709848b..5f9610f883 100644 --- a/source/standalone/tutorials/03_envs/run_cartpole_rl_env.py +++ b/source/standalone/tutorials/03_envs/run_cartpole_rl_env.py @@ -28,9 +28,9 @@ import torch -from omni.isaac.lab.envs import RLTaskEnv +from omni.isaac.lab.envs import ManagerBasedRLEnv -from omni.isaac.lab_tasks.classic.cartpole.cartpole_env_cfg import CartpoleEnvCfg +from omni.isaac.lab_tasks.manager_based.classic.cartpole.cartpole_env_cfg import CartpoleEnvCfg def main(): @@ -39,7 +39,7 @@ def main(): env_cfg = CartpoleEnvCfg() env_cfg.scene.num_envs = args_cli.num_envs # setup RL environment - env = RLTaskEnv(cfg=env_cfg) + env = ManagerBasedRLEnv(cfg=env_cfg) # simulate physics count = 0 diff --git a/source/standalone/tutorials/04_sensors/run_usd_camera.py b/source/standalone/tutorials/04_sensors/run_usd_camera.py index f769cd02e1..e798c98828 100644 --- a/source/standalone/tutorials/04_sensors/run_usd_camera.py +++ b/source/standalone/tutorials/04_sensors/run_usd_camera.py @@ -15,7 +15,7 @@ ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py # Usage with headless - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py --headless --offscreen_render + ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py --headless --enable_cameras """ @@ -54,6 +54,7 @@ AppLauncher.add_app_launcher_args(parser) # parse the arguments args_cli = parser.parse_args() +args_cli.enable_cameras = True # launch omniverse app app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app @@ -235,12 +236,20 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): single_cam_info = camera.data.info[camera_index] # Pack data back into replicator format to save them using its writer - rep_output = dict() - for key, data, info in zip(single_cam_data.keys(), single_cam_data.values(), single_cam_info.values()): - if info is not None: - rep_output[key] = {"data": data, "info": info} - else: - rep_output[key] = data + if sim.get_version()[0] == 4: + rep_output = {"annotators": {}} + for key, data, info in zip(single_cam_data.keys(), single_cam_data.values(), single_cam_info.values()): + if info is not None: + rep_output["annotators"][key] = {"render_product": {"data": data, **info}} + else: + rep_output["annotators"][key] = {"render_product": {"data": data}} + else: + rep_output = dict() + for key, data, info in zip(single_cam_data.keys(), single_cam_data.values(), single_cam_info.values()): + if info is not None: + rep_output[key] = {"data": data, "info": info} + else: + rep_output[key] = data # Save images # Note: We need to provide On-time data for Replicator to save the images. rep_output["trigger_outputs"] = {"on_time": camera.frame[camera_index]} diff --git a/source/standalone/workflows/rl_games/play.py b/source/standalone/workflows/rl_games/play.py index 729c091f09..f14b92448f 100644 --- a/source/standalone/workflows/rl_games/play.py +++ b/source/standalone/workflows/rl_games/play.py @@ -113,8 +113,13 @@ def main(): # reset environment obs = env.reset() + if isinstance(obs, dict): + obs = obs["obs"] # required: enables the flag for batched observations _ = agent.get_batch_size(obs, 1) + # initialize RNN states if used + if agent.is_rnn: + agent.init_rnn() # simulate environment # note: We simplified the logic in rl-games player.py (:func:`BasePlayer.run()`) function in an # attempt to have complete control over environment stepping. However, this removes other diff --git a/source/standalone/workflows/rl_games/train.py b/source/standalone/workflows/rl_games/train.py index 2c9d5be00d..eadb8ee858 100644 --- a/source/standalone/workflows/rl_games/train.py +++ b/source/standalone/workflows/rl_games/train.py @@ -23,6 +23,11 @@ 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.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +parser.add_argument( + "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes." +) +parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.") + # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -76,6 +81,19 @@ def main(): agent_cfg["params"]["config"]["train_dir"] = log_root_path agent_cfg["params"]["config"]["full_experiment_name"] = log_dir + # multi-gpu training config + if args_cli.distributed: + agent_cfg["params"]["seed"] += app_launcher.global_rank + agent_cfg["params"]["config"]["device"] = f"cuda:{app_launcher.local_rank}" + agent_cfg["params"]["config"]["device_name"] = f"cuda:{app_launcher.local_rank}" + agent_cfg["params"]["config"]["multi_gpu"] = True + # update env config device + env_cfg.sim.device = f"cuda:{app_launcher.local_rank}" + + # max iterations + if args_cli.max_iterations: + agent_cfg["params"]["config"]["max_epochs"] = args_cli.max_iterations + # dump the configuration into log-directory dump_yaml(os.path.join(log_root_path, log_dir, "params", "env.yaml"), env_cfg) dump_yaml(os.path.join(log_root_path, log_dir, "params", "agent.yaml"), agent_cfg) @@ -92,7 +110,7 @@ def main(): # wrap for video recording if args_cli.video: video_kwargs = { - "video_folder": os.path.join(log_dir, "videos"), + "video_folder": os.path.join(log_root_path, log_dir, "videos"), "step_trigger": lambda step: step % args_cli.video_interval == 0, "video_length": args_cli.video_length, "disable_logger": True, diff --git a/source/standalone/workflows/robomimic/collect_demonstrations.py b/source/standalone/workflows/robomimic/collect_demonstrations.py index 56e6252303..fb938fdf53 100644 --- a/source/standalone/workflows/robomimic/collect_demonstrations.py +++ b/source/standalone/workflows/robomimic/collect_demonstrations.py @@ -40,7 +40,7 @@ from omni.isaac.lab.utils.io import dump_pickle, dump_yaml import omni.isaac.lab_tasks # noqa: F401 -from omni.isaac.lab_tasks.manipulation.lift import mdp +from omni.isaac.lab_tasks.manager_based.manipulation.lift import mdp from omni.isaac.lab_tasks.utils.data_collector import RobomimicDataCollector from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg diff --git a/source/standalone/workflows/rsl_rl/play.py b/source/standalone/workflows/rsl_rl/play.py index f34923eeef..062ebe4956 100644 --- a/source/standalone/workflows/rsl_rl/play.py +++ b/source/standalone/workflows/rsl_rl/play.py @@ -80,7 +80,10 @@ def main(): # export policy to onnx export_model_dir = os.path.join(os.path.dirname(resume_path), "exported") - export_policy_as_onnx(ppo_runner.alg.actor_critic, export_model_dir, filename="policy.onnx") + export_policy_as_jit( + ppo_runner.alg.actor_critic, ppo_runner.obs_normalizer, path=export_model_dir, filename="policy.pt" + ) + export_policy_as_onnx(ppo_runner.alg.actor_critic, path=export_model_dir, filename="policy.onnx") # reset environment obs, _ = env.get_observations() diff --git a/source/standalone/workflows/rsl_rl/train.py b/source/standalone/workflows/rsl_rl/train.py index b8d6bc3e0f..748d9bd6be 100644 --- a/source/standalone/workflows/rsl_rl/train.py +++ b/source/standalone/workflows/rsl_rl/train.py @@ -27,6 +27,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.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.") # append RSL-RL cli arguments cli_args.add_rsl_rl_args(parser) # append AppLauncher cli args @@ -46,7 +47,7 @@ from rsl_rl.runners import OnPolicyRunner -from omni.isaac.lab.envs import RLTaskEnvCfg +from omni.isaac.lab.envs import ManagerBasedRLEnvCfg from omni.isaac.lab.utils.dict import print_dict from omni.isaac.lab.utils.io import dump_pickle, dump_yaml @@ -63,7 +64,7 @@ def main(): """Train with RSL-RL agent.""" # parse configuration - env_cfg: RLTaskEnvCfg = parse_env_cfg( + env_cfg: ManagerBasedRLEnvCfg = parse_env_cfg( args_cli.task, use_gpu=not args_cli.cpu, num_envs=args_cli.num_envs, use_fabric=not args_cli.disable_fabric ) agent_cfg: RslRlOnPolicyRunnerCfg = cli_args.parse_rsl_rl_cfg(args_cli.task, args_cli) @@ -78,6 +79,10 @@ def main(): log_dir += f"_{agent_cfg.run_name}" log_dir = os.path.join(log_root_path, log_dir) + # max iterations for training + if args_cli.max_iterations: + agent_cfg.max_iterations = args_cli.max_iterations + # create isaac environment env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None) # wrap for video recording diff --git a/source/standalone/workflows/sb3/train.py b/source/standalone/workflows/sb3/train.py index d7813cca30..61bc288e39 100644 --- a/source/standalone/workflows/sb3/train.py +++ b/source/standalone/workflows/sb3/train.py @@ -28,6 +28,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.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -69,6 +70,10 @@ def main(): if args_cli.seed is not None: agent_cfg["seed"] = args_cli.seed + # max iterations for training + if args_cli.max_iterations: + agent_cfg["n_timesteps"] = args_cli.max_iterations * agent_cfg["n_steps"] * env_cfg.scene.num_envs + # directory for logging into log_dir = os.path.join("logs", "sb3", args_cli.task, datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) # dump the configuration into log-directory diff --git a/source/standalone/workflows/skrl/play.py b/source/standalone/workflows/skrl/play.py index 2bc4dc5d62..8d2784eabc 100644 --- a/source/standalone/workflows/skrl/play.py +++ b/source/standalone/workflows/skrl/play.py @@ -63,7 +63,7 @@ def main(): env = SkrlVecEnvWrapper(env) # same as: `wrap_env(env, wrapper="isaac-orbit")` # instantiate models using skrl model instantiator utility - # https://skrl.readthedocs.io/en/latest/modules/skrl.utils.model_instantiators.html + # https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models = {} # non-shared models if experiment_cfg["models"]["separate"]: @@ -95,7 +95,7 @@ def main(): models["value"] = models["policy"] # configure and instantiate PPO agent - # https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html + # https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent_cfg = PPO_DEFAULT_CONFIG.copy() experiment_cfg["agent"]["rewards_shaper"] = None # avoid 'dictionary changed size during iteration' agent_cfg.update(process_skrl_cfg(experiment_cfg["agent"])) diff --git a/source/standalone/workflows/skrl/train.py b/source/standalone/workflows/skrl/train.py index a3f7d75257..4e925769ee 100644 --- a/source/standalone/workflows/skrl/train.py +++ b/source/standalone/workflows/skrl/train.py @@ -29,6 +29,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.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -82,6 +83,10 @@ def main(): # update log_dir log_dir = os.path.join(log_root_path, log_dir) + # max iterations for training + if args_cli.max_iterations: + experiment_cfg["trainer"]["timesteps"] = args_cli.max_iterations * experiment_cfg["agent"]["rollouts"] + # dump the configuration into log-directory dump_yaml(os.path.join(log_dir, "params", "env.yaml"), env_cfg) dump_yaml(os.path.join(log_dir, "params", "agent.yaml"), experiment_cfg) @@ -108,7 +113,7 @@ def main(): set_seed(args_cli_seed if args_cli_seed is not None else experiment_cfg["seed"]) # instantiate models using skrl model instantiator utility - # https://skrl.readthedocs.io/en/latest/modules/skrl.utils.model_instantiators.html + # https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html models = {} # non-shared models if experiment_cfg["models"]["separate"]: @@ -140,12 +145,12 @@ def main(): models["value"] = models["policy"] # instantiate a RandomMemory as rollout buffer (any memory can be used for this) - # https://skrl.readthedocs.io/en/latest/modules/skrl.memories.random.html + # https://skrl.readthedocs.io/en/latest/api/memories/random.html memory_size = experiment_cfg["agent"]["rollouts"] # memory_size is the agent's number of rollouts memory = RandomMemory(memory_size=memory_size, num_envs=env.num_envs, device=env.device) # configure and instantiate PPO agent - # https://skrl.readthedocs.io/en/latest/modules/skrl.agents.ppo.html + # https://skrl.readthedocs.io/en/latest/api/agents/ppo.html agent_cfg = PPO_DEFAULT_CONFIG.copy() experiment_cfg["agent"]["rewards_shaper"] = None # avoid 'dictionary changed size during iteration' agent_cfg.update(process_skrl_cfg(experiment_cfg["agent"])) @@ -163,7 +168,7 @@ def main(): ) # configure and instantiate a custom RL trainer for logging episode events - # https://skrl.readthedocs.io/en/latest/modules/skrl.trainers.base_class.html + # https://skrl.readthedocs.io/en/latest/api/trainers.html trainer_cfg = experiment_cfg["trainer"] trainer = SkrlSequentialLogTrainer(cfg=trainer_cfg, env=env, agents=agent) diff --git a/tools/install_deps.py b/tools/install_deps.py deleted file mode 100644 index cc004566f9..0000000000 --- a/tools/install_deps.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) 2022-2024, The Isaac Lab Project Developers. -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -A script with various methods of installing dependencies -defined in an extension.toml -""" - -import argparse -import os -import shutil -import sys -import toml -from subprocess import SubprocessError, run - -# add argparse arguments -parser = argparse.ArgumentParser(description="Utility to install dependencies based on an extension.toml") -parser.add_argument("type", type=str, choices=["all", "apt", "rosdep"], help="The type of packages to install") -parser.add_argument("path", type=str, help="The path to the extension which will have its deps installed") - - -def install_apt_packages(path): - """ - A function which attempts to install apt packages for Isaac Lab extensions. - It looks in {extension_root}/config/extension.toml for [isaaclab_settings][apt_deps] - and then attempts to install them. Exits on failure to stop the build process - from continuing despite missing dependencies. - - Args: - path: A path to the extension root - """ - try: - if shutil.which("apt"): - with open(f"{path}/config/extension.toml") as fd: - ext_toml = toml.load(fd) - if "isaaclab_settings" in ext_toml and "apt_deps" in ext_toml["isaaclab_settings"]: - deps = ext_toml["isaaclab_settings"]["apt_deps"] - print(f"[INFO] Installing the following apt packages: {deps}") - run_and_print(["apt-get", "update"]) - run_and_print(["apt-get", "install", "-y"] + deps) - else: - print("[INFO] No apt packages to install") - else: - raise RuntimeError("Exiting because 'apt' is not a known command") - except SubprocessError as e: - print(f"[ERROR]: {str(e.stderr, encoding='utf-8')}") - sys.exit(1) - except Exception as e: - print(f"[ERROR]: {e}") - sys.exit(1) - - -def install_rosdep_packages(path): - """ - A function which attempts to install rosdep packages for Isaac Lab extensions. - It looks in {extension_root}/config/extension.toml for [isaaclab_settings][ros_ws] - and then attempts to install all rosdeps under that workspace. - Exits on failure to stop the build process from continuing despite missing dependencies. - - Args: - path: A path to the extension root - """ - try: - if shutil.which("rosdep"): - with open(f"{path}/config/extension.toml") as fd: - ext_toml = toml.load(fd) - if "isaaclab_settings" in ext_toml and "ros_ws" in ext_toml["isaaclab_settings"]: - ws_path = ext_toml["isaaclab_settings"]["ros_ws"] - if not os.path.exists("/etc/ros/rosdep/sources.list.d/20-default.list"): - run_and_print(["rosdep", "init"]) - run_and_print(["rosdep", "update", "--rosdistro=humble"]) - run_and_print([ - "rosdep", - "install", - "--from-paths", - f"{path}/{ws_path}/src", - "--ignore-src", - "-y", - "--rosdistro=humble", - ]) - else: - print("[INFO] No rosdep packages to install") - else: - raise RuntimeError("Exiting because 'rosdep' is not a known command") - except SubprocessError as e: - print(f"[ERROR]: {str(e.stderr, encoding='utf-8')}") - sys.exit(1) - except Exception as e: - print(f"[ERROR]: {e}") - sys.exit(1) - - -def run_and_print(args): - """ - Runs a subprocess.run(args=args, capture_output=True, check=True), - and prints the output - """ - completed_process = run(args=args, capture_output=True, check=True) - print(f"{str(completed_process.stdout, encoding='utf-8')}") - - -def main(): - args = parser.parse_args() - if args.type == "all": - install_apt_packages(args.path) - install_rosdep_packages(args.path) - elif args.type == "apt": - install_apt_packages(args.path) - elif args.type == "rosdep": - install_rosdep_packages(args.path) - else: - print(f"[ERROR] '{args.type}' type dependencies not installable") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/tools/run_all_tests.py b/tools/run_all_tests.py index b8a61ec2d1..69019b6cf9 100644 --- a/tools/run_all_tests.py +++ b/tools/run_all_tests.py @@ -65,7 +65,7 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument("--discover_only", action="store_true", help="Only discover and print tests, don't run them.") parser.add_argument("--quiet", action="store_true", help="Don't print to console, only log to file.") - parser.add_argument("--timeout", type=int, default=600, help="Timeout for each test in seconds.") + parser.add_argument("--timeout", type=int, default=1200, help="Timeout for each test in seconds.") # parse arguments args = parser.parse_args() return args @@ -75,7 +75,7 @@ def test_all( test_dir: str, tests_to_skip: list[str], log_path: str, - timeout: float = 600.0, + timeout: float = 1200.0, discover_only: bool = False, quiet: bool = False, ) -> bool: diff --git a/tools/tests_to_skip.py b/tools/tests_to_skip.py index 0ad57f2115..9db9172be2 100644 --- a/tools/tests_to_skip.py +++ b/tools/tests_to_skip.py @@ -7,6 +7,7 @@ TESTS_TO_SKIP = [ # lab "test_argparser_launch.py", # app.close issue + "test_build_simulation_context_nonheadless.py", # headless "test_env_var_launch.py", # app.close issue "test_kwarg_launch.py", # app.close issue "test_differential_ik.py", # Failing