pounce-qp
Sparse parametric active-set quadratic programming subproblem solver
for POUNCE. The QP subproblem solver inside the active-set SQP NLP
path (Phase 5b) and the corrector inside pounce-sensitivity
(Phase 5c).
Pure Rust, no FFI. Layered on pounce-common + pounce-linalg +
pounce-linsol; tests use pounce-feral as the LDLᵀ backend.
Status
Phase 5a — feature-complete on correctness. Every problem
class identified in the design note (dev-notes/research/active-set-sqp-warm-start.md)
solves end-to-end; infeasibility is certified honestly; warm-start
is wired. Five of the six analytical-ladder problems (§8.0) pass;
problem #4 (LICQ-violating redundant equality) is a documented
limitation that needs rank-detection beyond inertia control.
Phase 5a.1 — performance and tooling refinements landed. The
Harris-style two-pass ratio test (cycling-prevention core of
GMSW EXPAND), the QPS RANGES section, the cached-factor resolve
infrastructure (the building block on which full Schur layers),
and basic §8.2 scaling-sweep diagnostics all ship.
Phase 5a.2 — algorithmic completion landed. §4.2 sparse
Schur-complement active-set updates (c18 standalone module +
c19 wired into solve_general behind use_schur_updates) and
§4.4 full GMSW EXPAND with τ-growth + snap-reset (c20) are both
done. The Schur path is opt-in; correctness verified by Schur-
vs-refactor cross-checks. EXPAND degrades to Harris-only
behavior on non-cycling problems and only kicks in on
pathological degeneracy. The remaining items (Maros-Mészáros
oracle comparison, large-n scaling benchmarks) require FFI or
benchmark infrastructure that fall outside the pure-Rust
constraint.
Phase 5b — SQP NLP integration core landed in
pounce-algorithm::sqp. The crate is now wired into the
SqpAlgorithm outer loop via ParametricActiveSetSolver; SQP
consumes any IpoptNlp and converges on convex equality NLPs
(1 iter) and nonlinear NLPs (l1-merit line search, demonstrated
on a circle-projection problem). AlgorithmBuilder exposes the
SQP path via algorithm = ActiveSetSqp and the sister
build_sqp_with_backend method.
What works
| Problem class | Path | Tested |
|---|---|---|
| Unconstrained QP | solve_equality_only fast path (m=0, no bounds) |
ladder #1 |
| Equality-only (free vars) | solve_equality_only |
ladder #2 |
| Box-constrained (m=0) | solve_box_constrained |
ladder #3 |
| Equality + bounds (bound-feasible eq solution) | solve_equality_plus_bounds |
dedicated tests |
| General inequalities (cold-feasible) | solve_general |
dedicated tests |
| Warm-start with arbitrary working set | solve_general |
drop / one-iter tests |
| Bound- or inequality-infeasible cold start | solve_elastic (l1-elastic mode, §4.3) |
dedicated tests |
| Certified infeasibility detection | solve_elastic + is_feasible check |
ladder #5 |
| Indefinite H with PD reduced Hessian | inertia-controlled factor (§4.5) | ladder #6 |
| Indefinite H requiring shift | factorize_with_inertia_control retry loop |
dedicated test |
| Algorithmic feature | Status |
|---|---|
Sparse triplet KKT assembly over SymTMatrix / GenTMatrix |
done |
| §4.5 inertia control via diagonal-shift retry | done |
| §4.3 l1-elastic mode | done |
| §4.7 iterative refinement (inherited from FERAL) | done |
§4.4 anti-cycling: Bland's rule (AntiCyclingChoice::Bland) |
done (c8) |
§4.4 anti-cycling: Harris two-pass (AntiCyclingChoice::Expand) |
done (c14) |
| §4.4 anti-cycling: full GMSW EXPAND (τ-growth + snap-reset) | done (c20) |
§4.2 cached-factor resolve infrastructure |
done (c16) |
§4.2 sparse Schur-complement update standalone (schur::SchurState) |
done (c18) |
§4.2 sparse Schur wired into solve_general (use_schur_updates) |
done (c19) |
| §8.1 Maros-Mészáros .qps reader (incl. RANGES) | done (c11, c13) |
| §8.1 Maros-Mészáros oracle comparison (qpOASES / OSQP) | deferred (FFI; not pure-Rust) |
| §8.2 basic scaling-sweep diagnostics | done (c15) |
| §8.2 large-n scaling (LASSO at 10²–10⁵, MPC horizon 10–160) | Phase 5a.2 |
§8.7 per-module unit tests for kkt, elastic, refinement, qps |
done |
Public API at a glance
use ;
use FeralSolverInterface;
let mut solver = new;
let sol = solver.solve?;
assert_eq!;
For the QPS / Maros-Mészáros on-ramp:
use parse_qps;
let model = parse_qps?;
// ... wrap model.h_irow / model.a_irow / model.g / model.bl ... into
// pounce-linalg SymTMatrix / GenTMatrix and pass to solver.solve.
Tests
76 tests: 70 unit tests across 8 modules under src/tests/, plus 6
integration tests under tests/.
src/tests/analytical.rs(26) — §8.0 ladder + integration tests for every problem class, with hand-derived expected values.src/tests/api.rs(11) — type-plumbing invariants forWorkingSet,QpProblem::validate, defaultQpOptions.src/tests/kkt_unit.rs(5) — §8.7 unit tests forKktTriplet::add_h_diagonal_shift.src/tests/elastic_unit.rs(7) — §8.7 unit tests forElasticReformulation::buildandinitial_seed.src/tests/refinement_unit.rs(4) — §4.7 pin that FERAL's iterative refinement is on by default and delivers near-machine-precision; plus c16 cached-resolvecontract tests.src/tests/qps_unit.rs(8) — QPS parser + round-trip solve + RANGES semantics for each row sense.src/tests/scaling_unit.rs(3) — §8.2 scaling-sweep diagnostics atn ∈ {10, 50, 100, 200}and a warm-restart speedup test (cold: ~30 refactors / ~20 ws changes; warm at optimum: 1 refactor / 0 ws changes — the §8.5 payoff in microcosm).src/tests/schur_unit.rs(6) — Schur-complement update unit tests.tests/(6) —cold_general_initial_regression.rsandmm_published_optima.rsintegration regressions.
Run with: cargo test -p pounce-qp. For per-test timings,
cargo test -p pounce-qp --release -- --nocapture.
Design reference
The full design note, including literature pinning for every
algorithmic choice, integration plan, and per-workload notes, is at
dev-notes/research/active-set-sqp-warm-start.md.