Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
503cfa1
make testsuite compatible with multifusion cats
borisdevos Feb 19, 2026
73c9707
add unitarity test
borisdevos Feb 19, 2026
18a10d9
change test to assert
borisdevos Feb 19, 2026
213d266
clean up additional multifusion tests
borisdevos Feb 19, 2026
12f95fa
typo
borisdevos Feb 19, 2026
58f9b5f
fix timereversed product sector values iteration
borisdevos Feb 19, 2026
b37d989
edit deligne product test
borisdevos Feb 19, 2026
057cee7
isingbimodule is not in base
borisdevos Feb 19, 2026
1034dcd
fix `sectorscalartype` of product sector + bring back relevant test
borisdevos Feb 23, 2026
d447ed1
remove `allunits` condition
borisdevos Feb 23, 2026
ed4e473
edit iterating over time-reversed product scetor
borisdevos Feb 23, 2026
44d7300
make `smallset` type-stable
borisdevos Feb 23, 2026
349d90a
`random_fusion` vector output
borisdevos Feb 23, 2026
cb7fc38
relax default size for smallset
borisdevos Feb 23, 2026
3152ec7
check colorings where relevant
borisdevos Feb 23, 2026
23c32e7
rename unitarity test
borisdevos Feb 24, 2026
5aa1bee
fusiontensor edit
borisdevos Feb 24, 2026
d1f7f7f
Merge branch 'main' of https://github.com/QuantumKitHub/TensorKitSect…
borisdevos Mar 3, 2026
16702b6
`eval_module` function for parse-show tests
borisdevos Mar 4, 2026
909c771
format
borisdevos Mar 4, 2026
2b5e7fb
Merge branch 'main' of https://github.com/QuantumKitHub/TensorKitSect…
borisdevos Mar 6, 2026
2bc98b4
remove commented code
borisdevos Mar 6, 2026
fa15e2e
Merge branch 'main' into bd/multitestcompat
lkdvos Mar 8, 2026
4a38245
remove eval_module
lkdvos Mar 8, 2026
e933f0f
`can_fuse` for readability
borisdevos Mar 8, 2026
9ca9a17
value iterator suggestion
borisdevos Mar 8, 2026
49be403
export `can_fuse` and remove unused import
borisdevos Mar 9, 2026
504b113
Merge branch 'main' of https://github.com/QuantumKitHub/TensorKitSect…
borisdevos Mar 9, 2026
ba5a823
bump version
borisdevos Mar 9, 2026
34f8fad
Merge branch 'main' into bd/multitestcompat
borisdevos Mar 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/product.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}})
Comment thread
borisdevos marked this conversation as resolved.
Outdated
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
Expand Down
66 changes: 4 additions & 62 deletions test/multifusion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -44,35 +41,7 @@ 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
Expand Down Expand Up @@ -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
10 changes: 7 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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},
Expand Down Expand Up @@ -81,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
Expand Down
67 changes: 26 additions & 41 deletions test/sectors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,60 @@ 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])
@test findindex(values(I), s) == i
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
Comment thread
borisdevos marked this conversation as resolved.
Outdated
for s in smallset(I)
@test (@testinferred values(I)[findindex(values(I), s)]) == s
end
end

@testsuite "Fusion and dimensions" I -> begin
for a in smallset(I), b in smallset(I)
isempty(⊗(a, b)) && continue
Comment thread
borisdevos marked this conversation as resolved.
Outdated
da = dim(a)
db = dim(b)
dc = sum(c -> dim(c) * Nsymbol(a, b, c), a ⊗ b)
Expand Down Expand Up @@ -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

Expand Down
58 changes: 56 additions & 2 deletions test/testsuite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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, unitarity_test

using Test
using TestExtras
Expand Down Expand Up @@ -69,7 +70,15 @@ 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}
Comment thread
borisdevos marked this conversation as resolved.
Outdated
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
Comment thread
borisdevos marked this conversation as resolved.
Outdated
else
vals # 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)
Expand All @@ -91,7 +100,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}
Comment thread
borisdevos marked this conversation as resolved.
Outdated
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))
Comment thread
borisdevos marked this conversation as resolved.
Outdated
return true
Expand All @@ -104,6 +132,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
@assert 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