This repository contains ROS 2 & Python implementations of Model Predictive Control (MPC) for the F1TENTH.
The nodes subscribe to the car state (odometry), track a waypoint-defined racing line, solve an MPC problem at each control tick, and publish commands to /drive.
Non-linear and Linear MPC (CasADi + IPOPT) with linear corridor visualization
A dual-mode MPC controller implemented with CasADi.

-
Subscribes:
/ego_racecar/odom(nav_msgs/Odometry) -
Publishes:
/drive(ackermann_msgs/AckermannDriveStamped) -
State:
$[x, y, v, yaw]$ -
Control:
$[a, \delta]$ (acceleration, steering angle) -
Horizon:
TK = 2steps withDTK = 0.2 s(≈0.4 stotal horizon)
- State tracking penalty:
with terminal cost
- Input magnitude penalty:
- Input rate penalty:
- Nonlinear kinematic bicycle rollout enforced as equality constraints:
- Solver: IPOPT via CasADi Opti
- Warm-start: Shifts the previous
$(x, u)$ solution forward one step each tick - If the solve fails, it attempts to use the last IPOPT iterate (
opti.debug.value(...)) as a best-effort fallback
- Dynamics are linearized each tick into:
- The optimization is still built and solved with CasADi Opti + IPOPT
- Warm-start: Shifts the previously solved
$(x, u)$ trajectory forward
-
Waypoints are loaded from
waypoints.csv, with:- Yaw unwrapping for continuity
- Loop-closure bridging if the last-to-first waypoint gap is large
- Resampling to uniform spacing (
dlk = 0.03 m)
-
Speed reference comes from
calc_speed_profile():- Curvature-based speed scaling
- Forward - Backward acceleration passes to avoid impossible step drops in speed at corner entry
-
The node publishes corridor wall markers computed from the reference path tangent normals:
/ego_racecar/corridor_left/ego_racecar/corridor_right
-
Corridor half-width:
-
D_MAX = 1.0 m(stored as_corridor_d_max)
-
-
In the CasADi LMPC path, the corridor is implemented as a hard linear constraint at each horizon step
$t \ge 1$ :
where
nmpc_casadi_performance.csvifUSE_NMPC=Truelmpc_casadi_performance.csvifUSE_NMPC=False
Linear MPC using OSQP with hard corridor constraints, and a Non-linear MPC using SLSQP with corridor penalty
A linear MPC controller solved as a sparse QP using OSQP.

- Subscribes:
/ego_racecar/odom - Publishes:
/drive
-
State:
$[x, y, v, yaw]$ -
Control:
$[a, \delta]$ -
Sparse QP form:
with decision vector:
- Initial state equality
$x_0 = x_{\text{current}}$ - Linearized dynamics equalities
- Speed bounds, acceleration bounds, steering bounds
- Steering-rate based bounds
$|\delta_{t+1} - \delta_t| \le \dot{\delta}_{\max} DT$ -
Hard corridor constraint for
$t = 1, \dots, T$ :
- Corridor half-width:
D_MAX = 1.0 m
- Shifts the previous
$x, u$ solution forward each tick - Reuses OSQP dual multipliers (
res.y) when dimensions match
- Implemented with
scipy.optimize.minimize(..., method='SLSQP') - Non-linear dynamics equality constraints
- Soft corridor enforcement via a quadratic hinge penalty:
lmpc_cvxpy_performance.csvnmpc_cvxpy_performance.csv
Linear MPC (OSQP)
A linear MPC baseline using OSQP.

- State:
$[x, y, v, yaw]$ - Control:
$[a, \delta]$ - Horizon:
TK = 4,DTK = 0.1 s
- Initial state equality
- Linearized dynamics equalities
- Speed bounds, acceleration bounds, steering bounds
- Steering-rate based bounds
- Shifts the previous solution forward
- Stores previous dual multipliers when available
lmpc_performance.csv
All controllers use a CSV file:
waypoints.csvloaded from the working directory and contains waypoints.
- Add the controller files inside the scripts directory inside the mpc folder of F1tenth. Start your virtual environment.
- Launch the F1TENTH gym bridge for ROS2 (through instructions on the dev-humble branch of F1Tenth gym ROS) to simulate the ego-vehicle on a Foxglove window.
- Once the simulator is up and running, in another terminal, source the virtual environment and ROS2. Then you run the controller:
ros2 run mpc mpc_linear_bordered_casadi.py
# or
ros2 run mpc mpc_linear_bordered_cvxpy.py
# or
ros2 run mpc mpc_linear_cvxpy.py