Skip to content

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):

\[ \dot z = v_r - \frac{\sigma_0 |v_r|}{g}\, z, \qquad F_{\mathrm{raw}} = \sigma_0 z + \sigma_1 \dot z + \sigma_2 v_r \]
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:

F_y = -F_raw(z_lat, v_{r,y}, g_y)     // lugre_wheel_forces()

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:

\[ g_x = \max\bigl(|F_x^{\mathrm{MF}}|,\; 1\,\mathrm{N}\bigr) \]
\[ g_y = \max\Bigl(|F_y^{\mathrm{MF}}|,\; w(\alpha)\,\mu_{\mathrm{lat}} F_z,\; 1\,\mathrm{N}\Bigr), \quad w(\alpha) = \exp\!\left(-\left(\frac{\alpha}{0.03}\right)^2\right) \]
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):

\[ F_{x,\max} = D_{\mathrm{long}}\,\mu_{\mathrm{long}}\,\mu_{\mathrm{eff}}\,F_z, \quad F_{y,\max} = D_{\mathrm{lat}}\,\mu_{\mathrm{lat}}\,\mu_{\mathrm{eff}}\,F_z \]
\[ \text{if } (F_x/F_{x,\max})^2 + (F_y/F_{y,\max})^2 > 1 \;\Rightarrow\; \text{scale } (F_x, F_y) \text{ onto the ellipse} \]

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

\[ z_{k+1} = \mathrm{clamp}\!\left( \frac{z_k + \Delta t\, v_r}{1 + \Delta t\,\sigma_0 |v_r| / g},\; \pm g/\sigma_0 \right) \]

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:

  1. derivatives(s0) → forces use current \(z\)
  2. advance_lugre_states(Δt/4)
  3. Repeat for RK2, RK3, RK4 stages
  4. 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_long transients

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.