Skip to content

Faster stable lower-weight CGC solve - #64

Merged
lkdvos merged 5 commits into
mainfrom
claude/sparse-cholesky
Oct 28, 2026
Merged

lkdvos merged 5 commits into
mainfrom
claude/sparse-cholesky

Conversation

@lkdvos

@lkdvos lkdvos commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

This PR speeds up the stable lower-weight CGC solve from #61. With dense factorizations, #61 made the per-weight cost cubic in the number of pairs. This PR brings the runtime back below that of the old, unstable code. It combines what were #64, #65 and #67, one commit per change:

  1. Sparse Cholesky. B, the Gram matrix of the raising operators on the pairs |m₁, m₂⟩ of a weight, is very sparse. It is now built as a sparse R * Rᵀ from the nonzero entries of the raising operators. Blocks of at least 100 pairs with Float64 entries are factorized with CHOLMOD. Small blocks and other element types keep the dense Cholesky. This adds SparseArrays, a stdlib, as a dependency.
  2. Dense per-weight blocks. The solution of every weight is kept as a dense block sols[p, α, j], together with its pairs and their indices. The right-hand sides read the parent CGCs from these blocks: one lookup in a small per-weight Dict per parent pair, instead of N123 lookups in the large SparseArray. The SparseArray is filled once at the end.
  3. A single shifted system per weight. The Sylvester equation B * sols + sols * A = 2 * rhs * eqs from Numerically stable computation of lower-weight CGCs #61, with A = eqsᵀ * eqs, needed one factorization of B + μ * I for every eigenvalue μ of A. The lowering and raising equations are consistent: the true CGCs satisfy both exactly, so any positive weighting of them has the same least-squares solution. Weighting the lowering equations with c * eqs * A⁻² * eqsᵀ gives
    (B + c * I) * sols = rhs * eqs * (I + c * A⁻¹)
    
    This needs a single factorization per weight and no eigendecomposition. Here c = tr(A) / size(A, 1) is the mean eigenvalue of A.
  4. Skip tiny entries. Entries with abs(v) <= TOL_PURGE are not written into the SparseArray, since purge! would remove them right afterwards. This leaves the result unchanged.

Timings

Single BLAS thread, CGCs computed directly (bypassing the caches):

case before #61 (unstable) main (#61, dense) this PR
SU(2) (250,0)³ 0.026 s 0.089 s 0.056 s
SU(3) (10,5,0)³ – 0.65 s 0.20 s
SU(3) (12,6,0)³ 2.4 s 4.1 s 0.75 s
SU(3) (16,8,0)³ 23 s 95 s 5.5 s
SU(4) (6,3,3,0)³ 0.90 s 1.4 s 0.32 s

Very small cases, such as SU(3) (6,3,0)³ at about 10 ms, are slightly slower, because the Gram matrix is always built as a sparse matrix. About 60 % of the remaining time goes to the sparse Cholesky factorization.

Accuracy

Largest residual:

case main this PR
SU(3) (14,0,0) ⊗ (14,14,0) → (14,7,0), intertwiner 6.7e-15 1.8e-15
SU(3) (20,0,0) ⊗ (20,20,0) → (20,10,0), intertwiner / isometry 1.3e-14 / 1.7e-14 7.9e-15 / 2.4e-15
SU(3) (16,8,0)³, intertwiner / isometry 1.8e-14 / 1.2e-14 1.8e-14 / 8.0e-15
SU(4) (6,3,3,0)³, intertwiner / isometry 2.7e-15 / 1.2e-14 2.8e-15 / 2.9e-15

The full test suite passes.

Tried and left out

  • Reverse Cuthill–McKee ordering instead of CHOLMOD's AMD: 3–10 % faster, but about 25 more lines of code.
  • Simplicial factorization instead of supernodal: slower.
  • LDLᵀ instead of Cholesky: slower.
  • Skipping pairs reached only from parent pairs with vanishing CGCs: this breaks SU(2) for spins ≥ 100, because gaugefix! zeroes small highest-weight CGCs (fixed in Keep small highest-weight CGCs in gaugefix! #66).

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/SUNRepresentations.jl 100.00% <ø> (ø)
src/clebschgordan.jl 97.90% <100.00%> (+0.05%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The Gram matrix `B` of the raising operators is very sparse, and all the
shifted matrices `B + μ * I` share its sparsity pattern. It is now built
as a sparse matrix `R * Rᵀ`, and for blocks of at least 100 pairs (and
`Float64`) the shifted systems are factorized with CHOLMOD, reusing the
symbolic factorization, instead of with a dense Cholesky. This restores
the scaling of the CGC computation from before the stable solve, e.g.
SU(3) (16,8,0)³ takes 12 s, compared to 23 s before the stable solve and
95 s with the dense solve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lkdvos
lkdvos force-pushed the claude/sparse-cholesky branch from 7919264 to 6ca74a2 Compare October 28, 2026 12:39
lkdvos and others added 4 commits October 28, 2026 10:54
The CGCs of every solved weight are now kept as a dense block
`sols[p, α, j]`, together with its pairs and their indices, so that the
right hand sides of the lowering equations read the parent CGCs from
these blocks instead of from the `Dict`-backed `SparseArray`. The CGCs
are written into the `SparseArray` once at the end, after reserving the
space for all entries.

Parent pairs are still enumerated over the full weight blocks, so that the
pairs reached from omitted parent pairs, of which the CGCs vanish, are
still solved for. This matters: `highest_weight_CGC` sets entries below
`TOL_GAUGE` to zero, which the raising equations correct at lower weights.

This reduces the time of e.g. SU(3) (12,6,0)³ from 1.41 s to 1.04 s,
which is now dominated by the sparse Cholesky factorizations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lowering and raising equations are consistent, so any positive
weighting of them has the same least-squares solution. Weighting the
lowering equations with `c * eqs * A⁻² * eqsᵀ`, with `A = eqsᵀ * eqs`,
turns the Sylvester equation `B * sols + sols * A = 2 * rhs * eqs` into
`(B + c * I) * sols = rhs * eqs * (I + c * A⁻¹)`. This needs a single
Cholesky factorization per weight instead of one for every eigenvalue of
`A`, and no eigendecomposition. With `c` the mean eigenvalue of `A`, the
CGCs are as accurate as before.

This reduces the time of e.g. SU(3) (16,8,0)³ from 9.3 s to 5.8 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lkdvos lkdvos changed the title Sparse Cholesky for the stable lower-weight CGC solve Faster stable lower-weight CGC solve Oct 28, 2026
@lkdvos
lkdvos merged commit 27cee1e into main Oct 28, 2026
8 checks passed
@lkdvos
lkdvos deleted the claude/sparse-cholesky branch October 28, 2026 21:48
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