v0.2 — LuGre dynamic tire (shipped)¶
Status: implemented and tuned (2026-06). Default on via TireParams.lugre.enabled
and catalog tire.default_pacejka. Kinematic blend is the fallback when
enabled: false (tire.kinematic_fallback, --no-lugre).
Theory: docs/theory/19_lugre_dynamic_tire.md
Default low-speed path (LuGre off): LOW_SPEED_HANDLING.md
User guide: CATALOG_AND_PHYSICS.md
1. Role in VDSim¶
| Mode | Tire constitutive law | Low-speed host path |
|---|---|---|
lugre.enabled: false (fallback) |
Algebraic MF96 (pacejka_mf96.cpp) |
Kinematic–dynamic blend, λ force fades, brake-hold damper |
lugre.enabled: true (default) |
LuGre brush on \(z_x\), \(z_y\); MF96 shapes \(g(\cdot)\) | None of the above — full dynamic EOM at all speeds |
LuGre is not a display overlay: when on, the same \(F_x\), \(F_y\) feed body Newton–Euler
and wheel-spin dynamics (fx_kin).
Hosts: L1 bicycle_dynamics.cpp, L2 seven_dof_dynamics.cpp, L3 (inner L2).
Code: core/include/vdsim/lugre_tire.hpp, call sites in the dynamics .cpp files above.
2. State and kinematics (per wheel)¶
| State | Meaning |
|---|---|
z_long |
Longitudinal bristle deflection [m] |
z_lat |
Lateral bristle deflection [m] |
Slip velocities (wheel frame, ISO 8855 — Chapter 01):
| Channel | \(v_r\) |
|---|---|
| Longitudinal | \(v_{r,x} = R\omega - v_{x,\mathrm{wheel}}\) |
| Lateral | \(v_{r,y} = v_{y,\mathrm{wheel}}\) |
MF96 inputs \((\kappa, \alpha, F_z, \mu_{\mathrm{long}}, \mu_{\mathrm{lat}})\) are still computed for \(g(\cdot)\), combined-slip axes, and \(M_z\) trail — same as the algebraic tire path.
3. Constitutive law¶
Canudas de Wit LuGre (decoupled channels):
| Param | YAML | Default behaviour |
|---|---|---|
| \(\sigma_0\) | sigma0 |
Bristle stiffness [N/m]; catalog lugre_on: 3e5 |
| \(\sigma_1\) | sigma1 |
0 → auto \(2\sqrt{\sigma_0 m_{\mathrm{eff}}}\) (critical) |
| \(\sigma_2\) | sigma2 |
Viscous [N·s/m]; lugre_on: 120 |
| \(m_{\mathrm{eff}}\) | m_eff |
For auto \(\sigma_1\) [kg]; default 40 |
3.1 ISO lateral sign¶
Pacejka lateral force is \(F_y = -D\sin(\cdots)\) (restoring). Bristle \(v_{r,y}\) has the same sign as \(\alpha\) at moderate speed, so the host applies:
Longitudinal \(F_x = F_{\mathrm{raw}}(z_{\mathrm{long}}, v_{r,x}, g_x)\) — no extra sign flip.
3.2 Breakaway envelope \(g\)¶
MF96 at current slip defines the sliding limit; VDSim adds a presliding floor only where MF goes to zero at small slip:
| Design choice | Why |
|---|---|
| No \(\mu F_z\) floor on \(g_x\) | At \(\kappa \approx 0\), a longitudinal floor produced phantom \(F_x\) / wheel-spin chatter with zero throttle |
| \(\mu_{\mathrm{lat}} F_z\) floor gated by \(w(\alpha)\) | Presliding cornering stiffness at \(\alpha \to 0\) without saturating \(F_y\) during turn+drive |
| \(g\) from instantaneous MF | Transient envelope tracks the slip curve, not a fixed Coulomb cap |
lugre_breakaway() in lugre_tire.hpp.
3.3 Combined slip (friction ellipse)¶
When combined_slip_enabled: true (catalog default), decoupled \((F_x, F_y)\) are rescaled
with the same peak axes as MF96 (Chapter 03 / task 15):
lugre_friction_ellipse() — not the instantaneous \(|F_x^{\mathrm{MF}}|\), \(|F_y^{\mathrm{MF}}|\).
When LuGre is on and combined_slip_enabled, the host skips the extra
\(\sqrt{F_x^2 + F_y^2} \le \mu F_z\) circular clip in seven_dof_dynamics.cpp /
bicycle_dynamics.cpp (avoids triple-counting grip loss in turn+throttle).
3.4 Aligning moment¶
\(M_z = -t_p(\alpha)\,F_y\) with pneumatic trail falloff — same as MF96 path.
4. Time discretization¶
4.1 Semi-implicit \(z\) update¶
lugre_advance_z(). \(\dot z\) in the force law uses \((z_{k+1}-z_k)/\Delta t\) when needed
via lugre_z_dot().
4.2 RK4 coupling¶
\(z\) is not frozen for the whole step:
derivatives(s0)→ forces use current \(z\)advance_lugre_states(Δt/4)- Repeat for RK2, RK3, RK4 stages
- No fifth advance at step end (total bristle integration = \(\Delta t\))
Euler: one derivatives + one advance_lugre_states(Δt).
Within a single derivatives() call, \(z\) is held fixed (all four RK substates in that
evaluation see the same \(z\)).
4.3 Wheel spin¶
Same longitudinal force as the body (after ellipse):
fx_kin = Fx_wheel // clipped LuGre Fx
dω = (T_drive + T_brake - fx_kin * R) / I_wheel // + engine inertia share if open diff
No split MF/LuGre coupling on fx_kin — one constitutive law avoids \(\kappa\) sign oscillation.
5. What LuGre disables in the host¶
When lugre.enabled: true:
- Kinematic \((v_y, r)\) relaxation (
kStickBlend,kKinTau) - \(\lambda\) fade on lateral tire force (and MF-only longitudinal display fade)
- Brake-hold viscous damper (
kStickC) relaxation_length_lat/relaxation_length_longtransients
kSpeedEps (0.15 m/s) still floors the display slip ratio \(\kappa\) denominator only.
6. Parameters and catalog¶
lugre:
enabled: true
sigma0: 300000.0
sigma1: 0.0 # 0 → critical from m_eff
sigma2: 120.0
m_eff: 40.0
Fallback part: configs/parts/tire/kinematic_fallback.yaml (enabled: false).
Scene demo: configs/scenes/lugre_grade_demo.yaml (alias preset).
Paths: default catalog; disable via tire.kinematic_fallback, CLI --no-lugre, GUI
toggle, Python Tire.preset().lugre(False).
7. Validation (ctest)¶
| Test | Checks |
|---|---|
LuGreTire.LateralForceOpposesSlipIso8855 |
\(\mathrm{sign}(F_y)\) matches MF96; $ |
LuGreTire.CoastFlatNoPhantomLongitudinal |
Zero cmd coast: bounded \(F_x\), \(\omega\) |
LuGreTire.LongitudinalWithSmallSteer |
Throttle + steer: \(v_x\) ≥ 65% of straight |
LuGreTire.RearSlipBoundedUnderThrottle |
RWD \(\kappa\) not runaway vs blend |
LuGreTire.ThrottleProducesForwardMotion |
From rest, throttle → \(v_x > 3\) m/s |
LuGreTire.LowSpeedYawDoesNotRunaway |
Small initial \(r\) does not spin up |
LuGreTire.LessGradeCreepThanKinematicBlend |
Grade hold vs blend |
LuGreTire.HighSpeedLongitudinalNearPacejka |
Cruise \(F_x\) near MF |
LuGreTire.BreakawayFloorMuFzAtZeroSlip |
\(g_y(0) \approx \mu F_z\); \(g_x(0) \approx 1\) N |
TireYaml.LuGreRoundtrip |
YAML params |
Run: cd build && ctest -R LuGreTire --output-on-failure
8. Known limits / future work¶
- Decoupled long/lat bristles + ellipse (not Deur 2D brush).
- No temperature, wear, or pressure transients in \(g\).
- Reverse driving: \(\alpha\) continuity at \(\pm 180^\circ\) not special-cased.
- Optional re-blend of kinematic \((v_y,r)\) at very low \(|v_x|\) with LuGre (not shipped).
- Steering-column LuGre (Ch. 17) is a different module (N·m, Stribeck on \(\omega\)).
9. References¶
See theory Ch. 19 §19.12. Primary: Canudas de Wit et al. IEEE TAC 1995; Deur et al. VSD 2004; Pacejka Ch. 3 brush model.