Unverified Commit a60168a2 authored by Maurice Rahme's avatar Maurice Rahme Committed by GitHub

Fixes termination term effort limit check logic (#3163)

# Description

Fix the logic in `joint_effort_out_of_limit()`; this function aims to
detect effort limit violations by comparing the desired (computed)
effort against the applied effort, noting that the simulator clips
applied torque to the limit assign on creation.

Originally, the logic was written such that if the applied and computed
torques are equal, then a violation has occurred. However, this is wrong
as shown by the below example:

```python
from isaaclab.managers import SceneEntityCfg
from isaaclab.envs.mdp.terminations import joint_effort_out_of_limit

env = ...  # any ManagerBasedRLEnv with an Articulation named "robot"
cfg = SceneEntityCfg(name="robot", joint_ids=[0])  # single joint for clarity
art = env.scene["robot"]

# Case A: no clipping (should be False but returns True now)
art.data.computed_torque[:] = 0.0
art.data.applied_torque[:] = 0.0
assert joint_effort_out_of_limit(env, cfg).item() is False  # CURRENT: True (bug)

# Case B: clipping (should be True but returns False now)
art.data.computed_torque[:] = 100.0
art.data.applied_torque[:] = 50.0  # pretend actuator clipped to ±50
assert joint_effort_out_of_limit(env, cfg).item() is True   # CURRENT: False (bug)

```

The solution is hence simply to flip the limit detection logic.

Fixes #3155


## Type of change

- Bug fix (non-breaking change which fixes an issue)

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation (N/A)
- [x] My changes generate no new warnings
- [x] 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 (N/A)
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent 6f605a8d
...@@ -90,6 +90,7 @@ Guidelines for modifications: ...@@ -90,6 +90,7 @@ Guidelines for modifications:
* Lukas Fröhlich * Lukas Fröhlich
* Manuel Schweiger * Manuel Schweiger
* Masoud Moghani * Masoud Moghani
* Maurice Rahme
* Michael Gussert * Michael Gussert
* Michael Noseworthy * Michael Noseworthy
* Miguel Alonso Jr * Miguel Alonso Jr
......
[package] [package]
# Note: Semantic Versioning is used: https://semver.org/ # Note: Semantic Versioning is used: https://semver.org/
version = "0.45.1" version = "0.45.3"
# Description # Description
......
Changelog Changelog
--------- ---------
0.45.3 (2025-08-20)
~~~~~~~~~~~~~~~~~~~
Fixed
^^^^^
* Fixed :meth:`isaaclab.envs.mdp.terminations.joint_effort_out_of_limit` so that it correctly reports whether a joint
effort limit has been violated. Previously, the implementation marked a violation when the applied and computed
torques were equal; in fact, equality should indicate no violation, and vice versa.
0.45.2 (2025-08-18) 0.45.2 (2025-08-18)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
......
...@@ -134,12 +134,12 @@ def joint_effort_out_of_limit( ...@@ -134,12 +134,12 @@ def joint_effort_out_of_limit(
In the actuators, the applied torque are the efforts applied on the joints. These are computed by clipping In the actuators, the applied torque are the efforts applied on the joints. These are computed by clipping
the computed torques to the joint limits. Hence, we check if the computed torques are equal to the applied the computed torques to the joint limits. Hence, we check if the computed torques are equal to the applied
torques. torques. If they are not, it means that clipping has occurred.
""" """
# extract the used quantities (to enable type-hinting) # extract the used quantities (to enable type-hinting)
asset: Articulation = env.scene[asset_cfg.name] asset: Articulation = env.scene[asset_cfg.name]
# check if any joint effort is out of limit # check if any joint effort is out of limit
out_of_limits = torch.isclose( out_of_limits = ~torch.isclose(
asset.data.computed_torque[:, asset_cfg.joint_ids], asset.data.applied_torque[:, asset_cfg.joint_ids] asset.data.computed_torque[:, asset_cfg.joint_ids], asset.data.applied_torque[:, asset_cfg.joint_ids]
) )
return torch.any(out_of_limits, dim=1) return torch.any(out_of_limits, dim=1)
......
...@@ -29,6 +29,8 @@ import isaaclab.utils.math as math_utils ...@@ -29,6 +29,8 @@ import isaaclab.utils.math as math_utils
import isaaclab.utils.string as string_utils import isaaclab.utils.string as string_utils
from isaaclab.actuators import ActuatorBase, IdealPDActuatorCfg, ImplicitActuatorCfg from isaaclab.actuators import ActuatorBase, IdealPDActuatorCfg, ImplicitActuatorCfg
from isaaclab.assets import Articulation, ArticulationCfg from isaaclab.assets import Articulation, ArticulationCfg
from isaaclab.envs.mdp.terminations import joint_effort_out_of_limit
from isaaclab.managers import SceneEntityCfg
from isaaclab.sim import build_simulation_context from isaaclab.sim import build_simulation_context
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
...@@ -728,6 +730,39 @@ def test_joint_pos_limits(sim, num_articulations, device, add_ground_plane): ...@@ -728,6 +730,39 @@ def test_joint_pos_limits(sim, num_articulations, device, add_ground_plane):
assert torch.all(within_bounds) assert torch.all(within_bounds)
@pytest.mark.parametrize("num_articulations", [1, 2])
@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
@pytest.mark.parametrize("add_ground_plane", [True])
def test_joint_effort_limits(sim, num_articulations, device, add_ground_plane):
"""Validate joint effort limits via joint_effort_out_of_limit()."""
# Create articulation
articulation_cfg = generate_articulation_cfg(articulation_type="panda")
articulation, _ = generate_articulation(articulation_cfg, num_articulations, device)
# Minimal env wrapper exposing scene["robot"]
class _Env:
def __init__(self, art):
self.scene = {"robot": art}
env = _Env(articulation)
robot_all = SceneEntityCfg(name="robot")
sim.reset()
assert articulation.is_initialized
# Case A: no clipping → should NOT terminate
articulation._data.computed_torque.zero_()
articulation._data.applied_torque.zero_()
out = joint_effort_out_of_limit(env, robot_all) # [N]
assert torch.all(~out)
# Case B: simulate clipping → should terminate
articulation._data.computed_torque.fill_(100.0) # pretend controller commanded 100
articulation._data.applied_torque.fill_(50.0) # pretend actuator clipped to 50
out = joint_effort_out_of_limit(env, robot_all) # [N]
assert torch.all(out)
@pytest.mark.parametrize("num_articulations", [1, 2]) @pytest.mark.parametrize("num_articulations", [1, 2])
@pytest.mark.parametrize("device", ["cuda:0", "cpu"]) @pytest.mark.parametrize("device", ["cuda:0", "cpu"])
def test_external_force_buffer(sim, num_articulations, device): def test_external_force_buffer(sim, num_articulations, device):
......
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