Skip to content

Fix active-set finish stall; precondition in any element type - #85

Merged
timholy merged 3 commits into
mainfrom
teh/fix-stall
Oct 10, 2026
Merged

timholy merged 3 commits into
mainfrom
teh/fix-stall

Conversation

@timholy

@timholy timholy commented Oct 10, 2026

Copy link
Copy Markdown
Member

Since #83 the finish solves over-budget tree Laplacians by conjugate gradients, whose forward error (1.5e-11 on a sparse nonsymmetric lognormal matrix with n = 31623) can exceed the certificate's ptol of 1e-12. Constraints on no cycle of the support are tight with zero multiplier at the minimizer, so that noise made a few of them look violated, and the forest shuffled among them until the step limit of 4n, about 5 h per solve.

Finish (active_set.jl)

  • _acyclic_constraints peels degree-one vertices of the support, and the seed activates those constraints first. Their residuals are exactly zero by construction, so they are never targeted, dropped, or blocking.
  • _laplacian_solve! on _LaplacianCG refines the CG solution with a double-double residual until the correction is below 8 eps ‖x‖ and records the last correction as an error estimate. _dual_feasible! raises the certificate's tolerances to twice that estimate, so certified never claims more accuracy than the solve delivers.
  • _LaplacianCG is parametric in the element type. Sparse tree Laplacians in types CHOLMOD does not factor take refined CG instead of an O(n^3) dense factorization, which remains only as the fallback when refinement stops contracting.

Multiplier iteration (minimal_covers.jl, new tree_cholesky.jl)

  • The LSQR preconditioner is now a maximum-weight spanning forest of the violated couplings, factored by eliminating from the leaves: no fill, no symbolic analysis, no budget check, and the arithmetic of the element type. On eight sparse lognormal matrices up to n = 100000, at most 45 of about two million violated couplings per solve closed a cycle, and restricting to the forest changed LSQR iteration totals by at most 8 %.
  • The diagonal preconditioner is retired along with its ndiagonal statistic. precond is now :factor, :forest, or :none.

Results

  • All three n = 31623 matrices certify in 0 steps in about 2 s under the CG path. Finishes that took steps run about 3x faster; finishes that already took 0 steps pay 3-8 % for the extra CG iterations.
  • Float64 solves on the sweep matrices run 20-30 % faster with the forest preconditioner.
  • Double64 on a 3162x3162 sparse lognormal matrix: the default solve's LSQR iterations fall from 67699 to 2052 (84 s to 12 s from the forest preconditioner alone), and a solve whose multiplier iteration stops early drops from 204 s to 16 s from the finish change. Results agree with Float64 to 1e-15 in the log scales.

Since #83 the finish solves over-budget tree Laplacians by conjugate
gradients. CG leaves a forward error of its normwise backward error
times the condition number: on a sparse nonsymmetric lognormal matrix
with n = 31623 the per-tree values were off by 1.5e-11 while the
certificate's ptol is 1e-12. Constraints on no cycle of the support
(single-entry rows and columns, chains hanging off the rest) are tight
with zero multiplier at the minimizer, so the noise made a few of them
look violated; adding one gave a step of order 1e-12 and a zero
multiplier, the ratio test then blocked at zero, and the forest shuffled
among the same handful of constraints until the step limit of 4n, about
5 h per solve. The paper's timing sweep stalled on two such matrices.

Two changes, both in the finish:

- `_acyclic_constraints` peels degree-one vertices of the support, on
  the edge-list and dense-grid layouts, and the seed activates those
  constraints first. They lie on no cycle, their residuals are exactly
  zero by construction of the tree offsets, and their routed multipliers
  and rates are sums of exact zeros, so they are never targeted, dropped,
  or blocking.

- `_laplacian_solve!` on `_LaplacianCG` refines the CG solution with the
  residual computed in double-double arithmetic until the correction is
  below 8 eps ‖x‖, and records the last correction as the solve's error
  estimate. `_dual_feasible!` raises the certificate's tolerances to twice
  that estimate, so `certified` never claims more accuracy than the solve
  delivers. On the stalling matrix the first correction equals the
  measured error and the second is 3e-16; the refined CG and Cholesky
  solutions then agree bit for bit.

All three n = 31623 matrices now certify in 0 steps in about 2 s under
the CG path. Finishes that took steps run about 3x faster; finishes that
already took 0 steps pay 3-8% for the extra CG iterations.

Assisted-by: Claude Fable 5.1 <noreply@anthropic.com>
The LSQR preconditioner was switched off for element types other than
Float64 because its factorizations are CHOLMOD's. Only those are now
gated on Float64; the diagonal preconditioner, weighted degrees, is
generic and serves other types throughout.

The finish's `_LaplacianCG` is now parametric in the element type. A
sparse tree Laplacian in a type CHOLMOD does not factor was densified
and factored at O(n^3); it now takes CG with compensated-residual
refinement like Float64, falling back to a dense Cholesky factorization
only when refinement stops contracting. The error-free transformations
in the residual hold for any type with a correctly rounded `fma`.

On a 3162x3162 sparse lognormal matrix in Double64, a solve whose
multiplier iteration stops early drops from 204 s to 16 s, and the
default solve's LSQR iterations fall from 67699 to 43901. The results
agree with Float64 to 1e-15 in the log scales.

Assisted-by: Claude Fable 5.1 <noreply@anthropic.com>
The heavy-forest preconditioner of the multiplier iteration kept every
coupling among violated entries and factored the result with CHOLMOD,
with a symbolic analysis and a fill-budget check at each change of
penalty or violation pattern, and fell back to the diagonal when that
factor was over budget or the element type was not Float64.

In practice the violated couplings form a forest: over eight sparse
lognormal matrices of the paper's sweep, up to n = 100000, at most 45 of
about two million violated couplings per solve closed a cycle, and
restricting the pattern to a maximum-weight spanning forest changed the
LSQR iteration totals by at most 8 %.

The preconditioner is now that forest, factored by eliminating from the
leaves (`tree_cholesky.jl`): no fill, no analysis, no budget, and the
arithmetic of the element type. Its diagonal is every entry's weighted
degree plus the ridge, so it is strictly diagonally dominant and the
pivots stay positive. The diagonal regime is retired, with its
`ndiagonal` statistic; `precond` is now `:factor`, `:forest`, or
`:none`.

Float64 solves on the sweep matrices run 20-30 % faster, the analysis
per rebuild being gone. A Double64 solve on a 3162x3162 sparse matrix
drops from 84 s and 43901 LSQR iterations to 12 s and 2052.

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

codecov Bot commented Oct 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.79%. Comparing base (e177c5e) to head (a5d8a6f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #85      +/-   ##
==========================================
- Coverage   99.07%   98.79%   -0.28%     
==========================================
  Files          22       23       +1     
  Lines        6132     6286     +154     
==========================================
+ Hits         6075     6210     +135     
- Misses         57       76      +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 67fd0a8 into main Oct 10, 2026
4 of 5 checks passed
@timholy
timholy deleted the teh/fix-stall branch October 10, 2026 10:24
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