Unverified Commit ee3f0224 authored by robotsfan's avatar robotsfan Committed by GitHub

Adds clip range for JointAction (#1476)

# Description

This PR adds support for action clip to all mdp/actions. Clip ranges can
be specified as a dictionary of joint names and tuple for the lower and
upper bounds of the clip in the ActionTermCfg.

## Type of change

- New feature (non-breaking change which adds functionality)
- This change requires a documentation update


## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------
Signed-off-by: 's avatarKelly Guo <kellyg@nvidia.com>
Co-authored-by: 's avatarKelly Guo <kellyg@nvidia.com>
parent 8ddc4830
[package] [package]
# Note: Semantic Versioning is used: https://semver.org/ # Note: Semantic Versioning is used: https://semver.org/
version = "0.27.28" version = "0.27.29"
# Description # Description
title = "Isaac Lab framework for Robot Learning" title = "Isaac Lab framework for Robot Learning"
......
Changelog Changelog
--------- ---------
0.27.29 (2024-12-15)
~~~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Added action clip to all :class:`omni.isaac.lab.envs.mdp.actions`.
0.27.28 (2024-12-14) 0.27.28 (2024-12-14)
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
......
...@@ -40,9 +40,10 @@ class BinaryJointAction(ActionTerm): ...@@ -40,9 +40,10 @@ class BinaryJointAction(ActionTerm):
cfg: actions_cfg.BinaryJointActionCfg cfg: actions_cfg.BinaryJointActionCfg
"""The configuration of the action term.""" """The configuration of the action term."""
_asset: Articulation _asset: Articulation
"""The articulation asset on which the action term is applied.""" """The articulation asset on which the action term is applied."""
_clip: torch.Tensor
"""The clip applied to the input action."""
def __init__(self, cfg: actions_cfg.BinaryJointActionCfg, env: ManagerBasedEnv) -> None: def __init__(self, cfg: actions_cfg.BinaryJointActionCfg, env: ManagerBasedEnv) -> None:
# initialize the action term # initialize the action term
...@@ -83,6 +84,17 @@ class BinaryJointAction(ActionTerm): ...@@ -83,6 +84,17 @@ class BinaryJointAction(ActionTerm):
) )
self._close_command[index_list] = torch.tensor(value_list, device=self.device) self._close_command[index_list] = torch.tensor(value_list, device=self.device)
# parse clip
if self.cfg.clip is not None:
if isinstance(cfg.clip, dict):
self._clip = torch.tensor([[-float("inf"), float("inf")]], device=self.device).repeat(
self.num_envs, self.action_dim, 1
)
index_list, _, value_list = string_utils.resolve_matching_names_values(self.cfg.clip, self._joint_names)
self._clip[:, index_list] = torch.tensor(value_list, device=self.device)
else:
raise ValueError(f"Unsupported clip type: {type(cfg.clip)}. Supported types are dict.")
""" """
Properties. Properties.
""" """
...@@ -115,6 +127,10 @@ class BinaryJointAction(ActionTerm): ...@@ -115,6 +127,10 @@ class BinaryJointAction(ActionTerm):
binary_mask = actions < 0 binary_mask = actions < 0
# compute the command # compute the command
self._processed_actions = torch.where(binary_mask, self._close_command, self._open_command) self._processed_actions = torch.where(binary_mask, self._close_command, self._open_command)
if self.cfg.clip is not None:
self._processed_actions = torch.clamp(
self._processed_actions, min=self._clip[:, :, 0], max=self._clip[:, :, 1]
)
def reset(self, env_ids: Sequence[int] | None = None) -> None: def reset(self, env_ids: Sequence[int] | None = None) -> None:
self._raw_actions[env_ids] = 0.0 self._raw_actions[env_ids] = 0.0
......
...@@ -50,6 +50,8 @@ class JointAction(ActionTerm): ...@@ -50,6 +50,8 @@ class JointAction(ActionTerm):
"""The scaling factor applied to the input action.""" """The scaling factor applied to the input action."""
_offset: torch.Tensor | float _offset: torch.Tensor | float
"""The offset applied to the input action.""" """The offset applied to the input action."""
_clip: torch.Tensor
"""The clip applied to the input action."""
def __init__(self, cfg: actions_cfg.JointActionCfg, env: ManagerBasedEnv) -> None: def __init__(self, cfg: actions_cfg.JointActionCfg, env: ManagerBasedEnv) -> None:
# initialize the action term # initialize the action term
...@@ -94,6 +96,16 @@ class JointAction(ActionTerm): ...@@ -94,6 +96,16 @@ class JointAction(ActionTerm):
self._offset[:, index_list] = torch.tensor(value_list, device=self.device) self._offset[:, index_list] = torch.tensor(value_list, device=self.device)
else: else:
raise ValueError(f"Unsupported offset type: {type(cfg.offset)}. Supported types are float and dict.") raise ValueError(f"Unsupported offset type: {type(cfg.offset)}. Supported types are float and dict.")
# parse clip
if self.cfg.clip is not None:
if isinstance(cfg.clip, dict):
self._clip = torch.tensor([[-float("inf"), float("inf")]], device=self.device).repeat(
self.num_envs, self.action_dim, 1
)
index_list, _, value_list = string_utils.resolve_matching_names_values(self.cfg.clip, self._joint_names)
self._clip[:, index_list] = torch.tensor(value_list, device=self.device)
else:
raise ValueError(f"Unsupported clip type: {type(cfg.clip)}. Supported types are dict.")
""" """
Properties. Properties.
...@@ -120,6 +132,11 @@ class JointAction(ActionTerm): ...@@ -120,6 +132,11 @@ class JointAction(ActionTerm):
self._raw_actions[:] = actions self._raw_actions[:] = actions
# apply the affine transformations # apply the affine transformations
self._processed_actions = self._raw_actions * self._scale + self._offset self._processed_actions = self._raw_actions * self._scale + self._offset
# clip actions
if self.cfg.clip is not None:
self._processed_actions = torch.clamp(
self._processed_actions, min=self._clip[:, :, 0], max=self._clip[:, :, 1]
)
def reset(self, env_ids: Sequence[int] | None = None) -> None: def reset(self, env_ids: Sequence[int] | None = None) -> None:
self._raw_actions[env_ids] = 0.0 self._raw_actions[env_ids] = 0.0
......
...@@ -44,6 +44,8 @@ class JointPositionToLimitsAction(ActionTerm): ...@@ -44,6 +44,8 @@ class JointPositionToLimitsAction(ActionTerm):
"""The articulation asset on which the action term is applied.""" """The articulation asset on which the action term is applied."""
_scale: torch.Tensor | float _scale: torch.Tensor | float
"""The scaling factor applied to the input action.""" """The scaling factor applied to the input action."""
_clip: torch.Tensor
"""The clip applied to the input action."""
def __init__(self, cfg: actions_cfg.JointPositionToLimitsActionCfg, env: ManagerBasedEnv): def __init__(self, cfg: actions_cfg.JointPositionToLimitsActionCfg, env: ManagerBasedEnv):
# initialize the action term # initialize the action term
...@@ -76,6 +78,16 @@ class JointPositionToLimitsAction(ActionTerm): ...@@ -76,6 +78,16 @@ class JointPositionToLimitsAction(ActionTerm):
self._scale[:, index_list] = torch.tensor(value_list, device=self.device) self._scale[:, index_list] = torch.tensor(value_list, device=self.device)
else: else:
raise ValueError(f"Unsupported scale type: {type(cfg.scale)}. Supported types are float and dict.") raise ValueError(f"Unsupported scale type: {type(cfg.scale)}. Supported types are float and dict.")
# parse clip
if self.cfg.clip is not None:
if isinstance(cfg.clip, dict):
self._clip = torch.tensor([[-float("inf"), float("inf")]], device=self.device).repeat(
self.num_envs, self.action_dim, 1
)
index_list, _, value_list = string_utils.resolve_matching_names_values(self.cfg.clip, self._joint_names)
self._clip[:, index_list] = torch.tensor(value_list, device=self.device)
else:
raise ValueError(f"Unsupported clip type: {type(cfg.clip)}. Supported types are dict.")
""" """
Properties. Properties.
...@@ -102,6 +114,10 @@ class JointPositionToLimitsAction(ActionTerm): ...@@ -102,6 +114,10 @@ class JointPositionToLimitsAction(ActionTerm):
self._raw_actions[:] = actions self._raw_actions[:] = actions
# apply affine transformations # apply affine transformations
self._processed_actions = self._raw_actions * self._scale self._processed_actions = self._raw_actions * self._scale
if self.cfg.clip is not None:
self._processed_actions = torch.clamp(
self._processed_actions, min=self._clip[:, :, 0], max=self._clip[:, :, 1]
)
# rescale the position targets if configured # rescale the position targets if configured
# this is useful when the input actions are in the range [-1, 1] # this is useful when the input actions are in the range [-1, 1]
if self.cfg.rescale_to_limits: if self.cfg.rescale_to_limits:
......
...@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING ...@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING
import omni.log import omni.log
import omni.isaac.lab.utils.string as string_utils
from omni.isaac.lab.assets.articulation import Articulation from omni.isaac.lab.assets.articulation import Articulation
from omni.isaac.lab.managers.action_manager import ActionTerm from omni.isaac.lab.managers.action_manager import ActionTerm
from omni.isaac.lab.utils.math import euler_xyz_from_quat from omni.isaac.lab.utils.math import euler_xyz_from_quat
...@@ -59,6 +60,8 @@ class NonHolonomicAction(ActionTerm): ...@@ -59,6 +60,8 @@ class NonHolonomicAction(ActionTerm):
"""The scaling factor applied to the input action. Shape is (1, 2).""" """The scaling factor applied to the input action. Shape is (1, 2)."""
_offset: torch.Tensor _offset: torch.Tensor
"""The offset applied to the input action. Shape is (1, 2).""" """The offset applied to the input action. Shape is (1, 2)."""
_clip: torch.Tensor
"""The clip applied to the input action."""
def __init__(self, cfg: actions_cfg.NonHolonomicActionCfg, env: ManagerBasedEnv): def __init__(self, cfg: actions_cfg.NonHolonomicActionCfg, env: ManagerBasedEnv):
# initialize the action term # initialize the action term
...@@ -104,6 +107,16 @@ class NonHolonomicAction(ActionTerm): ...@@ -104,6 +107,16 @@ class NonHolonomicAction(ActionTerm):
# save the scale and offset as tensors # save the scale and offset as tensors
self._scale = torch.tensor(self.cfg.scale, device=self.device).unsqueeze(0) self._scale = torch.tensor(self.cfg.scale, device=self.device).unsqueeze(0)
self._offset = torch.tensor(self.cfg.offset, device=self.device).unsqueeze(0) self._offset = torch.tensor(self.cfg.offset, device=self.device).unsqueeze(0)
# parse clip
if self.cfg.clip is not None:
if isinstance(cfg.clip, dict):
self._clip = torch.tensor([[-float("inf"), float("inf")]], device=self.device).repeat(
self.num_envs, self.action_dim, 1
)
index_list, _, value_list = string_utils.resolve_matching_names_values(self.cfg.clip, self._joint_names)
self._clip[:, index_list] = torch.tensor(value_list, device=self.device)
else:
raise ValueError(f"Unsupported clip type: {type(cfg.clip)}. Supported types are dict.")
""" """
Properties. Properties.
...@@ -129,6 +142,11 @@ class NonHolonomicAction(ActionTerm): ...@@ -129,6 +142,11 @@ class NonHolonomicAction(ActionTerm):
# store the raw actions # store the raw actions
self._raw_actions[:] = actions self._raw_actions[:] = actions
self._processed_actions = self.raw_actions * self._scale + self._offset self._processed_actions = self.raw_actions * self._scale + self._offset
# clip actions
if self.cfg.clip is not None:
self._processed_actions = torch.clamp(
self._processed_actions, min=self._clip[:, :, 0], max=self._clip[:, :, 1]
)
def apply_actions(self): def apply_actions(self):
# obtain current heading # obtain current heading
......
...@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING ...@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING
import omni.log import omni.log
import omni.isaac.lab.utils.math as math_utils import omni.isaac.lab.utils.math as math_utils
import omni.isaac.lab.utils.string as string_utils
from omni.isaac.lab.assets.articulation import Articulation from omni.isaac.lab.assets.articulation import Articulation
from omni.isaac.lab.controllers.differential_ik import DifferentialIKController from omni.isaac.lab.controllers.differential_ik import DifferentialIKController
from omni.isaac.lab.managers.action_manager import ActionTerm from omni.isaac.lab.managers.action_manager import ActionTerm
...@@ -42,6 +43,8 @@ class DifferentialInverseKinematicsAction(ActionTerm): ...@@ -42,6 +43,8 @@ class DifferentialInverseKinematicsAction(ActionTerm):
"""The articulation asset on which the action term is applied.""" """The articulation asset on which the action term is applied."""
_scale: torch.Tensor _scale: torch.Tensor
"""The scaling factor applied to the input action. Shape is (1, action_dim).""" """The scaling factor applied to the input action. Shape is (1, action_dim)."""
_clip: torch.Tensor
"""The clip applied to the input action."""
def __init__(self, cfg: actions_cfg.DifferentialInverseKinematicsActionCfg, env: ManagerBasedEnv): def __init__(self, cfg: actions_cfg.DifferentialInverseKinematicsActionCfg, env: ManagerBasedEnv):
# initialize the action term # initialize the action term
...@@ -101,6 +104,17 @@ class DifferentialInverseKinematicsAction(ActionTerm): ...@@ -101,6 +104,17 @@ class DifferentialInverseKinematicsAction(ActionTerm):
else: else:
self._offset_pos, self._offset_rot = None, None self._offset_pos, self._offset_rot = None, None
# parse clip
if self.cfg.clip is not None:
if isinstance(cfg.clip, dict):
self._clip = torch.tensor([[-float("inf"), float("inf")]], device=self.device).repeat(
self.num_envs, self.action_dim, 1
)
index_list, _, value_list = string_utils.resolve_matching_names_values(self.cfg.clip, self._joint_names)
self._clip[:, index_list] = torch.tensor(value_list, device=self.device)
else:
raise ValueError(f"Unsupported clip type: {type(cfg.clip)}. Supported types are dict.")
""" """
Properties. Properties.
""" """
...@@ -138,6 +152,10 @@ class DifferentialInverseKinematicsAction(ActionTerm): ...@@ -138,6 +152,10 @@ class DifferentialInverseKinematicsAction(ActionTerm):
# store the raw actions # store the raw actions
self._raw_actions[:] = actions self._raw_actions[:] = actions
self._processed_actions[:] = self.raw_actions * self._scale self._processed_actions[:] = self.raw_actions * self._scale
if self.cfg.clip is not None:
self._processed_actions = torch.clamp(
self._processed_actions, min=self._clip[:, :, 0], max=self._clip[:, :, 1]
)
# obtain quantities from simulation # obtain quantities from simulation
ee_pos_curr, ee_quat_curr = self._compute_frame_pose() ee_pos_curr, ee_quat_curr = self._compute_frame_pose()
# set command into controller # set command into controller
......
...@@ -93,6 +93,9 @@ class ActionTermCfg: ...@@ -93,6 +93,9 @@ class ActionTermCfg:
debug_vis: bool = False debug_vis: bool = False
"""Whether to visualize debug information. Defaults to False.""" """Whether to visualize debug information. Defaults to False."""
clip: dict[str, tuple] | None = None
"""Clip range for the action (dict of regex expressions). Defaults to None."""
## ##
# Command manager. # Command manager.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment