Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions scripts/tools/test/test_train_and_publish_checkpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ def test_select_physics_variants_includes_franka_osc_newton_mjwarp() -> None:
assert selections == [("newtonmjwarp", "newton_mjwarp")]


def test_select_physics_variants_selects_coupled_newton_preset() -> None:
"""Coupled tasks must publish under the MJWarp backend using their proxy preset."""
variants = ["physx", "isaacsim_physx", "ovphysx", "newton_mjwarp_vbd_proxy"]

selections = _select_physics_variants("Isaac-Test", variants, "newtonmjwarp", ["newtonmjwarp"])

assert selections == [("newtonmjwarp", "newton_mjwarp_vbd_proxy")]


def test_select_physics_variants_does_not_fall_back_to_automatic_physx() -> None:
"""A task without a concrete Isaac Sim selector must not run as OvPhysX."""
selections = _select_physics_variants("Isaac-Test", ["physx", "ovphysx"], "physx", ["physx"])
Expand Down
23 changes: 21 additions & 2 deletions scripts/tools/train_and_publish_checkpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@

import isaaclab_tasks # noqa: F401
from isaaclab_tasks.utils import parse_env_cfg
from isaaclab_tasks.utils.hydra import resolve_task_config
from isaaclab_tasks.utils.preset_cli import enumerate_task_presets
from isaaclab_tasks.utils.preset_target import PresetTarget

Expand Down Expand Up @@ -263,7 +264,11 @@ def _select_physics_variants(
selector = "isaacsim_physx"
elif backend == "newtonmjwarp":
selector = next(
(candidate for candidate in ("newton_mjwarp", "newton_mjwarp_vbd") if candidate in variants),
(
candidate
for candidate in ("newton_mjwarp", "newton_mjwarp_vbd", "newton_mjwarp_vbd_proxy")
if candidate in variants
),
None,
)
if selector is None:
Expand All @@ -274,6 +279,19 @@ def _select_physics_variants(
return selections


def _resolve_physics_backend(task_name: str, physics_selector: str | None, default_backend: str | None) -> str:
"""Return the checkpoint physics token produced by a task's selected physics preset.

The token names the solver tree, so a preset selector and its published filename can
only be kept in agreement by resolving the selector.
"""
if physics_selector is None:
return default_backend
env_cfg, _ = resolve_task_config(task_name, None, overrides=(f"physics={physics_selector}",))
physics_backend, _ = get_pretrained_checkpoint_backend_names(env_cfg)
return physics_backend


def _select_render_variants(
variants: list[str],
requested_backends: list[str],
Expand Down Expand Up @@ -329,7 +347,8 @@ def _build_core_jobs(args: argparse.Namespace) -> list[CheckpointJob]:
physics_backends,
)
render_selections = _select_render_variants(render_variants, render_backends)
for physics_backend, physics_selector in physics_selections:
for _physics_family, physics_selector in physics_selections:
physics_backend = _resolve_physics_backend(task_spec.id, physics_selector, default_physics)
for render_backend, render_selector in render_selections:
jobs.append(
CheckpointJob(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Fixed
^^^^^

* Fixed pretrained checkpoint resolution for coupled tasks such as ``Isaac-Lift-Cable-Franka``,
``Isaac-Lift-Cloth-Franka``, and ``Isaac-Lift-Soft-Franka``, which raised
``Unsupported Newton solver for pretrained checkpoints: CouplerProxyCfg``. 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 the ``newtonmjwarpvbdproxy`` physics token.
Checkpoint names for uncoupled solvers are unchanged.
30 changes: 23 additions & 7 deletions source/isaaclab_rl/isaaclab_rl/utils/pretrained_checkpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ def get_pretrained_checkpoint_filename(
Args:
workflow: RL workflow name.
task_name: Registered task name.
physics_backend: Physics backend name, such as ``"physx"`` or
``"newtonmjwarp"``.
physics_backend: Physics backend name, such as ``"physx"``,
``"newtonmjwarp"``, or ``"newtonmjwarpvbdproxy"`` for a coupled solver.
render_backend: Render backend name, such as ``"rtx"``, ``"newton"``, or ``"none"``.

Returns:
Expand All @@ -99,7 +99,7 @@ def get_pretrained_checkpoint_filename(
return WORKFLOW_PRETRAINED_CHECKPOINT_FILENAMES[workflow]
if physics_backend is None or render_backend is None:
raise ValueError("physics_backend and render_backend must be provided together")
if physics_backend not in {"newtonmjwarp", "physx"}:
if not physics_backend:
raise ValueError(f"Unsupported physics backend: {physics_backend!r}")
if render_backend not in {"newton", "none", "rtx"}:
raise ValueError(f"Unsupported render backend: {render_backend!r}")
Expand Down Expand Up @@ -319,15 +319,31 @@ def _get_physics_backend_name(physics_cfg: PhysicsCfg | None) -> str:
type_path = f"{type(physics_cfg).__module__}.{type(physics_cfg).__name__}".lower()
if "newton" in type_path:
solver_cfg = getattr(physics_cfg, "solver_cfg", None)
solver_type_path = f"{type(solver_cfg).__module__}.{type(solver_cfg).__name__}".lower()
if "mjwarp" in solver_type_path:
return "newtonmjwarp"
raise ValueError(f"Unsupported Newton solver for pretrained checkpoints: {type(solver_cfg).__name__}")
solver_name = _get_newton_solver_name(solver_cfg)
if solver_name is None:
raise ValueError(f"Unsupported Newton solver for pretrained checkpoints: {type(solver_cfg).__name__}")
return f"newton{solver_name}"
if "physx" in type_path:
return "physx"
raise ValueError(f"Unable to identify physics backend from {type(physics_cfg).__name__}")


def _get_newton_solver_name(solver_cfg) -> str | None:
"""Return the checkpoint name of a Newton solver config, or ``None`` when unpublished.

A coupled solver is named by its entry solvers in order followed by its coupling
scheme, so a proxy coupler over MJWarp and VBD entries gives ``mjwarpvbdproxy``.
"""
if solver_cfg is None:
return None
class_name = type(solver_cfg).__name__
entries = getattr(solver_cfg, "entries", None)
if entries is None:
return "mjwarp" if "mjwarp" in class_name.lower() else None
families = (type(entry.solver_cfg).__name__.removesuffix("SolverCfg").lower() for entry in entries)
return "".join(families) + class_name.removeprefix("Coupler").removesuffix("Cfg").lower()


def _normalize_render_backend_name(renderer_type: str) -> str:
"""Return the normalized render backend name for a renderer type identifier."""
if renderer_type == "newton_warp":
Expand Down
Loading