Extending Newton Solvers#
This page is for contributors adding a Newton solver to Isaac Lab or building a
custom coupled solver. It describes the
NewtonManager extension contract: what the
manager owns, when its hooks run, and what a subclass must provide.
If you only need to select and configure a shipped solver, use the user-facing pages instead: Backends and Presets for backend and preset selection, Solver Tuning for the per-solver guides, and Coupled Solvers for choosing a coupling approach.
When a Solver Manager Is Needed#
Each Newton solver is exposed as a
NewtonManager subclass. Write a new one when:
a Newton solver has no Isaac Lab manager yet;
the solver needs its own contact allocation, builder attributes, or reset handling;
several solvers must advance one shared model and the substep order is part of the algorithm.
Do not write one when an existing solver can advance the whole model, or when
the scene can be partitioned into named solver entries. Partitioning is already
covered by CouplerProxyCfg and
CouplerAdmmCfg, which
NewtonCouplerManager resolves into entry
views over a shared model. Prefer that path for mixed rigid and deformable
scenes. Write a coupled manager only when contact detection is shared but each
solver consumes the contacts differently, or when the exchange between solvers
is a custom force, impulse, or state transfer.
Responsibilities and Boundaries#
NewtonManager is a class-level singleton: all
state lives on the base class and there are no instance methods, so exactly one
manager subclass is active per simulation.
Owner |
Responsibility |
|---|---|
Simulation context |
Resolves the manager subclass from
|
|
Builder, finalized |
Manager subclass |
Solver construction and any solver-owned internal state, contact buffers, or builder attributes. |
Coupler entry |
A disjoint part of the shared model, when the active manager is a coupler. |
Task configuration |
A |
NewtonCfg copies solver_cfg.class_type
onto its own class_type, so task
configuration never names the manager directly.
Lifecycle#
The public entry points run in this order. Subclass hooks are private; the base class invokes them.
Public call |
What happens |
Subclass hooks |
|---|---|---|
Stores the simulation context, reads gravity from the simulation configuration, and creates the scene data backend. |
none |
|
|
Creates or imports the |
|
Finalizes the model, then allocates states, reset masks, and Fabric prims. |
|
|
Builds the solver, checks that it was assigned, and allocates contacts. |
|
|
A hard reset re-runs |
|
|
Runs one actuator pass plus |
|
|
Refreshes kinematics, then writes body, cable, and particle state to Fabric for rendering. |
|
|
Releases the solver, model, and all class-level state. |
|
_build_solver() runs after the model is finalized, so it may size solver
resources from the real model. _register_builder_attributes() runs before
particles are added and before finalize(), so it is the only place to
register Newton custom attributes.
step() takes one of two paths, selected by
handles_decimation(). When every
actuator is on the graph-safe Newton fast path, actuators and substeps run
together inside one graph and step() runs the whole decimation loop, so
_step_solver() runs decimation x num_substeps times per call. Otherwise
actuators run eagerly, only the substeps are graphed through
_simulate_physics_only(), and the environment drives decimation by calling
step() repeatedly. Both paths reach _step_solver() through the same
substep loop.
Graph capture happens at one of several points. initialize_solver() captures
unless the graph-safe path is active; in that case
set_decimation() captures once the
decimation is known, and the RTX path defers capture to the first step().
All routes check _supports_cuda_graph_capture(). The non-RTX route
additionally checks _requires_initial_reset_before_graph_capture().
Warning
With _use_single_state = False the base manager ping-pongs
NewtonManager._state_0 and NewtonManager._state_1 after each substep,
except on the final substep of an odd count, where it copies instead. Never
cache a State reference in _build_solver(); read the current state
through the class attribute on each use.
Extension Contract#
A subclass must implement _build_solver() and assign four slots on
NewtonManager itself, not on cls.
initialize_solver() raises
RuntimeError if _solver is still unset. The other three are not
validated and keep their defaults, so a subclass that forgets them runs with
double-buffered states, no collision pipeline, and no visualizer force input.
Slot |
Meaning |
|---|---|
|
The constructed Newton |
|
|
|
|
|
|
from newton import Model
from newton.solvers import SolverMySolver
from isaaclab.utils import configclass
from isaaclab_newton.physics import NewtonManager, NewtonSolverCfg
@configclass
class MySolverCfg(NewtonSolverCfg):
class_type: type[NewtonManager] | str = "{DIR}.my_solver_manager:NewtonMySolverManager"
solver_type: str = "my_solver"
iterations: int = 16
class NewtonMySolverManager(NewtonManager):
@classmethod
def _build_solver(cls, model: Model, solver_cfg: MySolverCfg) -> None:
NewtonManager._solver = SolverMySolver(model, iterations=solver_cfg.iterations)
NewtonManager._use_single_state = False
NewtonManager._needs_collision_pipeline = True
NewtonManager._supports_rigid_body_force_input = True
Override anything else only when the solver needs it:
_create_solver(): construct a solver without mutating manager state, so a coupler can nest this solver._initialize_contacts(): allocate custom contact buffers._step_solver(state_0, state_1, control, contacts, substep_dt): change one substep while keeping the base simulation loop._simulate_physics_only(): add per-step work around the substep loop._reset_solver_internals(): clear solver-owned state for reset worlds._register_builder_attributes(): register Newton custom particle, shape, or body attributes on the builder._prepare_builder_for_finalize(): normalize imported or replicated builder data immediately beforefinalize()._supports_cuda_graph_capture(): returnFalseto fall back to eager execution._requires_initial_reset_before_graph_capture(): delay headless capture until the first post-reset step._solver_specific_clear(): release class-level state the subclass owns._check_solver_status()and_log_solver_debug(): run after stepping.
NewtonMPMManager overrides both builder hooks.
Raise from _build_solver() on an unsupported configuration rather than
silently degrading. Name the manager Newton<Solver>Manager.
Coupling Paths#
The four architectures differ in what drives the substep loop:
Path |
Structure |
|---|---|
Standalone solver |
One manager subclass, one solver, one model. The base class owns the substep loop. |
Proxy coupling |
|
ADMM coupling |
|
Custom shared-model manager |
A subclass constructs several sub-solvers itself and overrides
|
A custom shared-model manager bypasses entry ownership resolution, so it cannot reuse the coupler’s selectors or validation. For the proxy and ADMM trade-offs, see Coupled Solvers.