Skip to content

dyn4: the torch twin of libcvc's four-wheel plant, with float64 parity against its golden - #123

Merged
transfix merged 1 commit into
mainfrom
feat/dyn4-twin
Oct 19, 2026
Merged

transfix merged 1 commit into
mainfrom
feat/dyn4-twin

Conversation

@transfix

@transfix transfix commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

This is the torch twin of libcvc's four-wheel plant: diff::dyn4_plant, P8d of the vehicle-physics roadmap, in transfix/libcvc#731. The trainers and cvcdbg get a body that rolls, pitches and loads each wheel from its own ground, differentiable in torch.

grl_snam/dyn4.py

  • dyn4_plant(p, lim, state, delta_cmd, a_cmd, ground) steps a (..., 14) state: (x, y, psi, vx, vy, r, z, zd, phi, phid, th, thd, delta, a). It is batched and runs on any device.
  • ground is a callable (wx, wy) -> (mu, height) on world positions, called once per wheel. Its gradients flow through autograd, as libcvc's sampler's flow through its duals. FlatGround(mu) is the default.
  • The model follows libcvc step for step:
    • the Ackermann front wheels (ackermann);
    • spring-damper corners, floored with softplus;
    • each wheel's drive or brake share saturated at mu_i Fz_i;
    • four tires in the implicit (vy, r) solve, each linearized by its secant;
    • heave, roll and pitch by an implicit 3x3 step, driven by the ground and the tire forces' moments;
    • P8a's low-speed blend and pose update.
  • The corners' rates reproduce libcvc's float arithmetic. With k = 0 the spring is stiff, at natural frequency 1/hdt; with c = 0 the damping is critical.
  • The helpers are shared with dyn_bike.py: _sat, _lateral, _secant_w, _clamp and PlantLimits.

Parity (tests/test_dyn4_parity.py)

The fixture tests/fixtures/dyn4_golden.json is libcvc's Dyn4.Golden at libcvc@368c0e4a, on #731's branch. It covers the Humvee with its springs and again with rigid corners, on a mu split with a round bump. The eleven cases are:

  • cruising, a hard corner, braking in a turn;
  • a wheel on the bump, braking across the split;
  • creeping, backing up, past the tires' peak, pulling away from rest;
  • two rigid-corner cases.

Each case records the next state and the VJP of a fixed output gradient.

check measured
next state against libcvc, float64 (gate 1e-9) worst relative 4.2e-17
VJP (state and both commands) against libcvc's forward-mode duals worst relative 1.7e-15
float32 on the same cases finite; 9.7e-8 next, 8.9e-7 VJP
CUDA, float64, against libcvc 1.1e-16 next, 1.1e-15 VJP
CUDA against CPU, float64 worst relative 6.6e-16 (RTX 4070 Laptop)
a batch of rows steps like each row exact to 1e-9
Ackermann: cot(right) - cot(left) = track / wheelbase to 1e-12
stopping from 20 m/s at mu 0.8 within 10% of v^2 / (2 mu g)

The tests ran locally with the cvcpkg Python's torch (CPU), and black and ruff are clean. The CUDA rows come from a one-off script run once with the DBG session's CUDA torch 2.8.0+cu128, which has no pytest.

The fixture tracks libcvc#731. If review there changes the plant, the golden is regenerated with CVC_DYN4_GOLDEN_WRITE=1 and copied here.

…y against its golden

grl_snam.dyn4.dyn4_plant is libcvc's diff::dyn4_plant (P8d, transfix/libcvc#731): four
contact patches with Ackermann front wheels, a ground callable (wx, wy) -> (mu, height) per
wheel, spring-damper corners floored by softplus, four tires in the implicit (vy, r) solve, and
heave, roll and pitch by an implicit 3x3 step. The corners' rates follow libcvc's float arithmetic.

tests/test_dyn4_parity.py holds it to libcvc's dyn4_golden.json (Dyn4.Golden, libcvc@368c0e4a):
the sprung and rigid Humvee on a mu split with a bump, eleven cases. Measured worst relative
error: next state 4.2e-17, VJP 1.7e-15 (the gate is 1e-9). Also a batch, the Ackermann relation,
and stopping from 20 m/s within 10% of v^2 / (2 mu g).
@transfix
transfix merged commit 5b8e21f into main Oct 19, 2026
5 checks passed
transfix added a commit to transfix/libcvc that referenced this pull request Oct 21, 2026
… (P8d) (#731)

diff::dyn4_plant (nav/detail/dyn_4w.h): P8a's single-track vehicle on its four wheels, with load transfer through per-corner springs and dampers, per-wheel friction and Ackermann steering, written over a scalar type with dyn4_plant_vjp on forward-mode duals. nav_plant::dyn4_si_of / dyn4_params_at derive it from a vehicle_desc on dyn_bike_si_of's Jolt-fitted defaults (#753). nav_dyn_4w_test gates the single-track limit, load transfer, mu-split braking, a bump, the VJP, the controller step, the envelopes against Jolt's Humvee, Ackermann, and dyn4_golden.json for the torch twin (CVC-Lab/GRL-SNAM#123, golden in #135).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant