Skip to content

Speed up [sym]cover_min - #45

Merged
timholy merged 7 commits into
mainfrom
woodbury-newton
Aug 23, 2026
Merged

Speed up [sym]cover_min#45
timholy merged 7 commits into
mainfrom
woodbury-newton

Conversation

@timholy

@timholy timholy commented Aug 23, 2026

Copy link
Copy Markdown
Member

The analysis of the unconstrained problem established a fast (O(n^3) -> O(n)) (though sometimes approximate) approach based on the Woodbury matrix identity and factorizations of the support. This brings the same architecture to the [sym]cover_min solvers. It's more complex here because it acts over violations, which change from iteration to iteration.

Nevertheless the speedups are quite dramatic. Timings (σ=1 lognormal): sym n=1000 5.2 s → 1.35 s; asym 600×500 5.6 s → 0.67 s; sparse n=10⁵ LSQR 747 s → 29 s

The sparse support traversal and the sparse `linsolve` defaults live in
`src/sparse_support.jl`, and the Unitful extension carries the sparse
methods that resolve its ambiguity with them.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Each AbsLog{2} Newton step solves reweighted normal equations whose
matrix, on a near-complete support, is the complete-support matrix
corrected by the zero set and by the currently violated entries. That
split is `C + U*Uᵀ` with `C` sparse and positive definite and `U` of rank
one (symmetric) or two (asymmetric), so a sparse Cholesky of `C` plus a
Woodbury update replaces the dense factorization. `linsolve=:woodbury`
selects it, `:auto` takes it wherever its requirements hold, and
`stats.linsolve` reports which path ran.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
The dense and Woodbury Newton steps minimize the quadratic model of the
current stage exactly. When such a step is taken whole and the violated
set at the new iterate is the one the step froze its weights on, the
gradient of the convex stage objective there is the model's, which
vanishes: the iterate is the stage minimizer and the stage ends without
a confirmation solve. The inexact `:lsqr` steps keep the decrease test
as their sole criterion.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
`C + U*Uᵀ` applies at O(n + |Z| + |V|) without being formed, and
Gershgorin bounds its condition number by `1 + (κ−1)*2*maxdeg(V)/n`.
Where that estimate stays under 1000, Jacobi-preconditioned conjugate
gradients reach the same answer to rounding in a few hundred such
applications; the sparse Cholesky serves the ill-conditioned stages and
any run that exhausts the iteration cap. `stats.cgiters` counts the
iterations.

The objective sweep now reports whether the violated set still matches
the one the step froze its weights on, so the line search and the
stage's stopping test share one pass over the support.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
The penalty strength enters the least-squares matrix only through the
rows it reweights, so `M = diag(RᵀR) + (κ−1)*Σ_{e∈V} rₑ*rₑᵀ` carries all
of it. Every generalized eigenvalue of `(RᵀWR, M)` is a mediant of
eigenvalues of `(RᵀR, diag(RᵀR))` and so lies in their range, which
holds the iteration count fixed as the continuation raises κ. LSQR runs
on `√W*R*K⁻ᵀ`, `K` the permuted sparse Cholesky factor of `M`, applied
through CHOLMOD's factor components; the violated rows join `M` only
once diagonal scaling alone would leave the system ill conditioned.
CHOLMOD is Float64-only, so other working types keep the plain
matrix-free iteration.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
The conjugate-gradient residual recurrence drifts from `f − B x`, so
convergence is confirmed against a freshly computed residual and a
disagreement restarts the iteration; only a confirmed result is reported
exact, which is what the sign-stability stopping test needs. A support
thin enough per row can still carry a quadratic number of zeros, which
would make the sparse correction dense work, so the Woodbury path also
requires the total zero count to be O(n). The LSQR preconditioner is a
plain scaling while its condition-number estimate stays low, forming and
factorizing nothing. `stats.cholsolves` counts the Woodbury solves that
reached the factorization, the two condition-number gates are named
constants, and the Woodbury workspace is allocated only on that path.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
`C` is assembled sparsely on every Woodbury solve, in full rather than in
one triangle, so the conjugate-gradient sub-path applies it with `mul!`
and adds the low-rank term directly instead of walking the edge lists.
The symmetric Sherman-Morrison and the asymmetric 2x2 Woodbury update
become one `_woodbury_solve!` over a low-rank block of k columns, taking
`[f U]` through the factor in a single multi-right-hand-side solve.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.82639% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.41%. Comparing base (f27e222) to head (c451718).

Files with missing lines Patch % Lines
src/minimal_covers.jl 99.82% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #45      +/-   ##
==========================================
- Coverage   98.84%   98.41%   -0.44%     
==========================================
  Files          14       13       -1     
  Lines        2417     2958     +541     
==========================================
+ Hits         2389     2911     +522     
- Misses         28       47      +19     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timholy
timholy merged commit f9c7a30 into main Aug 23, 2026
4 of 5 checks passed
@timholy
timholy deleted the woodbury-newton branch August 23, 2026 08:48
@timholy timholy mentioned this pull request Aug 23, 2026
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