v0.0.6
qtqp 0.0.6 is the largest release since the solver was published. It adopts Clarabel's termination criteria so that results are directly comparable with Clarabel, replaces the initialization with Clarabel's, rescales the problem so the factorized KKT block stays unit-scaled, adds certified warm starts, Gondzio centrality correctors, an adaptive endgame step schedule and GMRES-based iterative refinement, and removes the unproven heuristics that had accumulated around the linear algebra. At matched tolerances it now solves more problems than Clarabel 0.11.1 on Maros-Meszaros, MIPLIB and NETLIB, with about 11% fewer iterations on Maros-Meszaros, the same number on the LP collections, and about a third of the refinement work per KKT solve.
Correction (6 September 2026). The qtqp iteration counts originally published in these notes were the zero-based index of the last logged iteration, one below the number of completed IPM steps that Clarabel reports. The tables and text below now count completed steps for both solvers (qtqp reports them as Solution.iterations since #131), which raises qtqp's medians by one and changes the iteration ratios; statuses, solve counts, certificate counts, refinement statistics and times were never affected.
Benchmark against Clarabel 0.11.1 at matched termination criteria and matched linear algebra
Both solvers at their defaults, which are now the same criteria: all tolerances 1e-8, max_iter = 200, 1830 s wall clock per problem, with one deliberate exception so that times compare algorithms rather than factorization libraries: both use single-threaded, AMD-ordered simplicial LDL of the qdldl family (qtqp linear_solver=QDLDL; Clarabel direct_solve_method="qdldl", max_threads=1, instead of its default multithreaded faer solver). MIPLIB is the 173 MIPLIB 2017 root relaxations with MPS files under 20 MB. "Refinement steps" are extra factorization solves per KKT system beyond the first, averaged over a run; medians are over solved problems, excluding the nine equality-only Maros-Meszaros instances both initializations solve at iteration zero; the ratios are medians over problems both solvers solve.
| Maros-Meszaros (138) | MIPLIB (173) | NETLIB feasible (93) | NETLIB infeasible (29) | |
|---|---|---|---|---|
| qtqp / Clarabel | qtqp / Clarabel | qtqp / Clarabel | qtqp / Clarabel | |
| solved to 1e-8 | 131 / 126 | 171 / 152 | 93 / 91 | 1 / 2 |
| almost solved (reduced tolerances) | 7 / 10 | 1 / 18 | 0 / 1 | 1 / 0 |
| infeasibility or unboundedness certificate | 0 / 1* | 0 / 0 | 0 / 1* | 27 / 27 |
| hit the iteration limit | 7 / 1 | 1 / 1 | 0 / 0 | 1 / 0 |
| time limit or solver error | 0 / 0 | 1 / 3 | 0 / 0 | 0 / 0 |
| median completed IPM steps (solved) | 12 / 13 | 18 / 18 | 20 / 20 | 51† / 48.5† |
| median refinement steps per KKT solve | 0.44 / 1.29 | 0.39 / 1.18 | 0.33 / 1.54 | 3.92† / 1.68† |
| shifted geometric mean time, s (shift 10) | 1.51 / 0.68 | 5.38 / 3.75 | 0.44 / 0.15 | 0.22 / 0.06 |
| solved by both / only qtqp / only Clarabel | 126 / 5 / 0 | 151 / 20 / 1 | 91 / 2 / 0 | 1 / 0 / 1 |
| median iteration ratio qtqp/Clarabel (both solved) | 0.89 | 1.00 | 1.00 | 1.21† |
| median time ratio qtqp/Clarabel (both solved) | 5.6 | 2.6 | 4.2 | 3.4† |
* Clarabel's certificates on POWELL20 (Maros-Meszaros) and dfl001 (NETLIB) are on problems that HiGHS solves to optimality. † One or two problems, not a distribution.
qtqp solves more problems on every feasible collection and, on the problems both solve, needs about 11% fewer iterations on Maros-Meszaros, the same number on the LP collections, and about a third of the iterative-refinement work per KKT solve. Clarabel's shortfall is mostly its almost-solved endings (18 on MIPLIB), three solver errors, and the two certificates on feasible problems; qtqp returns no certificate on any feasible collection. qtqp's own failures are the six LISWET instances and BOYD2 (refinement at its budget on near-singular endgame systems), cbs-cta (a step-size stall), cplex2 (an endgame divergence after reaching 1e-10 residuals; the problem has an optimal solution despite being in the infeasible collection) and ns1116954 (wall clock). Both solvers certify the same 27 infeasible NETLIB problems.
Wall clock with matched linear algebra separates by size: on the largest instances the two solvers are equal to within a few percent (supportcase10 1656 vs 1657 s, ex9 1173 vs 1169 s, opm2-z10-s4 586 vs 557 s, supportcase40 50 vs 50 s), because the factorization dominates and the two qdldl implementations cost the same; on small problems qtqp's per-iteration Python overhead dominates, which is what the geometric means and the 2.6-5.6x median ratios measure. With each solver's default linear algebra instead (Accelerate for qtqp on macOS, Pardiso elsewhere; faer with all threads for Clarabel), the large instances are again at parity: CVXQP3_L, supportcase40, CONT-300 and satellites2-60-fs solve in 1.6, 1.7, 6.9 and 2.1 s with qtqp against 1.4, 2.5, 5.0 and 2.3 s with Clarabel on ten threads.
Warm starts
Two experiments measure the new certified warm start. On a closed-loop model-predictive control sequence (8 states, 3 inputs, horizon 40, box constraints, 60 steps, each step a QP with 440 variables, 320 equality and 880 inequality rows) a cold start needs a median of 7 completed steps per step, a warm start from the previous step's solution 4, and a warm start from that solution shifted forward by one stage 3, with the certificate accepting 59 of 59 shifted points. On the 224 Maros-Meszaros and feasible NETLIB problems qtqp solves cold, warm-starting a copy whose b and c entries are each perturbed by a relative amount up to δ from the unperturbed solution gives, over the copies both starts solve:
| δ | copies both solve | warm start accepted | median completed steps cold / warm | median ratio warm/cold | quartiles |
|---|---|---|---|---|---|
| 0 | 224 | 215 / 224 | 16 / 2 | 0.12 | 0.07 / 0.19 |
| 1e-3 | 204 | 214 / 224 | 16 / 6 | 0.36 | 0.25 / 0.54 |
| 1e-2 | 196 | 213 / 224 | 16 / 8 | 0.52 | 0.36 / 0.67 |
| 1e-1 | 181 | 205 / 224 | 16 / 9 | 0.64 | 0.50 / 0.79 |
This table is from a re-run of the experiment on main d217b8b with the corrected step counter and reproducible per-problem seeds (see the correction note above); the release-time records stored the zero-based label, which cannot distinguish a warm start that needed no step from one that needed one.
Copies the perturbation made infeasible or unbounded are excluded; on every one of them cold and warm starts return the same certificate. Nine of the non-accepted points at each level are the equality-only problems, on which the initialization solves the problem outright and the warm start is ignored. On no copy does a warm-started solve fail where the cold start succeeds.
Termination criteria and statuses (breaking)
- qtqp now uses Clarabel's termination criteria term for term (#122, #118, #110): relative primal and dual residuals normalized by
max(1, ||b||_inf + ||x|| + ||s||)andmax(1, ||c||_inf + ||x|| + ||y||), the duality gap tested absolutely or relative tomax(1, min(|p|, |d|)), solutions accepted only when κ/τ ≤ 1, and certificates considered only when κ/τ >certificate_ktratio(default 1e9). Because qtqp eliminates κ through τκ = μ, the ratio is evaluated as μ/τ². - The tolerance parameters are renamed to Clarabel's:
tol_feas,tol_gap_abs,tol_gap_rel,tol_infeas_abs,tol_infeas_rel, all defaulting to 1e-8. The oldatol,rtol,atol_infeas,rtol_infeasare gone. - New statuses:
SolutionStatus.ALMOST_SOLVED(#97, #98), returned with the best iterate meeting the same criteria at reduced tolerances (1e-4 feasibility, 5e-5 gap) when the iteration budget or the linear algebra stops progress, andSolutionStatus.HIT_MAX_ITER(#67).SolutionStatus.FAILEDis now returned for numerical failures instead of an exception (#93). - Infeasibility and unboundedness certificates are judged on the unit-slope ray with data-scaled residuals (#110, #118).
Algorithm
- Initialization (#99, #112, #123): the initial point is Clarabel's, the solution of
[P, A'; A, -H][x; y] = [-c; b]withHthe identity on inequality rows and zero on equality rows (separate primal and dual solves for LPs), shifted interior. Equality rows are satisfied exactly by the initial point, so problems with only equality constraints are solved at iteration 0 (#120, #123). - Equilibration (#124): Ruiz on the KKT block is followed on every pass by two scalars, one on
(b, c)taking||b||_infto 1 and one on(P, c)takingmax(||c||_inf, max|P_ij|)to 1, both kept within[1e-4, 1e4].bandcnever enter the row and column scalings, so the factorized block keeps unit rows and columns; this alone turned ten iteration-limit endings into solves. The previousequilibrate: boolis replaced byequilibration_strategywith valuesRUIZ(default),AUGMENTEDandNONE. - Certified warm starts (#107, #119):
solve(warm_start=(x, y, s))accepts a point from a nearby problem, embeds it interior at several centering shifts, and starts from the best candidate only when the distance-to-path certificate is belowwarm_start_threshold(default 100); otherwise the standard initialization is used, so a warm start is never worse than a cold start by more than three matrix-vector products per candidate.solver.warm_lambdaandsolver.warm_acceptedreport the decision. - Gondzio centrality correctors (#81): one corrector per iteration by default (
max_centrality_correctors), each one back-solve on the existing factorization, accepted only when it lengthens the step. 14-16% fewer median iterations on every benchmark collection. - Adaptive step schedule (#82, #108): once μ < 1e-3 the fraction-to-boundary factor rises from
step_size_scale(0.99) tomin(0.9999, 1 - 10 μ), unlocking the superlinear endgame (adaptive_step_size, default on). - τ solve (#60, #69, #97, #124): the exact quadratic is always attempted; its fallback now solves the same quadratic with the quadratic term of the objective linearized at the current iterate, keeping the first-order step as a last resort.
- The Mehrotra corrector's fused slack update is unconditional (#113); the current iterate's μ is reported (#64).
Linear algebra
- Iterative refinement defaults to right-preconditioned GMRES with the factorization as preconditioner and a full-budget restart length of 20 (#109);
max_iterative_refinement_stepsnow defaults to 20 (was 50) and counts the initial solve. Richardson refinement remains available throughrefinement_strategy. - Static regularization (
min_static_regularization, 1e-8) is applied in the factorized matrix only; refinement targets the true system, so the returned direction solves the unregularized system tolinear_solver_atol + linear_solver_rtol * ||rhs||. - The factorization retry ladder was removed (#121); refinement stalls roll back materially degrading corrections (#88, #115); GMRES Givens breakdown is guarded and duplicate CSC entries are canonicalized (#92).
- Dense CPU and GPU KKT solvers were optimized (#77, by @smitlg); a
CupyDenseSolvercrash was fixed (#87).
Presolve, validation and diagnostics
- A presolve drops trivially satisfied inequality rows (infinite bounds) and postsolve restores full-size
yands(#61, #95). - The constructor validates symmetry of
Pwith a data-relative tolerance (#70, #91) and finiteness ofaandc(#90). - With
collect_stats=True, every iteration logs Clarabel-form residuals (res_primal,res_dual,gap_rel,ktratio), the linear-solve statistics of each solve including refinement counts and the τ method used, and the distance-to-path certificatedelta_pathwith its barrier-metric companiondelta_path_local;lambda_initmeasures the initial point (#84, #106, #111). Stats are Python floats (#63).
API summary
| 0.0.5 | 0.0.6 |
|---|---|
atol=1e-7, rtol=1e-8 |
tol_feas=1e-8, tol_gap_abs=1e-8, tol_gap_rel=1e-8 |
atol_infeas=1e-8, rtol_infeas=1e-9 |
tol_infeas_abs=1e-8, tol_infeas_rel=1e-8, plus certificate_ktratio=1e9 |
equilibrate=True |
equilibration_strategy=EquilibrationStrategy.RUIZ |
max_iterative_refinement_steps=50 |
max_iterative_refinement_steps=20, refinement_strategy=RefinementStrategy.GMRES, gmres_restart=20 |
warm_start=None, warm_start_threshold=100.0 |
|
adaptive_step_size=True, max_centrality_correctors=1 |
|
statuses SOLVED, INFEASIBLE, UNBOUNDED, FAILED |
plus ALMOST_SOLVED, HIT_MAX_ITER |
QTQP(a=, b=, c=, z=, p=) is unchanged. Requires Python ≥ 3.11 and now NumPy ≥ 1.25.2; CI runs a ruff lint step (#94) and a weekly full test matrix.
Because the termination criteria changed, reported statuses and solve counts are not comparable with those of 0.0.5.
All pull requests since 0.0.5
- Add presolve to drop trivially satisfied inequality constraints by @bodono in #61
- Return Python floats for gap and complementarity stats by @bodono in #63
- Stabilize tau solve in homogeneous embedding by @bodono in #60
- Report mu of the current iterate instead of the prior step by @bodono in #64
- fixing init by @bodono in #65
- Bump conda-incubator/setup-miniconda from 3 to 4 by @dependabot[bot] in #66
- Add HIT_MAX_ITER solution status by @bodono in #67
- Install pip before CI pip commands by @bodono in #73
- Clarify equilibration sparse formats by @bodono in #68
- Free linear solver on solve errors by @bodono in #71
- Skip tests for unavailable optional solvers by @bodono in #72
- Handle nearly linear tau equations by @bodono in #69
- Validate quadratic matrix symmetry by @bodono in #70
- Additional solver parameters that improve solver reliability by @matteosantama in #74
- [codex] Use eigh for unbounded test generator by @bodono in #76
- Bump actions/checkout from 6 to 7 by @dependabot[bot] in #75
- Optimize Dense KKT Solvers (CPU & GPU) by @smitlg in #77
- Bump actions/setup-python from 6 to 7 by @dependabot[bot] in #78
- Validate finiteness of 'a' and 'c' at construction by @bodono in #90
- Fix NameError crash in CupyDenseSolver.update_diag by @bodono in #87
- Make P symmetry tolerance relative to data scale by @bodono in #91
- Add ruff lint step to CI (E9/F rules) and fix findings by @bodono in #94
- Tolerate representation noise in the presolve infinity sentinel by @bodono in #95
- Guard GMRES Givens breakdown; canonicalize duplicate CSC entries by @bodono in #92
- Make SolutionStatus.FAILED reachable for numeric failures by @bodono in #93
- Adopt backward-error termination criteria; remove central_path_exponent by @bodono in #79
- Revert #79 (backward-error criteria / eps-weighted path) pending re-review by @bodono in #103
- Remove tau-solve residual pre-check (always attempt exact quadratic) by @bodono in #97
- Fix the CVXOPT initialization and make it the default by @bodono in #99
- Pin deterministic linear solver in trajectory-sensitive tests by @bodono in #96
- Log delta_path: a posteriori distance-to-path certificate by @bodono in #84
- Log delta_path_local and lambda_init: barrier-metric path diagnostics by @bodono in #106
- Remove central_path_exponent: a false lever by @bodono in #104
- Roll back degrading correction on Richardson refinement stall by @bodono in #88
- Adopt backward-error termination criteria by @bodono in #105
- Add certified warm starts via the distance-to-path certificate by @bodono in #107
- Add an adaptive fraction-to-boundary step schedule, default on by @bodono in #82
- Restore the swept 0.9999 adaptive-endgame cap by @bodono in #108
- Add Gondzio multiple centrality correctors (default 1) by @bodono in #81
- Default to GMRES refinement with a full-budget restart length by @bodono in #109
- Judge infeasibility/unboundedness certificates by data-scaled backward error by @bodono in #110
- Gate the distance-to-path diagnostics on collect_stats; share the barrier-metric norm by @bodono in #111
- Remove init_strategy: the CVXOPT initialization is the only init by @bodono in #112
- Make the fused corrector slack update unconditional; remove the flag by @bodono in #113
- Remove the unproven factorization retry ladder by @bodono in #121
- Refinement robustness fixes by @bodono in #115
- Corrector and input-validation correctness fixes by @bodono in #116
- Contracts: sentinel threshold, stats consistency, documentation by @bodono in #117
- Gate acceptance on the embedding dichotomy; summand-only scales by @bodono in #118
- Certify warm starts before the cold initialization by @bodono in #119
- Solve z == m (all-equality) problems via the direct KKT path by @bodono in #120
- Adopt Clarabel's termination criteria by @bodono in #122
- Match Clarabel's initialization exactly; solve z == m through it by @bodono in #123
- Add the (b, c) and objective scalars to Ruiz equilibration by @bodono in #124
New Contributors
Full Changelog: v0.0.5...v0.0.6