From 185a370d72df7ca2f2577fbee7916d3f1944417a Mon Sep 17 00:00:00 2001 From: Ruben Grandia Date: Wed, 12 Aug 2026 16:51:29 +0200 Subject: [PATCH 1/3] Make KaminoSolverCfgBase public --- .../isaaclab_contrib/coupling/coupler.py | 7 +++---- .../isaaclab_newton/physics/__init__.pyi | 2 ++ .../isaaclab_newton/physics/kamino_manager.py | 10 +++++----- .../isaaclab_newton/physics/kamino_manager_cfg.py | 6 +++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py b/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py index d1b9aa581692..dc380899d81f 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py +++ b/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py @@ -12,8 +12,7 @@ import warp as wp from isaaclab_newton.physics import ( - KaminoDVISolverCfg, - KaminoPADMMSolverCfg, + KaminoSolverCfgBase, MJWarpSolverCfg, MPMSolverCfg, NewtonCollisionPipelineCfg, @@ -64,7 +63,7 @@ def _requires_external_contacts(solver_cfg: NewtonSolverCfg) -> bool: """ if isinstance(solver_cfg, MJWarpSolverCfg): return not solver_cfg.use_mujoco_contacts - if isinstance(solver_cfg, (KaminoPADMMSolverCfg, KaminoDVISolverCfg)): + if isinstance(solver_cfg, KaminoSolverCfgBase): return not solver_cfg.use_collision_detector if isinstance(solver_cfg, MPMSolverCfg): return False @@ -153,7 +152,7 @@ def _validate_config(cls, solver_cfg: CouplerCfg) -> None: f"CouplerEntryCfg {entry.name!r} uses {type(nested_cfg).__name__}, whose manager " "does not implement nested solver construction." ) - if isinstance(nested_cfg, (KaminoPADMMSolverCfg, KaminoDVISolverCfg)): + if isinstance(nested_cfg, KaminoSolverCfgBase): raise NotImplementedError( f"CouplerEntryCfg {entry.name!r} uses a Kamino solver config, whose manager-specific FK/reset " "lifecycle cannot yet be preserved inside Newton's coupled-solver entry API." diff --git a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi index 3e15ec5b1c54..d5e49148b4ea 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi +++ b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi @@ -15,6 +15,7 @@ __all__ = [ "KaminoMaterialsCfg", "KaminoPADMMCfg", "KaminoPADMMSolverCfg", + "KaminoSolverCfgBase", "MPMSolverCfg", "MJWarpSolverCfg", "NewtonCfg", @@ -43,6 +44,7 @@ from .kamino_manager_cfg import ( KaminoMaterialsCfg, KaminoPADMMCfg, KaminoPADMMSolverCfg, + KaminoSolverCfgBase, ) from .mjwarp_manager import NewtonMJWarpManager from .mjwarp_manager_cfg import MJWarpSolverCfg diff --git a/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager.py b/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager.py index 517cf93dbf00..400dae7b14ea 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager.py @@ -15,7 +15,7 @@ from isaaclab.physics import PhysicsManager -from .kamino_manager_cfg import _KaminoSolverCfgBase +from .kamino_manager_cfg import KaminoSolverCfgBase from .newton_manager import NewtonManager logger = logging.getLogger(__name__) @@ -58,12 +58,12 @@ class NewtonKaminoManager(NewtonManager): _solver: SolverKamino @classmethod - def _get_kamino_solver_cfg(cls) -> _KaminoSolverCfgBase: + def _get_kamino_solver_cfg(cls) -> KaminoSolverCfgBase: cfg = PhysicsManager._cfg if cfg is None: raise RuntimeError("Physics manager is not initialized.") solver_cfg = getattr(cfg, "solver_cfg", None) - if not isinstance(solver_cfg, _KaminoSolverCfgBase): + if not isinstance(solver_cfg, KaminoSolverCfgBase): raise TypeError(f"Expected a Kamino solver configuration, got {type(solver_cfg).__name__}.") return solver_cfg @@ -112,12 +112,12 @@ def _reset_solver_internals(cls, world_mask: wp.array | None) -> None: """ @classmethod - def _create_solver(cls, model: Model, solver_cfg: _KaminoSolverCfgBase) -> SolverKamino: + def _create_solver(cls, model: Model, solver_cfg: KaminoSolverCfgBase) -> SolverKamino: """Construct the configured Kamino solver.""" return SolverKamino(model, solver_cfg.to_solver_config()) @classmethod - def _build_solver(cls, model: Model, solver_cfg: _KaminoSolverCfgBase) -> None: + def _build_solver(cls, model: Model, solver_cfg: KaminoSolverCfgBase) -> None: """Construct :class:`SolverKamino` and populate the base-class slots. Sets :attr:`NewtonManager._needs_collision_pipeline` to ``True`` only diff --git a/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py b/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py index 4901bf747378..d640d4c3e5b1 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py @@ -223,7 +223,7 @@ class KaminoMaterialsCfg: @configclass -class _KaminoSolverCfgBase(NewtonSolverCfg): +class KaminoSolverCfgBase(NewtonSolverCfg): """Common configuration for Kamino solver-related parameters. Kamino simulates constrained rigid multi-body systems in maximal coordinates with @@ -393,7 +393,7 @@ def to_solver_config(self) -> SolverKamino.Config: @configclass -class KaminoPADMMSolverCfg(_KaminoSolverCfgBase): +class KaminoPADMMSolverCfg(KaminoSolverCfgBase): """Configuration for Kamino with the P-ADMM forward-dynamics solver.""" dynamics_solver_cfg: KaminoPADMMCfg = field(default_factory=KaminoPADMMCfg) @@ -405,7 +405,7 @@ def _get_dynamics_solver_config(self) -> tuple[Literal["padmm"], dict[str, Any]] @configclass -class KaminoDVISolverCfg(_KaminoSolverCfgBase): +class KaminoDVISolverCfg(KaminoSolverCfgBase): """Configuration for Kamino with the DVI forward-dynamics solver.""" dynamics_solver_cfg: KaminoDVICfg = field(default_factory=KaminoDVICfg) From 4b474197da1b13edc066212d9cfc61850e8fdef7 Mon Sep 17 00:00:00 2001 From: Ruben Grandia Date: Thu, 13 Aug 2026 09:43:28 +0200 Subject: [PATCH 2/3] Add Kamino solver changelog fragments Co-authored-by: Cursor --- .../changelog.d/kamino-solver-cfg-base-contrib.skip | 2 ++ .../changelog.d/kamino-solver-cfg-base.minor.rst | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 source/isaaclab_contrib/changelog.d/kamino-solver-cfg-base-contrib.skip create mode 100644 source/isaaclab_newton/changelog.d/kamino-solver-cfg-base.minor.rst diff --git a/source/isaaclab_contrib/changelog.d/kamino-solver-cfg-base-contrib.skip b/source/isaaclab_contrib/changelog.d/kamino-solver-cfg-base-contrib.skip new file mode 100644 index 000000000000..aa2517868275 --- /dev/null +++ b/source/isaaclab_contrib/changelog.d/kamino-solver-cfg-base-contrib.skip @@ -0,0 +1,2 @@ +Internal coupler type-check update accompanying the public Kamino solver +configuration base class. diff --git a/source/isaaclab_newton/changelog.d/kamino-solver-cfg-base.minor.rst b/source/isaaclab_newton/changelog.d/kamino-solver-cfg-base.minor.rst new file mode 100644 index 000000000000..952c590dc08b --- /dev/null +++ b/source/isaaclab_newton/changelog.d/kamino-solver-cfg-base.minor.rst @@ -0,0 +1,5 @@ +Added +^^^^^ + +* Added :class:`~isaaclab_newton.physics.KaminoSolverCfgBase` as the public + base configuration class for Kamino solvers. From 46b90afeb62e9bcd36b8e89cf2c021f6cf5b3d65 Mon Sep 17 00:00:00 2001 From: Ruben Grandia Date: Thu, 13 Aug 2026 12:15:36 +0200 Subject: [PATCH 3/3] Address Kamino solver configuration feedback Document the public base configuration, keep its test coverage extensible, and provide a clear error when it is used without a concrete dynamics solver. Co-authored-by: Cursor --- .../api/lab_newton/isaaclab_newton.physics.rst | 6 ++++++ .../physical-backends/newton/kamino-solver.rst | 5 ++--- .../isaaclab_newton/physics/kamino_manager_cfg.py | 4 +++- .../test/physics/test_newton_manager_abstraction.py | 12 +++++++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/source/api/lab_newton/isaaclab_newton.physics.rst b/docs/source/api/lab_newton/isaaclab_newton.physics.rst index 6fc699d4205e..f15ba9912236 100644 --- a/docs/source/api/lab_newton/isaaclab_newton.physics.rst +++ b/docs/source/api/lab_newton/isaaclab_newton.physics.rst @@ -29,6 +29,7 @@ KaminoMaterialsCfg KaminoPADMMCfg KaminoPADMMSolverCfg + KaminoSolverCfgBase MPMSolverCfg HydroelasticSDFCfg @@ -105,6 +106,11 @@ Physics Configuration :show-inheritance: :exclude-members: __init__ +.. autoclass:: KaminoSolverCfgBase + :members: + :show-inheritance: + :exclude-members: __init__ + .. autoclass:: KaminoPADMMSolverCfg :members: :show-inheritance: diff --git a/docs/source/overview/core-concepts/physical-backends/newton/kamino-solver.rst b/docs/source/overview/core-concepts/physical-backends/newton/kamino-solver.rst index 92023ca74113..b4f9738f0dfc 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/kamino-solver.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/kamino-solver.rst @@ -180,9 +180,8 @@ Kamino Solver Parameters ------------------------ The following fields are shared by -:class:`~isaaclab_newton.physics.KaminoPADMMSolverCfg` and -:class:`~isaaclab_newton.physics.KaminoDVISolverCfg`. They are grouped by the part -of the solver they affect. +:class:`~isaaclab_newton.physics.KaminoSolverCfgBase`. They are grouped by the part of +the solver they affect. Core Integration ^^^^^^^^^^^^^^^^ diff --git a/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py b/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py index d640d4c3e5b1..74ef35de3cb5 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/kamino_manager_cfg.py @@ -329,7 +329,9 @@ class KaminoSolverCfgBase(NewtonSolverCfg): def _get_dynamics_solver_config(self) -> tuple[Literal["padmm", "dvi"], dict[str, Any]]: """Return the selected Newton solver name and its configuration keyword arguments.""" - raise NotImplementedError + raise NotImplementedError( + f"{type(self).__name__} is a base configuration. Use KaminoPADMMSolverCfg or KaminoDVISolverCfg." + ) def to_solver_config(self) -> SolverKamino.Config: """Build a :class:`SolverKamino.Config` from this configuration. diff --git a/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py b/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py index 51e051e4be95..c62aac4db211 100644 --- a/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py +++ b/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py @@ -38,6 +38,7 @@ KaminoDynamicsCfg, KaminoPADMMCfg, KaminoPADMMSolverCfg, + KaminoSolverCfgBase, MJWarpSolverCfg, MPMSolverCfg, NewtonCfg, @@ -163,6 +164,15 @@ def test_newton_cfg_post_init_propagates_class_type( assert cfg.class_type.__name__ == expected_manager.__name__ +def test_kamino_solver_cfg_base_requires_concrete_configuration(): + """The public Kamino base config should direct users to a concrete solver config.""" + with pytest.raises( + NotImplementedError, + match="KaminoSolverCfgBase is a base configuration. Use KaminoPADMMSolverCfg or KaminoDVISolverCfg.", + ): + KaminoSolverCfgBase().to_solver_config() + + @pytest.mark.parametrize( "num_substeps, collision_decimation, should_warn", [ @@ -1000,7 +1010,7 @@ def test_initialize_solver_populates_canonical_state( # something to work with. body = builder.add_body(mass=1.0) builder.add_joint_revolute(parent=-1, child=body, axis=(0, 0, 1)) - if isinstance(solver_cfg, (KaminoPADMMSolverCfg, KaminoDVISolverCfg)) and solver_cfg.use_collision_detector: + if isinstance(solver_cfg, KaminoSolverCfgBase) and solver_cfg.use_collision_detector: builder.add_shape_sphere(body=body, radius=0.05) builder.add_ground_plane() NewtonManager.set_builder(builder)