newton.solvers.SolverVBD#

class newton.solvers.SolverVBD(model, *, iterations=None, friction_epsilon=1e-2, soft_contact_damping_bidirectional=True, dat_conservative_bound_relaxation=0.85, integrate_with_external_rigid_solver=False, particle_enable_self_contact=False, particle_self_contact_radius=None, particle_self_contact_margin=None, particle_self_contact_gap=None, particle_conservative_bound_relaxation=None, particle_vertex_contact_buffer_size=32, particle_edge_contact_buffer_size=64, particle_collision_detection_interval=None, particle_edge_parallel_epsilon=1e-5, particle_enable_tile_solve=True, particle_topological_contact_filter_threshold=2, particle_rest_shape_contact_exclusion_radius=0.0, particle_external_vertex_contact_filtering_map=None, particle_external_edge_contact_filtering_map=None, rigid_compliant_alm=True, rigid_avbd_alpha=None, rigid_avbd_joint_alpha=None, rigid_avbd_contact_alpha=None, rigid_avbd_beta=None, rigid_avbd_linear_beta=None, rigid_avbd_angular_beta=None, rigid_avbd_gamma=0.999, rigid_contact_hard=None, rigid_contact_history=None, rigid_contact_k_start=None, rigid_contact_damping_bidirectional=False, rigid_body_contact_buffer_size=64, rigid_body_particle_contact_buffer_size=256, rigid_soft_contact_use_log_barrier=False, rigid_joint_linear_ke=None, rigid_joint_angular_ke=None, rigid_joint_linear_k_start=None, rigid_joint_angular_k_start=None, rigid_joint_linear_kd=None, rigid_joint_angular_kd=None, tendon_max_sweeps=256, tendon_settle_tol=1.0e-3, tendon_settle_atol=1.0e-6, tendon_activation_tol=2.0e-3, rigid_soft_enable_dat=False, rigid_soft_dat_use_interval_arithmetic=False, deterministic=None, enable_fast_math=True, collision_pipeline=None, collision_frequency=None, collision_frequency_type=None)[source]#

Bases: TendonStateMixin, SolverBase, CouplingInterface

An implicit solver using Vertex Block Descent (VBD) for particles and Augmented VBD (AVBD) for rigid bodies.

Experimental

SolverVBD’s public API and behavior may change without prior notice.

Tuning guide: VBD Tuning (https://newton-physics.github.io/newton/stable/concepts/simulation_tuning_vbd.html) covers cloth, soft bodies, rigid bodies, cables, and their contacts:

  • Parameter effects: the constructor arguments that matter most for tuning, the model parameters, and the time step, substeps and iterations.

  • Contact handling: interaction distance, detection slack, query radius and motion budget; what DAT does to motion; how to set margin and gap.

  • A debug protocol: the jitter pipeline and the explosion checklist, each in order.

  • Special topics with symptom, cause and fix: the stiffness ratio (contact against elasticity, membrane against bending), a fine mesh converging slowly, DAT damping, bad assets, a smaller time step making things worse, self-contact tunneling.

  • Parameter combinations from the shipped examples, and rules of thumb.

  • Rigid bodies and cables: joint gains, cable materials, contact history and capacity, model updates, and reset.

The math behind the advice (step objective, block solve, rigid bodies and joints, the energy terms, DAT) is on the solver page VBD.

This unified solver supports:
  • Particle simulation (cloth, soft bodies) using the VBD algorithm

  • Rigid body simulation (joints, contacts) using the AVBD algorithm

  • Coupled particle-rigid body systems

  • Compliant and hard rigid-body-to-particle attachments

For rigid bodies, two paths are supported:

  • Compliant ALM (rigid_compliant_alm=True, default): one finite-material formulation for structural joints, drives, limits, and body-body contacts. Authored finite stiffness controls physical compliance, while SolverVBD selects an internal ALM metric rho for numerical conditioning. For a material row, they combine as k_eff = k*rho/(k+rho), and a multiplier lambda carries accumulated reaction. The per-slot joint hard/soft API (authored via model.vbd.joint_is_hard or set_joint_constraint_mode()) is deprecated. rigid_contact_hard is a separate legacy-contact control and has no formulation effect under compliant ALM. Both distinctions will be removed with the legacy path.

  • Legacy AVBD (rigid_compliant_alm=False, deprecated): penalty stiffness that is fixed by default (rigid_avbd_beta=0) or ramped per iteration from k_start seeds, where non-rod joint slots default to hard mode (augmented Lagrangian with persistent lambda and C0 stabilization) and rod stretch, shear, bend, and twist default to soft (penalty-based). Deprecated as of Newton 1.6 and will be removed in a future release; select it explicitly with rigid_compliant_alm=False.

Joint limitations:
  • Supported joint types: BALL, FIXED, FREE, REVOLUTE, PRISMATIC, D6, ROD. DISTANCE joints are not supported.

  • joint_enabled is supported for all joint types and is read live. After changing enable flags, call notify_model_changed() with JOINT_PROPERTIES to refresh derived contact conditioning. Structural-slot material (rigid_joint_linear_ke/ rigid_joint_angular_ke), constraint layout, and non-ROD drive/limit rest-angle offsets are captured at construction; rebuild SolverVBD after changing them.

  • joint_target_ke/joint_target_kd are supported for REVOLUTE, PRISMATIC, D6 (as drives), and ROD (as stretch, shear, bend, and twist stiffness and damping). VBD interprets kd as absolute damping in physical units. ROD values are cached in solver-owned buffers at construction; after editing them call notify_model_changed() with JOINT_DOF_PROPERTIES to refresh that cache. REVOLUTE, PRISMATIC, and D6 drive/limit coefficients are gathered live from the model on every solve and need no notification – except on the deprecated legacy AVBD path, whose cached penalty state the same flag refreshes.

  • joint_limit_lower/joint_limit_upper and joint_limit_ke/joint_limit_kd are supported for REVOLUTE, PRISMATIC, and D6 joints.

  • Angular winding is retained for REVOLUTE joints and D6 joints with exactly one angular DOF. Tracking requires less than pi radians of relative rotation per solver step, including prediction and solver corrections.

  • joint_f (feedforward forces) is supported, including six-component world-frame wrenches for ROD joints.

  • Not supported: joint_armature, joint_friction, joint_effort_limit, joint_velocity_limit, joint_target_mode, equality constraints, and the deprecated sparse mimic constraints.

  • Joint-owned mimic relationships are supported for PRISMATIC, REVOLUTE, and D6 joints.

See Joint Feature Support for the full comparison across solvers.

Tendon limitations:
  • Static routes and isolated dynamic ROLLER guides are supported. Zero segment compliance is approximated as 1.0e-8 m/N.

  • Call newton.ModelBuilder.color() after adding tendons so that segment endpoints receive different rigid-body colors.

Particle-body attachment limitations:
  • Attachments are translational and constrain one particle to a body-local point.

  • Attachments are compliant-ALM rows with a limit-row penalty: a finite stiffness keeps its physical spring offset under load, while infinite stiffness is the hard limit of the same row and ignores damping. Multipliers are retained across steps with rigid_avbd_gamma. Finite iterations can leave residual error, especially for conflicting constraints.

  • Both endpoints must be integrated by this solver. Attachments are not supported with integrate_with_external_rigid_solver=True.

See Particle-Body Attachments for authoring and cross-solver behavior.

Buffer sizing:

Body-body contact state is pre-allocated from model.rigid_contact_max when a CollisionPipeline has already published it and this solver owns the rigid system. Body-particle contact state is pre-sized from a world-aware particle-shape pair count, which excludes the enable_rigid_soft_full_surface_contact edge/face headroom. Both grow from Contacts on the first step(), and the rigid contact force outputs grow in collect_rigid_contact_forces(). During graph capture, ordinary lazy resizing is supported on CPU and on CUDA with Warp’s stream-ordered memory pool enabled; otherwise the solver raises with guidance to pre-size before capture. Rigid contact history is cross-replay-persistent state, so it must always be allocated before capture regardless of the device’s allocation-during-capture support – allocating it inside a graph records a wp.zeros fill that wipes the warm-start buffers on every replay. With rigid_contact_history=True, construct CollisionPipeline before SolverVBD, or run one uncaptured solver step before capture.

Contact force observables:

Request CONTACT_F with observables() and pass the container to step() to evaluate one wrench per body-body contact (when this solver integrates the rigid bodies) and per rigid-soft contact record – particle, edge, and face – at the final configuration. The step writes directly into contact_f without changing the simulation result. Unused rows, including rigid rows owned by an external rigid solver, are zero. Soft self-contact forces are not reported. See Contact forces from SolverVBD for the wrench convention and a usage guide.

References

  • Anka He Chen, Ziheng Liu, Yin Yang, and Cem Yuksel. 2024. Vertex Block Descent. ACM Trans. Graph. 43, 4, Article 116 (July 2024), 16 pages. https://doi.org/10.1145/3658179

  • Chris Giles, Elie Diaz, and Cem Yuksel. 2025. Augmented Vertex Block Descent. ACM Trans. Graph. 44, 4, Article 90 (August 2025), 12 pages. https://doi.org/10.1145/3731195

  • Anka H. Chen, Jerry Hsu, Youssef Ayman, and Miles Macklin. 2026. Divide and Truncate: A Penetration and Inversion Free Framework for Coupled Multi-physics System. In ACM SIGGRAPH 2026 Conference Papers. https://doi.org/10.1145/3799902.3811143

  • Anka He Chen, Jerry Hsu, Ziheng Liu, Miles Macklin, Yin Yang, and Cem Yuksel. 2025. Offset Geometric Contact. ACM Trans. Graph. 44, 4. https://doi.org/10.1145/3731205

Note

SolverVBD requires coloring for each system it solves:

Call newton.ModelBuilder.color() to automatically color both particles and rigid bodies.

For non-ROD rigid joints, VBD uses model.body_q as the structural rest pose and reads model.joint_q for drive/limit rest-angle offsets. The body transforms must match the joint angles at solver creation time (see example below).

For ROD joints, SolverVBD captures structural-rest bend and twist from model.joint_rest_q when constructed. After changing these Model-owned rest rotations, call notify_model_changed() with JOINT_REFERENCE_POSE_PROPERTIES; the broader JOINT_DOF_PROPERTIES also refreshes it.

For CUDA graph capture, the recommended construction order is CollisionPipeline -> Contacts -> SolverVBD, all before capture.

Example

# Automatically color both particles and rigid bodies
builder.color()

model = builder.finalize()

collision_pipeline = newton.CollisionPipeline(model)
contacts = collision_pipeline.contacts()

solver = newton.solvers.SolverVBD(model)

# Initialize states and control
state_in = model.state()
state_out = model.state()
control = model.control()

# Simulation loop
for i in range(100):
    collision_pipeline.collide(state_in, contacts)
    solver.step(state_in, state_out, control, contacts, dt)
    state_in, state_out = state_out, state_in
class JointSlot#

Bases: object

Named constraint slot indices for set_joint_constraint_mode().

Structural constraint slots by joint type:
  • ROD: STRETCH=0, SHEAR=1, BEND=2, TWIST=3

  • BALL: LINEAR=0 only

  • FIXED/REVOLUTE/PRISMATIC/D6: LINEAR=0, ANGULAR=1

STRETCH/SHEAR/BEND/TWIST name SolverVBD’s four-slot constraint layout and apply only to ROD. Only structural slots are named here; per-DOF drive/limit slots (slot 2+ on non-rod joints) are not.

These are solver constraint slots, not offsets into newton.Model.joint_target_ke or newton.Model.joint_target_kd. Rod material coefficients start at newton.Model.joint_qd_start in the order [shear_x, shear_y, stretch_z, bend_x, bend_y, twist_z]; see Rod joints.

ANGULAR = 1#
BEND = 2#
LINEAR = 0#
SHEAR = 1#
STRETCH = 0#
TWIST = 3#
classmethod register_custom_attributes(builder)#

Register SolverVBD custom Model attributes.

Currently registers:
  • vbd:max_solver_iterations for NewtonSceneAPI.newton:maxSolverIterations.

  • vbd:rigid_contact_history, vbd:rigid_joint_linear_ke, vbd:rigid_joint_angular_ke, vbd:rigid_joint_linear_kd and vbd:rigid_joint_angular_kd for the NewtonVbdSceneAPI solver settings, authored in USD as newton:vbd:rigid:contactHistory, newton:vbd:rigid:jointLinearStiffness, ...jointAngularStiffness, ...jointLinearDamping and ...jointAngularDamping. SolverVBD uses them as defaults: explicit constructor argument > model.vbd.* > built-in default. Scene-authored stiffness and damping must be finite and non-negative.

  • vbd:joint_is_hard for per-joint hard/soft constraint mode (non-rod joints)

  • vbd:dahl_eps_max and vbd:dahl_tau for optional rod angular Dahl friction

Attributes are declared in the vbd namespace so they can be authored in scenes. The Dahl and joint_is_hard attributes are authored in USD as newton:vbd:<attr>; the solver settings above use the names listed there.

Dahl rod friction is enabled per joint only where both model.vbd.dahl_eps_max and model.vbd.dahl_tau are authored positive; the attributes default to zero.

Parameters:

builder (ModelBuilder) – Model builder to register attributes on.

__init__(model, *, iterations=None, friction_epsilon=1e-2, soft_contact_damping_bidirectional=True, dat_conservative_bound_relaxation=0.85, integrate_with_external_rigid_solver=False, particle_enable_self_contact=False, particle_self_contact_radius=None, particle_self_contact_margin=None, particle_self_contact_gap=None, particle_conservative_bound_relaxation=None, particle_vertex_contact_buffer_size=32, particle_edge_contact_buffer_size=64, particle_collision_detection_interval=None, particle_edge_parallel_epsilon=1e-5, particle_enable_tile_solve=True, particle_topological_contact_filter_threshold=2, particle_rest_shape_contact_exclusion_radius=0.0, particle_external_vertex_contact_filtering_map=None, particle_external_edge_contact_filtering_map=None, rigid_compliant_alm=True, rigid_avbd_alpha=None, rigid_avbd_joint_alpha=None, rigid_avbd_contact_alpha=None, rigid_avbd_beta=None, rigid_avbd_linear_beta=None, rigid_avbd_angular_beta=None, rigid_avbd_gamma=0.999, rigid_contact_hard=None, rigid_contact_history=None, rigid_contact_k_start=None, rigid_contact_damping_bidirectional=False, rigid_body_contact_buffer_size=64, rigid_body_particle_contact_buffer_size=256, rigid_soft_contact_use_log_barrier=False, rigid_joint_linear_ke=None, rigid_joint_angular_ke=None, rigid_joint_linear_k_start=None, rigid_joint_angular_k_start=None, rigid_joint_linear_kd=None, rigid_joint_angular_kd=None, tendon_max_sweeps=256, tendon_settle_tol=1.0e-3, tendon_settle_atol=1.0e-6, tendon_activation_tol=2.0e-3, rigid_soft_enable_dat=False, rigid_soft_dat_use_interval_arithmetic=False, deterministic=None, enable_fast_math=True, collision_pipeline=None, collision_frequency=None, collision_frequency_type=None)#
Parameters:
  • model (Model) – The Model object used to initialize the integrator. Must be identical to the Model object passed to the step function.

  • iterations (int | None) – Number of VBD iterations per step. If None, use the registered newton:maxSolverIterations scene value, or 10 if absent or -1. Explicit values override the scene.

  • friction_epsilon (float) – Threshold to smooth small relative velocities in friction computation (used for both particle and rigid body contacts).

  • soft_contact_damping_bidirectional (bool) – Whether the damping of soft contacts (soft self-contact, driven by Model.soft_contact_kd, and rigid-soft contacts, driven by the mean of Model.soft_contact_kd and the shape’s Model.shape_material_kd) also acts while a contact separates. With the default True the normal damper and its Hessian act while the gap closes and while it opens, for every contact that is still within its contact radius. The self-contact damper acts on the change of the contact distance over the substep, so a rigid motion of a contact pair gives no damping force. With False the damper acts only while the gap closes and leaves the rebound undamped; in the card stack of example_cloth_poker_cards (issue #3494) that stack kept bouncing, and a larger soft_contact_kd made it worse. Two-sided damping also opposes the opening motion, so it can delay release, and a large soft_contact_kd can hold a separating pair briefly.

  • dat_conservative_bound_relaxation (float) – Relaxation factor in (0, 1) shared by every Divide-and-Truncate (DAT) truncation: soft self-contact (particle_enable_self_contact) and rigid-soft (rigid_soft_enable_dat). It scales every truncation scalar, and each side’s motion budget between detections is 0.5 x relaxation x the detection query radius.

  • integrate_with_external_rigid_solver (bool) – Indicator for coupled rigid body-cloth simulation. When set to True, the solver assumes rigid bodies are integrated by an external solver (one-way coupling).

  • particle_enable_self_contact (bool) – Whether to enable self-contact detection for particles. Requires an active soft self-contact collision schedule: CollisionFrequencyType.NONE for the SOFT_SELF_CONTACT slot raises at step(), because the self-contact trajectory reference is refreshed only when detection runs.

  • particle_self_contact_radius (float | None) – Deprecated; use particle_self_contact_margin + particle_self_contact_gap instead. When set, the legacy interpretation applies exactly: radius = interaction distance and particle_self_contact_margin = detection query radius.

  • particle_self_contact_margin (float | None) – Self-contact interaction distance [m] — the surface offset at which vertex-triangle and edge-edge pairs start to interact. Defaults to 0.2. (Legacy meaning — the detection query radius — applies only while the deprecated particle_self_contact_radius is set or when particle_self_contact_gap is omitted.)

  • particle_self_contact_gap (float | None) – Additional detection-only distance [m]; self-contact detection queries use margin + gap, mirroring the ShapeConfig.margin / gap convention. Defaults to 0. Give it ~0.5-1x the margin of slack to avoid missing contacts.

  • particle_conservative_bound_relaxation (float | None) –

    Deprecated; use dat_conservative_bound_relaxation instead. When set, it overrides dat_conservative_bound_relaxation.

    Deprecated since version 1.7.

  • particle_vertex_contact_buffer_size (int) – Preallocation size for each vertex’s vertex-triangle collision buffer. Pairs beyond this capacity are silently dropped during detection.

  • particle_edge_contact_buffer_size (int) – Preallocation size for each edge’s edge-edge collision buffer. Pairs beyond this capacity are silently dropped during detection.

  • particle_collision_detection_interval (int | None) – Deprecated; use the self-contact slot of collision_frequency / collision_frequency_type instead. Controls how frequently particle self-contact detection is applied during the simulation. If set to a value < 0, collision detection is only performed once before the initialization step. If set to 0, collision detection is applied twice: once before and once immediately after initialization. If set to a value n >= 1, collision detection is applied before every n VBD iterations.

  • particle_edge_parallel_epsilon (float) – Threshold to detect near-parallel edges in edge-edge collision handling.

  • particle_enable_tile_solve (bool) – Whether to accelerate the particle solver using tile API. The tiled kernel is specialized once at construction from the model’s element materials (e.g. a tetrahedra-only model compiles without triangle/edge code paths); rebuild the solver after changing triangle or edge stiffness.

  • particle_topological_contact_filter_threshold (int) – Maximum topological distance (measured in rings) under which candidate self-contacts are discarded. Set to a higher value to tolerate contacts between more closely connected mesh elements. Only used when particle_enable_self_contact is True. Note that setting this to a value larger than 3 will result in a significant increase in computation time.

  • particle_rest_shape_contact_exclusion_radius (float) – Additional world-space distance threshold for filtering topologically close primitives. Candidate contacts with a rest separation shorter than this value are ignored. The distance is evaluated in the rest configuration conveyed by model.particle_q. Only used when particle_enable_self_contact is True.

  • particle_external_vertex_contact_filtering_map (dict | None) – Optional dictionary used to exclude additional vertex-triangle pairs during contact generation. Keys must be vertex primitive ids (integers), and each value must be a list or set containing the triangle primitives to be filtered out. Only used when particle_enable_self_contact is True.

  • particle_external_edge_contact_filtering_map (dict | None) – Optional dictionary used to exclude additional edge-edge pairs during contact generation. Keys must be edge primitive ids (integers), and each value must be a list or set containing the edges to be filtered out. Only used when particle_enable_self_contact is True.

  • rigid_compliant_alm (bool) –

    Unified compliant-ALM mode for body-body contacts, structural joints, drives, and limits. This is the default path. Defaults to True. Pass False to keep the deprecated legacy penalty/AVBD path during its migration window. Finite authored coefficients define the material response, while SolverVBD selects rho internally for numerical conditioning. Values used with legacy hard constraints may require retuning for the desired deformation. Values must be finite and representable in float32; infinity is unsupported.

    Explicit non-None values for deprecated beta, hard-contact, or penalty-seed controls emit a NewtonDeprecationWarning.

    Deprecated since version 1.6: The legacy path selected by False will be removed in a future release. Explicit False emits a NewtonDeprecationWarning for all models. Use the default compliant ALM path instead.

  • rigid_avbd_alpha (float | None) – C0 stabilization strength (C_stab = C - alpha * C0). Range: [0, 1]. Controls both joints and body-body contacts when neither class-specific override (rigid_avbd_joint_alpha / rigid_avbd_contact_alpha) is set. None leaves mode defaults: compliant ALM uses 0.0 for both constraint classes (the raw material residual), while the legacy path uses 0.95.

  • rigid_avbd_joint_alpha (float | None) – Joint-specific alpha override. None falls back to rigid_avbd_alpha when set, otherwise to 0.0 under compliant ALM or 0.95 on the legacy path.

  • rigid_avbd_contact_alpha (float | None) – Body-body contact alpha override. None falls back to rigid_avbd_alpha when set, otherwise 0.0 under compliant ALM (authored contact stiffness applies to the raw residual) or 0.95 on the legacy path. Under compliant ALM, alpha is stabilization only; retention is set separately by rigid_avbd_gamma.

  • rigid_avbd_beta (float | None) –

    Legacy AVBD penalty ramp rate per iteration. None (default) uses 0, disabling ramping (fixed-k). Set to e.g. 1e5 for ramping. Used for both linear and angular constraints unless overridden. Does not tune the internal compliant-ALM rho for converted rigid rows. Note: linear (meters) and angular (radians) constraints have different units, so the overrides should be used for production tuning.

    Deprecated since version 1.6: Penalty ramping is deprecated for all uses. Body-particle contacts continue to honor this control during migration; keep the effective beta at 0 (the default behavior) and author fixed material stiffness instead.

  • rigid_avbd_linear_beta (float | None) –

    Legacy linear beta override for linear constraints (meters). None (default) uses rigid_avbd_beta. Does not tune compliant-ALM rho.

    Deprecated since version 1.6: Penalty ramping is deprecated for all uses. Body-particle contacts continue to honor this control during migration; keep the effective beta at 0 (the default behavior) and author fixed material stiffness instead.

  • rigid_avbd_angular_beta (float | None) –

    Legacy angular beta override for angular constraints (radians). None (default) uses rigid_avbd_beta. Does not tune compliant-ALM rho.

    Deprecated since version 1.6: Penalty ramping is deprecated. Keep the effective beta at 0 (the default behavior) and author fixed material stiffness instead.

  • rigid_avbd_gamma (float) – Per-step decay factor for penalty k and persisted lambda. Compliant ALM joints, particle-body attachments, and validated contacts retain lambda by gamma; the legacy path retains joint and contact lambda by alpha * gamma. Lower values discard history faster.

  • rigid_contact_hard (bool | None) –

    Legacy body-body contact hard/soft mode. With rigid_compliant_alm=True, contacts use the ALM path. With rigid_compliant_alm=False, True selects legacy hard AVBD contact and False selects legacy penalty-only contact. None (default) uses True.

    Deprecated since version 1.6: Use the default compliant ALM path and author finite contact stiffness.

  • rigid_contact_history (bool | None) – Whether to persist body-body numeric contact state across steps using Contacts.rigid_contact_match_index. Compliant ALM restores the normal multiplier for matched rows. With latest matching it also restores projected tangential multipliers as a numerical warm start; with sticky matching, tangential memory is represented by the collision pipeline’s replayed material anchor. Legacy hard contacts restore the full multiplier; legacy soft contacts restore penalty k only. History is restored only from the contact set the previous step solved: a step on the same contacts (no collision pass in between) restores each contact from itself, and the match indices are used only when Contacts.rigid_contact_match_generation reports that they refer to that set. Contacts start cold after a collision pass into another buffer, two passes between steps, or with another buffer. Contact geometry remains owned by the collision pipeline. Requires CollisionPipeline(contact_matching="latest") or "sticky". Ignored when integrate_with_external_rigid_solver=True or model.body_count == 0. During graph capture, construct the collision pipeline before SolverVBD so history is pre-allocated, or run one uncaptured solver step before capture. None (default) reads model.vbd.rigid_contact_history when registered, otherwise uses False.

  • rigid_contact_k_start (float | None) –

    Body-body and body-particle contact penalty seed for legacy AVBD ramping [N/m]. Used when rigid_avbd_linear_beta (or rigid_avbd_beta fallback) is greater than zero. When the linear beta is 0, k is fixed at the contact stiffness regardless of this value. None (default) uses 100.0.

    Deprecated since version 1.6: Penalty ramping is deprecated for all uses. Body-particle contacts continue to honor this control during migration; keep the effective beta at 0 (the default behavior) and author fixed contact stiffness instead.

  • rigid_contact_damping_bidirectional (bool) – Whether body-body contact damping, whose coefficient is the mean of the two shapes’ kd, also acts while the bodies separate. The default False keeps the closing-only damping of rigid contacts (while the row carries load). True also damps the rebound, so rigid contacts return less of the approach speed.

  • rigid_body_contact_buffer_size (int) – Max body-body contacts per rigid body for per-body contact lists.

  • rigid_body_particle_contact_buffer_size (int) – Capacity of the per-body soft-contact adjacency list, including currently ineligible mesh candidates. If a body’s list overflows, force evaluation and coupling feedback scan all stored soft contacts for that body instead. This preserves reactions but can increase evaluation cost.

  • rigid_soft_contact_use_log_barrier (bool) – Whether body-particle normal contacts use the same C2 log-barrier law as particle self-contact. When False (default), they use the legacy quadratic penalty law.

  • rigid_joint_linear_ke (float | None) – Material stiffness for non-rod structural linear joint slots [N/m]. None (default) reads model.vbd.rigid_joint_linear_ke when registered, otherwise uses 1.0e5. A scene-authored value must be finite and non-negative, and applies with and without rigid_compliant_alm.

  • rigid_joint_angular_ke (float | None) – Material stiffness for non-rod structural angular joint slots [N·m/rad]. None (default) reads model.vbd.rigid_joint_angular_ke when registered, otherwise uses 1.0e5. A scene-authored value must be finite and non-negative, and applies with and without rigid_compliant_alm.

  • rigid_joint_linear_k_start (float | None) –

    Linear penalty seed for legacy AVBD ramping [N/m]. Used when rigid_avbd_linear_beta (or rigid_avbd_beta fallback) is greater than zero. When the linear beta is 0, k is fixed at the joint stiffness regardless of this value. None (default) uses 100.0.

    Deprecated since version 1.6: Penalty ramping is deprecated. Keep the effective beta at 0 (the default behavior) and author fixed joint stiffness instead.

  • rigid_joint_angular_k_start (float | None) –

    Angular penalty seed for legacy AVBD ramping [N·m/rad]. Used when rigid_avbd_angular_beta (or rigid_avbd_beta fallback) is greater than zero. When the angular beta is 0, k is fixed at the joint stiffness regardless of this value. None (default) uses 10.0.

    Deprecated since version 1.6: Penalty ramping is deprecated. Keep the effective beta at 0 (the default behavior) and author fixed joint stiffness instead.

  • rigid_joint_linear_kd (float | None) – Damping coefficient for non-rod linear joint constraints [N·s/m]. None (default) reads model.vbd.rigid_joint_linear_kd when registered, otherwise uses 0.0. Negative explicit values are clamped to 0; a scene-authored value must be finite and non-negative. Scene values apply with and without rigid_compliant_alm.

  • rigid_joint_angular_kd (float | None) – Damping coefficient for non-rod angular joint constraints [N·m·s/rad]. None (default) reads model.vbd.rigid_joint_angular_kd when registered, otherwise uses 0.0. Negative explicit values are clamped to 0; a scene-authored value must be finite and non-negative. Scene values apply with and without rigid_compliant_alm.

  • rigid_soft_enable_dat (bool) –

    Whether to apply Divide-and-Truncate (DAT) penetration-free truncation to rigid-soft contacts, symmetric to particle self-contact truncation. Each rigid-soft contact reported by the owned collision pipeline defines a division plane at its detection-time reference configuration; rigid pose updates and particle displacements are jointly truncated so neither side crosses it. Rigid rotation makes trajectories curved, so crossing times are found by sampling and bisection, optionally certified with interval arithmetic. Full-surface mesh contacts use complete VT/TV/EE primitives through Contacts.soft_contact_rigid_indices; every corner stays on its assigned side of a certified separating plane. Requires a solver-owned pipeline (collision_pipeline=); each side’s motion budget between detections is 0.5 x dat_conservative_bound_relaxation x the minimum rigid-soft query radius, where a query radius is the pipeline gap plus the soft feature radius and rigid shape margin. With collision_frequency_type AUTO, the rigid slot resolves to PRE_POST_INIT (detect before and right after initialization), matching the self-contact slot; raise the detection frequency (ITERATIONS) to widen the per-step motion budget for fast bodies. Kinematic bodies move outside the solver and are not truncated. The motion budget is fixed at construction from the pipeline’s soft_contact_gap and the model’s particle radii and shape margins; later changes to those values are not reflected. While enabled, the isotropic budget bounds the per-detection motion of every dynamic body and particle in the model, not only those near soft geometry.

    Full-surface mesh coverage includes every mesh edge and nearby VT/TV/EE pair, including rows ineligible for contact forces. It requires initially separated geometry; topology, nonzero mesh scales, radii, and motion bounds must remain unchanged. Analytic and penetration-recovery contacts retain their point-anchor behavior; this is not a general initial-penetration recovery method. When no separating plane can be certified, the affected primitives freeze. Contact overflow makes DAT coverage incomplete; stop the simulation and increase contact capacity if an overflow is reported. Sampling and bisection alone can miss a rigid trajectory that crosses and returns between samples; enable the experimental interval path to certify the complete rigid trajectory.

  • rigid_soft_dat_use_interval_arithmetic (bool) –

    Selector for rigid DAT trajectory truncation. False uses sampling and bisection, plus an interval derivative bound that releases rows starting inside the empty band only when their signed distance is certified nonincreasing; True additionally certifies the whole sampled prefix with interval arithmetic to detect crossings between sample points. Only used when rigid_soft_enable_dat is True.

    Experimental

    The Stage-2 prefix certification is experimental and may change or be removed; the derivative bound is part of the default path.

  • deterministic (DeterministicMode | None) – Opt-in determinism for this solver’s atomic-emitting kernel modules. Pass a warp.DeterministicMode, or None (default) to inherit the current wp.config.deterministic mode. Body-particle contact forces are accumulated per contact record with atomics in NOT_GUARANTEED mode and gathered per particle without atomics in every other mode.

  • enable_fast_math (bool) – Compile the particle kernels, the rigid body solve, and the joint dual update with Warp’s fast_math (approximate division and square root). Results change at the floating-point rounding level. Ignored on CPU.

  • tendon_max_sweeps (int) – Maximum capstan material-relaxation sweeps per VBD iteration.

  • tendon_settle_tol (float) – Relative final capstan-force residual tolerance [dimensionless]. Zero disables early-out; must be finite and nonnegative.

  • tendon_settle_atol (float) – Absolute final capstan-force residual tolerance [N]. Combined with the relative tolerance; must be finite and nonnegative. Ignored when tendon_settle_tol is zero.

  • tendon_activation_tol (float) – Relative radius gap that inactive dynamic rollers must cross before activation.

  • collision_pipeline (CollisionPipeline | None) – Optional CollisionPipeline owned by this solver. When given, the solver allocates its own contacts buffer (contacts), seeds the pipeline’s self-contact configuration from the particle_self_contact_* parameters, and runs rigid collision detection itself per the schedule below; step() must then receive contacts=None.

  • collision_frequency (Mapping[CollisionSlot, int] | None) – Iteration frequencies keyed by SolverBase.CollisionSlot; only used by ITERATIONS slots (before iterations k, 2k, and so on).

  • collision_frequency_type (Mapping[CollisionSlot, CollisionFrequencyType] | None) – In-step detection points keyed by SolverBase.CollisionSlot; runtime-changeable via SolverBase.set_collision_frequency(). A slot whose DAT family is active may not be NONE (RIGID with rigid_soft_enable_dat, SOFT_SELF_CONTACT with particle_enable_self_contact); step() raises otherwise.

Note

  • The integrate_with_external_rigid_solver argument enables one-way coupling between rigid body and soft body solvers. If set to True, the rigid states should be integrated externally, with state_in passed to step representing the previous rigid state and state_out representing the current one. Frictional forces are computed accordingly.

  • particle_vertex_contact_buffer_size, particle_edge_contact_buffer_size, rigid_body_contact_buffer_size, and rigid_body_particle_contact_buffer_size are fixed and will not be dynamically resized during runtime. Setting them too small may result in undetected collisions (particles) or contact overflow (rigid body contacts). Body-particle list overflow uses the complete stored soft-contact buffer as a fallback; it cannot recover records lost to overflow of that collision buffer itself. Setting them excessively large may increase memory usage and degrade performance.

  • Dahl hysteresis friction for rod angular response is controlled by custom model attributes model.vbd.dahl_eps_max and model.vbd.dahl_tau. Register them with SolverVBD.register_custom_attributes before building the model. Dahl friction is enabled only when positive Dahl parameters are authored.

collect_rigid_contact_forces(body_q, body_q_prev, contacts, dt)#

Collect per-contact rigid contact forces and world-space application points.

Parameters:
  • body_q (wp.array[wp.transformf]) – Current body transforms (world frame), typically state_out.body_q after a step() call.

  • body_q_prev (wp.array[wp.transformf]) – Effective previous-pose history used by the step (world frame). Snapshot solver.body_q_prev before step() (it is advanced after the step). On a first or reset step, overwrite each rebaselined row with that step’s input body_q so its reported force matches the solve. For externally integrated bodies, pass the external solver’s previous transforms.

  • contacts (Contacts | None) – Contact data buffers containing rigid contact geometry/material references. If None, the function returns default zero/sentinel outputs.

  • dt (float) – Time step size [s].

Note

Call after collision generation and step() with the same Contacts buffer. If rigid contact state is absent or undersized, this returns sentinel/zero outputs without growing output buffers. Output buffers persist and grow on demand; they do not shrink, so iterate up to the returned rigid_contact_count rather than the array length.

Returns:

tuple[

wp.array[wp.int32], wp.array[wp.int32], wp.array[wp.vec3], wp.array[wp.vec3], wp.array[wp.vec3], wp.array[wp.int32],

]: Tuple of per-contact outputs:
  • body0: Body index for shape0, int32.

  • body1: Body index for shape1, int32.

  • point0_world: World-space contact point on body0, wp.vec3 [m].

  • point1_world: World-space contact point on body1, wp.vec3 [m].

  • force_on_body1: Contact force applied to body1 in world frame, wp.vec3 [N].

  • rigid_contact_count: Length-1 active rigid-contact count, int32.

Return type:

tuple[wp.array[wp.int32], wp.array[wp.int32], wp.array[wp.vec3f], wp.array[wp.vec3f], wp.array[wp.vec3f], wp.array[wp.int32]]

coupling_harvest_proxy_particle_forces(particle_local_to_proxy_global, out_particle_f, *, particle_qd_before, state, state_out, contacts, dt)#

Harvest contact-only proxy-particle forces.

As for proxy-body harvest, this stays contact-based because VBD allows some proxy interaction inside the destination solve for stronger coupling, but those proxy-only interactions should not appear as feedback forces on the source side.

coupling_harvest_proxy_wrenches(body_local_to_proxy_global, out_body_f, *, body_qd_before, state, state_out, contacts, dt)#

Harvest contact-only proxy-body wrenches.

VBD deliberately does not rely on the default momentum harvest here. The generic proxy path filters proxy-vs-proxy and proxy-vs-static rigid contacts so harvested momentum only reflects coupling-relevant interactions. VBD relaxes that restriction because allowing some proxy interaction inside the destination solve can strengthen the coupled solve. Those extra interactions still must not feed back through the coupling interface, so VBD harvests explicit contact forces instead of inferring feedback from total proxy momentum change.

coupling_notify_input_state_update(state, flags, *, iteration_restart=False, dt=0.0)#

Convert input body pose updates into VBD-compatible history updates.

Also snapshots the input particle pose, which the coupling harvest hooks pair with the body pose history over the whole coupled entry step (all substeps).

coupling_prepare_proxy_contacts(state, contacts, *, contacts_freshly_detected=False)#

Update rigid history cadence for proxy contacts.

coupling_supports_full_surface_soft_contacts()#
coupling_supports_inertial_property_refresh()#
notify_model_changed(flags)#

Refresh solver caches affected by the specified model-property flags.

rebuild_bvh(state)#

This function will rebuild the BVHs used for detecting self-contacts using the input state.

When the simulated object deforms significantly, simply refitting the BVH can lead to deterioration of the BVH’s quality. In these cases, rebuilding the entire tree is necessary to achieve better querying efficiency.

Parameters:

state (newton.State) – The state whose particle positions (particle_q) will be used for rebuilding the BVHs.

reset(state, world_mask=None, flags=None)#

Reset rigid solver history and optional body and particle state for selected worlds.

Body fields selected by flags are copied from the model defaults. Joint penalty is restored to its minimum; joint C0 and AVBD dual history is zeroed immediately. Pose and enabled-rod friction history (curvature, stress, and increment) are rebaselined together from the next step() input pose, after any intervening state edits or forward kinematics. Mutable tendon material and routing state is restored to its initialized values regardless of flags. REVOLUTE and one-axis-D6 winding re-anchors to the pose-equivalent angle nearest the authored rest coordinate; a pose alone cannot restore an independently intended multi-turn coordinate. Selected-world contact warm-start is cold-started when fresh rigid contacts are next processed. Internal rigid history is reset regardless of flags. When an external solver integrates the bodies, reset performs no rigid mutation; state and world_mask validation and particle reset still apply, but body State arrays are not accessed or validated.

BODY_Q / BODY_QD copy model.body_q / model.body_qd into state; they do not restore a previously supplied state. A requested field is skipped if its state array is None. If your initial pose differs from the model defaults, pass flags=0 and author the pose any time before the next step; reset then preserves it and only clears VBD history. JOINT_Q / JOINT_QD are ignored (VBD uses maximal body_q / body_qd); to reset from joint coordinates, run eval_fk() after reset so the resulting body_q supersedes reset’s model copy.

PARTICLE_Q / PARTICLE_QD likewise copy model.particle_q / model.particle_qd into state for particles in the selected worlds, using the same masking as the body fields (world_mask=None also restores global world == -1 particles; an explicit mask restores globals only through its final entry). One path covers both cloth and volumetric (tet) soft bodies, and it runs even when an external solver integrates the bodies or the model has none. A requested particle field is skipped if its state array is None. Particle and body-particle solver history is intentionally left untouched except for retained attachment multipliers, which are cleared in selected worlds. particle_q_prev is rebaselined from the incoming state at the start of the next step(), self-contact and body-particle contacts rebuild per step, and tet/cloth elasticity is stateless, so no particle history cold-start is required. Reset does not refresh the particle self-contact BVH; the next step() refits it from the incoming positions. After a large reset displacement, call rebuild_bvh() to restore acceleration-structure quality. Both reset and rebuild_bvh() are graph-capturable, so either may run inside a captured episode-reset graph.

Reset does not run collision detection, and step() consumes the supplied contacts rather than rerunning collide(). After moving bodies or particles, regenerate contacts so stale soft contacts are not reused, and let the next step() refresh rigid contact state. The next rigid step() consumes the pose and rod rebaseline even when contacts=None, so author the final pose (or run eval_fk()) before stepping; contact invalidation instead waits for a fresh refresh. VBD cold-starts its numeric contact state for reset-selected worlds. Frame-to-frame correspondence and sticky contact geometry remain owned by CollisionPipeline; after a discontinuous episode reset, call reset_contact_matching() with the same world mask to discard that history. Reset does not change set_rigid_history_update(); leave rigid history refresh enabled for the next contact-bearing step. Reusing contacts (set_rigid_history_update(False)) is unsupported only while contact invalidation is still pending.

Parameters:
  • state (newton.State) – The simulation state to reset (modified in place).

  • world_mask (wp.array[wp.bool] | None) – One-dimensional Warp boolean mask on the solver device. Shape (world_count + 1,), with the final entry selecting entities in global world -1. None selects all local and global entities.

  • flags (StateFlags | int | None) – StateFlags (or int) selecting which body and particle fields to copy from the model defaults. VBD honors BODY_Q, BODY_QD, PARTICLE_Q, and PARTICLE_QD; None requests all flags.

set_joint_constraint_mode(joint_index, hard, slot=None)#

Set legacy hard/soft mode for a joint’s structural slots at runtime.

Deprecated since version 1.6: Per-slot joint hard/soft mode is deprecated. Under compliant ALM (the default) all structural slots use the unified scheme, so this has no solver-mode effect; it will be removed with the legacy path.

Non-rod structural slots are LINEAR (slot 0) and ANGULAR (slot 1). Builder-created rod joints expose STRETCH (slot 0), SHEAR (slot 1), BEND (slot 2), and TWIST (slot 3). Other drive/limit slots are always soft and cannot be set to hard.

By default, rod stretch, shear, bend, and twist slots are soft, while non-rod structural slots are hard.

For non-rod joints, hard/soft mode can also be authored per joint at build time via the vbd:joint_is_hard custom attribute, avoiding a runtime set_joint_constraint_mode() call:

SolverVBD.register_custom_attributes(builder)  # before adding joints
builder.add_joint_fixed(..., custom_attributes={"vbd:joint_is_hard": 0})
model = builder.finalize()
solver = SolverVBD(model, rigid_compliant_alm=False)
Parameters:
  • joint_index (int) – Index of the joint to modify.

  • hard (bool) – In legacy mode, True selects hard AL mode and False selects soft penalty mode. Has no solver-mode effect under compliant ALM.

  • slot (int | None) – Specific slot index to set. If None, sets all structural slots. Use JointSlot.LINEAR / JointSlot.ANGULAR for non-rod joints, or JointSlot.STRETCH / JointSlot.SHEAR / JointSlot.BEND / JointSlot.TWIST for rod joints.

Raises:

ValueError – If the joint index is out of range or the slot is not a structural slot for this joint.

set_rigid_history_update(update)#

Set whether the next step() should update rigid solver history.

When True (default), the step refreshes rigid contact state from the provided Contacts buffer: rebuilds per-body contact lists, initializes penalty_k/lambda/C0, and restores warm-start state when contact history is enabled (from each contact’s own state when Contacts was not collided since the previous step, otherwise through Contacts.rigid_contact_match_index). When False, the step reuses the current rigid contact lists and records without reinitializing them. Pass the same contact result/buffers used by the previous refresh; do not run collision into the contacts buffer between refreshes. Passing newly collided contacts while update is disabled can mismatch stale per-body contact lists with current contact rows. For the same reason, do not change a body’s solvability (mass or kinematic flag) while update is disabled: the per-body lists depend on effective inverse mass and are not rebuilt until the next refresh. The per-particle contact adjacency is frozen with the same contract: its node ids index the contact buffer of the previous refresh, so passing a re-collided or smaller-capacity buffer while update is disabled reads the wrong records (or out of bounds).

Joint constraint maintenance (C0 snapshot, lambda retention/decay, and automatic rho refresh) runs every step regardless of this flag via step_joint_C0_lambda_rho(). Rigid contact history snapshotting also runs every step when enabled. Per-step contact maintenance (C0, conditioning, penalty, and lambda retention) also runs on the reused contact records.

This setting applies only to the next call to step() and is then reset to True. Useful for substepping where collision detection frequency differs from the simulation step frequency.

Parameters:

update (bool) – If True, update rigid solver state. If False, reuse previous.

step(state_in, state_out, control, contacts, dt, *, observables=None)#

Execute one simulation timestep using VBD (particles) and AVBD (rigid bodies).

The solver follows a 3-phase structure: 1. Initialize: Forward integrate particles and rigid bodies, detect collisions, initialize contact state 2. Iterate: Interleave particle and rigid-body VBD iterations 3. Finalize: Update velocities and persistent state (Dahl friction)

To control rigid body substepping behavior, call set_rigid_history_update(). When True (default), the step rebuilds rigid contact lists, re-initializes rigid contact state (penalty_k, lambda, C0), and restores from history if enabled. When False, reuses previous rigid contact state. The flag is reset to True when consumed.

Parameters:
  • state_in (newton.State) – Input state.

  • state_out (newton.State) – Output state.

  • control (Control) – Control inputs.

  • contacts (Contacts | None) – Contact data produced by collide() (rigid-rigid and rigid-particle contacts), allocated with contacts(). If None, rigid contact handling is skipped. Note that particle self-contact (if enabled) does not depend on this argument.

  • dt (float) – Time step size.

  • observables (Observables | None) – Optional solver observable arrays allocated by observables(). When CONTACT_F is requested, evaluate one wrench per body-body and rigid-soft contact record at the final configuration. See Contact forces from SolverVBD.

Raises:

RuntimeError – If required rigid contact-matching data is unavailable, or contact-history storage would need to be allocated or grown during graph capture.

SUPPORTED_OBSERVABLES: ClassVar[frozenset[str]] = frozenset({'contact_f'})#

Names of observables supported by this solver class.

Entries are string identifiers, such as ObservableKind.BODY_QDD. Query supported_observables for the capabilities of an instance.

supports_collision_pipeline: bool = True#

Whether this solver can own a CollisionPipeline and drive detection itself.

Currently only SolverVBD opts in; passing collision_pipeline to any other solver raises ValueError (drive detection externally instead).