Source code for isaaclab_newton.physics.kamino_manager_cfg

# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Configuration for Newton Kamino physics manager."""

from __future__ import annotations

from dataclasses import field
from typing import TYPE_CHECKING, Any, Literal

from isaaclab.utils.configclass import configclass

from .newton_manager_cfg import NewtonSolverCfg

if TYPE_CHECKING:
    from newton.solvers import SolverKamino

    from isaaclab_newton.physics import NewtonManager


def _non_none_kwargs(cfg: Any) -> dict[str, Any]:
    """Return ``cfg.to_dict()`` entries with ``None`` values omitted."""
    return {key: value for key, value in cfg.to_dict().items() if value is not None}


def _cfg_to_dict(cfg: Any) -> dict[str, Any]:
    """Return a configclass mapping with a type-checker-friendly interface."""
    return cfg.to_dict()


[docs] @configclass class KaminoPADMMCfg: """P-ADMM forward-dynamics solver parameters for Kamino.""" max_iterations: int = 100 """Maximum number of P-ADMM solver iterations.""" primal_tolerance: float = 1e-4 """Primal residual convergence tolerance.""" dual_tolerance: float = 1e-4 """Dual residual convergence tolerance.""" compl_tolerance: float = 1e-4 """Complementarity residual convergence tolerance.""" restart_tolerance: float = 0.999 """Combined primal-dual residual tolerance for acceleration restarts.""" rho_0: float = 0.05 """Initial penalty parameter.""" rho_min: float = 1e-5 """Lower bound on the penalty parameter.""" a_0: float = 1.0 """Initial acceleration parameter.""" alpha: float = 10.0 """Primal-dual residual threshold for penalty updates.""" tau: float = 1.5 """Penalty increase/decrease factor.""" eta: float = 1e-5 """Proximal regularization parameter. Must be greater than zero.""" penalty_update_freq: int = 1 """Frequency of penalty updates. Zero disables updates.""" penalty_update_method: Literal["fixed", "balanced"] = "fixed" """Penalty update method.""" linear_solver_tolerance: float = 0.0 """Absolute tolerance for the iterative linear solver. Zero leaves it unchanged.""" linear_solver_tolerance_ratio: float = 0.0 """Ratio adapting the linear solver tolerance from the ADMM primal residual.""" use_acceleration: bool = True """Whether to use Nesterov-type acceleration (APADMM).""" use_graph_conditionals: bool = False """Whether to use CUDA graph conditional nodes in the iterative solver.""" warmstart_mode: Literal["none", "internal", "containers"] = "containers" """Warmstart mode.""" contact_warmstart_method: Literal[ "key_and_position", "geom_pair_net_force", "geom_pair_net_wrench", "key_and_position_with_net_force_backup", "key_and_position_with_net_wrench_backup", ] = "geom_pair_net_force" """Contact warm-start method."""
[docs] @configclass class KaminoDVICfg: """DVI forward-dynamics solver parameters for Kamino.""" tolerance: float = 1e-5 """Convergence tolerance on the projected update size.""" regularization: float = 1e-6 """Diagonal regularization added to each projected update denominator.""" omega: float = 1.0 """Relaxation factor applied to projected Gauss-Seidel updates.""" max_alternating_iterations: int = 20 """Maximum outer DVI iterations.""" inequality_sweeps_per_iteration: int = 1 """Projected Gauss-Seidel sweeps per DVI iteration.""" bilateral_solve_interval: int = 1 """DVI iterations between repeated direct bilateral solves.""" bilateral_solver_type: Literal["LLTB", "LLTBRCM"] = "LLTB" """Direct linear solver for the bilateral constraint block.""" bilateral_solver_kwargs: dict[str, Any] = field(default_factory=dict) """Additional keyword arguments for the bilateral linear solver.""" warmstart_mode: Literal["none", "internal", "containers"] = "containers" """Warmstart mode.""" contact_warmstart_method: Literal[ "key_and_position", "geom_pair_net_force", "key_and_position_with_net_force_backup", ] = "key_and_position_with_net_force_backup" """Contact warm-start method when ``warmstart_mode`` is ``containers``."""
[docs] @configclass class KaminoDynamicsCfg: """Constrained forward-dynamics problem parameters for Kamino.""" preconditioning: bool = True """Whether to precondition the dual problem. Must be ``False`` when using DVI.""" linear_solver_type: Literal["LLTB", "LLTBRCM", "CR", "CRF"] = "LLTB" """Linear solver for the dynamics problem.""" linear_solver_kwargs: dict[str, Any] = field(default_factory=dict) """Additional keyword arguments for the linear solver."""
[docs] @configclass class KaminoConstraintsCfg: """Global constraint stabilization parameters for Kamino.""" alpha: float = 0.1 """Baumgarte stabilization for bilateral joint constraints. Valid range is [0, 1].""" beta: float = 0.01 """Baumgarte stabilization for unilateral joint-limit constraints. Valid range is [0, 1].""" gamma: float = 0.01 """Baumgarte stabilization for unilateral contact constraints. Valid range is [0, 1].""" delta: float = 1.0e-6 """Contact penetration margin [m]."""
[docs] @configclass class KaminoFKCfg: """Forward-kinematics reset solver parameters for Kamino.""" use_regularization: bool = True """Whether to regularize the FK reset solve (Tikhonov term on body poses).""" regularization_weight: float = 1e-5 """Weight of the FK reset regularizer when :attr:`use_regularization` is ``True``.""" tolerance: float = 1e-5 """Convergence tolerance of the FK reset solve."""
[docs] @configclass class KaminoCollisionDetectorCfg: """Internal Kamino collision-detector parameters.""" pipeline: Literal["primitive", "unified"] | None = None """Collision-detection pipeline. ``None`` uses Newton's default (``unified``).""" broadphase: Literal["nxn", "sap", "explicit"] | None = None """Broad-phase algorithm. ``None`` uses Newton's default.""" bvtype: Literal["aabb", "bs"] | None = None """Bounding-volume type. ``None`` uses Newton's default.""" max_contacts: int | None = None """Model-wide contact buffer capacity cap.""" max_contacts_per_world: int | None = None """Per-world contact buffer capacity override.""" max_contacts_per_pair: int | None = None """Maximum contacts generated per candidate geometry pair.""" max_triangle_pairs: int | None = None """Maximum triangle-primitive shape pairs in narrow phase.""" default_gap: float | None = None """Default detection gap [m] applied as a floor to per-geometry gaps."""
[docs] @configclass class KaminoMaterialsCfg: """Material mixing parameters for Kamino contacts.""" friction_mix_mode: Literal["average", "multiply", "max", "min"] = "average" """How friction coefficients are mixed for a contact pair.""" restitution_mix_mode: Literal["average", "multiply", "max", "min"] = "min" """How restitution coefficients are mixed for a contact pair."""
@configclass class _KaminoSolverCfgBase(NewtonSolverCfg): """Common configuration for Kamino solver-related parameters. Kamino simulates constrained rigid multi-body systems in maximal coordinates with hard frictional contacts. .. note:: This solver is currently in **Beta**. Its API and behavior may change in future releases. For more information, see the `Newton Kamino documentation`_. .. _Newton Kamino documentation: https://newton-physics.github.io/newton/latest/ """ class_type: type[NewtonManager] | str = "{DIR}.kamino_manager:NewtonKaminoManager" """Manager class for the Kamino solver.""" solver_type: str = "kamino" """Solver type. Can be "kamino".""" integrator: Literal["euler", "moreau"] = "moreau" """Integrator type.""" use_collision_detector: bool = False """Whether to use Kamino's internal collision detector instead of Newton's pipeline.""" use_fk_solver: bool | None = None """Whether to enable the forward kinematics solver for state resets. When ``None``, Kamino will automatically determine whether to use the FK solver based on the model's articulation structure. If the model has loop-closing joints, the FK solver will be used. When ``True``, :meth:`NewtonKaminoManager._eval_fk_impl` reconciles body state via :meth:`SolverKamino.reset` with :class:`SolverKamino.ResetConfig.from_joints`. Kamino's FK solver computes consistent body poses/velocities from the joint coordinates (including the base joint for floating bases), resolves passive / loop-closure joints, and writes back a consistent full joint state. Environment resets only need to write actuated DOFs in ``joint_q``; passive values are filled in by FK. This is required for closed-loop systems. When ``False``, Newton's articulated ``eval_fk`` is used instead over the full ``joint_q`` / ``joint_qd``. It is then up to the user to specify constraint-consistent values. This is the faster option for purely articulated (tree-structured) systems. """ sparse_jacobian: bool | None = None """Whether to use sparse Jacobian computation. ``None`` lets Newton pick per backend.""" sparse_dynamics: bool = False """Whether to use sparse dynamics computation.""" rotation_correction: Literal["twopi", "continuous", "none"] = "twopi" """Rotation correction mode.""" angular_velocity_damping: float = 0.0 """Angular velocity damping factor. Valid range is [0.0, 1.0].""" collect_solver_info: bool = False """Whether to collect solver convergence and performance info at each step. .. warning:: Enabling this significantly increases solver runtime and should only be used for debugging. """ compute_solution_metrics: bool = False """Whether to compute solution metrics at each step. .. warning:: Enabling this significantly increases solver runtime and should only be used for debugging. """ dynamics: KaminoDynamicsCfg | None = None """Constrained dynamics problem parameters. When ``None``, Newton selects defaults appropriate to the selected dynamics solver and sparsity settings. """ constraints: KaminoConstraintsCfg = field(default_factory=KaminoConstraintsCfg) """Constraint stabilization parameters.""" fk: KaminoFKCfg = field(default_factory=KaminoFKCfg) """Forward-kinematics reset solver parameters.""" collision_detector: KaminoCollisionDetectorCfg = field(default_factory=KaminoCollisionDetectorCfg) """Internal collision-detector parameters.""" materials: KaminoMaterialsCfg = field(default_factory=KaminoMaterialsCfg) """Material mixing parameters.""" max_contacts_per_world: int | None = None """Cap the per-world contact pre-allocation handed to Kamino. When ``None``, Kamino falls back to ``geoms.world_minimum_contacts`` derived from the collision pipeline, which over-allocates dramatically for contact-rich assets. Set this to bound GPU memory for multi-env training of contact-heavy tasks (e.g. legged locomotion or manipulation). The total ``model.rigid_contact_max`` is computed as ``max_contacts_per_world * model.world_count`` before solver construction. This field is applied by :class:`NewtonKaminoManager` and is not forwarded to Newton. """ 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 def to_solver_config(self) -> SolverKamino.Config: """Build a :class:`SolverKamino.Config` from this configuration. Returns: A ``SolverKamino.Config`` instance ready for solver construction. """ from newton._src.solvers.kamino.config import ( CollisionDetectorConfig, ConstrainedDynamicsConfig, ConstraintStabilizationConfig, DVISolverConfig, ForwardKinematicsSolverConfig, MaterialManagerConfig, PADMMSolverConfig, ) from newton.solvers import SolverKamino # Kamino Manager will set the automatic value before calling this method. # This is a fallback to true if that mechanism was bypassed. use_fk_solver = self.use_fk_solver if use_fk_solver is None: use_fk_solver = True collision_detector = None if self.use_collision_detector: collision_detector = CollisionDetectorConfig(**_non_none_kwargs(self.collision_detector)) # Initialize all solver configs with default values. solver_config_types = { "padmm": PADMMSolverConfig, "dvi": DVISolverConfig, } solver_configs = {name: config_type() for name, config_type in solver_config_types.items()} # Overwrite the selected dynamics solver's config with the user-provided config. dynamics_solver, active_solver_kwargs = self._get_dynamics_solver_config() solver_configs[dynamics_solver] = solver_config_types[dynamics_solver](**active_solver_kwargs) # Build the final solver config. config = SolverKamino.Config( dynamics_solver=dynamics_solver, integrator=self.integrator, use_collision_detector=self.use_collision_detector, use_fk_solver=use_fk_solver, sparse_jacobian=self.sparse_jacobian, sparse_dynamics=self.sparse_dynamics, rotation_correction=self.rotation_correction, angular_velocity_damping=self.angular_velocity_damping, collect_solver_info=self.collect_solver_info, compute_solution_metrics=self.compute_solution_metrics, collision_detector=collision_detector, fk=ForwardKinematicsSolverConfig(**_cfg_to_dict(self.fk)), constraints=ConstraintStabilizationConfig(**_cfg_to_dict(self.constraints)), dynamics=None if self.dynamics is None else ConstrainedDynamicsConfig(**_cfg_to_dict(self.dynamics)), materials=MaterialManagerConfig(**_cfg_to_dict(self.materials)), **solver_configs, ) config.validate() return config
[docs] @configclass class KaminoPADMMSolverCfg(_KaminoSolverCfgBase): """Configuration for Kamino with the P-ADMM forward-dynamics solver.""" dynamics_solver_cfg: KaminoPADMMCfg = field(default_factory=KaminoPADMMCfg) """P-ADMM forward-dynamics solver parameters.""" def _get_dynamics_solver_config(self) -> tuple[Literal["padmm"], dict[str, Any]]: """Return P-ADMM and its configuration keyword arguments.""" return "padmm", _cfg_to_dict(self.dynamics_solver_cfg)
[docs] @configclass class KaminoDVISolverCfg(_KaminoSolverCfgBase): """Configuration for Kamino with the DVI forward-dynamics solver.""" dynamics_solver_cfg: KaminoDVICfg = field(default_factory=KaminoDVICfg) """DVI forward-dynamics solver parameters.""" def _get_dynamics_solver_config(self) -> tuple[Literal["dvi"], dict[str, Any]]: """Return DVI and its configuration keyword arguments.""" return "dvi", _cfg_to_dict(self.dynamics_solver_cfg)