Skip to content

nav: DynBikePH.Golden, the pH form and its books for GRL-SNAM's twin - #848

Merged
transfix merged 1 commit into
masterfrom
feat/dyn-bike-ph-golden
Oct 28, 2026
Merged

transfix merged 1 commit into
masterfrom
feat/dyn-bike-ph-golden

Conversation

@transfix

Copy link
Copy Markdown
Owner

A golden for dyn_bike's port-Hamiltonian form (#831) and its per-step books, as DynBike.Golden is for the plant. GRL-SNAM's torch twin of dyn_ph_parts_at, dyn_ph_rates_at and dyn_plant_books will be held to it.

TEST(DynBikePH, Golden) writes src/cvc/tests/data/dyn_bike_ph_golden.json when CVC_DYN_BIKE_PH_GOLDEN_WRITE=1 is set, and otherwise checks it to 1e-9 relative (the double tier).

Cases. It uses DynBike.Golden's Humvee (steering and drive lags, the low-speed blend) and its seven states: cruising, a hard corner, trail braking, past the tire's peak, backing up, creeping, and pulling away from rest. An eighth case brakes to a stop at the speed clamp, so the clamp's share of the limiter term is covered as well as the blend's.

What each case records:

  • the parts at its state (dyn_ph_parts_at): u, sg, Fxf, Fxr, Fyf, Fyr, wf, wr, w0f, the secants Sf and Sr, Dx, and cd;
  • the rates at its state (dyn_ph_rates_at): dissipated, supplied, floor, and grade;
  • one dyn_plant_books step:
    • the next state;
    • the twist before the limiters (dyn_step_free);
    • the books: dH, the trapezoid's dissipated, supplied, floor and grade terms, the limiter, and the residual;
  • the gradient of a fixed weighting of the books (g_books) with respect to the state and the commands, by forward-mode duals. The twin's gradient comes from autograd, so the two are independent derivations, as in DynBike.Golden.

The test also checks that dyn_plant_books steps exactly as dyn_plant does.

DynBike.Golden and dyn_bike_golden.json are unchanged.

Locally (MSVC 14.44, Release): nav_dyn_bike_test passes all 22 tests, DynBike.Golden included. clang-format 18 reports no changes on the changed lines.

A golden for dyn_bike's port-Hamiltonian form, as DynBike.Golden is for
the plant: tests/data/dyn_bike_ph_golden.json, written with
CVC_DYN_BIKE_PH_GOLDEN_WRITE=1 and otherwise checked to 1e-9 relative.

On DynBike.Golden's Humvee (lags, low-speed blend), at its seven states
(cruising, a hard corner, trail braking, past the tire's peak, backing
up, creeping, pulling away from rest) and a stop at the speed clamp, each
case records:
- the parts at its state (dyn_ph_parts_at): u, sg, Fxf, Fxr, Fyf, Fyr,
  wf, wr, w0f, the secants Sf and Sr, Dx, cos delta;
- the rates at its state (dyn_ph_rates_at): dissipated, supplied, floor,
  grade;
- one dyn_plant_books step: the next state, the twist before the limiters
  (dyn_step_free), and the books (dH, the trapezoid's dissipated,
  supplied, floor and grade, limiter, residual);
- the gradient of a fixed weighting of the books on the state and the
  commands, by forward-mode duals.

The test also checks that dyn_plant_books steps exactly as dyn_plant.
DynBike.Golden and dyn_bike_golden.json are unchanged.

nav_dyn_bike_test: 22 tests pass (MSVC 14.44 Release).
@transfix
transfix enabled auto-merge (squash) October 28, 2026 00:27
@transfix
transfix merged commit 9b9270b into master Oct 28, 2026
19 of 24 checks passed
transfix added a commit to CVC-Lab/GRL-SNAM that referenced this pull request Oct 28, 2026
…t slip in its wheel's frame (#138)

Two commits, for Joe to review before merging:
1. a torch twin of libcvc's single-track vehicle in port-Hamiltonian form, with its per-step energy books;
2. `dyn_bike`'s front slip moved into its wheel's frame, following libcvc.

They sit beside `dyn_plant` in `grl_snam/dyn_bike.py`. The whitepaper (#137, `docs/vehicle-ph-plant.tex` §8.2–8.3 and §10) lists the twin as missing.

**1. The pH twin** (held to libcvc's `DynBikePH.Golden`, transfix/libcvc#848). New in `grl_snam/dyn_bike.py`, mirroring libcvc's `dyn_ph_*` in `cvc/nav/detail/dyn_bike.h`:
- `dyn_ph_parts_at`: everything the form is built from at a state:
  - the axle forces (the port);
  - the tires' lateral forces and secant stiffnesses;
  - `u`, `sg`, `uf` and `Dx`;
  - the vectors `n_f`, `n_r`, `t_f`, `t_r`.
- `dyn_ph_rates_at`: the power balance dH/dt = supplied − dissipated + floor + grade.
- `dyn_ph_hamiltonian`: ½ vᵀMv.
- `dyn_plant_books`: `dyn_plant`'s step, unchanged, plus its books:
  - the trapezoid of the rates between the step's start (with the actuators' outputs δ₁, a₁) and the twist the step reached before its limiters;
  - `limiter`: dH across the speed clamp and the low-speed blend;
  - `residual`: the rest.
- `dyn_plant(..., with_free=True)` also returns the pre-limiter twist (libcvc's `dyn_step_free`).
- `dyn_plant`'s step 2 moves into `_axles`, which the parts share.

`tests/test_dyn_bike_ph_parity.py` checks the twin against `dyn_bike_ph_golden.json`, double tier, 1e-9 relative, on eight Humvee cases. Each case checks:
- the parts and the rates;
- one step's next state, pre-limiter twist and books;
- the autograd gradient of a weighting of the books against libcvc's forward-mode duals.

The file also tests that a batch books like its rows. A torch-only gate checks that the books close to second order on a swerve.

**2. The wheel-frame front slip** (follows transfix/libcvc#852, approved by Joe). The front slip becomes the wheel-frame slip `dyn4`'s tires use:
- alpha_f = −atan2(w_f, u_f);
- w_f = cos δ (vy + lf r) − sin δ vx, which is n_f·v;
- u_f is the floored forward speed in the wheel's frame;
- the secant is rooted at w_f = 0.

The implicit step's Jacobian takes the front tire's −S_f cos² δ (1, lf) on the body's y. In the pH twin, R's front term becomes S_f n_f n_fᵀ, and f_ε is round-off. Three fixtures are regenerated from libcvc#852:
- `dyn_bike_golden.json`;
- `dyn_bike_ph_golden.json`;
- `material_ext_golden.json`: only its dynamic case changes; the surrogate and bicycle cases are byte-identical.

**Measured** (torch 2.8, CPU, after both commits):
- Worst relative difference from libcvc:

  | Values | Worst relative difference |
  |---|---|
  | Plant next state | 0 |
  | Plant gradient | 2.8e-16 |
  | pH parts | 3.4e-16 |
  | pH rates | 3.4e-16 |
  | Books | 7.1e-14 |
  | Books gradient | 4.4e-14 |
- On the golden's cases, the floor power is at most 6e-13 W.
- On the swerve, the mean |residual| per step falls 3.94× and then 3.97× per halving of the step.
- `test_dyn_bike_parity`, `test_dyn_bike_ph_parity`, `test_material_ext_parity`, `test_dyn4_parity` and `test_material_bike_parity` give 57 passed and 6 skipped. The skips are live-pycvc checks that this machine's pycvc lacks.
- ruff and black are clean on the changed files.

pycvc has no dynamic binding, so none of this depends on a pycvc release.
transfix added a commit that referenced this pull request Oct 28, 2026
… 0) (#852)

**Follows #848** (merged as 9b9270b; master is merged in, so the diff is the switch alone).

The front slip of `dyn_bike` becomes the wheel-frame slip that `dyn_4w` uses (approved by Joe):

    alpha_f = -atan2(w_f, u_f)
    w_f = cos(delta) (vy + lf r) - sin(delta) vx    (= n_fᵀ v, the front axle's sideways velocity in its wheel's frame)
    u_f = sqrt((cos(delta) vx + sin(delta) (vy + lf r))² + eps²)

The front secant is now rooted at w_f = 0. Before, it was rooted at the floored speed's root, u tan(sg δ). The rear slip was already in its wheel's frame.

The implicit step's Jacobian takes the front tire's d(F_yf cos δ)/d(vy, r) = −S_f cos² δ (1, lf), as `dyn_4w`'s does.

**The pH form**
- R's front term is S_f n_f n_fᵀ (it was (S_f / cos δ) n_f n_fᵀ). It is positive semi-definite at any δ.
- f_ε is now round-off. The speed floor enters only the slips' forward speeds. The `floor` line stays in `dyn_ph_rates`, `dyn_ph_books` and `dyn_bike_books`, so their APIs don't change.
- `dyn_ph_parts` gains `uf` and loses `w0f`.
- `dyn_ph_tangent_D` takes the wheel-frame slip. Its front sensitivity is exact in (vy, r), including the forward speed's dependence, which runs along the same (1, lf).

**Tests** (`nav_dyn_bike_test`, 23 tests)
- `DynBikePH.TheFloorTermVanishesWithTheFloor` becomes `TheFloorTermIsRoundOff`. |floor power| / (dissipated + |supplied| + 1 W) is ≤ 2.2e-16 at every floor from 0.5 to 1e-9 m/s. Before, it was 5.35e-4 at 0.5 m/s.
- New: `DynBikePH.TheTangentIsTheTiresDerivative` checks `dyn_ph_tangent_D` against central differences of the tires' lateral force and moment. Worst error: 1.4e-7 of its scale.
- Three goldens are regenerated:
  - `DynBike.Golden`;
  - `DynBikePH.Golden` (the key `w0f` becomes `uf`);
  - `NavMaterialExt.Golden`'s dynamic case. Its surrogate and bicycle cases stay byte-identical.

**Measured, before → after** (MSVC 14.44 Release)

| Measure | Before | After |
|---|---|---|
| Kinematic-limit gap (`DynBike.KinematicLimitFollowsBikeStep`) | 0.01074 | 0.01074 |
| Vector field vs plant, mu 0.9 / 0.6 | 1.62e-4 / 1.06e-4 m g | unchanged |
| States past the peak | 244 of 500 per mu | unchanged |
| Tangent bins | | unchanged to 0.1%: the front passes its peak from about 0.5 mu g; the rear never does |
| Swerve: mean \|residual\| ratio per halving | 3.83 / 3.92 / 3.95 | unchanged |
| Swerve: \|sum\| / flow at hdt 0.01 | 1.21e-3 | unchanged |
| Braking to a stop, hdt 0.01: limiter / \|sum residual\| / flow | −353.5 J / 7.23e-7 | −358.2 J / 4.43e-6 |
| Braking to a stop, hdt 0.005: limiter / \|sum residual\| / flow | −1.722 J / 1.72e-5 | −8.463 J / 1.76e-5 |
| `physics_dyn_bike_plant_test`, Humvee: floor / residual share | 1.14 J / 1.10e-4 | −5.4e-15 J / 1.10e-4 |
| `physics_dyn_bike_plant_test`, RoboRacer: residual share | 8.86e-4 | 8.88e-4 |
| `nav_dyn_4w_test` single-track limit | 0.130 m / 0.016 m/s | unchanged |

Also passing locally:

| Suite | Tests |
|---|---|
| `nav_binding` | 6 |
| `nav_dyn_4w` | 16 |
| `nav_material_bike_rollout` | 12 |
| `nav_material_ext` | 11 (with the regenerated golden) |
| `physics_dyn_bike_plant` | 3 |
| `physics_plant_host` | 5 |
| `nav_sim_world` | 3 |

`nav_plant`, `physics_plant_log`, `nav_coef_train` and `nav_potential` were still running when this was opened.

GRL-SNAM follows in CVC-Lab/GRL-SNAM#138, which brings the torch `dyn_bike.py` and the three fixtures over. It matches the new goldens to 2.8e-16 on the plant and 7.1e-14 on the books.

**Not changed:** `dyn_power.h`'s `dyn_tire_power` still uses the old front slip angle, sg δ − atan2(vy + lf r, u). Its tire-slip power therefore no longer reads the plant's own front slip. It wasn't part of the approved switch, and changing it moves the fitted power model, so it's left for a decision.
transfix added a commit that referenced this pull request Oct 28, 2026
The project version and the cvcpkg recipe family move to 3.7.2. Every recipe is at cvc_revision 1
already, so none restarts.

3.7.2 carries the dynamic plants on the terrain and in a Jolt world, and the four-wheel plant
(#869): --plant dynamic and dynamic4w drive the ground, collide with the
buildings and each other, and book the collisions. It also carries dyn_4w's port-Hamiltonian form
(#850), dyn_bike's wheel-frame front slip (#852), the books goldens for GRL-SNAM's twins (#848,
#849), pycvc's drive telemetry (#857) and keep-alives (#864), the macOS SDK's openssl (#846), and
the state_exec scheduler and evaluator work (#842, #843, #844, #861, #863).
@transfix
transfix deleted the feat/dyn-bike-ph-golden branch October 29, 2026 20:52
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