Newton Manager Abstraction#
Newton exposes multiple solver families, and Isaac Lab keeps that flexibility by
making each solver an implementation detail of a small
NewtonManager subclass. The simulation context
still sees a normal physics manager; the solver configuration decides which
manager class is used.
For most new Newton solvers, the integration surface is intentionally small:
define a solver config that inherits from
NewtonSolverCfg;point the config’s
class_typeat a manager subclass;implement
_build_solver()in that manager;set the three base-manager slots:
_solver,_use_single_state, and_needs_collision_pipeline.
The existing MuJoCo Warp, XPBD, Featherstone, and Kamino managers are examples of this pattern.
Adding a Solver Manager#
The solver config carries both user-tunable solver parameters and the manager dispatch target:
from isaaclab_newton.physics import NewtonManager, NewtonSolverCfg
from isaaclab.utils.configclass import configclass
@configclass
class MySolverCfg(NewtonSolverCfg):
class_type: type[NewtonManager] | str = "{DIR}.my_solver_manager:NewtonMySolverManager"
solver_type: str = "my_solver"
iterations: int = 16
NewtonCfg copies solver_cfg.class_type into its own class_type in
__post_init__. User code keeps the normal shape:
from isaaclab.sim import SimulationCfg
from isaaclab_newton.physics import NewtonCfg
sim_cfg = SimulationCfg(
physics=NewtonCfg(
solver_cfg=MySolverCfg(iterations=32),
num_substeps=2,
)
)
The manager then owns solver construction:
from newton import Model
from newton.solvers import SolverMySolver
from isaaclab_newton.physics import NewtonManager
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
_use_single_state tells the base manager whether the solver advances in
place or swaps input/output states. _needs_collision_pipeline tells the base
manager whether to allocate and pass Newton collision-pipeline contacts to the
solver. A solver with its own internal contact detector can set it to False.
Optional Overrides#
Most managers only implement _build_solver(). Override more only when the
solver actually needs it:
_initialize_contacts(): allocate custom contact buffers or support an internal contact detector._step_solver(state_0, state_1, control, substep_dt): change one substep of solver execution while keeping the base simulation loop._simulate_physics_only(): add per-step work around the base substep loop, such as rebuilding a BVH.step(): handle solver-specific reset masks, graph capture, or model-change notification before delegating to the base manager.start_simulation()orinstantiate_builder_from_stage(): customize model building or post-finalize setup._solver_specific_clear(): release any class-level state owned by the solver manager.
Keep the manager name prefixed with Newton and the solver config grouped
with the other Newton solver configs so autocomplete and backend discovery stay
predictable.
Custom Coupled Solvers#
Coupled solvers use the same abstraction. Instead of wrapping one Newton solver,
a coupled manager constructs two or more sub-solvers and overrides
_step_solver() to define the substep order.
That means a custom coupling usually needs only a config that stores existing
solver configs plus a manager that defines how data flows between them; the
component solvers can stay unchanged.
The MJWarp + VBD deformable manager is a concrete example:
CoupledMJWarpVBDSolverCfgstores arigid_solver_cfgforMJWarpSolverCfg, asoft_solver_cfgforVBDSolverCfg, and acoupling_mode.NewtonCoupledMJWarpVBDManager._build_solver()constructsSolverMuJoCoandSolverVBDfrom those sub-configs._step_solver()dispatches to either one-way or two-way coupling.The base
NewtonManagerstill owns state allocation, substep iteration, Fabric synchronization, and reset/clear lifecycle.
The two-way MJWarp + VBD substep stays compact because it is expressed as a short coupling algorithm:
Algorithm: Two-Way MJWarp + VBD Substep
Inputs: rigid body state, deformable particle state, and the shared Newton collision pipeline.
Output: updated rigid body and deformable particle state for one Newton substep.
Reset force accumulators. Clear the rigid body and particle force buffers before evaluating the next contact pass.
Detect coupled contacts. Run Newton collision detection once over the current rigid and deformable state.
Apply soft-to-rigid reactions. Inject body-particle contact reactions into
body_fso the rigid bodies can be pushed back by the deformable contact penalties.Advance the rigid solver. Step the MJWarp rigid solver with the coupled contact forces applied.
Preserve shared contacts for the soft solve. Clear particle forces written during the rigid step while keeping the detected contact information available.
Advance the deformable solver. Step the VBD soft solver against the same coupled contacts.
This keeps the custom part focused on the coupling policy. The manager does not need to reimplement scene loading, asset buffers, reset handling, or the outer simulation loop.
Franka manipulation using MJWarp for rigid bodies and VBD for the deformable object.#
You can exercise this coupling path with the Franka soft-body lifting task:
./isaaclab.sh -p scripts/environments/zero_agent.py --task Isaac-Lift-Soft-Franka-v0 --num_envs 1 --visualizer kit
For the surface-deformable cloth variant, use --task Isaac-Lift-Cloth-Franka-v0.
This environment configures
CoupledMJWarpVBDSolverCfg with
coupling_mode="two_way".
Tuning the Franka Soft-Body Lift#
Tune the coupled contact behavior before training a policy:
Start with
coupling_mode="two_way". Compared with one-way coupling, two-way coupling can prevent clipping more easily because body-particle contact penalties can push the robot back instead of only moving the deformable.Use a small scripted grasp/lift check before training to confirm that grasping is possible and to inspect what clips when the grasp fails.
Lower the arm actuator stiffness enough that the arm can respond to contact penalties. Prefer the arm being pushed back over the gripper clipping into the deformable.
Tune
soft_contact_kefirst. Increase it only as much as needed to prevent clipping, then adjustsoft_contact_muso the gripper can carry the object without requiring an obviously unphysical friction value. Usesoft_contact_kdfor stabilization if contacts chatter.Tune the
soft_contact_*values together withshape_material_*values because rigid shape material parameters also affect the effective contact.If
soft_contact_keis not sufficient, orsoft_contact_mumust be unphysically high, tune the Franka arm and hand actuator stiffness and maximum effort. For the gripper command, fully close the fingers and let the actuator maximum effort limit the actual squeeze.If the deformable no longer visibly deforms,
soft_contact_keis likely too high.If contacts are unstable or missed, increase the deformable mesh resolution or increase
particle_radiusin the deformable material so contact is detected earlier from a larger distance.If the rigid shapes still clip through the deformable, increase
iterations; more VBD iterations can improve contact convergence.
When to Add a Coupled Manager#
Add a coupled manager when one solver cannot own the whole model step by itself:
rigid bodies should use one solver while particles or cloth use another;
contact detection is shared, but each solver consumes the contacts differently;
you need a custom force, impulse, or state exchange between solvers;
the substep order is part of the algorithm.
Use a normal single-solver manager when all physics can be advanced by one Newton solver. Use a coupled manager only for the small amount of glue that is truly solver-specific.