Skip to content

MPC "constrained" solve is post-hoc clipping, not a constrained QP #254

Description

@gabrielfrasantos

Severity: high
Domain: controllers
Status: VERIFIED — read + hand-traced against a210d34 on 2026-08-10
Suggested labels: bug, numerical-correctness, controllers

Summary

Mpc::ComputeControl() solves the unconstrained system and then clamps each control element
independently. For a coupled Hessian this does not produce the constrained optimum. There are also
no state or output constraints, despite the documentation presenting constrained MPC.

Location

numerical/controllers/implementations/Mpc.hpp

Evidence

auto uOptimal = solvers::SolveSystem<T, TotalControlDim, 1>(hessian, negG);
ApplyConstraints(uOptimal);
for (std::size_t k = 0; k < ControlHorizon; ++k)
    for (std::size_t i = 0; i < InputSize; ++i)
    {
        auto& val = u.at(k * InputSize + i, 0);
        val = std::max(std::min(val, constraints.uMax->at(i, 0)), constraints.uMin->at(i, 0));
    }

Each element is clamped in isolation; no active-set re-optimisation occurs.

Trace

H = [[2,1],[1,2]], g = [−4,−2], constraint u₁ ≤ 1:

unconstrained optimum:  H⁻¹(−g) = [2, 0]
clipped:                [1, 0]
true constrained optimum (active set u₁ = 1):
    minimise over u₂ with u₁ fixed at 1  =>  2u₂ + 1 = 2  =>  u₂ = 0.5
                            [1, 0.5]

Clipping loses the compensation that the remaining free variables should provide.

Why tests do not catch it

The MPC test asserts only that the result lies within [uMin, uMax]. A clipped, non-optimal
sequence satisfies that.

Suggested fix

Implement a bounded active-set box-QP solver (no heap, fixed iteration cap) and use it in place
of SolveSystem + clamp. Add a test comparing against an analytically known constrained optimum
such as the one above.

SolveSystem is also called without a status check — see issue 004.

Notes

Depends on the "Priority 0" box-QP recommendation in the audit report. Related: issue 010 (reference
tracking).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions