Skip to content

Commit c9dca35

Browse files
authored
Name Newton coupled solvers in pretrained checkpoint paths (#7539)
# Description `--checkpoint pretrained` aborted for every coupled task with `ValueError: Unsupported Newton solver for pretrained checkpoints: CouplerProxyCfg`, because the physics token was derived only from the top-level Newton solver class and a coupled solver nests its solvers in entries. A Newton coupled solver is now named by its entry solvers in order followed by its coupling scheme, so a proxy coupler over MJWarp and VBD entries resolves to `newtonmjwarpvbdproxy`. Names for uncoupled solvers are unchanged, and this matches the names the coupled checkpoints are already published under. The physics token is no longer validated against a closed set, since it is derived from the solver tree rather than chosen from a fixed list. `train_and_publish_checkpoints.py` now derives each job's physics token by resolving its preset selector, so published filenames and the names `play` looks up cannot drift apart. Fixes 6672531 Fixes 6684411 ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Release backport - [x] <!-- backport-active-release --> Backport this pull request to the active release branch after it merges into `develop` ## Validation `uv run isaaclab play --rl_library rsl_rl --task Isaac-Lift-Cable-Franka --checkpoint pretrained --num_envs 4 --visualizer none` now fetches `Isaac-Lift-Cable-Franka_newtonmjwarpvbdproxy_none_rsl_rl.pt` and loads the policy, where it previously aborted before launch. `Isaac-Lift-Cloth-Franka` and `Isaac-Lift-Soft-Franka` resolve to the same token and are also published under it. - `uv run --extra test python -m pytest source/isaaclab_rl/test/test_pretrained_checkpoint.py scripts/tools/test/test_train_and_publish_checkpoints.py -q` — 21 passed - `uv run isaaclab -f` ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [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 - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have added a changelog fragment under `source/<pkg>/changelog.d/` for every touched package (do **not** edit `CHANGELOG.rst` or bump `extension.toml` — CI handles that) - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there
1 parent 40ada0f commit c9dca35

4 files changed

Lines changed: 62 additions & 9 deletions

File tree

scripts/tools/test/test_train_and_publish_checkpoints.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ def test_select_physics_variants_includes_franka_osc_newton_mjwarp() -> None:
8888
assert selections == [("newtonmjwarp", "newton_mjwarp")]
8989

9090

91+
def test_select_physics_variants_selects_coupled_newton_preset() -> None:
92+
"""Coupled tasks must publish under the MJWarp backend using their proxy preset."""
93+
variants = ["physx", "isaacsim_physx", "ovphysx", "newton_mjwarp_vbd_proxy"]
94+
95+
selections = _select_physics_variants("Isaac-Test", variants, "newtonmjwarp", ["newtonmjwarp"])
96+
97+
assert selections == [("newtonmjwarp", "newton_mjwarp_vbd_proxy")]
98+
99+
91100
def test_select_physics_variants_does_not_fall_back_to_automatic_physx() -> None:
92101
"""A task without a concrete Isaac Sim selector must not run as OvPhysX."""
93102
selections = _select_physics_variants("Isaac-Test", ["physx", "ovphysx"], "physx", ["physx"])

scripts/tools/train_and_publish_checkpoints.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696

9797
import isaaclab_tasks # noqa: F401
9898
from isaaclab_tasks.utils import parse_env_cfg
99+
from isaaclab_tasks.utils.hydra import resolve_task_config
99100
from isaaclab_tasks.utils.preset_cli import enumerate_task_presets
100101
from isaaclab_tasks.utils.preset_target import PresetTarget
101102

@@ -263,7 +264,11 @@ def _select_physics_variants(
263264
selector = "isaacsim_physx"
264265
elif backend == "newtonmjwarp":
265266
selector = next(
266-
(candidate for candidate in ("newton_mjwarp", "newton_mjwarp_vbd") if candidate in variants),
267+
(
268+
candidate
269+
for candidate in ("newton_mjwarp", "newton_mjwarp_vbd", "newton_mjwarp_vbd_proxy")
270+
if candidate in variants
271+
),
267272
None,
268273
)
269274
if selector is None:
@@ -274,6 +279,19 @@ def _select_physics_variants(
274279
return selections
275280

276281

282+
def _resolve_physics_backend(task_name: str, physics_selector: str | None, default_backend: str | None) -> str:
283+
"""Return the checkpoint physics token produced by a task's selected physics preset.
284+
285+
The token names the solver tree, so a preset selector and its published filename can
286+
only be kept in agreement by resolving the selector.
287+
"""
288+
if physics_selector is None:
289+
return default_backend
290+
env_cfg, _ = resolve_task_config(task_name, None, overrides=(f"physics={physics_selector}",))
291+
physics_backend, _ = get_pretrained_checkpoint_backend_names(env_cfg)
292+
return physics_backend
293+
294+
277295
def _select_render_variants(
278296
variants: list[str],
279297
requested_backends: list[str],
@@ -329,7 +347,8 @@ def _build_core_jobs(args: argparse.Namespace) -> list[CheckpointJob]:
329347
physics_backends,
330348
)
331349
render_selections = _select_render_variants(render_variants, render_backends)
332-
for physics_backend, physics_selector in physics_selections:
350+
for _physics_family, physics_selector in physics_selections:
351+
physics_backend = _resolve_physics_backend(task_spec.id, physics_selector, default_physics)
333352
for render_backend, render_selector in render_selections:
334353
jobs.append(
335354
CheckpointJob(
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed pretrained checkpoint resolution for coupled tasks such as ``Isaac-Lift-Cable-Franka``,
5+
``Isaac-Lift-Cloth-Franka``, and ``Isaac-Lift-Soft-Franka``, which raised
6+
``Unsupported Newton solver for pretrained checkpoints: CouplerProxyCfg``. A Newton coupled
7+
solver is now named by its entry solvers in order followed by its coupling scheme, so a proxy
8+
coupler over MJWarp and VBD entries resolves to the ``newtonmjwarpvbdproxy`` physics token.
9+
Checkpoint names for uncoupled solvers are unchanged.

source/isaaclab_rl/isaaclab_rl/utils/pretrained_checkpoint.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ def get_pretrained_checkpoint_filename(
8383
Args:
8484
workflow: RL workflow name.
8585
task_name: Registered task name.
86-
physics_backend: Physics backend name, such as ``"physx"`` or
87-
``"newtonmjwarp"``.
86+
physics_backend: Physics backend name, such as ``"physx"``,
87+
``"newtonmjwarp"``, or ``"newtonmjwarpvbdproxy"`` for a coupled solver.
8888
render_backend: Render backend name, such as ``"rtx"``, ``"newton"``, or ``"none"``.
8989
9090
Returns:
@@ -99,7 +99,7 @@ def get_pretrained_checkpoint_filename(
9999
return WORKFLOW_PRETRAINED_CHECKPOINT_FILENAMES[workflow]
100100
if physics_backend is None or render_backend is None:
101101
raise ValueError("physics_backend and render_backend must be provided together")
102-
if physics_backend not in {"newtonmjwarp", "physx"}:
102+
if not physics_backend:
103103
raise ValueError(f"Unsupported physics backend: {physics_backend!r}")
104104
if render_backend not in {"newton", "none", "rtx"}:
105105
raise ValueError(f"Unsupported render backend: {render_backend!r}")
@@ -319,15 +319,31 @@ def _get_physics_backend_name(physics_cfg: PhysicsCfg | None) -> str:
319319
type_path = f"{type(physics_cfg).__module__}.{type(physics_cfg).__name__}".lower()
320320
if "newton" in type_path:
321321
solver_cfg = getattr(physics_cfg, "solver_cfg", None)
322-
solver_type_path = f"{type(solver_cfg).__module__}.{type(solver_cfg).__name__}".lower()
323-
if "mjwarp" in solver_type_path:
324-
return "newtonmjwarp"
325-
raise ValueError(f"Unsupported Newton solver for pretrained checkpoints: {type(solver_cfg).__name__}")
322+
solver_name = _get_newton_solver_name(solver_cfg)
323+
if solver_name is None:
324+
raise ValueError(f"Unsupported Newton solver for pretrained checkpoints: {type(solver_cfg).__name__}")
325+
return f"newton{solver_name}"
326326
if "physx" in type_path:
327327
return "physx"
328328
raise ValueError(f"Unable to identify physics backend from {type(physics_cfg).__name__}")
329329

330330

331+
def _get_newton_solver_name(solver_cfg) -> str | None:
332+
"""Return the checkpoint name of a Newton solver config, or ``None`` when unpublished.
333+
334+
A coupled solver is named by its entry solvers in order followed by its coupling
335+
scheme, so a proxy coupler over MJWarp and VBD entries gives ``mjwarpvbdproxy``.
336+
"""
337+
if solver_cfg is None:
338+
return None
339+
class_name = type(solver_cfg).__name__
340+
entries = getattr(solver_cfg, "entries", None)
341+
if entries is None:
342+
return "mjwarp" if "mjwarp" in class_name.lower() else None
343+
families = (type(entry.solver_cfg).__name__.removesuffix("SolverCfg").lower() for entry in entries)
344+
return "".join(families) + class_name.removeprefix("Coupler").removesuffix("Cfg").lower()
345+
346+
331347
def _normalize_render_backend_name(renderer_type: str) -> str:
332348
"""Return the normalized render backend name for a renderer type identifier."""
333349
if renderer_type == "newton_warp":

0 commit comments

Comments
 (0)