Skip to content
Automation Systems Design

Vehicle dynamics in depth: bicycle model proved, Pacejka, load transfer, ABS, ESC, ACC, LKA and pure pursuit in Python

Full derivation of the bicycle model, proofs of steady-state steering and stability, Pacejka's magic formula with B, C, D, E, friction circle, relaxation length, load transfer, ABS, ESC, ACC, LKA and pure pursuit, with verified Python simulations.

This page takes the vehicle dynamics lesson to the level of detail needed by those who work on ADAS functions and stability control. It derives the bicycle model step by step, proves the stability conditions and the understeer formulas, describes the tyre with Pacejka’s model (the coefficients B, C, D, E and their meaning), the friction ellipse and the relaxation length, computes load transfers, and shows how these models become the ESC, ABS, ACC, LKA controllers and pure pursuit path tracking. The simulations are in Python, with verified code.

Numerical values of an average car used in the examples: m = 1500 kg, I_z = 2500 kg·m², a = 1.1 m, b = 1.5 m (L = 2.6 m), C_f = 100 kN/rad and C_r = 120 kN/rad (cornering stiffness of each axle), centre of gravity height h = 0.55 m.

The bicycle model, derived

Kinematics. In the vehicle-fixed frame (x forwards, y to the left) the centre of gravity has velocity (v_x, v_y) and the vehicle rotates with yaw rate r. A point at distance \ell ahead of the centre of gravity along x has velocity (v_x,\ v_y + r\ell) (rigid motion: \vec v_P = \vec v_G + \vec r\times\vec{GP}). So the centre of the front axle (\ell = a) has lateral velocity v_y + ar and the rear one (\ell = -b) v_y - br.

Slip angles. The angle between the wheel direction and the direction of its centre is, for the front wheel steered by \delta, \alpha_f = \delta - \arctan\frac{v_y + ar}{v_x}, and for the rear \alpha_r = -\arctan\frac{v_y - br}{v_x}. With small angles and constant v_x = v they linearise: \alpha_f = \delta - \frac{v_y + ar}{v}, \alpha_r = -\frac{v_y - br}{v}.

Dynamics. The frame rotates with the vehicle: the derivative of velocity in a rotating frame contains the term \vec r\times\vec v, whose lateral component is r\,v_x. Newton’s second law along y and the moment equation about z give, with the axle lateral forces F_{yf} = C_f\alpha_f and F_{yr} = C_r\alpha_r (the front force is almost perpendicular to the vehicle axis for small \delta):

    \[ m(\dot v_y + v\,r) = F_{yf} + F_{yr},\qquad I_z\,\dot r = a\,F_{yf} - b\,F_{yr} \]

Substituting the slip angles gives the state form \dot x = Ax + B\delta with x = (v_y, r):

    \[ A = \begin{pmatrix} -\dfrac{C_f + C_r}{mv} & -v - \dfrac{aC_f - bC_r}{mv} \\ -\dfrac{aC_f - bC_r}{I_zv} & -\dfrac{a^2C_f + b^2C_r}{I_zv} \end{pmatrix},\qquad B = \begin{pmatrix} \dfrac{C_f}{m} \\ \dfrac{aC_f}{I_z} \end{pmatrix} \]

The lateral acceleration measured by an accelerometer at the centre of gravity is a_y = \dot v_y + v\,r.

Steady state, understeer and stability, proved

Theorem (steady-state steering). In a steady turn of radius R, with r = v/R and a_y = v^2/R:

    \[ \delta = \frac{L}{R} + K_{us}\,a_y,\qquad K_{us} = \frac{m}{L}\left(\frac{b}{C_f} - \frac{a}{C_r}\right) \]

Proof. In steady state \dot v_y = \dot r = 0. From the moment equation aF_{yf} = bF_{yr}; from Newton’s F_{yf} + F_{yr} = m\,a_y. So F_{yf} = \frac{b}{L}m\,a_y and F_{yr} = \frac{a}{L}m\,a_y: each axle carries the fraction of centripetal force corresponding to the load it supports. The slip angles are \alpha_f = \frac{bm\,a_y}{LC_f}, \alpha_r = \frac{am\,a_y}{LC_r}. Subtracting the kinematic expressions: \alpha_f - \alpha_r = \delta - \frac{(a + b)r}{v} = \delta - \frac{L}{R}. Hence \delta = \frac{L}{R} + \alpha_f - \alpha_r = \frac{L}{R} + \frac{m}{L}\left(\frac{b}{C_f} - \frac{a}{C_r}\right)a_y. ∎

The sign of K_{us} tells which axle “slips more”: if the front slips more than the rear the vehicle understeers. With the example data K_{us} = 3.37 mrad/(m/s²) = 0.19° per m/s²: in a bend at 4 m/s² (0.4 g) 0.77° more road-wheel steering than geometry is needed, about 12° at the steering wheel with a steering ratio of 15.

Theorem (stability). The bicycle model is stable at every speed if K_{us} \ge 0; if K_{us} \lt  0 it becomes unstable above v_{crit} = \sqrt{L/|K_{us}|}.

Proof. A 2\times 2 system is stable if and only if the trace of A is negative and the determinant positive. The trace is always negative. Computing the determinant and simplifying with (C_f + C_r)(a^2C_f + b^2C_r) - (aC_f - bC_r)^2 = C_fC_rL^2:

    \[ \det A = \frac{C_fC_rL^2}{mI_zv^2} - \frac{aC_f - bC_r}{I_z} = \frac{C_fC_rL}{mI_z}\left(\frac{L}{v^2} + K_{us}\right) \]

(using aC_f - bC_r = -\frac{LC_fC_r}{m}K_{us}). With K_{us} \ge 0 it is always positive; with K_{us} \lt  0 it vanishes at v^2 = L/|K_{us}| and beyond becomes negative: a real pole moves into the right half-plane. ∎

The transfer function steering → yaw rate has static gain \frac{r}{\delta} = \frac{v}{L + K_{us}v^2} and poles that move with speed. With the example data (computed with the code below): at 10 m/s \omega_n = 15.6 rad/s and \zeta = 0.97; at 20 m/s 9.1 rad/s and 0.84; at 30 m/s 7.2 rad/s and 0.70. The vehicle becomes slower and less damped as speed increases: this is what the driver feels as a “lazier, more swaying” response on the motorway.

Response to a 2 degree steering step at 20 metres per second
The yaw rate, blue, rises to about 10 degrees per second with a small overshoot; the lateral acceleration, red dashed and multiplied by 2.5, has an initial jump due to the front axle force and settles around 3.5 metres per second squared.

The tyre: Pacejka’s model

Pacejka’s magic formula describes measured forces with a few constants:

    \[ y(x) = D\sin\Big(C\arctan\big(Bx - E(Bx - \arctan Bx)\big)\Big) \]

with x the slip angle (for the lateral force) or the slip ratio (for the longitudinal one), and

  • D the peak value, about \mu F_z;
  • C the shape factor, which decides how much the curve drops after the peak (about 1.3 for lateral force, 1.6-1.9 for longitudinal);
  • B the stiffness factor: the slope at the origin is BCD, i.e. the cornering stiffness C_\alpha = BCD;
  • E the curvature, which shapes the curve near the peak.

In complete models (MF 5.2, MF 6.1, used in industry simulators) each coefficient in turn depends on load, camber and pressure through dozens of parameters measured on a test rig. Two effects matter even in simple models:

  • load sensitivity: C_\alpha and \mu grow less than proportionally with F_z. This is why two wheels with different loads give less total lateral force than two wheels with the same average load: lateral load transfer “takes away” grip from the axle where it is larger;
  • friction ellipse: the total force cannot exceed about \mu F_z, in any direction: \left(\frac{F_x}{\mu_xF_z}\right)^2 + \left(\frac{F_y}{\mu_yF_z}\right)^2 \le 1. Braking in a bend takes away lateral force: this is why ABS leaves the steered wheels able to generate force.
Longitudinal friction coefficient as a function of slip ratio
On dry asphalt, curve 1, the peak is around 1 at a slip of about 0.15; on wet, curve 2, around 0.7; on snow, curve 3, around 0.3 with a less marked maximum. Beyond the peak the wheel tends to lock.
The friction circle
The resultant force on the tyre, red arrow, must stay inside the circle of radius mu times N: if part of the grip is used to brake or accelerate, less remains for cornering.

Relaxation length. The tyre does not generate force instantly: it has to roll a distance \sigma (of the order of 0.3-0.5 m) for the deformation to settle. The model is a first-order lag \frac{\sigma}{v}\dot F_y + F_y = C_\alpha\alpha, with time constant \sigma/v: at low speed the tyre is “slow”, and this matters in stability control at 10-20 km/h.

Load transfers

Longitudinal. When braking with deceleration a, moment balance about the rear contact point, with the inertial force m\,a applied at the centre of gravity at height h, gives

    \[ N_f = mg\frac{b}{L} + \frac{m\,a\,h}{L},\qquad N_r = mg\frac{a}{L} - \frac{m\,a\,h}{L} \]

With the example data and a = 0.8g: at rest 8.49 kN front and 6.23 rear; when braking 10.98 and 3.74 kN. This is why front brakes are larger and brake distribution must adapt to load (electronic brake-force distribution EBD): locking the rear wheels first makes the vehicle unstable.

Lateral. In a bend, on each axle of track t, load moves from the inner to the outer wheel by \Delta N = \frac{m_{axle}\,a_y\,h_{eff}}{t}, with a split between the axles decided by the anti-roll bar stiffnesses. Because of load sensitivity, the axle with more transfer loses more cornering stiffness: stiffening the front bar makes the vehicle more understeering, stiffening the rear one more oversteering. It is the most used adjustment on the track.

Load transfer when braking
The vehicle moves to the right and brakes: the braking forces point backwards, and the resultant m a applied at the centre of gravity, at height h, loads the front axle, longer arrow N f, and unloads the rear one.

From the wheel to ABS

The dynamics of a wheel is I_w\dot\omega = T - R_eF_x, with T the torque (drive or brake) and F_x = \mu_x(\lambda)F_z. When braking, if the brake torque exceeds what the road can return at the peak of \mu_x, the wheel decelerates faster and faster and locks (\lambda = -1): the tyre slides, loses lateral grip and the vehicle no longer steers. ABS estimates slip from the wheel speeds and modulates brake pressure to keep it near the peak (about 0.1-0.2), with 5-15 cycles per second. Traction control does the same when accelerating by reducing engine torque or braking the spinning wheel.

ESC: stability control

ESC compares the measured yaw rate with a reference given by the bicycle model and limited by grip:

    \[ r_{ref} = \text{sat}\left(\frac{v}{L + K_{us}v^2}\,\delta,\ \pm\frac{\mu g}{v}\right) \]

The limit comes from a_y = v\,r \le \mu g. If r exceeds the reference the vehicle oversteers: the outer front wheel is braked, creating a straightening moment. If r is too small the vehicle understeers: the inner rear wheel is braked. The controller is typically a PI on the yaw error with activation logic, and it works together with ABS on the same brakes.

The kinematic model and pure pursuit

At low speed slip is negligible: wheels move where they point. The kinematic bicycle model, referred to the rear axle, is

    \[ \dot x = v\cos\psi,\qquad \dot y = v\sin\psi,\qquad \dot\psi = \frac{v}{L}\tan\delta \]

Theorem (pure pursuit). To reach, along a circular arc, a point G of the path at distance L_d and seen at an angle \alpha from the vehicle axis, the curvature \kappa = \frac{2\sin\alpha}{L_d} is needed, i.e. the steering \delta = \arctan\frac{2L\sin\alpha}{L_d}.

Proof. The required circle is tangent to the vehicle axis at the rear axle and passes through G. Its centre lies on the perpendicular to the axis, at distance R. The triangle formed by the centre, the rear axle and G is isosceles (two sides are radii), and the angle at the centre is 2\alpha (the angle between chord and tangent is half the central angle). So \frac{L_d}{2} = R\sin\alpha, \kappa = \frac{1}{R} = \frac{2\sin\alpha}{L_d}. In the kinematic model \kappa = \frac{\tan\delta}{L}, giving \delta. ∎

L_d is the only parameter: small gives precise but oscillating tracking, large gives smooth tracking that “cuts” corners. It is usually increased with speed, L_d = k\,v.

Pure pursuit geometry
The vehicle, blue, points upwards; point G on the green path is at distance L d and seen at angle alpha. The dashed orange arc, tangent to the vehicle axis and passing through G, has radius R: it is the curve the steering must impose.

ACC and LKA

ACC. With the distance d from the preceding vehicle and its speed v_p, the constant time-gap policy wants d_{des} = d_0 + h\,v. A simple controller commands an acceleration a_{cmd} = k_1(d - d_{des}) + k_2(v_p - v), then limited for comfort (about −3 to +2 m/s²) and implemented by the engine and brake torque control. In a platoon of vehicles all with ACC, the time-gap policy with large enough h guarantees string stability: a disturbance of the first vehicle is not amplified along the line.

The ACC safety distance
The ACC vehicle, blue, keeps from the vehicle ahead a distance d equal to a fixed part d zero plus the time gap h times its own speed.

LKA. The bicycle model is written relative to the lane: e_y the lateral offset from the centre, e_\psi the angle between the vehicle and the lane tangent, \kappa the road curvature. For small angles \dot e_y = v_y + v\,e_\psi and \dot e_\psi = r - v\kappa: with the state (e_y, \dot e_y, e_\psi, \dot e_\psi) a fourth-order linear model is obtained (Rajamani) on which a state-feedback (LQR) or predictive (MPC) controller is designed, with the curvature as a measured disturbance to be compensated in advance.

Error coordinates relative to the lane
The lateral offset e y is the distance of the vehicle from the lane centre, green; the heading error e psi is the angle between the vehicle axis and the lane direction.

Simulations in Python

The bicycle model, with gains and poles at various speeds and the step response:

import numpy as np

# parameters of an average car
m, Iz = 1500.0, 2500.0        # mass [kg], yaw moment of inertia [kg m^2]
a, b = 1.1, 1.5               # distances of the CG from the front and rear axle [m]
L = a + b                     # wheelbase [m]
Cf, Cr = 100e3, 120e3         # axle cornering stiffnesses [N/rad]

def matrices(v):
    """Linear bicycle model: state x = [vy, r], input delta (road-wheel steering)."""
    A = np.array([[-(Cf + Cr) / (m * v), -v - (a * Cf - b * Cr) / (m * v)],
                  [-(a * Cf - b * Cr) / (Iz * v), -(a**2 * Cf + b**2 * Cr) / (Iz * v)]])
    B = np.array([Cf / m, a * Cf / Iz])
    return A, B

Kus = m / L * (b / Cf - a / Cr)               # understeer gradient [rad/(m/s^2)]
vch = np.sqrt(L / Kus)                         # characteristic speed [m/s]
print(f"Kus = {Kus*1e3:.2f} mrad/(m/s^2) = {np.degrees(Kus):.3f} deg/(m/s^2)")
print(f"characteristic speed = {vch:.1f} m/s = {vch*3.6:.0f} km/h")

for v in (10.0, 20.0, 30.0):
    A, B = matrices(v)
    x_ss = -np.linalg.solve(A, B)              # steady state for delta = 1 rad
    lam = np.linalg.eigvals(A)
    wn = np.sqrt(np.prod(lam).real); zeta = -lam.real.sum() / (2 * wn)
    print(f"v = {v:4.1f} m/s: r/delta = {x_ss[1]:.3f} 1/s (formula {v/(L + Kus*v**2):.3f}), "
          f"poles {np.round(lam, 2)}, wn = {wn:.2f} rad/s, zeta = {zeta:.2f}")

# response to a 2 degree steering step at 20 m/s (Euler, 1 ms step)
v, dt = 20.0, 0.001
A, B = matrices(v)
x = np.zeros(2); d = np.radians(2.0)
t_hist, r_hist, ay_hist = [], [], []
for k in range(3000):
    dx = A @ x + B * d
    ay = dx[0] + v * x[1]                      # lateral acceleration
    x = x + dt * dx
    t_hist.append(k * dt); r_hist.append(x[1]); ay_hist.append(ay)
r = np.array(r_hist)
print(f"2 deg step at 20 m/s: final r {np.degrees(r[-1]):.2f} deg/s, "
      f"final ay {ay_hist[-1]:.2f} m/s^2, peak r {np.degrees(r.max()):.2f} deg/s")

Running it prints K_{us} = 3.37 mrad/(m/s²), characteristic speed 27.8 m/s (100 km/h), the gains 3.405, 5.068 and 5.330 s^{-1} equal to the formula, the poles and damping ratios quoted above, and for the step r = 10.14°/s, a_y = 3.54 m/s² with a peak of 10.44°/s.

Pure pursuit on a 3.5 m lane change at 10 m/s:

import numpy as np

L, v, dt = 2.6, 10.0, 0.01            # wheelbase [m], speed [m/s], integration step [s]
Ld = 8.0                               # look-ahead distance [m]
delta_max = np.radians(30)

# reference path: smooth 3.5 m lane change (sinusoid)
xs = np.linspace(0, 200, 4001)
ys = np.where(xs < 50, 0.0, np.where(xs > 90, 3.5, 1.75 * (1 - np.cos(np.pi * (xs - 50) / 40))))

x, y, psi = 0.0, 0.0, 0.0              # rear-axle position and heading
err = []
for k in range(int(15 / dt)):
    # path point at distance Ld ahead of the vehicle
    d = np.hypot(xs - x, ys - y)
    ahead = np.where((d >= Ld) & (xs > x))[0]
    gx, gy = xs[ahead[0]], ys[ahead[0]]
    alpha = np.arctan2(gy - y, gx - x) - psi            # angle towards point G
    delta = np.arctan(2 * L * np.sin(alpha) / Ld)        # pure pursuit law
    delta = np.clip(delta, -delta_max, delta_max)
    # kinematic bicycle model (referred to the rear axle)
    x += v * np.cos(psi) * dt
    y += v * np.sin(psi) * dt
    psi += v / L * np.tan(delta) * dt
    err.append(y - np.interp(x, xs, ys))
err = np.array(err)
print(f"max lateral error {abs(err).max():.2f} m, final {abs(err[-1]):.3f} m")

The maximum lateral error is 7 cm and at the end it vanishes. For more complete models industry uses commercial simulators (IPG CarMaker, VI-grade, CarSim, MATLAB’s Vehicle Dynamics Blockset); in the open world there are CARLA (autonomous driving, with a Python API) and the Modelica vehicle models, usable with OpenModelica.

The exercises on understeer, tyres, load transfer, braking and ADAS functions are on the exercise page linked at the top.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
0
Would love your thoughts, please comment.x
()
x