Skip to content

Velocity iterations

19 · Impacts and penetration

Boxes spawned 2 cm into the ground. Steel corrects penetration through a velocity bias β·φ/h, and that bias becomes real velocity. With β = 0.8 the box is launched. velocity_iterations re-solve the velocities without the bias after the positions are integrated, so the error is still removed but the box is not thrown.

0.00 s

Cost

Time per step, CUDA graph replay, median of 7 interleaved rounds on one NVIDIA RTX PRO 6000 Blackwell Server Edition; ratios are against the first row. Solver allocation is the GPU memory the solver allocates at construction, before warm-up and graph capture. Scene: the 8-box stack in each world, 8 steps of 1/240 s per frame.

Option1 world64 worlds1024 worldssolver allocation, 1024 worldsresult in this tutorial
velocity_iterations=01.03 ms1.21 ms1.64 ms215 MBβ 0.8: box hops 74 cm
velocity_iterations=41.27 ms 1.24×1.47 ms 1.22×2.00 ms 1.22×217 MBβ 0.8: no hop

What to look at

  • baumgarte 0.2: a small hop of about 3 cm.
  • baumgarte 0.8: the box flies about 70 cm.
  • baumgarte 0.8 with 4 velocity iterations: no hop at all.

Key parameter

velocity_iterations

Velocity-only PGS iterations after the position solve. Default 0. baumgarte is the fraction of penetration removed per step, default 0.2.

Try changing

  • Use velocity_iterations=1.
  • Spawn the boxes 5 cm deep.

Concept

Baumgarte stabilization and velocity iterations

What it is

Penetration φ is corrected with a velocity bias of β·φ/h, removing a fraction β of the error per step. That bias becomes real velocity. velocity_iterations re-solve the velocities without the bias after positions are integrated, so the error is removed without leaving velocity behind.

target = −β · φ / h

In this scene

A box 2 cm deep hops 29 mm at β = 0.2 and 74 cm at β = 0.8; four velocity iterations remove the hop.

Considerations

Cost
4 velocity iterations add about 25%: 1.0 → 1.3 ms per step in one world, 1.6 → 2.0 ms in 1024.
Interactions
Not allowed with sleeping, contact compliance or contact torsion; disables pre-elimination.
Use it when
Scenes with overlap at reset, or a raised β.

The script

cd tutorials && python 19_velocity_iterations.py prints each variant's final metric; --record out.json also writes the replay. Every tutorial imports replay.py from the same folder.

tutorials/19_velocity_iterations.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Penetration recovery: Baumgarte bias adds velocity; velocity_iterations remove it."""
import newton
import warp as wp
from newton.solvers import SolverSteel
from replay import Recorder
DT = 1.0 / 240.0
SUBSTEPS = 8
def build():
builder = newton.ModelBuilder()
builder.add_ground_plane()
for i in range(3):
# each box starts 2 cm into the ground
box = builder.add_body(xform=wp.transform((0.25 * i - 0.25, 0.0, 0.03), wp.quat_identity()))
builder.add_shape_box(
box, hx=0.08, hy=0.08, hz=0.05, cfg=newton.ModelBuilder.ShapeConfig(mu=0.5)
)
return builder.finalize()
def simulate(rec, label, **options):
model = build()
solver = SolverSteel(model, **options)
pipeline = newton.CollisionPipeline(model)
contacts = pipeline.contacts()
state_0, state_1, control = model.state(), model.state(), model.control()
run = rec.variant(label, model)
for _ in range(rec.frames(1.5)):
run.frame(state_0, z_mm=1000.0 * (state_0.body_q.numpy()[1, 2] - 0.05))
for _ in range(SUBSTEPS):
pipeline.collide(state_0, contacts)
solver.step(state_0, state_1, control, contacts, DT)
state_0, state_1 = state_1, state_0
run.check(solver)
if __name__ == "__main__":
rec = Recorder()
simulate(rec, "baumgarte=0.2 (default)")
simulate(rec, "baumgarte=0.8", baumgarte=0.8)
simulate(rec, "baumgarte=0.8, velocity_iterations=4", baumgarte=0.8, velocity_iterations=4)
rec.save()

Recorded with SolverSteel on Newton 1.9.0.dev0, Warp 1.19.0, NVIDIA RTX PRO 6000 Blackwell Server Edition. Replays read the state back every frame, so they are not a speed benchmark. Script · page text