Skip to content

Commit 392e7d9

Browse files
committed
Add RSL-RL training and torch metrics to handover and camera Direct
Add the RSL-RL runner configuration for the Shadow handover Direct task and success-rate metrics on the torch-first path, fix handover construction on Newton (renamed distal joints), and land the camera Direct renderer presets with configuration validation. RSL-RL observations now read from the public environment-owned obs_buf on all Direct env bases (reset stores the buffer like step), replacing the adapter-side private hook.
1 parent 6e8a63e commit 392e7d9

28 files changed

Lines changed: 751 additions & 204 deletions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed :meth:`~isaaclab.envs.DirectRLEnv.reset` to store the observation buffer
5+
like :meth:`~isaaclab.envs.DirectRLEnv.step` already does, and exposed the
6+
latest observations on the multi-agent-to-single-agent adapter through the
7+
same public buffer.
8+
* Fixed kit-less installation to select one OpenUSD provider per architecture,
9+
preventing mixed ``pxr`` ABIs during Newton training on x86.

source/isaaclab/isaaclab/envs/direct_rl_env.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ def reset(self, seed: int | None = None, options: dict[str, Any] | None = None)
382382
self.sim.render()
383383

384384
# return observations
385-
return self._get_observations(), self.extras
385+
# store the buffer like step() does, so consumers can read the latest observations
386+
self.obs_buf = self._get_observations()
387+
return self.obs_buf, self.extras
386388

387389
def step(self, action: torch.Tensor) -> VecEnvStepReturn:
388390
"""Execute one time-step of the environment's dynamics.

source/isaaclab/isaaclab/envs/utils/marl.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,34 @@ def __init__(self, env: DirectMARLEnv) -> None:
8181
)
8282
self.action_space = gym.vector.utils.batch_space(self.single_action_space, self.num_envs)
8383

84-
def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[VecEnvObs, dict]:
85-
obs, extras = self.env.reset(seed, options)
84+
@property
85+
def episode_length_buf(self) -> torch.Tensor:
86+
"""Episode lengths from the wrapped multi-agent environment."""
87+
return self.env.episode_length_buf
8688

87-
# use environment state as observation
88-
if self._state_as_observation:
89-
obs = {"policy": self.env.state()}
90-
# concatenate agents' observations
89+
@episode_length_buf.setter
90+
def episode_length_buf(self, value: torch.Tensor) -> None:
91+
self.env.episode_length_buf = value
92+
93+
@property
94+
def obs_buf(self) -> VecEnvObs:
95+
"""Latest observations from the wrapped multi-agent environment."""
96+
return self._convert_observations(self.env.obs_dict)
97+
98+
def _convert_observations(self, obs: dict[AgentID, ObsType]) -> VecEnvObs:
99+
"""Convert multi-agent observations to the single-agent policy observation."""
91100
# FIXME: This implementation assumes the spaces are fundamental ones. Fix it to support composite spaces
92-
else:
93-
obs = {
94-
"policy": torch.cat(
95-
[obs[agent].reshape(self.num_envs, -1) for agent in self.env.possible_agents], dim=-1
96-
)
97-
}
101+
if self._state_as_observation:
102+
return {"policy": self.env.state()}
103+
return {
104+
"policy": torch.cat(
105+
[obs[agent].reshape(self.num_envs, -1) for agent in self.env.possible_agents], dim=-1
106+
)
107+
}
98108

99-
return obs, extras
109+
def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[VecEnvObs, dict]:
110+
obs, extras = self.env.reset(seed, options)
111+
return self._convert_observations(obs), extras
100112

101113
def step(self, action: torch.Tensor) -> VecEnvStepReturn:
102114
# split single-agent actions to build the multi-agent ones
@@ -111,17 +123,7 @@ def step(self, action: torch.Tensor) -> VecEnvStepReturn:
111123
# step the environment
112124
obs, rewards, terminated, time_outs, extras = self.env.step(_actions)
113125

114-
# use environment state as observation
115-
if self._state_as_observation:
116-
obs = {"policy": self.env.state()}
117-
# concatenate agents' observations
118-
# FIXME: This implementation assumes the spaces are fundamental ones. Fix it to support composite spaces
119-
else:
120-
obs = {
121-
"policy": torch.cat(
122-
[obs[agent].reshape(self.num_envs, -1) for agent in self.env.possible_agents], dim=-1
123-
)
124-
}
126+
obs = self._convert_observations(obs)
125127

126128
# process environment outputs to return single-agent data
127129
rewards = sum(rewards.values())
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
from types import SimpleNamespace
7+
8+
import gymnasium as gym
9+
import torch
10+
11+
from isaaclab.envs.utils.marl import multi_agent_to_single_agent
12+
13+
14+
class _FakeMultiAgentEnv:
15+
possible_agents = ["agent_0", "agent_1"]
16+
observation_spaces = {
17+
"agent_0": gym.spaces.Box(low=-1.0, high=1.0, shape=(2,)),
18+
"agent_1": gym.spaces.Box(low=-1.0, high=1.0, shape=(1,)),
19+
}
20+
action_spaces = {
21+
"agent_0": gym.spaces.Box(low=-1.0, high=1.0, shape=(1,)),
22+
"agent_1": gym.spaces.Box(low=-1.0, high=1.0, shape=(1,)),
23+
}
24+
render_mode = None
25+
26+
def __init__(self):
27+
self.unwrapped = self
28+
self.cfg = SimpleNamespace(state_space=2)
29+
self.state_space = gym.spaces.Box(low=-1.0, high=1.0, shape=(2,))
30+
self.sim = object()
31+
self.scene = SimpleNamespace(num_envs=2)
32+
self.episode_length_buf = torch.tensor([1, 2])
33+
self.obs_dict = {
34+
"agent_0": torch.tensor([[1.0, 2.0], [3.0, 4.0]]),
35+
"agent_1": torch.tensor([[5.0], [6.0]]),
36+
}
37+
38+
def reset(self, seed=None, options=None):
39+
return self.obs_dict, {}
40+
41+
def state(self):
42+
return torch.tensor([[7.0, 8.0], [9.0, 10.0]])
43+
44+
def close(self):
45+
pass
46+
47+
48+
def test_multi_agent_to_single_agent_reset_concatenates_agents():
49+
"""The adapter reset should concatenate the agents' observations."""
50+
env = multi_agent_to_single_agent(_FakeMultiAgentEnv())
51+
52+
observations, _ = env.reset()
53+
54+
torch.testing.assert_close(observations["policy"], torch.tensor([[1.0, 2.0, 5.0], [3.0, 4.0, 6.0]]))
55+
56+
57+
def test_multi_agent_to_single_agent_reset_can_use_state():
58+
"""The adapter reset should support the state-as-observation mode."""
59+
env = multi_agent_to_single_agent(_FakeMultiAgentEnv(), state_as_observation=True)
60+
61+
observations, _ = env.reset()
62+
63+
torch.testing.assert_close(observations["policy"], torch.tensor([[7.0, 8.0], [9.0, 10.0]]))
64+
65+
66+
def test_multi_agent_to_single_agent_forwards_episode_lengths():
67+
"""RSL-RL episode randomization should update the wrapped environment buffer."""
68+
source_env = _FakeMultiAgentEnv()
69+
env = multi_agent_to_single_agent(source_env)
70+
episode_lengths = torch.tensor([3, 4])
71+
72+
env.episode_length_buf = episode_lengths
73+
74+
assert env.episode_length_buf is episode_lengths
75+
assert source_env.episode_length_buf is episode_lengths
76+
77+
78+
def test_multi_agent_to_single_agent_exposes_latest_observations():
79+
"""The public observation buffer should reflect the wrapped environment's buffer."""
80+
env = multi_agent_to_single_agent(_FakeMultiAgentEnv())
81+
82+
torch.testing.assert_close(env.obs_buf["policy"], torch.tensor([[1.0, 2.0, 5.0], [3.0, 4.0, 6.0]]))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed :meth:`~isaaclab_experimental.envs.DirectRLEnvWarp.step` and
5+
:meth:`~isaaclab_experimental.envs.DirectRLEnvWarp.reset` to store the returned
6+
observation dictionary in ``obs_buf``, which
7+
:meth:`~isaaclab_rl.rsl_rl.RslRlVecEnvWrapper.get_observations` now reads.

source/isaaclab_experimental/isaaclab_experimental/envs/direct_rl_env_warp.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,9 @@ def reset(self, seed: int | None = None, options: dict[str, Any] | None = None)
377377

378378
# return observations
379379
self._get_observations()
380-
return {"policy": self.torch_obs_buf.clone()}, self.extras
380+
# store the returned buffer so RslRlVecEnvWrapper.get_observations() can read env.obs_buf
381+
self.obs_buf = {"policy": self.torch_obs_buf.clone()}
382+
return self.obs_buf, self.extras
381383

382384
@Timer(name="env_step", msg="Step took:", enable=DEBUG_TIMER_STEP or DEBUG_TIMERS)
383385
def step(self, action: torch.Tensor) -> VecEnvStepReturn:
@@ -460,8 +462,10 @@ def step(self, action: torch.Tensor) -> VecEnvStepReturn:
460462
self._post_step_visualize()
461463

462464
# return observations, rewards, resets and extras
465+
# store the returned buffer so RslRlVecEnvWrapper.get_observations() can read env.obs_buf
466+
self.obs_buf = {"policy": self.torch_obs_buf.clone()}
463467
return (
464-
{"policy": self.torch_obs_buf.clone()},
468+
self.obs_buf,
465469
self.torch_reward_buf,
466470
self.torch_reset_terminated,
467471
self.torch_reset_time_outs,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Changed
2+
^^^^^^^
3+
4+
* Changed :meth:`~isaaclab_rl.rsl_rl.RslRlVecEnvWrapper.get_observations` to read
5+
the environment-owned observation buffer instead of calling private environment
6+
methods. The returned observations now match the latest reset/step returns,
7+
including observation-noise corruption that the private path skipped, and
8+
multi-agent environments converted with
9+
:func:`~isaaclab.envs.utils.multi_agent_to_single_agent` train with RSL-RL
10+
without environment-side accommodations.

source/isaaclab_rl/isaaclab_rl/rsl_rl/vecenv_wrapper.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,7 @@ def reset(self) -> tuple[TensorDict, dict]: # noqa: D102
171171

172172
def get_observations(self) -> TensorDict:
173173
"""Returns the current observations of the environment."""
174-
if hasattr(self.unwrapped, "observation_manager"):
175-
obs_dict = self.unwrapped.observation_manager.compute()
176-
else:
177-
obs_dict = self.unwrapped._get_observations()
178-
return TensorDict(obs_dict, batch_size=[self.num_envs])
174+
return TensorDict(self.unwrapped.obs_buf, batch_size=[self.num_envs])
179175

180176
def step(self, actions: torch.Tensor) -> tuple[TensorDict, torch.Tensor, torch.Tensor, dict]:
181177
# clip actions
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Added
2+
^^^^^
3+
4+
* Added an RSL-RL training configuration and behavioral-success metrics to the
5+
Shadow handover Direct task.
6+
* Added renderer presets and configuration validation to the Shadow camera
7+
Direct task.
8+
9+
Changed
10+
^^^^^^^
11+
12+
* Changed the camera Direct feature-extractor keypoints helper to delegate to
13+
the shared :func:`~isaaclab_tasks.core.reorient.mdp.observations.compute_cube_keypoints`;
14+
the old ``compute_keypoints`` name is deprecated and warns.
15+
16+
Fixed
17+
^^^^^
18+
19+
* Fixed handover construction on Newton, broken by renamed distal joints in
20+
the current Shadow Newton asset.

source/isaaclab_tasks/isaaclab_tasks/core/handover/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
disable_env_checker=True,
2222
kwargs={
2323
"env_cfg_entry_point": f"{__name__}.handover_env_cfg:HandoverEnvCfg",
24+
"rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:HandoverPPORunnerCfg",
2425
"rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml",
2526
"skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml",
2627
"skrl_ippo_cfg_entry_point": f"{agents.__name__}:skrl_ippo_cfg.yaml",

0 commit comments

Comments
 (0)