Repository navigation
physics: dyn_bike_plant, the dynamic single-track vehicle as a live plant - #833
Merged
Merged
Conversation
… books The explicit pH form of the dynamic single-track plant (#748) booked the steering angle as a power port, G = [[1,0],[0,C_f],[0,lf C_f]] with u = (F_x, delta), so with no drive force the model could gain kinetic energy from steering alone. It held only in the linear-tire regime, and its comment said a positive-definite dissipation cannot represent sliding, which is true of the tangent only. Rewritten so that the form is dyn_plant's vector field exactly, at every slip: p' = (J(p) - R(v, delta)) v + G(delta) u + f_eps - m g grade e_x R = D_x e_x e_x^T + (S_f / cos delta) n_f n_f^T + S_r n_r n_r^T G = [t_f, t_r], u = (F_xf, F_xr), y = G^T v - n_f = (-sin d, cos d, lf cos d) and n_r = (0, 1, -lr) carry each tire's lateral force onto (vx, vy, r). S_f and S_r are the tires' secants through their roots (dyn_secant_w), >= 0 at any slip, so R is PSD sliding or not. - The inputs are the axles' applied forces along their wheels. Steering only turns n_f and t_f, so it does no work. - f_eps is what the secants leave of the tire forces: the slips' speed floor. The front force's root sits at u tan(sg delta), not at vx tan(delta). It is booked on its own line and vanishes as eps / |vx| -> 0. - dH/dt = -v^T R v + y^T u + v^T f_eps - m g grade vx. dyn_plant takes an optional dyn_step_free*, which records the twist before the speed clamp and the low-speed blend. Its outputs are unchanged (DynBike.Golden holds). dyn_plant_books returns one step's books: dH, the trapezoid of each power, the limiters' kinetic energy, and the residual. Gates (nav_dyn_bike_test, DynBikePH): - the structure at 500 states per mu, half of them past a tire's peak: J skew, R symmetric with nonnegative coefficients, no supply without axle force; - the vector field against a 1e-5 s dyn_plant step, forward and reversing, flat and on 3%: worst 1.6e-4 m g (was a 1% linear-regime check); - the step books on a swerve past the peak: the per-step residual falls 3.8-3.95x per halving of hdt (second order), and is 1.2e-3 of the flow over 6 s at 0.01 s. Braking to a stop charges the clamp and the blend; - the floor term scales as eps^2 and is 2e-16 of the flow at eps = 1e-9; - the tangent goes indefinite above half the grip while the secant never does. cvc::ph::se2_vehicle gains: - output(), the conjugate output y = G(v)^T v; - mass(); - noise_power(), 1/2 tr(Sigma^T M Sigma): the twist noise adds this to H on average, which the drift's balance does not book. Tested: y.u is the supplied power, and an antithetic Monte Carlo of one step measures 3.585e-6 J against 3.564e-6 J predicted. The clang-format-garbled equation comments in se2_vehicle.h, se2_train.h and se2_train.cpp are restored and fenced.
…lant nav's agents can now drive the dynamic single-track model (diff::dyn_plant: Pacejka tires, load transfer, actuator lag) as their plant, beside nav's kinematic bicycle and the Jolt vehicles. It needs no physics world and no Jolt. This is the plant rollout_kind::dynamic trains through, so a policy trained there runs on its own model. cvc::physics::dyn_bike_plant (a cvc::nav::vehicle_plant): - Agent i drives descs[i % n], with parameters from nav_plant::dyn_bike_si_of, so both plants read one vehicle_desc. Its rear axle starts on the agent's start, at rest. - Each tick runs ceil(dt / substep) dyn_plant steps (0.01 s by default) under the held command, in SI. - Every step is booked through dyn_plant_books. books(i) and last_tick(i) give kinetic, drive, brake, dissipated, floor, grade, limiter and residual in J, and H - H0 = drive - brake - dissipated + floor + grade + limiter + residual holds to rounding. - energy_step() is the tick's drive energy; sample() gives vx, vy, r, the steer, the commands and a_lat / a_lon as nav_plant measures them. - state() reads back the rear axle, heading, speed and steer, with tire_use the most used tire's slip over its Pacejka peak. - limits() follows nav_plant's plan: steering, braking, latency, and with plan_with_vehicle the vehicle's acceleration, top speed, cornering and size. plant_host takes "dynamic" (the Humvee) or "dynamic:<presets>" (cycled, as the Jolt presets are): - check() and use() accept them with or without Jolt in the build; - dynamic() and active() return the plant; - plant() and physics() stay null on it; - it drives on the host's ground_mu. plant_kind::dynamic is reported by sim_world's tree (nav.plant) and by pycvc's nav_sim_world_plant_kind. physics_dyn_bike_plant_test: - the plant's ticks are dyn_plant's substeps bit for bit; - the books close (identity to 1e-9 of the flow) on the Humvee and the RoboRacer, with the integrator's residual 1.1e-4 and 8.9e-4 of the flow over 30 s of weaving and braking; - plant_host puts a 3-agent sim_world on "dynamic" with no physics world, and every agent reaches its goal 120 m on in 11.5 s; - "dynamic:humvee,roboracer" cycles presets, an unknown preset throws and changes nothing, and "kinematic" takes the agents off again.
transfix
enabled auto-merge (squash)
October 27, 2026 04:14
transfix
added a commit
that referenced
this pull request
Oct 27, 2026
The project version and the cvcpkg recipe family move to 3.7.1. The recipes that carried 3.7.0-lineage revisions (libcvc, cvcgl, libcvc-examples-web, pycvc-gl-cp312) restart at cvc_revision 1, since a version bump restarts it. 3.7.1 carries dyn_bike's port-Hamiltonian form and per-step energy books (#831), the navigator's per-step energy identity and context work (#832), and the dynamic single-track plant for plant_host (#833).
transfix
added a commit
that referenced
this pull request
Oct 27, 2026
…on (#837) StepsAsTheDynamicPlantDoes compares dyn_bike_plant's steps with dyn_plant called inline in the test, bit for bit. The plant is built with -ffp-contract=off (the exact-fp list); the test was not, so on the macOS arm64 lanes clang fused its copy into FMAs and the two differed by one ulp (package-macos / libcvc-release on #833: 0.40661397752110373 vs ...368 at tick 4). Build the test the same way, as nav_potential_test and nav_material_ext_test already are.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #831; it will be retargeted to master when #831 merges.
Summary
nav's agents can now drive libcvc's dynamic single-track model (
diff::dyn_plant: Pacejka tires, load transfer, actuator lag) as their live plant, beside nav's kinematic bicycle and the Jolt vehicles. It needs no physics world and no Jolt. It is the plantrollout_kind::dynamictrains through, so a policy trained there runs on its own model.cvc::physics::dyn_bike_plant, acvc::nav::vehicle_plant:nav_plant::dyn_bike_si_of, so onevehicle_descfeeds both plants;ceil(dt / substep)dyn_plantsteps in SI, every step booked throughdyn_plant_books(nav: dyn_bike is port-Hamiltonian at every slip, with per-step energy books #831);books(i)andlast_tick(i)give kinetic, drive, brake, dissipated, floor, grade, limiter and residual in J.H - H0 = drive - brake - dissipated + floor + grade + limiter + residualholds to rounding;energy_step(),sample(),state()(withtire_use) andlimits()follownav_plant's conventions.plant_hostaccepts"dynamic"(the Humvee) or"dynamic:<presets>", with or without Jolt.dynamic()andactive()return the plant;plant()andphysics()stay null on it.plant_kind::dynamicis reported bynav.plantin the state tree and by pycvc'snav_sim_world_plant_kind.Tests (Windows, MSVC, Release)
physics_dyn_bike_plant_test:dyn_plant's substeps bit for bit;plant_host"dynamic": a 3-agentsim_world, no physics world, every agent reaches its goal 120 m away in 11.5 s;"kinematic".Regression:
physics_plant_host_test(5),nav_terrain_test(10),nav_sim_world_test(3) pass.nav_plant_testwas not run locally; this PR doesn't touch it.Downstream: cvcdbg's demo3 uses this as
?plant=dynamic, with the plant's books on its Ledger screen. That is a separate cvcdbg PR, which builds against libcvc with or without this one.