Skip to content

Vectorize fusiontree manipulations#261

Open
lkdvos wants to merge 88 commits intomainfrom
ld-outer
Open

Vectorize fusiontree manipulations#261
lkdvos wants to merge 88 commits intomainfrom
ld-outer

Conversation

@lkdvos
Copy link
Member

@lkdvos lkdvos commented Jul 31, 2025

Given the speed-up of the permutations, the bottleneck has now migrated to the actual computation of the treetransformers. (mostly for tensors with many legs again)
@ogauthe has reported cases where the first run takes ~2000seconds, while the second run is ~5seconds.

While I haven't really benchmarked or profiled myself yet (shame on me!), I immediately remembered that we are not really composing the fusion tree manipulations in a very optimal way, where we are effectively manually writing out the matrix multiplications on the one hand, but also the recursive implementation means that we are recomputing a lot of transformations that we would otherwise already have encountered. This never really showed up since these computations are somewhat fast and cached, but for large amount of fusiontrees these loops become prohibitively expensive.

This PR aims to address these issues in a very similar fashion as the previous optimization run: we consider the set of all fusion trees with equal uncoupled charges, and act on these as a block. If my back-of-the-envelope calculations are correct, if there are N such fusion trees and we are doing L substeps in the manipulation, we were previously doing $\sim N^L$ operations due to the recursive nature, while now this is $\sim L N^3$.

This PR now contains the following changes:

  • Some clean up of calling signatures (braid had some inconsistencies in its argument orders, Index2Tuple etc)
  • Use fusiontensor instead of convert(Array, ::FusionTreePair) to avoid type piracy
  • "vectorized" manipulations of non-UniqueFusion fusionstyles to avoid the exponential scaling in number of chained manipulations.
  • vectorized indexmanipulations beyond TensorMap specializations, approaching the entire "indexmanipulations" kernels in a more unified way
  • Updated the fusiontree tests to reflect these changes

@Jutho
Copy link
Member

Jutho commented Jul 31, 2025

I think a first general design remark is that now all the manipulations on the OuterTreeIterator (which is not an iterator 😄 ) have in them the lines:

    trees_src = fusiontrees(fs_src)
    trees_dst = fusiontrees(fs_dst)
    indexmap = Dict(f => ind for (ind, f) in enumerate(trees_dst))

If you know start composing elementary operations to make more complex ones (e.g. bendleft/right to implement repartition), there will be at least two fusiontrees calls on the same OuterTreeIterator object. In every step the previous destination is the new source for the following operation in the composition list. The fusiontrees method goes through blocksectors and the fusiontrees(uncoupled_sectors, blocksector), which should be pretty efficient, but nonetheless have some overhead.

It might thus be a better idea to store the blocksectors and associated fusion tree pairs (and there index mapping) within the OuterTreeIterator object. The initial data can be extracted from the data the tensor has, and then throughout the sequence of different operations, the old destination becoming new source would be able to reuse the same information.

Of course, this way, the OuterTreeIterator would become a lightweight version of FusionBlockStructure, so maybe there is some way to recycle some functionality or even redesing FusionBlockStructure.

Happy to discuss when you have time.

@lkdvos lkdvos force-pushed the ld-outer branch 8 times, most recently from 4e40fc3 to 50db028 Compare August 1, 2025 11:47
@codecov
Copy link

codecov bot commented Aug 1, 2025

Codecov Report

❌ Patch coverage is 83.50731% with 79 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.73%. Comparing base (cc18e95) to head (54b7abc).

Files with missing lines Patch % Lines
src/fusiontrees/fusiontreeblocks.jl 84.18% 59 Missing ⚠️
src/tensors/treetransformers.jl 66.66% 12 Missing ⚠️
src/fusiontrees/manipulations.jl 91.52% 5 Missing ⚠️
src/fusiontrees/fusiontrees.jl 33.33% 2 Missing ⚠️
src/tensors/braidingtensor.jl 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #261      +/-   ##
==========================================
- Coverage   82.85%   82.73%   -0.13%     
==========================================
  Files          44       45       +1     
  Lines        5757     6139     +382     
==========================================
+ Hits         4770     5079     +309     
- Misses        987     1060      +73     

☔ View full report in Codecov by Sentry.
📢 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.

@ogauthe
Copy link
Contributor

ogauthe commented Aug 1, 2025

So I compared the performances with master. I make some permutations on a bilayer PEPS tensor with size D^8, with V_D =Rep[SU₂](0=>2, 1=>3, 2=>1). I first compile the code with a fast pass at D=3, so compiling time is negligible.

TensorKit v0.14.9
1992.983477 seconds (15.41 G allocations: 2.188 TiB, 10.51% gc time, 1.87% compilation time)
ld-outer
46.581346 seconds (207.48 M allocations: 51.391 GiB, 12.97% gc time, 0.93% compilation time)
which is a factor 43 speed-up. As expected the 2nd run takes the same time in both cases (~3.5 sec). Very impressive @lkdvos !

@lkdvos lkdvos force-pushed the ld-outer branch 2 times, most recently from 1f4c68f to ecbdaa6 Compare August 14, 2025 08:24
@lkdvos
Copy link
Member Author

lkdvos commented Aug 15, 2025

As a small update here, I think I further optimized the implementations slightly, however there definitely is still some room for improvement. In particular the insertat functions are still using a recursive approach, which I think still scales badly in the number of steps, therefore hurting the performance of foldright by quite a bit, depending on the number of legs.

I'll try and run some profilers next week to see what is now actually the bottleneck, to verify what needs further improvements. In particular, I can try and reduce some allocations by trying to re-use a bunch of the unitary matrices, or we can try and cache the foldright unitaries directly if these turn out to be the limiting factor. Finally, we should probably try and split the computations of the unitaries of productsectors into their parts, which should avoid us from doubly computing some of that. (although this might actually be non-trivial in combination with the caching and the multithreading...)

@kshyatt
Copy link
Member

kshyatt commented Aug 21, 2025

BTW does @ogauthe have a MWE of what's causing the slowdown that could be shared, perhaps to run as part of a perf regression testsuite 👀 ?

@lkdvos
Copy link
Member Author

lkdvos commented Aug 21, 2025

using TensorKit
using TensorKit: treepermuter
Vsrc = (Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)'  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)'  Rep[ℤ₂ × SU₂]((0, 0) => 1, (1, 1) => 1))  (Rep[ℤ₂ × SU₂]((0, 0) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)'  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)')
Vdst = (Rep[ℤ₂ × SU₂]((0, 0) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (1, 1) => 1)')  (Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)'  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)'  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)'  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)  Rep[ℤ₂ × SU₂]((0, 0) => 1, (0, 1) => 1, (1, 1) => 1)')
p = ((5, 6), (1, 7, 2, 8, 3, 9, 4, 10))

TensorKit.empty_globalcaches!()
@time treepermuter(Vdst, Vsrc, p);
Profile.clear()
TensorKit.empty_globalcaches!()
Profile.init(n = 10^7, delay = 0.01)
@profile treepermuter(Vdst, Vsrc, p);

Here's a relevant case I took from the code at some point. It's really just a matter of taking one of the peps contractions and looking for the dominant treepermuter cost, which you can typically identify by adding ENV["JULIA_DEBUG"] = TensorKit

@ogauthe
Copy link
Contributor

ogauthe commented Sep 2, 2025

@kshyatt here is a slightly larger example. @lkdvos minimal case should appear at some point. The timing was done using this branch as of August 15th.

using TensorOperations: @tensor
using TensorKit

function proj_braket(rD)
    fs = map(s -> sign(frobeniusschur(s)), sectors(rD))
    !(all(fs .== 1) || all(fs .== -1)) && return error("Cannot handle quaternionic irreps")

    rD2 = fuse(rD, rD')
    isoD2 = isomorphism(rD2, rD  rD')
    permuted_iso = flip(permute(isoD2', ((3,), (2, 1))), (1,))
    proj2_even_fullspace = isoD2 + permuted_iso
    proj2_odd_fullspace = isoD2 - permuted_iso

    proj2_even = rightnull(proj2_odd_fullspace; alg=SVD(), atol=1e-12)
    proj2_odd = rightnull(proj2_even_fullspace; alg=SVD(), atol=1e-12)
    return proj2_even, proj2_odd
end

function project_Z2(rd, rD)
    tket = randn(rd  rD  rD  rD'  rD')
    pd_even_oddd = proj_braket(rd)
    projN = map(t -> permute(t, ((2, 3), (1,))), proj_braket(rD))
    projE = map(t -> permute(t, ((2, 3), (1,))), proj_braket(rD))
    projS = map(t -> permute(t, ((2, 3), (1,))), proj_braket(rD'))
    projW = map(t -> permute(t, ((2, 3), (1,))), proj_braket(rD'))

    t_double = permute(tket'  tket, ((5, 6), (1, 7, 2, 8, 3, 9, 4, 10)))

    for i_d in 1:2
        projectedd = permute(pd_even_oddd[i_d] * t_double, ((1, 4, 5, 6, 7, 8, 9), (2, 3)))
        for iN in 1:2
            @tensor projectedN[d2, D2N, ketW, braW, ketS, braS; ketE, braE] :=
                projectedd[d2, ketE, braE, ketS, braS, ketW, braW; ketN, braN] *
                projN[iN][ketN, braN, D2N]
            for iE in 1:2
                @tensor projectedNE[d2, D2N, D2E, ketW, braW; ketS, braS] :=
                    projectedN[d2, D2N, ketS, braS, ketW, braW; ketE, braE] *
                    projE[iE][ketE, braE, D2E]
                for iS in 1:2
                    iW = mod1(i_d + iN + iE + iS + 1, 2)
                    @tensor projected[d2, D2N, D2E, D2S, D2W] :=
                        projectedNE[d2, D2N, D2E, ketW, braW; ketS, braS] *
                        projS[iS][ketS, braS, D2S] *
                        projW[iW][ketW, braW, D2W]
                end
            end
        end
    end
    return 1
end


rd = Rep[ℤ₂ × SU₂]((0, 0) => 1, (1, 1) => 1)
rD7 = Rep[ℤ₂ × SU₂]((0, 0)=>1, (0, 1)=>1, (1, 1)=>1)
rD11 = Rep[ℤ₂ × SU₂]((0, 0) => 2, (0, 1) => 1, (1, 1) => 2)
rD16 = Rep[ℤ₂ × SU₂]((0, 0) => 2, (0, 1) => 1, (1, 1) => 2, (0, 2) => 1)

@time project_Z2(rd, rD7)
@time project_Z2(rd, rD7)
@time project_Z2(rd, rD11)
@time project_Z2(rd, rD11)


# 1st run D = 7
@time project_Z2(rd, rD7)
# 400.344116 seconds (1.39 G allocations: 465.458 GiB, 7.96% gc time, 24.47% compilation time)

# 2nd run D = 7
@time project_Z2(rd, rD7)
#5.408577 seconds (20.24 M allocations: 2.738 GiB, 5.07% gc time, 5.34% compilation time)

# 1st run D = 11  (*after D = 7*)
@time project_Z2(rd, rD11)
# 319.394331 seconds (1.36 G allocations: 482.086 GiB, 8.63% gc time, 0.00% compilation time)

# 2nd run D = 11
@time project_Z2(rd, rD11)
#  7.989900 seconds (20.06 M allocations: 4.710 GiB, 4.31% gc time)

@ogauthe
Copy link
Contributor

ogauthe commented Oct 29, 2025

I have been using this branch for expensive computations with SU2Irrep and Z2Irrep ⊠ SU2Irrep and I am very happy with the performance improvement. However I found an issue with SU3Irrep.

using TensorKit
using SUNRepresentations: SU3Irrep

vd = Vect[SU3Irrep]((1, 0, 0) => 1)
h = TensorMap([0.0, 2.0], vd  vd  vd  vd)
permute(h, (1, 2, 3), (4,))
ERROR: LoadError: KeyError: key (FusionTree{Irrep[SU₃]}(((1, 0, 0), (1, 0, 0), (1, 1, 0)), (1, 0, 0), (false, false, true), ((1, 1, 0),), (1, 1)), FusionTree{Irrep[SU₃]}(((1, 0, 0),), (1, 0, 0), (false,), (), ())) not found
Stacktrace:
  [1] getindex(h::Dict{Tuple{Tuple{SU3Irrep, SU3Irrep}, Tuple{Int64, Int64}}, Int64}, key::Tuple{FusionTree{SU3Irrep, 3, 1, 2}, FusionTree{SU3Irrep, 1, 0, 0}})
    @ Base ./dict.jl:498
  [2] bendright(src::TensorKit.FusionTreeBlock{SU3Irrep, 4, 0, Tuple{FusionTree{SU3Irrep, 4, 2, 3}, FusionTree{SU3Irrep, 0, 0, 0}}})
    @ TensorKit ~/.julia/packages/TensorKit/Y7XJ1/src/fusiontrees/fusiontreeblocks.jl:138
  [3] macro expansion
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/fusiontrees/fusiontreeblocks.jl:397 [inlined]
  [4] repartition
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/fusiontrees/fusiontreeblocks.jl:379 [inlined]
  [5] repartition
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/fusiontrees/fusiontreeblocks.jl:364 [inlined]
  [6] __fsbraid(key::Tuple{TensorKit.FusionTreeBlock{SU3Irrep, 2, 2, Tuple{FusionTree{SU3Irrep, 2, 0, 1}, FusionTree{SU3Irrep, 2, 0, 1}}}, Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}, Tuple{Tuple{Int64, Int64}, Tuple{Int64, Int64}}})
    @ TensorKit ~/.julia/packages/TensorKit/Y7XJ1/src/fusiontrees/fusiontreeblocks.jl:658
  [7] #54
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/auxiliary/caches.jl:108 [inlined]
  [8] get!(default::TensorKit.var"#54#59"{Tuple{TensorKit.FusionTreeBlock{SU3Irrep, 2, 2, Tuple{FusionTree{SU3Irrep, 2, 0, 1}, FusionTree{SU3Irrep, 2, 0, 1}}}, Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}, Tuple{Tuple{Int64, Int64}, Tuple{Int64, Int64}}}}, lru::LRUCache.LRU{Any, Any}, key::Tuple{TensorKit.FusionTreeBlock{SU3Irrep, 2, 2, Tuple{FusionTree{SU3Irrep, 2, 0, 1}, FusionTree{SU3Irrep, 2, 0, 1}}}, Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}, Tuple{Tuple{Int64, Int64}, Tuple{Int64, Int64}}})
    @ LRUCache ~/.julia/packages/LRUCache/ZH7qB/src/LRUCache.jl:169
  [9] _fsbraid(key::Tuple{TensorKit.FusionTreeBlock{SU3Irrep, 2, 2, Tuple{FusionTree{SU3Irrep, 2, 0, 1}, FusionTree{SU3Irrep, 2, 0, 1}}}, Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}, Tuple{Tuple{Int64, Int64}, Tuple{Int64, Int64}}}, ::TensorKit.GlobalLRUCache)
    @ TensorKit ./reduce.jl:0
 [10] _fsbraid
    @ ./none:0 [inlined]
 [11] braid
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/fusiontrees/fusiontreeblocks.jl:618 [inlined]
 [12] permute
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/fusiontrees/fusiontreeblocks.jl:676 [inlined]
 [13] fusiontreetransform
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/treetransformers.jl:228 [inlined]
 [14] TensorKit.GenericTreeTransformer(transform::TensorKit.var"#fusiontreetransform#259"{Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}}, p::Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}, Vdst::TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 3, 1}, Vsrc::TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2})
    @ TensorKit ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/treetransformers.jl:130
 [15] TensorKit.TreeTransformer(transform::Function, p::Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}, Vdst::TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 3, 1}, Vsrc::TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2})
    @ TensorKit ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/treetransformers.jl:199
 [16] _treepermuter
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/treetransformers.jl:229 [inlined]
 [17] #257
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/auxiliary/caches.jl:108 [inlined]
 [18] get!(default::TensorKit.var"#257#262"{TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 3, 1}, TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2}, Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}}, lru::LRUCache.LRU{Any, Any}, key::Tuple{TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 3, 1}, TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2}, Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}})
    @ LRUCache ~/.julia/packages/LRUCache/ZH7qB/src/LRUCache.jl:169
 [19] treepermuter(Vdst::TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 3, 1}, Vsrc::TensorMapSpace{GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2}, p::Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}, ::TensorKit.GlobalLRUCache)
    @ TensorKit ./reduce.jl:0
 [20] treepermuter
    @ ./none:0 [inlined]
 [21] treepermuter
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/treetransformers.jl:224 [inlined]
 [22] add_permute!
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/indexmanipulations.jl:405 [inlined]
 [23] permute!
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/indexmanipulations.jl:38 [inlined]
 [24] permute(t::TensorMap{Float64, GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2, Vector{Float64}}, ::Tuple{Tuple{Int64, Int64, Int64}, Tuple{Int64}}; copy::Bool)
    @ TensorKit ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/indexmanipulations.jl:77
 [25] permute
    @ ~/.julia/packages/TensorKit/Y7XJ1/src/tensors/indexmanipulations.jl:65 [inlined]
 [26] permute(t::TensorMap{Float64, GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2, Vector{Float64}}, p1::Tuple{Int64, Int64, Int64}, p2::Tuple{Int64}; copy::Bool)
    @ TensorKit ./deprecated.jl:105
 [27] permute(t::TensorMap{Float64, GradedSpace{SU3Irrep, TensorKit.SortedVectorDict{SU3Irrep, Int64}}, 2, 2, Vector{Float64}}, p1::Tuple{Int64, Int64, Int64}, p2::Tuple{Int64})
    @ TensorKit ./deprecated.jl:103
 [28] top-level scope
    @ ~/Documents/tensorkit/ThermalPEPS.jl/nogit.draft.jl:9
in expression starting at /home/ogauthe/Documents/tensorkit/ThermalPEPS.jl/nogit.draft.jl:9

There is no error with the same code with TensorKit release v0.14.11. I run the code on the same machine, just changing TensorKit version so this is not a SUNRepresentation / cache issue.

@lkdvos
Copy link
Member Author

lkdvos commented Oct 29, 2025

Ah, these great self-dual sectors... Thanks for the report and MWE, I'll see if I can find out something although it might not be for this week

@lkdvos lkdvos force-pushed the ld-outer branch 2 times, most recently from 8507405 to fc4deeb Compare November 12, 2025 14:41
@lkdvos
Copy link
Member Author

lkdvos commented Nov 12, 2025

I think at least now this should be working, and it is back in line with the latest main branch so all of the latest and greatest new features should be usable now :)
This PR still needs more work to actually get out of the WIP stage, since I really need to update the tests for this, but I'm somewhat confident that it is working at least.

@codecov
Copy link

codecov bot commented Nov 12, 2025

Codecov Report

❌ Patch coverage is 90.07692% with 129 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/fusiontrees/braiding_manipulations.jl 83.11% 38 Missing ⚠️
src/tensors/indexmanipulations.jl 63.36% 37 Missing ⚠️
src/tensors/treetransformers.jl 59.18% 20 Missing ⚠️
src/fusiontrees/duality_manipulations.jl 96.83% 12 Missing ⚠️
src/TensorKit.jl 14.28% 6 Missing ⚠️
src/tensors/braidingtensor.jl 85.71% 5 Missing ⚠️
src/fusiontrees/basic_manipulations.jl 98.78% 4 Missing ⚠️
src/fusiontrees/fusiontrees.jl 95.29% 4 Missing ⚠️
src/tensors/diagonal.jl 92.85% 2 Missing ⚠️
src/tensors/blockiterator.jl 0.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/auxiliary/auxiliary.jl 93.87% <100.00%> (+1.37%) ⬆️
src/planar/planaroperations.jl 72.79% <100.00%> (ø)
src/spaces/homspace.jl 95.14% <100.00%> (+0.24%) ⬆️
src/tensors/abstracttensor.jl 55.05% <100.00%> (+0.16%) ⬆️
src/tensors/linalg.jl 82.59% <ø> (ø)
src/tensors/tensoroperations.jl 97.67% <100.00%> (+0.25%) ⬆️
src/tensors/blockiterator.jl 45.09% <0.00%> (ø)
src/tensors/diagonal.jl 93.02% <92.85%> (+1.31%) ⬆️
src/fusiontrees/basic_manipulations.jl 98.78% <98.78%> (ø)
src/fusiontrees/fusiontrees.jl 96.11% <95.29%> (+5.78%) ⬆️
... and 6 more

... 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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 2026

Your PR no longer requires formatting changes. Thank you for your contribution!

Comment on lines +162 to +164
conj(Rsymbol(d, c, e) * Fsymbol(d, a, b, e, c′, c)) * Rsymbol(d, a, c′)
else
Rsymbol(c, d, e) * conj(Fsymbol(d, a, b, e, c′, c) * Rsymbol(a, d, c′))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to mention here that the a and d of the last R-symbols might be switched, as that is consistent with https://quantumkithub.github.io/TensorKit.jl/stable/man/sectors/#Manipulations-on-a-fusion-tree

@lkdvos lkdvos requested a review from Jutho March 20, 2026 18:28
function iscyclicpermutation(v1, v2)
length(v1) == length(v2) || return false
return iscyclicpermutation(indexin(v1, v2))
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might these two be a good upstream contribution to Permutations.jl?

#----------------------------------------------
# -> rewrite generic fusion tree in basis of fusion trees in standard form
# -> only depend on Fsymbol
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example might be nice here 🥺

operation is the inverse of split, in the sense that `f == join(split(f, M)...)`
holds for all `M` between `0` and `N`, where `N` is the number of uncoupled sectors of `f`.

See also [`split`](@ref) and [`insertat`](@ref).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another good spot for an example?

k += 1
end
end
k > N₃ && throw(ArgumentError("Not a planar trace"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message here could be better, maybe at least display k and N?

"""
fusionblocks(W::HomSpace)

Return the fusionblocks corresponding to all valid fusion channels of a given `HomSpace`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "Return the FusionTreeBlocks"?

style = FusionStyle(I)
if use_threaded_transform(tdst, transformer)
_add_general_kernel_threaded!(tdst, tsrc, p, transformer, α, β, backend...)
add_kernel_threaded!(style, tdst, tsrc, p, transformer, α, β, backend...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a general comment here but we should think how this interacts with the CUDA/AMD stuff, since threads have their own streams associated with them

r₂ = (p₂..., q₂...)

# TODO: Is it worth to add multithreading support?
if FusionStyle(I) isa UniqueFusion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylistic thing here but this could be refactored into two new functions, dispatching on FusionStyle(I), that might be easier to read?

[`braid(f::FusionTree{I, N}, levels::NTuple{N, Int}, permutation::NTuple{N, Int})`](@ref braid(f::FusionTree{I, N}, levels::NTuple{N, Int}, p::NTuple{N, Int}) where {I <: Sector, N})
[`braid(f::FusionTree{I, N}, p::IndexTuple{N}, levels::IndexTuple{N})`](@ref braid(::FusionTree{I, N}, ::IndexTuple{N}, ::IndexTuple{N}) where {I, N})

where the braid is specified as a permutation, such that the new sector at position `i` was originally at position `permutation[i]`, and where every uncoupled sector is also assigned a level or depth.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
where the braid is specified as a permutation, such that the new sector at position `i` was originally at position `permutation[i]`, and where every uncoupled sector is also assigned a level or depth.
where the braid is specified as a permutation, such that the new sector at position `i` was originally at position `p[i]`, and where every uncoupled sector is also assigned a level or depth.

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.

5 participants