Skip to content

fix(qp): align fast/cvxopt objective convention, reject dropped equalities, tune PDIPM - #372

Merged
bardhh merged 1 commit into
mainfrom
fix/qp-solver-stack
Aug 10, 2026
Merged

fix(qp): align fast/cvxopt objective convention, reject dropped equalities, tune PDIPM#372
bardhh merged 1 commit into
mainfrom
fix/qp-solver-stack

Conversation

@bardhh

@bardhh bardhh commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Correctness

  • fast and cvxopt backends solved the wrong objective. The registry convention is min x'Hx + f'x, but both passed H straight into their native 0.5 x'Px + q'x form, tracking 2·u_nom instead of u_nom. Hidden in safety tests because the active barrier pins the solution. Fixed with P = 2H at the registry boundary; all four backends now agree on the audit probe (u_nom=[1.0,-0.5][1.0,-0.5]) and on a new cross-solver parity suite (interior / single-active / corner / random / end-to-end CBF-filter cases).
  • fast silently dropped equality constraints, so MPC through get_solver("fast") returned dynamics-violating trajectories with success status. Now raises NotImplementedError pointing at jaxopt/casadi.
  • test_fast_qp.py expected [2,4] — the bug's answer. The correct minimizer of x'x − 2x₀ − 4x₁ under this convention is [1,2].

PDIPM performance (1.64× per solve, bit-identical results)

  • Factor the reduced Newton matrix once per Mehrotra iteration (predictor and corrector differ only in RHS); residuals computed once for the convergence check and the step.
  • DEFAULT_MAX_ITER 25 → 16, calibrated on 250 benign + 250 ill-conditioned near-boundary QPs: deviation vs max_iter=60 is exactly 0.0 on both families. (12 fails accuracy on 30/250 hard QPs precisely when the barrier is nearly violated — table in qp_solver_pdipm.py.)
  • fori_loop retained deliberately: while_loop has no reverse-mode AD rule and the differentiable-CBF-QP work takes gradients through this solver. The max_iter=25 pins in solve_qp_fast/fast_solver now defer to the solver default.

Verification

  • 527 passed, 5 skipped on this branch (baseline was 492).
  • New tests fail against pre-fix code: 21 failures when the source is reverted (parity, guard, controller regressions), so they pin the fix rather than passing vacuously.
  • Reverse-mode jax.grad through the solver verified finite.

Merge order: this is PR 1 of a 4-PR stack (merge top-down).

…ities, tune PDIPM

The registry documents min x'Hx + f'x, but the fast (PDIPM) and cvxopt
backends passed H straight into their native 0.5 x'Px + q'x form, so both
tracked 2*u_nom instead of u_nom (hidden in safety tests because the active
barrier pins the solution). Convert P = 2H at the registry boundary for
both; all four backends now agree on the audit probe and a new cross-solver
parity suite (interior, single-active, corner, random, and end-to-end
CBF-filter cases).

The fast backend also silently ignored a_mat/b_vec equality constraints, so
MPC through get_solver('fast') returned dynamics-violating trajectories with
success status; it now raises NotImplementedError pointing at jaxopt/casadi.

PDIPM: factor the reduced Newton matrix once per Mehrotra iteration
(predictor and corrector differ only in RHS), compute residuals once for
the convergence check and the step, and lower DEFAULT_MAX_ITER 25 -> 16.
16 was calibrated on 250 benign + 250 ill-conditioned near-boundary QPs:
deviation vs max_iter=60 is exactly 0.0 on both families (12 fails on 30/250
hard QPs precisely when the barrier is nearly violated; table in-file).
Measured 1.64x per solve. fori_loop retained: reverse-mode AD has no
while_loop rule and the differentiable-CBF-QP work grads through this
solver. The max_iter=25 pins in solve_qp_fast and fast_solver now defer to
the solver default.

test_fast_qp.py:112 expected [2,4], the registry-convention bug's answer;
the correct minimizer of x'x - 2x0 - 4x1 is [1,2] (2Hx + f = 0).
@bardhh
bardhh merged commit 9d4ab3e into main Aug 10, 2026
4 checks passed
@bardhh
bardhh deleted the fix/qp-solver-stack branch August 10, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant