Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/source/concepts/sensors/joint_wrench_sensor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ the same number or order of entries:

.. literalinclude:: ../../../../source/isaaclab_tasks/isaaclab_tasks/core/locomotion/ant/ant_manager_env_cfg.py
:language: python
:lines: 78-82
:start-at: joint_wrench = JointWrenchSensorCfg
:end-at: joint_wrench = JointWrenchSensorCfg

Manager-based environments can select a body subset through
:class:`~isaaclab.managers.SceneEntityCfg` and use
:func:`~isaaclab.envs.mdp.body_incoming_wrench` as an observation term:

.. literalinclude:: ../../../../source/isaaclab_tasks/isaaclab_tasks/core/locomotion/ant/ant_manager_env_cfg.py
:language: python
:lines: 122-131
:start-at: feet_body_forces = ObsTerm(
:end-at: actions = ObsTerm(func=mdp.last_action)

Read the data
-------------
Expand Down
6 changes: 3 additions & 3 deletions docs/source/features/hydra.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ For example, for the configuration of the Cartpole camera environment:

.. literalinclude:: ../../../source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env_cfg.py
:language: python
:start-at: class CartpoleTiledCameraCfg
:start-at: class CartpoleCameraEnvCfg(PresetCfg):
:end-at: observation_space = [3, 96, 96]

The configuration declares the single-frame channel count and a default spatial size.
Expand Down Expand Up @@ -313,10 +313,10 @@ Physics backend selection uses the same preset system. A task can define a

The Cartpole task's definition is a maintained example:

.. literalinclude:: ../../../source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_manager_env_cfg.py
.. literalinclude:: ../../../source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_common.py
:language: python
:start-at: class CartpolePhysicsCfg(PresetCfg):
:end-before: ##
:end-before: @configclass

The ``newton_mjwarp`` and ``newton_kamino`` entries both select the Newton physics backend because
both entries are :class:`~isaaclab_newton.physics.NewtonCfg` objects. The difference
Expand Down
2 changes: 1 addition & 1 deletion docs/source/how-to/create_manager_rl_env.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ For this tutorial, we use the cartpole environment defined in ``isaaclab_tasks.c

.. literalinclude:: ../../../source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_manager_env_cfg.py
:language: python
:emphasize-lines: 117-141, 144-154, 172-174
:emphasize-lines: 67-84, 87-110, 174-184
:linenos:

The script for running the environment ``run_cartpole_rl_env.py`` is present in the
Expand Down
10 changes: 10 additions & 0 deletions source/isaaclab/changelog.d/unify-task-mdp-terms.minor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Added
^^^^^

* Added :class:`~isaaclab.envs.mdp.rewards.survival_success_rate`, :func:`~isaaclab.envs.mdp.rewards.terminated_penalty`
and :func:`~isaaclab.envs.mdp.rewards.joint_pos_target_l2` reward terms, previously duplicated across the cartpole,
locomotion and DR-legs task packages.
* Added :class:`~isaaclab.envs.mdp.curriculums.DifficultyScheduler` and
:func:`~isaaclab.envs.mdp.curriculums.initial_final_interpolate_fn` for adaptive domain randomization curricula,
previously local to the lift task package. The scheduler reads the success flag from the reward term named by the new
``success_term_name`` parameter (default ``"success"``).
16 changes: 15 additions & 1 deletion source/isaaclab/isaaclab/envs/mdp/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ __all__ = [
"UniformPoseCommandCfg",
"UniformVelocityCommand",
"UniformVelocityCommandCfg",
"DifficultyScheduler",
"initial_final_interpolate_fn",
"modify_env_param",
"modify_reward_weight",
"modify_term_cfg",
Expand Down Expand Up @@ -127,6 +129,7 @@ __all__ = [
"joint_acc_l2",
"joint_deviation_l1",
"joint_pos_limits",
"joint_pos_target_l2",
"joint_torques_l2",
"joint_vel_l1",
"joint_vel_l2",
Expand All @@ -135,6 +138,8 @@ __all__ = [
"orientation_command_error",
"position_command_error",
"position_command_error_tanh",
"survival_success_rate",
"terminated_penalty",
"track_ang_vel_z_exp",
"track_lin_vel_xy_exp",
"undesired_contacts",
Expand Down Expand Up @@ -197,7 +202,13 @@ from .commands import (
UniformVelocityCommand,
UniformVelocityCommandCfg,
)
from .curriculums import modify_env_param, modify_reward_weight, modify_term_cfg
from .curriculums import (
DifficultyScheduler,
initial_final_interpolate_fn,
modify_env_param,
modify_reward_weight,
modify_term_cfg,
)
from .events import (
apply_external_force_torque,
push_by_setting_velocity,
Expand Down Expand Up @@ -282,6 +293,7 @@ from .rewards import (
joint_acc_l2,
joint_deviation_l1,
joint_pos_limits,
joint_pos_target_l2,
joint_torques_l2,
joint_vel_l1,
joint_vel_l2,
Expand All @@ -290,6 +302,8 @@ from .rewards import (
orientation_command_error,
position_command_error,
position_command_error_tanh,
survival_success_rate,
terminated_penalty,
track_ang_vel_z_exp,
track_lin_vel_xy_exp,
undesired_contacts,
Expand Down
89 changes: 88 additions & 1 deletion source/isaaclab/isaaclab/envs/mdp/curriculums.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

import re
from collections.abc import Sequence
from typing import TYPE_CHECKING, ClassVar
from typing import TYPE_CHECKING, Any, ClassVar

import torch

from isaaclab.managers import CurriculumTermCfg, ManagerTermBase

Expand Down Expand Up @@ -294,3 +296,88 @@ def __init__(self, cfg, env):
super().__init__(cfg, env)
# overwrite the simplified address with the full manager path
self._address = self._address.replace("s.", "_manager.cfg.", 1)


class DifficultyScheduler(ManagerTermBase):
"""Adaptive difficulty scheduler for curriculum learning.

Each environment keeps an integer difficulty level. At episode end the level is promoted when the
reward term named by ``success_term_name`` reports success for that environment through a sticky
boolean ``succeeded`` buffer, and demoted otherwise unless ``promotion_only`` is set. The normalized
mean difficulty across environments is exposed as :attr:`difficulty_frac` for other curriculum terms,
such as :func:`initial_final_interpolate_fn`, to interpolate their targets.
"""

def __init__(self, cfg: CurriculumTermCfg, env: ManagerBasedRLEnv):
super().__init__(cfg, env)
init_difficulty: int = cfg.params.get("init_difficulty", 0)
self.current_difficulties = torch.full((env.num_envs,), float(init_difficulty), device=env.device)
self.difficulty_frac: float = 0.0
"""Mean difficulty across environments, normalized by ``max_difficulty``."""

def get_state(self) -> torch.Tensor:
return self.current_difficulties

def set_state(self, state: torch.Tensor) -> None:
self.current_difficulties = state.clone().to(self._env.device)

def __call__(
self,
env: ManagerBasedRLEnv,
env_ids: Sequence[int],
init_difficulty: int = 0,
min_difficulty: int = 0,
max_difficulty: int = 50,
promotion_only: bool = False,
success_term_name: str = "success",
) -> float:
# the success term must be a class-based reward exposing a per-environment boolean ``succeeded`` buffer
succeeded = env.reward_manager.get_term_cfg(success_term_name).func.succeeded[env_ids]
current = self.current_difficulties[env_ids]
demoted = current if promotion_only else current - 1
self.current_difficulties[env_ids] = torch.where(succeeded, current + 1, demoted).clamp(
min=min_difficulty, max=max_difficulty
)
# Python float: the dependent curriculum terms compare and interpolate host-side
self.difficulty_frac = (torch.mean(self.current_difficulties) / max(max_difficulty, 1)).item()
return self.difficulty_frac


def initial_final_interpolate_fn(
env: ManagerBasedRLEnv,
env_ids: Sequence[int],
data: Any,
initial_value: Any,
final_value: Any,
difficulty_term_str: str,
) -> Any:
"""Interpolate a term parameter between initial and final values by the current difficulty fraction.

Intended as the ``modify_fn`` of :class:`modify_term_cfg`. Works on arbitrarily nested lists and
tuples; scalars (int and float) are interpolated at the leaves and integers stay integers.

Args:
env: The environment.
env_ids: Environments being updated. Unused, the interpolation is shared by all environments.
data: Current value of the parameter, which fixes the structure and leaf types of the result.
initial_value: Value at zero difficulty.
final_value: Value at maximum difficulty.
difficulty_term_str: Name of the :class:`DifficultyScheduler` curriculum term to read.

Returns:
The interpolated value, or :attr:`modify_env_param.NO_CHANGE` while the difficulty is below 10%.
"""
difficulty_term: DifficultyScheduler = getattr(env.curriculum_manager.cfg, difficulty_term_str).func
frac = difficulty_term.difficulty_frac
# leave the parameter at its configured value until the curriculum has made some progress
if frac < 0.1:
return modify_env_param.NO_CHANGE
return _interpolate_nested(initial_value, final_value, data, frac)


def _interpolate_nested(initial: Any, final: Any, data: Any, frac: float) -> Any:
"""Interpolate leaf scalars of nested sequences, preserving the container and leaf types of ``data``."""
if isinstance(data, Sequence) and not isinstance(data, (str, bytes)):
return type(data)(_interpolate_nested(i, f, d, frac) for i, f, d in zip(initial, final, data))
value = frac * (final - initial) + initial
return int(value) if isinstance(data, int) else value
40 changes: 39 additions & 1 deletion source/isaaclab/isaaclab/envs/mdp/rewards.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from isaaclab.managers import SceneEntityCfg
from isaaclab.managers.manager_base import ManagerTermBase
from isaaclab.managers.manager_term_cfg import RewardTermCfg
from isaaclab.utils.math import combine_frame_transforms, quat_error_magnitude, quat_mul
from isaaclab.utils.math import combine_frame_transforms, quat_error_magnitude, quat_mul, wrap_to_pi

if TYPE_CHECKING:
from isaaclab.assets import Articulation, RigidObject
Expand Down Expand Up @@ -70,6 +70,33 @@ def __call__(self, env: ManagerBasedRLEnv, term_keys: str | list[str] = ".*") ->
return (reset_buf * (~env.termination_manager.time_outs)).float()


def terminated_penalty(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize early termination once, independently of the environment step size.

:class:`~isaaclab.managers.RewardManager` scales every term by the step interval, which would make a
plain terminal penalty depend on ``sim.dt`` and ``decimation``. Dividing by the step interval here
cancels that scaling, so the term contributes exactly its weight on the step the episode terminates. This
keeps the penalty equal to the fixed death cost the direct workflow applies.
"""
return env.termination_manager.terminated.float() / env.step_dt


class survival_success_rate(ManagerTermBase):
"""Track episode survival as the success metric.

The term returns zero reward and only tracks the metric. On episode reset it writes
``Metrics/success_rate`` into ``extras["log"]``, where an episode counts as a success when it
timed out without terminating early.
"""

def reset(self, env_ids: torch.Tensor) -> None:
survived = self._env.termination_manager.time_outs[env_ids]
self._env.extras.setdefault("log", {})["Metrics/success_rate"] = survived.float().mean().item()

def __call__(self, env: ManagerBasedRLEnv) -> torch.Tensor:
return torch.zeros(env.num_envs, device=env.device)


"""
Root penalties.
"""
Expand Down Expand Up @@ -189,6 +216,17 @@ def joint_deviation_l1(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = Scene
return torch.sum(torch.abs(angle), dim=1)


def joint_pos_target_l2(env: ManagerBasedRLEnv, target: float, asset_cfg: SceneEntityCfg) -> torch.Tensor:
"""Penalize joint positions that deviate from a target value using an L2 squared kernel.

The joint positions are wrapped to ``[-pi, pi]`` before the deviation is computed.
"""
# extract the used quantities (to enable type-hinting)
asset: Articulation = env.scene[asset_cfg.name]
joint_pos = wrap_to_pi(asset.data.joint_pos.torch[:, asset_cfg.joint_ids])
return torch.sum(torch.square(joint_pos - target), dim=1)


def joint_pos_limits(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Penalize joint positions if they cross the soft limits.

Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@
import isaaclab.envs.mdp.terminations as stable_term
from isaaclab.managers.manager_term_cfg import RewardTermCfg, TerminationTermCfg

import isaaclab_tasks.core.locomotion.mdp.rewards as stable_loco_rew


@dataclasses.dataclass(frozen=True)
class CaptureCase:
Expand Down Expand Up @@ -208,7 +206,7 @@ def mutate() -> None:

return CaptureCase(
warp_fn=warp_loco_rew.terminated_penalty,
stable_fn=stable_loco_rew.terminated_penalty,
stable_fn=stable_rew.terminated_penalty,
warp_env=env,
stable_env=env,
params={},
Expand All @@ -228,8 +226,8 @@ def mutate() -> None:

return CaptureCase(
warp_fn=warp_loco_rew.survival_success_rate(cfg, env),
stable_fn=stable_loco_rew.survival_success_rate(
RewardTermCfg(func=stable_loco_rew.survival_success_rate, weight=0.0, params={}), env
stable_fn=stable_rew.survival_success_rate(
RewardTermCfg(func=stable_rew.survival_success_rate, weight=0.0, params={}), env
),
warp_env=env,
stable_env=env,
Expand Down Expand Up @@ -394,7 +392,7 @@ def _discover_warp_mdp_terms() -> set[str]:
"""Return every public warp MDP term as a ``"<module>:<name>"`` identity.

Qualified rather than bare: the same term name legitimately appears in more than one task
mirror (``survival_success_rate`` is defined by both cartpole and locomotion), and keying
mirror (``survival_success_rate`` is twinned by both cartpole and locomotion), and keying
by name alone would let a spec for one of them mark the other as declared.
"""
terms: set[str] = set()
Expand Down
39 changes: 39 additions & 0 deletions source/isaaclab_tasks/changelog.d/unify-task-coding-style.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Changed
^^^^^^^

* Unified the coding style of the core task packages: module docstrings, section banners, import style, gym
registration layout, stub (``.pyi``) layout and ``ManagerTermBase`` constructor signatures now follow one convention.
* Moved the duplicated ``survival_success_rate``, ``terminated_penalty``, ``joint_pos_target_l2``,
``DifficultyScheduler`` and ``initial_final_interpolate_fn`` terms to :mod:`isaaclab.envs.mdp`. The task ``mdp``
packages re-export them, so ``mdp.<term>`` references and wildcard imports keep working. Import them from
:mod:`isaaclab.envs.mdp` instead of the task packages.
* Unified the import style of the core task packages: relative imports within a task package, absolute imports
across packages.
* Renamed the helpers shared across modules that carried a leading underscore: ``ResetDatasetSampler`` and
``configure_mpm_capacities`` (Franka pour), ``nearest_grasp_to_tcp_quat`` (Franka pour), ``build_gr1t2_pickplace_pipeline``
(pick-place), ``offset_body_pose`` (multitask manipulation), ``FONT_5X7`` (keyboard), ``get_delta_dof_pos`` (factory and
AutoMate control) and :func:`isaaclab_tasks.utils.hydra.user_stacklevel`. Drop the underscore at the call sites.
* Moved the fourbar-pole ``joint_pos_cos`` and ``joint_pos_sin`` observation terms from ``mdp/rewards.py`` to
``mdp/observations.py``; they remain available as ``mdp.joint_pos_cos`` and ``mdp.joint_pos_sin``.
* Shared the physics, camera and asset presets of the direct and manager-based cartpole, Ant and Humanoid tasks
through new ``cartpole_common``, ``ant_common`` and ``humanoid_common`` modules instead of duplicating them.
* Renamed the private ``_FrankaSoftSceneCfg`` and ``_FrankaSoftCameraSceneCfg`` scene configurations of the Franka
soft-body tasks to the public ``FrankaSoftBaseSceneCfg`` and ``FrankaSoftBaseCameraSceneCfg``.
* Replaced the deprecated ``viewer`` settings of the handover and Franka soft-body tasks with
``sim.default_visualizer_cfg``.
* Registered a default agent for the ``Isaac-Shadow-Handover``, ``Isaac-Lift-Cable-Franka`` and
``Isaac-Lift-Cable-Franka-Camera`` tasks.

Fixed
^^^^^

* Fixed the lift ADR curriculum interpolating the point-cloud noise upper bound towards ``-0.01`` instead of ``0.01``.
* Fixed the ``LiftEnvCfg`` configuration class missing the ``@configclass`` decorator.
* Fixed the keyboard typing command importing ``SuccessMonitor`` from the lift package instead of
:mod:`isaaclab_tasks.utils.success_monitor`.
* Fixed the in-hand reorientation keypoint helpers rebuilding constant corner offsets on the device every step, and
the lift deformable and cable out-of-bounds terminations allocating constant bound tensors every step.
* Fixed the lift, handover and reorientation tasks rebuilding per-step index and origin tensors with ``repeat`` where a
broadcast suffices.
* Fixed docstrings stating a ``(w, x, y, z)`` quaternion order in the lift, handover, deploy and keyboard task
packages; Isaac Lab uses ``(x, y, z, w)``.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause

"""Reward terms for the trocar assembly environment."""

from __future__ import annotations

import logging
Expand All @@ -11,6 +13,7 @@

import torch

import isaaclab.utils.math as math_utils
from isaaclab.managers import SceneEntityCfg
from isaaclab.utils.math import quat_apply

Expand Down Expand Up @@ -295,10 +298,9 @@ def get_trocar_tip_position(
Returns:
torch.Tensor: Shape (num_envs, 3) - Position in world coordinates
"""
# USD is a runtime dependency that must not load at config-import time
from pxr import Gf, Usd, UsdGeom

import isaaclab.utils.math as math_utils

# Cache the tip offset to avoid recalculating every step.
# The local offset from root to tip is a static geometric property of the USD
# asset and is identical across all replicated envs. We read it once from env_0's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause

"""Termination terms for the trocar assembly environment."""

from __future__ import annotations

import logging
Expand Down
Loading
Loading