Extending Newton Solvers#
To add a Newton solver or a custom coupled solver, write an Isaac Lab solver, a
NewtonSolver subclass, and its configuration, a
NewtonSolverCfg subclass whose class_type selects it. The sections below explain
what the solver overrides, resource ownership, and extension requirements.
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, the solver tuning guides for shipped solvers, and Coupled Solvers for choosing a coupling approach.
When a New Solver Is Needed#
Every Newton solver runs through one generic path written against Newton’s
SolverBase API: build the solver from the configuration fields matching its constructor,
step it, reset it for masked worlds, evaluate forward kinematics, and collide with Newton’s
CollisionPipeline. An Isaac Lab solver names its Newton solver class
(solver_class) and overrides capabilities and methods only where its
solver differs. Write a new one when:
a Newton solver has no Isaac Lab configuration yet;
the solver needs its own construction, contact allocation, 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 resolve into entry views over a
shared model. Prefer that path for mixed rigid and deformable scenes. Write a
custom coupled solver 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#
NewtonBackend holds everything bound to one finalized model and initializes, steps
and queries it (init_solver, step, forward, invalidate_fk, …), so backends with different solvers can
be built and stepped side by side. The NewtonManager runs one backend through the
Isaac Lab lifecycle. The backend’s Isaac Lab solver (NewtonBackend.solver_ops) is built with
instantiate(solver_cfg, model, mode, env_template) and holds the configuration as cfg and Newton’s solver as
native. The backend’s solver (NewtonBackend.solver) is that Newton
solver. A coupled solver holds one Isaac Lab solver per entry, built from the entry’s configuration.
Owner |
Responsibility |
|---|---|
Simulation context |
Builds the manager from |
What must survive a hard reset: site requests, the replication outputs
( |
|
Model, states, control, solver, collision pipeline and contacts, sensors, Newton actuators, reset masks, step callbacks, and the captured step. A hard reset or close discards it, together with its callbacks. |
|
Solver configuration |
Solver parameters, and |
Isaac Lab solver |
What differs from the generic path: construction, capabilities, stepping, per-world reset of solver-owned buffers, forward kinematics, contacts, and builder preparation. |
Coupler entry |
A disjoint part of the shared model, when the solver configuration is a coupler. |
NewtonCfg.class_type selects the integration manager independently of the solver. Asset factories use the
manager’s backend_name key, so a custom class name does not change backend selection.
Custom Managers#
To customize Isaac Lab integration, subclass NewtonManager and select it with
NewtonCfg(class_type=MyManager, solver_cfg=...). Call the base implementation from overridden lifecycle methods.
A custom solver only needs an Isaac Lab solver and its configuration, not a new manager.
Lifecycle#
Public call |
What happens |
Isaac Lab solver |
|---|---|---|
Stores the simulation context and acquires the shared |
|
|
|
Dispatches |
|
Runs |
|
|
Closes the backend and clears the session state. |
none |
The custom attributes of the Newton solvers in solver_classes are registered before particles are added and
before finalize(). The solver is constructed after PHYSICS_READY, so it sees model properties that consumers
author while binding.
The Step#
Each step() covers the whole decimation loop when the manager handles
decimation (handles_decimation(): an articulation runs its Newton
actuators in the step), and one physics step otherwise. Every physics step runs collide -> pre-actuator callbacks
-> Newton actuators -> post-actuator callbacks -> substeps, and post-step callbacks and sensors run once at the end.
Every substep runs staged body forces -> state-force callbacks -> step, and pre_step and post_step run
around each physics step’s substeps.
Every physics step ends in state_0, so manager.backend.state_0 is always the same object. The captured step
records the callbacks, so they must be graph-safe. Consumers register them with
register_post_step_callback() and its siblings while PHYSICS_READY
dispatches.
Property writers call add_model_change(). The backend combines the
queued categories and refreshes solver constants once before the next forward(), scene-data state read, or step.
invalidate_fk() takes selections in the articulation view’s row space
and uses model.articulation_world to flag the containing worlds. A view over a subset of worlds therefore resets
the selected worlds, while a global articulation requests FK without resetting the global world.
Captured Work#
step() runs its parts eagerly the first time and, when the solver
supports capture, captures them with wp.ScopedCapture and replays them with wp.capture_launch. With
capture-safe actuators the whole decimation loop is one graph. Newton actuators that cannot be captured, such as Torch
networks, run eagerly between a captured graph before them and one after them in every physics step. Registering a
step callback or reallocating contacts captures the step again at the next call. When a step, BVH refit or sensor
query runs inside an enclosing Warp capture, it records its work into that capture instead of starting its own;
recorded BVH refits run on every replay.
backend.live is true during a step and while a CUDA graph is being recorded. Derived asset properties then
recompute on every read, so callbacks see the current substep state and graph replays update their outputs. Outside
these intervals, the usual timestamp caches avoid repeated work.
Extension Contract#
Subclass NewtonSolver, name the Newton solver class, and point a
configuration at it. A solver whose constructor takes the configuration fields as keyword arguments needs nothing
else:
from isaaclab.utils import configclass
from isaaclab_newton.physics import NewtonSolverCfg, NewtonSolver
from my_package.solvers import SolverMySolver
class MySolver(NewtonSolver):
solver_class = SolverMySolver
deterministic = True
@configclass
class MySolverCfg(NewtonSolverCfg):
class_type: type | str = "my_package.my_solver:MySolver"
iterations: int = 16
MySolver(cfg, model, mode, env_template) calls build to construct Newton’s solver. Methods called before the
model exists (solver_classes, prepare_builder, applied_deterministic_mode, shares_materials,
coupling_error) and fixed_tendon_control are class methods that take the configuration. The rest are instance
methods that read self.cfg and self.native, e.g. reset(self, state, world_mask). Override only what
differs:
Member |
Meaning |
|---|---|
|
Newton solver class that |
|
Class-level capabilities: stepping in place on one |
|
|
|
Newton solvers whose custom builder attributes and USD schemas the builder needs before import; defaults to
|
|
Normalize the complete builder before |
|
The determinism mode the solver runs with for a request, and why the solver cannot run as a coupled-solver entry. |
|
Whether contacts read one material table shared by all environments, e.g. Kamino’s own collision detector. |
|
Construct Newton’s solver for a model, given the determinism mode and the clone plan’s environment template. |
|
Initialize the output state of double-buffered solvers before capture. |
|
Configuration-dependent capabilities: consuming applied body forces, and CUDA graph capture. |
|
Detect contacts internally, customize Newton’s collision pipeline, allocate the contacts the solver reports, or bind solver buffers to them. |
|
Refresh acceleration structures before, or publish derived state such as joint coordinates after, each physics step. Both must be CUDA-graph-safe. |
|
Change one substep, for example to run a projection after the solve. |
|
Clear solver-owned history for masked worlds without touching authored joint state; solver-specific forward kinematics. |
|
Raise asynchronous failures and log diagnostics after a step launch. |
|
Build a fixed-tendon command adapter (MJWarp only). |
A task that needs a different Newton solver class subclasses the Isaac Lab solver it extends and selects it through
its configuration’s class_type: a custom VBD solver only sets solver_class, and a custom implicit MPM solver
overrides build around isaaclab_newton.physics.mpm_solver.mpm_solver_config, which keeps the shared MPM
configuration resolution. Subclass a Newton solver only to change the physics; integration behavior belongs in the
Isaac Lab solver. Raise from a method on an unsupported configuration rather than silently degrading.
Coupling Paths#
The four architectures differ in what drives the substep loop:
Path |
Structure |
|---|---|
Standalone solver |
One manager, one solver, one model. The captured step owns the substep loop. |
Proxy coupling |
|
ADMM coupling |
|
Custom shared-model solver |
|
A custom shared-model solver bypasses entry ownership resolution, so it cannot reuse the coupler’s selectors or validation. For the proxy and ADMM trade-offs, see Coupled Solvers.
Where to go next#
Add a Physics Backend: adding a whole physics backend.
isaaclab_newton.physics: manager and solver configuration API reference.