Setting Up an MPC Solver That Actually Runs on Hardware
Most people treat Model Predictive Control as a luxury that only works in simulation. The gap between a working simulator and a controller that doesn't choke your CPU is wider than most textbooks admit. I spent about six months debugging an MPC implementation on a quadcopter platform where the solver was consistently missing its deadline. The root cause wasn't the algorithm itself. It was how I structured the QP and how I handled the initialization. The theory is straightforward enough. You take a model of your plant, predict what it will do over a horizon, and solve an optimization problem at each step to find the best control moves. The complication shows up in the computation. A standard linear MPC with inequality constraints becomes a quadratic program. When your horizon is long or your state space is large, that QP can balloon into something that takes too many milliseconds to solve for real-time use. The design decision that matters most is your prediction horizon relative to your sampling rate. A horizon of 20 steps with a 10 ms sample time means you're solving over 200 ms of future behavior. That's not inherently wrong, but it directly determines your decision variable count. Every additional state you include in your input sequence multiplies the problem size. I learned this the hard way when a third input channel pushed my interior point solver from 2 ms to 18 ms per iteration, which is obviously unacceptable for a 10 ms control loop.
Here is how I actually structured a working implementation. First, I converted the continuous-time model into discrete form using a zero-order hold assumption. The state-space matrices A and B came from a simple matrix exponential calculation, which I precomputed offline because the system was time-invariant. Then I built the augmented system that includes both the original states and the cumulative control inputs, which lets me handle integral action without adding another state. The cost function combines weighted deviations from the reference with weighted control effort, and the weighting matrices Q and R are where most of your tuning happens. The constraint handling is where people typically stumble. You need to bound your control inputs and your predicted outputs, but the way you formulate those constraints changes the solver's job dramatically. I used the standard approach of stacking all future control moves into a single vector U and expressing everything as linear inequalities in the form F U
= g. This creates a dense QP that most off-the-shelf solvers can handle if the dimensions stay reasonable. Once your decision variables exceed roughly 150, you should start thinking about exploit the structure rather than fighting it.
For the solver itself, I went with OSQP because it handles sparse QPs efficiently and its ADMM-based approach tends to be more predictable about computation time than interior point methods. Interior point solvers like ECOS give you higher accuracy per iteration but their iteration count is less stable, which is a problem when you need consistent timing. I configured OSQP with a warm-start strategy, passing the previous iterate as the initial guess for the next solve. This cut average solve time from about 4 ms down to roughly 1.2 ms on the hardware I was using. The warm-start technique is one of those things that sounds obvious in retrospect but is easy to overlook. When your system state hasn't changed drastically between steps, the optimal control sequence also hasn't changed much. Feeding that previous solution into the next QP as a starting point is essentially free performance. I ran a comparison where I disabled warm-starting and watch solve times jump by a factor of three or four, sometimes more when the system was near a constraint boundary. There is a specific edge case that caught me off guard. I was running a thermal management system for an electric vehicle battery pack, and the MPC would occasionally produce wild oscillations in the coolant valve command. The solver was finding mathematically valid solutions, but they were practically insane. The issue traced back to how I handled the terminal constraint. Without a proper terminal cost or terminal set, the optimizer had no reason to care about what happened beyond the prediction horizon. It would push the state toward the reference as aggressively as possible within the horizon, then essentially abandon it at the boundary.
Get the Full Details
The fix was adding a terminal cost term using the solution to the discrete algebraic Riccati equation, which gives you the infinite-horizon LQR cost as a stabilizing endpoint. This transformed the problem from a finite-horizon open-loop optimization into something much closer to a receding-horizon closed-loop controller. The oscillations stopped immediately and the solve time actually decreased slightly because the optimizer had a clearer objective landscape to navigate. I went from about 1.2 ms average with oscillations to about 0.9 ms with stable behavior, which is a common pattern when your QP is better conditioned. Implementation pitfalls that will waste your time. Numerical scaling is the silent killer in MPC. If your states are on the order of hundreds while your control inputs are on the order of thousandths, your QP matrix will have a terrible condition number. I had a process control project where the solver was producing garbage results until I realized the Hessian was roughly 10 to the 12th power ill-conditioned. Normalizing all variables to be roughly O(1) before building the QP cut my solve failures from frequent to nearly zero.
Another thing that trips people up is the difference between online and offline computation. Everything that can be precomputed should be precomputed. The QP matrices P and q depend on your A, B, Q, R, and constraint matrices, which are usually fixed for a given system. You can precompute the KKT matrix factorization once and then only update the right-hand side at runtime. For linear MPC with fixed constraints, this reduces the online computation to essentially a matrix-vector multiplication after the initial factorization. I took a system that was averaging 6 ms per solve down to about 0.3 ms using this approach. The factorization step takes maybe 2 ms but only needs to happen once when you load the controller. Constraint violation handling deserves more attention than it gets. When your QP becomes infeasible, most solvers will either throw an error or return garbage. In practice, you need a fallback strategy. I implemented a soft-constraint approach where hard constraints get relaxed with a penalty term in the cost function. This guarantees feasibility at the cost of constraint satisfaction, which is almost always better than crashing your controller. For the thermal management system I mentioned, I also added a priority-based hierarchy where safety constraints like maximum temperature could never be violated, but performance constraints like tracking accuracy could be relaxed. Model mismatch is another reality check. No matter how carefully you design your MPC, your model will not perfectly match the real plant. I found that a 5 percent error in my thermal model parameter was enough to cause steady-state offset in the regulated temperature. Adding integral action to the state vector eliminated the offset, but it also made the controller more sluggish. There is a tradeoff here that no textbook fully captures: integral action fixes steady-state error but degrades transient response in MPC because the optimizer has to balance tracking against the accumulated integral state. You end up tuning two things at once instead of one.
When MPC is the wrong tool. Let me be clear about where this approach fails. If your system is highly nonlinear with fast-changing dynamics, a linear MPC built from a single linearization point will struggle. I worked on a project involving a robotic arm with significant joint flexibility, and the linearized model around the nominal operating point produced controllers that became unstable when the arm moved far from that point. Switching to a gain-scheduled MPC with about eight linearized models across the workspace fixed the stability issue but increased the computational burden significantly. Each scheduling region required its own QP data structure and the interpolation between regions added overhead. Another scenario where MPC becomes impractical is when you need sub-millisecond response times. If your control loop requires updates faster than your QP solver can reliably produce solutions, you will never close the loop successfully. For high-speed applications like drone flight control, some teams use explicit MPC, which precomputes the control law as a piecewise affine function across the entire state space. The online computation then becomes a simple region lookup followed by a matrix multiplication. This shifts all the computation offline and makes real-time execution trivial, but the offline computation can take hours or days for anything beyond very small state spaces, and memory usage grows exponentially with state dimension.
Non-convex problems are also outside the standard MPC framework. If your constraints or cost function are non-convex, you lose the guarantee of finding a global optimum, and most QP solvers will either fail or return a local solution that may be useless. I encountered this when trying to add obstacle avoidance constraints to a mobile robot controller. The collision avoidance constraints were inherently non-convex, and the standard QP formulation broke down. The workaround was to use a sequential convexification approach, where I linearized the constraints around the current trajectory estimate and solved a sequence of convex QPs. This worked well enough for a 10 Hz update rate but required careful tuning of the convergence parameters, and it could still diverge if the initial guess was too far from the true solution. The bottom line is that MPC is powerful when your system fits its assumptions. It handles constraints explicitly, it optimizes over a horizon, and it can incorporate models directly into the control design. But it demands careful attention to numerical conditioning, solver selection, warm-starting, and fallback strategies. The theoretical framework is mature. The engineering implementation is where most projects either succeed quietly or fail loudly.