Skip to content

Commit d2304a7

Browse files
authored
[PresetCLI] Make physics=/renderer=/presets= work on the unified train/play scripts (#5715)
1 parent 6fb5335 commit d2304a7

9 files changed

Lines changed: 165 additions & 20 deletions

File tree

scripts/reinforcement_learning/rl_games/play_rl_games.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,14 @@
2727
from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint
2828

2929
import isaaclab_tasks # noqa: F401
30-
from isaaclab_tasks.utils import add_launcher_args, get_checkpoint_path, launch_simulation, resolve_task_config
30+
from isaaclab_tasks.utils import (
31+
add_launcher_args,
32+
fold_preset_tokens,
33+
get_checkpoint_path,
34+
launch_simulation,
35+
resolve_task_config,
36+
setup_preset_cli,
37+
)
3138

3239
# PLACEHOLDER: Extension template (do not remove this comment)
3340
with contextlib.suppress(ImportError):
@@ -59,12 +66,12 @@
5966
)
6067
parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.")
6168
add_launcher_args(parser)
62-
args_cli, hydra_args = parser.parse_known_args()
69+
args_cli, hydra_args = setup_preset_cli(parser)
6370

6471
if args_cli.video:
6572
args_cli.enable_cameras = True
6673

67-
sys.argv = [sys.argv[0]] + hydra_args
74+
sys.argv = [sys.argv[0]] + fold_preset_tokens(hydra_args)
6875

6976

7077
def main():

scripts/reinforcement_learning/rl_games/train_rl_games.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,14 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
6060
const=True,
6161
help="if toggled, this experiment will be tracked with Weights and Biases",
6262
)
63+
from isaaclab_tasks.utils import fold_preset_tokens, setup_preset_cli
64+
6365
add_isaaclab_launcher_args(parser)
64-
args_cli, hydra_args = parser.parse_known_args(argv)
66+
# setup_preset_cli registers preset-selection help text + runs parse_known_args;
67+
# fold_preset_tokens rewrites typed selectors (physics=, renderer=, presets=) post-argparse.
68+
args_cli, hydra_args = setup_preset_cli(parser, argv)
6569
enable_cameras_for_video(args_cli)
66-
set_hydra_args(hydra_args)
70+
set_hydra_args(fold_preset_tokens(hydra_args))
6771
return args_cli
6872

6973

scripts/reinforcement_learning/rsl_rl/play_rsl_rl.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@
3232
from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint
3333

3434
import isaaclab_tasks # noqa: F401
35-
from isaaclab_tasks.utils import add_launcher_args, get_checkpoint_path, launch_simulation
35+
from isaaclab_tasks.utils import (
36+
add_launcher_args,
37+
fold_preset_tokens,
38+
get_checkpoint_path,
39+
launch_simulation,
40+
setup_preset_cli,
41+
)
3642
from isaaclab_tasks.utils.hydra import hydra_task_config
3743

3844
# local imports
@@ -64,7 +70,7 @@
6470
parser.add_argument("--external_callback", default=None, help="Fully qualified path to an externally defined callback.")
6571
cli_args.add_rsl_rl_args(parser)
6672
add_launcher_args(parser)
67-
args_cli, remaining_args = parser.parse_known_args()
73+
args_cli, remaining_args = setup_preset_cli(parser)
6874

6975
if args_cli.video:
7076
args_cli.enable_cameras = True
@@ -81,7 +87,7 @@
8187
# The remaining arguments are the arguments that were not consumed by both this scripts
8288
# argparser and (optionally) the external callback function.
8389
remaining_args = list_intersection(remaining_args, remaining_args_env_registration)
84-
sys.argv = [sys.argv[0]] + remaining_args
90+
sys.argv = [sys.argv[0]] + fold_preset_tokens(remaining_args)
8591

8692
# Check for installed RSL-RL version
8793
installed_version = metadata.version("rsl-rl-lib")

scripts/reinforcement_learning/rsl_rl/train_rsl_rl.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
6666
"""Parse RSL-RL training arguments."""
6767
from isaaclab.utils.string import list_intersection, string_to_callable
6868

69+
from isaaclab_tasks.utils import fold_preset_tokens, setup_preset_cli
70+
6971
parser = argparse.ArgumentParser(description="Train an RL agent with RSL-RL.")
7072
add_common_train_args(
7173
parser,
@@ -79,15 +81,17 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
7981
)
8082
CLI_ARGS.add_rsl_rl_args(parser)
8183
add_isaaclab_launcher_args(parser)
82-
args_cli, remaining_args = parser.parse_known_args(argv)
84+
# setup_preset_cli registers preset-selection help text + runs parse_known_args
85+
args_cli, remaining_args = setup_preset_cli(parser, argv)
8386
enable_cameras_for_video(args_cli)
8487

8588
remaining_args_env_registration = None
8689
if args_cli.external_callback:
8790
external_callback_function = string_to_callable(args_cli.external_callback, separator=".")
8891
remaining_args_env_registration = external_callback_function()
8992

90-
set_hydra_args(list_intersection(remaining_args, remaining_args_env_registration))
93+
# fold_preset_tokens rewrites typed selectors (physics=, renderer=, presets=) post-argparse
94+
set_hydra_args(fold_preset_tokens(list_intersection(remaining_args, remaining_args_env_registration)))
9195
return args_cli
9296

9397

scripts/reinforcement_learning/sb3/play_sb3.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@
2525
from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint
2626

2727
import isaaclab_tasks # noqa: F401
28-
from isaaclab_tasks.utils import add_launcher_args, get_checkpoint_path, launch_simulation, resolve_task_config
28+
from isaaclab_tasks.utils import (
29+
add_launcher_args,
30+
fold_preset_tokens,
31+
get_checkpoint_path,
32+
launch_simulation,
33+
resolve_task_config,
34+
setup_preset_cli,
35+
)
2936

3037
# PLACEHOLDER: Extension template (do not remove this comment)
3138
with contextlib.suppress(ImportError):
@@ -63,12 +70,12 @@
6370
help="Use a slower SB3 wrapper but keep all the extra training info.",
6471
)
6572
add_launcher_args(parser)
66-
args_cli, hydra_args = parser.parse_known_args()
73+
args_cli, hydra_args = setup_preset_cli(parser)
6774

6875
if args_cli.video:
6976
args_cli.enable_cameras = True
7077

71-
sys.argv = [sys.argv[0]] + hydra_args
78+
sys.argv = [sys.argv[0]] + fold_preset_tokens(hydra_args)
7279

7380

7481
def main():

scripts/reinforcement_learning/sb3/train_sb3.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,14 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
6767
default=False,
6868
help="Use a slower SB3 wrapper but keep all the extra training info.",
6969
)
70+
from isaaclab_tasks.utils import fold_preset_tokens, setup_preset_cli
71+
7072
add_isaaclab_launcher_args(parser)
71-
args_cli, hydra_args = parser.parse_known_args(argv)
73+
# setup_preset_cli registers preset-selection help text + runs parse_known_args;
74+
# fold_preset_tokens rewrites typed selectors (physics=, renderer=, presets=) post-argparse.
75+
args_cli, hydra_args = setup_preset_cli(parser, argv)
7276
enable_cameras_for_video(args_cli)
73-
set_hydra_args(hydra_args)
77+
set_hydra_args(fold_preset_tokens(hydra_args))
7478
return args_cli
7579

7680

scripts/reinforcement_learning/skrl/play_skrl.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@
2828
from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint
2929

3030
import isaaclab_tasks # noqa: F401
31-
from isaaclab_tasks.utils import add_launcher_args, get_checkpoint_path, launch_simulation, resolve_task_config
31+
from isaaclab_tasks.utils import (
32+
add_launcher_args,
33+
fold_preset_tokens,
34+
get_checkpoint_path,
35+
launch_simulation,
36+
resolve_task_config,
37+
setup_preset_cli,
38+
)
3239

3340
# PLACEHOLDER: Extension template (do not remove this comment)
3441
with contextlib.suppress(ImportError):
@@ -77,12 +84,12 @@
7784
)
7885
parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.")
7986
add_launcher_args(parser)
80-
args_cli, hydra_args = parser.parse_known_args()
87+
args_cli, hydra_args = setup_preset_cli(parser)
8188

8289
if args_cli.video:
8390
args_cli.enable_cameras = True
8491

85-
sys.argv = [sys.argv[0]] + hydra_args
92+
sys.argv = [sys.argv[0]] + fold_preset_tokens(hydra_args)
8693

8794
# -- check skrl version ------------------------------------------------------
8895
if version.parse(skrl.__version__) < version.parse(SKRL_VERSION):

scripts/reinforcement_learning/skrl/train_skrl.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,14 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
6666
choices=["AMP", "PPO", "IPPO", "MAPPO"],
6767
help="The RL algorithm used for training the skrl agent.",
6868
)
69+
from isaaclab_tasks.utils import fold_preset_tokens, setup_preset_cli
70+
6971
add_isaaclab_launcher_args(parser)
70-
args_cli, hydra_args = parser.parse_known_args(argv)
72+
# setup_preset_cli registers preset-selection help text + runs parse_known_args;
73+
# fold_preset_tokens rewrites typed selectors (physics=, renderer=, presets=) post-argparse.
74+
args_cli, hydra_args = setup_preset_cli(parser, argv)
7175
enable_cameras_for_video(args_cli)
72-
set_hydra_args(hydra_args)
76+
set_hydra_args(fold_preset_tokens(hydra_args))
7377
return args_cli
7478

7579

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
"""End-to-end test that typed preset selectors reach the resolver from each
7+
unified train/play entrypoint.
8+
9+
The four supported RL libraries (rl_games, rsl_rl, sb3, skrl) each have a
10+
``train_<library>.py`` / ``play_<library>.py`` script that the unified
11+
``scripts/reinforcement_learning/{train,play}.py`` dispatchers route to via
12+
``--rl_library``. Each entrypoint must wire the typed preset CLI
13+
(``setup_preset_cli`` + ``fold_preset_tokens`` from
14+
:mod:`isaaclab_tasks.utils.preset_cli`) so that user-typed ``physics=NAME``
15+
/ ``renderer=NAME`` / ``presets=NAME`` tokens are folded into the canonical
16+
form Hydra consumes. Without the fold, those tokens hit Hydra as a struct
17+
override against a non-existent top-level key and raise
18+
``Key 'physics' is not in struct`` -- the original symptom of the #5715
19+
regression.
20+
21+
This test invokes each entrypoint via the unified dispatcher with
22+
``physics=does_not_exist`` and asserts:
23+
24+
* the Hydra struct error is **absent** (would indicate the fold did not
25+
run), and
26+
* the resolver's own ``Unknown preset(s)`` error is **present** (the
27+
fold ran and the resolver received the canonical token).
28+
29+
Resolve fails before any Kit/sim launch, so each subprocess exits in a
30+
few seconds without needing GPU.
31+
32+
``rlinf`` is intentionally excluded: those scripts manage their own
33+
``GlobalHydra`` instance and have never been integrated with the typed
34+
preset CLI.
35+
"""
36+
37+
from __future__ import annotations
38+
39+
import os
40+
import subprocess
41+
import sys
42+
from pathlib import Path
43+
44+
import pytest
45+
46+
REPO_ROOT = Path(__file__).resolve().parents[3]
47+
48+
# Each (action, library) pair runs through the unified dispatcher at
49+
# ``scripts/reinforcement_learning/{action}.py``. Dispatching matches how
50+
# ``./isaaclab.sh train`` / ``./isaaclab.sh play`` invoke these in practice.
51+
_ENTRYPOINT_CASES = [
52+
(action, library) for action in ("train", "play") for library in ("rl_games", "rsl_rl", "sb3", "skrl")
53+
]
54+
55+
56+
@pytest.mark.parametrize("action,library", _ENTRYPOINT_CASES)
57+
def test_typed_preset_reaches_resolver(action: str, library: str) -> None:
58+
"""``physics=<unknown>`` must reach the resolver, not crash Hydra's struct check.
59+
60+
Confirms that the dispatched entrypoint wired ``setup_preset_cli`` +
61+
``fold_preset_tokens`` correctly: the typed selector got rewritten into
62+
the canonical ``presets=<csv>`` form before Hydra received it, and the
63+
resolver then surfaced its own ``Unknown preset(s)`` error against the
64+
deliberately invalid name.
65+
"""
66+
dispatcher = REPO_ROOT / "scripts" / "reinforcement_learning" / f"{action}.py"
67+
assert dispatcher.exists(), f"missing dispatcher: {dispatcher}"
68+
69+
cmd = [
70+
sys.executable,
71+
str(dispatcher),
72+
"--rl_library",
73+
library,
74+
"--task=Isaac-Ant-v0",
75+
"physics=does_not_exist",
76+
"--headless",
77+
]
78+
result = subprocess.run(
79+
cmd,
80+
cwd=REPO_ROOT,
81+
capture_output=True,
82+
text=True,
83+
timeout=120,
84+
check=False,
85+
env=os.environ.copy(),
86+
)
87+
88+
combined = result.stdout + result.stderr
89+
label = f"{action}.py --rl_library {library}"
90+
assert "is not in struct" not in combined, (
91+
f"{label}: Hydra's struct-override error reached the user, meaning the typed preset "
92+
f"selector was NOT folded before Hydra processed it. The entrypoint must call "
93+
f"setup_preset_cli + fold_preset_tokens before set_hydra_args / sys.argv assignment.\n"
94+
f"--- stderr tail ---\n{result.stderr[-2000:]}\n"
95+
f"--- stdout tail ---\n{result.stdout[-2000:]}\n"
96+
)
97+
assert "Unknown preset(s): does_not_exist" in combined, (
98+
f"{label}: resolver's 'Unknown preset(s)' error did not appear. Either the fold did not "
99+
f"reach the resolver, or the script exited earlier with a different error.\n"
100+
f"--- stderr tail ---\n{result.stderr[-2000:]}\n"
101+
f"--- stdout tail ---\n{result.stdout[-2000:]}\n"
102+
)

0 commit comments

Comments
 (0)