Skip to content

ph: steering as a modulation of D and G, opt-in, so the drive alone is the port - #851

Merged
transfix merged 2 commits into
masterfrom
feat/ph-steer-modulation
Oct 28, 2026
Merged

transfix merged 2 commits into
masterfrom
feat/ph-steer-modulation

Conversation

@transfix

Copy link
Copy Markdown
Owner

For Joe's review before merge (auto-merge is not enabled).

Why

cvc::ph::se2_vehicle follows LieSPHGP's form, in which every logged input is a port, steering included. So y^T u, with y = G(v)^T v, books power through the steering input. A vehicle's steering does no work on its body: dyn_bike's exact pH form (#831) shows that steering only turns the directions the tires push along.

Measured:

  • on the golden fit, steering is 13.4% of |y·u| at its 16 states, and with no drive at all it supplies energy at 10 of them;
  • fit to the analytic plant's flights, the LieSPHGP-form model still books 5.6% of |y·u| through steering, against 0 in the plant.

What

se2_setup::modulation_dim (0 by default) makes the last inputs modulations:

D(v, delta) = spd(level + GP_D(v / twist_scale, delta))
G(v, delta) = level + GP_G(v / twist_scale, delta)
dp = ( ad*_v p - [R^T grad V; 0] - D(v, delta) v + G(v, delta) u ) dt,   y = G(v, delta)^T v

u is then the drive alone. With the drive at zero nothing is supplied, whatever delta is, and the steering still steers through D and G.

  • Inputs: modulations are scaled like ports; control_scale has one entry per input, ports first. Plant logs (drive, steering) fit with control_dim 1 and modulation_dim 1.
  • File format:
    • a modulated model is written as cvc-ph-se2/2, with modulation_dim and D_input/G_input set to "twist+modulation";
    • models without modulations are still written as /1, byte for byte;
    • a /1 reader refuses a /2 file.
  • Reverse pass: it carries the modulation entries of the GP input and drops their gradient, since they are data.
  • CLI: cvc ph-train --steer-modulates fits the modulated form, --init refuses a model that disagrees with the flag, and provenance records steering.
  • Default unchanged: modulation_dim 0 is LieSPHGP's form, and the JAX parity tests hold as before.

Tests

Test Shows Measured
PhSe2.TheReferenceFormBooksSteeringAsPower in LieSPHGP's form, steering alone supplies power share 0.134; supplies energy at 10 of 16 states
PhSe2.ModulatedSteeringSuppliesNoPower the balance holds; zero supply at zero drive; steering still changes the motion balance to 1e-11 at 200 states; a 0.5 steer step changes the twist rate by ≥ 0.18
PhSe2.ModulatedModelRoundTripsExactly /2 round-trips exactly; unmodulated models stay /1 exact
PhSe2.ModulatedStepIsSecondOrder Lie-IMEX stays second order ×4.29, 4.08, 4.02
PhSe2Train.ModulatedGradientMatchesFiniteDifferences the reverse pass is correct worst 3.9e-7 over 68 entries (gate 2e-5)
PhSe2Train.ModulatedShortFitLowersTheLoss the modulated form trains held-out loss 1.417 → 0.070
PhSe2Train.BothFormsFitTheDynamicBicycle both forms fit diff::dyn_plant's flights; only the reference form books steering power reference 8.52 → 0.0006 (steering share 0.056, supplies energy at 74 of 250 steps); modulated 7.63 → 0.0092 (share 0)
CvcCliTest.PhTrainFitsSteeringAsAModulation --steer-modulates end to end passes

Note on BothFormsFitTheDynamicBicycle:

  • It uses Adam at 1e-3.
  • At the default 3e-3, the reference fit's gradient norm blew up (1e6 to 1e9) in all three runs I tried, while the modulated fit converged in all four configurations. That is one dataset and one seed, so it is reported here, not claimed.
  • At 1e-3 the reference form fits this data better.

Locally (Windows, MSVC 19.44, Release): ph_se2_test 18, ph_se2_train_test 11 and the four PhTrain CLI tests pass. git clang-format (18) reports no changes. The Linux and macOS runs are CI's.

Not in this PR

  • The EKF-ELBO stage (M3).
  • Proposing the modulation to the LieSPHGP authors: a note is drafted for Joe to send. Public CVC-Lab/LieSPHGP (65db994) has no SE(2) vehicle package; the golden's provenance names 65db994 plus a separate SE(2) package.

…s the port

In LieSPHGP's form every logged input is a port, steering included, so y^T u
books power through the steering: on the golden fit the steering is 13.4% of
|y.u| at its 16 states and supplies energy at 10 of them, with no drive at all.
A vehicle's steering does no work on its body (dyn_bike's pH form, #831): it
only turns the directions its tires push along.

se2_setup::modulation_dim (0 by default) makes the last inputs modulations:
D(v, delta) and G(v, delta) read them beside the twist, u is the remaining
ports, and y = G(v, delta)^T v. With the drive at zero nothing is supplied
whatever delta is, and the steering still steers through D and G.

- The modulations are scaled like the ports (control_scale has one entry per
  input, ports first). Plant logs (drive, steering) fit with control_dim 1 and
  modulation_dim 1.
- A modulated model is written as cvc-ph-se2/2 (modulation_dim, D_input and
  G_input "twist+modulation"); models without modulations are still /1, byte
  for byte, and a /1 reader refuses /2.
- The reverse pass carries the GP input's modulation entries and drops their
  gradient (they are data).
- cvc ph-train --steer-modulates fits it; --init refuses a model that
  disagrees with the flag; provenance records "steering".
- modulation_dim 0 is unchanged: the JAX parity tests hold as before.

Tests:
- ph_se2_test, 14 -> 18:
  - TheReferenceFormBooksSteeringAsPower: the golden fit's steering share of
    |y.u| is 0.134; it supplies energy at 10 of 16 states;
  - ModulatedSteeringSuppliesNoPower: the balance to 1e-11 at 200 states,
    zero supply at zero drive, and a 0.5 steer step changes the twist's rate
    by at least 0.18;
  - ModulatedModelRoundTripsExactly;
  - ModulatedStepIsSecondOrder: x4.29, 4.08, 4.02.
- ph_se2_train_test, 8 -> 11:
  - ModulatedGradientMatchesFiniteDifferences: worst 3.9e-7 over 68 entries;
  - ModulatedShortFitLowersTheLoss: 1.417 -> 0.070;
  - BothFormsFitTheDynamicBicycle: both forms fit diff::dyn_plant's flights
    (Adam 1e-3: reference 8.52 -> 0.0006, modulated 7.63 -> 0.0092). The
    reference fit still books 5.6% of |y.u| through steering and supplies
    energy by steering alone at 74 of 250 held-out steps; the plant books
    none. At Adam's default 3e-3 the reference fit's gradient norm blew up
    (1e6-1e9) in three runs; the modulated fit converged in all four tried.
- cvc_cli_test: PhTrainFitsSteeringAsAModulation.

Locally (MSVC 19.44, Release): ph_se2_test 18, ph_se2_train_test 11 and the
four PhTrain CLI tests pass. git clang-format (18) reports no changes.
transfix added a commit to CVC-Lab/GRL-SNAM that referenced this pull request Oct 28, 2026
… review fixes, links (#140)

* docs(vehicle-ph-plant): reproduce appendix, steering as a modulation, review fixes, links

- Appendix A: the exact setup behind every measured value (libcvc c1f104c0,
  MSVC 19.44, CMake 3.31.7, Ninja 1.12.1, cvcpkg 2.5.6, torch 2.8.0+cpu);
  the Windows commands that were run, with the extra CMake flags and why;
  the Linux/macOS equivalents (not run verbatim here; libcvc's CI runs the
  suites on every platform); each test's printed lines; GRL-SNAM's pytest
  line and its expected 53 passed, 2 skipped.
- Section 6.6 rewritten for the learned plant's steering: the reference
  form books power through steering (13.4% of |y.u| on the golden fit;
  5.6% on a fit to the analytic plant, which books none). The opt-in
  modulated form (transfix/libcvc#851) has D(v, delta) and G(v, delta), with
  the drive alone as the port. A new validation table holds its tests.
- The learned plant's provenance corrected: public CVC-Lab/LieSPHGP has no
  SE(2) vehicle; the port and its golden follow an SE(2) package built on it
  at 65db994. LieSPHGP's own trainers use the EKF ELBO.
- Review fixes:
  - the dynamic plant is 3.7.0 (#714), the learned plant 3.7.1 (#758);
  - psi for the kinematic heading (theta is the grade);
  - a "mixed" parity tier;
  - the sharp check's wording and its 3.5e-5;
  - the health-check range;
  - test totals, the CTest regex and NavPotential.DriveForce*;
  - the GRL-SNAM paper's full title.
- Links from README.md and the guide's "See also".

pdflatex: 26 pages, three passes, no warnings.

* docs(vehicle-ph-plant): the front slip in its wheel's frame (libcvc #852), and the pH twin (#138)

The plant's front slip is now measured in its wheel's frame, as the
four-wheel model's: alpha_w = -atan2(n_w.v, sqrt((t_w.v)^2 + eps^2)), each
rooted at n_w.v = 0 (transfix/libcvc#852). The paper follows:
- Section 3: the directions come first, then the slips in the wheels'
  frames; the old front slip is noted.
- Lemma 1 holds at every steering angle (the secant through w = 0, slope
  P C B u_w / (u_w^2 + w^2) at the root); f_eps is the root branch's
  rounding only.
- Section 4.2 and Proposition 1: R's front term is S_f n_f n_f^T, and the
  vehicle is passive from the drive port alone at every floor.
- Section 4.4: S_f ~ C_f / u_f; the front tire's power is exactly its
  dissipation but for the root branch's rounding.
- Section 4.5: the floor reaches only the slips' forward speeds;
  TheFloorTermIsRoundOff measures at most 2.2e-16 of the flow; the old
  remainder and its eps^2 numbers are kept as history.
- Section 4.7: k_f is the wheel-frame slip's exact sensitivity (eq. kf),
  checked by the new TheTangentIsTheTiresDerivative (1.4e-7).
- Section 5.2: braking limiter -358 J, residual 4e-6.
- Section 10: the four-wheel form now carries the single track's over.
- Tables: the plant table gains the renamed floor test and the tangent
  test, with #852's numbers; the live plant's rows move to their own table
  (the plant table no longer fit a page); the twins table and the GRL-SNAM
  validation rows gain #138's pH twin (test_dyn_bike_ph_parity, 18 passed,
  worst 7.1e-14).
- Section 8.2-8.3, Python access and the equation-to-code map: the pH
  form's parts, rates and books are twinned in GRL-SNAM #138.
- Appendix A: #852's and #138's branch commits, and the printed lines at
  1a0acc77 with the c1f104c0 values they replace.
- Versions: #852.

Built with TeX Live pdflatex, three passes: 27 pages, no warnings.

* docs(vehicle-ph-plant): cite #138's squash commit 2f76ab0

The #138 rows, the reproduction appendix and the versions table now cite main at 2f76ab0, where the dynamic twins' parity tests pass again (57 passed, 6 skipped for pycvc). The PDF is rebuilt: 27 pages, no warnings.

* docs(vehicle-ph-plant): the point-mass books' twin (#139) and pycvc's tick telemetry (libcvc #857)

- Table 9 (the twins) gains the navigator's point-mass books: book_step,
  energy_residual_bound, material_energy and integrate_surrogate_material's
  books hook, held to libcvc's NavPotential.BooksGolden. book_step on
  libcvc's arguments is held to 1e-12 (double tier); the books are held to
  1e-5 of the step's bound, and 2e-5 on the clamped rollout (float tier).
- 8.3, "What is not twinned yet": the point-mass books are twinned. The tick
  telemetry is a running world's, so it has no twin; libcvc #857 binds it in
  pycvc (nav_sim_world_set_capture_drive_telemetry,
  nav_sim_world_drive_telemetry).
- 8.4 gains "Book the navigator's energy": how to ask for the books, the
  measured parity (1.1e-6 and 8.3e-8 of the bound), and the curved-patch
  caveat. On a patch whose gradient channels are not its values' gradients,
  each step books the force/-grad U gap, which fills 99.65% of the bound at
  h = 0.002. It also says where a live world's context work comes from.
- 10, "Python access", and the equation-to-code map: updated to match.

PDF rebuilt with TeX Live pdflatex, three passes: 28 pages, no warnings.
@transfix transfix closed this Oct 28, 2026
@transfix transfix reopened this Oct 28, 2026
@transfix
transfix merged commit b8ac997 into master Oct 28, 2026
24 of 48 checks passed
transfix added a commit that referenced this pull request Oct 29, 2026
The project version and the cvcpkg recipe family move to 3.7.3. Every recipe is at cvc_revision 1
already, so none restarts.

3.7.3 carries the dynamic plants' driver and chassis friction (#873): --plant
dynamic and dynamic4w take nav's acceleration as the Jolt plant's driver does (a standstill hold,
the grade fed forward, a trim on the measured acceleration) and their chassis boxes scrape walls
at a body panel's friction, so on Austin at 864 s they bring 35 and 24 of 48 vehicles to the
objective (3.7.2: 11 and 7). It also carries pycvc-gl's wasm deps with ImageMagick (#872), the
steering modulation of D and G in cvc::ph (#851), the bench's compare and sweep keys (#859), and
the sanitizer CI jobs for state_exec (#858).
transfix added a commit that referenced this pull request Oct 29, 2026
The project version and the cvcpkg recipe family move to 3.7.3. Every recipe is at cvc_revision 1
already, so none restarts.

3.7.3 carries the dynamic plants' driver and chassis friction (#873): --plant
dynamic and dynamic4w take nav's acceleration as the Jolt plant's driver does (a standstill hold,
the grade fed forward, a trim on the measured acceleration) and their chassis boxes scrape walls
at a body panel's friction, so on Austin at 864 s they bring 35 and 24 of 48 vehicles to the
objective (3.7.2: 11 and 7). It also carries pycvc-gl's wasm deps with ImageMagick (#872), the
steering modulation of D and G in cvc::ph (#851), the bench's compare and sweep keys (#859), and
the sanitizer CI jobs for state_exec (#858).
@transfix
transfix deleted the feat/ph-steer-modulation 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