A track-coordinate MPC example where the ego car follows an L-shaped circuit
while avoiding other cars (treated as moving obstacles) with density constraints.
Run
Run the racing density MPC example on the L-shaped track.
python examples/racing/density_mpc/density_mpc.py
System Dynamics
The racing controller predicts in curvilinear track coordinates. The
state keeps the vehicle dynamics, the heading error relative to the
track tangent, the progress along the centerline, and the lateral
error from the centerline.
Here \(v_x\) and \(v_y\) are body-frame velocities, \(\omega_z\) is
yaw rate, \(e_\psi\) is the heading error, \(s\) is track progress,
and \(e_y\) is lateral error. The page visualizes the predicted
states in global coordinates, but the MPC optimizes the track state
above.
Nonlinear Bicycle Model
The code keeps a nonlinear dynamic bicycle model available. Let
\(\kappa(s)\) denote the track curvature, and define the front and
rear slip angles using a small lower bound on \(v_x\) in the
implementation to avoid division by zero.
The default racing MPC uses the discrete-time linearized model copied
into the example configuration. This keeps the optimizer lightweight
while preserving the same state and control notation.
Conceptually, this model comes from linearizing the nonlinear bicycle
dynamics about a nominal racing operating point
\((\bar x,\bar u)\), then discretizing with the MPC sample time
\(\Delta t\). If \(f_c(x,u)\) denotes the continuous-time nonlinear
curvilinear dynamics above, the local perturbation model is
\(\dot{\tilde x}=A_c\tilde x+B_c\tilde u\), where
\(\tilde x=x-\bar x\) and \(\tilde u=u-\bar u\).
In this example, the nominal point corresponds to driving along the
track centerline at the target speed with no lateral motion, no yaw
rate, no heading error, no steering, and no acceleration.
The implementation stores the resulting discrete matrices directly as
\(A\) and \(B\) in the racing configuration and uses them in absolute
coordinates as \(x_{k+1}=Ax_k+Bu_k\). Any constant affine offset from
the chosen operating point is absorbed into this fitted discrete
model, so the online MPC only propagates with the fixed matrices.
Change of Coordinates
The track provides a centerline map from progress \(s\) and lateral
offset \(e_y\) to global position. Let \(p_c(s)\) be the centerline
point, \(\psi_c(s)\) be the centerline heading, and \(n_c(s)\) be the
unit normal. The global pose used for animation is obtained from the
curvilinear state as follows.
The density has one term for staying inside the track and one term for
each moving obstacle car. The total safety density is the product of
these terms, so the density decreases near the track boundary or near
another car.
Track safety is measured by lateral clearance from the track boundary.
The function \(h_{\mathrm{track}}\) is positive when the car has more
than the configured margin, and \(s_{\mathrm{track}}\) normalizes the
transition band used by the bump function.
Each obstacle is represented by a superellipse in the \((s,e_y)\)
frame. The longitudinal difference wraps around the closed lap so a
car near the start line can still be close to a car near the end of
the lap.
The unsafe obstacle set is \(h_i(x_k)\lt0\). The value \(s_i(x_k)\) is
the normalized density coordinate: \(s_i=0\) at the safety boundary
and \(s_i=1\) at the outer edge of the transition band.
Bump and Sigmoid Densities
The racing example compares two obstacle-density maps. Both use the
same superellipse value \(z_i(x_k)\), safety boundary \(1+m_o\), and
transition width \(\Delta_o\), but they shape the transition
differently.
In the generic bump notation, \(r\) is the scalar safety coordinate
being shaped, \(r_1\) is the inner boundary where density is zero,
\(r_2\) is the outer boundary where density reaches one, and
\(\mu=(r-r_1)/(r_2-r_1)\) is the normalized transition coordinate.
For obstacle \(i\), \(r=z_i(x_k)\), \(r_1=1+m_o\), and
\(r_2=1+m_o+\Delta_o\). For the track boundary,
\(r=c_{\mathrm{track}}(x_k)\), \(r_1=m_{\mathrm{track}}\), and
\(r_2=m_{\mathrm{track}}+\Delta_{\mathrm{track}}\).
Bump Density
The bump density gives a compact transition: it is zero inside
the safety boundary, one outside the transition band, and smooth
in between. In the obstacle case, \(\mu_i=0\) exactly on the
inflated safety boundary and \(\mu_i=1\) at the outer edge of the
sensing/transition band.
The sigmoid density replaces the compact transition with a
logistic map, making the change less abrupt while keeping a
useful gradient near the unsafe region.
At every simulation step, the controller solves an MPC over steering,
acceleration, and nonnegative density-rate slack variables. The
objective tracks the desired speed and centerline, penalizes control
effort and control-rate changes, and discourages low-density states.
Both rollouts use the same L-shaped track, moving obstacle cars, MPC
cost, and prediction horizon. The only change is the obstacle density
map: compact bump versus sigmoid. In this setup, the bump density is
clearly better: it gives an earlier and cleaner passing maneuver with
the default parameters. The sigmoid density can work, but it is much
more sensitive to the transition width and sharpness, so tuning it can
be tricky. The compact bump density works much more reliably out of
the box.
Bump Density
The bump density creates a crisp safety transition around the
obstacle cars, so the MPC commits to the pass early and keeps the
trajectory smooth.
Sigmoid Density
The sigmoid density gives a softer transition. That smoothness can
be useful, but in this racing example it reacts more gradually and
needs more careful tuning to match the bump result.