From 503cfa193e359a2f8d3f1499091730ff2bf744e6 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 10:55:41 +0100 Subject: [PATCH 01/25] make testsuite compatible with multifusion cats --- test/runtests.jl | 4 +++- test/sectors.jl | 50 +++++++++++++++++++++++------------------------ test/testsuite.jl | 31 +++++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index f450c8b8..2d2e85ac 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,7 +11,7 @@ const sectorlist = ( DNIrrep{3}, DNIrrep{4}, DNIrrep{5}, CU1Irrep, A4Irrep, SU2Irrep, NewSU2Irrep, FibonacciAnyon, IsingAnyon, FermionParity, - FermionParity ⊠ FermionParity, + FermionParity ⊠ FermionParity, PlanarTrivial ⊠ FibonacciAnyon, Z3Irrep ⊠ Z4Irrep, FermionParity ⊠ U1Irrep ⊠ SU2Irrep, FermionParity ⊠ SU2Irrep ⊠ SU2Irrep, NewSU2Irrep ⊠ NewSU2Irrep, NewSU2Irrep ⊠ SU2Irrep, FermionParity ⊠ SU2Irrep ⊠ NewSU2Irrep, @@ -22,6 +22,8 @@ const sectorlist = ( Z4Element{0}, Z4Element{1}, Z4Element{2}, Z3Element{1} ⊠ SU2Irrep, FibonacciAnyon ⊠ Z4Element{3}, + IsingBimodule, IsingBimodule ⊠ IsingBimodule, IsingBimodule ⊠ Z2Irrep, + IsingBimodule ⊠ SU2Irrep, IsingBimodule ⊠ FibonacciAnyon, TimeReversed{Z2Irrep}, TimeReversed{Z3Irrep}, TimeReversed{Z4Irrep}, TimeReversed{A4Irrep}, TimeReversed{U1Irrep}, TimeReversed{CU1Irrep}, TimeReversed{SU2Irrep}, diff --git a/test/sectors.jl b/test/sectors.jl index 8e8cce3c..5332c39c 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -3,43 +3,44 @@ using LinearAlgebra using TensorKitSectors: TensorKitSectors as TKS @testsuite "Basic properties" I -> begin - s = (randsector(I), randsector(I), randsector(I)) + s = random_fusion(I, Val(2)) + sc = @testinferred(first(⊗(s...))) @test Base.eval(Main, Meta.parse(sprint(show, I))) == I @test Base.eval(Main, Meta.parse(TensorKitSectors.type_repr(I))) == I - @test Base.eval(Main, Meta.parse(sprint(show, s[1]))) == s[1] - @test @testinferred(hash(s[1])) == hash(deepcopy(s[1])) - @test @testinferred(unit(s[1])) == @testinferred(unit(I)) - @testinferred dual(s[1]) - @testinferred dim(s[1]) - @testinferred frobenius_schur_phase(s[1]) - @testinferred frobenius_schur_indicator(s[1]) - @testinferred Nsymbol(s...) - @testinferred Asymbol(s...) - B = @testinferred Bsymbol(s...) - F = @testinferred Fsymbol(s..., s...) + @test Base.eval(Main, Meta.parse(sprint(show, sc))) == sc + @test @testinferred(hash(sc)) == hash(deepcopy(sc)) + if UnitStyle(I) isa SimpleUnit + @test @testinferred(unit(sc)) == @testinferred(unit(I)) + end + @testinferred dual(sc) + @testinferred dim(sc) + @testinferred frobenius_schur_phase(sc) + @testinferred frobenius_schur_indicator(sc) + @testinferred(⊗(s..., s...)) + @testinferred Nsymbol(s..., sc) + @testinferred Asymbol(s..., sc) + B = @testinferred Bsymbol(s..., sc) + s2 = random_fusion(I, Val(3)) + s2c = first(⊗(s2...)) + e, f = first(⊗(s2[1], s2[2])), first(⊗(s2[2], s2[3])) + F = @testinferred Fsymbol(s2..., s2c, e, f) @test eltype(F) === @testinferred fusionscalartype(I) if BraidingStyle(I) isa HasBraiding - R = @testinferred Rsymbol(s...) + R = @testinferred Rsymbol(s..., sc) @test eltype(R) === @testinferred braidingscalartype(I) if FusionStyle(I) === SimpleFusion() @test typeof(R * F) <: @testinferred sectorscalartype(I) else @test Base.promote_op(*, eltype(R), eltype(F)) <: @testinferred sectorscalartype(I) end - else - if FusionStyle(I) === SimpleFusion() - @test typeof(F) <: @testinferred sectorscalartype(I) - else - @test eltype(F) <: @testinferred sectorscalartype(I) - end end - @testinferred(s[1] ⊗ s[2]) - @testinferred(⊗(s..., s...)) end @testsuite "Value iterator" I -> begin @test eltype(values(I)) == I - sprev = unit(I) + simple = UnitStyle(I) isa SimpleUnit + sprev = simple ? unit(I) : first(values(I)) + @test (@testinferred findindex(values(I), sprev)) == 1 for (i, s) in enumerate(values(I)) @test !isless(s, sprev) @test s == @testinferred(values(I)[i]) @@ -47,9 +48,7 @@ end sprev = s i >= 10 && break end - @test unit(I) == first(values(I)) - @test length(allunits(I)) == 1 - @test (@testinferred findindex(values(I), unit(I))) == 1 + @test simple ? length(allunits(I)) == 1 : length(allunits(I)) > 1 for s in smallset(I) @test (@testinferred values(I)[findindex(values(I), s)]) == s end @@ -57,6 +56,7 @@ end @testsuite "Fusion and dimensions" I -> begin for a in smallset(I), b in smallset(I) + isempty(⊗(a, b)) && continue da = dim(a) db = dim(b) dc = sum(c -> dim(c) * Nsymbol(a, b, c), a ⊗ b) diff --git a/test/testsuite.jl b/test/testsuite.jl index 71a07df3..e8f9d543 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -20,11 +20,12 @@ SectorTestSuite.test_sector(MySectorType) Additionally, this test suite exports the following convenience testing utilities: * [`smallset`](@ref) * [`randsector`](@ref) +* [`random_fusion`](@ref) * [`hasfusiontensor`](@ref) """ module SectorTestSuite -export smallset, randsector, hasfusiontensor +export smallset, randsector, random_fusion, hasfusiontensor using Test using TestExtras @@ -69,7 +70,14 @@ function test_sector(I::Type) end end -smallset(::Type{I}) where {I <: Sector} = take(values(I), 5) +function smallset(::Type{I}, size::Int = 5) where {I <: Sector} + Base.IteratorSize(I) === Base.IsInfinite() && return take(values(I), size) + return if length(values(I)) > size + Random.shuffle(collect(values(I)))[1:size] # take random size of elements + else + values(I) # take all + end +end function smallset(::Type{ProductSector{Tuple{I1, I2}}}) where {I1, I2} iter = product(smallset(I1), smallset(I2)) s = collect(i ⊠ j for (i, j) in iter if dim(i) * dim(j) <= 6) @@ -91,7 +99,26 @@ function randsector(::Type{I}) where {I <: Sector} end randsector(::Type{I}) where {I <: Union{Trivial, PlanarTrivial}} = unit(I) +""" + random_fusion(I::Type, ::Val{N}) + +Returns a random tuple of `N` sectors from `I` that have a non-empty coupled sector. +Compatible with any `Sector` type, including those with `UnitStyle(I) == GenericUnit()`. +""" +function random_fusion(I::Type{<:Sector}, ::Val{N}) where {N} + N == 1 && return (randsector(I),) + tail = random_fusion(I, Val(N - 1)) + s = randsector(I) + counter = 0 + while isempty(⊗(s, first(tail))) && counter < 20 + counter += 1 + s = (counter < 20) ? randsector(I) : leftunit(first(tail)) + end + return (s, tail...) +end + function hasfusiontensor(I::Type{<:Sector}) + UnitStyle(I) isa SimpleUnit || return false try fusiontensor(unit(I), unit(I), unit(I)) return true From 73c970780ed5a062aa968ec60629264a52d9253a Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 11:15:02 +0100 Subject: [PATCH 02/25] add unitarity test --- test/sectors.jl | 17 +---------------- test/testsuite.jl | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index 5332c39c..16436b12 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -170,22 +170,7 @@ end @testsuite "Unitarity of F-move" I -> begin for a in smallset(I), b in smallset(I), c in smallset(I) - for d in ⊗(a, b, c) - es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) - fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) - if FusionStyle(I) isa MultiplicityFreeFusion - @test length(es) == length(fs) - F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs] - else - Fblocks = Vector{Any}() - for e in es, f in fs - Fs = Fsymbol(a, b, c, d, e, f) - push!(Fblocks, reshape(Fs, (size(Fs, 1) * size(Fs, 2), size(Fs, 3) * size(Fs, 4)))) - end - F = hvcat(length(fs), Fblocks...) - end - @test isapprox(F' * F, one(F); atol = 1.0e-12, rtol = 1.0e-12) - end + @test unitarity_test(a, b, c) end end diff --git a/test/testsuite.jl b/test/testsuite.jl index e8f9d543..4724c812 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -25,7 +25,7 @@ Additionally, this test suite exports the following convenience testing utilitie """ module SectorTestSuite -export smallset, randsector, random_fusion, hasfusiontensor +export smallset, randsector, random_fusion, hasfusiontensor, unitarity_test using Test using TestExtras @@ -131,6 +131,32 @@ function hasfusiontensor(I::Type{<:Sector}) end end +""" + unitarity_test(a::I, b::I, c::I) where {I <: Sector} + +Tests the unitarity of the F-symbols for the fusion of `a`, `b`, and `c`. +Returns `true` if the F-symbols are unitary, and `false` otherwise. +""" +function unitarity_test(a::I, b::I, c::I) where {I <: Sector} + for d in ⊗(a, b, c) + es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) + fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) + if FusionStyle(I) isa MultiplicityFreeFusion + @test length(es) == length(fs) + F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs] + else + Fblocks = Vector{Any}() + for e in es, f in fs + Fs = Fsymbol(a, b, c, d, e, f) + push!(Fblocks, reshape(Fs, (size(Fs, 1) * size(Fs, 2), size(Fs, 3) * size(Fs, 4)))) + end + F = hvcat(length(fs), Fblocks...) + end + isapprox(F' * F, one(F); atol = 1.0e-12, rtol = 1.0e-12) || return false + end + return true +end + include("sectors.jl") end # module SectorTestSuite From 18a10d999f4526f229014d2e2b23ce8141157c29 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 11:17:49 +0100 Subject: [PATCH 03/25] change test to assert --- test/testsuite.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsuite.jl b/test/testsuite.jl index 4724c812..71a8eb21 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -142,7 +142,7 @@ function unitarity_test(a::I, b::I, c::I) where {I <: Sector} es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) if FusionStyle(I) isa MultiplicityFreeFusion - @test length(es) == length(fs) + @assert length(es) == length(fs) F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs] else Fblocks = Vector{Any}() From 213d2662f5efed516399cca7e6e756b4f201f3ab Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 11:26:26 +0100 Subject: [PATCH 04/25] clean up additional multifusion tests --- test/multifusion.jl | 74 +++++---------------------------------------- 1 file changed, 8 insertions(+), 66 deletions(-) diff --git a/test/multifusion.jl b/test/multifusion.jl index 189ca759..2811777f 100644 --- a/test/multifusion.jl +++ b/test/multifusion.jl @@ -2,9 +2,6 @@ I = IsingBimodule Istr = TensorKitSectors.type_repr(I) @testset "$Istr sector" begin @testset "Basic type properties" begin - @test eval(Meta.parse(sprint(show, I))) == I - @test eval(Meta.parse(TensorKitSectors.type_repr(I))) == I - prodsec = I ⊠ Z2Irrep @test UnitStyle(prodsec) isa GenericUnit @test FusionStyle(prodsec) isa SimpleFusion @@ -23,13 +20,13 @@ Istr = TensorKitSectors.type_repr(I) s = rand((M, Mop, C, D)) @testset "Basic properties" begin - @test @constinferred(unit(C1)) == @constinferred(leftunit(C1)) == - @constinferred(rightunit(C1)) + @test @testinferred(unit(C1)) == @testinferred(leftunit(C1)) == + @testinferred(rightunit(C1)) @test unit(D1) == leftunit(D1) == rightunit(D1) @test unit(C1) == leftunit(M) == rightunit(Mop) @test unit(D1) == rightunit(M) == leftunit(Mop) - @test @constinferred(isunit(C0)) + @test @testinferred(isunit(C0)) @test isunit(D0) @test !isunit(C1) && !isunit(D1) && !isunit(M) && !isunit(Mop) @@ -44,42 +41,14 @@ Istr = TensorKitSectors.type_repr(I) @test length(allunits(I ⊠ I)) == 4 @test leftunit(M ⊠ Mop) == C0 ⊠ D0 == rightunit(Mop ⊠ M) - - @test eval(Meta.parse(sprint(show, s))) == s - @test @constinferred(hash(s)) == hash(deepcopy(s)) - @constinferred dual(s) - @test dual(dual(s)) == s - @constinferred dim(s) - @constinferred frobenius_schur_phase(s) - @constinferred convert(IsingAnyon, s) - - @constinferred Bsymbol(C, C, C) - @constinferred Fsymbol(D, D, D, D, D, D) - end - - @testset "$Istr: Value iterator" begin - @test eltype(values(I)) == I - @test_throws ArgumentError unit(I) - sprev = C0 # first in SectorValues - for (i, s) in enumerate(values(I)) - @test !isless(s, sprev) # confirm compatibility with sort order - @test s == @constinferred (values(I)[i]) - @test findindex(values(I), s) == i - sprev = s - i >= 10 && break - end - @test C0 == first(values(I)) - @test (@constinferred findindex(values(I), C0)) == 1 - for s in collect(values(I)) - @test (@constinferred values(I)[findindex(values(I), s)]) == s - end + @testinferred convert(IsingAnyon, s) end @testset "$Istr: Printing and errors" begin - @test eval(Meta.parse(sprint(show, C))) == C - @test eval(Meta.parse(sprint(show, M))) == M - @test eval(Meta.parse(sprint(show, Mop))) == Mop - @test eval(Meta.parse(sprint(show, D))) == D + @test Base.eval(Meta.parse(sprint(show, C))) == C + @test Base.eval(Meta.parse(sprint(show, M))) == M + @test Base.eval(Meta.parse(sprint(show, Mop))) == Mop + @test Base.eval(Meta.parse(sprint(show, D))) == D @test_throws DomainError unit(M) @test_throws DomainError unit(Mop) end @@ -122,31 +91,4 @@ Istr = TensorKitSectors.type_repr(I) @test_throws argerr Fsymbol(M, Mop, M, Mop, C, D) end - - @testset "$Istr: Unitarity of F-move" begin - objects = collect(values(I)) - for a in objects, b in objects, c in objects - for d in ⊗(a, b, c) - es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) - fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) - @test length(es) == length(fs) - F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs] - @test isapprox(F' * F, one(F); atol = 1.0e-12, rtol = 1.0e-12) - end - end - end - - @testset "$Istr: Triangle equation" begin - for a in smallset(I), b in smallset(I) - @test triangle_equation(a, b; atol = 1.0e-12, rtol = 1.0e-12) - end - end - - @testset "$Istr: Pentagon equation" begin - objects = collect(values(I)) - for a in objects, b in objects, c in objects, d in objects - # compatibility checks built in Fsymbol - @test pentagon_equation(a, b, c, d; atol = 1.0e-12, rtol = 1.0e-12) - end - end end From 12f95fa19e3946b88a1f39e40912dcc466ce1dba Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 11:29:53 +0100 Subject: [PATCH 05/25] typo --- test/testsuite.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsuite.jl b/test/testsuite.jl index 71a8eb21..2b83edb0 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -71,7 +71,7 @@ function test_sector(I::Type) end function smallset(::Type{I}, size::Int = 5) where {I <: Sector} - Base.IteratorSize(I) === Base.IsInfinite() && return take(values(I), size) + Base.IteratorSize(values(I)) === Base.IsInfinite() && return take(values(I), size) return if length(values(I)) > size Random.shuffle(collect(values(I)))[1:size] # take random size of elements else From 58f9b5ff65a76d883ce2c98b292bdb8217311911 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 12:18:18 +0100 Subject: [PATCH 06/25] fix timereversed product sector values iteration --- src/product.jl | 11 +++++++++++ test/testsuite.jl | 9 +++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/product.jl b/src/product.jl index 456252a1..9bd96a07 100644 --- a/src/product.jl +++ b/src/product.jl @@ -34,6 +34,17 @@ function Base.size(::SectorValues{ProductSector{T}}) where {T <: SectorTuple} end Base.length(P::SectorValues{<:ProductSector}) = *(size(P)...) +# time reversed +function Base.IteratorSize(::Type{SectorValues{TimeReversed{ProductSector{T}}}}) where {T <: SectorTuple} + return Base.IteratorSize(SectorValues{ProductSector{T}}) +end +function Base.size(P::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} + return size(SectorValues{ProductSector{T}}()) +end +function Base.length(P::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} + return length(SectorValues{ProductSector{T}}()) +end + function _length(iter::SectorValues{I}) where {I <: Sector} return Base.IteratorSize(iter) === Base.IsInfinite() ? typemax(Int) : length(iter) end diff --git a/test/testsuite.jl b/test/testsuite.jl index 2b83edb0..f616e28e 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -71,11 +71,12 @@ function test_sector(I::Type) end function smallset(::Type{I}, size::Int = 5) where {I <: Sector} - Base.IteratorSize(values(I)) === Base.IsInfinite() && return take(values(I), size) - return if length(values(I)) > size - Random.shuffle(collect(values(I)))[1:size] # take random size of elements + vals = values(I) + Base.IteratorSize(vals) === Base.IsInfinite() && return take(vals, size) + return if length(vals) > size + Random.shuffle(collect(vals))[1:size] # take random size of elements else - values(I) # take all + vals # take all end end function smallset(::Type{ProductSector{Tuple{I1, I2}}}) where {I1, I2} From b37d989b9a21a6b186b717d8b9390e734295b249 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 12:24:01 +0100 Subject: [PATCH 07/25] edit deligne product test --- src/product.jl | 4 ++-- test/runtests.jl | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/product.jl b/src/product.jl index 9bd96a07..0b56d7b2 100644 --- a/src/product.jl +++ b/src/product.jl @@ -38,10 +38,10 @@ Base.length(P::SectorValues{<:ProductSector}) = *(size(P)...) function Base.IteratorSize(::Type{SectorValues{TimeReversed{ProductSector{T}}}}) where {T <: SectorTuple} return Base.IteratorSize(SectorValues{ProductSector{T}}) end -function Base.size(P::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} +function Base.size(::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} return size(SectorValues{ProductSector{T}}()) end -function Base.length(P::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} +function Base.length(::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} return length(SectorValues{ProductSector{T}}()) end diff --git a/test/runtests.jl b/test/runtests.jl index 2d2e85ac..cf0c9159 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -83,8 +83,10 @@ end @testinferred I1 ⊠ I2 @test typeof(a ⊠ b) == I1 ⊠ I2 - @test @testinferred(length(allunits(I1 ⊠ I2))) == 1 - @test @testinferred(unit(I1 ⊠ I2)) == leftunit(a ⊠ b) == rightunit(a ⊠ b) + if UnitStyle(I1 ⊠ I2) isa SimpleUnit + @test @testinferred(length(allunits(I1 ⊠ I2))) == 1 + @test @testinferred(unit(I1 ⊠ I2)) == leftunit(a ⊠ b) == rightunit(a ⊠ b) + end end @test @testinferred(Tuple(SU2Irrep(1) ⊠ U1Irrep(0))) == (SU2Irrep(1), U1Irrep(0)) @test @testinferred(length(FermionParity(1) ⊠ SU2Irrep(1 // 2) ⊠ U1Irrep(1))) == 3 From 057cee7c1e9bbd7c39bb3721d870652c50ccf089 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 19 Feb 2026 12:37:33 +0100 Subject: [PATCH 08/25] isingbimodule is not in base --- test/multifusion.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/multifusion.jl b/test/multifusion.jl index 2811777f..d352811d 100644 --- a/test/multifusion.jl +++ b/test/multifusion.jl @@ -45,10 +45,10 @@ Istr = TensorKitSectors.type_repr(I) end @testset "$Istr: Printing and errors" begin - @test Base.eval(Meta.parse(sprint(show, C))) == C - @test Base.eval(Meta.parse(sprint(show, M))) == M - @test Base.eval(Meta.parse(sprint(show, Mop))) == Mop - @test Base.eval(Meta.parse(sprint(show, D))) == D + @test eval(Meta.parse(sprint(show, C))) == C + @test eval(Meta.parse(sprint(show, M))) == M + @test eval(Meta.parse(sprint(show, Mop))) == Mop + @test eval(Meta.parse(sprint(show, D))) == D @test_throws DomainError unit(M) @test_throws DomainError unit(Mop) end From 1034dcd77404644bd3d22aeb9ad047f67b957a79 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 23 Feb 2026 09:26:08 +0100 Subject: [PATCH 09/25] fix `sectorscalartype` of product sector + bring back relevant test --- src/product.jl | 6 +++++- test/sectors.jl | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/product.jl b/src/product.jl index 0b56d7b2..84325efc 100644 --- a/src/product.jl +++ b/src/product.jl @@ -239,7 +239,11 @@ function braidingscalartype(::Type{<:ProductSector{T}}) where {T <: SectorTuple} return typeof(prod(zero ∘ braidingscalartype, _sectors(T))) end function sectorscalartype(::Type{<:ProductSector{T}}) where {T <: SectorTuple} - return typeof(prod(zero ∘ sectorscalartype, _sectors(T))) + return if BraidingStyle(ProductSector{T}) == NoBraiding() + typeof(prod(zero ∘ fusionscalartype, _sectors(T))) + else + typeof(prod(zero ∘ sectorscalartype, _sectors(T))) + end end function dimscalartype(::Type{<:ProductSector{T}}) where {T <: SectorTuple} return typeof(prod(zero ∘ dimscalartype, _sectors(T))) diff --git a/test/sectors.jl b/test/sectors.jl index 16436b12..ac814070 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -33,6 +33,12 @@ using TensorKitSectors: TensorKitSectors as TKS else @test Base.promote_op(*, eltype(R), eltype(F)) <: @testinferred sectorscalartype(I) end + else + if FusionStyle(I) === SimpleFusion() + @test typeof(F) <: @testinferred sectorscalartype(I) + else + @test eltype(F) <: @testinferred sectorscalartype(I) + end end end From d447ed17c15b9a0a225ab9ef678f3a9fe4703d89 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 23 Feb 2026 09:28:52 +0100 Subject: [PATCH 10/25] remove `allunits` condition --- test/sectors.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index ac814070..328f510f 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -44,8 +44,7 @@ end @testsuite "Value iterator" I -> begin @test eltype(values(I)) == I - simple = UnitStyle(I) isa SimpleUnit - sprev = simple ? unit(I) : first(values(I)) + sprev = UnitStyle(I) isa SimpleUnit ? unit(I) : first(values(I)) @test (@testinferred findindex(values(I), sprev)) == 1 for (i, s) in enumerate(values(I)) @test !isless(s, sprev) @@ -54,7 +53,6 @@ end sprev = s i >= 10 && break end - @test simple ? length(allunits(I)) == 1 : length(allunits(I)) > 1 for s in smallset(I) @test (@testinferred values(I)[findindex(values(I), s)]) == s end From ed4e473d1b1499fc333ece7adcd90a6510ed791b Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 23 Feb 2026 09:35:15 +0100 Subject: [PATCH 11/25] edit iterating over time-reversed product scetor --- src/product.jl | 11 ----------- src/timereversed.jl | 9 +++++++-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/product.jl b/src/product.jl index 84325efc..41d90905 100644 --- a/src/product.jl +++ b/src/product.jl @@ -34,17 +34,6 @@ function Base.size(::SectorValues{ProductSector{T}}) where {T <: SectorTuple} end Base.length(P::SectorValues{<:ProductSector}) = *(size(P)...) -# time reversed -function Base.IteratorSize(::Type{SectorValues{TimeReversed{ProductSector{T}}}}) where {T <: SectorTuple} - return Base.IteratorSize(SectorValues{ProductSector{T}}) -end -function Base.size(::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} - return size(SectorValues{ProductSector{T}}()) -end -function Base.length(::SectorValues{TimeReversed{ProductSector{T}}}) where {T <: SectorTuple} - return length(SectorValues{ProductSector{T}}()) -end - function _length(iter::SectorValues{I}) where {I <: Sector} return Base.IteratorSize(iter) === Base.IsInfinite() ? typemax(Int) : length(iter) end diff --git a/src/timereversed.jl b/src/timereversed.jl index 7b70d8c4..9debce81 100644 --- a/src/timereversed.jl +++ b/src/timereversed.jl @@ -51,10 +51,15 @@ dual(c::TimeReversed{I}) where {I <: Sector} = TimeReversed{I}(dual(c.a)) function ⊗(c1::TimeReversed{I}, c2::TimeReversed{I}) where {I <: Sector} return SectorSet{TimeReversed{I}}(TimeReversed{I}, c1.a ⊗ c2.a) end -function Base.IteratorSize(::Type{SectorValues{TimeReversed{I}}}) where {I <: Sector} + +# capture product sectors as well +function Base.IteratorSize(::Type{SectorValues{TimeReversed{I}}}) where {I} return Base.IteratorSize(values(I)) end -function Base.length(::SectorValues{TimeReversed{I}}) where {I <: Sector} +function Base.size(::SectorValues{TimeReversed{I}}) where {I} + return size(values(I)) +end +function Base.length(::SectorValues{TimeReversed{I}}) where {I} return length(values(I)) end function Base.getindex(::SectorValues{TimeReversed{I}}, i::Int) where {I <: Sector} From 44d7300d4d281f0cc6cda07282dbd7beb666e592 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 23 Feb 2026 09:53:35 +0100 Subject: [PATCH 12/25] make `smallset` type-stable --- test/testsuite.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/testsuite.jl b/test/testsuite.jl index f616e28e..1c18e8c9 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -70,13 +70,18 @@ function test_sector(I::Type) end end -function smallset(::Type{I}, size::Int = 5) where {I <: Sector} +function Random.rand(::SectorValues{I}, size::Int) where {I <: Sector} + Base.IteratorSize(values(I)) === Base.IsInfinite() && + throw(ArgumentError("Cannot take random sample of infinite sector values.")) + return rand(collect(values(I)), size) +end +function smallset(::Type{I}, size::Int = 10) where {I <: Sector} vals = values(I) Base.IteratorSize(vals) === Base.IsInfinite() && return take(vals, size) return if length(vals) > size - Random.shuffle(collect(vals))[1:size] # take random size of elements + Random.rand(vals, size) # take random size of elements else - vals # take all + Random.rand(vals, length(vals)) # take all end end function smallset(::Type{ProductSector{Tuple{I1, I2}}}) where {I1, I2} From 349d90a868bf0df5d4e17ff1c3a969b3455b7ff0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 23 Feb 2026 10:33:27 +0100 Subject: [PATCH 13/25] `random_fusion` vector output --- test/sectors.jl | 4 ++-- test/testsuite.jl | 32 +++++++++++++++++++------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index 328f510f..c883881a 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -3,7 +3,7 @@ using LinearAlgebra using TensorKitSectors: TensorKitSectors as TKS @testsuite "Basic properties" I -> begin - s = random_fusion(I, Val(2)) + s = random_fusion(I, 2) sc = @testinferred(first(⊗(s...))) @test Base.eval(Main, Meta.parse(sprint(show, I))) == I @test Base.eval(Main, Meta.parse(TensorKitSectors.type_repr(I))) == I @@ -20,7 +20,7 @@ using TensorKitSectors: TensorKitSectors as TKS @testinferred Nsymbol(s..., sc) @testinferred Asymbol(s..., sc) B = @testinferred Bsymbol(s..., sc) - s2 = random_fusion(I, Val(3)) + s2 = random_fusion(I, 3) s2c = first(⊗(s2...)) e, f = first(⊗(s2[1], s2[2])), first(⊗(s2[2], s2[3])) F = @testinferred Fsymbol(s2..., s2c, e, f) diff --git a/test/testsuite.jl b/test/testsuite.jl index 1c18e8c9..74925765 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -78,10 +78,11 @@ end function smallset(::Type{I}, size::Int = 10) where {I <: Sector} vals = values(I) Base.IteratorSize(vals) === Base.IsInfinite() && return take(vals, size) - return if length(vals) > size + l = length(vals) + return if l > size Random.rand(vals, size) # take random size of elements else - Random.rand(vals, length(vals)) # take all + reshape(collect(vals), l) # take all, no copies end end function smallset(::Type{ProductSector{Tuple{I1, I2}}}) where {I1, I2} @@ -106,21 +107,26 @@ end randsector(::Type{I}) where {I <: Union{Trivial, PlanarTrivial}} = unit(I) """ - random_fusion(I::Type, ::Val{N}) + random_fusion(I::Type, N::Int) -Returns a random tuple of `N` sectors from `I` that have a non-empty coupled sector. +Returns a vector of `N` random sectors from `I` that have a non-empty coupled sector. Compatible with any `Sector` type, including those with `UnitStyle(I) == GenericUnit()`. """ -function random_fusion(I::Type{<:Sector}, ::Val{N}) where {N} - N == 1 && return (randsector(I),) - tail = random_fusion(I, Val(N - 1)) - s = randsector(I) - counter = 0 - while isempty(⊗(s, first(tail))) && counter < 20 - counter += 1 - s = (counter < 20) ? randsector(I) : leftunit(first(tail)) +function random_fusion(I::Type{<:Sector}, N::Int) + vec = Vector{I}(undef, N) + vec[1] = randsector(I) + N == 1 && return vec + for i in 2:N + s = randsector(I) + sprev = vec[i - 1] + counter = 0 + while isempty(⊗(sprev, s)) && counter < 20 + counter += 1 + s = (counter < 20) ? randsector(I) : rightunit(sprev) + end + vec[i] = s end - return (s, tail...) + return vec end function hasfusiontensor(I::Type{<:Sector}) From cb7fc38280b19f7635a291cd4a0fc08dbab4a0a7 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 23 Feb 2026 10:42:32 +0100 Subject: [PATCH 14/25] relax default size for smallset --- test/testsuite.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsuite.jl b/test/testsuite.jl index 74925765..3851434f 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -75,7 +75,7 @@ function Random.rand(::SectorValues{I}, size::Int) where {I <: Sector} throw(ArgumentError("Cannot take random sample of infinite sector values.")) return rand(collect(values(I)), size) end -function smallset(::Type{I}, size::Int = 10) where {I <: Sector} +function smallset(::Type{I}, size::Int = 5) where {I <: Sector} vals = values(I) Base.IteratorSize(vals) === Base.IsInfinite() && return take(vals, size) l = length(vals) From 3152ec7abca14be3a50414a976b69cd13f836477 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 23 Feb 2026 11:25:27 +0100 Subject: [PATCH 15/25] check colorings where relevant --- test/sectors.jl | 20 ++++++++++++++++---- test/testsuite.jl | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index c883881a..bb51392d 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -173,20 +173,32 @@ end end @testsuite "Unitarity of F-move" I -> begin - for a in smallset(I), b in smallset(I), c in smallset(I) - @test unitarity_test(a, b, c) + for a in smallset(I), b in smallset(I) + !isempty(⊗(a, b)) || continue + for c in smallset(I) + !isempty(⊗(b, c)) || continue + @test unitarity_test(a, b, c; atol = 1.0e-12, rtol = 1.0e-12) + end end end @testsuite "Triangle equation" I -> begin for a in smallset(I), b in smallset(I) + !isempty(⊗(a, b)) || continue @test triangle_equation(a, b; atol = 1.0e-12, rtol = 1.0e-12) end end @testsuite "Pentagon equation" I -> begin - for a in smallset(I), b in smallset(I), c in smallset(I), d in smallset(I) - @test pentagon_equation(a, b, c, d; atol = 1.0e-12, rtol = 1.0e-12) + for a in smallset(I), b in smallset(I) + !isempty(⊗(a, b)) || continue + for c in smallset(I) + !isempty(⊗(b, c)) || continue + for d in smallset(I) + !isempty(⊗(c, d)) || continue + @test pentagon_equation(a, b, c, d; atol = 1.0e-12, rtol = 1.0e-12) + end + end end end diff --git a/test/testsuite.jl b/test/testsuite.jl index 3851434f..10c503d5 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -149,7 +149,7 @@ end Tests the unitarity of the F-symbols for the fusion of `a`, `b`, and `c`. Returns `true` if the F-symbols are unitary, and `false` otherwise. """ -function unitarity_test(a::I, b::I, c::I) where {I <: Sector} +function unitarity_test(a::I, b::I, c::I; kwargs...) where {I <: Sector} for d in ⊗(a, b, c) es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) @@ -164,7 +164,7 @@ function unitarity_test(a::I, b::I, c::I) where {I <: Sector} end F = hvcat(length(fs), Fblocks...) end - isapprox(F' * F, one(F); atol = 1.0e-12, rtol = 1.0e-12) || return false + isapprox(F' * F, one(F); kwargs...) || return false end return true end From 23c32e754f8087d94586f7a89310f4c1e6763a00 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 24 Feb 2026 09:54:01 +0100 Subject: [PATCH 16/25] rename unitarity test --- test/sectors.jl | 2 +- test/testsuite.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index bb51392d..c97ac2c7 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -177,7 +177,7 @@ end !isempty(⊗(a, b)) || continue for c in smallset(I) !isempty(⊗(b, c)) || continue - @test unitarity_test(a, b, c; atol = 1.0e-12, rtol = 1.0e-12) + @test F_unitarity_test(a, b, c; atol = 1.0e-12, rtol = 1.0e-12) end end end diff --git a/test/testsuite.jl b/test/testsuite.jl index 10c503d5..e4aad4d1 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -25,7 +25,7 @@ Additionally, this test suite exports the following convenience testing utilitie """ module SectorTestSuite -export smallset, randsector, random_fusion, hasfusiontensor, unitarity_test +export smallset, randsector, random_fusion, hasfusiontensor, F_unitarity_test using Test using TestExtras @@ -144,12 +144,12 @@ function hasfusiontensor(I::Type{<:Sector}) end """ - unitarity_test(a::I, b::I, c::I) where {I <: Sector} + F_unitarity_test(a::I, b::I, c::I; kwargs...) where {I <: Sector} Tests the unitarity of the F-symbols for the fusion of `a`, `b`, and `c`. Returns `true` if the F-symbols are unitary, and `false` otherwise. """ -function unitarity_test(a::I, b::I, c::I; kwargs...) where {I <: Sector} +function F_unitarity_test(a::I, b::I, c::I; kwargs...) where {I <: Sector} for d in ⊗(a, b, c) es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) From 5aa1bee21a1eca73aead28880733170a58eea34e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 24 Feb 2026 09:55:12 +0100 Subject: [PATCH 17/25] fusiontensor edit --- test/testsuite.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/testsuite.jl b/test/testsuite.jl index e4aad4d1..494181cd 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -130,9 +130,9 @@ function random_fusion(I::Type{<:Sector}, N::Int) end function hasfusiontensor(I::Type{<:Sector}) - UnitStyle(I) isa SimpleUnit || return false try - fusiontensor(unit(I), unit(I), unit(I)) + u = first(allunits(I)) + fusiontensor(u, u, u) return true catch e if e isa MethodError From 16702b610f326d13cea2a7de0fb46c09fb1a408e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 4 Mar 2026 15:38:36 +0100 Subject: [PATCH 18/25] `eval_module` function for parse-show tests --- test/sectors.jl | 7 ++++--- test/testsuite.jl | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index c97ac2c7..a79d0ea7 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -5,9 +5,10 @@ using TensorKitSectors: TensorKitSectors as TKS @testsuite "Basic properties" I -> begin s = random_fusion(I, 2) sc = @testinferred(first(⊗(s...))) - @test Base.eval(Main, Meta.parse(sprint(show, I))) == I - @test Base.eval(Main, Meta.parse(TensorKitSectors.type_repr(I))) == I - @test Base.eval(Main, Meta.parse(sprint(show, sc))) == sc + # @test Base.eval(Main, Meta.parse(sprint(show, I))) == I + # @test Base.eval(Main, Meta.parse(TensorKitSectors.type_repr(I))) == I + # @test Base.eval(Main, Meta.parse(sprint(show, sc))) == sc + @test eval_module(sc) @test @testinferred(hash(sc)) == hash(deepcopy(sc)) if UnitStyle(I) isa SimpleUnit @test @testinferred(unit(sc)) == @testinferred(unit(I)) diff --git a/test/testsuite.jl b/test/testsuite.jl index 494181cd..cbf094e7 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -22,10 +22,13 @@ Additionally, this test suite exports the following convenience testing utilitie * [`randsector`](@ref) * [`random_fusion`](@ref) * [`hasfusiontensor`](@ref) +* [`F_unitarity_test`](@ref) +* [`eval_module`](@ref) """ module SectorTestSuite export smallset, randsector, random_fusion, hasfusiontensor, F_unitarity_test +export eval_module using Test using TestExtras @@ -143,6 +146,20 @@ function hasfusiontensor(I::Type{<:Sector}) end end +""" + eval_module(a::I; _module = parentmodule(I)) where {I <: Sector} + +Passes the sector `a` through the `show` machinery to generate a string representation, then parses and evaluates the resulting expression in the module `_module`, by default the parent module of the sector. +""" +function eval_module(a::I; _module = parentmodule(I)) where {I <: Sector} + if occursin("NewSU2Irrep", string(I)) # special case since NewSU2Irrep is defined in test setup, not in TensorKitSectors + _module = Main + end + Base.eval(_module, Meta.parse(sprint(show, I))) == I && + Base.eval(_module, Meta.parse(TensorKitSectors.type_repr(I))) == I && + Base.eval(_module, Meta.parse(sprint(show, a))) == a +end + """ F_unitarity_test(a::I, b::I, c::I; kwargs...) where {I <: Sector} From 909c771384d4c54475f8e1186b63ec43d7ea88ea Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 4 Mar 2026 15:42:08 +0100 Subject: [PATCH 19/25] format --- test/testsuite.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/testsuite.jl b/test/testsuite.jl index cbf094e7..58e5cd59 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -155,9 +155,9 @@ function eval_module(a::I; _module = parentmodule(I)) where {I <: Sector} if occursin("NewSU2Irrep", string(I)) # special case since NewSU2Irrep is defined in test setup, not in TensorKitSectors _module = Main end - Base.eval(_module, Meta.parse(sprint(show, I))) == I && - Base.eval(_module, Meta.parse(TensorKitSectors.type_repr(I))) == I && - Base.eval(_module, Meta.parse(sprint(show, a))) == a + return Base.eval(_module, Meta.parse(sprint(show, I))) == I && + Base.eval(_module, Meta.parse(TensorKitSectors.type_repr(I))) == I && + Base.eval(_module, Meta.parse(sprint(show, a))) == a end """ From 2bc98b448d5b738950074877d94e74c8fdeef0c8 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 6 Mar 2026 09:56:08 +0100 Subject: [PATCH 20/25] remove commented code --- test/sectors.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index a79d0ea7..4ee4b989 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -5,9 +5,6 @@ using TensorKitSectors: TensorKitSectors as TKS @testsuite "Basic properties" I -> begin s = random_fusion(I, 2) sc = @testinferred(first(⊗(s...))) - # @test Base.eval(Main, Meta.parse(sprint(show, I))) == I - # @test Base.eval(Main, Meta.parse(TensorKitSectors.type_repr(I))) == I - # @test Base.eval(Main, Meta.parse(sprint(show, sc))) == sc @test eval_module(sc) @test @testinferred(hash(sc)) == hash(deepcopy(sc)) if UnitStyle(I) isa SimpleUnit From 4a38245cdfa0351af38ce1f128b2acb2742983af Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sun, 8 Mar 2026 08:55:54 -0400 Subject: [PATCH 21/25] remove eval_module --- test/sectors.jl | 1 - test/testsuite.jl | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index 0db92289..0edd153d 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -5,7 +5,6 @@ using TensorKitSectors: TensorKitSectors as TKS @testsuite "Basic properties" I -> begin s = random_fusion(I, 2) sc = @testinferred(first(⊗(s...))) - @test eval_module(sc) @test @testinferred(hash(sc)) == hash(deepcopy(sc)) if UnitStyle(I) isa SimpleUnit @test @testinferred(unit(sc)) == @testinferred(unit(I)) diff --git a/test/testsuite.jl b/test/testsuite.jl index 8f44937c..d84e383f 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -23,12 +23,10 @@ Additionally, this test suite exports the following convenience testing utilitie * [`random_fusion`](@ref) * [`hasfusiontensor`](@ref) * [`F_unitarity_test`](@ref) -* [`eval_module`](@ref) """ module SectorTestSuite export smallset, randsector, random_fusion, hasfusiontensor, F_unitarity_test -export eval_module using Test using TestExtras @@ -126,20 +124,6 @@ function hasfusiontensor(I::Type{<:Sector}) end end -""" - eval_module(a::I; _module = parentmodule(I)) where {I <: Sector} - -Passes the sector `a` through the `show` machinery to generate a string representation, then parses and evaluates the resulting expression in the module `_module`, by default the parent module of the sector. -""" -function eval_module(a::I; _module = parentmodule(I)) where {I <: Sector} - if occursin("NewSU2Irrep", string(I)) # special case since NewSU2Irrep is defined in test setup, not in TensorKitSectors - _module = Main - end - return Base.eval(_module, Meta.parse(sprint(show, I))) == I && - Base.eval(_module, Meta.parse(TensorKitSectors.type_repr(I))) == I && - Base.eval(_module, Meta.parse(sprint(show, a))) == a -end - """ F_unitarity_test(a::I, b::I, c::I; kwargs...) where {I <: Sector} From e933f0f6ff429b5ca275b942cc3646debb96c5a0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 8 Mar 2026 17:05:14 +0100 Subject: [PATCH 22/25] `can_fuse` for readability --- test/sectors.jl | 14 +++++++------- test/testsuite.jl | 9 ++++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index 0edd153d..050eb969 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -69,7 +69,7 @@ end @testsuite "Fusion and dimensions" I -> begin for a in smallset(I), b in smallset(I) - isempty(⊗(a, b)) && continue + can_fuse(a, b) || continue da = dim(a) db = dim(b) dc = sum(c -> dim(c) * Nsymbol(a, b, c), a ⊗ b) @@ -183,9 +183,9 @@ end @testsuite "Unitarity of F-move" I -> begin for a in smallset(I), b in smallset(I) - !isempty(⊗(a, b)) || continue + can_fuse(a, b) || continue for c in smallset(I) - !isempty(⊗(b, c)) || continue + can_fuse(b, c) || continue @test F_unitarity_test(a, b, c; atol = 1.0e-12, rtol = 1.0e-12) end end @@ -193,18 +193,18 @@ end @testsuite "Triangle equation" I -> begin for a in smallset(I), b in smallset(I) - !isempty(⊗(a, b)) || continue + can_fuse(a, b) || continue @test triangle_equation(a, b; atol = 1.0e-12, rtol = 1.0e-12) end end @testsuite "Pentagon equation" I -> begin for a in smallset(I), b in smallset(I) - !isempty(⊗(a, b)) || continue + can_fuse(a, b) || continue for c in smallset(I) - !isempty(⊗(b, c)) || continue + can_fuse(b, c) || continue for d in smallset(I) - !isempty(⊗(c, d)) || continue + can_fuse(c, d) || continue @test pentagon_equation(a, b, c, d; atol = 1.0e-12, rtol = 1.0e-12) end end diff --git a/test/testsuite.jl b/test/testsuite.jl index d84e383f..209eaa75 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -87,6 +87,13 @@ function randsector(::Type{I}) where {I <: Sector} end randsector(::Type{I}) where {I <: Union{Trivial, PlanarTrivial}} = unit(I) +""" + can_fuse(a::I, b::I) where {I <: Sector} + +Returns a boolean indicating whether the fusion of `a` and `b` is allowed, i.e. whether there exists a sector `c` such that `c ∈ ⊗(a, b)`. +""" +can_fuse(a::I, b::I) where {I <: Sector} = !isempty(⊗(a, b)) + """ random_fusion(I::Type, N::Int) @@ -101,7 +108,7 @@ function random_fusion(I::Type{<:Sector}, N::Int) s = randsector(I) sprev = vec[i - 1] counter = 0 - while isempty(⊗(sprev, s)) && counter < 20 + while !can_fuse(sprev, s) && counter < 20 counter += 1 s = (counter < 20) ? randsector(I) : rightunit(sprev) end From 9ca9a17580ad98b313dd0ea8e2d99450c9aca2ba Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 8 Mar 2026 17:15:12 +0100 Subject: [PATCH 23/25] value iterator suggestion --- test/sectors.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/sectors.jl b/test/sectors.jl index 050eb969..88cd7d30 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -53,9 +53,11 @@ end @testsuite "Value iterator" I -> begin @test eltype(values(I)) == I - sprev = UnitStyle(I) isa SimpleUnit ? unit(I) : first(values(I)) - @test (@testinferred findindex(values(I), sprev)) == 1 - for (i, s) in enumerate(values(I)) + sprev, rest = Iterators.peel(values(I)) + i = 1 + @test (@testinferred findindex(values(I), sprev)) == i + for s in rest + i += 1 @test !isless(s, sprev) @test s == @testinferred(values(I)[i]) @test findindex(values(I), s) == i From 49be403de07c22d3ab176aa8fcac6f236cc8fbe4 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 9 Mar 2026 09:25:44 +0100 Subject: [PATCH 24/25] export `can_fuse` and remove unused import --- test/testsuite.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/testsuite.jl b/test/testsuite.jl index 209eaa75..de4348f8 100644 --- a/test/testsuite.jl +++ b/test/testsuite.jl @@ -23,17 +23,18 @@ Additionally, this test suite exports the following convenience testing utilitie * [`random_fusion`](@ref) * [`hasfusiontensor`](@ref) * [`F_unitarity_test`](@ref) +* [`can_fuse`](@ref) """ module SectorTestSuite -export smallset, randsector, random_fusion, hasfusiontensor, F_unitarity_test +export smallset, randsector, random_fusion, hasfusiontensor, F_unitarity_test, can_fuse using Test using TestExtras using TensorKitSectors using TensorKitSectors: type_repr using Random -using Base.Iterators: take, product +using Base.Iterators: take const tests = Dict() @@ -90,7 +91,8 @@ randsector(::Type{I}) where {I <: Union{Trivial, PlanarTrivial}} = unit(I) """ can_fuse(a::I, b::I) where {I <: Sector} -Returns a boolean indicating whether the fusion of `a` and `b` is allowed, i.e. whether there exists a sector `c` such that `c ∈ ⊗(a, b)`. +Returns a boolean indicating whether the fusion of `a` and `b` is allowed, +i.e. whether there exists a sector `c` such that `c ∈ ⊗(a, b)`. """ can_fuse(a::I, b::I) where {I <: Sector} = !isempty(⊗(a, b)) From ba5a8230917efcdf3f1696b7092c307dc7f6f755 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 9 Mar 2026 13:32:32 +0100 Subject: [PATCH 25/25] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3503d991..d7c5f618 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "TensorKitSectors" uuid = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" authors = ["Lukas Devos", "Jutho Haegeman"] -version = "0.3.6" +version = "0.3.7" [deps] HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721"