Skip to content

Cache SU2 F-symbols as Float64 - #114

Closed
leburgel wants to merge 1 commit into
mainfrom
lb/su2_fsymbol_cache
Closed

leburgel wants to merge 1 commit into
mainfrom
lb/su2_fsymbol_cache

Conversation

@leburgel

Copy link
Copy Markdown
Member

This is a draft illustrating some potential performance improvements; the actual implementations are
very much up for discussion. Follow-up to QuantumKitHub/TensorKit.jl#580 and QuantumKitHub/TensorKit.jl#581.

Fsymbol(::SU2Irrep, ...) calls WignerSymbols.racahW, which takes two SpinLocks and converts a
Rational{BigInt} on every call, so threaded fusion tree manipulations serialize on it. The
F-symbols are now cached as Float64 in a lock-free SnapshotCache (internal; as in
QuantumKitHub/SUNRepresentations.jl#57): lookups read an immutable Dict snapshot, insertions copy it
under a lock. A full CTMRG iteration of a fℤ₂ × U(1) × SU(2) × U(1) PEPS needs only 107–180 distinct
F-symbols, so the copies are negligible.

Cold construction of the recoupling data for one permutation of a tensor from a PEPS CTMRG step
(script below), with the two TensorKit PRs applied, median of 4 rounds:

before after
1 thread 1.23 s 1.05 s (1.2×)
8 threads 1.96 s 0.60 s (3.3×)

TensorKit main (ae8abfc) with both TensorKit PRs, Julia 1.12.5; shared 80-core machine at load 12–29, rounds
vary by up to 35 %.

fℤ₂ × U(1) × SU(2) × U(1) reproducer (julia -t8)
using TensorKit

# the spaces of one index permutation in a CTMRG step for a fℤ₂ × U(1) × SU(2) × U(1)-symmetric PEPS
const I = FermionParity ⊠ U1Irrep ⊠ SU2Irrep ⊠ U1Irrep
s(f, q, j, q′) = FermionParity(f) ⊠ U1Irrep(q) ⊠ SU2Irrep(j) ⊠ U1Irrep(q′)
V1 = Vect[I](s(0, 0, 1/2, 1) => 1, s(1, -1, 0, 0) => 1, s(0, 2, 0, 0) => 1, s(0, 0, 0, -2) => 1, s(1, 1, 1/2, -1) => 1, s(1, 1, 0, 2) => 1)
V2 = Vect[I](s(0, 0, 1/2, 1) => 1, s(0, 2, 0, 0) => 1, s(0, 0, 0, -2) => 1, s(1, 1, 1/2, -1) => 1, s(1, 1, 0, 2) => 1)
V3 = Vect[I](s(1, 1, 0, 0) => 1, s(0, 0, 1/2, -1) => 1, s(0, 0, 0, 2) => 1, s(0, 2, 1/2, 1) => 1, s(0, 2, 0, -2) => 1)
V4 = Vect[I](s(1, 1, 0, 0) => 1, s(0, 0, 1/2, -1) => 1, s(0, 0, 0, 2) => 1, s(1, -1, 1/2, 1) => 1, s(1, -1, 0, -2) => 1)
V5 = Vect[I](s(0, 0, 0, 0) => 5, s(0, 0, 1, 0) => 1, s(1, 1, 1/2, 1) => 4, s(1, -1, 1/2, -1) => 4, s(0, 0, 1/2, 3) => 1, s(1, 1, 0, -2) => 3, s(1, -1, 0, 2) => 3, s(0, 0, 1/2, -3) => 1, s(1, 1, 1, -2) => 1, s(1, -1, 1, 2) => 1, s(1, 1, 0, 4) => 1, s(1, -1, 0, -4) => 1)
V6 = Vect[I](s(0, 0, 0, 0) => 5, s(0, 0, 1, 0) => 2, s(1, 1, 1/2, 1) => 4, s(1, -1, 1/2, -1) => 3, s(0, 0, 1/2, 3) => 2, s(0, 2, 1/2, -1) => 1, s(0, -2, 1/2, 1) => 1, s(1, 1, 0, -2) => 3, s(1, -1, 0, 2) => 3, s(0, 0, 1/2, -3) => 2, s(0, 2, 0, 2) => 1, s(1, 1, 1, -2) => 1, s(0, -2, 0, -2) => 1, s(1, 1, 0, 4) => 1)
V7 = Vect[I](s(0, 0, 1/2, 1) => 1, s(1, -1, 0, 0) => 1, s(0, 0, 0, -2) => 1, s(1, 1, 1/2, -1) => 1, s(1, 1, 0, 2) => 1)
W = (V1 ⊗ V2' ⊗ V3) ← (V4' ⊗ V5 ⊗ V6' ⊗ V7)
p = ((1, 7, 4), (2, 3, 5, 6))

t = randn(W)

# cold construction of the cached recoupling data of the permutation (TensorKit's caches emptied,
# the symbols warm), with 1 and with all threads
function cold_permute(t, p, n)
    TensorKit.set_num_manipulation_threads(n)
    TensorKit.set_num_transformer_threads(n)
    TensorKit.empty_globalcaches!()
    return @elapsed permute(t, p)
end
cold_permute(t, p, Threads.nthreads()) # compilation, and the symbols
for n in unique((1, Threads.nthreads()))
    ts = [cold_permute(t, p, n) for _ in 1:3]
    println("cold permute, $n thread(s): ", round(minimum(ts); digits = 2), " s (min of 3)")
end

🤖 Generated with Claude Code

`WignerSymbols.racahW` takes the exact 6j symbol from a global cache behind two lock acquisitions and converts it from `Rational{BigInt}` at every call, which dominates and serializes fusion tree manipulations with many SU2 sectors. The F-symbols are now cached as floating-point numbers in a `SnapshotCache`, which is read without locking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.21053% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/auxiliary.jl 82.35% 3 Missing ⚠️
Files with missing lines Coverage Δ
src/irreps/su2irrep.jl 100.00% <100.00%> (ø)
src/auxiliary.jl 90.69% <82.35%> (-2.06%) ⬇️

... and 3 files with indirect coverage changes

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

@leburgel

Copy link
Copy Markdown
Member Author

Confirmed that this is superseded by Jutho/WignerSymbols.jl#19, so closing here in favor of waiting for that update.

@leburgel leburgel closed this Oct 15, 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