Newton Integration#
sap_with_newton.py demonstrates the direct Newton integration path. The
example builds a Newton cartpole model, displays it with Newton’s viewer, and
uses SolverSAP as the timestepper.
The integration is intentionally thin: Newton still owns the model, state, control, viewer, and optional collision buffers. SAP Warp wraps those arrays at the solver boundary and writes the next Newton state in place.
Run the Example#
Newton is an optional, on-demand dependency for this repository. Run the demo
with uv run --with so the package is available for this command without
adding it to pyproject.toml:
uv run --frozen --with 'newton[examples]' python sap_with_newton.py
The defaults are the interactive GL viewer, SAP as the solver backend, and one Newton world. The script builds the Newton cartpole model directly in Python rather than loading one of the repository YAML scene files.
Run a short headless smoke test:
uv run --frozen --with 'newton[examples]' python sap_with_newton.py \
--viewer null \
--num-frames 10
Run the same scene through Newton’s solver for comparison:
uv run --frozen --with 'newton[examples]' python sap_with_newton.py \
--viewer null \
--num-frames 10 \
--solver newton
Data Boundary#
The adapter functions in sim.sap_runtime turn Newton runtime objects into
SAP-facing containers:
sap_model_from_newton()wraps a Newton model as aSapModel.sap_state_from_newton()wraps a Newton state as aSapState.sap_control_from_newton()wraps a Newton control object as aSapControl.Newton contact buffers can be passed directly to
step(); the solver converts them withsap_contacts_from_newton()when needed.
These wrappers do not replace Newton as the owner of the simulation data. They
record how SAP should interpret the same Warp arrays, including public Newton
generalized velocity and force ordering. Before the solve, SolverSAP maps
public Newton ordering into SAP’s internal ordering; after integration, it maps
the output velocity back to the public state layout. The SAP runtime wrapper
data structures are based on
Newton’s model, state, control,
and contact container code so this adapter can stay thin and compatible with
Newton-owned arrays.
Minimal Pattern#
The core setup in sap_with_newton.py is:
model = create_newton_cartpole_model(args, device)
state_0 = model.state()
state_1 = model.state()
control = model.control()
contacts = model.contacts() if args.collision == "newton" else None
newton.eval_fk(model, model.joint_q, model.joint_qd, state_0)
state_1.assign(state_0)
sap_model = sap_model_from_newton(model)
solver = SolverSAP(
sap_model,
max_rigid_contact=args.contact_cap,
max_iterations=args.solver_iterations,
contact_tau_d=args.contact_tau_d,
contact_preset_variant=args.contact_preset,
line_search_variant=args.line_search,
)
sap_state_0 = sap_state_from_newton(state_0)
sap_state_1 = sap_state_from_newton(state_1)
sap_control = sap_control_from_newton(control)
newton.eval_fk initializes the body transforms from the Newton generalized
coordinates before SAP takes the first step. The two Newton states remain the
double buffers used by the viewer and by either solver backend.
Step Loop#
Each rendered frame can contain multiple simulation substeps. The SAP path keeps Newton’s normal frame structure:
state_0.clear_forces()
viewer.apply_forces(state_0)
if contacts is not None:
model.collide(state_0, contacts)
solver.step(sap_state_0, sap_state_1, sap_control, contacts, sim_dt)
sap_state_0, sap_state_1 = sap_state_1, sap_state_0
state_0, state_1 = state_1, state_0
The SAP state wrappers point at the same arrays as state_0 and state_1.
After every step, swap both wrapper buffers and Newton buffers together. Newton’s
viewer can then render state_0 without any copy back from SAP.
Contacts#
For the cartpole example, contacts are disabled by default because the model is useful as a pure articulated dynamics test:
uv run --frozen --with 'newton[examples]' python sap_with_newton.py --collision none
For contact experiments, ask Newton to allocate and refresh contacts:
uv run --frozen --with 'newton[examples]' python sap_with_newton.py --collision newton --contact-cap 128
--contact-cap is the per-world rigid-contact capacity passed to
SolverSAP. When using replicated Newton worlds, keep this value large enough
for the maximum active contacts in one world; the solver sizes the total contact
work from that per-world cap and the Newton model’s world count.
Solver Controls#
The example exposes the main SAP knobs without changing Newton’s outer loop:
--solver-iterationssets the maximum SAP contact solve iterations.--contact-presetselects the precision and contact Jacobian preset.--line-searchselects the SAP line-search variant.--contact-tau-dprovides the fallback contact dissipation time scale.--world-countreplicates the Newton cartpole model before conversion.
Use this pattern when you want Newton’s asset loading, replication, viewer, or interaction utilities, but want the SAP Warp solver to advance the articulated state.