From 882617d45e0bc7121a732058f1058a20b84529e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 11:28:12 -0400 Subject: [PATCH 001/194] clean start for non-abelian on main rebase on latest GradedAxes set GradedAxes as a Sector dependency define fusion rules for simple categories pass test for simple categories --- NDTensors/src/imports.jl | 2 +- .../src/lib/GradedAxes/src/GradedAxes.jl | 1 - .../src/lib/GradedAxes/src/gradedunitrange.jl | 4 + .../src/lib/GradedAxes/test/test_basics.jl | 7 +- .../src/lib/Sectors/src/abstractcategory.jl | 114 ++++++++++++++---- .../Sectors/src/category_definitions/fib.jl | 4 +- .../Sectors/src/category_definitions/ising.jl | 4 +- .../Sectors/src/category_definitions/su.jl | 14 ++- .../Sectors/src/category_definitions/su2.jl | 8 +- .../Sectors/src/category_definitions/su2k.jl | 6 +- .../Sectors/src/category_definitions/u1.jl | 4 +- .../Sectors/src/category_definitions/zn.jl | 10 +- .../src/lib/Sectors/src/category_product.jl | 17 ++- .../src/lib/Sectors/test/test_category.jl | 70 +++++------ .../lib/Sectors/test/test_category_product.jl | 28 +++-- .../src/lib/Sectors/test/test_graded_axes.jl | 18 +++ 16 files changed, 221 insertions(+), 90 deletions(-) create mode 100644 NDTensors/src/lib/Sectors/test/test_graded_axes.jl diff --git a/NDTensors/src/imports.jl b/NDTensors/src/imports.jl index 21d3bcd5d5..e4c0feb814 100644 --- a/NDTensors/src/imports.jl +++ b/NDTensors/src/imports.jl @@ -35,9 +35,9 @@ for lib in [ :Expose, :BroadcastMapConversion, :RankFactorization, - :Sectors, :LabelledNumbers, :GradedAxes, + :Sectors, :TensorAlgebra, :SparseArrayInterface, :SparseArrayDOKs, diff --git a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl index 3524abcaba..42392c7394 100644 --- a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl +++ b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl @@ -3,5 +3,4 @@ include("gradedunitrange.jl") include("fusion.jl") include("dual.jl") include("unitrangedual.jl") -include("../ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl") end diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index c10d5052e3..f824b4c9ab 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -80,6 +80,10 @@ function gradedrange(lblocklengths::AbstractVector{<:Pair{<:Any,<:Integer}}) return gradedrange(labelled.(last.(lblocklengths), first.(lblocklengths))) end +function chain(a::GradedUnitRange, b::GradedUnitRange) + return gradedrange(vcat(blocklengths(a), blocklengths(b))) +end + function labelled_blocks(a::BlockedUnitRange, labels) return BlockArrays._BlockedUnitRange(a.first, labelled.(a.lasts, labels)) end diff --git a/NDTensors/src/lib/GradedAxes/test/test_basics.jl b/NDTensors/src/lib/GradedAxes/test/test_basics.jl index aa3a9d7560..5881fc7bbb 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_basics.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_basics.jl @@ -8,7 +8,7 @@ using BlockArrays: blocklength, blocklengths, blocks -using NDTensors.GradedAxes: GradedUnitRange, blocklabels, gradedrange +using NDTensors.GradedAxes: GradedUnitRange, blocklabels, chain, gradedrange using NDTensors.LabelledNumbers: LabelledUnitRange, label, labelled, unlabel using Test: @test, @test_broken, @testset @testset "GradedAxes basics" begin @@ -120,5 +120,10 @@ using Test: @test, @test_broken, @testset @test blocklabels(a) == ["z", "y"] @test a[Block(1)] == 7:8 @test a[Block(2)] == 4:5 + + x = gradedrange(["x" => 2, "y" => 3]) + y = gradedrange(["x" => 1, "z" => 2]) + z = chain(x, y) + @test z == gradedrange(["x" => 2, "y" => 3, "x" => 1, "z" => 2]) end end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 4aaf56c6c5..85a2a4fbe2 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -1,26 +1,13 @@ -abstract type AbstractCategory end - -label(c::AbstractCategory) = error("method `label` not defined for type $(typeof(c))") - -function dimension(c::AbstractCategory) - return error("method `dimension` not defined for type $(typeof(c))") -end - -function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) - return error("`label_fusion_rule` not defined for type $(category_type).") -end - -function fusion_rule(c1::AbstractCategory, c2::AbstractCategory) - category_type = typeof(c1) - return [category_type(l) for l in label_fusion_rule(category_type, label(c1), label(c2))] -end +# This file defines the abstract type AbstractCategory +# all fusion categories (Z{2}, SU2, Ising...) are subtypes of AbstractCategory -⊗(c1::AbstractCategory, c2::AbstractCategory) = fusion_rule(c1, c2) +using NDTensors.LabelledNumbers +using NDTensors.GradedAxes +using BlockArrays: blockedrange, blocklengths -⊕(c1::AbstractCategory, c2::AbstractCategory) = [c1, c2] -⊕(cs::Vector{<:AbstractCategory}, c::AbstractCategory) = [cs; c] -⊕(c::AbstractCategory, cs::Vector{<:AbstractCategory}) = [c; cs] +abstract type AbstractCategory end +# ============ Base interface ================= function Base.show(io::IO, cs::Vector{<:AbstractCategory}) (length(cs) <= 1) && print(io, "[") symbol = "" @@ -32,14 +19,97 @@ function Base.show(io::IO, cs::Vector{<:AbstractCategory}) return nothing end +Base.isless(c1::AbstractCategory, c2::AbstractCategory) = isless(label(c1), label(c2)) + +# ================= Misc ====================== function trivial(category_type::Type{<:AbstractCategory}) return error("`trivial` not defined for type $(category_type).") end istrivial(c::AbstractCategory) = (c == trivial(typeof(c))) -function dual(category_type::Type{<:AbstractCategory}) +# name conflict with LabelledNumber. TBD is that an issue? +label(c::AbstractCategory) = error("method `label` not defined for type $(typeof(c))") + +# TBD dimension in Sectors or in GradedAxes namespace? +function dimension(c::AbstractCategory) + return error("method `dimension` not defined for type $(typeof(c))") +end + +function dimension(g::GradedAxes.GradedUnitRange) + return sum(LabelledNumber.unlabel(b) * dimension(label(b)) for b in blocklengths(g)) +end + +function GradedAxes.dual(category_type::Type{<:AbstractCategory}) return error("`dual` not defined for type $(category_type).") end -Base.isless(c1::AbstractCategory, c2::AbstractCategory) = isless(label(c1), label(c2)) +# ================ fuion rule interface ==================== +function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) + return error("`label_fusion_rule` not defined for type $(category_type).") +end + +# TBD always return GradedUnitRange? +function fusion_rule(c1::C, c2::C) where {C<:AbstractCategory} + out = label_fusion_rule(C, label(c1), label(c2)) + if typeof(out) <: Tuple{Vector,Vector} # TODO replace with Trait + degen, labels = out + # NonAbelianGroup or NonGroupCategory: return GradedUnitRange + return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) + end + return C(out) # AbelianGroup: return Category +end + +function fusion_rule(g::GradedAxes.GradedUnitRange, c::AbstractCategory) + return fusion_rule(c, g) +end + +function ⊗(c1::C, c2::C) where {C<:AbstractCategory} + return fusion_rule(c1, c2) +end + +# ============= fusion rule and gradedunitrange =================== +# TBD define ⊗(c, g2), ⊗(g1, c), ⊗(g1, g2)? +function GradedAxes.tensor_product( + g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, c::C +) where {V,C<:AbstractCategory} + g2 = gradedrange(c) + return GradedAxes.tensor_product(g1, g2) +end + +function GradedAxes.tensor_product( + c::C, g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}} +) where {V,C<:AbstractCategory} + return c ⊗ g +end + +function GradedAxes.tensor_product( + g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, + g2::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, +) where {V,C<:AbstractCategory} + return g1 ⊗ g2 +end + +GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) = c1 ⊗ c2 + +# =============== sum rules ==================== +⊕(c1::C, c2::C) where {C<:AbstractCategory} = GradedAxes.gradedrange([c1 => 1, c2 => 1]) + +function ⊕( + c::C, g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}} +) where {V<:Integer,C<:AbstractCategory} + return GradedAxes.gradedrange([c => 1]) ⊕ g +end + +function ⊕( + g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, c::C +) where {V<:Integer,C<:AbstractCategory} + return g ⊕ GradedAxes.gradedrange([c => 1]) +end + +function ⊕( + g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, + g2::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, +) where {V<:Integer,C<:AbstractCategory} + return GradedAxes.chain(g1, g2) +end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index b7b8a89d49..8bfc5f308b 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -18,13 +18,13 @@ function Fib(s::AbstractString) return error("Unrecognized input \"$s\" to Fib constructor") end -dual(f::Fib) = f +GradedAxes.dual(f::Fib) = f label(f::Fib) = f.l trivial(::Type{Fib}) = Fib(0) -dimension(f::Fib) = istrivial(f) ? 1 : ((1 + √5) / 2) +dimension(f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) # Fusion rules identical to su2₃ label_fusion_rule(::Type{Fib}, l1, l2) = label_fusion_rule(su2{3}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index 1d1089046b..a88939b6f6 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -18,13 +18,13 @@ function Ising(s::AbstractString) return error("Unrecognized input \"$s\" to Ising constructor") end -dual(i::Ising) = i +GradedAxes.dual(i::Ising) = i label(i::Ising) = i.l trivial(::Type{Ising}) = Ising(0) -dimension(i::Ising) = (label(i) == 1//2) ? √2 : 1 +dimension(i::Ising) = (label(i) == 1//2) ? √2 : 1.0 # Fusion rules identical to su2₂ label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 8fc82df329..2820f23676 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -30,7 +30,7 @@ function dimension(s::SU) return Int(d) end -function dual(s::SU) +function GradedAxes.dual(s::SU) l = label(s) nl = ((reverse(cumsum(l[begin:(end - 1)] .- l[(begin + 1):end]))..., 0)) return typeof(s)(nl) @@ -65,17 +65,21 @@ end # # Specializations for the case SU{2} # Where irreps specified by dimension "d" +# TBD remove me? # dimension(s::SU{2}) = 1 + label(s)[1] SU{2}(d::Integer) = SU{2}((d - 1, 0)) -dual(s::SU{2}) = s +GradedAxes.dual(s::SU{2}) = s -function fusion_rule(s1::SU{2}, s2::SU{2}) - d1, d2 = dimension(s1), dimension(s2) - return [SU{2}(d) for d in (abs(d1 - d2) + 1):2:(d1 + d2 - 1)] +function label_fusion_rule(::Type{SU{2}}, s1, s2) + d1 = s1[1] + 1 + d2 = s2[1] + 1 + labels = collect((abs(d1 - d2) + 1):2:(d1 + d2 - 1)) + degen = ones(Int, length(labels)) + return degen, labels end function Base.show(io::IO, s::SU{2}) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl index 2996b63f08..1e63b5bc2a 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl @@ -9,7 +9,7 @@ struct SU2 <: AbstractCategory j::Half{Int} end -dual(s::SU2) = s +GradedAxes.dual(s::SU2) = s label(s::SU2) = s.j @@ -19,4 +19,8 @@ adjoint(::Type{SU2}) = SU2(1) dimension(s::SU2) = twice(label(s)) + 1 -label_fusion_rule(::Type{SU2}, j1, j2) = abs(j1 - j2):(j1 + j2) +function label_fusion_rule(::Type{SU2}, j1, j2) + labels = collect(abs(j1 - j2):(j1 + j2)) + degen = ones(Int, length(labels)) + return degen, labels +end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index 86cf5f5abf..6a11916653 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -12,10 +12,12 @@ dual(s::su2) = s label(s::su2) = s.j -level(s::su2{k}) where {k} = k +level(::su2{k}) where {k} = k trivial(::Type{su2{k}}) where {k} = su2{k}(0) function label_fusion_rule(::Type{su2{k}}, j1, j2) where {k} - return abs(j1 - j2):min(k - j1 - j2, j1 + j2) + labels = collect(abs(j1 - j2):min(k - j1 - j2, j1 + j2)) + degen = ones(Int, length(labels)) + return degen, labels end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 7af8d22b9f..07d8c2f193 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -8,7 +8,7 @@ struct U1 <: AbstractCategory n::Half{Int} end -dual(u::U1) = U1(-u.n) +GradedAxes.dual(u::U1) = U1(-u.n) label(u::U1) = u.n @@ -16,4 +16,4 @@ dimension(::U1) = 1 trivial(::Type{U1}) = U1(0) -label_fusion_rule(::Type{U1}, n1, n2) = (n1 + n2,) +label_fusion_rule(::Type{U1}, n1, n2) = n1 + n2 diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 1348052d97..1fb3f2df00 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -1,11 +1,9 @@ -using HalfIntegers: Half - # # Cyclic group Zₙ # struct Z{N} <: AbstractCategory - m::Half{Int} + m::Int Z{N}(m) where {N} = new{N}(m % N) end @@ -18,6 +16,8 @@ dimension(::Z) = 1 trivial(category_type::Type{<:Z}) = category_type(0) -label_fusion_rule(category_type::Type{<:Z}, n1, n2) = ((n1 + n2) % modulus(category_type),) +function label_fusion_rule(category_type::Type{<:Z}, n1, n2) + return (n1 + n2) % modulus(category_type) +end -dual(c::Z) = typeof(c)(mod(-label(c), modulus(c))) +GradedAxes.dual(c::Z) = typeof(c)(mod(-label(c), modulus(c))) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 12baf35be1..1205af1484 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -1,3 +1,5 @@ +# This files defines a structure for Cartesian product of 2 or more fusion categories +# e.g. U(1)×U(1), U(1)×SU2(2)×SU(3) struct CategoryProduct{Categories} <: AbstractCategory cats::Categories @@ -8,9 +10,18 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -Base.isempty(S::CategoryProduct) = isempty(categories(S)) -Base.length(S::CategoryProduct) = length(categories(S)) -Base.getindex(S::CategoryProduct, args...) = getindex(categories(S), args...) +Base.isempty(s::CategoryProduct) = isempty(categories(s)) +Base.length(s::CategoryProduct) = length(categories(s)) +Base.getindex(s::CategoryProduct, args...) = getindex(categories(s), args...) + +function dimension(s::CategoryProduct) + if length(s) == 0 + return 0 + end + return prod(map(dimension, categories(s))) +end + +GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) function fusion_rule(s1::CategoryProduct, s2::CategoryProduct) return [ diff --git a/NDTensors/src/lib/Sectors/test/test_category.jl b/NDTensors/src/lib/Sectors/test/test_category.jl index e14582afc8..542f16bfe8 100644 --- a/NDTensors/src/lib/Sectors/test/test_category.jl +++ b/NDTensors/src/lib/Sectors/test/test_category.jl @@ -1,20 +1,8 @@ @eval module $(gensym()) using NDTensors.Sectors: - Fib, - Ising, - SU, - SU2, - U1, - Z, - ⊗, - ⊕, - dimension, - dual, - istrivial, - trivial, - fundamental, - adjoint -using Test: @test, @testset + ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, adjoint, dimension, fundamental, istrivial, trivial +using NDTensors.GradedAxes: dual, gradedrange +using Test: @inferred, @test, @testset @testset "Test Category Types" begin @testset "U(1)" begin q1 = U1(1) @@ -24,12 +12,15 @@ using Test: @test, @testset @test dimension(q1) == 1 @test dimension(q2) == 1 - @test q1 ⊗ q1 == [q2] - @test q1 ⊗ q2 == [q3] - @test q2 ⊗ q1 == [q3] + @test q1 ⊗ q1 == U1(2) + @test q1 ⊗ q2 == U1(3) + @test q2 ⊗ q1 == U1(3) + @test (@inferred q1 ⊗ q2) == q3 # no better way, see Julia PR 23426 @test trivial(U1) == U1(0) @test istrivial(U1(0)) + @test q1 ⊕ q2 == gradedrange([q1 => 1, q2 => 1]) + @test q1 ⊕ q1 == gradedrange([q1 => 1, q1 => 1]) @test dual(U1(2)) == U1(-2) @test isless(U1(1), U1(2)) @@ -49,9 +40,10 @@ using Test: @test, @testset @test dual(z0) == z0 @test dual(z1) == z1 - @test z0 ⊗ z0 == [z0] - @test z0 ⊗ z1 == [z1] - @test z1 ⊗ z1 == [z0] + @test z0 ⊗ z0 == z0 + @test z0 ⊗ z1 == z1 + @test z1 ⊗ z1 == z0 + @test (@inferred z0 ⊗ z0) == z0 @test dual(Z{2}(1)) == Z{2}(1) @test isless(Z{2}(0), Z{2}(1)) @@ -81,10 +73,11 @@ using Test: @test, @testset @test dual(j3) == j3 @test dual(j4) == j4 - @test j1 ⊗ j2 == [j2] + @test j1 ⊗ j2 == gradedrange([j2 => 1]) @test j2 ⊗ j2 == j1 ⊕ j3 @test j2 ⊗ j3 == j2 ⊕ j4 @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 + @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) end @testset "SU(2)" begin @@ -110,10 +103,11 @@ using Test: @test, @testset @test dual(j3) == j3 @test dual(j4) == j4 - @test j1 ⊗ j2 == [j2] + @test j1 ⊗ j2 == gradedrange([j2 => 1]) @test j2 ⊗ j2 == j1 ⊕ j3 @test j2 ⊗ j3 == j2 ⊕ j4 @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 + @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) end @testset "SU(N)" begin @@ -157,13 +151,14 @@ using Test: @test, @testset @test dual(ı) == ı @test dual(τ) == τ - @test dimension(ı) == 1 + @test dimension(ı) === 1.0 @test dimension(τ) == ((1 + √5) / 2) - @test ı ⊗ ı == [ı] - @test ı ⊗ τ == [τ] - @test τ ⊗ ı == [τ] + @test ı ⊗ ı == gradedrange([ı => 1]) + @test ı ⊗ τ == gradedrange([τ => 1]) + @test τ ⊗ ı == gradedrange([τ => 1]) @test τ ⊗ τ == ı ⊕ τ + @test (@inferred τ ⊗ τ) == ı ⊕ τ end @testset "Ising" begin @@ -178,15 +173,20 @@ using Test: @test, @testset @test dual(σ) == σ @test dual(ψ) == ψ - @test ı ⊗ ı == [ı] - @test ı ⊗ σ == [σ] - @test σ ⊗ ı == [σ] - @test ı ⊗ ψ == [ψ] - @test ψ ⊗ ı == [ψ] + @test dimension(ı) === 1.0 + @test dimension(σ) == √2 + @test dimension(ψ) === 1.0 + + @test ı ⊗ ı == gradedrange([ı => 1]) + @test ı ⊗ σ == gradedrange([σ => 1]) + @test σ ⊗ ı == gradedrange([σ => 1]) + @test ı ⊗ ψ == gradedrange([ψ => 1]) + @test ψ ⊗ ı == gradedrange([ψ => 1]) @test σ ⊗ σ == ı ⊕ ψ - @test σ ⊗ ψ == [σ] - @test ψ ⊗ σ == [σ] - @test ψ ⊗ ψ == [ı] + @test σ ⊗ ψ == gradedrange([σ => 1]) + @test ψ ⊗ σ == gradedrange([σ => 1]) + @test ψ ⊗ ψ == gradedrange([ı => 1]) + @test (@inferred ψ ⊗ ψ) == gradedrange([ı => 1]) end end end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 58f095c110..dd182520e7 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,5 +1,6 @@ @eval module $(gensym()) -using NDTensors.Sectors: Fib, Ising, SU, SU2, U1, Z, ⊗, ⊕, ×, sector +using NDTensors.Sectors: ×, ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, sector, dimension +using NDTensors.GradedAxes: dual, gradedrange using Test: @test, @testset, @test_throws @testset "Test Named Category Products" begin @testset "Construct from × of NamedTuples" begin @@ -7,10 +8,14 @@ using Test: @test, @testset, @test_throws @test length(s) == 2 @test s[:A] == U1(1) @test s[:B] == SU2(2) + @test dimension(s) == 5 + @test dual(s) == (A=U1(-1),) × (B=SU2(2),) s = s × (C=Ising("ψ"),) @test length(s) == 3 @test s[:C] == Ising("ψ") + @test dimension(s) == 5.0 + @test dual(s) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) end @testset "Construct from Pairs" begin @@ -18,6 +23,8 @@ using Test: @test, @testset, @test_throws @test length(s) == 1 @test s[:A] == U1(2) @test s == sector(; A=U1(2)) + @test dimension(s) == 1 + @test dual(s) == sector("A" => U1(-2)) s = sector("B" => Ising("ψ"), :C => Z{2}(1)) @test length(s) == 2 @@ -31,10 +38,13 @@ using Test: @test, @testset, @test_throws q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test q00 ⊗ q00 == [q00] - @test q01 ⊗ q00 == [q01] - @test q00 ⊗ q01 == [q01] - @test q10 ⊗ q01 == [q11] + @test dimension(q00) == 0 + @test dual(q00) == q00 + + @test q00 ⊗ q00 == q00 + @test q01 ⊗ q00 == q01 + @test q00 ⊗ q01 == q01 + @test q10 ⊗ q01 == q11 end @testset "U(1) ⊗ SU(2) conventional" begin @@ -50,7 +60,7 @@ using Test: @test, @testset, @test_throws q22 = (N=U1(2),) × (J=SU2(2),) @test q1h ⊗ q1h == q20 ⊕ q21 - @test q10 ⊗ q1h == [q2h] + @test q10 ⊗ q1h == gradedrange([q2h => 1]) @test q0h ⊗ q1h == q10 ⊕ q11 @test q11 ⊗ q11 == q20 ⊕ q21 ⊕ q22 end @@ -68,7 +78,7 @@ using Test: @test, @testset, @test_throws q22 = (N=U1(2),) × (J=SU{2}(5),) @test q1h ⊗ q1h == q20 ⊕ q21 - @test q10 ⊗ q1h == [q2h] + @test q10 ⊗ q1h == gradedrange([q2h => 1]) @test q0h ⊗ q1h == q10 ⊕ q11 @test q11 ⊗ q11 == q20 ⊕ q21 ⊕ q22 end @@ -93,11 +103,15 @@ end @testset "Ordered Constructor" begin s = sector(U1(1), U1(2)) @test length(s) == 2 + @test dimension(s) == 1 + @test dual(s) == sector(U1(-1), U1(-2)) @test s[1] == U1(1) @test s[2] == U1(2) s = U1(1) × SU2(1//2) × U1(3) @test length(s) == 3 + @test dimension(s) == 2 + @test dual(s) == U1(-1) × SU2(1//2) × U1(-3) @test s[1] == U1(1) @test s[2] == SU2(1//2) @test s[3] == U1(3) diff --git a/NDTensors/src/lib/Sectors/test/test_graded_axes.jl b/NDTensors/src/lib/Sectors/test/test_graded_axes.jl new file mode 100644 index 0000000000..a37cc51d18 --- /dev/null +++ b/NDTensors/src/lib/Sectors/test/test_graded_axes.jl @@ -0,0 +1,18 @@ +@eval module $(gensym()) +using NDTensors.GradedAxes: dual, fuse_labels +using NDTensors.Sectors: U1, Z +using Test: @test, @testset + +@testset "GradedAxesSectorsExt" begin + @test fuse_labels(U1(1), U1(2)) == U1(3) + @test dual(U1(2)) == U1(-2) + + @test fuse_labels(Z{2}(1), Z{2}(1)) == Z{2}(0) + @test fuse_labels(Z{2}(0), Z{2}(1)) == Z{2}(1) + @test dual(Z{2}(1)) == Z{2}(1) + @test dual(Z{2}(0)) == Z{2}(0) + + g1 = gradedrange([U1(0), U1(1)], [1, 2]) + @test dimension(g1) == 3 +end +end From 11121c96ea0dba727c5d7caaec9d2eecf63543c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 12:57:25 -0400 Subject: [PATCH 002/194] split fusion rules test --- .../src/lib/Sectors/src/abstractcategory.jl | 7 +- .../src/lib/Sectors/test/test_category.jl | 47 +------- .../src/lib/Sectors/test/test_fusion_rules.jl | 105 ++++++++++++++++++ .../src/lib/Sectors/test/test_graded_axes.jl | 18 --- 4 files changed, 112 insertions(+), 65 deletions(-) create mode 100644 NDTensors/src/lib/Sectors/test/test_fusion_rules.jl delete mode 100644 NDTensors/src/lib/Sectors/test/test_graded_axes.jl diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 85a2a4fbe2..a338b20608 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -28,7 +28,7 @@ end istrivial(c::AbstractCategory) = (c == trivial(typeof(c))) -# name conflict with LabelledNumber. TBD is that an issue? +# name conflict with LabelledNumber.label. TBD is that an issue? label(c::AbstractCategory) = error("method `label` not defined for type $(typeof(c))") # TBD dimension in Sectors or in GradedAxes namespace? @@ -37,7 +37,10 @@ function dimension(c::AbstractCategory) end function dimension(g::GradedAxes.GradedUnitRange) - return sum(LabelledNumber.unlabel(b) * dimension(label(b)) for b in blocklengths(g)) + return sum( + LabelledNumbers.unlabel(b) * dimension(LabelledNumbers.label(b)) for + b in blocklengths(g) + ) end function GradedAxes.dual(category_type::Type{<:AbstractCategory}) diff --git a/NDTensors/src/lib/Sectors/test/test_category.jl b/NDTensors/src/lib/Sectors/test/test_category.jl index 542f16bfe8..c2be89fc5c 100644 --- a/NDTensors/src/lib/Sectors/test/test_category.jl +++ b/NDTensors/src/lib/Sectors/test/test_category.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) +using NDTensors.GradedAxes: dual using NDTensors.Sectors: - ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, adjoint, dimension, fundamental, istrivial, trivial -using NDTensors.GradedAxes: dual, gradedrange + Fib, Ising, SU, SU2, U1, Z, adjoint, dimension, fundamental, istrivial, trivial using Test: @inferred, @test, @testset @testset "Test Category Types" begin @testset "U(1)" begin @@ -12,15 +12,8 @@ using Test: @inferred, @test, @testset @test dimension(q1) == 1 @test dimension(q2) == 1 - @test q1 ⊗ q1 == U1(2) - @test q1 ⊗ q2 == U1(3) - @test q2 ⊗ q1 == U1(3) - @test (@inferred q1 ⊗ q2) == q3 # no better way, see Julia PR 23426 - @test trivial(U1) == U1(0) @test istrivial(U1(0)) - @test q1 ⊕ q2 == gradedrange([q1 => 1, q2 => 1]) - @test q1 ⊕ q1 == gradedrange([q1 => 1, q1 => 1]) @test dual(U1(2)) == U1(-2) @test isless(U1(1), U1(2)) @@ -40,11 +33,6 @@ using Test: @inferred, @test, @testset @test dual(z0) == z0 @test dual(z1) == z1 - @test z0 ⊗ z0 == z0 - @test z0 ⊗ z1 == z1 - @test z1 ⊗ z1 == z0 - @test (@inferred z0 ⊗ z0) == z0 - @test dual(Z{2}(1)) == Z{2}(1) @test isless(Z{2}(0), Z{2}(1)) @test !isless(Z{2}(1), Z{2}(0)) @@ -55,7 +43,6 @@ using Test: @inferred, @test, @testset j2 = SU2(1//2) j3 = SU2(1) j4 = SU2(3//2) - j5 = SU2(2) @test trivial(SU2) == SU2(0) @test istrivial(SU2(0)) @@ -72,12 +59,6 @@ using Test: @inferred, @test, @testset @test dual(j2) == j2 @test dual(j3) == j3 @test dual(j4) == j4 - - @test j1 ⊗ j2 == gradedrange([j2 => 1]) - @test j2 ⊗ j2 == j1 ⊕ j3 - @test j2 ⊗ j3 == j2 ⊕ j4 - @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 - @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) end @testset "SU(2)" begin @@ -85,7 +66,6 @@ using Test: @inferred, @test, @testset j2 = SU{2}(2) j3 = SU{2}(3) j4 = SU{2}(4) - j5 = SU{2}(5) @test trivial(SU{2}) == SU{2}(1) @test istrivial(SU{2}(1)) @@ -102,12 +82,6 @@ using Test: @inferred, @test, @testset @test dual(j2) == j2 @test dual(j3) == j3 @test dual(j4) == j4 - - @test j1 ⊗ j2 == gradedrange([j2 => 1]) - @test j2 ⊗ j2 == j1 ⊕ j3 - @test j2 ⊗ j3 == j2 ⊕ j4 - @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 - @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) end @testset "SU(N)" begin @@ -153,12 +127,6 @@ using Test: @inferred, @test, @testset @test dimension(ı) === 1.0 @test dimension(τ) == ((1 + √5) / 2) - - @test ı ⊗ ı == gradedrange([ı => 1]) - @test ı ⊗ τ == gradedrange([τ => 1]) - @test τ ⊗ ı == gradedrange([τ => 1]) - @test τ ⊗ τ == ı ⊕ τ - @test (@inferred τ ⊗ τ) == ı ⊕ τ end @testset "Ising" begin @@ -176,17 +144,6 @@ using Test: @inferred, @test, @testset @test dimension(ı) === 1.0 @test dimension(σ) == √2 @test dimension(ψ) === 1.0 - - @test ı ⊗ ı == gradedrange([ı => 1]) - @test ı ⊗ σ == gradedrange([σ => 1]) - @test σ ⊗ ı == gradedrange([σ => 1]) - @test ı ⊗ ψ == gradedrange([ψ => 1]) - @test ψ ⊗ ı == gradedrange([ψ => 1]) - @test σ ⊗ σ == ı ⊕ ψ - @test σ ⊗ ψ == gradedrange([σ => 1]) - @test ψ ⊗ σ == gradedrange([σ => 1]) - @test ψ ⊗ ψ == gradedrange([ı => 1]) - @test (@inferred ψ ⊗ ψ) == gradedrange([ı => 1]) end end end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl new file mode 100644 index 0000000000..e8486155f9 --- /dev/null +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -0,0 +1,105 @@ +@eval module $(gensym()) +using NDTensors.GradedAxes: fuse_labels, gradedrange +using NDTensors.Sectors: ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, dimension +using Test: @inferred, @test, @testset + +@testset "sum rules" begin + + # test abelian + q1 = U1(1) + q2 = U1(2) + @test q1 ⊕ q2 == gradedrange([q1 => 1, q2 => 1]) + @test q2 ⊕ q1 == gradedrange([q2 => 1, q1 => 1]) # unsorted + @test q1 ⊕ q1 == gradedrange([q1 => 1, q1 => 1]) + @test dimension(gradedrange([q1 => 1, q2 => 2])) == 3 + + # test non-abelian + j2 = SU2(1//2) + j3 = SU2(1) + @test j2 ⊕ j3 == gradedrange([j2 => 1, j3 => 1]) + @test j3 ⊕ j2 == gradedrange([j3 => 1, j2 => 1]) # unsorted + @test j2 ⊕ j2 == gradedrange([j2 => 1, j2 => 1]) + @test dimension(gradedrange([j2 => 2, j3 => 3])) == 13 +end + +@testset "fusion rules" begin + @testset "Z{2} fusion rules" begin + z0 = Z{2}(0) + z1 = Z{2}(1) + + @test z0 ⊗ z0 == z0 + @test z0 ⊗ z1 == z1 + @test z1 ⊗ z1 == z0 + @test (@inferred z0 ⊗ z0) == z0 # no better way, see Julia PR 23426 + + # using GradedAxes interface + @test fuse_labels(z0, z0) == z0 + @test fuse_labels(z0, z1) == z1 + end + @testset "U(1) fusion rules" begin + q1 = U1(1) + q2 = U1(2) + q3 = U1(3) + + @test q1 ⊗ q1 == U1(2) + @test q1 ⊗ q2 == U1(3) + @test q2 ⊗ q1 == U1(3) + @test (@inferred q1 ⊗ q2) == q3 # no better way, see Julia PR 23426 + end + @testset "SU2 fusion rules" begin + j1 = SU2(0) + j2 = SU2(1//2) + j3 = SU2(1) + j4 = SU2(3//2) + j5 = SU2(2) + + @test j1 ⊗ j2 == gradedrange([j2 => 1]) + @test j2 ⊗ j2 == j1 ⊕ j3 + @test j2 ⊗ j3 == j2 ⊕ j4 + @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 + @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) + end + + @testset "SU{2} fusion rules" begin + j1 = SU{2}(1) + j2 = SU{2}(2) + j3 = SU{2}(3) + j4 = SU{2}(4) + j5 = SU{2}(5) + + @test j1 ⊗ j2 == gradedrange([j2 => 1]) + @test j2 ⊗ j2 == j1 ⊕ j3 + @test j2 ⊗ j3 == j2 ⊕ j4 + @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 + @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) + end + + @testset "Fibonacci fusion rules" begin + ı = Fib("1") + τ = Fib("τ") + + @test ı ⊗ ı == gradedrange([ı => 1]) + @test ı ⊗ τ == gradedrange([τ => 1]) + @test τ ⊗ ı == gradedrange([τ => 1]) + @test τ ⊗ τ == ı ⊕ τ + @test (@inferred τ ⊗ τ) == ı ⊕ τ + end + + @testset "Ising fusion rules" begin + ı = Ising("1") + σ = Ising("σ") + ψ = Ising("ψ") + + @test ı ⊗ ı == gradedrange([ı => 1]) + @test ı ⊗ σ == gradedrange([σ => 1]) + @test σ ⊗ ı == gradedrange([σ => 1]) + @test ı ⊗ ψ == gradedrange([ψ => 1]) + @test ψ ⊗ ı == gradedrange([ψ => 1]) + @test σ ⊗ σ == ı ⊕ ψ + @test σ ⊗ ψ == gradedrange([σ => 1]) + @test ψ ⊗ σ == gradedrange([σ => 1]) + @test ψ ⊗ ψ == gradedrange([ı => 1]) + @test (@inferred ψ ⊗ ψ) == gradedrange([ı => 1]) + end +end +end diff --git a/NDTensors/src/lib/Sectors/test/test_graded_axes.jl b/NDTensors/src/lib/Sectors/test/test_graded_axes.jl deleted file mode 100644 index a37cc51d18..0000000000 --- a/NDTensors/src/lib/Sectors/test/test_graded_axes.jl +++ /dev/null @@ -1,18 +0,0 @@ -@eval module $(gensym()) -using NDTensors.GradedAxes: dual, fuse_labels -using NDTensors.Sectors: U1, Z -using Test: @test, @testset - -@testset "GradedAxesSectorsExt" begin - @test fuse_labels(U1(1), U1(2)) == U1(3) - @test dual(U1(2)) == U1(-2) - - @test fuse_labels(Z{2}(1), Z{2}(1)) == Z{2}(0) - @test fuse_labels(Z{2}(0), Z{2}(1)) == Z{2}(1) - @test dual(Z{2}(1)) == Z{2}(1) - @test dual(Z{2}(0)) == Z{2}(0) - - g1 = gradedrange([U1(0), U1(1)], [1, 2]) - @test dimension(g1) == 3 -end -end From 703c2f1b5dd3872d4b77690601723730cedcaf2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 14:48:42 -0400 Subject: [PATCH 003/194] rename chain axis_cat --- NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl | 13 +++++++++++-- NDTensors/src/lib/GradedAxes/test/test_basics.jl | 4 ++-- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index f824b4c9ab..5094497d50 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -80,10 +80,19 @@ function gradedrange(lblocklengths::AbstractVector{<:Pair{<:Any,<:Integer}}) return gradedrange(labelled.(last.(lblocklengths), first.(lblocklengths))) end -function chain(a::GradedUnitRange, b::GradedUnitRange) - return gradedrange(vcat(blocklengths(a), blocklengths(b))) +# Generic function for concatenating axes with blocks. +function blockedunitrange_axis_cat(a::AbstractUnitRange, b::AbstractUnitRange) + return blockedrange(vcat(blocklengths(a), blocklengths(b))) end +axis_cat(a::GradedUnitRange, b::GradedUnitRange) = blockedunitrange_axis_cat(a, b) + +axis_cat(a::BlockedUnitRange, b::BlockedUnitRange) = blockedunitrange_axis_cat(a, b) + +# Assume in general there aren't blocks. +# Probably should check the axes are one-based. +axis_cat(a::AbstractUnitRange, b::AbstractUnitRange) = Base.OneTo(length(a) + length(b)) + function labelled_blocks(a::BlockedUnitRange, labels) return BlockArrays._BlockedUnitRange(a.first, labelled.(a.lasts, labels)) end diff --git a/NDTensors/src/lib/GradedAxes/test/test_basics.jl b/NDTensors/src/lib/GradedAxes/test/test_basics.jl index 5881fc7bbb..3119612962 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_basics.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_basics.jl @@ -8,7 +8,7 @@ using BlockArrays: blocklength, blocklengths, blocks -using NDTensors.GradedAxes: GradedUnitRange, blocklabels, chain, gradedrange +using NDTensors.GradedAxes: GradedUnitRange, blocklabels, axis_cat, gradedrange using NDTensors.LabelledNumbers: LabelledUnitRange, label, labelled, unlabel using Test: @test, @test_broken, @testset @testset "GradedAxes basics" begin @@ -123,7 +123,7 @@ using Test: @test, @test_broken, @testset x = gradedrange(["x" => 2, "y" => 3]) y = gradedrange(["x" => 1, "z" => 2]) - z = chain(x, y) + z = axis_cat(x, y) @test z == gradedrange(["x" => 2, "y" => 3, "x" => 1, "z" => 2]) end end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index a338b20608..445913bc9e 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -114,5 +114,5 @@ function ⊕( g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, g2::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, ) where {V<:Integer,C<:AbstractCategory} - return GradedAxes.chain(g1, g2) + return GradedAxes.axis_cat(g1, g2) end From 9b5eb7e28d37ede1c917e704e4d1847c091473f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 15:25:12 -0400 Subject: [PATCH 004/194] rename dimension quantum_dimension --- .../src/lib/Sectors/src/abstractcategory.jl | 7 ++- .../Sectors/src/category_definitions/fib.jl | 2 +- .../Sectors/src/category_definitions/ising.jl | 2 +- .../Sectors/src/category_definitions/su.jl | 8 +-- .../Sectors/src/category_definitions/su2.jl | 2 +- .../Sectors/src/category_definitions/u1.jl | 2 +- .../Sectors/src/category_definitions/zn.jl | 2 +- .../src/lib/Sectors/test/test_category.jl | 52 +++++++++---------- .../lib/Sectors/test/test_category_product.jl | 14 ++--- .../src/lib/Sectors/test/test_fusion_rules.jl | 6 +-- 10 files changed, 48 insertions(+), 49 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 445913bc9e..4699bf8582 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -31,14 +31,13 @@ istrivial(c::AbstractCategory) = (c == trivial(typeof(c))) # name conflict with LabelledNumber.label. TBD is that an issue? label(c::AbstractCategory) = error("method `label` not defined for type $(typeof(c))") -# TBD dimension in Sectors or in GradedAxes namespace? -function dimension(c::AbstractCategory) +function quantum_dimension(c::AbstractCategory) return error("method `dimension` not defined for type $(typeof(c))") end -function dimension(g::GradedAxes.GradedUnitRange) +function quantum_dimension(g::AbstractUnitRange) return sum( - LabelledNumbers.unlabel(b) * dimension(LabelledNumbers.label(b)) for + LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for b in blocklengths(g) ) end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index 8bfc5f308b..3aceb4310a 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -24,7 +24,7 @@ label(f::Fib) = f.l trivial(::Type{Fib}) = Fib(0) -dimension(f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) +quantum_dimension(f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) # Fusion rules identical to su2₃ label_fusion_rule(::Type{Fib}, l1, l2) = label_fusion_rule(su2{3}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index a88939b6f6..b69018a791 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -24,7 +24,7 @@ label(i::Ising) = i.l trivial(::Type{Ising}) = Ising(0) -dimension(i::Ising) = (label(i) == 1//2) ? √2 : 1.0 +quantum_dimension(i::Ising) = (label(i) == 1//2) ? √2 : 1.0 # Fusion rules identical to su2₂ label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 2820f23676..1705171728 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -20,7 +20,7 @@ fundamental(::Type{SU{N}}) where {N} = SU{N}(ntuple(i -> Int(i == 1), Val(N))) adjoint(::Type{SU{N}}) where {N} = SU{N}((ntuple(i -> Int(i == 1) + Int(i < N), Val(N)))) -function dimension(s::SU) +function quantum_dimension(s::SU) N = groupdim(s) l = label(s) d = 1 @@ -64,11 +64,11 @@ end # # Specializations for the case SU{2} -# Where irreps specified by dimension "d" +# Where irreps specified by quantum_dimension "d" # TBD remove me? # -dimension(s::SU{2}) = 1 + label(s)[1] +quantum_dimension(s::SU{2}) = 1 + label(s)[1] SU{2}(d::Integer) = SU{2}((d - 1, 0)) @@ -83,5 +83,5 @@ function label_fusion_rule(::Type{SU{2}}, s1, s2) end function Base.show(io::IO, s::SU{2}) - return print(io, "SU{2}(", dimension(s), ")") + return print(io, "SU{2}(", quantum_dimension(s), ")") end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl index 1e63b5bc2a..786eea5255 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl @@ -17,7 +17,7 @@ trivial(::Type{SU2}) = SU2(0) fundamental(::Type{SU2}) = SU2(half(1)) adjoint(::Type{SU2}) = SU2(1) -dimension(s::SU2) = twice(label(s)) + 1 +quantum_dimension(s::SU2) = twice(label(s)) + 1 function label_fusion_rule(::Type{SU2}, j1, j2) labels = collect(abs(j1 - j2):(j1 + j2)) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 07d8c2f193..503dfea383 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -12,7 +12,7 @@ GradedAxes.dual(u::U1) = U1(-u.n) label(u::U1) = u.n -dimension(::U1) = 1 +quantum_dimension(::U1) = 1 trivial(::Type{U1}) = U1(0) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 1fb3f2df00..3f9a06d1bd 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -12,7 +12,7 @@ modulus(::Type{Z{N}}) where {N} = N modulus(c::Z) = modulus(typeof(c)) -dimension(::Z) = 1 +quantum_dimension(::Z) = 1 trivial(category_type::Type{<:Z}) = category_type(0) diff --git a/NDTensors/src/lib/Sectors/test/test_category.jl b/NDTensors/src/lib/Sectors/test/test_category.jl index c2be89fc5c..9abc69fa21 100644 --- a/NDTensors/src/lib/Sectors/test/test_category.jl +++ b/NDTensors/src/lib/Sectors/test/test_category.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) using NDTensors.GradedAxes: dual using NDTensors.Sectors: - Fib, Ising, SU, SU2, U1, Z, adjoint, dimension, fundamental, istrivial, trivial + Fib, Ising, SU, SU2, U1, Z, adjoint, quantum_dimension, fundamental, istrivial, trivial using Test: @inferred, @test, @testset @testset "Test Category Types" begin @testset "U(1)" begin @@ -9,8 +9,8 @@ using Test: @inferred, @test, @testset q2 = U1(2) q3 = U1(3) - @test dimension(q1) == 1 - @test dimension(q2) == 1 + @test quantum_dimension(q1) == 1 + @test quantum_dimension(q2) == 1 @test trivial(U1) == U1(0) @test istrivial(U1(0)) @@ -27,8 +27,8 @@ using Test: @inferred, @test, @testset @test trivial(Z{2}) == Z{2}(0) @test istrivial(Z{2}(0)) - @test dimension(z0) == 1 - @test dimension(z1) == 1 + @test quantum_dimension(z0) == 1 + @test quantum_dimension(z1) == 1 @test dual(z0) == z0 @test dual(z1) == z1 @@ -50,10 +50,10 @@ using Test: @inferred, @test, @testset @test fundamental(SU2) == SU2(1//2) @test adjoint(SU2) == SU2(1) - @test dimension(j1) == 1 - @test dimension(j2) == 2 - @test dimension(j3) == 3 - @test dimension(j4) == 4 + @test quantum_dimension(j1) == 1 + @test quantum_dimension(j2) == 2 + @test quantum_dimension(j3) == 3 + @test quantum_dimension(j4) == 4 @test dual(j1) == j1 @test dual(j2) == j2 @@ -73,10 +73,10 @@ using Test: @inferred, @test, @testset @test fundamental(SU{2}) == SU{2}(2) @test adjoint(SU{2}) == SU{2}(3) - @test dimension(j1) == 1 - @test dimension(j2) == 2 - @test dimension(j3) == 3 - @test dimension(j4) == 4 + @test quantum_dimension(j1) == 1 + @test quantum_dimension(j2) == 2 + @test quantum_dimension(j3) == 3 + @test quantum_dimension(j4) == 4 @test dual(j1) == j1 @test dual(j2) == j2 @@ -105,14 +105,14 @@ using Test: @inferred, @test, @testset @test dual(ad3) == ad3 @test dual(ad4) == ad4 - @test dimension(f3) == 3 - @test dimension(f4) == 4 - @test dimension(ad3) == 8 - @test dimension(ad4) == 15 - @test dimension(SU{3}((4, 2, 0))) == 27 - @test dimension(SU{3}((3, 3, 0))) == 10 - @test dimension(SU{3}((3, 0, 0))) == 10 - @test dimension(SU{3}((0, 0, 0))) == 1 + @test quantum_dimension(f3) == 3 + @test quantum_dimension(f4) == 4 + @test quantum_dimension(ad3) == 8 + @test quantum_dimension(ad4) == 15 + @test quantum_dimension(SU{3}((4, 2, 0))) == 27 + @test quantum_dimension(SU{3}((3, 3, 0))) == 10 + @test quantum_dimension(SU{3}((3, 0, 0))) == 10 + @test quantum_dimension(SU{3}((0, 0, 0))) == 1 end @testset "Fibonacci" begin @@ -125,8 +125,8 @@ using Test: @inferred, @test, @testset @test dual(ı) == ı @test dual(τ) == τ - @test dimension(ı) === 1.0 - @test dimension(τ) == ((1 + √5) / 2) + @test quantum_dimension(ı) === 1.0 + @test quantum_dimension(τ) == ((1 + √5) / 2) end @testset "Ising" begin @@ -141,9 +141,9 @@ using Test: @inferred, @test, @testset @test dual(σ) == σ @test dual(ψ) == ψ - @test dimension(ı) === 1.0 - @test dimension(σ) == √2 - @test dimension(ψ) === 1.0 + @test quantum_dimension(ı) === 1.0 + @test quantum_dimension(σ) == √2 + @test quantum_dimension(ψ) === 1.0 end end end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index dd182520e7..02abe90198 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,5 +1,5 @@ @eval module $(gensym()) -using NDTensors.Sectors: ×, ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, sector, dimension +using NDTensors.Sectors: ×, ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, sector, quantum_dimension using NDTensors.GradedAxes: dual, gradedrange using Test: @test, @testset, @test_throws @testset "Test Named Category Products" begin @@ -8,13 +8,13 @@ using Test: @test, @testset, @test_throws @test length(s) == 2 @test s[:A] == U1(1) @test s[:B] == SU2(2) - @test dimension(s) == 5 + @test quantum_dimension(s) == 5 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) s = s × (C=Ising("ψ"),) @test length(s) == 3 @test s[:C] == Ising("ψ") - @test dimension(s) == 5.0 + @test quantum_dimension(s) == 5.0 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) end @@ -23,7 +23,7 @@ using Test: @test, @testset, @test_throws @test length(s) == 1 @test s[:A] == U1(2) @test s == sector(; A=U1(2)) - @test dimension(s) == 1 + @test quantum_dimension(s) == 1 @test dual(s) == sector("A" => U1(-2)) s = sector("B" => Ising("ψ"), :C => Z{2}(1)) @@ -38,7 +38,7 @@ using Test: @test, @testset, @test_throws q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test dimension(q00) == 0 + @test quantum_dimension(q00) == 0 @test dual(q00) == q00 @test q00 ⊗ q00 == q00 @@ -103,14 +103,14 @@ end @testset "Ordered Constructor" begin s = sector(U1(1), U1(2)) @test length(s) == 2 - @test dimension(s) == 1 + @test quantum_dimension(s) == 1 @test dual(s) == sector(U1(-1), U1(-2)) @test s[1] == U1(1) @test s[2] == U1(2) s = U1(1) × SU2(1//2) × U1(3) @test length(s) == 3 - @test dimension(s) == 2 + @test quantum_dimension(s) == 2 @test dual(s) == U1(-1) × SU2(1//2) × U1(-3) @test s[1] == U1(1) @test s[2] == SU2(1//2) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index e8486155f9..eb69faf9f7 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,6 +1,6 @@ @eval module $(gensym()) using NDTensors.GradedAxes: fuse_labels, gradedrange -using NDTensors.Sectors: ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, dimension +using NDTensors.Sectors: ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension using Test: @inferred, @test, @testset @testset "sum rules" begin @@ -11,7 +11,7 @@ using Test: @inferred, @test, @testset @test q1 ⊕ q2 == gradedrange([q1 => 1, q2 => 1]) @test q2 ⊕ q1 == gradedrange([q2 => 1, q1 => 1]) # unsorted @test q1 ⊕ q1 == gradedrange([q1 => 1, q1 => 1]) - @test dimension(gradedrange([q1 => 1, q2 => 2])) == 3 + @test quantum_dimension(gradedrange([q1 => 1, q2 => 2])) == 3 # test non-abelian j2 = SU2(1//2) @@ -19,7 +19,7 @@ using Test: @inferred, @test, @testset @test j2 ⊕ j3 == gradedrange([j2 => 1, j3 => 1]) @test j3 ⊕ j2 == gradedrange([j3 => 1, j2 => 1]) # unsorted @test j2 ⊕ j2 == gradedrange([j2 => 1, j2 => 1]) - @test dimension(gradedrange([j2 => 2, j3 => 3])) == 13 + @test quantum_dimension(gradedrange([j2 => 2, j3 => 3])) == 13 end @testset "fusion rules" begin From d4ef71aad0d3af6c7be29c956bdc801c8747c431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 15:25:59 -0400 Subject: [PATCH 005/194] rename test_category.jl test_simple_categories.jl --- .../Sectors/test/{test_category.jl => test_simple_categories.jl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename NDTensors/src/lib/Sectors/test/{test_category.jl => test_simple_categories.jl} (100%) diff --git a/NDTensors/src/lib/Sectors/test/test_category.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl similarity index 100% rename from NDTensors/src/lib/Sectors/test/test_category.jl rename to NDTensors/src/lib/Sectors/test/test_simple_categories.jl From 11a762aa47512849f5a58063004abf6c18a1942d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 15:36:01 -0400 Subject: [PATCH 006/194] test inferred quantum_dimension --- NDTensors/src/lib/Sectors/src/category_product.jl | 4 ++-- .../src/lib/Sectors/test/test_fusion_rules.jl | 6 ++++-- .../lib/Sectors/test/test_simple_categories.jl | 15 ++++++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 1205af1484..659f4c749a 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -14,11 +14,11 @@ Base.isempty(s::CategoryProduct) = isempty(categories(s)) Base.length(s::CategoryProduct) = length(categories(s)) Base.getindex(s::CategoryProduct, args...) = getindex(categories(s), args...) -function dimension(s::CategoryProduct) +function quantum_dimension(s::CategoryProduct) if length(s) == 0 return 0 end - return prod(map(dimension, categories(s))) + return prod(map(quantum_dimension, categories(s))) end GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index eb69faf9f7..72bc2a829d 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -11,7 +11,7 @@ using Test: @inferred, @test, @testset @test q1 ⊕ q2 == gradedrange([q1 => 1, q2 => 1]) @test q2 ⊕ q1 == gradedrange([q2 => 1, q1 => 1]) # unsorted @test q1 ⊕ q1 == gradedrange([q1 => 1, q1 => 1]) - @test quantum_dimension(gradedrange([q1 => 1, q2 => 2])) == 3 + @test (@inferred quantum_dimension(gradedrange([q1 => 1, q2 => 2]))) == 3 # test non-abelian j2 = SU2(1//2) @@ -19,7 +19,7 @@ using Test: @inferred, @test, @testset @test j2 ⊕ j3 == gradedrange([j2 => 1, j3 => 1]) @test j3 ⊕ j2 == gradedrange([j3 => 1, j2 => 1]) # unsorted @test j2 ⊕ j2 == gradedrange([j2 => 1, j2 => 1]) - @test quantum_dimension(gradedrange([j2 => 2, j3 => 3])) == 13 + @test (@inferred quantum_dimension(gradedrange([j2 => 2, j3 => 3]))) == 13 end @testset "fusion rules" begin @@ -83,6 +83,7 @@ end @test τ ⊗ ı == gradedrange([τ => 1]) @test τ ⊗ τ == ı ⊕ τ @test (@inferred τ ⊗ τ) == ı ⊕ τ + @test (@inferred quantum_dimension(ı ⊕ ı)) == 2.0 end @testset "Ising fusion rules" begin @@ -100,6 +101,7 @@ end @test ψ ⊗ σ == gradedrange([σ => 1]) @test ψ ⊗ ψ == gradedrange([ı => 1]) @test (@inferred ψ ⊗ ψ) == gradedrange([ı => 1]) + @test (@inferred quantum_dimension(ı ⊕ ψ)) == 2.0 end end end diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index 9abc69fa21..e28cd953cd 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -11,6 +11,7 @@ using Test: @inferred, @test, @testset @test quantum_dimension(q1) == 1 @test quantum_dimension(q2) == 1 + @test (@inferred quantum_dimension(q1)) == 1 @test trivial(U1) == U1(0) @test istrivial(U1(0)) @@ -29,6 +30,7 @@ using Test: @inferred, @test, @testset @test quantum_dimension(z0) == 1 @test quantum_dimension(z1) == 1 + @test (@inferred quantum_dimension(z0)) == 1 @test dual(z0) == z0 @test dual(z1) == z1 @@ -54,6 +56,7 @@ using Test: @inferred, @test, @testset @test quantum_dimension(j2) == 2 @test quantum_dimension(j3) == 3 @test quantum_dimension(j4) == 4 + @test (@inferred quantum_dimension(j1)) == 1 @test dual(j1) == j1 @test dual(j2) == j2 @@ -77,6 +80,7 @@ using Test: @inferred, @test, @testset @test quantum_dimension(j2) == 2 @test quantum_dimension(j3) == 3 @test quantum_dimension(j4) == 4 + @test (@inferred quantum_dimension(j1)) == 1 @test dual(j1) == j1 @test dual(j2) == j2 @@ -113,6 +117,7 @@ using Test: @inferred, @test, @testset @test quantum_dimension(SU{3}((3, 3, 0))) == 10 @test quantum_dimension(SU{3}((3, 0, 0))) == 10 @test quantum_dimension(SU{3}((0, 0, 0))) == 1 + @test (@inferred quantum_dimension(f3)) == 3 end @testset "Fibonacci" begin @@ -125,8 +130,8 @@ using Test: @inferred, @test, @testset @test dual(ı) == ı @test dual(τ) == τ - @test quantum_dimension(ı) === 1.0 - @test quantum_dimension(τ) == ((1 + √5) / 2) + @test (@inferred quantum_dimension(ı)) == 1.0 + @test (@inferred quantum_dimension(τ)) == ((1 + √5) / 2) end @testset "Ising" begin @@ -141,9 +146,9 @@ using Test: @inferred, @test, @testset @test dual(σ) == σ @test dual(ψ) == ψ - @test quantum_dimension(ı) === 1.0 - @test quantum_dimension(σ) == √2 - @test quantum_dimension(ψ) === 1.0 + @test (@inferred quantum_dimension(ı)) == 1.0 + @test (@inferred quantum_dimension(σ)) == √2 + @test (@inferred quantum_dimension(ψ)) == 1.0 end end end From 10ff71b6a7b51233795727a8d9d4c9470a4b8d7f Mon Sep 17 00:00:00 2001 From: Miles Date: Tue, 26 Mar 2024 23:49:44 +0900 Subject: [PATCH 007/194] Remove isempty, length, and getindex for CategoryProduct --- .../src/lib/Sectors/src/category_product.jl | 8 +--- .../lib/Sectors/test/test_category_product.jl | 37 ++++++++++--------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 659f4c749a..7cb5105e4e 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -10,12 +10,8 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -Base.isempty(s::CategoryProduct) = isempty(categories(s)) -Base.length(s::CategoryProduct) = length(categories(s)) -Base.getindex(s::CategoryProduct, args...) = getindex(categories(s), args...) - function quantum_dimension(s::CategoryProduct) - if length(s) == 0 + if length(categories(s)) == 0 return 0 end return prod(map(quantum_dimension, categories(s))) @@ -34,7 +30,7 @@ function Base.:(==)(A::CategoryProduct, B::CategoryProduct) end function Base.show(io::IO, s::CategoryProduct) - (length(s) < 2) && print(io, "sector") + (length(categories(s)) < 2) && print(io, "sector") print(io, "(") symbol = "" for p in pairs(categories(s)) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 02abe90198..ed33c46a08 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,35 +1,36 @@ @eval module $(gensym()) -using NDTensors.Sectors: ×, ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, sector, quantum_dimension +using NDTensors.Sectors: + ×, ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension using NDTensors.GradedAxes: dual, gradedrange using Test: @test, @testset, @test_throws @testset "Test Named Category Products" begin @testset "Construct from × of NamedTuples" begin s = (A=U1(1),) × (B=SU2(2),) - @test length(s) == 2 - @test s[:A] == U1(1) - @test s[:B] == SU2(2) + @test length(categories(s)) == 2 + @test categories(s)[:A] == U1(1) + @test categories(s)[:B] == SU2(2) @test quantum_dimension(s) == 5 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) s = s × (C=Ising("ψ"),) - @test length(s) == 3 - @test s[:C] == Ising("ψ") + @test length(categories(s)) == 3 + @test categories(s)[:C] == Ising("ψ") @test quantum_dimension(s) == 5.0 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) end @testset "Construct from Pairs" begin s = sector("A" => U1(2)) - @test length(s) == 1 - @test s[:A] == U1(2) + @test length(categories(s)) == 1 + @test categories(s)[:A] == U1(2) @test s == sector(; A=U1(2)) @test quantum_dimension(s) == 1 @test dual(s) == sector("A" => U1(-2)) s = sector("B" => Ising("ψ"), :C => Z{2}(1)) - @test length(s) == 2 - @test s[:B] == Ising("ψ") - @test s[:C] == Z{2}(1) + @test length(categories(s)) == 2 + @test categories(s)[:B] == Ising("ψ") + @test categories(s)[:C] == Z{2}(1) end @testset "Multiple U(1)'s" begin @@ -102,19 +103,19 @@ end @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin s = sector(U1(1), U1(2)) - @test length(s) == 2 + @test length(categories(s)) == 2 @test quantum_dimension(s) == 1 @test dual(s) == sector(U1(-1), U1(-2)) - @test s[1] == U1(1) - @test s[2] == U1(2) + @test categories(s)[1] == U1(1) + @test categories(s)[2] == U1(2) s = U1(1) × SU2(1//2) × U1(3) - @test length(s) == 3 + @test length(categories(s)) == 3 @test quantum_dimension(s) == 2 @test dual(s) == U1(-1) × SU2(1//2) × U1(-3) - @test s[1] == U1(1) - @test s[2] == SU2(1//2) - @test s[3] == U1(3) + @test categories(s)[1] == U1(1) + @test categories(s)[2] == SU2(1//2) + @test categories(s)[3] == U1(3) end @testset "Fusion of U1 products" begin From c64af630b331101d6ef687c4df8c12b16f8931d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 17:01:03 -0400 Subject: [PATCH 008/194] rm GradedAxesSectorsExt --- .../ext/GradedAxesSectorsExt/Project.toml | 2 -- .../src/GradedAxesSectorsExt.jl | 9 --------- .../ext/GradedAxesSectorsExt/test/Project.toml | 3 --- .../ext/GradedAxesSectorsExt/test/runtests.jl | 15 --------------- 4 files changed, 29 deletions(-) delete mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml delete mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl delete mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/Project.toml delete mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/runtests.jl diff --git a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml deleted file mode 100644 index 9b1d5ccd25..0000000000 --- a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -NDTensors = "23ae76d9-e61a-49c4-8f12-3f1a16adf9cf" diff --git a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl deleted file mode 100644 index aa3056438e..0000000000 --- a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl +++ /dev/null @@ -1,9 +0,0 @@ -module GradedAxesSectorsExt -using ..GradedAxes: GradedAxes -using ...Sectors: Sectors, AbstractCategory, ⊗ # , dual - -GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) = only(c1 ⊗ c2) - -# TODO: Decide the fate of `dual`. -## GradedAxes.dual(c::AbstractCategory) = dual(c) -end diff --git a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/Project.toml b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/Project.toml deleted file mode 100644 index ef491a529c..0000000000 --- a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/Project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[deps] -NDTensors = "23ae76d9-e61a-49c4-8f12-3f1a16adf9cf" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/runtests.jl b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/runtests.jl deleted file mode 100644 index 371e7e57cd..0000000000 --- a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/runtests.jl +++ /dev/null @@ -1,15 +0,0 @@ -@eval module $(gensym()) -using NDTensors.GradedAxes: dual, fuse_labels -using NDTensors.Sectors: U1, Z -using Test: @test, @testset - -@testset "GradedAxesSectorsExt" begin - @test fuse_labels(U1(1), U1(2)) == U1(3) - @test dual(U1(2)) == U1(-2) - - @test fuse_labels(Z{2}(1), Z{2}(1)) == Z{2}(0) - @test fuse_labels(Z{2}(0), Z{2}(1)) == Z{2}(1) - @test dual(Z{2}(1)) == Z{2}(1) - @test dual(Z{2}(0)) == Z{2}(0) -end -end From e6c5c49115c9da5deea069757ca9ed1f1089debc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 25 Mar 2024 19:28:16 -0400 Subject: [PATCH 009/194] fix category name --- NDTensors/src/lib/Sectors/test/test_category_product.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index ed33c46a08..572ebe3c3d 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -48,7 +48,7 @@ using Test: @test, @testset, @test_throws @test q10 ⊗ q01 == q11 end - @testset "U(1) ⊗ SU(2) conventional" begin + @testset "U(1) × SU(2) conventional" begin q0 = sector() q0h = sector(; J=SU2(1//2)) q10 = (N=U1(1),) × (J=SU2(0),) @@ -66,7 +66,7 @@ using Test: @test, @testset, @test_throws @test q11 ⊗ q11 == q20 ⊕ q21 ⊕ q22 end - @testset "U(1) ⊗ SU(2)" begin + @testset "U(1) × SU(2)" begin q0 = sector() q0h = sector(; J=SU{2}(2)) q10 = (N=U1(1),) × (J=SU{2}(1),) From 57738e6b0979393a690a9f6a9fe6baff7cd19e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 26 Mar 2024 13:02:22 -0400 Subject: [PATCH 010/194] =?UTF-8?q?remove=20=E2=8A=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/Sectors/src/abstractcategory.jl | 22 ---------- .../lib/Sectors/test/test_category_product.jl | 27 +++++++----- .../src/lib/Sectors/test/test_fusion_rules.jl | 42 +++++-------------- 3 files changed, 27 insertions(+), 64 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 4699bf8582..40ae230d3a 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -93,25 +93,3 @@ function GradedAxes.tensor_product( end GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) = c1 ⊗ c2 - -# =============== sum rules ==================== -⊕(c1::C, c2::C) where {C<:AbstractCategory} = GradedAxes.gradedrange([c1 => 1, c2 => 1]) - -function ⊕( - c::C, g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}} -) where {V<:Integer,C<:AbstractCategory} - return GradedAxes.gradedrange([c => 1]) ⊕ g -end - -function ⊕( - g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, c::C -) where {V<:Integer,C<:AbstractCategory} - return g ⊕ GradedAxes.gradedrange([c => 1]) -end - -function ⊕( - g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, - g2::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, -) where {V<:Integer,C<:AbstractCategory} - return GradedAxes.axis_cat(g1, g2) -end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 572ebe3c3d..21ba438162 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,6 +1,6 @@ @eval module $(gensym()) using NDTensors.Sectors: - ×, ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension + ×, ⊗, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension using NDTensors.GradedAxes: dual, gradedrange using Test: @test, @testset, @test_throws @testset "Test Named Category Products" begin @@ -60,10 +60,10 @@ using Test: @test, @testset, @test_throws q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test q1h ⊗ q1h == q20 ⊕ q21 + @test q1h ⊗ q1h == gradedrange([q20 => 1, q21 => 1]) @test q10 ⊗ q1h == gradedrange([q2h => 1]) - @test q0h ⊗ q1h == q10 ⊕ q11 - @test q11 ⊗ q11 == q20 ⊕ q21 ⊕ q22 + @test q0h ⊗ q1h == gradedrange([q10 => 1, q11 => 1]) + @test q11 ⊗ q11 == gradedrange([q20 => 1, q21 => 1, q22 => 1]) end @testset "U(1) × SU(2)" begin @@ -78,10 +78,10 @@ using Test: @test, @testset, @test_throws q21 = (N=U1(2),) × (J=SU{2}(3),) q22 = (N=U1(2),) × (J=SU{2}(5),) - @test q1h ⊗ q1h == q20 ⊕ q21 + @test q1h ⊗ q1h == gradedrange([q20 => 1, q21 => 1]) @test q10 ⊗ q1h == gradedrange([q2h => 1]) - @test q0h ⊗ q1h == q10 ⊕ q11 - @test q11 ⊗ q11 == q20 ⊕ q21 ⊕ q22 + @test q0h ⊗ q1h == gradedrange([q10 => 1, q11 => 1]) + @test q11 ⊗ q11 == gradedrange([q20 => 1, q21 => 1, q22 => 1]) end @testset "Comparisons with unspecified labels" begin @@ -134,17 +134,22 @@ end @testset "Fusion of SU2 products" begin phh = SU2(1//2) × SU2(1//2) - @test phh ⊗ phh == - (SU2(0) × SU2(0)) ⊕ (SU2(1) × SU2(0)) ⊕ (SU2(0) × SU2(1)) ⊕ (SU2(1) × SU2(1)) + @test phh ⊗ phh == gradedrange([ + 1 => (SU2(0) × SU2(0)), + 1 => (SU2(1) × SU2(0)), + 1 => (SU2(0) × SU2(1)), + 1 => (SU2(1) × SU2(1)), + ]) end @testset "Fusion of mixed U1 and SU2 products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) - @test p2h ⊗ p1h == (U1(3) × SU2(0)) ⊕ (U1(3) × SU2(1)) + @test p2h ⊗ p1h == gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) - @test p1h1 ⊗ p1h1 == (U1(2) × SU2(0) × Z{2}(0)) ⊕ (U1(2) × SU2(1) × Z{2}(0)) + @test p1h1 ⊗ p1h1 == + gradedrabge([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) end end end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 72bc2a829d..820b4fcdb8 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,27 +1,8 @@ @eval module $(gensym()) using NDTensors.GradedAxes: fuse_labels, gradedrange -using NDTensors.Sectors: ⊕, ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension +using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension using Test: @inferred, @test, @testset -@testset "sum rules" begin - - # test abelian - q1 = U1(1) - q2 = U1(2) - @test q1 ⊕ q2 == gradedrange([q1 => 1, q2 => 1]) - @test q2 ⊕ q1 == gradedrange([q2 => 1, q1 => 1]) # unsorted - @test q1 ⊕ q1 == gradedrange([q1 => 1, q1 => 1]) - @test (@inferred quantum_dimension(gradedrange([q1 => 1, q2 => 2]))) == 3 - - # test non-abelian - j2 = SU2(1//2) - j3 = SU2(1) - @test j2 ⊕ j3 == gradedrange([j2 => 1, j3 => 1]) - @test j3 ⊕ j2 == gradedrange([j3 => 1, j2 => 1]) # unsorted - @test j2 ⊕ j2 == gradedrange([j2 => 1, j2 => 1]) - @test (@inferred quantum_dimension(gradedrange([j2 => 2, j3 => 3]))) == 13 -end - @testset "fusion rules" begin @testset "Z{2} fusion rules" begin z0 = Z{2}(0) @@ -54,9 +35,9 @@ end j5 = SU2(2) @test j1 ⊗ j2 == gradedrange([j2 => 1]) - @test j2 ⊗ j2 == j1 ⊕ j3 - @test j2 ⊗ j3 == j2 ⊕ j4 - @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 + @test j2 ⊗ j2 == gradedrange([j1 => 1, j3 => 1]) + @test j2 ⊗ j3 == gradedrange([j2 => 1, j4 => 1]) + @test j3 ⊗ j3 == gradedrange([j1 => 1, j3 => 1, j5 => 1]) @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) end @@ -68,9 +49,9 @@ end j5 = SU{2}(5) @test j1 ⊗ j2 == gradedrange([j2 => 1]) - @test j2 ⊗ j2 == j1 ⊕ j3 - @test j2 ⊗ j3 == j2 ⊕ j4 - @test j3 ⊗ j3 == j1 ⊕ j3 ⊕ j5 + @test j2 ⊗ j2 == gradedrange([j1 => 1, j3 => 1]) + @test j2 ⊗ j3 == gradedrange([j2 => 1, j4 => 1]) + @test j3 ⊗ j3 == gradedrange([j1 => 1, j3 => 1, j5 => 1]) @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) end @@ -81,9 +62,8 @@ end @test ı ⊗ ı == gradedrange([ı => 1]) @test ı ⊗ τ == gradedrange([τ => 1]) @test τ ⊗ ı == gradedrange([τ => 1]) - @test τ ⊗ τ == ı ⊕ τ - @test (@inferred τ ⊗ τ) == ı ⊕ τ - @test (@inferred quantum_dimension(ı ⊕ ı)) == 2.0 + @test (@inferred τ ⊗ τ) == gradedrange([ı => 1, τ => 1]) + @test (@inferred quantum_dimension(gradedrange([ı => 1, ı => 1]))) == 2.0 end @testset "Ising fusion rules" begin @@ -96,12 +76,12 @@ end @test σ ⊗ ı == gradedrange([σ => 1]) @test ı ⊗ ψ == gradedrange([ψ => 1]) @test ψ ⊗ ı == gradedrange([ψ => 1]) - @test σ ⊗ σ == ı ⊕ ψ + @test σ ⊗ σ == gradedrange([ı => 1, ψ => 1]) @test σ ⊗ ψ == gradedrange([σ => 1]) @test ψ ⊗ σ == gradedrange([σ => 1]) @test ψ ⊗ ψ == gradedrange([ı => 1]) @test (@inferred ψ ⊗ ψ) == gradedrange([ı => 1]) - @test (@inferred quantum_dimension(ı ⊕ ψ)) == 2.0 + @test (@inferred quantum_dimension(σ ⊗ σ)) == 2.0 end end end From af6b20fd54ab8a500b48f29b2c054a8b6f32a6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 26 Mar 2024 20:21:54 -0400 Subject: [PATCH 011/194] tensor_product for GradedUnitRange --- .../src/lib/Sectors/src/abstractcategory.jl | 83 ++++++++++++++----- .../src/lib/Sectors/test/test_fusion_rules.jl | 18 +++- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 40ae230d3a..c0b0e3628c 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -3,7 +3,7 @@ using NDTensors.LabelledNumbers using NDTensors.GradedAxes -using BlockArrays: blockedrange, blocklengths +using BlockArrays: blockedrange, blocklengths, blocks abstract type AbstractCategory end @@ -62,34 +62,79 @@ function fusion_rule(c1::C, c2::C) where {C<:AbstractCategory} return C(out) # AbelianGroup: return Category end -function fusion_rule(g::GradedAxes.GradedUnitRange, c::AbstractCategory) - return fusion_rule(c, g) -end - -function ⊗(c1::C, c2::C) where {C<:AbstractCategory} +function ⊗(c1::AbstractCategory, c2::AbstractCategory) return fusion_rule(c1, c2) end # ============= fusion rule and gradedunitrange =================== # TBD define ⊗(c, g2), ⊗(g1, c), ⊗(g1, g2)? -function GradedAxes.tensor_product( - g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, c::C -) where {V,C<:AbstractCategory} - g2 = gradedrange(c) - return GradedAxes.tensor_product(g1, g2) + +# 1. make GradedAxes.tensor_product return fusion_rule +function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) + return fusion_rule(c1, c2) end -function GradedAxes.tensor_product( - c::C, g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}} -) where {V,C<:AbstractCategory} - return c ⊗ g +function GradedAxes.tensor_product(r::AbstractUnitRange, c::AbstractCategory) + return fusion_rule(r, c) +end + +function GradedAxes.tensor_product(c::AbstractCategory, r::AbstractUnitRange) + return fusion_rule(c, r) end function GradedAxes.tensor_product( - g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, - g2::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, -) where {V,C<:AbstractCategory} - return g1 ⊗ g2 + g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange +) + return fusion_rule(g1, g2) end +# 2. make GradedAxes.fuse_labels return fusion_rule GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) = c1 ⊗ c2 + +# 3. promote Category to GradedAxes +# TODO define promote_rule +function fusion_rule(c::AbstractCategory, r::AbstractUnitRange) + return fusion_rule(GradedAxes.gradedrange([c => 1]), r) +end + +function fusion_rule(r::AbstractUnitRange, c::AbstractCategory) + return fusion_rule(GradedAxes.gradedrange(r, [c => 1])) +end + +# 4. define fusion rule for reducible representations +# TODO deal with dual +function fusion_rule(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange) + blocks3 = empty(blocklengths(g1)) + for b1 in blocklengths(g1) + cat1 = LabelledNumbers.label(b1) + degen1 = LabelledNumbers.unlabel(b1) + for b2 in blocklengths(g2) + cat2 = LabelledNumbers.label(b2) + degen2 = LabelledNumbers.unlabel(b2) + degen3 = degen1 * degen2 + fuse12 = cat1 ⊗ cat2 + + if typeof(fuse12) <: AbstractCategory # TODO replace with Trait or promote + push!(blocks3, LabelledNumbers.LabelledInteger(degen3, fuse12)) + else + g12 = blocklengths(fuse12) + # Int * LabelledInteger -> Int, need to recast explicitly + scaled_g12 = + LabelledNumbers.LabelledInteger.(degen3 .* g12, LabelledNumbers.label.(g12)) + append!(blocks3, scaled_g12) + end + end + end + # sort and fuse blocks carrying the same category label + # there is probably a better way to do this + unsorted_g3 = GradedAxes.gradedrange(blocks3) + perm = GradedAxes.blockmergesortperm(unsorted_g3) + vec3 = empty(blocks3) + for b in blocks(perm) + x = unsorted_g3[b] + n = LabelledNumbers.LabelledInteger(sum(length(x); init=0), LabelledNumbers.label(x[1])) + push!(vec3, n) + end + g3 = GradedAxes.gradedrange(vec3) + return g3 +end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 820b4fcdb8..6b0883e638 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,9 +1,9 @@ @eval module $(gensym()) -using NDTensors.GradedAxes: fuse_labels, gradedrange +using NDTensors.GradedAxes: fuse_labels, gradedrange, tensor_product using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension using Test: @inferred, @test, @testset -@testset "fusion rules" begin +@testset "Simple object fusion rules" begin @testset "Z{2} fusion rules" begin z0 = Z{2}(0) z1 = Z{2}(1) @@ -84,4 +84,18 @@ using Test: @inferred, @test, @testset @test (@inferred quantum_dimension(σ ⊗ σ)) == 2.0 end end +@testset "Reducible object fusion rules" begin + @testset "GradedUnitRange fusion rules" begin + g1 = gradedrange([U1(1) => 1, U1(2) => 2]) + g2 = gradedrange([U1(-1) => 2, U1(0) => 1, U1(1) => 2]) + @test tensor_product(g1, g2) == + gradedrange([U1(0) => 2, U1(1) => 5, U1(2) => 4, U1(3) => 4]) + + g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2, SU2(1) => 1]) + g4 = gradedrange([SU2(1//2) => 1, SU2(1) => 2]) + @test tensor_product(g3, g4) == gradedrange([ + SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2 + ]) + end +end end From b84772f16e78ec02f2731ebcc5c7ceda66b92e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 28 Mar 2024 11:22:38 -0400 Subject: [PATCH 012/194] fix pairing --- NDTensors/src/lib/Sectors/test/test_category_product.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 21ba438162..cdd8384753 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -135,10 +135,10 @@ end @testset "Fusion of SU2 products" begin phh = SU2(1//2) × SU2(1//2) @test phh ⊗ phh == gradedrange([ - 1 => (SU2(0) × SU2(0)), - 1 => (SU2(1) × SU2(0)), - 1 => (SU2(0) × SU2(1)), - 1 => (SU2(1) × SU2(1)), + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, ]) end From 9f86bf5d2a11ce321671b3872b7ca15b630d869e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 28 Mar 2024 11:24:18 -0400 Subject: [PATCH 013/194] typo --- NDTensors/src/lib/Sectors/test/test_category_product.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index cdd8384753..0a6076483d 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -149,7 +149,7 @@ end p1h1 = U1(1) × SU2(1//2) × Z{2}(1) @test p1h1 ⊗ p1h1 == - gradedrabge([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) + gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) end end end From 256f2752b04056d51b908b38a52c21b266a7b6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 28 Mar 2024 18:27:58 -0400 Subject: [PATCH 014/194] do not define methods for unused Vector{<:AbstractCategory} --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index c0b0e3628c..b092ea3924 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -8,17 +8,6 @@ using BlockArrays: blockedrange, blocklengths, blocks abstract type AbstractCategory end # ============ Base interface ================= -function Base.show(io::IO, cs::Vector{<:AbstractCategory}) - (length(cs) <= 1) && print(io, "[") - symbol = "" - for c in cs - print(io, symbol, c) - symbol = " ⊕ " - end - (length(cs) <= 1) && print(io, "]") - return nothing -end - Base.isless(c1::AbstractCategory, c2::AbstractCategory) = isless(label(c1), label(c2)) # ================= Misc ====================== From 7c575e0de7ef647df0a25ed2131e2f9da5c9b788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 28 Mar 2024 19:42:43 -0400 Subject: [PATCH 015/194] test different categories cannot be fused --- NDTensors/src/lib/Sectors/test/test_fusion_rules.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 6b0883e638..26c43b4b32 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) using NDTensors.GradedAxes: fuse_labels, gradedrange, tensor_product using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension -using Test: @inferred, @test, @testset +using Test: @inferred, @test, @testset, @test_throws @testset "Simple object fusion rules" begin @testset "Z{2} fusion rules" begin @@ -96,6 +96,9 @@ end @test tensor_product(g3, g4) == gradedrange([ SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2 ]) + + # test different categories cannot be fused + @test_throws MethodError tensor_product(g1, g4) end end end From f695c5fa279d03611b1cf8c6ea65b1bdd73d5c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 29 Mar 2024 17:43:41 -0400 Subject: [PATCH 016/194] define SymmetryStyle and fusion of CategoryProduct{Tuple} --- NDTensors/src/lib/Sectors/src/Sectors.jl | 1 + .../src/lib/Sectors/src/abstractcategory.jl | 40 ++----- .../Sectors/src/category_definitions/fib.jl | 4 +- .../Sectors/src/category_definitions/ising.jl | 4 +- .../Sectors/src/category_definitions/su.jl | 4 +- .../Sectors/src/category_definitions/su2.jl | 4 +- .../Sectors/src/category_definitions/su2k.jl | 2 + .../Sectors/src/category_definitions/u1.jl | 4 +- .../Sectors/src/category_definitions/zn.jl | 4 +- .../src/lib/Sectors/src/category_product.jl | 100 ++++++++++------ .../src/lib/Sectors/src/symmetry_style.jl | 39 +++++++ .../lib/Sectors/test/test_category_product.jl | 110 +++++++++++++++--- .../src/lib/Sectors/test/test_fusion_rules.jl | 1 + 13 files changed, 230 insertions(+), 87 deletions(-) create mode 100644 NDTensors/src/lib/Sectors/src/symmetry_style.jl diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index fc813e602e..5eaae95b56 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -1,5 +1,6 @@ module Sectors +include("symmetry_style.jl") include("abstractcategory.jl") include("category_definitions/u1.jl") include("category_definitions/zn.jl") diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index b092ea3924..9522baeed4 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -1,10 +1,6 @@ # This file defines the abstract type AbstractCategory # all fusion categories (Z{2}, SU2, Ising...) are subtypes of AbstractCategory -using NDTensors.LabelledNumbers -using NDTensors.GradedAxes -using BlockArrays: blockedrange, blocklengths, blocks - abstract type AbstractCategory end # ============ Base interface ================= @@ -20,22 +16,11 @@ istrivial(c::AbstractCategory) = (c == trivial(typeof(c))) # name conflict with LabelledNumber.label. TBD is that an issue? label(c::AbstractCategory) = error("method `label` not defined for type $(typeof(c))") -function quantum_dimension(c::AbstractCategory) - return error("method `dimension` not defined for type $(typeof(c))") -end - -function quantum_dimension(g::AbstractUnitRange) - return sum( - LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for - b in blocklengths(g) - ) -end - function GradedAxes.dual(category_type::Type{<:AbstractCategory}) return error("`dual` not defined for type $(category_type).") end -# ================ fuion rule interface ==================== +# ================ fusion rule interface ==================== function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) return error("`label_fusion_rule` not defined for type $(category_type).") end @@ -43,12 +28,12 @@ end # TBD always return GradedUnitRange? function fusion_rule(c1::C, c2::C) where {C<:AbstractCategory} out = label_fusion_rule(C, label(c1), label(c2)) - if typeof(out) <: Tuple{Vector,Vector} # TODO replace with Trait - degen, labels = out - # NonAbelianGroup or NonGroupCategory: return GradedUnitRange - return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) + if SymmetryStyle(c1) == AbelianGroup() + return C(out) # AbelianGroup: return Category end - return C(out) # AbelianGroup: return Category + degen, labels = out + # NonAbelianGroup or NonGroupCategory: return GradedUnitRange + return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) end function ⊗(c1::AbstractCategory, c2::AbstractCategory) @@ -93,20 +78,19 @@ end # 4. define fusion rule for reducible representations # TODO deal with dual function fusion_rule(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange) - blocks3 = empty(blocklengths(g1)) - for b1 in blocklengths(g1) + blocks3 = empty(BlockArrays.blocklengths(g1)) + for b1 in BlockArrays.blocklengths(g1) cat1 = LabelledNumbers.label(b1) degen1 = LabelledNumbers.unlabel(b1) - for b2 in blocklengths(g2) + for b2 in BlockArrays.blocklengths(g2) cat2 = LabelledNumbers.label(b2) degen2 = LabelledNumbers.unlabel(b2) degen3 = degen1 * degen2 fuse12 = cat1 ⊗ cat2 - - if typeof(fuse12) <: AbstractCategory # TODO replace with Trait or promote + if typeof(fuse12) <: AbstractCategory # TBD define SymmetryStyle(GradedUnitRange)? promote? push!(blocks3, LabelledNumbers.LabelledInteger(degen3, fuse12)) else - g12 = blocklengths(fuse12) + g12 = BlockArrays.blocklengths(fuse12) # Int * LabelledInteger -> Int, need to recast explicitly scaled_g12 = LabelledNumbers.LabelledInteger.(degen3 .* g12, LabelledNumbers.label.(g12)) @@ -119,7 +103,7 @@ function fusion_rule(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRa unsorted_g3 = GradedAxes.gradedrange(blocks3) perm = GradedAxes.blockmergesortperm(unsorted_g3) vec3 = empty(blocks3) - for b in blocks(perm) + for b in BlockArrays.blocks(perm) x = unsorted_g3[b] n = LabelledNumbers.LabelledInteger(sum(length(x); init=0), LabelledNumbers.label(x[1])) push!(vec3, n) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index 3aceb4310a..5993f3503c 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -18,13 +18,15 @@ function Fib(s::AbstractString) return error("Unrecognized input \"$s\" to Fib constructor") end +SymmetryStyle(::Fib) = NonGroupCategory() + GradedAxes.dual(f::Fib) = f label(f::Fib) = f.l trivial(::Type{Fib}) = Fib(0) -quantum_dimension(f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) +quantum_dimension(::NonGroupCategory, f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) # Fusion rules identical to su2₃ label_fusion_rule(::Type{Fib}, l1, l2) = label_fusion_rule(su2{3}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index b69018a791..a69705f366 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -18,13 +18,15 @@ function Ising(s::AbstractString) return error("Unrecognized input \"$s\" to Ising constructor") end +SymmetryStyle(::Ising) = NonGroupCategory() + GradedAxes.dual(i::Ising) = i label(i::Ising) = i.l trivial(::Type{Ising}) = Ising(0) -quantum_dimension(i::Ising) = (label(i) == 1//2) ? √2 : 1.0 +quantum_dimension(::NonGroupCategory, i::Ising) = (label(i) == 1//2) ? √2 : 1.0 # Fusion rules identical to su2₂ label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 1705171728..d51d71fae4 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -10,6 +10,8 @@ struct SU{N} <: AbstractCategory l::NTuple{N,Int} end +SymmetryStyle(::SU) = NonAbelianGroup() + label(s::SU) = s.l groupdim(::SU{N}) where {N} = N @@ -20,7 +22,7 @@ fundamental(::Type{SU{N}}) where {N} = SU{N}(ntuple(i -> Int(i == 1), Val(N))) adjoint(::Type{SU{N}}) where {N} = SU{N}((ntuple(i -> Int(i == 1) + Int(i < N), Val(N)))) -function quantum_dimension(s::SU) +function quantum_dimension(::NonAbelianGroup, s::SU) N = groupdim(s) l = label(s) d = 1 diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl index 786eea5255..6f98756a78 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl @@ -9,6 +9,8 @@ struct SU2 <: AbstractCategory j::Half{Int} end +SymmetryStyle(::SU2) = NonAbelianGroup() + GradedAxes.dual(s::SU2) = s label(s::SU2) = s.j @@ -17,7 +19,7 @@ trivial(::Type{SU2}) = SU2(0) fundamental(::Type{SU2}) = SU2(half(1)) adjoint(::Type{SU2}) = SU2(1) -quantum_dimension(s::SU2) = twice(label(s)) + 1 +quantum_dimension(::NonAbelianGroup, s::SU2) = twice(label(s)) + 1 function label_fusion_rule(::Type{SU2}, j1, j2) labels = collect(abs(j1 - j2):(j1 + j2)) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index 6a11916653..f6eaeae85c 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -8,6 +8,8 @@ struct su2{k} <: AbstractCategory j::Half{Int} end +SymmetryStyle(::su2) = NonGroupCategory() + dual(s::su2) = s label(s::su2) = s.j diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 503dfea383..36027c757b 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -8,12 +8,12 @@ struct U1 <: AbstractCategory n::Half{Int} end +SymmetryStyle(::U1) = AbelianGroup() + GradedAxes.dual(u::U1) = U1(-u.n) label(u::U1) = u.n -quantum_dimension(::U1) = 1 - trivial(::Type{U1}) = U1(0) label_fusion_rule(::Type{U1}, n1, n2) = n1 + n2 diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 3f9a06d1bd..94d7e928c4 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -7,13 +7,13 @@ struct Z{N} <: AbstractCategory Z{N}(m) where {N} = new{N}(m % N) end +SymmetryStyle(::Z) = AbelianGroup() + label(c::Z) = c.m modulus(::Type{Z{N}}) where {N} = N modulus(c::Z) = modulus(typeof(c)) -quantum_dimension(::Z) = 1 - trivial(category_type::Type{<:Z}) = category_type(0) function label_fusion_rule(category_type::Type{<:Z}, n1, n2) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 7cb5105e4e..df0ee3f45a 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -1,6 +1,7 @@ # This files defines a structure for Cartesian product of 2 or more fusion categories # e.g. U(1)×U(1), U(1)×SU2(2)×SU(3) +# ============== Definition and getters ================= struct CategoryProduct{Categories} <: AbstractCategory cats::Categories global _CategoryProduct(l) = new{typeof(l)}(l) @@ -10,21 +11,35 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -function quantum_dimension(s::CategoryProduct) - if length(categories(s)) == 0 - return 0 +# ============== SymmetryStyle ============================== +combine_styles(::AbelianGroup, ::AbelianGroup) = AbelianGroup() +combine_styles(::AbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() +combine_styles(::AbelianGroup, ::NonGroupCategory) = NonGroupCategory() +combine_styles(::NonAbelianGroup, ::AbelianGroup) = NonAbelianGroup() +combine_styles(::NonAbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() +combine_styles(::NonAbelianGroup, ::NonGroupCategory) = NonGroupCategory() +combine_styles(::NonGroupCategory, ::SymmetryStyle) = NonGroupCategory() + +function SymmetryStyle(c::CategoryProduct) + return if length(categories(c)) == 0 + EmptyCategory() + else + reduce(combine_styles, map(SymmetryStyle, (categories(c)))) end - return prod(map(quantum_dimension, categories(s))) end -GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) +# ============== Sector interface ================= +function quantum_dimension(::NonAbelianGroup, s::CategoryProduct) + return prod(map(quantum_dimension, categories(s))) +end -function fusion_rule(s1::CategoryProduct, s2::CategoryProduct) - return [ - CategoryProduct(l) for l in categories_fusion_rule(categories(s1), categories(s2)) - ] +function quantum_dimension(::NonGroupCategory, s::CategoryProduct) + return prod(map(quantum_dimension, categories(s))) end +GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) + +# ============== Base interface ================= function Base.:(==)(A::CategoryProduct, B::CategoryProduct) return categories_equal(categories(A), categories(B)) end @@ -45,11 +60,14 @@ category_show(io::IO, k, v) = print(io, v) category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") +# ============== Cartesian product ================= ×(c1::AbstractCategory, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) function ×(p1::CategoryProduct, p2::CategoryProduct) return CategoryProduct(categories_product(categories(p1), categories(p2))) end +# currently (A=U1(1),) × (A=U1(2),) = sector((A=U1(1),)) +# this is misleading. TBD throw in this case? categories_product(l1::NamedTuple, l2::NamedTuple) = union_keys(l1, l2) categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) @@ -58,10 +76,24 @@ categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) ×(c1::NamedTuple, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) ×(c1::AbstractCategory, c2::NamedTuple) = ×(CategoryProduct(c1), CategoryProduct(c2)) -# -# Dictionary-like implementation -# +function ×(l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger) + c3 = LabelledNumbers.label(l1) × LabelledNumbers.label(l2) + m3 = LabelledNumbers.unlabel(l1) * LabelledNumbers.unlabel(l2) + return LabelledNumbers.LabelledInteger(m3, c3) +end + +×(g::AbstractUnitRange, c::AbstractCategory) = ×(g, GradedAxes.gradedrange([c => 1])) +×(c::AbstractCategory, g::AbstractUnitRange) = ×(GradedAxes.gradedrange([c => 1]), g) + +function ×(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange) + # keep F convention in loop order + v = [ + l1 × l2 for l2 in BlockArrays.blocklengths(g2) for l1 in BlockArrays.blocklengths(g1) + ] + return GradedAxes.gradedrange(v) +end +# ============== Dictionary-like implementation ================= function CategoryProduct(nt::NamedTuple) categories = sort_keys(nt) return _CategoryProduct(categories) @@ -75,6 +107,13 @@ function CategoryProduct(pairs::Pair...) return CategoryProduct(NamedTuple{keys}(vals)) end +function categories_equal(A::NamedTuple, B::NamedTuple) + common_categories = zip(pairs(intersect_keys(A, B)), pairs(intersect_keys(B, A))) + common_categories_match = all(nl -> (nl[1] == nl[2]), common_categories) + unique_categories_zero = all(l -> istrivial(l), symdiff_keys(A, B)) + return common_categories_match && unique_categories_zero +end + function categories_fusion_rule(A::NamedTuple, B::NamedTuple) qs = [A] for (la, lb) in zip(pairs(intersect_keys(A, B)), pairs(intersect_keys(B, A))) @@ -87,32 +126,27 @@ function categories_fusion_rule(A::NamedTuple, B::NamedTuple) return qs end -function categories_equal(A::NamedTuple, B::NamedTuple) - common_categories = zip(pairs(intersect_keys(A, B)), pairs(intersect_keys(B, A))) - common_categories_match = all(nl -> (nl[1] == nl[2]), common_categories) - unique_categories_zero = all(l -> istrivial(l), symdiff_keys(A, B)) - return common_categories_match && unique_categories_zero -end - -# -# Ordered implementation -# +# allow ⊗ for different types in NamedTuple +function fusion_rule( + s1::CategoryProduct{Cat1}, s2::CategoryProduct{Cat2} +) where {Cat1<:NamedTuple,Cat2<:NamedTuple} end +# ============== Ordered implementation ================= CategoryProduct(t::Tuple) = _CategoryProduct(t) CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) -function categories_fusion_rule(o1::Tuple, o2::Tuple) - N = length(o1) - length(o2) == N || - throw(DimensionMismatch("Ordered CategoryProduct must have same size in ⊗")) - os = [o1] - replace(o, n, val) = ntuple(m -> (m == n) ? val : o[m], length(o)) - for n in 1:N - os = [replace(o, n, f) for f in ⊗(o1[n], o2[n]) for o in os] +sector(args...; kws...) = CategoryProduct(args...; kws...) + +# for ordered tuple, impose same type in fusion +function fusion_rule(s1::CategoryProduct{Cat}, s2::CategoryProduct{Cat}) where {Cat<:Tuple} + if SymmetryStyle(s1) == EmptyCategory() # compile-time; simpler than specifying init + return s1 end - return os + cat1 = categories(s1) + cat2 = categories(s2) + prod12 = ntuple(i -> cat1[i] ⊗ cat2[i], length(cat1)) + g = reduce(×, prod12) + return g end - -sector(args...; kws...) = CategoryProduct(args...; kws...) diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl new file mode 100644 index 0000000000..bd5b71e950 --- /dev/null +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -0,0 +1,39 @@ +using BlockArrays + +using NDTensors.LabelledNumbers +using NDTensors.GradedAxes + +abstract type SymmetryStyle end + +struct AbelianGroup <: SymmetryStyle end +struct NonAbelianGroup <: SymmetryStyle end +struct NonGroupCategory <: SymmetryStyle end +struct EmptyCategory <: SymmetryStyle end + +# crash for empty g. Currently impossible to construct. +SymmetryStyle(g::AbstractUnitRange) = SymmetryStyle(LabelledNumbers.label(first(g))) + +quantum_dimension(c::Any) = quantum_dimension(SymmetryStyle(c), c) + +function quantum_dimension(::SymmetryStyle, ::Any) + return error("method `dimension` not defined for type $(typeof(c))") +end + +quantum_dimension(::AbelianGroup, ::Any) = 1 +quantum_dimension(::EmptyCategory, ::Any) = 0 + +function quantum_dimension(g::AbstractUnitRange) + if SymmetryStyle(g) == AbelianGroup() + return length(g) + elseif SymmetryStyle(g) == NonAbelianGroup() + return sum( + LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for + b in BlockArrays.blocklengths(g), init in 0 + ) + else + return sum( + LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for + b in BlockArrays.blocklengths(g), init in 0.0 + ) + end +end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 0a6076483d..671ed163ca 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,21 +1,22 @@ @eval module $(gensym()) using NDTensors.Sectors: - ×, ⊗, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension + ×, ⊗, CategoryProduct, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension using NDTensors.GradedAxes: dual, gradedrange -using Test: @test, @testset, @test_throws +using Test: @inferred, @test, @testset, @test_broken, @test_throws + @testset "Test Named Category Products" begin @testset "Construct from × of NamedTuples" begin s = (A=U1(1),) × (B=SU2(2),) @test length(categories(s)) == 2 @test categories(s)[:A] == U1(1) @test categories(s)[:B] == SU2(2) - @test quantum_dimension(s) == 5 + @test (@inferred quantum_dimension(s)) == 5 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 @test categories(s)[:C] == Ising("ψ") - @test quantum_dimension(s) == 5.0 + @test (@inferred quantum_dimension(s)) == 5.0 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) end @@ -24,13 +25,14 @@ using Test: @test, @testset, @test_throws @test length(categories(s)) == 1 @test categories(s)[:A] == U1(2) @test s == sector(; A=U1(2)) - @test quantum_dimension(s) == 1 + @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == sector("A" => U1(-2)) s = sector("B" => Ising("ψ"), :C => Z{2}(1)) @test length(categories(s)) == 2 @test categories(s)[:B] == Ising("ψ") @test categories(s)[:C] == Z{2}(1) + @test (@inferred quantum_dimension(s)) == 1.0 end @testset "Multiple U(1)'s" begin @@ -39,7 +41,8 @@ using Test: @test, @testset, @test_throws q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test quantum_dimension(q00) == 0 + @test (@inferred quantum_dimension(q00)) == 0 + @test (@inferred quantum_dimension(q11)) == 1 @test dual(q00) == q00 @test q00 ⊗ q00 == q00 @@ -104,35 +107,57 @@ end @testset "Ordered Constructor" begin s = sector(U1(1), U1(2)) @test length(categories(s)) == 2 - @test quantum_dimension(s) == 1 + @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == sector(U1(-1), U1(-2)) @test categories(s)[1] == U1(1) @test categories(s)[2] == U1(2) s = U1(1) × SU2(1//2) × U1(3) @test length(categories(s)) == 3 - @test quantum_dimension(s) == 2 + @test (@inferred quantum_dimension(s)) == 2 @test dual(s) == U1(-1) × SU2(1//2) × U1(-3) @test categories(s)[1] == U1(1) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) - end - @testset "Fusion of U1 products" begin - p11 = U1(1) × U1(1) - @test p11 ⊗ p11 == [U1(2) × U1(2)] + s = U1(3) × SU2(1//2) × Fib("τ") + @test length(categories(s)) == 3 + @test (@inferred quantum_dimension(s)) == 1.0 + √5 + @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") + @test categories(s)[1] == U1(3) + @test categories(s)[2] == SU2(1//2) + @test categories(s)[3] == Fib("τ") + end + @testset "Enforce same spaces in fusion" begin + p12 = U1(1) × U1(2) p123 = U1(1) × U1(2) × U1(3) - @test p123 ⊗ p123 == [U1(2) × U1(4) × U1(6)] + @test_throws MethodError p12 ⊗ p123 + + z12 = Z{2}(1) × Z{2}(1) + @test_throws MethodError p12 ⊗ z12 end - @testset "Enforce same number of spaces" begin - p12 = U1(1) × U1(2) + @testset "Empty category" begin + s = CategoryProduct(()) + @test s × s == s + @test s ⊗ s == s + @test (@inferred quantum_dimension(s)) == 0 + end + + @testset "Fusion of Abelian products" begin + p11 = U1(1) × U1(1) + @test p11 ⊗ p11 == U1(2) × U1(2) + p123 = U1(1) × U1(2) × U1(3) - @test_throws DimensionMismatch p12 ⊗ p123 + @test p123 ⊗ p123 == U1(2) × U1(4) × U1(6) + + s1 = sector(U1(1), Z{2}(1)) + s2 = sector(U1(0), Z{2}(0)) + @test s1 ⊗ s2 == U1(1) × Z{2}(1) end - @testset "Fusion of SU2 products" begin + @testset "Fusion of NonAbelian products" begin phh = SU2(1//2) × SU2(1//2) @test phh ⊗ phh == gradedrange([ (SU2(0) × SU2(0)) => 1, @@ -140,9 +165,31 @@ end (SU2(0) × SU2(1)) => 1, (SU2(1) × SU2(1)) => 1, ]) + @test (@inferred quantum_dimension(phh ⊗ phh)) == 16 + end + + @testset "Fusion of NonGroupCategory products" begin + ı = Fib("1") + τ = Fib("τ") + s = ı × ı + g = gradedrange([(ı × ı) => 1]) + @test s ⊗ s == g + @test_broken (@inferred quantum_dimension(g)) == 1.0 # I don't understand + + s = τ × τ + g = gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]) + @test s ⊗ s == g + @test_broken (@inferred quantum_dimension(g)) == 2.0 + 3quantum_dimension(τ) # I don't understand + + σ = Ising("σ") + ψ = Ising("ψ") + s = τ × σ + g = gradedrange([(ı × Ising(1)) => 1, (τ × Ising(1)) => 1, (ı × ψ) => 1, (τ × ψ) => 1]) + @test s ⊗ s == g + @test (@inferred quantum_dimension(g)) == 2.0 + 2quantum_dimension(τ) # ??? end - @testset "Fusion of mixed U1 and SU2 products" begin + @testset "Fusion of mixed Abelian and NonAbelian products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) @test p2h ⊗ p1h == gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) @@ -150,6 +197,33 @@ end p1h1 = U1(1) × SU2(1//2) × Z{2}(1) @test p1h1 ⊗ p1h1 == gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) + @test (@inferred quantum_dimension(p1h1 ⊗ p1h1)) == 4 + end + + @testset "Fusion of fully mixed products" begin + s = U1(1) × SU2(1//2) × Ising("σ") + @test s ⊗ s == gradedrange([ + (U1(2) × SU2(0) × Ising(1)) => 1, + (U1(2) × SU2(1) × Ising(1)) => 1, + (U1(2) × SU2(0) × Ising("ψ")) => 1, + (U1(2) × SU2(1) × Ising("ψ")) => 1, + ]) + @test (@inferred quantum_dimension(s ⊗ s)) == 8 + + ı = Fib("1") + τ = Fib("τ") + s = U1(1) × SU2(1//2) × τ + @test s ⊗ s == gradedrange([ + (U1(2) × SU2(0) × ı) => 1, + (U1(2) × SU2(1) × ı) => 1, + (U1(2) × SU2(0) × τ) => 1, + (U1(2) × SU2(1) × τ) => 1, + ]) + @test (@inferred quantum_dimension(s ⊗ s)) == 4.0 + 4.0quantum_dimension(τ) + + s = U1(1) × ı × τ + @test s ⊗ s == gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) + @test_broken (@inferred quantum_dimension(s ⊗ s)) == 1.0 + quantum_dimension(τ) end end end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 26c43b4b32..8aa09d66d8 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -39,6 +39,7 @@ using Test: @inferred, @test, @testset, @test_throws @test j2 ⊗ j3 == gradedrange([j2 => 1, j4 => 1]) @test j3 ⊗ j3 == gradedrange([j1 => 1, j3 => 1, j5 => 1]) @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) + @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 end @testset "SU{2} fusion rules" begin From c7b8b50d1c84969cc19108b1255779c300e9a256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 29 Mar 2024 17:48:28 -0400 Subject: [PATCH 017/194] use SymmetryStyle --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 9522baeed4..9c446f22aa 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -87,7 +87,7 @@ function fusion_rule(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRa degen2 = LabelledNumbers.unlabel(b2) degen3 = degen1 * degen2 fuse12 = cat1 ⊗ cat2 - if typeof(fuse12) <: AbstractCategory # TBD define SymmetryStyle(GradedUnitRange)? promote? + if SymmetryStyle(fuse12) == AbelianGroup() push!(blocks3, LabelledNumbers.LabelledInteger(degen3, fuse12)) else g12 = BlockArrays.blocklengths(fuse12) From 2508c618aa87af9e9dcd12de5bb3579f38679229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 29 Mar 2024 20:40:11 -0400 Subject: [PATCH 018/194] fusion rules for NamedTuple --- .../src/lib/Sectors/src/category_product.jl | 36 +++++++++++++------ .../lib/Sectors/test/test_category_product.jl | 25 +++++++++---- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index df0ee3f45a..23f85e22c0 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -70,6 +70,10 @@ end # this is misleading. TBD throw in this case? categories_product(l1::NamedTuple, l2::NamedTuple) = union_keys(l1, l2) +# edge cases +categories_product(l1::NamedTuple, l2::Tuple{}) = l1 +categories_product(l1::Tuple{}, l2::NamedTuple) = l2 + categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) ×(nt1::NamedTuple, nt2::NamedTuple) = ×(CategoryProduct(nt1), CategoryProduct(nt2)) @@ -114,22 +118,34 @@ function categories_equal(A::NamedTuple, B::NamedTuple) return common_categories_match && unique_categories_zero end -function categories_fusion_rule(A::NamedTuple, B::NamedTuple) - qs = [A] - for (la, lb) in zip(pairs(intersect_keys(A, B)), pairs(intersect_keys(B, A))) - @assert la[1] == lb[1] - fused_vals = ⊗(la[2], lb[2]) - qs = [union_keys((; la[1] => v), q) for v in fused_vals for q in qs] +function fusion_rule(k::Symbol, c1::C, c2::C) where {C<:AbstractCategory} + fused = c1 ⊗ c2 + if SymmetryStyle(c1) == AbelianGroup() + return sector(k => fused) end - # Include sectors of B not in A - qs = [union_keys(q, B) for q in qs] - return qs + return GradedAxes.gradedrange([ + sector(k => LabelledNumbers.label(b)) => LabelledNumbers.unlabel(b) for + b in blocklengths(fused) + ]) end # allow ⊗ for different types in NamedTuple function fusion_rule( s1::CategoryProduct{Cat1}, s2::CategoryProduct{Cat2} -) where {Cat1<:NamedTuple,Cat2<:NamedTuple} end +) where {Cat1<:NamedTuple,Cat2<:NamedTuple} + if SymmetryStyle(s1) == EmptyCategory() # still works when s2 is also empty + return s2 + end + A = categories(s1) + B = categories(s2) + diff_cat = A[setdiff(keys(A), keys(B))] × B[setdiff(keys(B), keys(A))] + shared_keys = intersect(keys(A), keys(B)) + shared_cat = ntuple( + i -> fusion_rule(shared_keys[i], A[shared_keys[i]], B[shared_keys[i]]), + length(shared_keys), + ) + return diff_cat × reduce(×, shared_cat; init=sector()) +end # ============== Ordered implementation ================= CategoryProduct(t::Tuple) = _CategoryProduct(t) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 671ed163ca..aeb576656f 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -6,6 +6,13 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @testset "Test Named Category Products" begin @testset "Construct from × of NamedTuples" begin + s = (A=U1(1),) × (B=Z{2}(0),) + @test length(categories(s)) == 2 + @test categories(s)[:A] == U1(1) + @test categories(s)[:B] == Z{2}(0) + @test (@inferred quantum_dimension(s)) == 1 + @test dual(s) == (A=U1(-1),) × (B=Z{2}(0),) + s = (A=U1(1),) × (B=SU2(2),) @test length(categories(s)) == 2 @test categories(s)[:A] == U1(1) @@ -35,20 +42,25 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred quantum_dimension(s)) == 1.0 end - @testset "Multiple U(1)'s" begin + @testset "Empty category" begin + s = sector() + @test dual(s) == s + @test s × s == s + @test s ⊗ s == s + @test (@inferred quantum_dimension(s)) == 0 + end + + @testset "Abelian fusion rules" begin q00 = sector() q10 = sector(; A=U1(1)) q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test (@inferred quantum_dimension(q00)) == 0 - @test (@inferred quantum_dimension(q11)) == 1 - @test dual(q00) == q00 - - @test q00 ⊗ q00 == q00 + @test q10 ⊗ q10 == sector(; A=U1(2)) @test q01 ⊗ q00 == q01 @test q00 ⊗ q01 == q01 @test q10 ⊗ q01 == q11 + @test q11 ⊗ q11 == sector(; A=U1(2), B=U1(2)) end @testset "U(1) × SU(2) conventional" begin @@ -140,6 +152,7 @@ end @testset "Empty category" begin s = CategoryProduct(()) + @test dual(s) == s @test s × s == s @test s ⊗ s == s @test (@inferred quantum_dimension(s)) == 0 From e234a7512973d0134f4795cccde799a66f9cc1c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 1 Apr 2024 17:46:49 -0400 Subject: [PATCH 019/194] further investigate quantum_dimension type stability --- .../lib/Sectors/test/test_category_product.jl | 60 ++++++++++++++----- .../src/lib/Sectors/test/test_fusion_rules.jl | 9 +-- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index aeb576656f..9552eafa2f 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -141,6 +141,32 @@ end @test categories(s)[3] == Fib("τ") end + @testset "Quantum dimension and GradedUnitRange" begin + g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) + g_ising = gradedrange([(Ising("1") × Ising("1")) => 1]) + # for the next 2 tests, the first one will be broken, the second will pass + # it does not matter which one is Fib and which one is Ising + # only compilation order matters + # I don't understand. + @test_broken (@inferred quantum_dimension(g_fib)) == 1.0 + @test (@inferred quantum_dimension(g_ising)) == 1.0 + + # check commenting the two tests above and uncommenting the two below + #@test_broken (@inferred quantum_dimension(g_ising)) == 1.0 + #@test (@inferred quantum_dimension(g_fib)) == 1.0 + + # or even executing the sector-wise test below *before* magically makes the tests pass + @test (@inferred quantum_dimension((Fib("1") × Fib("1")))) == 1.0 + @test (@inferred quantum_dimension((Ising("1") × Ising("1")))) == 1.0 + + # similar story below: swapping the two tests make both pass. + @test_broken (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 + @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 + # check commenting above and uncommenting below! + # @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 + # @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 + end + @testset "Enforce same spaces in fusion" begin p12 = U1(1) × U1(2) p123 = U1(1) × U1(2) × U1(3) @@ -152,22 +178,22 @@ end @testset "Empty category" begin s = CategoryProduct(()) - @test dual(s) == s - @test s × s == s - @test s ⊗ s == s + @test (@inferred dual(s)) == s + @test (@inferred s × s) == s + @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 0 end @testset "Fusion of Abelian products" begin p11 = U1(1) × U1(1) - @test p11 ⊗ p11 == U1(2) × U1(2) + @test @inferred(p11 ⊗ p11 == U1(2) × U1(2)) p123 = U1(1) × U1(2) × U1(3) - @test p123 ⊗ p123 == U1(2) × U1(4) × U1(6) + @test @inferred(p123 ⊗ p123 == U1(2) × U1(4) × U1(6)) s1 = sector(U1(1), Z{2}(1)) s2 = sector(U1(0), Z{2}(0)) - @test s1 ⊗ s2 == U1(1) × Z{2}(1) + @test @inferred(s1 ⊗ s2 == U1(1) × Z{2}(1)) end @testset "Fusion of NonAbelian products" begin @@ -179,27 +205,30 @@ end (SU2(1) × SU2(1)) => 1, ]) @test (@inferred quantum_dimension(phh ⊗ phh)) == 16 + @test (@inferred phh ⊗ phh == gradedrange([ + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, + ])) end @testset "Fusion of NonGroupCategory products" begin ı = Fib("1") τ = Fib("τ") s = ı × ı - g = gradedrange([(ı × ı) => 1]) - @test s ⊗ s == g - @test_broken (@inferred quantum_dimension(g)) == 1.0 # I don't understand + @test @inferred(s ⊗ s == gradedrange([s => 1])) s = τ × τ - g = gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]) - @test s ⊗ s == g - @test_broken (@inferred quantum_dimension(g)) == 2.0 + 3quantum_dimension(τ) # I don't understand + @test @inferred( + s ⊗ s == gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]) + ) σ = Ising("σ") ψ = Ising("ψ") s = τ × σ g = gradedrange([(ı × Ising(1)) => 1, (τ × Ising(1)) => 1, (ı × ψ) => 1, (τ × ψ) => 1]) - @test s ⊗ s == g - @test (@inferred quantum_dimension(g)) == 2.0 + 2quantum_dimension(τ) # ??? + @test @inferred(s ⊗ s) == g end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -235,8 +264,7 @@ end @test (@inferred quantum_dimension(s ⊗ s)) == 4.0 + 4.0quantum_dimension(τ) s = U1(1) × ı × τ - @test s ⊗ s == gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) - @test_broken (@inferred quantum_dimension(s ⊗ s)) == 1.0 + quantum_dimension(τ) + @test @inferred(s ⊗ s) == gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) end end end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 8aa09d66d8..e7c389229b 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -89,14 +89,15 @@ end @testset "GradedUnitRange fusion rules" begin g1 = gradedrange([U1(1) => 1, U1(2) => 2]) g2 = gradedrange([U1(-1) => 2, U1(0) => 1, U1(1) => 2]) - @test tensor_product(g1, g2) == + @test (@inferred tensor_product(g1, g2)) == gradedrange([U1(0) => 2, U1(1) => 5, U1(2) => 4, U1(3) => 4]) g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2, SU2(1) => 1]) g4 = gradedrange([SU2(1//2) => 1, SU2(1) => 2]) - @test tensor_product(g3, g4) == gradedrange([ - SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2 - ]) + @test @inferred( + tensor_product(g3, g4) == + gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]) + ) # test different categories cannot be fused @test_throws MethodError tensor_product(g1, g4) From be3f3514fd758f4f6676d0c4ea84a4a8bfe228b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 2 Apr 2024 18:45:26 -0400 Subject: [PATCH 020/194] improve type stability --- .../src/lib/Sectors/src/abstractcategory.jl | 7 + .../Sectors/src/category_definitions/su.jl | 5 + .../src/lib/Sectors/src/category_product.jl | 88 +++++-- NDTensors/src/lib/Sectors/src/filter.jl | 36 +++ NDTensors/src/lib/Sectors/src/g_fib.html | 134 ++++++++++ NDTensors/src/lib/Sectors/src/g_ising.html | 134 ++++++++++ .../src/lib/Sectors/src/symmetry_style.jl | 33 +-- .../lib/Sectors/test/test_category_product.jl | 228 ++++++++++++++---- 8 files changed, 575 insertions(+), 90 deletions(-) create mode 100644 NDTensors/src/lib/Sectors/src/filter.jl create mode 100644 NDTensors/src/lib/Sectors/src/g_fib.html create mode 100644 NDTensors/src/lib/Sectors/src/g_ising.html diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 9c446f22aa..0b1c37b23d 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -20,6 +20,13 @@ function GradedAxes.dual(category_type::Type{<:AbstractCategory}) return error("`dual` not defined for type $(category_type).") end +function quantum_dimension(::SymmetryStyle, c::AbstractCategory) + return error("method `quantum_dimension` not defined for type $(typeof(c))") +end + +quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 +quantum_dimension(::EmptyCategory, ::AbstractCategory) = 0 + # ================ fusion rule interface ==================== function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) return error("`label_fusion_rule` not defined for type $(category_type).") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index d51d71fae4..d3239b459b 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -7,6 +7,11 @@ struct SU{N} <: AbstractCategory # Gelfand-Tsetlin (GT) pattern describing # an SU(N) irrep #TODO: any way this could be NTuple{N-1,Int} ? + # not in a natural way + # see https://discourse.julialang.org/t/addition-to-parameter-of-parametric-type/20059/15 + # and https://github.com/JuliaLang/julia/issues/8472 + # can use https://github.com/vtjnash/ComputedFieldTypes.jl + # can define SU{N,M} and impose M=N-1 in the constructor l::NTuple{N,Int} end diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 23f85e22c0..1b20ad78b0 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -28,6 +28,14 @@ function SymmetryStyle(c::CategoryProduct) end end +function SymmetryStyle(nt::NamedTuple) + return if length(nt) == 0 + EmptyCategory() + else + reduce(combine_styles, map(SymmetryStyle, (values(nt)))) + end +end + # ============== Sector interface ================= function quantum_dimension(::NonAbelianGroup, s::CategoryProduct) return prod(map(quantum_dimension, categories(s))) @@ -118,33 +126,69 @@ function categories_equal(A::NamedTuple, B::NamedTuple) return common_categories_match && unique_categories_zero end -function fusion_rule(k::Symbol, c1::C, c2::C) where {C<:AbstractCategory} - fused = c1 ⊗ c2 - if SymmetryStyle(c1) == AbelianGroup() - return sector(k => fused) - end - return GradedAxes.gradedrange([ - sector(k => LabelledNumbers.label(b)) => LabelledNumbers.unlabel(b) for - b in blocklengths(fused) - ]) -end - # allow ⊗ for different types in NamedTuple function fusion_rule( s1::CategoryProduct{Cat1}, s2::CategoryProduct{Cat2} ) where {Cat1<:NamedTuple,Cat2<:NamedTuple} - if SymmetryStyle(s1) == EmptyCategory() # still works when s2 is also empty - return s2 + + # avoid issues with length 0 CategoryProduct + if SymmetryStyle(s1) == EmptyCategory() + if SymmetryStyle(s2) == AbelianGroup() || SymmetryStyle(s2) == EmptyCategory() + return s2 + end + return GradedAxes.gradedrange([s2 => 1]) + end + if SymmetryStyle(s2) == EmptyCategory() + if SymmetryStyle(s1) == AbelianGroup() + return s1 + end + return GradedAxes.gradedrange([s1 => 1]) + end + + cats1 = categories(s1) + cats2 = categories(s2) + diff_cat = CategoryProduct(symdiff_keys(cats1, cats2)) + shared1 = intersect_keys(cats1, cats2) + if length(shared1) == 0 + if SymmetryStyle(diff_cat) == AbelianGroup() + return diff_cat + end + return GradedAxes.gradedrange([diff_cat => 1]) end - A = categories(s1) - B = categories(s2) - diff_cat = A[setdiff(keys(A), keys(B))] × B[setdiff(keys(B), keys(A))] - shared_keys = intersect(keys(A), keys(B)) - shared_cat = ntuple( - i -> fusion_rule(shared_keys[i], A[shared_keys[i]], B[shared_keys[i]]), - length(shared_keys), - ) - return diff_cat × reduce(×, shared_cat; init=sector()) + + shared2 = intersect_keys(cats2, cats1) + fused = fusion_rule(shared1, shared2) + out = diff_cat × fused + return out +end + +function fusion_rule( + cats1::NT, cats2::NT +) where {Names,NT<:NamedTuple{Names,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} + return fusion_rule(cats1[(Names[1],)], cats2[(Names[1],)]) × + fusion_rule(cats1[Names[2:end]], cats2[Names[2:end]]) +end + +fusion_rule(cats1::NamedTuple{}, cats2::NamedTuple{}) = sector() + +function fusion_rule( + cats1::NT, cats2::NT +) where {NT<:NamedTuple{<:Any,<:Tuple{AbstractCategory}}} + # cannot be EmptyCategory + key = only(keys(cats1)) + fused = only(values(cats1)) ⊗ only(values(cats2)) + if SymmetryStyle(cats1) == AbelianGroup() + return sector(key => fused) + end + la = fused[1] + v = [sector(key => LabelledNumbers.label(la)) => LabelledNumbers.unlabel(la)] + for la in blocklengths(fused[2:end]) + push!(v, sector(key => LabelledNumbers.label(la)) => LabelledNumbers.unlabel(la)) + end + g = GradedAxes.gradedrange(v) + + #g = GradedAxes.gradedrange(set_name.(BlockArrays.blocklengths(fused))) + return g end # ============== Ordered implementation ================= diff --git a/NDTensors/src/lib/Sectors/src/filter.jl b/NDTensors/src/lib/Sectors/src/filter.jl new file mode 100644 index 0000000000..41ea93d415 --- /dev/null +++ b/NDTensors/src/lib/Sectors/src/filter.jl @@ -0,0 +1,36 @@ +# https://discourse.julialang.org/t/compile-time-type-filtering-from-a-tuple-is-it-possible/101090/2 + +# Length zero +filtered(::Type{C}, ::Tuple{}) where {C} = () + +# Length one +filtered(::Type{C}, cats::T) where {C,T<:Tuple{C}} = cats +filtered(::Type{C}, cats::T) where {C,T<:Tuple{Any}} = () + +# Length two or more +function filtered(::Type{C}, cats::T) where {C,T<:Tuple{Any,Any,Vararg{Any}}} + return (filtered(C, (first(cats),))..., filtered(C, Base.tail(cats))...) +end + +sieve(c::@NamedTuple) = (cats=categories(c)filtered(B, cats), filtered(Consonant, cats)) + +function f( + cats1::NT, cats2::NT +) where {NT<:NamedTuple{<:Any,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} + return println(NT) +end + +function gg( + cats1::NT, cats2::NT +) where {NT<:NamedTuple{<:Any,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} + k = first(keys(cats1)) + return cats1[k] +end + +function hh( + cats1::NT, cats2::NT +) where {Names,NT<:NamedTuple{Names,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} + println(typeof(cats1), " ", Names) + return fusion_rule(cats1[(Names[1],)], cats2[(Names[1],)]), + (cats1[(Names[2:end],)], cats2[(Names[2:end],)]) +end diff --git a/NDTensors/src/lib/Sectors/src/g_fib.html b/NDTensors/src/lib/Sectors/src/g_fib.html new file mode 100644 index 0000000000..7c127d780c --- /dev/null +++ b/NDTensors/src/lib/Sectors/src/g_fib.html @@ -0,0 +1,134 @@ +
═════ 27 possible errors found ═════
+quantum_dimension(g::BlockArrays.BlockedUnitRange{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:34
+sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:564
+sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:564
+sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:535
+sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:535
+mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:307
+mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:44
+foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:48
+_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:58
+(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ Base ./reduce.jl:100
+(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ NDTensors.Sectors ./none:0
+quantum_dimension(c::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
+quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:37
+prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
+prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
+mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
+mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; kw::Base.Pairs) @ Base ./reduce.jl:307
+merge(a::@NamedTuple{}, itr::Base.Pairs) @ Base ./namedtuple.jl:364
+iterate(::Base.Pairs) @ Base.Iterators ./iterators.jl:301
+_pairs_elt(p::Base.Pairs, idx::Any) @ Base.Iterators ./iterators.jl:294
+│ runtime dispatch detected: (%4::Any)[idx::Any]::Any
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
+foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
+_foldl_impl(op::Base.BottomRF, init::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
+afoldl(::Base.BottomRF, ::Base._InitialValue, ::Float64, ::Float64) @ Base ./operators.jl:545
+(::Base.BottomRF)(acc::Float64, x::Float64) @ Base ./reduce.jl:86
+│ runtime dispatch detected: %1::Any(acc::Float64, x::Float64)::Any
+└────────────────────
+foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:49
+reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:383
+reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}, ::Base.HasEltype) @ Base ./reduce.jl:384
+│ runtime dispatch detected: Base.reduce_empty(op::Base.BottomRF, ::Float64)::Any
+└────────────────────
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:42
+│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.mul_prod), ::Base._InitialValue, ::Tuple{Float64, Float64})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
+└────────────────────
+kwcall(::NamedTuple, ::typeof(mapfoldl), f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
+pairs(nt::NamedTuple) @ Base.Iterators ./iterators.jl:279
+(Base.Pairs{Symbol})(data::NamedTuple, itr::Tuple{Vararg{Symbol}}) @ Base ./essentials.jl:343
+eltype(::Type{A} where A<:NamedTuple) @ Base ./namedtuple.jl:237
+nteltype(::Type{NamedTuple{names, T}} where names) where T<:Tuple @ Base ./namedtuple.jl:239
+eltype(t::Type{<:Tuple{Vararg{E}}}) where E @ Base ./tuple.jl:208
+_compute_eltype(t::Type{<:Tuple{Vararg{E}}} where E) @ Base ./tuple.jl:231
+afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:544
+(::Base.var"#54#55")(a::Any, b::Any) @ Base ./tuple.jl:235
+promote_typejoin(a::Any, b::Any) @ Base ./promotion.jl:172
+typejoin(a::Any, b::Any) @ Base ./promotion.jl:127
+│ runtime dispatch detected: Base.UnionAll(%403::Any, %405::Any)::Any
+└────────────────────
+afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:545
+(::Base.var"#54#55")(a::Type, b::Any) @ Base ./tuple.jl:235
+promote_typejoin(a::Type, b::Any) @ Base ./promotion.jl:172
+typejoin(a::Type, b::Any) @ Base ./promotion.jl:127
+│ runtime dispatch detected: Base.UnionAll(%398::Any, %400::Any)::Any
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Any) @ Base ./reduce.jl:175
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
+foldl_impl(op::Base.BottomRF, nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
+_foldl_impl(op::Base.BottomRF, init::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
+afoldl(::Base.BottomRF, ::Any, ::Float64, ::Float64) @ Base ./operators.jl:544
+(::Base.BottomRF)(acc::Any, x::Float64) @ Base ./reduce.jl:86
+│ runtime dispatch detected: %1::Any(acc::Any, x::Float64)::Any
+└────────────────────
+mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
+│ failed to optimize due to recursion: mapreduce(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
+└────────────────────
+prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
+│ failed to optimize due to recursion: Base.var"#prod#309"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(prod), ::Tuple{Float64, Float64})
+└────────────────────
+prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
+│ failed to optimize due to recursion: prod(::Tuple{Float64, Float64})
+└────────────────────
+quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:36
+│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::NDTensors.Sectors.NonGroupCategory, ::CategoryProduct{Tuple{Fib, Fib}})
+└────────────────────
+quantum_dimension(c::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
+│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::CategoryProduct{Tuple{Fib, Fib}})
+└────────────────────
+(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ NDTensors.Sectors ./none:0
+│ failed to optimize due to recursion: (::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64})
+└────────────────────
+(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
+reduce_first(::typeof(Base.add_sum), x::Any) @ Base ./reduce.jl:407
+reduce_first(::typeof(+), x::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/fillalgebra.jl:396
+getindex_value(Z::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:321
+│ runtime dispatch detected: FillArrays.one(%1::Any)::Any
+└────────────────────
+axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
+│ runtime dispatch detected: size(A::FillArrays.AbstractOnes)::Any
+└────────────────────
+axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
+│ runtime dispatch detected: map(Base.oneto, %1::Any)::Any
+└────────────────────
+FillArrays.Fill(x::T, sz::Tuple{Vararg{Any, N}}) where {T, N} @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:133
+│ runtime dispatch detected: %3::Type{FillArrays.Fill{_A, _B}} where {_A, _B}(x::Any, sz::Tuple{Vararg{Any, N}} where N)::Any
+└────────────────────
+(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
+│ runtime dispatch detected: Base.reduce_first(add_sum, x::Any)::Any
+└────────────────────
+(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ Base ./reduce.jl:100
+│ failed to optimize due to recursion: (::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(::Base._InitialValue, ::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64})
+└────────────────────
+_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:53
+│ failed to optimize due to recursion: Base._foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}})
+└────────────────────
+foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:47
+│ failed to optimize due to recursion: Base.foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}})
+└────────────────────
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:42
+│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.add_sum), ::Base._InitialValue, ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
+│ failed to optimize due to recursion: Base.var"#mapreduce#302"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(mapreduce), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+
diff --git a/NDTensors/src/lib/Sectors/src/g_ising.html b/NDTensors/src/lib/Sectors/src/g_ising.html new file mode 100644 index 0000000000..664dcedc4a --- /dev/null +++ b/NDTensors/src/lib/Sectors/src/g_ising.html @@ -0,0 +1,134 @@ +
═════ 27 possible errors found ═════
+quantum_dimension(g::BlockArrays.BlockedUnitRange{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:34
+sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:564
+sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:564
+sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:535
+sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:535
+mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:307
+mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:44
+foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:48
+_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:58
+(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ Base ./reduce.jl:100
+(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ NDTensors.Sectors ./none:0
+quantum_dimension(c::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
+quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:37
+prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
+prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
+mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
+mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; kw::Base.Pairs) @ Base ./reduce.jl:307
+merge(a::@NamedTuple{}, itr::Base.Pairs) @ Base ./namedtuple.jl:364
+iterate(::Base.Pairs) @ Base.Iterators ./iterators.jl:301
+_pairs_elt(p::Base.Pairs, idx::Any) @ Base.Iterators ./iterators.jl:294
+│ runtime dispatch detected: (%4::Any)[idx::Any]::Any
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
+foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
+_foldl_impl(op::Base.BottomRF, init::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
+afoldl(::Base.BottomRF, ::Base._InitialValue, ::Float64, ::Float64) @ Base ./operators.jl:545
+(::Base.BottomRF)(acc::Float64, x::Float64) @ Base ./reduce.jl:86
+│ runtime dispatch detected: %1::Any(acc::Float64, x::Float64)::Any
+└────────────────────
+foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:49
+reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:383
+reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}, ::Base.HasEltype) @ Base ./reduce.jl:384
+│ runtime dispatch detected: Base.reduce_empty(op::Base.BottomRF, ::Float64)::Any
+└────────────────────
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:42
+│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.mul_prod), ::Base._InitialValue, ::Tuple{Float64, Float64})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
+└────────────────────
+kwcall(::NamedTuple, ::typeof(mapfoldl), f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
+pairs(nt::NamedTuple) @ Base.Iterators ./iterators.jl:279
+(Base.Pairs{Symbol})(data::NamedTuple, itr::Tuple{Vararg{Symbol}}) @ Base ./essentials.jl:343
+eltype(::Type{A} where A<:NamedTuple) @ Base ./namedtuple.jl:237
+nteltype(::Type{NamedTuple{names, T}} where names) where T<:Tuple @ Base ./namedtuple.jl:239
+eltype(t::Type{<:Tuple{Vararg{E}}}) where E @ Base ./tuple.jl:208
+_compute_eltype(t::Type{<:Tuple{Vararg{E}}} where E) @ Base ./tuple.jl:231
+afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:544
+(::Base.var"#54#55")(a::Any, b::Any) @ Base ./tuple.jl:235
+promote_typejoin(a::Any, b::Any) @ Base ./promotion.jl:172
+typejoin(a::Any, b::Any) @ Base ./promotion.jl:127
+│ runtime dispatch detected: Base.UnionAll(%403::Any, %405::Any)::Any
+└────────────────────
+afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:545
+(::Base.var"#54#55")(a::Type, b::Any) @ Base ./tuple.jl:235
+promote_typejoin(a::Type, b::Any) @ Base ./promotion.jl:172
+typejoin(a::Type, b::Any) @ Base ./promotion.jl:127
+│ runtime dispatch detected: Base.UnionAll(%398::Any, %400::Any)::Any
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Any) @ Base ./reduce.jl:175
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
+foldl_impl(op::Base.BottomRF, nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
+_foldl_impl(op::Base.BottomRF, init::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
+afoldl(::Base.BottomRF, ::Any, ::Float64, ::Float64) @ Base ./operators.jl:544
+(::Base.BottomRF)(acc::Any, x::Float64) @ Base ./reduce.jl:86
+│ runtime dispatch detected: %1::Any(acc::Any, x::Float64)::Any
+└────────────────────
+mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
+│ failed to optimize due to recursion: mapreduce(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
+└────────────────────
+prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
+│ failed to optimize due to recursion: Base.var"#prod#309"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(prod), ::Tuple{Float64, Float64})
+└────────────────────
+prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
+│ failed to optimize due to recursion: prod(::Tuple{Float64, Float64})
+└────────────────────
+quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:36
+│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::NDTensors.Sectors.NonGroupCategory, ::CategoryProduct{Tuple{Ising, Ising}})
+└────────────────────
+quantum_dimension(c::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
+│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::CategoryProduct{Tuple{Ising, Ising}})
+└────────────────────
+(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ NDTensors.Sectors ./none:0
+│ failed to optimize due to recursion: (::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64})
+└────────────────────
+(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
+reduce_first(::typeof(Base.add_sum), x::Any) @ Base ./reduce.jl:407
+reduce_first(::typeof(+), x::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/fillalgebra.jl:396
+getindex_value(Z::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:321
+│ runtime dispatch detected: FillArrays.one(%1::Any)::Any
+└────────────────────
+axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
+│ runtime dispatch detected: size(A::FillArrays.AbstractOnes)::Any
+└────────────────────
+axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
+│ runtime dispatch detected: map(Base.oneto, %1::Any)::Any
+└────────────────────
+FillArrays.Fill(x::T, sz::Tuple{Vararg{Any, N}}) where {T, N} @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:133
+│ runtime dispatch detected: %3::Type{FillArrays.Fill{_A, _B}} where {_A, _B}(x::Any, sz::Tuple{Vararg{Any, N}} where N)::Any
+└────────────────────
+(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
+│ runtime dispatch detected: Base.reduce_first(add_sum, x::Any)::Any
+└────────────────────
+(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ Base ./reduce.jl:100
+│ failed to optimize due to recursion: (::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(::Base._InitialValue, ::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64})
+└────────────────────
+_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:53
+│ failed to optimize due to recursion: Base._foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}})
+└────────────────────
+foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:47
+│ failed to optimize due to recursion: Base.foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}})
+└────────────────────
+mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:42
+│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.add_sum), ::Base._InitialValue, ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
+│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
+│ failed to optimize due to recursion: Base.var"#mapreduce#302"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(mapreduce), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
+└────────────────────
+
diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index bd5b71e950..1669abf701 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -13,27 +13,20 @@ struct EmptyCategory <: SymmetryStyle end # crash for empty g. Currently impossible to construct. SymmetryStyle(g::AbstractUnitRange) = SymmetryStyle(LabelledNumbers.label(first(g))) -quantum_dimension(c::Any) = quantum_dimension(SymmetryStyle(c), c) +quantum_dimension(c) = quantum_dimension(SymmetryStyle(c), c) -function quantum_dimension(::SymmetryStyle, ::Any) - return error("method `dimension` not defined for type $(typeof(c))") +quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) + +function quantum_dimension(::NonAbelianGroup, g::GradedAxes.GradedUnitRange) + return sum( + LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for + b in BlockArrays.blocklengths(g), init in 0 + ) end -quantum_dimension(::AbelianGroup, ::Any) = 1 -quantum_dimension(::EmptyCategory, ::Any) = 0 - -function quantum_dimension(g::AbstractUnitRange) - if SymmetryStyle(g) == AbelianGroup() - return length(g) - elseif SymmetryStyle(g) == NonAbelianGroup() - return sum( - LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for - b in BlockArrays.blocklengths(g), init in 0 - ) - else - return sum( - LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for - b in BlockArrays.blocklengths(g), init in 0.0 - ) - end +function quantum_dimension(::NonGroupCategory, g::GradedAxes.GradedUnitRange) + return sum( + LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for + b in BlockArrays.blocklengths(g), init in 0.0 + ) end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 9552eafa2f..758b69d8d9 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -42,29 +42,141 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred quantum_dimension(s)) == 1.0 end + @testset "Comparisons with unspecified labels" begin + q2 = sector(; N=U1(2)) + q20 = (N=U1(2),) × (J=SU{2}(1),) + @test q20 == q2 + + q21 = (N=U1(2),) × (J=SU{2}(3),) + @test q21 != q2 + + a = (A=U1(0),) × (B=U1(2),) + b = (B=U1(2),) × (C=U1(0),) + @test a == b + c = (B=U1(2),) × (C=U1(1),) + @test a != c + end + + @testset "Quantum dimension and GradedUnitRange" begin + g = gradedrange([sector(; A=U1(0), B=Z{2}(0)) => 1, sector(; A=U1(1), B=Z{2}(0)) => 2]) # abelian + @test (@inferred quantum_dimension(g)) == 3 + + g = gradedrange([ # non-abelian + sector(; A=SU2(0), B=SU2(0)) => 1, + sector(; A=SU2(1), B=SU2(0)) => 1, + sector(; A=SU2(0), B=SU2(1)) => 1, + sector(; A=SU2(1), B=SU2(1)) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 16 + + # mixed group + g = gradedrange([ + sector(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, + sector(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, + ]) + @test_broken (@inferred quantum_dimension(g)) == 4 # TODO + + # non group categories + # make no sense, see Ordered Products + g_fib = gradedrange([sector(; A=Fib("1"), B=Fib("1")) => 1]) + g_ising = gradedrange([sector(; A=Ising("1"), B=Ising("1")) => 1]) + @test_broken (@inferred quantum_dimension(g_fib)) == 1.0 + @test (@inferred quantum_dimension(g_ising)) == 1.0 + + # mixed product Abelian / NonAbelian / NonGroup + g = gradedrange([ + sector(; A=U1(2), B=SU2(0), C=Ising(1)) => 1, + sector(; A=U1(2), B=SU2(1), C=Ising(1)) => 1, + sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, + sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, + ]) + @test @inferred(quantum_dimension(g)) == 8.0 + + g = gradedrange([ + sector(; A=U1(2), B=SU2(0), C=Fib("1")) => 1, + sector(; A=U1(2), B=SU2(1), C=Fib("1")) => 1, + sector(; A=U1(2), B=SU2(0), C=Fib("τ")) => 1, + sector(; A=U1(2), B=SU2(1), C=Fib("τ")) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) + end + @testset "Empty category" begin s = sector() - @test dual(s) == s - @test s × s == s - @test s ⊗ s == s + @test @inferred(dual(s)) == s + @test @inferred(s × s) == s + @test @inferred(s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 0 end - @testset "Abelian fusion rules" begin + @testset "Fusion of Abelian products" begin q00 = sector() q10 = sector(; A=U1(1)) q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test q10 ⊗ q10 == sector(; A=U1(2)) - @test q01 ⊗ q00 == q01 - @test q00 ⊗ q01 == q01 - @test q10 ⊗ q01 == q11 - @test q11 ⊗ q11 == sector(; A=U1(2), B=U1(2)) + @test @inferred(q10 ⊗ q10) == sector(; A=U1(2)) + @test @inferred(q01 ⊗ q00) == q01 + @test @inferred(q00 ⊗ q01) == q01 + @test @inferred(q10 ⊗ q01) == q11 + @test @inferred(q11 ⊗ q11) == sector(; A=U1(2), B=U1(2)) + + s11 = sector(; A=U1(1), B=Z{2}(1)) + s10 = sector(; A=U1(1)) + s01 = sector(; B=Z{2}(1)) + @test @inferred(s01 ⊗ q00) == s01 + @test @inferred(q00 ⊗ s01) == s01 + @test @inferred(s10 ⊗ s01) == s11 + @test @inferred(s11 ⊗ s11) == sector(; A=U1(2), B=Z{2}(0)) + end + + @testset "Fusion of NonAbelian products" begin + p0 = sector() + pha = sector(; A=SU2(1//2)) + phb = sector(; B=SU2(1//2)) + phab = sector(; A=SU2(1//2), B=SU2(1//2)) + + @test (@inferred pha ⊗ pha) == + gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) + @test (@inferred pha ⊗ p0) == gradedrange([pha => 1]) + @test (@inferred p0 ⊗ phb) == gradedrange([phb => 1]) + @test (@inferred pha ⊗ phb) == gradedrange([phab => 1]) + + @test (@inferred phab ⊗ phab) == gradedrange([ + sector(; A=SU2(0), B=SU2(0)) => 1, + sector(; A=SU2(1), B=SU2(0)) => 1, + sector(; A=SU2(0), B=SU2(1)) => 1, + sector(; A=SU2(1), B=SU2(1)) => 1, + ]) + end + + @testset "Fusion of NonGroupCategory products" begin + ı = Fib("1") + τ = Fib("τ") + s = sector(; A=ı, B=ı) + @test_broken @inferred(s ⊗ s) == gradedrange([s => 1]) # TODO + + s = sector(; A=τ, B=τ) + @test @inferred(s ⊗ s) == gradedrange([ + sector(; A=ı, B=ı) => 1, + sector(; A=τ, B=ı) => 1, + sector(; A=ı, B=τ) => 1, + sector(; A=τ, B=τ) => 1, + ]) + + σ = Ising("σ") + ψ = Ising("ψ") + s = τ × σ + g = gradedrange([ + sector(; A=ı, B=Ising(1)) => 1, + sector(; A=τ, B=Ising(1)) => 1, + sector(; A=ı, B=ψ) => 1, + sector(; A=τ, B=ψ) => 1, + ]) + @test @inferred(s ⊗ s) == g end - @testset "U(1) × SU(2) conventional" begin - q0 = sector() + @testset "Fusion of mixed Abelian and NonAbelian products" begin q0h = sector(; J=SU2(1//2)) q10 = (N=U1(1),) × (J=SU2(0),) # Put names in reverse order sometimes: @@ -75,43 +187,34 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test q1h ⊗ q1h == gradedrange([q20 => 1, q21 => 1]) - @test q10 ⊗ q1h == gradedrange([q2h => 1]) - @test q0h ⊗ q1h == gradedrange([q10 => 1, q11 => 1]) - @test q11 ⊗ q11 == gradedrange([q20 => 1, q21 => 1, q22 => 1]) + @test @inferred(q1h ⊗ q1h) == gradedrange([q20 => 1, q21 => 1]) + @test_broken @inferred(q10 ⊗ q1h) == gradedrange([q2h => 1]) # TODO + @test @inferred(q0h ⊗ q1h) == gradedrange([q10 => 1, q11 => 1]) + @test @inferred(q11 ⊗ q11) == gradedrange([q20 => 1, q21 => 1, q22 => 1]) end - @testset "U(1) × SU(2)" begin - q0 = sector() - q0h = sector(; J=SU{2}(2)) - q10 = (N=U1(1),) × (J=SU{2}(1),) - # Put names in reverse order sometimes: - q1h = (J=SU{2}(2),) × (N=U1(1),) - q11 = (N=U1(1),) × (J=SU{2}(3),) - q20 = sector(; N=U1(2)) - q2h = (N=U1(2),) × (J=SU{2}(2),) - q21 = (N=U1(2),) × (J=SU{2}(3),) - q22 = (N=U1(2),) × (J=SU{2}(5),) - - @test q1h ⊗ q1h == gradedrange([q20 => 1, q21 => 1]) - @test q10 ⊗ q1h == gradedrange([q2h => 1]) - @test q0h ⊗ q1h == gradedrange([q10 => 1, q11 => 1]) - @test q11 ⊗ q11 == gradedrange([q20 => 1, q21 => 1, q22 => 1]) - end - - @testset "Comparisons with unspecified labels" begin - q2 = sector(; N=U1(2)) - q20 = (N=U1(2),) × (J=SU{2}(1),) - @test q20 == q2 + @testset "Fusion of fully mixed products" begin + s = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) + @test @inferred(s ⊗ s) == gradedrange([ + sector(; A=U1(2), B=SU2(0), C=Ising(1)) => 1, + sector(; A=U1(2), B=SU2(1), C=Ising(1)) => 1, + sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, + sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, + ]) - q21 = (N=U1(2),) × (J=SU{2}(3),) - @test q21 != q2 + ı = Fib("1") + τ = Fib("τ") + s = U1(1) × SU2(1//2) × τ + @test_broken @inferred(s ⊗ s) == gradedrange([ # TODO + sector(; A=U1(2), B=SU2(0), C=ı) => 1, + sector(; A=U1(2), B=SU2(1), C=ı) => 1, + sector(; A=U1(2), B=SU2(0), C=τ) => 1, + sector(; A=U1(2), B=SU2(1), C=τ) => 1, + ]) - a = (A=U1(0),) × (B=U1(2),) - b = (B=U1(2),) × (C=U1(0),) - @test a == b - c = (B=U1(2),) × (C=U1(1),) - @test a != c + s = U1(1) × ı × τ + @test @inferred(s ⊗ s) == + gradedrange([sector(; A=U1(2), B=ı, C=ı) => 1, sector(; A=U1(2), B=ı, C=τ) => 1]) end end @@ -142,6 +245,22 @@ end end @testset "Quantum dimension and GradedUnitRange" begin + g = gradedrange([(U1(0) × Z{2}(0)) => 1, (U1(1) × Z{2}(0)) => 2]) # abelian + @test (@inferred quantum_dimension(g)) == 3 + + g = gradedrange([ # non-abelian + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 16 + + # mixed group + g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) + @test (@inferred quantum_dimension(g)) == 4 + + # NonGroupCategory is strange g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) g_ising = gradedrange([(Ising("1") × Ising("1")) => 1]) # for the next 2 tests, the first one will be broken, the second will pass @@ -165,6 +284,23 @@ end # check commenting above and uncommenting below! # @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 # @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 + + # mixed product Abelian / NonAbelian / NonGroup + g = gradedrange([ + (U1(2) × SU2(0) × Ising(1)) => 1, + (U1(2) × SU2(1) × Ising(1)) => 1, + (U1(2) × SU2(0) × Ising("ψ")) => 1, + (U1(2) × SU2(1) × Ising("ψ")) => 1, + ]) + @test @inferred(quantum_dimension(g)) == 8.0 + + g = gradedrange([ + (U1(2) × SU2(0) × Fib("1")) => 1, + (U1(2) × SU2(1) × Fib("1")) => 1, + (U1(2) × SU2(0) × Fib("τ")) => 1, + (U1(2) × SU2(1) × Fib("τ")) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end @testset "Enforce same spaces in fusion" begin @@ -204,7 +340,6 @@ end (SU2(0) × SU2(1)) => 1, (SU2(1) × SU2(1)) => 1, ]) - @test (@inferred quantum_dimension(phh ⊗ phh)) == 16 @test (@inferred phh ⊗ phh == gradedrange([ (SU2(0) × SU2(0)) => 1, (SU2(1) × SU2(0)) => 1, @@ -239,7 +374,6 @@ end p1h1 = U1(1) × SU2(1//2) × Z{2}(1) @test p1h1 ⊗ p1h1 == gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) - @test (@inferred quantum_dimension(p1h1 ⊗ p1h1)) == 4 end @testset "Fusion of fully mixed products" begin @@ -250,7 +384,6 @@ end (U1(2) × SU2(0) × Ising("ψ")) => 1, (U1(2) × SU2(1) × Ising("ψ")) => 1, ]) - @test (@inferred quantum_dimension(s ⊗ s)) == 8 ı = Fib("1") τ = Fib("τ") @@ -261,7 +394,6 @@ end (U1(2) × SU2(0) × τ) => 1, (U1(2) × SU2(1) × τ) => 1, ]) - @test (@inferred quantum_dimension(s ⊗ s)) == 4.0 + 4.0quantum_dimension(τ) s = U1(1) × ı × τ @test @inferred(s ⊗ s) == gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) From bb0d7575042fb6ae29c31cc55896746445f7e48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 2 Apr 2024 19:28:28 -0400 Subject: [PATCH 021/194] fix type stability for quantum_dimension --- .../src/lib/Sectors/src/abstractcategory.jl | 12 +++++++++ .../src/lib/Sectors/src/symmetry_style.jl | 18 ------------- .../lib/Sectors/test/test_category_product.jl | 27 +++++-------------- 3 files changed, 18 insertions(+), 39 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 0b1c37b23d..bda68dd0e0 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -20,10 +20,22 @@ function GradedAxes.dual(category_type::Type{<:AbstractCategory}) return error("`dual` not defined for type $(category_type).") end +quantum_dimension(c::AbstractCategory) = quantum_dimension(SymmetryStyle(c), c) + function quantum_dimension(::SymmetryStyle, c::AbstractCategory) return error("method `quantum_dimension` not defined for type $(typeof(c))") end +function quantum_dimension(g::AbstractUnitRange) + if SymmetryStyle(g) == AbelianGroup() + return length(g) + end + + mult = LabelledNumbers.unlabel.(BlockArrays.blocklengths(g)) + dims = quantum_dimension.(LabelledNumbers.label.(BlockArrays.blocklengths(g))) + return sum(m * d for (m, d) in zip(mult, dims)) +end + quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 quantum_dimension(::EmptyCategory, ::AbstractCategory) = 0 diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index 1669abf701..6de8838e20 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -12,21 +12,3 @@ struct EmptyCategory <: SymmetryStyle end # crash for empty g. Currently impossible to construct. SymmetryStyle(g::AbstractUnitRange) = SymmetryStyle(LabelledNumbers.label(first(g))) - -quantum_dimension(c) = quantum_dimension(SymmetryStyle(c), c) - -quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) - -function quantum_dimension(::NonAbelianGroup, g::GradedAxes.GradedUnitRange) - return sum( - LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for - b in BlockArrays.blocklengths(g), init in 0 - ) -end - -function quantum_dimension(::NonGroupCategory, g::GradedAxes.GradedUnitRange) - return sum( - LabelledNumbers.unlabel(b) * quantum_dimension(LabelledNumbers.label(b)) for - b in BlockArrays.blocklengths(g), init in 0.0 - ) -end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 758b69d8d9..e4914c6cfa 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -74,13 +74,12 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws sector(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, sector(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, ]) - @test_broken (@inferred quantum_dimension(g)) == 4 # TODO + @test (@inferred quantum_dimension(g)) == 4 # non group categories - # make no sense, see Ordered Products g_fib = gradedrange([sector(; A=Fib("1"), B=Fib("1")) => 1]) g_ising = gradedrange([sector(; A=Ising("1"), B=Ising("1")) => 1]) - @test_broken (@inferred quantum_dimension(g_fib)) == 1.0 + @test (@inferred quantum_dimension(g_fib)) == 1.0 @test (@inferred quantum_dimension(g_ising)) == 1.0 # mixed product Abelian / NonAbelian / NonGroup @@ -260,30 +259,16 @@ end g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) @test (@inferred quantum_dimension(g)) == 4 - # NonGroupCategory is strange + # NonGroupCategory g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) g_ising = gradedrange([(Ising("1") × Ising("1")) => 1]) - # for the next 2 tests, the first one will be broken, the second will pass - # it does not matter which one is Fib and which one is Ising - # only compilation order matters - # I don't understand. - @test_broken (@inferred quantum_dimension(g_fib)) == 1.0 - @test (@inferred quantum_dimension(g_ising)) == 1.0 - - # check commenting the two tests above and uncommenting the two below - #@test_broken (@inferred quantum_dimension(g_ising)) == 1.0 - #@test (@inferred quantum_dimension(g_fib)) == 1.0 - - # or even executing the sector-wise test below *before* magically makes the tests pass @test (@inferred quantum_dimension((Fib("1") × Fib("1")))) == 1.0 + @test (@inferred quantum_dimension(g_fib)) == 1.0 + @test (@inferred quantum_dimension(g_ising)) == 1.0 @test (@inferred quantum_dimension((Ising("1") × Ising("1")))) == 1.0 - # similar story below: swapping the two tests make both pass. - @test_broken (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 - # check commenting above and uncommenting below! - # @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 - # @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 + @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 # mixed product Abelian / NonAbelian / NonGroup g = gradedrange([ From 6e6b10cca498fc4dd9d4fd2db72c5527d68e68eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 2 Apr 2024 19:44:13 -0400 Subject: [PATCH 022/194] fix product of singlet --- NDTensors/src/lib/Sectors/src/category_product.jl | 6 ++---- NDTensors/src/lib/Sectors/test/test_category_product.jl | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 1b20ad78b0..b19815f4fb 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -181,13 +181,11 @@ function fusion_rule( return sector(key => fused) end la = fused[1] - v = [sector(key => LabelledNumbers.label(la)) => LabelledNumbers.unlabel(la)] - for la in blocklengths(fused[2:end]) + v = Vector{Pair{CategoryProduct{NT},Int64}}() + for la in blocklengths(fused) push!(v, sector(key => LabelledNumbers.label(la)) => LabelledNumbers.unlabel(la)) end g = GradedAxes.gradedrange(v) - - #g = GradedAxes.gradedrange(set_name.(BlockArrays.blocklengths(fused))) return g end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index e4914c6cfa..b430bf90fa 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -153,7 +153,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws ı = Fib("1") τ = Fib("τ") s = sector(; A=ı, B=ı) - @test_broken @inferred(s ⊗ s) == gradedrange([s => 1]) # TODO + @test @inferred(s ⊗ s) == gradedrange([s => 1]) s = sector(; A=τ, B=τ) @test @inferred(s ⊗ s) == gradedrange([ @@ -187,7 +187,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws q22 = (N=U1(2),) × (J=SU2(2),) @test @inferred(q1h ⊗ q1h) == gradedrange([q20 => 1, q21 => 1]) - @test_broken @inferred(q10 ⊗ q1h) == gradedrange([q2h => 1]) # TODO + @test @inferred(q10 ⊗ q1h) == gradedrange([q2h => 1]) @test @inferred(q0h ⊗ q1h) == gradedrange([q10 => 1, q11 => 1]) @test @inferred(q11 ⊗ q11) == gradedrange([q20 => 1, q21 => 1, q22 => 1]) end From 667ea0f40794359159a3998881afb9399f485141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 2 Apr 2024 19:56:07 -0400 Subject: [PATCH 023/194] pass Named Category Products, broken Ordered Products --- .../lib/Sectors/test/test_category_product.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index b430bf90fa..e40e5a5f3c 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -203,15 +203,15 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws ı = Fib("1") τ = Fib("τ") - s = U1(1) × SU2(1//2) × τ - @test_broken @inferred(s ⊗ s) == gradedrange([ # TODO + s = sector(; A=U1(1), B=SU2(1//2), C=τ) + @test @inferred(s ⊗ s) == gradedrange([ sector(; A=U1(2), B=SU2(0), C=ı) => 1, sector(; A=U1(2), B=SU2(1), C=ı) => 1, sector(; A=U1(2), B=SU2(0), C=τ) => 1, sector(; A=U1(2), B=SU2(1), C=τ) => 1, ]) - s = U1(1) × ı × τ + s = sector(; A=U1(1), B=ı, C=τ) @test @inferred(s ⊗ s) == gradedrange([sector(; A=U1(2), B=ı, C=ı) => 1, sector(; A=U1(2), B=ı, C=τ) => 1]) end @@ -354,16 +354,18 @@ end @testset "Fusion of mixed Abelian and NonAbelian products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) - @test p2h ⊗ p1h == gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) + @test @inferred(p2h ⊗ p1h) == + gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) - @test p1h1 ⊗ p1h1 == - gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) + @test_broken @inferred(p1h1 ⊗ p1h1) == gradedrange([ + (U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1 + ]) end @testset "Fusion of fully mixed products" begin s = U1(1) × SU2(1//2) × Ising("σ") - @test s ⊗ s == gradedrange([ + @test_broken @inferred(s ⊗ s) == gradedrange([ (U1(2) × SU2(0) × Ising(1)) => 1, (U1(2) × SU2(1) × Ising(1)) => 1, (U1(2) × SU2(0) × Ising("ψ")) => 1, @@ -373,7 +375,7 @@ end ı = Fib("1") τ = Fib("τ") s = U1(1) × SU2(1//2) × τ - @test s ⊗ s == gradedrange([ + @test_broken @inferred(s ⊗ s) == gradedrange([ (U1(2) × SU2(0) × ı) => 1, (U1(2) × SU2(1) × ı) => 1, (U1(2) × SU2(0) × τ) => 1, From 11434d6493d81a28c485367d8e1f86d573773590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 2 Apr 2024 20:05:47 -0400 Subject: [PATCH 024/194] trickier tests --- .../lib/Sectors/test/test_category_product.jl | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index e40e5a5f3c..c776aeac52 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -75,6 +75,11 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws sector(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4 + g = gradedrange([ + sector(; A=SU2(0), B=Z{2}(0), C=SU2(1//2)) => 1, + sector(; A=SU2(0), B=Z{2}(1), C=SU2(1//2)) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 4 # non group categories g_fib = gradedrange([sector(; A=Fib("1"), B=Fib("1")) => 1]) @@ -92,10 +97,10 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test @inferred(quantum_dimension(g)) == 8.0 g = gradedrange([ - sector(; A=U1(2), B=SU2(0), C=Fib("1")) => 1, - sector(; A=U1(2), B=SU2(1), C=Fib("1")) => 1, - sector(; A=U1(2), B=SU2(0), C=Fib("τ")) => 1, - sector(; A=U1(2), B=SU2(1), C=Fib("τ")) => 1, + sector(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, + sector(; A=Fib("1"), B=SU2(1), C=U1(2)) => 1, + sector(; A=Fib("τ"), B=SU2(0), C=U1(2)) => 1, + sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end @@ -258,6 +263,8 @@ end # mixed group g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) @test (@inferred quantum_dimension(g)) == 4 + g = gradedrange([(SU2(0) × U1(0) × SU2(1//2)) => 1, (SU2(0) × U1(1) × SU2(1//2)) => 1]) + @test (@inferred quantum_dimension(g)) == 4 # NonGroupCategory g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) @@ -280,10 +287,10 @@ end @test @inferred(quantum_dimension(g)) == 8.0 g = gradedrange([ - (U1(2) × SU2(0) × Fib("1")) => 1, - (U1(2) × SU2(1) × Fib("1")) => 1, - (U1(2) × SU2(0) × Fib("τ")) => 1, - (U1(2) × SU2(1) × Fib("τ")) => 1, + (Fib("1") × SU2(0) × U1(2)) => 1, + (Fib("1") × SU2(1) × U1(2)) => 1, + (Fib("τ") × SU2(0) × U1(2)) => 1, + (Fib("τ") × SU2(1) × U1(2)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end From 18307818b47b35e2c5df8760b6339d358ef36673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 2 Apr 2024 20:18:09 -0400 Subject: [PATCH 025/194] all test passing --- NDTensors/src/lib/Sectors/src/category_product.jl | 8 ++++---- .../src/lib/Sectors/test/test_category_product.jl | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index b19815f4fb..49bcbdc0a6 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -202,9 +202,9 @@ function fusion_rule(s1::CategoryProduct{Cat}, s2::CategoryProduct{Cat}) where { if SymmetryStyle(s1) == EmptyCategory() # compile-time; simpler than specifying init return s1 end - cat1 = categories(s1) - cat2 = categories(s2) - prod12 = ntuple(i -> cat1[i] ⊗ cat2[i], length(cat1)) - g = reduce(×, prod12) + cats1 = categories(s1) + cats2 = categories(s2) + fused = map(fusion_rule, cats1, cats2) + g = reduce(×, fused) return g end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index c776aeac52..b885ee0418 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -216,9 +216,9 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws sector(; A=U1(2), B=SU2(1), C=τ) => 1, ]) - s = sector(; A=U1(1), B=ı, C=τ) + s = sector(; A=τ, B=U1(1), C=ı) @test @inferred(s ⊗ s) == - gradedrange([sector(; A=U1(2), B=ı, C=ı) => 1, sector(; A=U1(2), B=ı, C=τ) => 1]) + gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]) end end @@ -365,14 +365,13 @@ end gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) - @test_broken @inferred(p1h1 ⊗ p1h1) == gradedrange([ - (U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1 - ]) + @test @inferred(p1h1 ⊗ p1h1) == + gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) end @testset "Fusion of fully mixed products" begin s = U1(1) × SU2(1//2) × Ising("σ") - @test_broken @inferred(s ⊗ s) == gradedrange([ + @test @inferred(s ⊗ s) == gradedrange([ (U1(2) × SU2(0) × Ising(1)) => 1, (U1(2) × SU2(1) × Ising(1)) => 1, (U1(2) × SU2(0) × Ising("ψ")) => 1, @@ -382,7 +381,7 @@ end ı = Fib("1") τ = Fib("τ") s = U1(1) × SU2(1//2) × τ - @test_broken @inferred(s ⊗ s) == gradedrange([ + @test @inferred(s ⊗ s) == gradedrange([ (U1(2) × SU2(0) × ı) => 1, (U1(2) × SU2(1) × ı) => 1, (U1(2) × SU2(0) × τ) => 1, From 3ae8e46352408229c21d9b2955283575f28384d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 3 Apr 2024 11:47:12 -0400 Subject: [PATCH 026/194] def and use gradedisequal --- .../src/lib/GradedAxes/src/gradedunitrange.jl | 7 +- .../lib/Sectors/test/test_category_product.jl | 222 ++++++++++-------- .../src/lib/Sectors/test/test_fusion_rules.jl | 62 ++--- 3 files changed, 166 insertions(+), 125 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index 5094497d50..03fdc67ea2 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -8,7 +8,7 @@ using BlockArrays: blockedrange, BlockIndexRange, blockfirsts, - blocklasts, + blockisequal, blocklength, blocklengths, findblock, @@ -45,6 +45,11 @@ end const GradedUnitRange{BlockLasts<:Vector{<:LabelledInteger}} = BlockedUnitRange{BlockLasts} +# == is just a range comparison that ignores labels. Need dedicated function to check equality. +function gradedisequal(a1::AbstractUnitRange, a2::AbstractUnitRange) + return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) +end + # TODO: Use `TypeParameterAccessors`. Base.eltype(::Type{<:GradedUnitRange{<:Vector{T}}}) where {T} = T diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index b885ee0418..c8b17eb7e4 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) using NDTensors.Sectors: ×, ⊗, CategoryProduct, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension -using NDTensors.GradedAxes: dual, gradedrange +using NDTensors.GradedAxes: dual, gradedisequal, gradedrange using Test: @inferred, @test, @testset, @test_broken, @test_throws @testset "Test Named Category Products" begin @@ -94,7 +94,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]) - @test @inferred(quantum_dimension(g)) == 8.0 + @test (@inferred quantum_dimension(g)) == 8.0 g = gradedrange([ sector(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, @@ -107,9 +107,9 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @testset "Empty category" begin s = sector() - @test @inferred(dual(s)) == s - @test @inferred(s × s) == s - @test @inferred(s ⊗ s) == s + @test (@inferred dual(s)) == s + @test (@inferred s × s) == s + @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 0 end @@ -119,19 +119,19 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test @inferred(q10 ⊗ q10) == sector(; A=U1(2)) - @test @inferred(q01 ⊗ q00) == q01 - @test @inferred(q00 ⊗ q01) == q01 - @test @inferred(q10 ⊗ q01) == q11 - @test @inferred(q11 ⊗ q11) == sector(; A=U1(2), B=U1(2)) + @test (@inferred q10 ⊗ q10) == sector(; A=U1(2)) + @test (@inferred q01 ⊗ q00) == q01 + @test (@inferred q00 ⊗ q01) == q01 + @test (@inferred q10 ⊗ q01) == q11 + @test (@inferred q11 ⊗ q11) == sector(; A=U1(2), B=U1(2)) s11 = sector(; A=U1(1), B=Z{2}(1)) s10 = sector(; A=U1(1)) s01 = sector(; B=Z{2}(1)) - @test @inferred(s01 ⊗ q00) == s01 - @test @inferred(q00 ⊗ s01) == s01 - @test @inferred(s10 ⊗ s01) == s11 - @test @inferred(s11 ⊗ s11) == sector(; A=U1(2), B=Z{2}(0)) + @test (@inferred s01 ⊗ q00) == s01 + @test (@inferred q00 ⊗ s01) == s01 + @test (@inferred s10 ⊗ s01) == s11 + @test (@inferred s11 ⊗ s11) == sector(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin @@ -140,44 +140,51 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws phb = sector(; B=SU2(1//2)) phab = sector(; A=SU2(1//2), B=SU2(1//2)) - @test (@inferred pha ⊗ pha) == - gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) - @test (@inferred pha ⊗ p0) == gradedrange([pha => 1]) - @test (@inferred p0 ⊗ phb) == gradedrange([phb => 1]) - @test (@inferred pha ⊗ phb) == gradedrange([phab => 1]) - - @test (@inferred phab ⊗ phab) == gradedrange([ - sector(; A=SU2(0), B=SU2(0)) => 1, - sector(; A=SU2(1), B=SU2(0)) => 1, - sector(; A=SU2(0), B=SU2(1)) => 1, - sector(; A=SU2(1), B=SU2(1)) => 1, - ]) + @test gradedisequal( + (@inferred pha ⊗ pha), gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) + ) + @test gradedisequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) + @test gradedisequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) + @test gradedisequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) + + @test gradedisequal( + (@inferred phab ⊗ phab), + gradedrange([ + sector(; A=SU2(0), B=SU2(0)) => 1, + sector(; A=SU2(1), B=SU2(0)) => 1, + sector(; A=SU2(0), B=SU2(1)) => 1, + sector(; A=SU2(1), B=SU2(1)) => 1, + ]), + ) end @testset "Fusion of NonGroupCategory products" begin ı = Fib("1") τ = Fib("τ") s = sector(; A=ı, B=ı) - @test @inferred(s ⊗ s) == gradedrange([s => 1]) + @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) s = sector(; A=τ, B=τ) - @test @inferred(s ⊗ s) == gradedrange([ - sector(; A=ı, B=ı) => 1, - sector(; A=τ, B=ı) => 1, - sector(; A=ı, B=τ) => 1, - sector(; A=τ, B=τ) => 1, - ]) + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([ + sector(; A=ı, B=ı) => 1, + sector(; A=τ, B=ı) => 1, + sector(; A=ı, B=τ) => 1, + sector(; A=τ, B=τ) => 1, + ]), + ) σ = Ising("σ") ψ = Ising("ψ") - s = τ × σ + s = sector(; A=τ, B=σ) g = gradedrange([ - sector(; A=ı, B=Ising(1)) => 1, - sector(; A=τ, B=Ising(1)) => 1, + sector(; A=ı, B=Ising("1")) => 1, + sector(; A=τ, B=Ising("1")) => 1, sector(; A=ı, B=ψ) => 1, sector(; A=τ, B=ψ) => 1, ]) - @test @inferred(s ⊗ s) == g + @test gradedisequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -191,34 +198,42 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test @inferred(q1h ⊗ q1h) == gradedrange([q20 => 1, q21 => 1]) - @test @inferred(q10 ⊗ q1h) == gradedrange([q2h => 1]) - @test @inferred(q0h ⊗ q1h) == gradedrange([q10 => 1, q11 => 1]) - @test @inferred(q11 ⊗ q11) == gradedrange([q20 => 1, q21 => 1, q22 => 1]) + @test gradedisequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) + @test gradedisequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) + @test gradedisequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) + @test gradedisequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) end @testset "Fusion of fully mixed products" begin s = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) - @test @inferred(s ⊗ s) == gradedrange([ - sector(; A=U1(2), B=SU2(0), C=Ising(1)) => 1, - sector(; A=U1(2), B=SU2(1), C=Ising(1)) => 1, - sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, - sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, - ]) + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([ + sector(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, + sector(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, + sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, + sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, + ]), + ) ı = Fib("1") τ = Fib("τ") s = sector(; A=U1(1), B=SU2(1//2), C=τ) - @test @inferred(s ⊗ s) == gradedrange([ - sector(; A=U1(2), B=SU2(0), C=ı) => 1, - sector(; A=U1(2), B=SU2(1), C=ı) => 1, - sector(; A=U1(2), B=SU2(0), C=τ) => 1, - sector(; A=U1(2), B=SU2(1), C=τ) => 1, - ]) + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([ + sector(; A=U1(2), B=SU2(0), C=ı) => 1, + sector(; A=U1(2), B=SU2(1), C=ı) => 1, + sector(; A=U1(2), B=SU2(0), C=τ) => 1, + sector(; A=U1(2), B=SU2(1), C=τ) => 1, + ]), + ) s = sector(; A=τ, B=U1(1), C=ı) - @test @inferred(s ⊗ s) == - gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]) + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]), + ) end end @@ -284,7 +299,7 @@ end (U1(2) × SU2(0) × Ising("ψ")) => 1, (U1(2) × SU2(1) × Ising("ψ")) => 1, ]) - @test @inferred(quantum_dimension(g)) == 8.0 + @test (@inferred quantum_dimension(g)) == 8.0 g = gradedrange([ (Fib("1") × SU2(0) × U1(2)) => 1, @@ -314,82 +329,101 @@ end @testset "Fusion of Abelian products" begin p11 = U1(1) × U1(1) - @test @inferred(p11 ⊗ p11 == U1(2) × U1(2)) + @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) p123 = U1(1) × U1(2) × U1(3) - @test @inferred(p123 ⊗ p123 == U1(2) × U1(4) × U1(6)) + @test (@inferred p123 ⊗ p123) == U1(2) × U1(4) × U1(6) s1 = sector(U1(1), Z{2}(1)) s2 = sector(U1(0), Z{2}(0)) - @test @inferred(s1 ⊗ s2 == U1(1) × Z{2}(1)) + @test (@inferred s1 ⊗ s2) == U1(1) × Z{2}(1) end @testset "Fusion of NonAbelian products" begin phh = SU2(1//2) × SU2(1//2) - @test phh ⊗ phh == gradedrange([ - (SU2(0) × SU2(0)) => 1, - (SU2(1) × SU2(0)) => 1, - (SU2(0) × SU2(1)) => 1, - (SU2(1) × SU2(1)) => 1, - ]) - @test (@inferred phh ⊗ phh == gradedrange([ - (SU2(0) × SU2(0)) => 1, - (SU2(1) × SU2(0)) => 1, - (SU2(0) × SU2(1)) => 1, - (SU2(1) × SU2(1)) => 1, - ])) + @test gradedisequal( + phh ⊗ phh, + gradedrange([ + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, + ]), + ) + @test gradedisequal( + (@inferred phh ⊗ phh), + gradedrange([ + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, + ]), + ) end @testset "Fusion of NonGroupCategory products" begin ı = Fib("1") τ = Fib("τ") s = ı × ı - @test @inferred(s ⊗ s == gradedrange([s => 1])) + @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) s = τ × τ - @test @inferred( - s ⊗ s == gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]) + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]), ) σ = Ising("σ") ψ = Ising("ψ") s = τ × σ - g = gradedrange([(ı × Ising(1)) => 1, (τ × Ising(1)) => 1, (ı × ψ) => 1, (τ × ψ) => 1]) - @test @inferred(s ⊗ s) == g + g = gradedrange([ + (ı × Ising("1")) => 1, (τ × Ising("1")) => 1, (ı × ψ) => 1, (τ × ψ) => 1 + ]) + @test gradedisequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) - @test @inferred(p2h ⊗ p1h) == - gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) + @test gradedisequal( + (@inferred p2h ⊗ p1h), gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) + ) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) - @test @inferred(p1h1 ⊗ p1h1) == - gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) + @test gradedisequal( + (@inferred p1h1 ⊗ p1h1), + gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]), + ) end @testset "Fusion of fully mixed products" begin s = U1(1) × SU2(1//2) × Ising("σ") - @test @inferred(s ⊗ s) == gradedrange([ - (U1(2) × SU2(0) × Ising(1)) => 1, - (U1(2) × SU2(1) × Ising(1)) => 1, - (U1(2) × SU2(0) × Ising("ψ")) => 1, - (U1(2) × SU2(1) × Ising("ψ")) => 1, - ]) - + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([ + (U1(2) × SU2(0) × Ising("1")) => 1, + (U1(2) × SU2(1) × Ising("1")) => 1, + (U1(2) × SU2(0) × Ising("ψ")) => 1, + (U1(2) × SU2(1) × Ising("ψ")) => 1, + ]), + ) ı = Fib("1") τ = Fib("τ") s = U1(1) × SU2(1//2) × τ - @test @inferred(s ⊗ s) == gradedrange([ - (U1(2) × SU2(0) × ı) => 1, - (U1(2) × SU2(1) × ı) => 1, - (U1(2) × SU2(0) × τ) => 1, - (U1(2) × SU2(1) × τ) => 1, - ]) + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([ + (U1(2) × SU2(0) × ı) => 1, + (U1(2) × SU2(1) × ı) => 1, + (U1(2) × SU2(0) × τ) => 1, + (U1(2) × SU2(1) × τ) => 1, + ]), + ) s = U1(1) × ı × τ - @test @inferred(s ⊗ s) == gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) + @test gradedisequal( + (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) + ) end end end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index e7c389229b..bafa56c983 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,5 +1,5 @@ @eval module $(gensym()) -using NDTensors.GradedAxes: fuse_labels, gradedrange, tensor_product +using NDTensors.GradedAxes: fuse_labels, gradedisequal, gradedrange, tensor_product using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension using Test: @inferred, @test, @testset, @test_throws @@ -34,11 +34,11 @@ using Test: @inferred, @test, @testset, @test_throws j4 = SU2(3//2) j5 = SU2(2) - @test j1 ⊗ j2 == gradedrange([j2 => 1]) - @test j2 ⊗ j2 == gradedrange([j1 => 1, j3 => 1]) - @test j2 ⊗ j3 == gradedrange([j2 => 1, j4 => 1]) - @test j3 ⊗ j3 == gradedrange([j1 => 1, j3 => 1, j5 => 1]) - @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) + @test gradedisequal(j1 ⊗ j2, gradedrange([j2 => 1])) + @test gradedisequal(j2 ⊗ j2, gradedrange([j1 => 1, j3 => 1])) + @test gradedisequal(j2 ⊗ j3, gradedrange([j2 => 1, j4 => 1])) + @test gradedisequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) + @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 end @@ -49,21 +49,21 @@ using Test: @inferred, @test, @testset, @test_throws j4 = SU{2}(4) j5 = SU{2}(5) - @test j1 ⊗ j2 == gradedrange([j2 => 1]) - @test j2 ⊗ j2 == gradedrange([j1 => 1, j3 => 1]) - @test j2 ⊗ j3 == gradedrange([j2 => 1, j4 => 1]) - @test j3 ⊗ j3 == gradedrange([j1 => 1, j3 => 1, j5 => 1]) - @test (@inferred j1 ⊗ j2) == gradedrange([j2 => 1]) + @test gradedisequal(j1 ⊗ j2, gradedrange([j2 => 1])) + @test gradedisequal(j2 ⊗ j2, gradedrange([j1 => 1, j3 => 1])) + @test gradedisequal(j2 ⊗ j3, gradedrange([j2 => 1, j4 => 1])) + @test gradedisequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) + @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) end @testset "Fibonacci fusion rules" begin ı = Fib("1") τ = Fib("τ") - @test ı ⊗ ı == gradedrange([ı => 1]) - @test ı ⊗ τ == gradedrange([τ => 1]) - @test τ ⊗ ı == gradedrange([τ => 1]) - @test (@inferred τ ⊗ τ) == gradedrange([ı => 1, τ => 1]) + @test gradedisequal(ı ⊗ ı, gradedrange([ı => 1])) + @test gradedisequal(ı ⊗ τ, gradedrange([τ => 1])) + @test gradedisequal(τ ⊗ ı, gradedrange([τ => 1])) + @test gradedisequal((@inferred τ ⊗ τ), gradedrange([ı => 1, τ => 1])) @test (@inferred quantum_dimension(gradedrange([ı => 1, ı => 1]))) == 2.0 end @@ -72,16 +72,16 @@ using Test: @inferred, @test, @testset, @test_throws σ = Ising("σ") ψ = Ising("ψ") - @test ı ⊗ ı == gradedrange([ı => 1]) - @test ı ⊗ σ == gradedrange([σ => 1]) - @test σ ⊗ ı == gradedrange([σ => 1]) - @test ı ⊗ ψ == gradedrange([ψ => 1]) - @test ψ ⊗ ı == gradedrange([ψ => 1]) - @test σ ⊗ σ == gradedrange([ı => 1, ψ => 1]) - @test σ ⊗ ψ == gradedrange([σ => 1]) - @test ψ ⊗ σ == gradedrange([σ => 1]) - @test ψ ⊗ ψ == gradedrange([ı => 1]) - @test (@inferred ψ ⊗ ψ) == gradedrange([ı => 1]) + @test gradedisequal(ı ⊗ ı, gradedrange([ı => 1])) + @test gradedisequal(ı ⊗ σ, gradedrange([σ => 1])) + @test gradedisequal(σ ⊗ ı, gradedrange([σ => 1])) + @test gradedisequal(ı ⊗ ψ, gradedrange([ψ => 1])) + @test gradedisequal(ψ ⊗ ı, gradedrange([ψ => 1])) + @test gradedisequal(σ ⊗ σ, gradedrange([ı => 1, ψ => 1])) + @test gradedisequal(σ ⊗ ψ, gradedrange([σ => 1])) + @test gradedisequal(ψ ⊗ σ, gradedrange([σ => 1])) + @test gradedisequal(ψ ⊗ ψ, gradedrange([ı => 1])) + @test gradedisequal((@inferred ψ ⊗ ψ), gradedrange([ı => 1])) @test (@inferred quantum_dimension(σ ⊗ σ)) == 2.0 end end @@ -89,14 +89,16 @@ end @testset "GradedUnitRange fusion rules" begin g1 = gradedrange([U1(1) => 1, U1(2) => 2]) g2 = gradedrange([U1(-1) => 2, U1(0) => 1, U1(1) => 2]) - @test (@inferred tensor_product(g1, g2)) == - gradedrange([U1(0) => 2, U1(1) => 5, U1(2) => 4, U1(3) => 4]) + @test gradedisequal( + (@inferred tensor_product(g1, g2)), + gradedrange([U1(0) => 2, U1(1) => 5, U1(2) => 4, U1(3) => 4]), + ) g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2, SU2(1) => 1]) g4 = gradedrange([SU2(1//2) => 1, SU2(1) => 2]) - @test @inferred( - tensor_product(g3, g4) == - gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]) + @test gradedisequal( + (@inferred tensor_product(g3, g4)), + gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), ) # test different categories cannot be fused From fc9623832e119b7185824a33c87bd32aec75aae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 3 Apr 2024 12:28:56 -0400 Subject: [PATCH 027/194] test mixed GradedUnitRange - Category --- .../src/lib/Sectors/src/abstractcategory.jl | 4 ++-- .../src/lib/Sectors/test/test_fusion_rules.jl | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index bda68dd0e0..69b05e809d 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -44,7 +44,6 @@ function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) return error("`label_fusion_rule` not defined for type $(category_type).") end -# TBD always return GradedUnitRange? function fusion_rule(c1::C, c2::C) where {C<:AbstractCategory} out = label_fusion_rule(C, label(c1), label(c2)) if SymmetryStyle(c1) == AbelianGroup() @@ -82,6 +81,7 @@ function GradedAxes.tensor_product( end # 2. make GradedAxes.fuse_labels return fusion_rule +# TBD return GradedAxis for AbelianGroup too? GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) = c1 ⊗ c2 # 3. promote Category to GradedAxes @@ -91,7 +91,7 @@ function fusion_rule(c::AbstractCategory, r::AbstractUnitRange) end function fusion_rule(r::AbstractUnitRange, c::AbstractCategory) - return fusion_rule(GradedAxes.gradedrange(r, [c => 1])) + return fusion_rule(r, GradedAxes.gradedrange([c => 1])) end # 4. define fusion rule for reducible representations diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index bafa56c983..031be327b5 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -104,5 +104,21 @@ end # test different categories cannot be fused @test_throws MethodError tensor_product(g1, g4) end + + @testset "Mixed GradedUnitRange - Category fusion rules" begin + g1 = gradedrange([U1(1) => 1, U1(2) => 2]) + g2 = gradedrange([U1(2) => 1, U1(3) => 2]) + @test gradedisequal((@inferred tensor_product(g1, U1(1))), g2) + @test gradedisequal((@inferred tensor_product(U1(1), g1)), g2) + + g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2]) + g4 = gradedrange([SU2(0) => 2, SU2(1//2) => 1, SU2(1) => 2]) + @test gradedisequal((@inferred tensor_product(g3, SU2(1//2))), g4) + @test gradedisequal((@inferred tensor_product(SU2(1//2), g3)), g4) + + # test different categories cannot be fused + @test_throws MethodError tensor_product(g1, SU2(1)) + @test_throws MethodError tensor_product(U1(1), g3) + end end end From b71f9b2d5939e1078fe3929679292f969b4606a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 3 Apr 2024 14:37:14 -0400 Subject: [PATCH 028/194] delete files not supposed to be under git --- NDTensors/src/lib/Sectors/src/filter.jl | 36 ------ NDTensors/src/lib/Sectors/src/g_fib.html | 134 --------------------- NDTensors/src/lib/Sectors/src/g_ising.html | 134 --------------------- 3 files changed, 304 deletions(-) delete mode 100644 NDTensors/src/lib/Sectors/src/filter.jl delete mode 100644 NDTensors/src/lib/Sectors/src/g_fib.html delete mode 100644 NDTensors/src/lib/Sectors/src/g_ising.html diff --git a/NDTensors/src/lib/Sectors/src/filter.jl b/NDTensors/src/lib/Sectors/src/filter.jl deleted file mode 100644 index 41ea93d415..0000000000 --- a/NDTensors/src/lib/Sectors/src/filter.jl +++ /dev/null @@ -1,36 +0,0 @@ -# https://discourse.julialang.org/t/compile-time-type-filtering-from-a-tuple-is-it-possible/101090/2 - -# Length zero -filtered(::Type{C}, ::Tuple{}) where {C} = () - -# Length one -filtered(::Type{C}, cats::T) where {C,T<:Tuple{C}} = cats -filtered(::Type{C}, cats::T) where {C,T<:Tuple{Any}} = () - -# Length two or more -function filtered(::Type{C}, cats::T) where {C,T<:Tuple{Any,Any,Vararg{Any}}} - return (filtered(C, (first(cats),))..., filtered(C, Base.tail(cats))...) -end - -sieve(c::@NamedTuple) = (cats=categories(c)filtered(B, cats), filtered(Consonant, cats)) - -function f( - cats1::NT, cats2::NT -) where {NT<:NamedTuple{<:Any,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} - return println(NT) -end - -function gg( - cats1::NT, cats2::NT -) where {NT<:NamedTuple{<:Any,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} - k = first(keys(cats1)) - return cats1[k] -end - -function hh( - cats1::NT, cats2::NT -) where {Names,NT<:NamedTuple{Names,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} - println(typeof(cats1), " ", Names) - return fusion_rule(cats1[(Names[1],)], cats2[(Names[1],)]), - (cats1[(Names[2:end],)], cats2[(Names[2:end],)]) -end diff --git a/NDTensors/src/lib/Sectors/src/g_fib.html b/NDTensors/src/lib/Sectors/src/g_fib.html deleted file mode 100644 index 7c127d780c..0000000000 --- a/NDTensors/src/lib/Sectors/src/g_fib.html +++ /dev/null @@ -1,134 +0,0 @@ -
═════ 27 possible errors found ═════
-quantum_dimension(g::BlockArrays.BlockedUnitRange{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:34
-sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:564
-sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:564
-sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:535
-sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:535
-mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:307
-mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:44
-foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:48
-_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:58
-(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ Base ./reduce.jl:100
-(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ NDTensors.Sectors ./none:0
-quantum_dimension(c::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
-quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:37
-prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
-prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
-mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
-mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; kw::Base.Pairs) @ Base ./reduce.jl:307
-merge(a::@NamedTuple{}, itr::Base.Pairs) @ Base ./namedtuple.jl:364
-iterate(::Base.Pairs) @ Base.Iterators ./iterators.jl:301
-_pairs_elt(p::Base.Pairs, idx::Any) @ Base.Iterators ./iterators.jl:294
-│ runtime dispatch detected: (%4::Any)[idx::Any]::Any
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
-foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
-_foldl_impl(op::Base.BottomRF, init::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
-afoldl(::Base.BottomRF, ::Base._InitialValue, ::Float64, ::Float64) @ Base ./operators.jl:545
-(::Base.BottomRF)(acc::Float64, x::Float64) @ Base ./reduce.jl:86
-│ runtime dispatch detected: %1::Any(acc::Float64, x::Float64)::Any
-└────────────────────
-foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:49
-reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:383
-reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}, ::Base.HasEltype) @ Base ./reduce.jl:384
-│ runtime dispatch detected: Base.reduce_empty(op::Base.BottomRF, ::Float64)::Any
-└────────────────────
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:42
-│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.mul_prod), ::Base._InitialValue, ::Tuple{Float64, Float64})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
-└────────────────────
-kwcall(::NamedTuple, ::typeof(mapfoldl), f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
-pairs(nt::NamedTuple) @ Base.Iterators ./iterators.jl:279
-(Base.Pairs{Symbol})(data::NamedTuple, itr::Tuple{Vararg{Symbol}}) @ Base ./essentials.jl:343
-eltype(::Type{A} where A<:NamedTuple) @ Base ./namedtuple.jl:237
-nteltype(::Type{NamedTuple{names, T}} where names) where T<:Tuple @ Base ./namedtuple.jl:239
-eltype(t::Type{<:Tuple{Vararg{E}}}) where E @ Base ./tuple.jl:208
-_compute_eltype(t::Type{<:Tuple{Vararg{E}}} where E) @ Base ./tuple.jl:231
-afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:544
-(::Base.var"#54#55")(a::Any, b::Any) @ Base ./tuple.jl:235
-promote_typejoin(a::Any, b::Any) @ Base ./promotion.jl:172
-typejoin(a::Any, b::Any) @ Base ./promotion.jl:127
-│ runtime dispatch detected: Base.UnionAll(%403::Any, %405::Any)::Any
-└────────────────────
-afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:545
-(::Base.var"#54#55")(a::Type, b::Any) @ Base ./tuple.jl:235
-promote_typejoin(a::Type, b::Any) @ Base ./promotion.jl:172
-typejoin(a::Type, b::Any) @ Base ./promotion.jl:127
-│ runtime dispatch detected: Base.UnionAll(%398::Any, %400::Any)::Any
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Any) @ Base ./reduce.jl:175
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
-foldl_impl(op::Base.BottomRF, nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
-_foldl_impl(op::Base.BottomRF, init::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
-afoldl(::Base.BottomRF, ::Any, ::Float64, ::Float64) @ Base ./operators.jl:544
-(::Base.BottomRF)(acc::Any, x::Float64) @ Base ./reduce.jl:86
-│ runtime dispatch detected: %1::Any(acc::Any, x::Float64)::Any
-└────────────────────
-mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
-│ failed to optimize due to recursion: mapreduce(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
-└────────────────────
-prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
-│ failed to optimize due to recursion: Base.var"#prod#309"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(prod), ::Tuple{Float64, Float64})
-└────────────────────
-prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
-│ failed to optimize due to recursion: prod(::Tuple{Float64, Float64})
-└────────────────────
-quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:36
-│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::NDTensors.Sectors.NonGroupCategory, ::CategoryProduct{Tuple{Fib, Fib}})
-└────────────────────
-quantum_dimension(c::CategoryProduct{Tuple{Fib, Fib}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
-│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::CategoryProduct{Tuple{Fib, Fib}})
-└────────────────────
-(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ NDTensors.Sectors ./none:0
-│ failed to optimize due to recursion: (::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64})
-└────────────────────
-(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
-reduce_first(::typeof(Base.add_sum), x::Any) @ Base ./reduce.jl:407
-reduce_first(::typeof(+), x::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/fillalgebra.jl:396
-getindex_value(Z::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:321
-│ runtime dispatch detected: FillArrays.one(%1::Any)::Any
-└────────────────────
-axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
-│ runtime dispatch detected: size(A::FillArrays.AbstractOnes)::Any
-└────────────────────
-axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
-│ runtime dispatch detected: map(Base.oneto, %1::Any)::Any
-└────────────────────
-FillArrays.Fill(x::T, sz::Tuple{Vararg{Any, N}}) where {T, N} @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:133
-│ runtime dispatch detected: %3::Type{FillArrays.Fill{_A, _B}} where {_A, _B}(x::Any, sz::Tuple{Vararg{Any, N}} where N)::Any
-└────────────────────
-(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
-│ runtime dispatch detected: Base.reduce_first(add_sum, x::Any)::Any
-└────────────────────
-(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64}) @ Base ./reduce.jl:100
-│ failed to optimize due to recursion: (::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(::Base._InitialValue, ::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}, Float64})
-└────────────────────
-_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:53
-│ failed to optimize due to recursion: Base._foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}})
-└────────────────────
-foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}) @ Base ./reduce.jl:47
-│ failed to optimize due to recursion: Base.foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}})
-└────────────────────
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:42
-│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.add_sum), ::Base._InitialValue, ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
-│ failed to optimize due to recursion: Base.var"#mapreduce#302"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(mapreduce), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Fib, Fib}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-
diff --git a/NDTensors/src/lib/Sectors/src/g_ising.html b/NDTensors/src/lib/Sectors/src/g_ising.html deleted file mode 100644 index 664dcedc4a..0000000000 --- a/NDTensors/src/lib/Sectors/src/g_ising.html +++ /dev/null @@ -1,134 +0,0 @@ -
═════ 27 possible errors found ═════
-quantum_dimension(g::BlockArrays.BlockedUnitRange{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:34
-sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:564
-sum(a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:564
-sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:535
-sum(f::typeof(identity), a::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:535
-mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:307
-mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:44
-foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:48
-_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:58
-(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ Base ./reduce.jl:100
-(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ NDTensors.Sectors ./none:0
-quantum_dimension(c::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
-quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:37
-prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
-prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
-mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
-mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; kw::Base.Pairs) @ Base ./reduce.jl:307
-merge(a::@NamedTuple{}, itr::Base.Pairs) @ Base ./namedtuple.jl:364
-iterate(::Base.Pairs) @ Base.Iterators ./iterators.jl:301
-_pairs_elt(p::Base.Pairs, idx::Any) @ Base.Iterators ./iterators.jl:294
-│ runtime dispatch detected: (%4::Any)[idx::Any]::Any
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
-foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
-_foldl_impl(op::Base.BottomRF, init::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
-afoldl(::Base.BottomRF, ::Base._InitialValue, ::Float64, ::Float64) @ Base ./operators.jl:545
-(::Base.BottomRF)(acc::Float64, x::Float64) @ Base ./reduce.jl:86
-│ runtime dispatch detected: %1::Any(acc::Float64, x::Float64)::Any
-└────────────────────
-foldl_impl(op::Base.BottomRF, nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:49
-reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:383
-reduce_empty_iter(op::Base.BottomRF, itr::Tuple{Float64, Float64}, ::Base.HasEltype) @ Base ./reduce.jl:384
-│ runtime dispatch detected: Base.reduce_empty(op::Base.BottomRF, ::Float64)::Any
-└────────────────────
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Base._InitialValue, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:42
-│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.mul_prod), ::Base._InitialValue, ::Tuple{Float64, Float64})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Base._InitialValue) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
-└────────────────────
-kwcall(::NamedTuple, ::typeof(mapfoldl), f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:175
-pairs(nt::NamedTuple) @ Base.Iterators ./iterators.jl:279
-(Base.Pairs{Symbol})(data::NamedTuple, itr::Tuple{Vararg{Symbol}}) @ Base ./essentials.jl:343
-eltype(::Type{A} where A<:NamedTuple) @ Base ./namedtuple.jl:237
-nteltype(::Type{NamedTuple{names, T}} where names) where T<:Tuple @ Base ./namedtuple.jl:239
-eltype(t::Type{<:Tuple{Vararg{E}}}) where E @ Base ./tuple.jl:208
-_compute_eltype(t::Type{<:Tuple{Vararg{E}}} where E) @ Base ./tuple.jl:231
-afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:544
-(::Base.var"#54#55")(a::Any, b::Any) @ Base ./tuple.jl:235
-promote_typejoin(a::Any, b::Any) @ Base ./promotion.jl:172
-typejoin(a::Any, b::Any) @ Base ./promotion.jl:127
-│ runtime dispatch detected: Base.UnionAll(%403::Any, %405::Any)::Any
-└────────────────────
-afoldl(op::Base.var"#54#55", a::Any, bs::Vararg{Any}) @ Base ./operators.jl:545
-(::Base.var"#54#55")(a::Type, b::Any) @ Base ./tuple.jl:235
-promote_typejoin(a::Type, b::Any) @ Base ./promotion.jl:172
-typejoin(a::Type, b::Any) @ Base ./promotion.jl:127
-│ runtime dispatch detected: Base.UnionAll(%398::Any, %400::Any)::Any
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}; init::Any) @ Base ./reduce.jl:175
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.mul_prod), nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:44
-foldl_impl(op::Base.BottomRF, nt::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:48
-_foldl_impl(op::Base.BottomRF, init::Any, itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:68
-afoldl(::Base.BottomRF, ::Any, ::Float64, ::Float64) @ Base ./operators.jl:544
-(::Base.BottomRF)(acc::Any, x::Float64) @ Base ./reduce.jl:86
-│ runtime dispatch detected: %1::Any(acc::Any, x::Float64)::Any
-└────────────────────
-mapreduce(f::typeof(identity), op::typeof(Base.mul_prod), itr::Tuple{Float64, Float64}) @ Base ./reduce.jl:307
-│ failed to optimize due to recursion: mapreduce(::typeof(identity), ::typeof(Base.mul_prod), ::Tuple{Float64, Float64})
-└────────────────────
-prod(a::Tuple{Float64, Float64}; kw::@Kwargs{}) @ Base ./reduce.jl:620
-│ failed to optimize due to recursion: Base.var"#prod#309"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(prod), ::Tuple{Float64, Float64})
-└────────────────────
-prod(a::Tuple{Float64, Float64}) @ Base ./reduce.jl:620
-│ failed to optimize due to recursion: prod(::Tuple{Float64, Float64})
-└────────────────────
-quantum_dimension(::NDTensors.Sectors.NonGroupCategory, s::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/category_product.jl:36
-│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::NDTensors.Sectors.NonGroupCategory, ::CategoryProduct{Tuple{Ising, Ising}})
-└────────────────────
-quantum_dimension(c::CategoryProduct{Tuple{Ising, Ising}}) @ NDTensors.Sectors /mnt/home/ogauthe/Documents/itensor/ITensors.jl/NDTensors/src/lib/Sectors/src/symmetry_style.jl:16
-│ failed to optimize due to recursion: NDTensors.Sectors.quantum_dimension(::CategoryProduct{Tuple{Ising, Ising}})
-└────────────────────
-(::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ NDTensors.Sectors ./none:0
-│ failed to optimize due to recursion: (::NDTensors.Sectors.var"#2#4")(::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64})
-└────────────────────
-(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
-reduce_first(::typeof(Base.add_sum), x::Any) @ Base ./reduce.jl:407
-reduce_first(::typeof(+), x::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/fillalgebra.jl:396
-getindex_value(Z::FillArrays.AbstractOnes) @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:321
-│ runtime dispatch detected: FillArrays.one(%1::Any)::Any
-└────────────────────
-axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
-│ runtime dispatch detected: size(A::FillArrays.AbstractOnes)::Any
-└────────────────────
-axes(A::FillArrays.AbstractOnes) @ Base ./abstractarray.jl:98
-│ runtime dispatch detected: map(Base.oneto, %1::Any)::Any
-└────────────────────
-FillArrays.Fill(x::T, sz::Tuple{Vararg{Any, N}}) where {T, N} @ FillArrays /mnt/home/ogauthe/.julia/packages/FillArrays/oXkMk/src/FillArrays.jl:133
-│ runtime dispatch detected: %3::Type{FillArrays.Fill{_A, _B}} where {_A, _B}(x::Any, sz::Tuple{Vararg{Any, N}} where N)::Any
-└────────────────────
-(::Base.BottomRF{typeof(Base.add_sum)})(::Base._InitialValue, x::Any) @ Base ./reduce.jl:85
-│ runtime dispatch detected: Base.reduce_first(add_sum, x::Any)::Any
-└────────────────────
-(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(acc::Base._InitialValue, x::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64}) @ Base ./reduce.jl:100
-│ failed to optimize due to recursion: (::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}})(::Base._InitialValue, ::Tuple{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}, Float64})
-└────────────────────
-_foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, init::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:53
-│ failed to optimize due to recursion: Base._foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}})
-└────────────────────
-foldl_impl(op::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, nt::Base._InitialValue, itr::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}) @ Base ./reduce.jl:47
-│ failed to optimize due to recursion: Base.foldl_impl(::Base.MappingRF{NDTensors.Sectors.var"#2#4", Base.BottomRF{typeof(Base.add_sum)}}, ::Base._InitialValue, ::Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}})
-└────────────────────
-mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:42
-│ failed to optimize due to recursion: Base.mapfoldl_impl(::typeof(identity), ::typeof(Base.add_sum), ::Base._InitialValue, ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; init::Base._InitialValue) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: Base.var"#mapfoldl#298"(::Base._InitialValue, ::typeof(mapfoldl), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}) @ Base ./reduce.jl:175
-│ failed to optimize due to recursion: mapfoldl(::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"}; kw::@Kwargs{}) @ Base ./reduce.jl:307
-│ failed to optimize due to recursion: Base.var"#mapreduce#302"(::Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, ::typeof(mapreduce), ::typeof(identity), ::typeof(Base.add_sum), ::Base.Generator{Base.Iterators.ProductIterator{Tuple{Vector{NDTensors.LabelledNumbers.LabelledInteger{Int64, CategoryProduct{Tuple{Ising, Ising}}}}, Float64}}, NDTensors.Sectors.var"#2#4"})
-└────────────────────
-
From 56de936d87033ee3d52d48dce1a45d2081dd003b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 4 Apr 2024 14:29:40 -0400 Subject: [PATCH 029/194] dispatch instead of test type --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 4 + .../src/lib/Sectors/src/abstractcategory.jl | 124 +++++++----------- .../src/lib/Sectors/src/category_product.jl | 124 ++++++++---------- .../lib/Sectors/test/test_category_product.jl | 25 ++-- .../src/lib/Sectors/test/test_fusion_rules.jl | 24 ++-- 5 files changed, 134 insertions(+), 167 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index fce6a85072..bd763672b1 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -18,6 +18,10 @@ function tensor_product( return foldl(tensor_product, (a1, a2, a3, a_rest...)) end +function fusion_product() + return error("Not implemented") +end + function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) return error("Not implemented yet.") end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 69b05e809d..2d764295e7 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -20,113 +20,79 @@ function GradedAxes.dual(category_type::Type{<:AbstractCategory}) return error("`dual` not defined for type $(category_type).") end -quantum_dimension(c::AbstractCategory) = quantum_dimension(SymmetryStyle(c), c) +quantum_dimension(x) = quantum_dimension(SymmetryStyle(x), x) function quantum_dimension(::SymmetryStyle, c::AbstractCategory) return error("method `quantum_dimension` not defined for type $(typeof(c))") end -function quantum_dimension(g::AbstractUnitRange) - if SymmetryStyle(g) == AbelianGroup() - return length(g) - end +quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 +quantum_dimension(::EmptyCategory, ::AbstractCategory) = 0 +function quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) mult = LabelledNumbers.unlabel.(BlockArrays.blocklengths(g)) dims = quantum_dimension.(LabelledNumbers.label.(BlockArrays.blocklengths(g))) return sum(m * d for (m, d) in zip(mult, dims)) end -quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 -quantum_dimension(::EmptyCategory, ::AbstractCategory) = 0 +quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) # ================ fusion rule interface ==================== -function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) - return error("`label_fusion_rule` not defined for type $(category_type).") -end +⊗(c1::AbstractCategory, c2::AbstractCategory) = fusion_rule(c1, c2) function fusion_rule(c1::C, c2::C) where {C<:AbstractCategory} - out = label_fusion_rule(C, label(c1), label(c2)) - if SymmetryStyle(c1) == AbelianGroup() - return C(out) # AbelianGroup: return Category - end - degen, labels = out - # NonAbelianGroup or NonGroupCategory: return GradedUnitRange - return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) -end - -function ⊗(c1::AbstractCategory, c2::AbstractCategory) - return fusion_rule(c1, c2) -end - -# ============= fusion rule and gradedunitrange =================== -# TBD define ⊗(c, g2), ⊗(g1, c), ⊗(g1, g2)? - -# 1. make GradedAxes.tensor_product return fusion_rule -function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) - return fusion_rule(c1, c2) + return fusion_rule(SymmetryStyle(c1), c1, c2) end -function GradedAxes.tensor_product(r::AbstractUnitRange, c::AbstractCategory) - return fusion_rule(r, c) +function fusion_rule(::SymmetryStyle, c1::C, c2::C) where {C<:AbstractCategory} + degen, labels = label_fusion_rule(C, label(c1), label(c2)) + return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) end -function GradedAxes.tensor_product(c::AbstractCategory, r::AbstractUnitRange) - return fusion_rule(c, r) +function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} + return C(label_fusion_rule(C, label(c1), label(c2))) # return AbelianGroup end -function GradedAxes.tensor_product( - g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange -) - return fusion_rule(g1, g2) +function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) + return error("`label_fusion_rule` not defined for type $(category_type).") end -# 2. make GradedAxes.fuse_labels return fusion_rule -# TBD return GradedAxis for AbelianGroup too? -GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) = c1 ⊗ c2 - -# 3. promote Category to GradedAxes -# TODO define promote_rule -function fusion_rule(c::AbstractCategory, r::AbstractUnitRange) - return fusion_rule(GradedAxes.gradedrange([c => 1]), r) -end +# ============= fusion rule and gradedunitrange =================== +to_graded_axis(c::AbstractCategory) = GradedAxes.gradedrange([c => 1]) +to_graded_axis(g::AbstractUnitRange) = g -function fusion_rule(r::AbstractUnitRange, c::AbstractCategory) - return fusion_rule(r, GradedAxes.gradedrange([c => 1])) +function GradedAxes.fusion_product(a, b) + return GradedAxes.fusion_product(to_graded_axis(a), to_graded_axis(b)) end -# 4. define fusion rule for reducible representations # TODO deal with dual -function fusion_rule(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange) - blocks3 = empty(BlockArrays.blocklengths(g1)) - for b1 in BlockArrays.blocklengths(g1) - cat1 = LabelledNumbers.label(b1) - degen1 = LabelledNumbers.unlabel(b1) - for b2 in BlockArrays.blocklengths(g2) - cat2 = LabelledNumbers.label(b2) - degen2 = LabelledNumbers.unlabel(b2) +function GradedAxes.fusion_product(g1::AbstractUnitRange, g2::AbstractUnitRange) + blocks2 = BlockArrays.blocklengths(g2) + blocks3 = empty(blocks2) + sym = SymmetryStyle(g2) + for b2 in blocks2 + c2 = LabelledNumbers.label(b2) + degen2 = LabelledNumbers.unlabel(b2) + for b1 in BlockArrays.blocklengths(g1) + c1 = LabelledNumbers.label(b1) + degen1 = LabelledNumbers.unlabel(b1) degen3 = degen1 * degen2 - fuse12 = cat1 ⊗ cat2 - if SymmetryStyle(fuse12) == AbelianGroup() - push!(blocks3, LabelledNumbers.LabelledInteger(degen3, fuse12)) - else - g12 = BlockArrays.blocklengths(fuse12) - # Int * LabelledInteger -> Int, need to recast explicitly - scaled_g12 = - LabelledNumbers.LabelledInteger.(degen3 .* g12, LabelledNumbers.label.(g12)) - append!(blocks3, scaled_g12) - end + _append_fusion!(blocks3, sym, degen3, c1, c2) end end - # sort and fuse blocks carrying the same category label - # there is probably a better way to do this - unsorted_g3 = GradedAxes.gradedrange(blocks3) - perm = GradedAxes.blockmergesortperm(unsorted_g3) - vec3 = empty(blocks3) - for b in BlockArrays.blocks(perm) - x = unsorted_g3[b] - n = LabelledNumbers.LabelledInteger(sum(length(x); init=0), LabelledNumbers.label(x[1])) - push!(vec3, n) - end - g3 = GradedAxes.gradedrange(vec3) - return g3 + la3 = LabelledNumbers.label.(blocks3) + pairs3 = [r => sum(blocks3[findall(==(r), la3)]; init=0) for r in sort(unique(la3))] + return GradedAxes.gradedrange(pairs3) +end + +function _append_fusion!(blocks3, ::AbelianGroup, degen3, c1::C, c2::C) where {C} + return push!(blocks3, LabelledNumbers.LabelledInteger(degen3, fusion_rule(c1, c2))) +end +function _append_fusion!(blocks3, ::SymmetryStyle, degen3, c1::C, c2::C) where {C} + fused_blocks = BlockArrays.blocklengths(fusion_rule(c1, c2)) + g12 = + LabelledNumbers.LabelledInteger.( + degen3 * LabelledNumbers.unlabel.(fused_blocks), LabelledNumbers.label.(fused_blocks) + ) + return append!(blocks3, g12) end diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 49bcbdc0a6..b5ce67edc5 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -19,21 +19,16 @@ combine_styles(::NonAbelianGroup, ::AbelianGroup) = NonAbelianGroup() combine_styles(::NonAbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() combine_styles(::NonAbelianGroup, ::NonGroupCategory) = NonGroupCategory() combine_styles(::NonGroupCategory, ::SymmetryStyle) = NonGroupCategory() +combine_styles(::EmptyCategory, s::SymmetryStyle) = s +combine_styles(s::SymmetryStyle, ::EmptyCategory) = s +combine_styles(::EmptyCategory, ::EmptyCategory) = EmptyCategory() function SymmetryStyle(c::CategoryProduct) - return if length(categories(c)) == 0 - EmptyCategory() - else - reduce(combine_styles, map(SymmetryStyle, (categories(c)))) - end + return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategory()) end function SymmetryStyle(nt::NamedTuple) - return if length(nt) == 0 - EmptyCategory() - else - reduce(combine_styles, map(SymmetryStyle, (values(nt)))) - end + return reduce(combine_styles, map(SymmetryStyle, values(nt)); init=EmptyCategory()) end # ============== Sector interface ================= @@ -74,9 +69,12 @@ function ×(p1::CategoryProduct, p2::CategoryProduct) return CategoryProduct(categories_product(categories(p1), categories(p2))) end -# currently (A=U1(1),) × (A=U1(2),) = sector((A=U1(1),)) -# this is misleading. TBD throw in this case? -categories_product(l1::NamedTuple, l2::NamedTuple) = union_keys(l1, l2) +function categories_product(l1::NamedTuple, l2::NamedTuple) + if length(intersect_keys(l1, l2)) > 0 + throw(MethodError("Cannot define product of shared keys")) + end + return union_keys(l1, l2) +end # edge cases categories_product(l1::NamedTuple, l2::Tuple{}) = l1 @@ -84,6 +82,8 @@ categories_product(l1::Tuple{}, l2::NamedTuple) = l2 categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) +×(a, g::AbstractUnitRange) = ×(to_graded_axis(a), g) +×(g::AbstractUnitRange, b) = ×(g, to_graded_axis(b)) ×(nt1::NamedTuple, nt2::NamedTuple) = ×(CategoryProduct(nt1), CategoryProduct(nt2)) ×(c1::NamedTuple, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) ×(c1::AbstractCategory, c2::NamedTuple) = ×(CategoryProduct(c1), CategoryProduct(c2)) @@ -94,10 +94,7 @@ function ×(l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInt return LabelledNumbers.LabelledInteger(m3, c3) end -×(g::AbstractUnitRange, c::AbstractCategory) = ×(g, GradedAxes.gradedrange([c => 1])) -×(c::AbstractCategory, g::AbstractUnitRange) = ×(GradedAxes.gradedrange([c => 1]), g) - -function ×(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange) +function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) # keep F convention in loop order v = [ l1 × l2 for l2 in BlockArrays.blocklengths(g2) for l1 in BlockArrays.blocklengths(g1) @@ -105,6 +102,26 @@ function ×(g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange) return GradedAxes.gradedrange(v) end +# =================== Fusion rule ==================== +function fusion_rule(s1::CategoryProduct, s2::CategoryProduct) + return fusion_rule(combine_styles(SymmetryStyle(s1), SymmetryStyle(s2)), s1, s2) +end + +# generic case: fusion returns a GradedAxes, even for fusion with Empty +function fusion_rule(::SymmetryStyle, s1::CategoryProduct, s2::CategoryProduct) + return to_graded_axis(categories_fusion_rule(categories(s1), categories(s2))) +end + +# Abelian case: fusion returns CategoryProduct +function fusion_rule(::AbelianGroup, s1::CategoryProduct, s2::CategoryProduct) + return categories_fusion_rule(categories(s1), categories(s2)) +end + +# Empty case: use × cast conventions between NamedTupled and ordered implem +function fusion_rule(::EmptyCategory, s1::CategoryProduct, s2::CategoryProduct) + return s1 × s2 +end + # ============== Dictionary-like implementation ================= function CategoryProduct(nt::NamedTuple) categories = sort_keys(nt) @@ -127,59 +144,41 @@ function categories_equal(A::NamedTuple, B::NamedTuple) end # allow ⊗ for different types in NamedTuple -function fusion_rule( - s1::CategoryProduct{Cat1}, s2::CategoryProduct{Cat2} -) where {Cat1<:NamedTuple,Cat2<:NamedTuple} - - # avoid issues with length 0 CategoryProduct - if SymmetryStyle(s1) == EmptyCategory() - if SymmetryStyle(s2) == AbelianGroup() || SymmetryStyle(s2) == EmptyCategory() - return s2 - end - return GradedAxes.gradedrange([s2 => 1]) - end - if SymmetryStyle(s2) == EmptyCategory() - if SymmetryStyle(s1) == AbelianGroup() - return s1 - end - return GradedAxes.gradedrange([s1 => 1]) - end - - cats1 = categories(s1) - cats2 = categories(s2) +function categories_fusion_rule(cats1::NamedTuple, cats2::NamedTuple) diff_cat = CategoryProduct(symdiff_keys(cats1, cats2)) shared1 = intersect_keys(cats1, cats2) - if length(shared1) == 0 - if SymmetryStyle(diff_cat) == AbelianGroup() - return diff_cat - end - return GradedAxes.gradedrange([diff_cat => 1]) - end - shared2 = intersect_keys(cats2, cats1) - fused = fusion_rule(shared1, shared2) - out = diff_cat × fused - return out + return diff_cat × categories_fusion_rule(shared1, shared2) end -function fusion_rule( +# iterate over keys +function categories_fusion_rule( cats1::NT, cats2::NT ) where {Names,NT<:NamedTuple{Names,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} - return fusion_rule(cats1[(Names[1],)], cats2[(Names[1],)]) × - fusion_rule(cats1[Names[2:end]], cats2[Names[2:end]]) + return categories_fusion_rule(cats1[(Names[1],)], cats2[(Names[1],)]) × + categories_fusion_rule(cats1[Names[2:end]], cats2[Names[2:end]]) end -fusion_rule(cats1::NamedTuple{}, cats2::NamedTuple{}) = sector() +# zero key +categories_fusion_rule(cats1::NT, cats2::NT) where {NT<:NamedTuple{}} = sector() -function fusion_rule( +# one key +function categories_fusion_rule( cats1::NT, cats2::NT ) where {NT<:NamedTuple{<:Any,<:Tuple{AbstractCategory}}} - # cannot be EmptyCategory + return single_fusion_rule(SymmetryStyle(cats1), cats1, cats2) +end + +# abelian fusion of one category +function single_fusion_rule(::AbelianGroup, cats1::NT, cats2::NT) where {NT} + fused = fusion_rule(only(values(cats1)), only(values(cats2))) + return sector(only(keys(cats1)) => fused) +end + +# generic fusion of one category +function single_fusion_rule(::SymmetryStyle, cats1::NT, cats2::NT) where {NT} + fused = fusion_rule(only(values(cats1)), only(values(cats2))) key = only(keys(cats1)) - fused = only(values(cats1)) ⊗ only(values(cats2)) - if SymmetryStyle(cats1) == AbelianGroup() - return sector(key => fused) - end la = fused[1] v = Vector{Pair{CategoryProduct{NT},Int64}}() for la in blocklengths(fused) @@ -198,13 +197,6 @@ categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) sector(args...; kws...) = CategoryProduct(args...; kws...) # for ordered tuple, impose same type in fusion -function fusion_rule(s1::CategoryProduct{Cat}, s2::CategoryProduct{Cat}) where {Cat<:Tuple} - if SymmetryStyle(s1) == EmptyCategory() # compile-time; simpler than specifying init - return s1 - end - cats1 = categories(s1) - cats2 = categories(s2) - fused = map(fusion_rule, cats1, cats2) - g = reduce(×, fused) - return g +function categories_fusion_rule(cats1::Tu, cats2::Tu) where {Tu<:Tuple} + return reduce(×, map(fusion_rule, cats1, cats2); init=CategoryProduct(())) end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index c8b17eb7e4..88bcfec9a9 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -25,6 +25,10 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[:C] == Ising("ψ") @test (@inferred quantum_dimension(s)) == 5.0 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) + + s1 = (A=U1(1),) × (B=Z{2}(0),) + s2 = (A=U1(1),) × (C=Z{2}(0),) + @test_throws MethodError s1 × s2 end @testset "Construct from Pairs" begin @@ -218,14 +222,14 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws ı = Fib("1") τ = Fib("τ") - s = sector(; A=U1(1), B=SU2(1//2), C=τ) + s = sector(; A=SU2(1//2), B=U1(1), C=τ) @test gradedisequal( (@inferred s ⊗ s), gradedrange([ - sector(; A=U1(2), B=SU2(0), C=ı) => 1, - sector(; A=U1(2), B=SU2(1), C=ı) => 1, - sector(; A=U1(2), B=SU2(0), C=τ) => 1, - sector(; A=U1(2), B=SU2(1), C=τ) => 1, + sector(; A=SU2(0), B=U1(2), C=ı) => 1, + sector(; A=SU2(1), B=U1(2), C=ı) => 1, + sector(; A=SU2(0), B=U1(2), C=τ) => 1, + sector(; A=SU2(1), B=U1(2), C=τ) => 1, ]), ) @@ -407,16 +411,17 @@ end (U1(2) × SU2(1) × Ising("ψ")) => 1, ]), ) + ı = Fib("1") τ = Fib("τ") - s = U1(1) × SU2(1//2) × τ + s = SU2(1//2) × U1(1) × τ @test gradedisequal( (@inferred s ⊗ s), gradedrange([ - (U1(2) × SU2(0) × ı) => 1, - (U1(2) × SU2(1) × ı) => 1, - (U1(2) × SU2(0) × τ) => 1, - (U1(2) × SU2(1) × τ) => 1, + (SU2(0) × U1(2) × ı) => 1, + (SU2(1) × U1(2) × ı) => 1, + (SU2(0) × U1(2) × τ) => 1, + (SU2(1) × U1(2) × τ) => 1, ]), ) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 031be327b5..9fbee0885e 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,5 +1,5 @@ @eval module $(gensym()) -using NDTensors.GradedAxes: fuse_labels, gradedisequal, gradedrange, tensor_product +using NDTensors.GradedAxes: gradedisequal, gradedrange, fusion_product using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension using Test: @inferred, @test, @testset, @test_throws @@ -14,8 +14,8 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred z0 ⊗ z0) == z0 # no better way, see Julia PR 23426 # using GradedAxes interface - @test fuse_labels(z0, z0) == z0 - @test fuse_labels(z0, z1) == z1 + @test gradedisequal(fusion_product(z0, z0), gradedrange([z0 => 1])) + @test gradedisequal(fusion_product(z0, z1), gradedrange([z1 => 1])) end @testset "U(1) fusion rules" begin q1 = U1(1) @@ -90,35 +90,35 @@ end g1 = gradedrange([U1(1) => 1, U1(2) => 2]) g2 = gradedrange([U1(-1) => 2, U1(0) => 1, U1(1) => 2]) @test gradedisequal( - (@inferred tensor_product(g1, g2)), + (@inferred fusion_product(g1, g2)), gradedrange([U1(0) => 2, U1(1) => 5, U1(2) => 4, U1(3) => 4]), ) g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2, SU2(1) => 1]) g4 = gradedrange([SU2(1//2) => 1, SU2(1) => 2]) @test gradedisequal( - (@inferred tensor_product(g3, g4)), + (@inferred fusion_product(g3, g4)), gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), ) # test different categories cannot be fused - @test_throws MethodError tensor_product(g1, g4) + @test_throws MethodError fusion_product(g1, g4) end @testset "Mixed GradedUnitRange - Category fusion rules" begin g1 = gradedrange([U1(1) => 1, U1(2) => 2]) g2 = gradedrange([U1(2) => 1, U1(3) => 2]) - @test gradedisequal((@inferred tensor_product(g1, U1(1))), g2) - @test gradedisequal((@inferred tensor_product(U1(1), g1)), g2) + @test gradedisequal((@inferred fusion_product(g1, U1(1))), g2) + @test gradedisequal((@inferred fusion_product(U1(1), g1)), g2) g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2]) g4 = gradedrange([SU2(0) => 2, SU2(1//2) => 1, SU2(1) => 2]) - @test gradedisequal((@inferred tensor_product(g3, SU2(1//2))), g4) - @test gradedisequal((@inferred tensor_product(SU2(1//2), g3)), g4) + @test gradedisequal((@inferred fusion_product(g3, SU2(1//2))), g4) + @test gradedisequal((@inferred fusion_product(SU2(1//2), g3)), g4) # test different categories cannot be fused - @test_throws MethodError tensor_product(g1, SU2(1)) - @test_throws MethodError tensor_product(U1(1), g3) + @test_throws MethodError fusion_product(g1, SU2(1)) + @test_throws MethodError fusion_product(U1(1), g3) end end end From 34dc147999a643b7207c6b79de7c7d3e34a7098f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 4 Apr 2024 17:22:22 -0400 Subject: [PATCH 030/194] define fusion_product for CategoryProduct --- .../src/lib/Sectors/src/abstractcategory.jl | 26 +++++---- .../src/lib/Sectors/src/category_product.jl | 19 ++++-- .../lib/Sectors/test/test_category_product.jl | 58 +++++++++++++++---- 3 files changed, 75 insertions(+), 28 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 2d764295e7..1f6fdc05c7 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -4,7 +4,7 @@ abstract type AbstractCategory end # ============ Base interface ================= -Base.isless(c1::AbstractCategory, c2::AbstractCategory) = isless(label(c1), label(c2)) +Base.isless(c1::C, c2::C) where {C<:AbstractCategory} = isless(label(c1), label(c2)) # ================= Misc ====================== function trivial(category_type::Type{<:AbstractCategory}) @@ -69,15 +69,14 @@ end function GradedAxes.fusion_product(g1::AbstractUnitRange, g2::AbstractUnitRange) blocks2 = BlockArrays.blocklengths(g2) blocks3 = empty(blocks2) - sym = SymmetryStyle(g2) for b2 in blocks2 c2 = LabelledNumbers.label(b2) degen2 = LabelledNumbers.unlabel(b2) for b1 in BlockArrays.blocklengths(g1) c1 = LabelledNumbers.label(b1) - degen1 = LabelledNumbers.unlabel(b1) - degen3 = degen1 * degen2 - _append_fusion!(blocks3, sym, degen3, c1, c2) + degen = degen2 * LabelledNumbers.unlabel(b1) + fused = c1 ⊗ c2 + _append_fusion!(blocks3, degen, fused) end end la3 = LabelledNumbers.label.(blocks3) @@ -85,14 +84,17 @@ function GradedAxes.fusion_product(g1::AbstractUnitRange, g2::AbstractUnitRange) return GradedAxes.gradedrange(pairs3) end -function _append_fusion!(blocks3, ::AbelianGroup, degen3, c1::C, c2::C) where {C} - return push!(blocks3, LabelledNumbers.LabelledInteger(degen3, fusion_rule(c1, c2))) +# AbelianGroup case +function _append_fusion!(blocks3, degen, fused::AbstractCategory) + return push!(blocks3, LabelledNumbers.LabelledInteger(degen, fused)) end -function _append_fusion!(blocks3, ::SymmetryStyle, degen3, c1::C, c2::C) where {C} - fused_blocks = BlockArrays.blocklengths(fusion_rule(c1, c2)) - g12 = + +# generic case +function _append_fusion!(blocks3, degen, fused::AbstractUnitRange) + fused_blocks = BlockArrays.blocklengths(fused) + scaled = LabelledNumbers.LabelledInteger.( - degen3 * LabelledNumbers.unlabel.(fused_blocks), LabelledNumbers.label.(fused_blocks) + degen * LabelledNumbers.unlabel.(fused_blocks), LabelledNumbers.label.(fused_blocks) ) - return append!(blocks3, g12) + return append!(blocks3, scaled) end diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index b5ce67edc5..1ff26a9bb0 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -63,6 +63,10 @@ category_show(io::IO, k, v) = print(io, v) category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") +function Base.isless(s1::C, s2::C) where {C<:CategoryProduct} + return isless(label.(values(categories(s1))), label.(values(categories(s2)))) +end + # ============== Cartesian product ================= ×(c1::AbstractCategory, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) function ×(p1::CategoryProduct, p2::CategoryProduct) @@ -102,7 +106,7 @@ function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) return GradedAxes.gradedrange(v) end -# =================== Fusion rule ==================== +# =================== Fusion rules ==================== function fusion_rule(s1::CategoryProduct, s2::CategoryProduct) return fusion_rule(combine_styles(SymmetryStyle(s1), SymmetryStyle(s2)), s1, s2) end @@ -179,9 +183,8 @@ end function single_fusion_rule(::SymmetryStyle, cats1::NT, cats2::NT) where {NT} fused = fusion_rule(only(values(cats1)), only(values(cats2))) key = only(keys(cats1)) - la = fused[1] v = Vector{Pair{CategoryProduct{NT},Int64}}() - for la in blocklengths(fused) + for la in BlockArrays.blocklengths(fused) push!(v, sector(key => LabelledNumbers.label(la)) => LabelledNumbers.unlabel(la)) end g = GradedAxes.gradedrange(v) @@ -196,7 +199,11 @@ categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) sector(args...; kws...) = CategoryProduct(args...; kws...) -# for ordered tuple, impose same type in fusion -function categories_fusion_rule(cats1::Tu, cats2::Tu) where {Tu<:Tuple} - return reduce(×, map(fusion_rule, cats1, cats2); init=CategoryProduct(())) +# allow additional categories at one end +function categories_fusion_rule(cats1::Tuple, cats2::Tuple) + n = min(length(cats1), length(cats2)) + shared = map(fusion_rule, cats1[begin:n], cats2[begin:n]) + sup1 = CategoryProduct(cats1[(n + 1):end]) + sup2 = CategoryProduct(cats2[(n + 1):end]) + return reduce(×, (shared..., sup1, sup2)) end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 88bcfec9a9..79670faa32 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) using NDTensors.Sectors: ×, ⊗, CategoryProduct, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension -using NDTensors.GradedAxes: dual, gradedisequal, gradedrange +using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange using Test: @inferred, @test, @testset, @test_broken, @test_throws @testset "Test Named Category Products" begin @@ -239,6 +239,15 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]), ) end + @testset "GradedUnitRange fusion rules" begin + s1 = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) + s2 = sector(; A=U1(0), B=SU2(1//2), C=Ising("1")) + g1 = gradedrange([s1 => 2]) + g2 = gradedrange([s2 => 1]) + s3 = sector(; A=U1(1), B=SU2(0), C=Ising("σ")) + s4 = sector(; A=U1(1), B=SU2(1), C=Ising("σ")) + @test gradedisequal((@inferred fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2])) + end end @testset "Test Ordered Products" begin @@ -314,15 +323,6 @@ end @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end - @testset "Enforce same spaces in fusion" begin - p12 = U1(1) × U1(2) - p123 = U1(1) × U1(2) × U1(3) - @test_throws MethodError p12 ⊗ p123 - - z12 = Z{2}(1) × Z{2}(1) - @test_throws MethodError p12 ⊗ z12 - end - @testset "Empty category" begin s = CategoryProduct(()) @test (@inferred dual(s)) == s @@ -332,6 +332,10 @@ end end @testset "Fusion of Abelian products" begin + p1 = sector(U1(1)) + p2 = sector(U1(2)) + @test (@inferred p1 ⊗ p2) == sector(U1(3)) + p11 = U1(1) × U1(1) @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) @@ -344,6 +348,10 @@ end end @testset "Fusion of NonAbelian products" begin + p0 = sector(SU2(0)) + ph = sector(SU2(1//2)) + @test gradedisequal((@inferred p0 ⊗ ph), gradedrange([sector(SU2(1//2)) => 1])) + phh = SU2(1//2) × SU2(1//2) @test gradedisequal( phh ⊗ phh, @@ -430,5 +438,35 @@ end (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) ) end + + @testset "Fusion of different length Categories" begin + @test sector(U1(1) × U1(0)) ⊗ sector(U1(1)) == sector(U1(2) × U1(0)) + @test gradedisequal( + sector(SU2(0) × SU2(0)) ⊗ sector(SU2(1)), gradedrange([sector(SU2(1) × SU2(0)) => 1]) + ) + + @test gradedisequal( + sector(SU2(1) × U1(1)) ⊗ sector(SU2(0)), gradedrange([sector(SU2(1) × U1(1)) => 1]) + ) + @test gradedisequal( + sector(U1(1) × SU2(1)) ⊗ sector(U1(2)), gradedrange([sector(U1(3) × SU2(1)) => 1]) + ) + + # check incompatible categories + p12 = Z{2}(1) × U1(2) + z12 = Z{2}(1) × Z{2}(1) + @test_throws MethodError p12 ⊗ z12 + end + + @testset "GradedUnitRange fusion rules" begin + s1 = U1(1) × SU2(1//2) × Ising("σ") + s2 = U1(0) × SU2(1//2) × Ising("1") + g1 = gradedrange([s1 => 2]) + g2 = gradedrange([s2 => 1]) + @test gradedisequal( + (@inferred fusion_product(g1, g2)), + gradedrange([U1(1) × SU2(0) × Ising("σ") => 2, U1(1) × SU2(1) × Ising("σ") => 2]), + ) + end end end From 551db7a5675d88b8b6d43dec99d8fde2927912a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 5 Apr 2024 16:28:44 -0400 Subject: [PATCH 031/194] rename label -> category_label --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 13 ++++++++----- .../src/lib/Sectors/src/category_definitions/fib.jl | 2 +- .../lib/Sectors/src/category_definitions/ising.jl | 6 +++--- .../src/lib/Sectors/src/category_definitions/su.jl | 10 +++++----- .../src/lib/Sectors/src/category_definitions/su2.jl | 4 ++-- .../lib/Sectors/src/category_definitions/su2k.jl | 2 +- .../src/lib/Sectors/src/category_definitions/u1.jl | 2 +- .../src/lib/Sectors/src/category_definitions/zn.jl | 4 ++-- NDTensors/src/lib/Sectors/src/category_product.jl | 4 +++- 9 files changed, 26 insertions(+), 21 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 1f6fdc05c7..79945a2cc7 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -4,7 +4,9 @@ abstract type AbstractCategory end # ============ Base interface ================= -Base.isless(c1::C, c2::C) where {C<:AbstractCategory} = isless(label(c1), label(c2)) +function Base.isless(c1::C, c2::C) where {C<:AbstractCategory} + return isless(category_label(c1), category_label(c2)) +end # ================= Misc ====================== function trivial(category_type::Type{<:AbstractCategory}) @@ -13,8 +15,9 @@ end istrivial(c::AbstractCategory) = (c == trivial(typeof(c))) -# name conflict with LabelledNumber.label. TBD is that an issue? -label(c::AbstractCategory) = error("method `label` not defined for type $(typeof(c))") +function category_label(c::AbstractCategory) + return error("method `category_label` not defined for type $(typeof(c))") +end function GradedAxes.dual(category_type::Type{<:AbstractCategory}) return error("`dual` not defined for type $(category_type).") @@ -45,12 +48,12 @@ function fusion_rule(c1::C, c2::C) where {C<:AbstractCategory} end function fusion_rule(::SymmetryStyle, c1::C, c2::C) where {C<:AbstractCategory} - degen, labels = label_fusion_rule(C, label(c1), label(c2)) + degen, labels = label_fusion_rule(C, category_label(c1), category_label(c2)) return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) end function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} - return C(label_fusion_rule(C, label(c1), label(c2))) # return AbelianGroup + return C(label_fusion_rule(C, category_label(c1), category_label(c2))) # return AbelianGroup end function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index 5993f3503c..ff7f69795e 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -22,7 +22,7 @@ SymmetryStyle(::Fib) = NonGroupCategory() GradedAxes.dual(f::Fib) = f -label(f::Fib) = f.l +category_label(f::Fib) = f.l trivial(::Type{Fib}) = Fib(0) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index a69705f366..d0d04c7319 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -22,17 +22,17 @@ SymmetryStyle(::Ising) = NonGroupCategory() GradedAxes.dual(i::Ising) = i -label(i::Ising) = i.l +category_label(i::Ising) = i.l trivial(::Type{Ising}) = Ising(0) -quantum_dimension(::NonGroupCategory, i::Ising) = (label(i) == 1//2) ? √2 : 1.0 +quantum_dimension(::NonGroupCategory, i::Ising) = (category_label(i) == 1//2) ? √2 : 1.0 # Fusion rules identical to su2₂ label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) # TODO: Use `Val` dispatch here? -label_to_str(i::Ising) = ("1", "σ", "ψ")[twice(label(i)) + 1] +label_to_str(i::Ising) = ("1", "σ", "ψ")[twice(category_label(i)) + 1] function Base.show(io::IO, f::Ising) return print(io, "Ising(", label_to_str(f), ")") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index d3239b459b..04f3000e6d 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -17,7 +17,7 @@ end SymmetryStyle(::SU) = NonAbelianGroup() -label(s::SU) = s.l +category_label(s::SU) = s.l groupdim(::SU{N}) where {N} = N @@ -29,7 +29,7 @@ adjoint(::Type{SU{N}}) where {N} = SU{N}((ntuple(i -> Int(i == 1) + Int(i < N), function quantum_dimension(::NonAbelianGroup, s::SU) N = groupdim(s) - l = label(s) + l = category_label(s) d = 1 for k1 in 1:N, k2 in (k1 + 1):N d *= ((k2 - k1) + (l[k1] - l[k2]))//(k2 - k1) @@ -38,14 +38,14 @@ function quantum_dimension(::NonAbelianGroup, s::SU) end function GradedAxes.dual(s::SU) - l = label(s) + l = category_label(s) nl = ((reverse(cumsum(l[begin:(end - 1)] .- l[(begin + 1):end]))..., 0)) return typeof(s)(nl) end # display SU(N) irrep as a Young tableau with utf8 box char function Base.show(io::IO, ::MIME"text/plain", s::SU) - l = label(s) + l = category_label(s) if l[1] == 0 # singlet = no box println(io, "●") return nothing @@ -75,7 +75,7 @@ end # TBD remove me? # -quantum_dimension(s::SU{2}) = 1 + label(s)[1] +quantum_dimension(s::SU{2}) = 1 + category_label(s)[1] SU{2}(d::Integer) = SU{2}((d - 1, 0)) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl index 6f98756a78..af87b06d79 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl @@ -13,13 +13,13 @@ SymmetryStyle(::SU2) = NonAbelianGroup() GradedAxes.dual(s::SU2) = s -label(s::SU2) = s.j +category_label(s::SU2) = s.j trivial(::Type{SU2}) = SU2(0) fundamental(::Type{SU2}) = SU2(half(1)) adjoint(::Type{SU2}) = SU2(1) -quantum_dimension(::NonAbelianGroup, s::SU2) = twice(label(s)) + 1 +quantum_dimension(::NonAbelianGroup, s::SU2) = twice(category_label(s)) + 1 function label_fusion_rule(::Type{SU2}, j1, j2) labels = collect(abs(j1 - j2):(j1 + j2)) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index f6eaeae85c..66d6c3b2a3 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -12,7 +12,7 @@ SymmetryStyle(::su2) = NonGroupCategory() dual(s::su2) = s -label(s::su2) = s.j +category_label(s::su2) = s.j level(::su2{k}) where {k} = k diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 36027c757b..6e891b9e66 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -12,7 +12,7 @@ SymmetryStyle(::U1) = AbelianGroup() GradedAxes.dual(u::U1) = U1(-u.n) -label(u::U1) = u.n +category_label(u::U1) = u.n trivial(::Type{U1}) = U1(0) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 94d7e928c4..665080b73d 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -9,7 +9,7 @@ end SymmetryStyle(::Z) = AbelianGroup() -label(c::Z) = c.m +category_label(c::Z) = c.m modulus(::Type{Z{N}}) where {N} = N modulus(c::Z) = modulus(typeof(c)) @@ -20,4 +20,4 @@ function label_fusion_rule(category_type::Type{<:Z}, n1, n2) return (n1 + n2) % modulus(category_type) end -GradedAxes.dual(c::Z) = typeof(c)(mod(-label(c), modulus(c))) +GradedAxes.dual(c::Z) = typeof(c)(mod(-category_label(c), modulus(c))) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 1ff26a9bb0..8424c37332 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -64,7 +64,9 @@ category_show(io::IO, k, v) = print(io, v) category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") function Base.isless(s1::C, s2::C) where {C<:CategoryProduct} - return isless(label.(values(categories(s1))), label.(values(categories(s2)))) + return isless( + category_label.(values(categories(s1))), category_label.(values(categories(s2))) + ) end # ============== Cartesian product ================= From 7f54001fad1838075ddb1a7c6db254b7e916835c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 5 Apr 2024 17:16:33 -0400 Subject: [PATCH 032/194] split tensor_product and fusion_product --- .../src/lib/GradedAxes/src/GradedAxes.jl | 2 +- NDTensors/src/lib/GradedAxes/src/dual.jl | 3 + NDTensors/src/lib/GradedAxes/src/fusion.jl | 33 ++++- .../src/lib/Sectors/src/abstractcategory.jl | 85 ++++++++----- .../Sectors/src/category_definitions/su.jl | 65 ++++++++++ .../src/lib/Sectors/src/category_product.jl | 15 --- .../src/lib/Sectors/src/symmetry_style.jl | 15 ++- .../lib/Sectors/test/test_category_product.jl | 7 ++ .../src/lib/Sectors/test/test_fusion_rules.jl | 119 ++++++++++++++++-- 9 files changed, 280 insertions(+), 64 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl index 42392c7394..2992a27b05 100644 --- a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl +++ b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl @@ -1,6 +1,6 @@ module GradedAxes include("gradedunitrange.jl") -include("fusion.jl") include("dual.jl") include("unitrangedual.jl") +include("fusion.jl") end diff --git a/NDTensors/src/lib/GradedAxes/src/dual.jl b/NDTensors/src/lib/GradedAxes/src/dual.jl index 985ebe33cc..087c0db336 100644 --- a/NDTensors/src/lib/GradedAxes/src/dual.jl +++ b/NDTensors/src/lib/GradedAxes/src/dual.jl @@ -5,3 +5,6 @@ using NDTensors.LabelledNumbers: label_dual(x) = label_dual(LabelledStyle(x), x) label_dual(::NotLabelled, x) = x label_dual(::IsLabelled, x) = labelled(unlabel(x), dual(label(x))) + +# TBD rename deepdual? yet another name? +label_dual(g::GradedUnitRange) = gradedrange(label_dual.(blocklengths(g))) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index bd763672b1..19ae9b8e94 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -18,10 +18,6 @@ function tensor_product( return foldl(tensor_product, (a1, a2, a3, a_rest...)) end -function fusion_product() - return error("Not implemented") -end - function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) return error("Not implemented yet.") end @@ -42,6 +38,18 @@ function tensor_product(a1::OneToOne, a2::OneToOne) return OneToOne() end +function tensor_product(a1::AbstractUnitRange, a2::UnitRangeDual) + return tensor_product(a1, label_dual(dual(a2))) +end + +function tensor_product(a1::UnitRangeDual, a2::AbstractUnitRange) + return tensor_product(label_dual(dual(a1)), a2) +end + +function tensor_product(a1::UnitRangeDual, a2::UnitRangeDual) + return tensor_product(label_dual(dual(a1)), label_dual(dual(a2))) +end + function fuse_labels(x, y) return error( "`fuse_labels` not implemented for object of type `$(typeof(x))` and `$(typeof(y))`." @@ -108,3 +116,20 @@ function blockmergesortperm(a::GradedUnitRange) # TODO: `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`. return Block.(groupsortperm(blocklabels(a))) end + +# fusion_product generalizes tensor_product to non-abelian groups and fusion categories +# in the case of abelian groups, it is equivalent to blockmergesortperm ∘ tensor_product +# deal with dual. Always return a non-dual GradedUnitRange. +function fusion_product(a1::AbstractUnitRange, a2::AbstractUnitRange) + return error("Not implemented") +end + +function fusion_product(g1::UnitRangeDual, g2::AbstractUnitRange) + return fusion_product(label_dual(dual(g1)), g2) +end +function fusion_product(g1::AbstractUnitRange, g2::UnitRangeDual) + return fusion_product(g1, label_dual(dual(g2))) +end +function fusion_product(g1::UnitRangeDual, g2::UnitRangeDual) + return fusion_product(label_dual(dual(g1)), label_dual(dual(g2))) +end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 79945a2cc7..b97467c639 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -39,12 +39,16 @@ function quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) end quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) +function quantum_dimension(::SymmetryStyle, g::GradedAxes.UnitRangeDual) + return quantum_dimension(GradedAxes.dual(g)) +end +quantum_dimension(::AbelianGroup, g::GradedAxes.UnitRangeDual) = length(g) # resolves ambiguity # ================ fusion rule interface ==================== ⊗(c1::AbstractCategory, c2::AbstractCategory) = fusion_rule(c1, c2) -function fusion_rule(c1::C, c2::C) where {C<:AbstractCategory} - return fusion_rule(SymmetryStyle(c1), c1, c2) +function fusion_rule(c1, c2) + return fusion_rule(combine_styles(SymmetryStyle(c1), SymmetryStyle(c2)), c1, c2) end function fusion_rule(::SymmetryStyle, c1::C, c2::C) where {C<:AbstractCategory} @@ -52,52 +56,67 @@ function fusion_rule(::SymmetryStyle, c1::C, c2::C) where {C<:AbstractCategory} return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) end +# abelian case: return Category function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} - return C(label_fusion_rule(C, category_label(c1), category_label(c2))) # return AbelianGroup + return C(label_fusion_rule(C, category_label(c1), category_label(c2))) end function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) return error("`label_fusion_rule` not defined for type $(category_type).") end +# convenient to define fusion rule for LabelledInteger too +function fusion_rule( + ::SymmetryStyle, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger +) + blocks12 = BlockArrays.blocklengths(LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2)) + v = + LabelledNumbers.LabelledInteger.(l1 * l2 .* blocks12, LabelledNumbers.label.(blocks12)) + return GradedAxes.gradedrange(v) +end + +function fusion_rule( + ::AbelianGroup, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger +) + fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) + return LabelledNumbers.LabelledInteger(l1 * l2, fused) +end + # ============= fusion rule and gradedunitrange =================== +# GradedAxes.tensor_product interface. Only for abelian groups. +function GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) + return GradedAxes.fuse_labels( + combine_styles(SymmetryStyle(c1), SymmetryStyle(c2)), c1, c2 + ) +end +function GradedAxes.fuse_labels(::SymmetryStyle, c1::AbstractCategory, c2::AbstractCategory) + return error("`fuse_labels` is only defined for abelian groups") +end +function GradedAxes.fuse_labels(::AbelianGroup, c1::AbstractCategory, c2::AbstractCategory) + return fusion_rule(c1, c2) +end + +# cast to range to_graded_axis(c::AbstractCategory) = GradedAxes.gradedrange([c => 1]) +to_graded_axis(l::LabelledNumbers.LabelledInteger) = GradedAxes.gradedrange([l]) to_graded_axis(g::AbstractUnitRange) = g +# allow to fuse a category with a GradedUnitRange function GradedAxes.fusion_product(a, b) return GradedAxes.fusion_product(to_graded_axis(a), to_graded_axis(b)) end -# TODO deal with dual -function GradedAxes.fusion_product(g1::AbstractUnitRange, g2::AbstractUnitRange) - blocks2 = BlockArrays.blocklengths(g2) - blocks3 = empty(blocks2) - for b2 in blocks2 - c2 = LabelledNumbers.label(b2) - degen2 = LabelledNumbers.unlabel(b2) - for b1 in BlockArrays.blocklengths(g1) - c1 = LabelledNumbers.label(b1) - degen = degen2 * LabelledNumbers.unlabel(b1) - fused = c1 ⊗ c2 - _append_fusion!(blocks3, degen, fused) +function GradedAxes.fusion_product( + g1::BlockArrays.BlockedUnitRange, g2::BlockArrays.BlockedUnitRange +) + blocks12 = Vector{eltype(to_graded_axis(fusion_rule(first(g1), first(g2))))}() + for l1 in BlockArrays.blocklengths(g1) + for l2 in BlockArrays.blocklengths(g2) + append!(blocks12, BlockArrays.blocklengths(to_graded_axis(fusion_rule(l1, l2)))) end end - la3 = LabelledNumbers.label.(blocks3) - pairs3 = [r => sum(blocks3[findall(==(r), la3)]; init=0) for r in sort(unique(la3))] - return GradedAxes.gradedrange(pairs3) -end - -# AbelianGroup case -function _append_fusion!(blocks3, degen, fused::AbstractCategory) - return push!(blocks3, LabelledNumbers.LabelledInteger(degen, fused)) -end - -# generic case -function _append_fusion!(blocks3, degen, fused::AbstractUnitRange) - fused_blocks = BlockArrays.blocklengths(fused) - scaled = - LabelledNumbers.LabelledInteger.( - degen * LabelledNumbers.unlabel.(fused_blocks), LabelledNumbers.label.(fused_blocks) - ) - return append!(blocks3, scaled) + la3 = LabelledNumbers.label.(blocks12) + pairs3 = [r => sum(blocks12[findall(==(r), la3)]; init=0) for r in sort(unique(la3))] + out = GradedAxes.gradedrange(pairs3) + return out end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 04f3000e6d..3524567dc4 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -92,3 +92,68 @@ end function Base.show(io::IO, s::SU{2}) return print(io, "SU{2}(", quantum_dimension(s), ")") end + +# Specializations for the case SU{3} +# aimed for testing non-abelian non self-conjugate representations +# TODO replace with generic implementation + +function label_fusion_rule(::Type{SU{3}}, left, right) + # Compute SU(3) fusion rules using Littlewood-Richardson rule for Young tableaus. + # See e.g. Di Francesco, Mathieu and Sénéchal, section 13.5.3. + if sum(right) > sum(left) # impose more boxes in left Young tableau + return label_fusion_rule(SU{3}, right, left) + end + + if right[1] == 0 # avoid issues with singlet + return [1], [left] + end + + left_row1 = left[1] + left_row2 = left[2] + right_row1 = right[1] + right_row2 = right[2] + + irreps = [] + + # put a23 boxes on 2nd or 3rd line + a23max1 = 2 * left_row1 # row2a <= row1a + a23max2 = right_row1 # a2 + a3 <= total number of a + a23max = min(a23max1, a23max2) + for a23 in 0:a23max + a3min1 = left_row2 + 2 * a23 - left_row1 - right_row1 + a3min2 = left_row2 - left_row1 + a23 # no a below a: row2a <= row1 + a3min = max(0, a3min1, a3min2) + a3max1 = left_row2 # row3a <= row2a + a3max2 = a23 # a3 <= a2 + a3 + a3max3 = right_row1 - right_row2 # more a than b, right to left: b2 + b3 <= a1 + a2 + a3max = min(a3max1, a3max2, a3max3) + for a3 in a3min:a3max + a2 = a23 - a3 + row1a = left_row1 + right_row1 - a23 + row2a = left_row2 + a23 - a3 + + # cannot put any b on 1st line: row1ab = row1a + b3min1 = row2a + right_row2 - row1a # row2ab <= row1ab = row1a + b3min2 = right_row2 + a23 - right_row1 + b3min = max(0, b3min1, b3min2) + b3max1 = right_row2 # only other.row2 b boxes + b3max2 = (row2a + right_row2 - a3)//2 # row3ab >= row2ab + b3max3 = right_row1 - a3 # more a than b, right to left: b2 <= a1 + b3max4 = row2a - a3 # no b below b: row2a >= row3ab + b3max = min(b3max1, b3max2, b3max3, b3max4) + for b3 in b3min:b3max + b2 = right_row2 - b3 + row2ab = row2a + b2 + row3ab = a3 + b3 + yt = (row1a - row3ab, row2ab - row3ab, 0) + + push!(irreps, yt) + end + end + end + + unique_labels = sort(unique(irreps)) + degen = [count(==(irr), irreps) for irr in unique_labels] + + return degen, unique_labels +end diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 8424c37332..22d1036abc 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -12,17 +12,6 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats # ============== SymmetryStyle ============================== -combine_styles(::AbelianGroup, ::AbelianGroup) = AbelianGroup() -combine_styles(::AbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() -combine_styles(::AbelianGroup, ::NonGroupCategory) = NonGroupCategory() -combine_styles(::NonAbelianGroup, ::AbelianGroup) = NonAbelianGroup() -combine_styles(::NonAbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() -combine_styles(::NonAbelianGroup, ::NonGroupCategory) = NonGroupCategory() -combine_styles(::NonGroupCategory, ::SymmetryStyle) = NonGroupCategory() -combine_styles(::EmptyCategory, s::SymmetryStyle) = s -combine_styles(s::SymmetryStyle, ::EmptyCategory) = s -combine_styles(::EmptyCategory, ::EmptyCategory) = EmptyCategory() - function SymmetryStyle(c::CategoryProduct) return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategory()) end @@ -109,10 +98,6 @@ function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) end # =================== Fusion rules ==================== -function fusion_rule(s1::CategoryProduct, s2::CategoryProduct) - return fusion_rule(combine_styles(SymmetryStyle(s1), SymmetryStyle(s2)), s1, s2) -end - # generic case: fusion returns a GradedAxes, even for fusion with Empty function fusion_rule(::SymmetryStyle, s1::CategoryProduct, s2::CategoryProduct) return to_graded_axis(categories_fusion_rule(categories(s1), categories(s2))) diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index 6de8838e20..a796b3d3c8 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -10,5 +10,18 @@ struct NonAbelianGroup <: SymmetryStyle end struct NonGroupCategory <: SymmetryStyle end struct EmptyCategory <: SymmetryStyle end +combine_styles(::AbelianGroup, ::AbelianGroup) = AbelianGroup() +combine_styles(::AbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() +combine_styles(::AbelianGroup, ::NonGroupCategory) = NonGroupCategory() +combine_styles(::NonAbelianGroup, ::AbelianGroup) = NonAbelianGroup() +combine_styles(::NonAbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() +combine_styles(::NonAbelianGroup, ::NonGroupCategory) = NonGroupCategory() +combine_styles(::NonGroupCategory, ::SymmetryStyle) = NonGroupCategory() +combine_styles(::EmptyCategory, s::SymmetryStyle) = s +combine_styles(s::SymmetryStyle, ::EmptyCategory) = s +combine_styles(::EmptyCategory, ::EmptyCategory) = EmptyCategory() + +SymmetryStyle(l::LabelledNumbers.LabelledInteger) = SymmetryStyle(LabelledNumbers.label(l)) + # crash for empty g. Currently impossible to construct. -SymmetryStyle(g::AbstractUnitRange) = SymmetryStyle(LabelledNumbers.label(first(g))) +SymmetryStyle(g::AbstractUnitRange) = SymmetryStyle(first(g)) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 79670faa32..e0497d68ef 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -247,6 +247,13 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws s3 = sector(; A=U1(1), B=SU2(0), C=Ising("σ")) s4 = sector(; A=U1(1), B=SU2(1), C=Ising("σ")) @test gradedisequal((@inferred fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2])) + + sA = sector(; A=U1(1)) + sB = sector(; B=SU2(1//2)) + sAB = sector(; A=U1(1), B=SU2(1//2)) + gA = gradedrange([sA => 2]) + gB = gradedrange([sB => 1]) + @test gradedisequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) end end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 9fbee0885e..f6a1074870 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,5 +1,6 @@ @eval module $(gensym()) -using NDTensors.GradedAxes: gradedisequal, gradedrange, fusion_product +using NDTensors.GradedAxes: + dual, fusion_product, gradedisequal, gradedrange, label_dual, tensor_product using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension using Test: @inferred, @test, @testset, @test_throws @@ -86,23 +87,121 @@ using Test: @inferred, @test, @testset, @test_throws end end @testset "Reducible object fusion rules" begin - @testset "GradedUnitRange fusion rules" begin - g1 = gradedrange([U1(1) => 1, U1(2) => 2]) - g2 = gradedrange([U1(-1) => 2, U1(0) => 1, U1(1) => 2]) - @test gradedisequal( - (@inferred fusion_product(g1, g2)), - gradedrange([U1(0) => 2, U1(1) => 5, U1(2) => 4, U1(3) => 4]), - ) + @testset "GradedUnitRange abelian tensor/fusion product" begin + g1 = gradedrange([U1(-1) => 1, U1(0) => 1, U1(1) => 2]) + g2 = gradedrange([U1(-2) => 2, U1(0) => 1, U1(1) => 2]) + + @test gradedisequal(label_dual(g1), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) + + gt = gradedrange([ + U1(-3) => 2, + U1(-2) => 2, + U1(-1) => 4, + U1(-1) => 1, + U1(0) => 1, + U1(1) => 2, + U1(0) => 2, + U1(1) => 2, + U1(2) => 4, + ]) + gf = gradedrange([ + U1(-3) => 2, U1(-2) => 2, U1(-1) => 5, U1(0) => 3, U1(1) => 4, U1(2) => 4 + ]) + @test gradedisequal((@inferred tensor_product(g1, g2)), gt) + @test gradedisequal((@inferred fusion_product(g1, g2)), gf) + + gtd1 = gradedrange([ + U1(-1) => 2, + U1(-2) => 2, + U1(-3) => 4, + U1(1) => 1, + U1(0) => 1, + U1(-1) => 2, + U1(2) => 2, + U1(1) => 2, + U1(0) => 4, + ]) + gfd1 = gradedrange([ + U1(-3) => 4, U1(-2) => 2, U1(-1) => 4, U1(0) => 5, U1(1) => 3, U1(2) => 2 + ]) + @test gradedisequal((@inferred tensor_product(dual(g1), g2)), gtd1) + @test gradedisequal((@inferred fusion_product(dual(g1), g2)), gfd1) + + gtd2 = gradedrange([ + U1(1) => 2, + U1(2) => 2, + U1(3) => 4, + U1(-1) => 1, + U1(0) => 1, + U1(1) => 2, + U1(-2) => 2, + U1(-1) => 2, + U1(0) => 4, + ]) + gfd2 = gradedrange([ + U1(-2) => 2, U1(-1) => 3, U1(0) => 5, U1(1) => 4, U1(2) => 2, U1(3) => 4 + ]) + @test gradedisequal((@inferred tensor_product(g1, dual(g2))), gtd2) + @test gradedisequal((@inferred fusion_product(g1, dual(g2))), gfd2) + + gtd = gradedrange([ + U1(3) => 2, + U1(2) => 2, + U1(1) => 4, + U1(1) => 1, + U1(0) => 1, + U1(-1) => 2, + U1(0) => 2, + U1(-1) => 2, + U1(-2) => 4, + ]) + gfd = gradedrange([ + U1(-2) => 4, U1(-1) => 4, U1(0) => 3, U1(1) => 5, U1(2) => 2, U1(3) => 2 + ]) + @test gradedisequal((@inferred tensor_product(dual(g1), dual(g2))), gtd) + @test gradedisequal((@inferred fusion_product(dual(g1), dual(g2))), gfd) + + # test different (non-product) categories cannot be fused + @test_throws MethodError fusion_product(gradedrange([Z{2}(0) => 1]), g1) + @test_throws MethodError tensor_product(gradedrange([Z{2}(0) => 1]), g2) + end + @testset "GradedUnitRange non-abelian fusion rules" begin g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2, SU2(1) => 1]) g4 = gradedrange([SU2(1//2) => 1, SU2(1) => 2]) + + # test tensor_product not defined for abelian + @test_throws ErrorException tensor_product(g3, g4) + + @test gradedisequal(label_dual(g3), g3) # trivial for SU(2) @test gradedisequal( (@inferred fusion_product(g3, g4)), gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), ) - # test different categories cannot be fused - @test_throws MethodError fusion_product(g1, g4) + # test dual on non self-conjugate non-abelian representations + s1 = SU{3}((0, 0, 0)) + f3 = SU{3}((1, 0, 0)) + c3 = SU{3}((1, 1, 0)) + ad8 = SU{3}((2, 1, 0)) + + g5 = gradedrange([s1 => 1, f3 => 1]) + g6 = gradedrange([s1 => 1, c3 => 1]) + @test gradedisequal(label_dual(g5), g6) + @test gradedisequal( + fusion_product(g5, g6), gradedrange([s1 => 2, f3 => 1, c3 => 1, ad8 => 1]) + ) + @test gradedisequal( + fusion_product(dual(g5), g6), + gradedrange([s1 => 1, f3 => 1, c3 => 2, SU{3}((2, 2, 0)) => 1]), + ) + @test gradedisequal( + fusion_product(g5, dual(g6)), + gradedrange([s1 => 1, f3 => 2, c3 => 1, SU{3}((2, 0, 0)) => 1]), + ) + @test gradedisequal( + fusion_product(dual(g5), dual(g6)), gradedrange([s1 => 2, f3 => 1, c3 => 1, ad8 => 1]) + ) end @testset "Mixed GradedUnitRange - Category fusion rules" begin From 06febc1ce9322aba7d12a8fba72113b2bd1ec30f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 8 Apr 2024 16:21:57 -0400 Subject: [PATCH 033/194] simplify quantum_dimensions --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index b97467c639..8b6682da94 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -33,9 +33,8 @@ quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 quantum_dimension(::EmptyCategory, ::AbstractCategory) = 0 function quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) - mult = LabelledNumbers.unlabel.(BlockArrays.blocklengths(g)) - dims = quantum_dimension.(LabelledNumbers.label.(BlockArrays.blocklengths(g))) - return sum(m * d for (m, d) in zip(mult, dims)) + gblocks = BlockArrays.blocklengths(g) + return sum(gblocks .* quantum_dimension.(LabelledNumbers.label.(gblocks))) end quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) From 2396e5e4307cbe5f65e3eaef6f6c097a206c94f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 8 Apr 2024 18:41:36 -0400 Subject: [PATCH 034/194] blockmergesortperm for dual --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 25 ++++++++++--------- .../src/lib/GradedAxes/test/test_dual.jl | 12 +++++++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 19ae9b8e94..0912fe6c89 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -74,12 +74,16 @@ function tensor_product(a1::BlockedUnitRange, a2::BlockedUnitRange) end function blocksortperm(a::BlockedUnitRange) - # TODO: Figure out how to deal with dual sectors. - # TODO: `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`. - ## return Block.(sortperm(nondual_sectors(a); rev=isdual(a))) return Block.(sortperm(blocklabels(a))) end +function blocksortperm(a::UnitRangeDual) + # If it is dual, reverse the sorting so the sectors + # end up sorted in the same way whether or not the space + # is dual. + return Block.(sortperm(blocklabels(label_dual(dual(a))))) +end + using BlockArrays: Block, BlockVector using SplitApplyCombine: groupcount # Get the permutation for sorting, then group by common elements. @@ -95,12 +99,6 @@ end # Get the permutation for sorting, then group by common elements. # groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]] function blockmergesortperm(a::BlockedUnitRange) - # If it is dual, reverse the sorting so the sectors - # end up sorted in the same way whether or not the space - # is dual. - # TODO: Figure out how to deal with dual sectors. - # TODO: `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`. - ## return Block.(groupsortperm(nondual_sectors(a); rev=isdual(a))) return Block.(groupsortperm(blocklabels(a))) end @@ -108,13 +106,16 @@ end invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) # Used by `TensorAlgebra.fusedims` in `BlockSparseArraysGradedAxesExt`. +# TBD remove me? Same as a::BlockedUnitRange? function blockmergesortperm(a::GradedUnitRange) + return Block.(groupsortperm(blocklabels(a))) +end + +function blockmergesortperm(a::UnitRangeDual) # If it is dual, reverse the sorting so the sectors # end up sorted in the same way whether or not the space # is dual. - # TODO: Figure out how to deal with dual sectors. - # TODO: `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`. - return Block.(groupsortperm(blocklabels(a))) + return Block.(groupsortperm(blocklabels(label_dual(dual(a))))) end # fusion_product generalizes tensor_product to non-abelian groups and fusion categories diff --git a/NDTensors/src/lib/GradedAxes/test/test_dual.jl b/NDTensors/src/lib/GradedAxes/test/test_dual.jl index 0fb15d31bf..f0db3698b5 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_dual.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_dual.jl @@ -1,12 +1,14 @@ @eval module $(gensym()) -using BlockArrays: Block, blockaxes, blockfirsts, blocklasts, blocks, findblock -using NDTensors.GradedAxes: GradedAxes, UnitRangeDual, dual, gradedrange, nondual +using BlockArrays: Block, blockaxes, blockfirsts, blocklasts, blocklength, blocks, findblock +using NDTensors.GradedAxes: + GradedAxes, UnitRangeDual, blockmergesortperm, blocksortperm, dual, gradedrange, nondual using NDTensors.LabelledNumbers: LabelledInteger, label, labelled using Test: @test, @test_broken, @testset struct U1 n::Int end GradedAxes.dual(c::U1) = U1(-c.n) +Base.isless(c1::U1, c2::U1) = c1.n < c2.n @testset "dual" begin a = gradedrange([U1(0) => 2, U1(1) => 3]) ad = dual(a) @@ -34,5 +36,11 @@ GradedAxes.dual(c::U1) = U1(-c.n) @test label(ad[[Block(2), Block(1)]][Block(1)]) == U1(-1) @test ad[[Block(2)[1:2], Block(1)[1:2]]][Block(1)] == 3:4 @test label(ad[[Block(2)[1:2], Block(1)[1:2]]][Block(1)]) == U1(-1) + @test blocksortperm(a) == [Block(1), Block(2)] + @test blocksortperm(ad) == [Block(2), Block(1)] + @test blocklength(blockmergesortperm(a)) == 2 + @test blocklength(blockmergesortperm(ad)) == 2 + @test blockmergesortperm(a) == [Block(1), Block(2)] + @test blockmergesortperm(ad) == [Block(2), Block(1)] end end From dfa478d9893e5f3d88200024acc0753069b88b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 8 Apr 2024 19:20:10 -0400 Subject: [PATCH 035/194] remove unused functions --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 6 ------ NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl | 13 ------------- NDTensors/src/lib/GradedAxes/test/test_basics.jl | 7 +------ 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 0912fe6c89..a7057f4751 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -105,12 +105,6 @@ end # Used by `TensorAlgebra.splitdims` in `BlockSparseArraysGradedAxesExt`. invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) -# Used by `TensorAlgebra.fusedims` in `BlockSparseArraysGradedAxesExt`. -# TBD remove me? Same as a::BlockedUnitRange? -function blockmergesortperm(a::GradedUnitRange) - return Block.(groupsortperm(blocklabels(a))) -end - function blockmergesortperm(a::UnitRangeDual) # If it is dual, reverse the sorting so the sectors # end up sorted in the same way whether or not the space diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index 03fdc67ea2..076e95bc31 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -85,19 +85,6 @@ function gradedrange(lblocklengths::AbstractVector{<:Pair{<:Any,<:Integer}}) return gradedrange(labelled.(last.(lblocklengths), first.(lblocklengths))) end -# Generic function for concatenating axes with blocks. -function blockedunitrange_axis_cat(a::AbstractUnitRange, b::AbstractUnitRange) - return blockedrange(vcat(blocklengths(a), blocklengths(b))) -end - -axis_cat(a::GradedUnitRange, b::GradedUnitRange) = blockedunitrange_axis_cat(a, b) - -axis_cat(a::BlockedUnitRange, b::BlockedUnitRange) = blockedunitrange_axis_cat(a, b) - -# Assume in general there aren't blocks. -# Probably should check the axes are one-based. -axis_cat(a::AbstractUnitRange, b::AbstractUnitRange) = Base.OneTo(length(a) + length(b)) - function labelled_blocks(a::BlockedUnitRange, labels) return BlockArrays._BlockedUnitRange(a.first, labelled.(a.lasts, labels)) end diff --git a/NDTensors/src/lib/GradedAxes/test/test_basics.jl b/NDTensors/src/lib/GradedAxes/test/test_basics.jl index 3119612962..aa3a9d7560 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_basics.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_basics.jl @@ -8,7 +8,7 @@ using BlockArrays: blocklength, blocklengths, blocks -using NDTensors.GradedAxes: GradedUnitRange, blocklabels, axis_cat, gradedrange +using NDTensors.GradedAxes: GradedUnitRange, blocklabels, gradedrange using NDTensors.LabelledNumbers: LabelledUnitRange, label, labelled, unlabel using Test: @test, @test_broken, @testset @testset "GradedAxes basics" begin @@ -120,10 +120,5 @@ using Test: @test, @test_broken, @testset @test blocklabels(a) == ["z", "y"] @test a[Block(1)] == 7:8 @test a[Block(2)] == 4:5 - - x = gradedrange(["x" => 2, "y" => 3]) - y = gradedrange(["x" => 1, "z" => 2]) - z = axis_cat(x, y) - @test z == gradedrange(["x" => 2, "y" => 3, "x" => 1, "z" => 2]) end end From 0000e5c2a083ca1f2975c0470d8caef7124a324b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 9 Apr 2024 10:05:22 -0400 Subject: [PATCH 036/194] add more comments --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 5 +++-- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 1 + NDTensors/src/lib/Sectors/src/symmetry_style.jl | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index a7057f4751..7207f1cc41 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -38,6 +38,7 @@ function tensor_product(a1::OneToOne, a2::OneToOne) return OneToOne() end +# Handle dual. Always return a non-dual GradedUnitRange. function tensor_product(a1::AbstractUnitRange, a2::UnitRangeDual) return tensor_product(a1, label_dual(dual(a2))) end @@ -113,12 +114,12 @@ function blockmergesortperm(a::UnitRangeDual) end # fusion_product generalizes tensor_product to non-abelian groups and fusion categories -# in the case of abelian groups, it is equivalent to blockmergesortperm ∘ tensor_product -# deal with dual. Always return a non-dual GradedUnitRange. +# in the case of abelian groups, it is equivalent to tensor_product + applying blockmergesortperm function fusion_product(a1::AbstractUnitRange, a2::AbstractUnitRange) return error("Not implemented") end +# Handle dual. Always return a non-dual GradedUnitRange. function fusion_product(g1::UnitRangeDual, g2::AbstractUnitRange) return fusion_product(label_dual(dual(g1)), g2) end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 8b6682da94..9ac0ce81d7 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -65,6 +65,7 @@ function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) end # convenient to define fusion rule for LabelledInteger too +# TBD expose this through ⊗? Currently not accessible. function fusion_rule( ::SymmetryStyle, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index a796b3d3c8..400e80a002 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -1,3 +1,6 @@ +# This file defines SymmetryStyle, a trait to distinguish abelian groups, non-abelian groups +# and non-group fusion categories. + using BlockArrays using NDTensors.LabelledNumbers @@ -8,7 +11,7 @@ abstract type SymmetryStyle end struct AbelianGroup <: SymmetryStyle end struct NonAbelianGroup <: SymmetryStyle end struct NonGroupCategory <: SymmetryStyle end -struct EmptyCategory <: SymmetryStyle end +struct EmptyCategory <: SymmetryStyle end # CategoryProduct with zero category inside combine_styles(::AbelianGroup, ::AbelianGroup) = AbelianGroup() combine_styles(::AbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() From 57b3fc8ef71b171f592c49148756c3eb3475bc2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 10 Apr 2024 19:53:32 -0400 Subject: [PATCH 037/194] support julia 1.6 --- .../src/lib/Sectors/src/category_product.jl | 28 ++++--------------- .../lib/Sectors/src/namedtuple_operations.jl | 18 ++++++++++++ .../lib/Sectors/test/test_category_product.jl | 11 ++++---- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 22d1036abc..e1b795f4fe 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -66,7 +66,7 @@ end function categories_product(l1::NamedTuple, l2::NamedTuple) if length(intersect_keys(l1, l2)) > 0 - throw(MethodError("Cannot define product of shared keys")) + throw(error("Cannot define product of shared keys")) end return union_keys(l1, l2) end @@ -137,37 +137,19 @@ end # allow ⊗ for different types in NamedTuple function categories_fusion_rule(cats1::NamedTuple, cats2::NamedTuple) diff_cat = CategoryProduct(symdiff_keys(cats1, cats2)) - shared1 = intersect_keys(cats1, cats2) - shared2 = intersect_keys(cats2, cats1) + shared1 = pack_named_tuple(intersect_keys(cats1, cats2)) + shared2 = pack_named_tuple(intersect_keys(cats2, cats1)) return diff_cat × categories_fusion_rule(shared1, shared2) end -# iterate over keys -function categories_fusion_rule( - cats1::NT, cats2::NT -) where {Names,NT<:NamedTuple{Names,<:Tuple{AbstractCategory,Vararg{AbstractCategory}}}} - return categories_fusion_rule(cats1[(Names[1],)], cats2[(Names[1],)]) × - categories_fusion_rule(cats1[Names[2:end]], cats2[Names[2:end]]) -end - -# zero key -categories_fusion_rule(cats1::NT, cats2::NT) where {NT<:NamedTuple{}} = sector() - -# one key -function categories_fusion_rule( - cats1::NT, cats2::NT -) where {NT<:NamedTuple{<:Any,<:Tuple{AbstractCategory}}} - return single_fusion_rule(SymmetryStyle(cats1), cats1, cats2) -end - # abelian fusion of one category -function single_fusion_rule(::AbelianGroup, cats1::NT, cats2::NT) where {NT} +function fusion_rule(::AbelianGroup, cats1::NT, cats2::NT) where {NT<:NamedTuple} fused = fusion_rule(only(values(cats1)), only(values(cats2))) return sector(only(keys(cats1)) => fused) end # generic fusion of one category -function single_fusion_rule(::SymmetryStyle, cats1::NT, cats2::NT) where {NT} +function fusion_rule(::SymmetryStyle, cats1::NT, cats2::NT) where {NT<:NamedTuple} fused = fusion_rule(only(values(cats1)), only(values(cats2))) key = only(keys(cats1)) v = Vector{Pair{CategoryProduct{NT},Int64}}() diff --git a/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl b/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl index a325dea2b2..bb5b68e375 100644 --- a/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl +++ b/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl @@ -14,3 +14,21 @@ setdiff_keys(ns1::NamedTuple, ns2::NamedTuple) = Base.structdiff(ns1, ns2) function symdiff_keys(ns1::NamedTuple, ns2::NamedTuple) return merge(Base.structdiff(ns1, ns2), Base.structdiff(ns2, ns1)) end + +# iterate over keys +# maps (;a=0, b=1, c="x") to ((;a=1), (;b=2), (;c="x")) +function pack_named_tuple(nt::NamedTuple) + name1 = first(keys(nt)) + t1 = (; name1 => nt[name1]) + return (t1, pack_named_tuple(symdiff_keys(t1, nt))...) +end + +# one key +function pack_named_tuple(nt::NamedTuple{<:Any,<:Tuple{<:Any}}) + return (nt,) +end + +# zero key +function pack_named_tuple(nt::NamedTuple{()}) + return () +end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index e0497d68ef..c318a1a025 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -23,12 +23,12 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 @test categories(s)[:C] == Ising("ψ") - @test (@inferred quantum_dimension(s)) == 5.0 + @test quantum_dimension(s) == 5.0 # type not inferred for Julia 1.6 only @test dual(s) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) s1 = (A=U1(1),) × (B=Z{2}(0),) s2 = (A=U1(1),) × (C=Z{2}(0),) - @test_throws MethodError s1 × s2 + @test_throws ErrorException s1 × s2 end @testset "Construct from Pairs" begin @@ -98,7 +98,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]) - @test (@inferred quantum_dimension(g)) == 8.0 + @test quantum_dimension(g) == 8.0 g = gradedrange([ sector(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, @@ -197,7 +197,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws # Put names in reverse order sometimes: q1h = (J=SU2(1//2),) × (N=U1(1),) q11 = (N=U1(1),) × (J=SU2(1),) - q20 = sector(; N=U1(2)) + q20 = (N=U1(2),) × (J=SU2(0),) # julia 1.6 does not accept gradedrange without J q2h = (N=U1(2),) × (J=SU2(1//2),) q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) @@ -246,7 +246,8 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws g2 = gradedrange([s2 => 1]) s3 = sector(; A=U1(1), B=SU2(0), C=Ising("σ")) s4 = sector(; A=U1(1), B=SU2(1), C=Ising("σ")) - @test gradedisequal((@inferred fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2])) + # type not inferred on julia 1.6 only + @test gradedisequal(fusion_product(g1, g2), gradedrange([s3 => 2, s4 => 2])) sA = sector(; A=U1(1)) sB = sector(; B=SU2(1//2)) From c6f24f8a2fb553c77476ccf0d4246d5d21e0f17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 10 Apr 2024 21:21:01 -0400 Subject: [PATCH 038/194] Swap Ordered Products and Named Category Products Named Category implementation calls Ordered Products, better to define and test Ordered Product first. --- .../src/lib/Sectors/src/category_product.jl | 34 +- .../lib/Sectors/test/test_category_product.jl | 440 +++++++++--------- 2 files changed, 237 insertions(+), 237 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index e1b795f4fe..11c5866f1b 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -113,6 +113,23 @@ function fusion_rule(::EmptyCategory, s1::CategoryProduct, s2::CategoryProduct) return s1 × s2 end +# ============== Ordered implementation ================= +CategoryProduct(t::Tuple) = _CategoryProduct(t) +CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) + +categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) + +sector(args...; kws...) = CategoryProduct(args...; kws...) + +# allow additional categories at one end +function categories_fusion_rule(cats1::Tuple, cats2::Tuple) + n = min(length(cats1), length(cats2)) + shared = map(fusion_rule, cats1[begin:n], cats2[begin:n]) + sup1 = CategoryProduct(cats1[(n + 1):end]) + sup2 = CategoryProduct(cats2[(n + 1):end]) + return reduce(×, (shared..., sup1, sup2)) +end + # ============== Dictionary-like implementation ================= function CategoryProduct(nt::NamedTuple) categories = sort_keys(nt) @@ -159,20 +176,3 @@ function fusion_rule(::SymmetryStyle, cats1::NT, cats2::NT) where {NT<:NamedTupl g = GradedAxes.gradedrange(v) return g end - -# ============== Ordered implementation ================= -CategoryProduct(t::Tuple) = _CategoryProduct(t) -CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) - -categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) - -sector(args...; kws...) = CategoryProduct(args...; kws...) - -# allow additional categories at one end -function categories_fusion_rule(cats1::Tuple, cats2::Tuple) - n = min(length(cats1), length(cats2)) - shared = map(fusion_rule, cats1[begin:n], cats2[begin:n]) - sup1 = CategoryProduct(cats1[(n + 1):end]) - sup2 = CategoryProduct(cats2[(n + 1):end]) - return reduce(×, (shared..., sup1, sup2)) -end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index c318a1a025..89c5ca573d 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -4,6 +4,226 @@ using NDTensors.Sectors: using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange using Test: @inferred, @test, @testset, @test_broken, @test_throws +@testset "Test Ordered Products" begin + @testset "Ordered Constructor" begin + s = sector(U1(1), U1(2)) + @test length(categories(s)) == 2 + @test (@inferred quantum_dimension(s)) == 1 + @test dual(s) == sector(U1(-1), U1(-2)) + @test categories(s)[1] == U1(1) + @test categories(s)[2] == U1(2) + + s = U1(1) × SU2(1//2) × U1(3) + @test length(categories(s)) == 3 + @test (@inferred quantum_dimension(s)) == 2 + @test dual(s) == U1(-1) × SU2(1//2) × U1(-3) + @test categories(s)[1] == U1(1) + @test categories(s)[2] == SU2(1//2) + @test categories(s)[3] == U1(3) + + s = U1(3) × SU2(1//2) × Fib("τ") + @test length(categories(s)) == 3 + @test (@inferred quantum_dimension(s)) == 1.0 + √5 + @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") + @test categories(s)[1] == U1(3) + @test categories(s)[2] == SU2(1//2) + @test categories(s)[3] == Fib("τ") + end + + @testset "Quantum dimension and GradedUnitRange" begin + g = gradedrange([(U1(0) × Z{2}(0)) => 1, (U1(1) × Z{2}(0)) => 2]) # abelian + @test (@inferred quantum_dimension(g)) == 3 + + g = gradedrange([ # non-abelian + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 16 + + # mixed group + g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) + @test (@inferred quantum_dimension(g)) == 4 + g = gradedrange([(SU2(0) × U1(0) × SU2(1//2)) => 1, (SU2(0) × U1(1) × SU2(1//2)) => 1]) + @test (@inferred quantum_dimension(g)) == 4 + + # NonGroupCategory + g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) + g_ising = gradedrange([(Ising("1") × Ising("1")) => 1]) + @test (@inferred quantum_dimension((Fib("1") × Fib("1")))) == 1.0 + @test (@inferred quantum_dimension(g_fib)) == 1.0 + @test (@inferred quantum_dimension(g_ising)) == 1.0 + @test (@inferred quantum_dimension((Ising("1") × Ising("1")))) == 1.0 + + @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 + @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 + + # mixed product Abelian / NonAbelian / NonGroup + g = gradedrange([ + (U1(2) × SU2(0) × Ising(1)) => 1, + (U1(2) × SU2(1) × Ising(1)) => 1, + (U1(2) × SU2(0) × Ising("ψ")) => 1, + (U1(2) × SU2(1) × Ising("ψ")) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 8.0 + + g = gradedrange([ + (Fib("1") × SU2(0) × U1(2)) => 1, + (Fib("1") × SU2(1) × U1(2)) => 1, + (Fib("τ") × SU2(0) × U1(2)) => 1, + (Fib("τ") × SU2(1) × U1(2)) => 1, + ]) + @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) + end + + @testset "Empty category" begin + s = CategoryProduct(()) + @test (@inferred dual(s)) == s + @test (@inferred s × s) == s + @test (@inferred s ⊗ s) == s + @test (@inferred quantum_dimension(s)) == 0 + end + + @testset "Fusion of Abelian products" begin + p1 = sector(U1(1)) + p2 = sector(U1(2)) + @test (@inferred p1 ⊗ p2) == sector(U1(3)) + + p11 = U1(1) × U1(1) + @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) + + p123 = U1(1) × U1(2) × U1(3) + @test (@inferred p123 ⊗ p123) == U1(2) × U1(4) × U1(6) + + s1 = sector(U1(1), Z{2}(1)) + s2 = sector(U1(0), Z{2}(0)) + @test (@inferred s1 ⊗ s2) == U1(1) × Z{2}(1) + end + + @testset "Fusion of NonAbelian products" begin + p0 = sector(SU2(0)) + ph = sector(SU2(1//2)) + @test gradedisequal((@inferred p0 ⊗ ph), gradedrange([sector(SU2(1//2)) => 1])) + + phh = SU2(1//2) × SU2(1//2) + @test gradedisequal( + phh ⊗ phh, + gradedrange([ + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, + ]), + ) + @test gradedisequal( + (@inferred phh ⊗ phh), + gradedrange([ + (SU2(0) × SU2(0)) => 1, + (SU2(1) × SU2(0)) => 1, + (SU2(0) × SU2(1)) => 1, + (SU2(1) × SU2(1)) => 1, + ]), + ) + end + + @testset "Fusion of NonGroupCategory products" begin + ı = Fib("1") + τ = Fib("τ") + s = ı × ı + @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) + + s = τ × τ + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]), + ) + + σ = Ising("σ") + ψ = Ising("ψ") + s = τ × σ + g = gradedrange([ + (ı × Ising("1")) => 1, (τ × Ising("1")) => 1, (ı × ψ) => 1, (τ × ψ) => 1 + ]) + @test gradedisequal((@inferred s ⊗ s), g) + end + + @testset "Fusion of mixed Abelian and NonAbelian products" begin + p2h = U1(2) × SU2(1//2) + p1h = U1(1) × SU2(1//2) + @test gradedisequal( + (@inferred p2h ⊗ p1h), gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) + ) + + p1h1 = U1(1) × SU2(1//2) × Z{2}(1) + @test gradedisequal( + (@inferred p1h1 ⊗ p1h1), + gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]), + ) + end + + @testset "Fusion of fully mixed products" begin + s = U1(1) × SU2(1//2) × Ising("σ") + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([ + (U1(2) × SU2(0) × Ising("1")) => 1, + (U1(2) × SU2(1) × Ising("1")) => 1, + (U1(2) × SU2(0) × Ising("ψ")) => 1, + (U1(2) × SU2(1) × Ising("ψ")) => 1, + ]), + ) + + ı = Fib("1") + τ = Fib("τ") + s = SU2(1//2) × U1(1) × τ + @test gradedisequal( + (@inferred s ⊗ s), + gradedrange([ + (SU2(0) × U1(2) × ı) => 1, + (SU2(1) × U1(2) × ı) => 1, + (SU2(0) × U1(2) × τ) => 1, + (SU2(1) × U1(2) × τ) => 1, + ]), + ) + + s = U1(1) × ı × τ + @test gradedisequal( + (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) + ) + end + + @testset "Fusion of different length Categories" begin + @test sector(U1(1) × U1(0)) ⊗ sector(U1(1)) == sector(U1(2) × U1(0)) + @test gradedisequal( + sector(SU2(0) × SU2(0)) ⊗ sector(SU2(1)), gradedrange([sector(SU2(1) × SU2(0)) => 1]) + ) + + @test gradedisequal( + sector(SU2(1) × U1(1)) ⊗ sector(SU2(0)), gradedrange([sector(SU2(1) × U1(1)) => 1]) + ) + @test gradedisequal( + sector(U1(1) × SU2(1)) ⊗ sector(U1(2)), gradedrange([sector(U1(3) × SU2(1)) => 1]) + ) + + # check incompatible categories + p12 = Z{2}(1) × U1(2) + z12 = Z{2}(1) × Z{2}(1) + @test_throws MethodError p12 ⊗ z12 + end + + @testset "GradedUnitRange fusion rules" begin + s1 = U1(1) × SU2(1//2) × Ising("σ") + s2 = U1(0) × SU2(1//2) × Ising("1") + g1 = gradedrange([s1 => 2]) + g2 = gradedrange([s2 => 1]) + @test gradedisequal( + (@inferred fusion_product(g1, g2)), + gradedrange([U1(1) × SU2(0) × Ising("σ") => 2, U1(1) × SU2(1) × Ising("σ") => 2]), + ) + end +end + @testset "Test Named Category Products" begin @testset "Construct from × of NamedTuples" begin s = (A=U1(1),) × (B=Z{2}(0),) @@ -257,224 +477,4 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test gradedisequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) end end - -@testset "Test Ordered Products" begin - @testset "Ordered Constructor" begin - s = sector(U1(1), U1(2)) - @test length(categories(s)) == 2 - @test (@inferred quantum_dimension(s)) == 1 - @test dual(s) == sector(U1(-1), U1(-2)) - @test categories(s)[1] == U1(1) - @test categories(s)[2] == U1(2) - - s = U1(1) × SU2(1//2) × U1(3) - @test length(categories(s)) == 3 - @test (@inferred quantum_dimension(s)) == 2 - @test dual(s) == U1(-1) × SU2(1//2) × U1(-3) - @test categories(s)[1] == U1(1) - @test categories(s)[2] == SU2(1//2) - @test categories(s)[3] == U1(3) - - s = U1(3) × SU2(1//2) × Fib("τ") - @test length(categories(s)) == 3 - @test (@inferred quantum_dimension(s)) == 1.0 + √5 - @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") - @test categories(s)[1] == U1(3) - @test categories(s)[2] == SU2(1//2) - @test categories(s)[3] == Fib("τ") - end - - @testset "Quantum dimension and GradedUnitRange" begin - g = gradedrange([(U1(0) × Z{2}(0)) => 1, (U1(1) × Z{2}(0)) => 2]) # abelian - @test (@inferred quantum_dimension(g)) == 3 - - g = gradedrange([ # non-abelian - (SU2(0) × SU2(0)) => 1, - (SU2(1) × SU2(0)) => 1, - (SU2(0) × SU2(1)) => 1, - (SU2(1) × SU2(1)) => 1, - ]) - @test (@inferred quantum_dimension(g)) == 16 - - # mixed group - g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) - @test (@inferred quantum_dimension(g)) == 4 - g = gradedrange([(SU2(0) × U1(0) × SU2(1//2)) => 1, (SU2(0) × U1(1) × SU2(1//2)) => 1]) - @test (@inferred quantum_dimension(g)) == 4 - - # NonGroupCategory - g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) - g_ising = gradedrange([(Ising("1") × Ising("1")) => 1]) - @test (@inferred quantum_dimension((Fib("1") × Fib("1")))) == 1.0 - @test (@inferred quantum_dimension(g_fib)) == 1.0 - @test (@inferred quantum_dimension(g_ising)) == 1.0 - @test (@inferred quantum_dimension((Ising("1") × Ising("1")))) == 1.0 - - @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 - @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 - - # mixed product Abelian / NonAbelian / NonGroup - g = gradedrange([ - (U1(2) × SU2(0) × Ising(1)) => 1, - (U1(2) × SU2(1) × Ising(1)) => 1, - (U1(2) × SU2(0) × Ising("ψ")) => 1, - (U1(2) × SU2(1) × Ising("ψ")) => 1, - ]) - @test (@inferred quantum_dimension(g)) == 8.0 - - g = gradedrange([ - (Fib("1") × SU2(0) × U1(2)) => 1, - (Fib("1") × SU2(1) × U1(2)) => 1, - (Fib("τ") × SU2(0) × U1(2)) => 1, - (Fib("τ") × SU2(1) × U1(2)) => 1, - ]) - @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) - end - - @testset "Empty category" begin - s = CategoryProduct(()) - @test (@inferred dual(s)) == s - @test (@inferred s × s) == s - @test (@inferred s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 0 - end - - @testset "Fusion of Abelian products" begin - p1 = sector(U1(1)) - p2 = sector(U1(2)) - @test (@inferred p1 ⊗ p2) == sector(U1(3)) - - p11 = U1(1) × U1(1) - @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) - - p123 = U1(1) × U1(2) × U1(3) - @test (@inferred p123 ⊗ p123) == U1(2) × U1(4) × U1(6) - - s1 = sector(U1(1), Z{2}(1)) - s2 = sector(U1(0), Z{2}(0)) - @test (@inferred s1 ⊗ s2) == U1(1) × Z{2}(1) - end - - @testset "Fusion of NonAbelian products" begin - p0 = sector(SU2(0)) - ph = sector(SU2(1//2)) - @test gradedisequal((@inferred p0 ⊗ ph), gradedrange([sector(SU2(1//2)) => 1])) - - phh = SU2(1//2) × SU2(1//2) - @test gradedisequal( - phh ⊗ phh, - gradedrange([ - (SU2(0) × SU2(0)) => 1, - (SU2(1) × SU2(0)) => 1, - (SU2(0) × SU2(1)) => 1, - (SU2(1) × SU2(1)) => 1, - ]), - ) - @test gradedisequal( - (@inferred phh ⊗ phh), - gradedrange([ - (SU2(0) × SU2(0)) => 1, - (SU2(1) × SU2(0)) => 1, - (SU2(0) × SU2(1)) => 1, - (SU2(1) × SU2(1)) => 1, - ]), - ) - end - - @testset "Fusion of NonGroupCategory products" begin - ı = Fib("1") - τ = Fib("τ") - s = ı × ı - @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) - - s = τ × τ - @test gradedisequal( - (@inferred s ⊗ s), - gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]), - ) - - σ = Ising("σ") - ψ = Ising("ψ") - s = τ × σ - g = gradedrange([ - (ı × Ising("1")) => 1, (τ × Ising("1")) => 1, (ı × ψ) => 1, (τ × ψ) => 1 - ]) - @test gradedisequal((@inferred s ⊗ s), g) - end - - @testset "Fusion of mixed Abelian and NonAbelian products" begin - p2h = U1(2) × SU2(1//2) - p1h = U1(1) × SU2(1//2) - @test gradedisequal( - (@inferred p2h ⊗ p1h), gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) - ) - - p1h1 = U1(1) × SU2(1//2) × Z{2}(1) - @test gradedisequal( - (@inferred p1h1 ⊗ p1h1), - gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]), - ) - end - - @testset "Fusion of fully mixed products" begin - s = U1(1) × SU2(1//2) × Ising("σ") - @test gradedisequal( - (@inferred s ⊗ s), - gradedrange([ - (U1(2) × SU2(0) × Ising("1")) => 1, - (U1(2) × SU2(1) × Ising("1")) => 1, - (U1(2) × SU2(0) × Ising("ψ")) => 1, - (U1(2) × SU2(1) × Ising("ψ")) => 1, - ]), - ) - - ı = Fib("1") - τ = Fib("τ") - s = SU2(1//2) × U1(1) × τ - @test gradedisequal( - (@inferred s ⊗ s), - gradedrange([ - (SU2(0) × U1(2) × ı) => 1, - (SU2(1) × U1(2) × ı) => 1, - (SU2(0) × U1(2) × τ) => 1, - (SU2(1) × U1(2) × τ) => 1, - ]), - ) - - s = U1(1) × ı × τ - @test gradedisequal( - (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) - ) - end - - @testset "Fusion of different length Categories" begin - @test sector(U1(1) × U1(0)) ⊗ sector(U1(1)) == sector(U1(2) × U1(0)) - @test gradedisequal( - sector(SU2(0) × SU2(0)) ⊗ sector(SU2(1)), gradedrange([sector(SU2(1) × SU2(0)) => 1]) - ) - - @test gradedisequal( - sector(SU2(1) × U1(1)) ⊗ sector(SU2(0)), gradedrange([sector(SU2(1) × U1(1)) => 1]) - ) - @test gradedisequal( - sector(U1(1) × SU2(1)) ⊗ sector(U1(2)), gradedrange([sector(U1(3) × SU2(1)) => 1]) - ) - - # check incompatible categories - p12 = Z{2}(1) × U1(2) - z12 = Z{2}(1) × Z{2}(1) - @test_throws MethodError p12 ⊗ z12 - end - - @testset "GradedUnitRange fusion rules" begin - s1 = U1(1) × SU2(1//2) × Ising("σ") - s2 = U1(0) × SU2(1//2) × Ising("1") - g1 = gradedrange([s1 => 2]) - g2 = gradedrange([s2 => 1]) - @test gradedisequal( - (@inferred fusion_product(g1, g2)), - gradedrange([U1(1) × SU2(0) × Ising("σ") => 2, U1(1) × SU2(1) × Ising("σ") => 2]), - ) - end -end end From c0e8e8d0b4f23403ecd91e3dfa3319fd2760d49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 15 Apr 2024 11:02:10 -0400 Subject: [PATCH 039/194] inline pack_named_tuple, adjust tests --- .../src/lib/Sectors/src/category_product.jl | 6 ++-- .../lib/Sectors/src/namedtuple_operations.jl | 18 ---------- .../lib/Sectors/test/test_category_product.jl | 33 ++++++++++--------- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 11c5866f1b..11f2e60483 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -154,8 +154,10 @@ end # allow ⊗ for different types in NamedTuple function categories_fusion_rule(cats1::NamedTuple, cats2::NamedTuple) diff_cat = CategoryProduct(symdiff_keys(cats1, cats2)) - shared1 = pack_named_tuple(intersect_keys(cats1, cats2)) - shared2 = pack_named_tuple(intersect_keys(cats2, cats1)) + nt1 = intersect_keys(cats1, cats2) + shared1 = ntuple(i -> (; keys(nt1)[i] => values(nt1)[i]), length(nt1)) + nt2 = intersect_keys(cats2, cats1) + shared2 = ntuple(i -> (; keys(nt2)[i] => values(nt2)[i]), length(nt2)) return diff_cat × categories_fusion_rule(shared1, shared2) end diff --git a/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl b/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl index bb5b68e375..a325dea2b2 100644 --- a/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl +++ b/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl @@ -14,21 +14,3 @@ setdiff_keys(ns1::NamedTuple, ns2::NamedTuple) = Base.structdiff(ns1, ns2) function symdiff_keys(ns1::NamedTuple, ns2::NamedTuple) return merge(Base.structdiff(ns1, ns2), Base.structdiff(ns2, ns1)) end - -# iterate over keys -# maps (;a=0, b=1, c="x") to ((;a=1), (;b=2), (;c="x")) -function pack_named_tuple(nt::NamedTuple) - name1 = first(keys(nt)) - t1 = (; name1 => nt[name1]) - return (t1, pack_named_tuple(symdiff_keys(t1, nt))...) -end - -# one key -function pack_named_tuple(nt::NamedTuple{<:Any,<:Tuple{<:Any}}) - return (nt,) -end - -# zero key -function pack_named_tuple(nt::NamedTuple{()}) - return () -end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 89c5ca573d..c29f4efc1e 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -4,6 +4,9 @@ using NDTensors.Sectors: using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange using Test: @inferred, @test, @testset, @test_broken, @test_throws +# some types are not correctly inferred on julia 1.6 +# every operation is type stable on julia 1.10 + @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin s = sector(U1(1), U1(2)) @@ -343,11 +346,11 @@ end q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test (@inferred q10 ⊗ q10) == sector(; A=U1(2)) + @test q10 ⊗ q10 == sector(; A=U1(2)) @test (@inferred q01 ⊗ q00) == q01 @test (@inferred q00 ⊗ q01) == q01 @test (@inferred q10 ⊗ q01) == q11 - @test (@inferred q11 ⊗ q11) == sector(; A=U1(2), B=U1(2)) + @test q11 ⊗ q11 == sector(; A=U1(2), B=U1(2)) s11 = sector(; A=U1(1), B=Z{2}(1)) s10 = sector(; A=U1(1)) @@ -355,7 +358,7 @@ end @test (@inferred s01 ⊗ q00) == s01 @test (@inferred q00 ⊗ s01) == s01 @test (@inferred s10 ⊗ s01) == s11 - @test (@inferred s11 ⊗ s11) == sector(; A=U1(2), B=Z{2}(0)) + @test s11 ⊗ s11 == sector(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin @@ -365,14 +368,14 @@ end phab = sector(; A=SU2(1//2), B=SU2(1//2)) @test gradedisequal( - (@inferred pha ⊗ pha), gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) + pha ⊗ pha, gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) ) @test gradedisequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) @test gradedisequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) @test gradedisequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) @test gradedisequal( - (@inferred phab ⊗ phab), + phab ⊗ phab, gradedrange([ sector(; A=SU2(0), B=SU2(0)) => 1, sector(; A=SU2(1), B=SU2(0)) => 1, @@ -386,11 +389,11 @@ end ı = Fib("1") τ = Fib("τ") s = sector(; A=ı, B=ı) - @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) + @test gradedisequal(s ⊗ s, gradedrange([s => 1])) s = sector(; A=τ, B=τ) @test gradedisequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ sector(; A=ı, B=ı) => 1, sector(; A=τ, B=ı) => 1, @@ -408,7 +411,7 @@ end sector(; A=ı, B=ψ) => 1, sector(; A=τ, B=ψ) => 1, ]) - @test gradedisequal((@inferred s ⊗ s), g) + @test gradedisequal(s ⊗ s, g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -422,16 +425,16 @@ end q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test gradedisequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) - @test gradedisequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) - @test gradedisequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) - @test gradedisequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) + @test gradedisequal(q1h ⊗ q1h, gradedrange([q20 => 1, q21 => 1])) + @test gradedisequal(q10 ⊗ q1h, gradedrange([q2h => 1])) + @test gradedisequal(q0h ⊗ q1h, gradedrange([q10 => 1, q11 => 1])) + @test gradedisequal(q11 ⊗ q11, gradedrange([q20 => 1, q21 => 1, q22 => 1])) end @testset "Fusion of fully mixed products" begin s = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) @test gradedisequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ sector(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, sector(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, @@ -444,7 +447,7 @@ end τ = Fib("τ") s = sector(; A=SU2(1//2), B=U1(1), C=τ) @test gradedisequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ sector(; A=SU2(0), B=U1(2), C=ı) => 1, sector(; A=SU2(1), B=U1(2), C=ı) => 1, @@ -455,7 +458,7 @@ end s = sector(; A=τ, B=U1(1), C=ı) @test gradedisequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]), ) end From 0e1869f7c577ff013093b59844531f66d24cadd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 15 Apr 2024 11:09:10 -0400 Subject: [PATCH 040/194] add comment on label type --- NDTensors/src/lib/Sectors/src/category_definitions/u1.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 6e891b9e66..0b3bb0a36a 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -4,6 +4,8 @@ using HalfIntegers: Half # U₁ group (circle group, or particle number, total Sz etc.) # +# use HalfInteger as internal label to allow easy conversion to/from SU(2) +# still allows to call U1(::Int) and to use it as an Int struct U1 <: AbstractCategory n::Half{Int} end From 9b3419c3faf29e9d092dbde0cee48dd6231f5604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 23 Apr 2024 13:20:15 -0400 Subject: [PATCH 041/194] parametric U1 and SU{N}, remove SU2 --- NDTensors/src/lib/Sectors/src/Sectors.jl | 9 ++- .../Sectors/src/category_definitions/ising.jl | 6 +- .../Sectors/src/category_definitions/su.jl | 67 ++++++++++++------- .../Sectors/src/category_definitions/su2.jl | 28 -------- .../Sectors/src/category_definitions/su2k.jl | 4 +- .../Sectors/src/category_definitions/u1.jl | 20 +++--- .../lib/Sectors/test/test_category_product.jl | 4 +- .../src/lib/Sectors/test/test_fusion_rules.jl | 26 ++----- .../Sectors/test/test_simple_categories.jl | 58 ++++++---------- 9 files changed, 89 insertions(+), 133 deletions(-) delete mode 100644 NDTensors/src/lib/Sectors/src/category_definitions/su2.jl diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index 5eaae95b56..f851dc4002 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -2,13 +2,12 @@ module Sectors include("symmetry_style.jl") include("abstractcategory.jl") -include("category_definitions/u1.jl") -include("category_definitions/zn.jl") -include("category_definitions/su.jl") -include("category_definitions/su2.jl") -include("category_definitions/su2k.jl") include("category_definitions/fib.jl") include("category_definitions/ising.jl") +include("category_definitions/su.jl") +include("category_definitions/su2k.jl") +include("category_definitions/u1.jl") +include("category_definitions/zn.jl") include("namedtuple_operations.jl") include("category_product.jl") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index d0d04c7319..d390f524c9 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -1,4 +1,4 @@ -using HalfIntegers: Half, twice +using HalfIntegers: HalfIntegers # # Ising category @@ -7,7 +7,7 @@ using HalfIntegers: Half, twice # struct Ising <: AbstractCategory - l::Half{Int} + l::HalfIntegers.Half{Int} end # TODO: Use `Val` dispatch here? @@ -32,7 +32,7 @@ quantum_dimension(::NonGroupCategory, i::Ising) = (category_label(i) == 1//2) ? label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) # TODO: Use `Val` dispatch here? -label_to_str(i::Ising) = ("1", "σ", "ψ")[twice(category_label(i)) + 1] +label_to_str(i::Ising) = ("1", "σ", "ψ")[HalfIntegers.twice(category_label(i)) + 1] function Base.show(io::IO, f::Ising) return print(io, "Ising(", label_to_str(f), ")") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 3524567dc4..33728d2fd7 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -2,34 +2,37 @@ # Special unitary group SU{N} # -struct SU{N} <: AbstractCategory +struct SU{N,T,M} <: AbstractCategory # l is the first row of the # Gelfand-Tsetlin (GT) pattern describing # an SU(N) irrep - #TODO: any way this could be NTuple{N-1,Int} ? - # not in a natural way - # see https://discourse.julialang.org/t/addition-to-parameter-of-parametric-type/20059/15 - # and https://github.com/JuliaLang/julia/issues/8472 - # can use https://github.com/vtjnash/ComputedFieldTypes.jl - # can define SU{N,M} and impose M=N-1 in the constructor - l::NTuple{N,Int} + l::NTuple{M,T} + + function SU{N,T,M}(t::NTuple{M,T}) where {N,T<:Integer,M} + return N == M + 1 ? new{N,T,M}(t) : error("Invalid tuple length") + end end +SU{N}(t::NTuple{M,T}) where {N,T,M} = SU{N,T,M}(t) + SymmetryStyle(::SU) = NonAbelianGroup() category_label(s::SU) = s.l groupdim(::SU{N}) where {N} = N -trivial(::Type{SU{N}}) where {N} = SU{N}(ntuple(_ -> 0, Val(N))) +trivial(::Type{<:SU{N}}) where {N} = trivial(SU{N,Int}) +trivial(::Type{<:SU{N,T}}) where {N,T} = SU{N}(ntuple(_ -> T(0), Val(N - 1))) -fundamental(::Type{SU{N}}) where {N} = SU{N}(ntuple(i -> Int(i == 1), Val(N))) +fundamental(::Type{<:SU{N}}) where {N} = fundamental(SU{N,Int}) +fundamental(::Type{<:SU{N,T}}) where {N,T} = SU{N}(ntuple(i -> T(i == 1), Val(N - 1))) -adjoint(::Type{SU{N}}) where {N} = SU{N}((ntuple(i -> Int(i == 1) + Int(i < N), Val(N)))) +adjoint(::Type{<:SU{N}}) where {N} = adjoint(SU{N,Int}) +adjoint(::Type{<:SU{N,T}}) where {N,T} = SU{N}((ntuple(i -> T(1 + (i == 1)), Val(N - 1)))) function quantum_dimension(::NonAbelianGroup, s::SU) N = groupdim(s) - l = category_label(s) + l = (category_label(s)..., 0) d = 1 for k1 in 1:N, k2 in (k1 + 1):N d *= ((k2 - k1) + (l[k1] - l[k2]))//(k2 - k1) @@ -39,12 +42,17 @@ end function GradedAxes.dual(s::SU) l = category_label(s) - nl = ((reverse(cumsum(l[begin:(end - 1)] .- l[(begin + 1):end]))..., 0)) + nl = reverse(cumsum((l[begin:(end - 1)] .- l[(begin + 1):end]..., l[end]))) return typeof(s)(nl) end +function Base.show(io::IO, s::SU) + disp = join([string(l) for l in category_label(s)], ", ") + return print(io, "SU(", groupdim(s), ")[", disp, "]") +end + # display SU(N) irrep as a Young tableau with utf8 box char -function Base.show(io::IO, ::MIME"text/plain", s::SU) +function Base.show(io::IO, ::MIME"text/plain", s::SU{N}) where {N} l = category_label(s) if l[1] == 0 # singlet = no box println(io, "●") @@ -53,7 +61,7 @@ function Base.show(io::IO, ::MIME"text/plain", s::SU) println("┌─" * "┬─"^(l[1] - 1) * "┐") i = 1 - while l[i + 1] != 0 + while i < N - 1 && l[i + 1] != 0 println( io, "├─", @@ -72,32 +80,39 @@ end # # Specializations for the case SU{2} # Where irreps specified by quantum_dimension "d" -# TBD remove me? # -quantum_dimension(s::SU{2}) = 1 + category_label(s)[1] +# SU2 is an alias for SU{2} +const SU2 = SU{2} + +# specific constructor for SU{2} with a half-integer +SU{2}(h::Real) = SU{2}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) -SU{2}(d::Integer) = SU{2}((d - 1, 0)) +quantum_dimension(s::SU{2}) = 1 + Int(category_label(s)[1]) GradedAxes.dual(s::SU{2}) = s -function label_fusion_rule(::Type{SU{2}}, s1, s2) - d1 = s1[1] + 1 - d2 = s2[1] + 1 - labels = collect((abs(d1 - d2) + 1):2:(d1 + d2 - 1)) +function label_fusion_rule(::Type{<:SU{2}}, s1, s2) + labels = collect((i,) for i in (abs(s1[1] - s2[1])):2:(s1[1] + s2[1])) degen = ones(Int, length(labels)) return degen, labels end +# display SU2 using half integers function Base.show(io::IO, s::SU{2}) - return print(io, "SU{2}(", quantum_dimension(s), ")") + return print(io, "SU(2)[S=", HalfIntegers.half(quantum_dimension(s) - 1), "]") +end + +function Base.show(io::IO, ::MIME"text/plain", s::SU{2}) + println("S = ", HalfIntegers.half(quantum_dimension(s) - 1)) + return nothing end # Specializations for the case SU{3} # aimed for testing non-abelian non self-conjugate representations # TODO replace with generic implementation -function label_fusion_rule(::Type{SU{3}}, left, right) +function label_fusion_rule(::Type{<:SU{3}}, left, right) # Compute SU(3) fusion rules using Littlewood-Richardson rule for Young tableaus. # See e.g. Di Francesco, Mathieu and Sénéchal, section 13.5.3. if sum(right) > sum(left) # impose more boxes in left Young tableau @@ -137,7 +152,7 @@ function label_fusion_rule(::Type{SU{3}}, left, right) b3min2 = right_row2 + a23 - right_row1 b3min = max(0, b3min1, b3min2) b3max1 = right_row2 # only other.row2 b boxes - b3max2 = (row2a + right_row2 - a3)//2 # row3ab >= row2ab + b3max2 = (row2a + right_row2 - a3) ÷ 2 # row3ab >= row2ab b3max3 = right_row1 - a3 # more a than b, right to left: b2 <= a1 b3max4 = row2a - a3 # no b below b: row2a >= row3ab b3max = min(b3max1, b3max2, b3max3, b3max4) @@ -145,7 +160,7 @@ function label_fusion_rule(::Type{SU{3}}, left, right) b2 = right_row2 - b3 row2ab = row2a + b2 row3ab = a3 + b3 - yt = (row1a - row3ab, row2ab - row3ab, 0) + yt = (row1a - row3ab, row2ab - row3ab) push!(irreps, yt) end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl deleted file mode 100644 index af87b06d79..0000000000 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2.jl +++ /dev/null @@ -1,28 +0,0 @@ -using HalfIntegers: Half, half, twice - -# -# Conventional SU2 group -# using "J" labels -# - -struct SU2 <: AbstractCategory - j::Half{Int} -end - -SymmetryStyle(::SU2) = NonAbelianGroup() - -GradedAxes.dual(s::SU2) = s - -category_label(s::SU2) = s.j - -trivial(::Type{SU2}) = SU2(0) -fundamental(::Type{SU2}) = SU2(half(1)) -adjoint(::Type{SU2}) = SU2(1) - -quantum_dimension(::NonAbelianGroup, s::SU2) = twice(category_label(s)) + 1 - -function label_fusion_rule(::Type{SU2}, j1, j2) - labels = collect(abs(j1 - j2):(j1 + j2)) - degen = ones(Int, length(labels)) - return degen, labels -end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index 66d6c3b2a3..736e9987b8 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -1,11 +1,9 @@ -using HalfIntegers: Half - # # Quantum 'group' su2ₖ # struct su2{k} <: AbstractCategory - j::Half{Int} + j::HalfIntegers.Half{Int} end SymmetryStyle(::su2) = NonGroupCategory() diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 0b3bb0a36a..055b08f630 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -1,13 +1,11 @@ -using HalfIntegers: Half - # # U₁ group (circle group, or particle number, total Sz etc.) # -# use HalfInteger as internal label to allow easy conversion to/from SU(2) -# still allows to call U1(::Int) and to use it as an Int -struct U1 <: AbstractCategory - n::Half{Int} +# Parametric type to allow both integer label as well as +# HalfInteger for easy conversion to/from SU(2) +struct U1{T} <: AbstractCategory + n::T end SymmetryStyle(::U1) = AbelianGroup() @@ -16,6 +14,12 @@ GradedAxes.dual(u::U1) = U1(-u.n) category_label(u::U1) = u.n -trivial(::Type{U1}) = U1(0) +trivial(::Type{U1}) = trivial(U1{Int}) +trivial(::Type{U1{T}}) where {T} = U1(T(0)) + +label_fusion_rule(::Type{<:U1}, n1, n2) = n1 + n2 -label_fusion_rule(::Type{U1}, n1, n2) = n1 + n2 +# hide label type in printing +function Base.show(io::IO, u::U1) + return print(io, "U(1)[", category_label(u), "]") +end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index c29f4efc1e..c6363eec35 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -271,10 +271,10 @@ end @testset "Comparisons with unspecified labels" begin q2 = sector(; N=U1(2)) - q20 = (N=U1(2),) × (J=SU{2}(1),) + q20 = (N=U1(2),) × (J=SU2(0),) @test q20 == q2 - q21 = (N=U1(2),) × (J=SU{2}(3),) + q21 = (N=U1(2),) × (J=SU2(1),) @test q21 != q2 a = (A=U1(0),) × (B=U1(2),) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index f6a1074870..a6a67373e3 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -43,20 +43,6 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 end - @testset "SU{2} fusion rules" begin - j1 = SU{2}(1) - j2 = SU{2}(2) - j3 = SU{2}(3) - j4 = SU{2}(4) - j5 = SU{2}(5) - - @test gradedisequal(j1 ⊗ j2, gradedrange([j2 => 1])) - @test gradedisequal(j2 ⊗ j2, gradedrange([j1 => 1, j3 => 1])) - @test gradedisequal(j2 ⊗ j3, gradedrange([j2 => 1, j4 => 1])) - @test gradedisequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) - @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) - end - @testset "Fibonacci fusion rules" begin ı = Fib("1") τ = Fib("τ") @@ -180,10 +166,10 @@ end ) # test dual on non self-conjugate non-abelian representations - s1 = SU{3}((0, 0, 0)) - f3 = SU{3}((1, 0, 0)) - c3 = SU{3}((1, 1, 0)) - ad8 = SU{3}((2, 1, 0)) + s1 = SU{3}((0, 0)) + f3 = SU{3}((1, 0)) + c3 = SU{3}((1, 1)) + ad8 = SU{3}((2, 1)) g5 = gradedrange([s1 => 1, f3 => 1]) g6 = gradedrange([s1 => 1, c3 => 1]) @@ -193,11 +179,11 @@ end ) @test gradedisequal( fusion_product(dual(g5), g6), - gradedrange([s1 => 1, f3 => 1, c3 => 2, SU{3}((2, 2, 0)) => 1]), + gradedrange([s1 => 1, f3 => 1, c3 => 2, SU{3}((2, 2)) => 1]), ) @test gradedisequal( fusion_product(g5, dual(g6)), - gradedrange([s1 => 1, f3 => 2, c3 => 1, SU{3}((2, 0, 0)) => 1]), + gradedrange([s1 => 1, f3 => 2, c3 => 1, SU{3}((2, 0)) => 1]), ) @test gradedisequal( fusion_product(dual(g5), dual(g6)), gradedrange([s1 => 2, f3 => 1, c3 => 1, ad8 => 1]) diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index e28cd953cd..352234b155 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -46,6 +46,12 @@ using Test: @inferred, @test, @testset j3 = SU2(1) j4 = SU2(3//2) + # alternatative tuple constructor + @test j1 == SU2((0,)) + @test j2 == SU2((1,)) + @test j3 == SU2((2,)) + @test j4 == SU2((3,)) + @test trivial(SU2) == SU2(0) @test istrivial(SU2(0)) @@ -64,48 +70,24 @@ using Test: @inferred, @test, @testset @test dual(j4) == j4 end - @testset "SU(2)" begin - j1 = SU{2}(1) - j2 = SU{2}(2) - j3 = SU{2}(3) - j4 = SU{2}(4) - - @test trivial(SU{2}) == SU{2}(1) - @test istrivial(SU{2}(1)) - - @test fundamental(SU{2}) == SU{2}(2) - @test adjoint(SU{2}) == SU{2}(3) - - @test quantum_dimension(j1) == 1 - @test quantum_dimension(j2) == 2 - @test quantum_dimension(j3) == 3 - @test quantum_dimension(j4) == 4 - @test (@inferred quantum_dimension(j1)) == 1 - - @test dual(j1) == j1 - @test dual(j2) == j2 - @test dual(j3) == j3 - @test dual(j4) == j4 - end - @testset "SU(N)" begin - f3 = SU{3}((1, 0, 0)) - f4 = SU{4}((1, 0, 0, 0)) - ad3 = SU{3}((2, 1, 0)) - ad4 = SU{4}((2, 1, 1, 0)) + f3 = SU{3}((1, 0)) + f4 = SU{4}((1, 0, 0)) + ad3 = SU{3}((2, 1)) + ad4 = SU{4}((2, 1, 1)) - @test trivial(SU{3}) == SU{3}((0, 0, 0)) - @test istrivial(SU{3}((0, 0, 0))) - @test trivial(SU{4}) == SU{4}((0, 0, 0, 0)) - @test istrivial(SU{4}((0, 0, 0, 0))) + @test trivial(SU{3}) == SU{3}((0, 0)) + @test istrivial(SU{3}((0, 0))) + @test trivial(SU{4}) == SU{4}((0, 0, 0)) + @test istrivial(SU{4}((0, 0, 0))) @test fundamental(SU{3}) == f3 @test adjoint(SU{3}) == ad3 @test fundamental(SU{4}) == f4 @test adjoint(SU{4}) == ad4 - @test dual(f3) == SU{3}((1, 1, 0)) - @test dual(f4) == SU{4}((1, 1, 1, 0)) + @test dual(f3) == SU{3}((1, 1)) + @test dual(f4) == SU{4}((1, 1, 1)) @test dual(ad3) == ad3 @test dual(ad4) == ad4 @@ -113,10 +95,10 @@ using Test: @inferred, @test, @testset @test quantum_dimension(f4) == 4 @test quantum_dimension(ad3) == 8 @test quantum_dimension(ad4) == 15 - @test quantum_dimension(SU{3}((4, 2, 0))) == 27 - @test quantum_dimension(SU{3}((3, 3, 0))) == 10 - @test quantum_dimension(SU{3}((3, 0, 0))) == 10 - @test quantum_dimension(SU{3}((0, 0, 0))) == 1 + @test quantum_dimension(SU{3}((4, 2))) == 27 + @test quantum_dimension(SU{3}((3, 3))) == 10 + @test quantum_dimension(SU{3}((3, 0))) == 10 + @test quantum_dimension(SU{3}((0, 0))) == 1 @test (@inferred quantum_dimension(f3)) == 3 end From b0e71feb47245ad406da8815135e2ad1d90bd17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 23 Apr 2024 17:33:05 -0400 Subject: [PATCH 042/194] typo --- .../src/lib/Sectors/test/test_simple_categories.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index 352234b155..0db03ea580 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -46,11 +46,11 @@ using Test: @inferred, @test, @testset j3 = SU2(1) j4 = SU2(3//2) - # alternatative tuple constructor - @test j1 == SU2((0,)) - @test j2 == SU2((1,)) - @test j3 == SU2((2,)) - @test j4 == SU2((3,)) + # alternative tuple constructor + @test j1 == SU{2}((0,)) + @test j2 == SU{2}((1,)) + @test j3 == SU{2}((2,)) + @test j4 == SU{2}((3,)) @test trivial(SU2) == SU2(0) @test istrivial(SU2(0)) From 68662a509844bbd52d91db8bf584456012d67ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 24 Apr 2024 14:13:05 -0400 Subject: [PATCH 043/194] improve display --- NDTensors/src/lib/Sectors/src/category_definitions/su.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 33728d2fd7..33546716c5 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -59,7 +59,7 @@ function Base.show(io::IO, ::MIME"text/plain", s::SU{N}) where {N} return nothing end - println("┌─" * "┬─"^(l[1] - 1) * "┐") + println(io, "┌─" * "┬─"^(l[1] - 1) * "┐") i = 1 while i < N - 1 && l[i + 1] != 0 println( @@ -73,7 +73,7 @@ function Base.show(io::IO, ::MIME"text/plain", s::SU{N}) where {N} i += 1 end - println(io, "└─", "┴─"^max(0, l[i] - 1), "┘") + print(io, "└─", "┴─"^max(0, l[i] - 1), "┘") return nothing end @@ -104,7 +104,7 @@ function Base.show(io::IO, s::SU{2}) end function Base.show(io::IO, ::MIME"text/plain", s::SU{2}) - println("S = ", HalfIntegers.half(quantum_dimension(s) - 1)) + print(io, "S = ", HalfIntegers.half(quantum_dimension(s) - 1)) return nothing end From 298338d5f54f248dc7aa2480c50c20c04bc68cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 24 Apr 2024 14:48:38 -0400 Subject: [PATCH 044/194] remove unused abstractgradedunitrange.jl --- .../GradedAxes/src/abstractgradedunitrange.jl | 158 ------------------ 1 file changed, 158 deletions(-) delete mode 100644 NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl diff --git a/NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl deleted file mode 100644 index c5c6d328ac..0000000000 --- a/NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl +++ /dev/null @@ -1,158 +0,0 @@ -using BlockArrays: - BlockArrays, - AbstractBlockVector, - Block, - BlockRange, - BlockedUnitRange, - blockaxes, - blockedrange, - blockfirsts, - blocklasts, - blocklength, - blocklengths, - findblock -using Dictionaries: Dictionary - -# Fuse two symmetry labels -fuse(l1, l2) = error("Not implemented") - -abstract type AbstractGradedUnitRange{T,G} <: AbstractUnitRange{Int} end - -""" - blockedrange(::AbstractGradedUnitRange) - -The blocked range of values the graded space can take. -""" -BlockArrays.blockedrange(::AbstractGradedUnitRange) = error("Not implemented") - -""" - nondual_sectors(::AbstractGradedUnitRange) - -A vector of the non-dual sectors of the graded space, one for each block in the space. -""" -nondual_sectors(::AbstractGradedUnitRange) = error("Not implemented") - -""" - isdual(::AbstractGradedUnitRange) - -If the graded space is dual or not. -""" -isdual(::AbstractGradedUnitRange) = error("Not implemented") - -# Overload if there are contravariant and covariant -# spaces. -dual(a::AbstractGradedUnitRange) = a - -# BlockArrays block axis interface -BlockArrays.blockaxes(a::AbstractGradedUnitRange) = blockaxes(blockedrange(a)) -Base.getindex(a::AbstractGradedUnitRange, b::Block{1}) = blockedrange(a)[b] -BlockArrays.blockfirsts(a::AbstractGradedUnitRange) = blockfirsts(blockedrange(a)) -BlockArrays.blocklasts(a::AbstractGradedUnitRange) = blocklasts(blockedrange(a)) -function BlockArrays.findblock(a::AbstractGradedUnitRange, k::Integer) - return findblock(blockedrange(a), k) -end - -# Base axis interface -Base.getindex(a::AbstractGradedUnitRange, I::Integer) = blockedrange(a)[I] -Base.first(a::AbstractGradedUnitRange) = first(blockedrange(a)) -Base.last(a::AbstractGradedUnitRange) = last(blockedrange(a)) -Base.length(a::AbstractGradedUnitRange) = length(blockedrange(a)) -Base.step(a::AbstractGradedUnitRange) = step(blockedrange(a)) -Base.unitrange(b::AbstractGradedUnitRange) = first(b):last(b) - -nondual_sector(a::AbstractGradedUnitRange, b::Block{1}) = nondual_sectors(a)[only(b.n)] -function sector(a::AbstractGradedUnitRange, b::Block{1}) - return isdual(a) ? dual(nondual_sector(a, b)) : nondual_sector(a, b) -end -sector(a::AbstractGradedUnitRange, I::Integer) = sector(a, findblock(a, I)) -sectors(a) = map(s -> isdual(a) ? dual(s) : s, nondual_sectors(a)) - -function default_isdual(a1::AbstractGradedUnitRange, a2::AbstractGradedUnitRange) - return isdual(a1) && isdual(a2) -end - -# Tensor product, no sorting -function tensor_product( - a1::AbstractGradedUnitRange, a2::AbstractGradedUnitRange; isdual=default_isdual(a1, a2) -) - a = tensor_product(blockedrange(a1), blockedrange(a2)) - nondual_sectors_a = vec( - map(Iterators.product(sectors(a1), sectors(a2))) do (l1, l2) - return fuse(isdual ? dual(l1) : l1, isdual ? dual(l2) : l2) - end, - ) - return gradedrange(nondual_sectors_a, a, isdual) -end - -function Base.show(io::IO, mimetype::MIME"text/plain", a::AbstractGradedUnitRange) - show(io, mimetype, nondual_sectors(a)) - println(io) - println(io, "isdual = ", isdual(a)) - return show(io, mimetype, blockedrange(a)) -end - -# TODO: This is not part of the `BlockArrays` interface, should -# we give this a different name? -function Base.length(a::AbstractGradedUnitRange, b::Block{1}) - return blocklengths(a)[Int(b)] -end - -# Sort and merge by the grade of the blocks. -function blockmergesort(a::AbstractGradedUnitRange) - return a[blockmergesortperm(a)] -end - -function blocksortperm(a::AbstractGradedUnitRange) - # TODO: `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`. - return Block.(sortperm(nondual_sectors(a); rev=isdual(a))) -end - -# Get the permutation for sorting, then group by common elements. -# groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]] -function blockmergesortperm(a::AbstractGradedUnitRange) - # If it is dual, reverse the sorting so the sectors - # end up sorted in the same way whether or not the space - # is dual. - # TODO: `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`. - return Block.(groupsortperm(nondual_sectors(a); rev=isdual(a))) -end - -function block_getindex(a::AbstractGradedUnitRange, I::AbstractVector{<:Block{1}}) - nondual_sectors_sub = map(b -> nondual_sector(a, b), I) - blocklengths_sub = map(b -> length(a, b), I) - return gradedrange(nondual_sectors_sub, blocklengths_sub, isdual(a)) -end - -function Base.getindex(a::AbstractGradedUnitRange, I::AbstractVector{<:Block{1}}) - return block_getindex(a, I) -end - -function Base.getindex(a::AbstractGradedUnitRange, I::BlockRange{1}) - return block_getindex(a, I) -end - -function Base.getindex( - a::AbstractGradedUnitRange, grouped_perm::AbstractBlockVector{<:Block} -) - merged_nondual_sectors = map(blocks(grouped_perm)) do group - return nondual_sector(a, first(group)) - end - # Length of each block - merged_lengths = map(blocks(grouped_perm)) do group - return sum(b -> length(a, b), group) - end - return gradedrange(merged_nondual_sectors, merged_lengths, isdual(a)) -end - -function fuse( - a1::AbstractGradedUnitRange, a2::AbstractGradedUnitRange; isdual=default_isdual(a1, a2) -) - a = tensor_product(a1, a2; isdual) - return blockmergesort(a) -end - -# Broadcasting -# This removes the block structure when mixing dense and graded blocked arrays, -# maybe keep the block structure (like `BlockArrays` does). -Broadcast.axistype(a1::AbstractGradedUnitRange, a2::Base.OneTo) = a2 -Broadcast.axistype(a1::Base.OneTo, a2::AbstractGradedUnitRange) = a1 From 598cd42e0c98aa844c48bb6632e424fd1812f7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 24 Apr 2024 15:37:41 -0400 Subject: [PATCH 045/194] define blocklabels and gradedisequal for UnitRangeDual --- NDTensors/src/lib/GradedAxes/src/unitrangedual.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index 52d8042736..fe2fadf149 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -62,3 +62,8 @@ BlockArrays.blockaxes(a::UnitRangeDual) = blockaxes(nondual(a)) BlockArrays.blockfirsts(a::UnitRangeDual) = label_dual.(blockfirsts(nondual(a))) BlockArrays.blocklasts(a::UnitRangeDual) = label_dual.(blocklasts(nondual(a))) BlockArrays.findblock(a::UnitRangeDual, index::Integer) = findblock(nondual(a), index) + +blocklabels(a::UnitRangeDual) = blocklabels(nondual(a)) +function gradedisequal(a1::UnitRangeDual, a2::UnitRangeDual) + return gradedisequal(nondual(a1), nondual(a2)) +end From bc10ca098183a3b1c90aa3508b17ade8996f1570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 25 Apr 2024 11:23:07 -0400 Subject: [PATCH 046/194] def trivial(::CategoryProduct) --- .../src/lib/Sectors/src/category_product.jl | 9 ++++++ .../lib/Sectors/test/test_category_product.jl | 29 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 11f2e60483..cc4dc3e931 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -121,6 +121,10 @@ categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) sector(args...; kws...) = CategoryProduct(args...; kws...) +function trivial(::Type{<:CategoryProduct{T}}) where {T<:Tuple} + return sector(ntuple(i -> trivial(fieldtype(T, i)), fieldcount(T))) +end + # allow additional categories at one end function categories_fusion_rule(cats1::Tuple, cats2::Tuple) n = min(length(cats1), length(cats2)) @@ -151,6 +155,11 @@ function categories_equal(A::NamedTuple, B::NamedTuple) return common_categories_match && unique_categories_zero end +function trivial(::Type{<:CategoryProduct{NT}}) where {Keys,NT<:NamedTuple{Keys}} + return reduce(×, (ntuple(i -> (; Keys[i] => trivial(fieldtype(NT, i))), fieldcount(NT)))) +end +trivial(::Type{<:CategoryProduct{<:NamedTuple{()}}}) = sector() # Empty NamedTuple + # allow ⊗ for different types in NamedTuple function categories_fusion_rule(cats1::NamedTuple, cats2::NamedTuple) diff_cat = CategoryProduct(symdiff_keys(cats1, cats2)) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index c6363eec35..ace9fb7c59 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,6 +1,18 @@ @eval module $(gensym()) using NDTensors.Sectors: - ×, ⊗, CategoryProduct, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension + ×, + ⊗, + CategoryProduct, + Fib, + Ising, + SU, + SU2, + U1, + Z, + categories, + sector, + quantum_dimension, + trivial using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange using Test: @inferred, @test, @testset, @test_broken, @test_throws @@ -9,12 +21,20 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin + s = sector(U1(1)) + @test length(categories(s)) == 1 + @test (@inferred quantum_dimension(s)) == 1 + @test dual(s) == sector(U1(-1)) + @test categories(s)[1] == U1(1) + @test (@inferred trivial(typeof((s)))) == sector(U1(0)) + s = sector(U1(1), U1(2)) @test length(categories(s)) == 2 @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == sector(U1(-1), U1(-2)) @test categories(s)[1] == U1(1) @test categories(s)[2] == U1(2) + @test (@inferred trivial(typeof((s)))) == sector(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) @test length(categories(s)) == 3 @@ -23,6 +43,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[1] == U1(1) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) + @test (@inferred trivial(typeof((s)))) == sector(U1(0), SU2(0), U1(0)) s = U1(3) × SU2(1//2) × Fib("τ") @test length(categories(s)) == 3 @@ -31,6 +52,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[1] == U1(3) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") + @test (@inferred trivial(typeof((s)))) == sector(U1(0), SU2(0), Fib("1")) end @testset "Quantum dimension and GradedUnitRange" begin @@ -86,6 +108,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 0 + @test (@inferred trivial(typeof(s))) == s end @testset "Fusion of Abelian products" begin @@ -235,6 +258,7 @@ end @test categories(s)[:B] == Z{2}(0) @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == (A=U1(-1),) × (B=Z{2}(0),) + @test trivial(typeof(s)) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) @test length(categories(s)) == 2 @@ -242,6 +266,7 @@ end @test categories(s)[:B] == SU2(2) @test (@inferred quantum_dimension(s)) == 5 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) + @test trivial(typeof(s)) == (A=U1(0),) × (B=SU2(0),) s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 @@ -261,6 +286,7 @@ end @test s == sector(; A=U1(2)) @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == sector("A" => U1(-2)) + @test trivial(typeof(s)) == (A=U1(0),) s = sector("B" => Ising("ψ"), :C => Z{2}(1)) @test length(categories(s)) == 2 @@ -338,6 +364,7 @@ end @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 0 + @test (@inferred trivial(typeof(s))) == s end @testset "Fusion of Abelian products" begin From 60a5410d70ac1d20b152b09c36a4fd3c2938de93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 25 Apr 2024 11:42:49 -0400 Subject: [PATCH 047/194] simplify SU{N} --- .../Sectors/src/category_definitions/su.jl | 50 +++++++++++-------- .../Sectors/test/test_simple_categories.jl | 2 +- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 33546716c5..fce50e2520 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -1,19 +1,24 @@ # -# Special unitary group SU{N} +# Special unitary group SU(N) # -struct SU{N,T,M} <: AbstractCategory +struct SU{N,M} <: AbstractCategory # l is the first row of the # Gelfand-Tsetlin (GT) pattern describing # an SU(N) irrep - l::NTuple{M,T} - - function SU{N,T,M}(t::NTuple{M,T}) where {N,T<:Integer,M} - return N == M + 1 ? new{N,T,M}(t) : error("Invalid tuple length") + # this first row is identical to the Young tableau of the irrep + l::NTuple{M,Int} + + # M is there to avoid storing a N-Tuple with an extra zero. + # inner constructor enforces M = N - 1 + # It does NOT check for Young Tableau validity (non-increasing positive integers) + function SU{N,M}(t::NTuple{M,Integer}) where {N,M} + return N == M + 1 && M > 0 ? new{N,M}(t) : error("Invalid tuple length") end end -SU{N}(t::NTuple{M,T}) where {N,T,M} = SU{N,T,M}(t) +SU{N}(t::Tuple) where {N} = SU{N,length(t)}(t) +SU(t::Tuple) = SU{length(t) + 1}(t) # infer N from tuple length SymmetryStyle(::SU) = NonAbelianGroup() @@ -21,14 +26,11 @@ category_label(s::SU) = s.l groupdim(::SU{N}) where {N} = N -trivial(::Type{<:SU{N}}) where {N} = trivial(SU{N,Int}) -trivial(::Type{<:SU{N,T}}) where {N,T} = SU{N}(ntuple(_ -> T(0), Val(N - 1))) +trivial(::Type{<:SU{N}}) where {N} = SU{N}(ntuple(_ -> 0, Val(N - 1))) -fundamental(::Type{<:SU{N}}) where {N} = fundamental(SU{N,Int}) -fundamental(::Type{<:SU{N,T}}) where {N,T} = SU{N}(ntuple(i -> T(i == 1), Val(N - 1))) +fundamental(::Type{<:SU{N}}) where {N} = SU{N}(ntuple(i -> i == 1, Val(N - 1))) -adjoint(::Type{<:SU{N}}) where {N} = adjoint(SU{N,Int}) -adjoint(::Type{<:SU{N,T}}) where {N,T} = SU{N}((ntuple(i -> T(1 + (i == 1)), Val(N - 1)))) +adjoint(::Type{<:SU{N}}) where {N} = SU{N}((ntuple(i -> 1 + (i == 1), Val(N - 1)))) function quantum_dimension(::NonAbelianGroup, s::SU) N = groupdim(s) @@ -79,16 +81,10 @@ end # # Specializations for the case SU{2} -# Where irreps specified by quantum_dimension "d" # -# SU2 is an alias for SU{2} -const SU2 = SU{2} - -# specific constructor for SU{2} with a half-integer -SU{2}(h::Real) = SU{2}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) - -quantum_dimension(s::SU{2}) = 1 + Int(category_label(s)[1]) +# optimize implementation +quantum_dimension(s::SU{2}) = category_label(s)[1] + 1 GradedAxes.dual(s::SU{2}) = s @@ -98,7 +94,15 @@ function label_fusion_rule(::Type{<:SU{2}}, s1, s2) return degen, labels end -# display SU2 using half integers +# define angular momentum-like interface using half-integers +# SU2 is an alias for SU{2} +const SU2 = SU{2} + +# specific constructor for SU{2} with a half-integer +# note that SU{2}(1) = spin 1 while SU{2}((1,)) = spin 1/2 +SU{2}(h::Number) = SU{2}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) + +# display SU2 using half-integers function Base.show(io::IO, s::SU{2}) return print(io, "SU(2)[S=", HalfIntegers.half(quantum_dimension(s) - 1), "]") end @@ -108,9 +112,11 @@ function Base.show(io::IO, ::MIME"text/plain", s::SU{2}) return nothing end +# # Specializations for the case SU{3} # aimed for testing non-abelian non self-conjugate representations # TODO replace with generic implementation +# function label_fusion_rule(::Type{<:SU{3}}, left, right) # Compute SU(3) fusion rules using Littlewood-Richardson rule for Young tableaus. diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index 0db03ea580..001ac83ecd 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -50,7 +50,7 @@ using Test: @inferred, @test, @testset @test j1 == SU{2}((0,)) @test j2 == SU{2}((1,)) @test j3 == SU{2}((2,)) - @test j4 == SU{2}((3,)) + @test j4 == SU((3,)) # infer N from tuple length @test trivial(SU2) == SU2(0) @test istrivial(SU2(0)) From de95045032234a24ca1eca69db07d3ae5932e7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 25 Apr 2024 14:51:05 -0400 Subject: [PATCH 048/194] SU2 as a concrete type --- .../lib/Sectors/src/category_definitions/su.jl | 6 +++--- .../lib/Sectors/test/test_simple_categories.jl | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index fce50e2520..a384be5975 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -95,12 +95,12 @@ function label_fusion_rule(::Type{<:SU{2}}, s1, s2) end # define angular momentum-like interface using half-integers -# SU2 is an alias for SU{2} -const SU2 = SU{2} +const SU2 = SU{2,1} # intuitive alias # specific constructor for SU{2} with a half-integer # note that SU{2}(1) = spin 1 while SU{2}((1,)) = spin 1/2 -SU{2}(h::Number) = SU{2}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) +SU{2}(h::Number) = SU{2,1}(h) +SU{2,1}(h::Number) = SU{2,1}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) # display SU2 using half-integers function Base.show(io::IO, s::SU{2}) diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index 001ac83ecd..ce75136e45 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -42,15 +42,18 @@ using Test: @inferred, @test, @testset @testset "SU2" begin j1 = SU2(0) - j2 = SU2(1//2) + j2 = SU2(1//2) # Rational will be cast to HalfInteger j3 = SU2(1) j4 = SU2(3//2) - # alternative tuple constructor - @test j1 == SU{2}((0,)) - @test j2 == SU{2}((1,)) - @test j3 == SU{2}((2,)) - @test j4 == SU((3,)) # infer N from tuple length + # alternative constructors + @test j2 == SU{2}((1,)) # tuple SU(N)-like constructor + @test j2 == SU{2,1}((1,)) # tuple constructor with explicit {N,N-1} + @test j2 == SU{2}(1//2) # half-integer constructor without N-1 + @test j2 == SU((1,)) # infer N from tuple length + @test j2 == SU{2}((Int8(1),)) # any Integer type accepted + @test j2 == SU{2}((UInt32(1),)) # any Integer type accepted + @test j2 == SU2(1 / 2) # Float will be cast to HalfInteger @test trivial(SU2) == SU2(0) @test istrivial(SU2(0)) From 043d106d3bb2fc55d3a50db8135e8488354b70f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 1 May 2024 18:05:55 -0400 Subject: [PATCH 049/194] trivial(::AbstractCategory) --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 1 + NDTensors/src/lib/Sectors/test/test_simple_categories.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 9ac0ce81d7..234e8959cf 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -9,6 +9,7 @@ function Base.isless(c1::C, c2::C) where {C<:AbstractCategory} end # ================= Misc ====================== +trivial(c::AbstractCategory) = trivial(typeof(c)) function trivial(category_type::Type{<:AbstractCategory}) return error("`trivial` not defined for type $(category_type).") end diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index ce75136e45..4bd54153b7 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -13,6 +13,7 @@ using Test: @inferred, @test, @testset @test quantum_dimension(q2) == 1 @test (@inferred quantum_dimension(q1)) == 1 + @test trivial(q1) == U1(0) @test trivial(U1) == U1(0) @test istrivial(U1(0)) From 63ab67e14fe59b74851f5faeb09dda9add3654d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 1 May 2024 18:20:30 -0400 Subject: [PATCH 050/194] avoid confusion between integer and tuple interfaces --- .../src/lib/Sectors/src/category_definitions/su.jl | 7 +------ .../src/lib/Sectors/test/test_simple_categories.jl | 11 ++++++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index a384be5975..22f2039833 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -95,12 +95,7 @@ function label_fusion_rule(::Type{<:SU{2}}, s1, s2) end # define angular momentum-like interface using half-integers -const SU2 = SU{2,1} # intuitive alias - -# specific constructor for SU{2} with a half-integer -# note that SU{2}(1) = spin 1 while SU{2}((1,)) = spin 1/2 -SU{2}(h::Number) = SU{2,1}(h) -SU{2,1}(h::Number) = SU{2,1}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) +SU2(h::Number) = SU{2,1}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) # display SU2 using half-integers function Base.show(io::IO, s::SU{2}) diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index 4bd54153b7..7553367b87 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -2,7 +2,7 @@ using NDTensors.GradedAxes: dual using NDTensors.Sectors: Fib, Ising, SU, SU2, U1, Z, adjoint, quantum_dimension, fundamental, istrivial, trivial -using Test: @inferred, @test, @testset +using Test: @inferred, @test, @testset, @test_throws @testset "Test Category Types" begin @testset "U(1)" begin q1 = U1(1) @@ -50,17 +50,18 @@ using Test: @inferred, @test, @testset # alternative constructors @test j2 == SU{2}((1,)) # tuple SU(N)-like constructor @test j2 == SU{2,1}((1,)) # tuple constructor with explicit {N,N-1} - @test j2 == SU{2}(1//2) # half-integer constructor without N-1 @test j2 == SU((1,)) # infer N from tuple length @test j2 == SU{2}((Int8(1),)) # any Integer type accepted @test j2 == SU{2}((UInt32(1),)) # any Integer type accepted @test j2 == SU2(1 / 2) # Float will be cast to HalfInteger + @test_throws MethodError SU2((1,)) # avoid confusion between tuple and half-integer interfaces + @test_throws MethodError SU{2,1}(1) # avoid confusion - @test trivial(SU2) == SU2(0) + @test trivial(SU{2}) == SU2(0) @test istrivial(SU2(0)) - @test fundamental(SU2) == SU2(1//2) - @test adjoint(SU2) == SU2(1) + @test fundamental(SU{2}) == SU2(1//2) + @test adjoint(SU{2}) == SU2(1) @test quantum_dimension(j1) == 1 @test quantum_dimension(j2) == 2 From c4f10e01e63917416dca36a6c48d47ba594d09ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 1 May 2024 20:19:34 -0400 Subject: [PATCH 051/194] define trivial(::AbstractUnitRange) --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 12 +++++++++--- NDTensors/src/lib/Sectors/test/test_fusion_rules.jl | 9 ++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 234e8959cf..a01b949062 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -9,9 +9,15 @@ function Base.isless(c1::C, c2::C) where {C<:AbstractCategory} end # ================= Misc ====================== -trivial(c::AbstractCategory) = trivial(typeof(c)) -function trivial(category_type::Type{<:AbstractCategory}) - return error("`trivial` not defined for type $(category_type).") +trivial(x) = trivial(typeof(x)) +function trivial(axis_type::Type{<:AbstractUnitRange}) + return GradedAxes.gradedrange([trivial(eltype(axis_type))]) # always returns nondual +end +function trivial(la_type::Type{<:LabelledNumbers.LabelledInteger}) + return la_type(1, trivial(LabelledNumbers.label_type(la_type))) +end +function trivial(type::Type) + return error("`trivial` not defined for type $(type).") end istrivial(c::AbstractCategory) = (c == trivial(typeof(c))) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index a6a67373e3..912a0fb658 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange, label_dual, tensor_product -using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension +using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension, trivial using Test: @inferred, @test, @testset, @test_throws @testset "Simple object fusion rules" begin @@ -73,6 +73,13 @@ using Test: @inferred, @test, @testset, @test_throws end end @testset "Reducible object fusion rules" begin + @testset "Trivial GradedUnitRange" begin + g1 = gradedrange([U1(0) => 1]) + g2 = gradedrange([SU2(0) => 1]) + @test gradedisequal(trivial(g1), g1) + @test gradedisequal(trivial(dual(g1)), g1) # trivial returns nondual + @test gradedisequal(trivial(typeof(g2)), g2) + end @testset "GradedUnitRange abelian tensor/fusion product" begin g1 = gradedrange([U1(-1) => 1, U1(0) => 1, U1(1) => 2]) g2 = gradedrange([U1(-2) => 2, U1(0) => 1, U1(1) => 2]) From d3e0f349182af501e392d21e1c59833be43e1f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 16 May 2024 20:21:36 -0400 Subject: [PATCH 052/194] def isdual --- NDTensors/src/lib/GradedAxes/src/unitrangedual.jl | 2 ++ NDTensors/src/lib/GradedAxes/test/test_dual.jl | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index fe2fadf149..f504b2bee9 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -7,6 +7,8 @@ dual(a::AbstractUnitRange) = UnitRangeDual(a) nondual(a::UnitRangeDual) = a.nondual_unitrange dual(a::UnitRangeDual) = nondual(a) nondual(a::AbstractUnitRange) = a +isdual(::GradedUnitRange) = false +isdual(::UnitRangeDual) = true ## TODO: Define this to instantiate a dual unit range. ## materialize_dual(a::UnitRangeDual) = materialize_dual(nondual(a)) diff --git a/NDTensors/src/lib/GradedAxes/test/test_dual.jl b/NDTensors/src/lib/GradedAxes/test/test_dual.jl index f0db3698b5..4efd1ed9cc 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_dual.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_dual.jl @@ -1,7 +1,14 @@ @eval module $(gensym()) using BlockArrays: Block, blockaxes, blockfirsts, blocklasts, blocklength, blocks, findblock using NDTensors.GradedAxes: - GradedAxes, UnitRangeDual, blockmergesortperm, blocksortperm, dual, gradedrange, nondual + GradedAxes, + UnitRangeDual, + blockmergesortperm, + blocksortperm, + dual, + gradedrange, + isdual, + nondual using NDTensors.LabelledNumbers: LabelledInteger, label, labelled using Test: @test, @test_broken, @testset struct U1 @@ -16,6 +23,8 @@ Base.isless(c1::U1, c2::U1) = c1.n < c2.n @test dual(ad) == a @test nondual(ad) == a @test nondual(a) == a + @test isdual(ad) + @test !isdual(a) @test blockfirsts(ad) == [labelled(1, U1(0)), labelled(3, U1(-1))] @test blocklasts(ad) == [labelled(2, U1(0)), labelled(5, U1(-1))] @test findblock(ad, 4) == Block(2) From bc5a2b7837876e8f97998ee24d2081263d14a1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 21 May 2024 20:03:24 -0400 Subject: [PATCH 053/194] define block_boundaries --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 7 +++++++ NDTensors/src/lib/Sectors/test/test_fusion_rules.jl | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index a01b949062..88fed9c986 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -30,6 +30,13 @@ function GradedAxes.dual(category_type::Type{<:AbstractCategory}) return error("`dual` not defined for type $(category_type).") end +block_boundaries(g::AbstractUnitRange) = block_boundaries(SymmetryStyle(g), g) +block_boundaries(::AbelianGroup, g) = GradedAxes.unlabel.(BlockArrays.blocklengths(g)) +function block_boundaries(::NonAbelianGroup, g) + return Sectors.quantum_dimension.(GradedAxes.blocklabels(g)) .* + BlockArrays.blocklengths(g) +end + quantum_dimension(x) = quantum_dimension(SymmetryStyle(x), x) function quantum_dimension(::SymmetryStyle, c::AbstractCategory) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 912a0fb658..4a3cd3eea9 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,7 +1,8 @@ @eval module $(gensym()) using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange, label_dual, tensor_product -using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, quantum_dimension, trivial +using NDTensors.Sectors: + ⊗, Fib, Ising, SU, SU2, U1, Z, block_boundaries, quantum_dimension, trivial using Test: @inferred, @test, @testset, @test_throws @testset "Simple object fusion rules" begin @@ -17,6 +18,7 @@ using Test: @inferred, @test, @testset, @test_throws # using GradedAxes interface @test gradedisequal(fusion_product(z0, z0), gradedrange([z0 => 1])) @test gradedisequal(fusion_product(z0, z1), gradedrange([z1 => 1])) + @test block_boundaries(gradedrange([z1 => 1])) == [1] end @testset "U(1) fusion rules" begin q1 = U1(1) @@ -41,6 +43,7 @@ using Test: @inferred, @test, @testset, @test_throws @test gradedisequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 + @test block_boundaries(j1 ⊗ j2) == [2] end @testset "Fibonacci fusion rules" begin @@ -85,6 +88,7 @@ end g2 = gradedrange([U1(-2) => 2, U1(0) => 1, U1(1) => 2]) @test gradedisequal(label_dual(g1), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) + @test block_boundaries(g1) == [1, 1, 2] gt = gradedrange([ U1(-3) => 2, @@ -171,6 +175,7 @@ end (@inferred fusion_product(g3, g4)), gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), ) + @test block_boundaries(g3) == [1, 4, 3] # test dual on non self-conjugate non-abelian representations s1 = SU{3}((0, 0)) From 96e9992adf2f9fc1234a9fb48116ab3962acfc16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 28 May 2024 20:02:23 -0400 Subject: [PATCH 054/194] define fusion_product(g1,g2,g3) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 7 ++++++- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 7 +++++++ NDTensors/src/lib/Sectors/test/test_fusion_rules.jl | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 7207f1cc41..54c7308755 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -115,10 +115,15 @@ end # fusion_product generalizes tensor_product to non-abelian groups and fusion categories # in the case of abelian groups, it is equivalent to tensor_product + applying blockmergesortperm -function fusion_product(a1::AbstractUnitRange, a2::AbstractUnitRange) +function fusion_product(::AbstractUnitRange, ::AbstractUnitRange) return error("Not implemented") end +# recursive fusion_product. Simpler than reduce + fix type stability issues with reduce +function fusion_product(g1, g2, g3...) + return fusion_product(fusion_product(g1, g2), g3...) +end + # Handle dual. Always return a non-dual GradedUnitRange. function fusion_product(g1::UnitRangeDual, g2::AbstractUnitRange) return fusion_product(label_dual(dual(g1)), g2) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 88fed9c986..4e5081ed4a 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -120,6 +120,13 @@ function GradedAxes.fusion_product(a, b) return GradedAxes.fusion_product(to_graded_axis(a), to_graded_axis(b)) end +# fusion_product with one input to be used in generic fusion_product(Tuple...) +# TBD define fusion_product() = gradedrange([sector(())=>1])? +GradedAxes.fusion_product(x) = GradedAxes.fusion_product(to_graded_axis(x)) + +# product with trivial = easy handling of UnitRangeDual + sort and merge blocks +GradedAxes.fusion_product(g::AbstractUnitRange) = GradedAxes.fusion_product(trivial(g), g) + function GradedAxes.fusion_product( g1::BlockArrays.BlockedUnitRange, g2::BlockArrays.BlockedUnitRange ) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 4a3cd3eea9..a5ab354740 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -18,6 +18,11 @@ using Test: @inferred, @test, @testset, @test_throws # using GradedAxes interface @test gradedisequal(fusion_product(z0, z0), gradedrange([z0 => 1])) @test gradedisequal(fusion_product(z0, z1), gradedrange([z1 => 1])) + + # test different input number + @test gradedisequal(fusion_product(z0), gradedrange([z0 => 1])) + @test gradedisequal(fusion_product(z0, z0, z0), gradedrange([z0 => 1])) + @test gradedisequal(fusion_product(z0, z0, z0, z0), gradedrange([z0 => 1])) @test block_boundaries(gradedrange([z1 => 1])) == [1] end @testset "U(1) fusion rules" begin @@ -44,6 +49,10 @@ using Test: @inferred, @test, @testset, @test_throws @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 @test block_boundaries(j1 ⊗ j2) == [2] + + @test gradedisequal(fusion_product(j2), gradedrange([j2 => 1])) + @test gradedisequal(fusion_product(j2, j1), gradedrange([j2 => 1])) + @test gradedisequal(fusion_product(j2, j1, j1), gradedrange([j2 => 1])) end @testset "Fibonacci fusion rules" begin From ba863f1870e5c45a5214ed29e6e30f4f06c53e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 29 May 2024 11:14:39 -0400 Subject: [PATCH 055/194] remove unused dual(type) --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 4e5081ed4a..3971745b61 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -26,10 +26,6 @@ function category_label(c::AbstractCategory) return error("method `category_label` not defined for type $(typeof(c))") end -function GradedAxes.dual(category_type::Type{<:AbstractCategory}) - return error("`dual` not defined for type $(category_type).") -end - block_boundaries(g::AbstractUnitRange) = block_boundaries(SymmetryStyle(g), g) block_boundaries(::AbelianGroup, g) = GradedAxes.unlabel.(BlockArrays.blocklengths(g)) function block_boundaries(::NonAbelianGroup, g) From e618ed0ef289f6dd42872cc3ac1d78a714c40480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 29 May 2024 11:30:14 -0400 Subject: [PATCH 056/194] rename block_boundaries into block_dimensions --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 3971745b61..8f9947df79 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -26,9 +26,9 @@ function category_label(c::AbstractCategory) return error("method `category_label` not defined for type $(typeof(c))") end -block_boundaries(g::AbstractUnitRange) = block_boundaries(SymmetryStyle(g), g) -block_boundaries(::AbelianGroup, g) = GradedAxes.unlabel.(BlockArrays.blocklengths(g)) -function block_boundaries(::NonAbelianGroup, g) +block_dimensions(g::AbstractUnitRange) = block_dimensions(SymmetryStyle(g), g) +block_dimensions(::AbelianGroup, g) = GradedAxes.unlabel.(BlockArrays.blocklengths(g)) +function block_dimensions(::NonAbelianGroup, g) return Sectors.quantum_dimension.(GradedAxes.blocklabels(g)) .* BlockArrays.blocklengths(g) end From 27b03d9f6dfa9cdd73657561172dda0099101178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 29 May 2024 12:04:42 -0400 Subject: [PATCH 057/194] use block_dimensions in quantum_dimension. Set quantum_dimension(EmptyCategory) to 1. --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 15 +++------------ .../src/lib/Sectors/test/test_category_product.jl | 6 +++--- .../src/lib/Sectors/test/test_fusion_rules.jl | 10 +++++----- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 8f9947df79..6041942ab8 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -28,7 +28,7 @@ end block_dimensions(g::AbstractUnitRange) = block_dimensions(SymmetryStyle(g), g) block_dimensions(::AbelianGroup, g) = GradedAxes.unlabel.(BlockArrays.blocklengths(g)) -function block_dimensions(::NonAbelianGroup, g) +function block_dimensions(::SymmetryStyle, g) return Sectors.quantum_dimension.(GradedAxes.blocklabels(g)) .* BlockArrays.blocklengths(g) end @@ -40,18 +40,9 @@ function quantum_dimension(::SymmetryStyle, c::AbstractCategory) end quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 -quantum_dimension(::EmptyCategory, ::AbstractCategory) = 0 - -function quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) - gblocks = BlockArrays.blocklengths(g) - return sum(gblocks .* quantum_dimension.(LabelledNumbers.label.(gblocks))) -end - +quantum_dimension(::EmptyCategory, ::AbstractCategory) = 1 +quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) = sum(block_dimensions(g)) quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) -function quantum_dimension(::SymmetryStyle, g::GradedAxes.UnitRangeDual) - return quantum_dimension(GradedAxes.dual(g)) -end -quantum_dimension(::AbelianGroup, g::GradedAxes.UnitRangeDual) = length(g) # resolves ambiguity # ================ fusion rule interface ==================== ⊗(c1::AbstractCategory, c2::AbstractCategory) = fusion_rule(c1, c2) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index ace9fb7c59..2647bd5bda 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -103,11 +103,11 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws end @testset "Empty category" begin - s = CategoryProduct(()) + s = sector(()) @test (@inferred dual(s)) == s @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 0 + @test (@inferred quantum_dimension(s)) == 1 @test (@inferred trivial(typeof(s))) == s end @@ -363,7 +363,7 @@ end @test (@inferred dual(s)) == s @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 0 + @test (@inferred quantum_dimension(s)) == 1 @test (@inferred trivial(typeof(s))) == s end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index a5ab354740..39c910612f 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -2,7 +2,7 @@ using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange, label_dual, tensor_product using NDTensors.Sectors: - ⊗, Fib, Ising, SU, SU2, U1, Z, block_boundaries, quantum_dimension, trivial + ⊗, Fib, Ising, SU, SU2, U1, Z, block_dimensions, quantum_dimension, trivial using Test: @inferred, @test, @testset, @test_throws @testset "Simple object fusion rules" begin @@ -23,7 +23,7 @@ using Test: @inferred, @test, @testset, @test_throws @test gradedisequal(fusion_product(z0), gradedrange([z0 => 1])) @test gradedisequal(fusion_product(z0, z0, z0), gradedrange([z0 => 1])) @test gradedisequal(fusion_product(z0, z0, z0, z0), gradedrange([z0 => 1])) - @test block_boundaries(gradedrange([z1 => 1])) == [1] + @test block_dimensions(gradedrange([z1 => 1])) == [1] end @testset "U(1) fusion rules" begin q1 = U1(1) @@ -48,7 +48,7 @@ using Test: @inferred, @test, @testset, @test_throws @test gradedisequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 - @test block_boundaries(j1 ⊗ j2) == [2] + @test block_dimensions(j1 ⊗ j2) == [2] @test gradedisequal(fusion_product(j2), gradedrange([j2 => 1])) @test gradedisequal(fusion_product(j2, j1), gradedrange([j2 => 1])) @@ -97,7 +97,7 @@ end g2 = gradedrange([U1(-2) => 2, U1(0) => 1, U1(1) => 2]) @test gradedisequal(label_dual(g1), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) - @test block_boundaries(g1) == [1, 1, 2] + @test block_dimensions(g1) == [1, 1, 2] gt = gradedrange([ U1(-3) => 2, @@ -184,7 +184,7 @@ end (@inferred fusion_product(g3, g4)), gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), ) - @test block_boundaries(g3) == [1, 4, 3] + @test block_dimensions(g3) == [1, 4, 3] # test dual on non self-conjugate non-abelian representations s1 = SU{3}((0, 0)) From c36654e27db5518b897a941174e24bb7331d9d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 29 May 2024 17:16:01 -0400 Subject: [PATCH 058/194] simplify istrivial --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 6041942ab8..ba36dd982f 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -20,7 +20,7 @@ function trivial(type::Type) return error("`trivial` not defined for type $(type).") end -istrivial(c::AbstractCategory) = (c == trivial(typeof(c))) +istrivial(c::AbstractCategory) = (c == trivial(c)) function category_label(c::AbstractCategory) return error("method `category_label` not defined for type $(typeof(c))") From 7683df0d7bb632544e769c7fc373452b6cf77cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 30 May 2024 12:03:54 -0400 Subject: [PATCH 059/194] use iterator --- .../src/lib/Sectors/src/category_product.jl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index cc4dc3e931..11e5023186 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -70,12 +70,11 @@ function categories_product(l1::NamedTuple, l2::NamedTuple) end return union_keys(l1, l2) end +categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) # edge cases -categories_product(l1::NamedTuple, l2::Tuple{}) = l1 -categories_product(l1::Tuple{}, l2::NamedTuple) = l2 - -categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) +categories_product(l1::NamedTuple, ::Tuple{}) = l1 +categories_product(::Tuple{}, l2::NamedTuple) = l2 ×(a, g::AbstractUnitRange) = ×(to_graded_axis(a), g) ×(g::AbstractUnitRange, b) = ×(g, to_graded_axis(b)) @@ -90,10 +89,12 @@ function ×(l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInt end function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) - # keep F convention in loop order - v = [ - l1 × l2 for l2 in BlockArrays.blocklengths(g2) for l1 in BlockArrays.blocklengths(g1) - ] + v = map( + ((l1, l2),) -> l1 × l2, + Iterators.flatten(( + Iterators.product(BlockArrays.blocklengths(g1), BlockArrays.blocklengths(g2)), + ),), + ) return GradedAxes.gradedrange(v) end From be768ab83adc601b8cf66430e1a40e78ba2f6e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 30 May 2024 15:42:33 -0400 Subject: [PATCH 060/194] single EmptyCategory type, act as trivial --- .../src/lib/Sectors/src/category_product.jl | 52 +++++++++++++++-- .../src/lib/Sectors/src/symmetry_style.jl | 1 + .../lib/Sectors/test/test_category_product.jl | 56 +++++++++---------- 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 11e5023186..dfe7ba65f5 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -109,9 +109,45 @@ function fusion_rule(::AbelianGroup, s1::CategoryProduct, s2::CategoryProduct) return categories_fusion_rule(categories(s1), categories(s2)) end -# Empty case: use × cast conventions between NamedTupled and ordered implem -function fusion_rule(::EmptyCategory, s1::CategoryProduct, s2::CategoryProduct) - return s1 × s2 +# Empty case +function fusion_rule( + ::EmptyCategory, ::CategoryProduct{Tuple{}}, ::CategoryProduct{Tuple{}} +) + return sector() +end + +# EmptyCategory acts as trivial on any AbstractCategory, not just CategoryProduct +function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c2::AbstractCategory) + return to_graded_axis(c2) +end + +function fusion_rule(::SymmetryStyle, c1::AbstractCategory, ::CategoryProduct{Tuple{}}) + return to_graded_axis(c1) +end + +function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c2::CategoryProduct) + return to_graded_axis(c2) +end + +function fusion_rule(::SymmetryStyle, c1::CategoryProduct, ::CategoryProduct{Tuple{}}) + return to_graded_axis(c1) +end + +# abelian case: return Category +function fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c2::AbstractCategory) + return c2 +end + +function fusion_rule(::AbelianGroup, c1::AbstractCategory, ::CategoryProduct{Tuple{}}) + return c1 +end + +function fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c2::CategoryProduct) + return c2 +end + +function fusion_rule(::AbelianGroup, c1::CategoryProduct, ::CategoryProduct{Tuple{}}) + return c1 end # ============== Ordered implementation ================= @@ -141,6 +177,9 @@ function CategoryProduct(nt::NamedTuple) return _CategoryProduct(categories) end +# avoid having 2 different kinds of EmptyCategory: cast empty NamedTuple to Tuple{} +CategoryProduct(::NamedTuple{()}) = CategoryProduct(()) + CategoryProduct(; kws...) = CategoryProduct((; kws...)) function CategoryProduct(pairs::Pair...) @@ -157,9 +196,12 @@ function categories_equal(A::NamedTuple, B::NamedTuple) end function trivial(::Type{<:CategoryProduct{NT}}) where {Keys,NT<:NamedTuple{Keys}} - return reduce(×, (ntuple(i -> (; Keys[i] => trivial(fieldtype(NT, i))), fieldcount(NT)))) + return reduce( + ×, + (ntuple(i -> (; Keys[i] => trivial(fieldtype(NT, i))), fieldcount(NT))); + init=sector(), + ) end -trivial(::Type{<:CategoryProduct{<:NamedTuple{()}}}) = sector() # Empty NamedTuple # allow ⊗ for different types in NamedTuple function categories_fusion_rule(cats1::NamedTuple, cats2::NamedTuple) diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index 400e80a002..f65a65fc91 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -20,6 +20,7 @@ combine_styles(::NonAbelianGroup, ::AbelianGroup) = NonAbelianGroup() combine_styles(::NonAbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() combine_styles(::NonAbelianGroup, ::NonGroupCategory) = NonGroupCategory() combine_styles(::NonGroupCategory, ::SymmetryStyle) = NonGroupCategory() +combine_styles(::NonGroupCategory, ::EmptyCategory) = NonGroupCategory() combine_styles(::EmptyCategory, s::SymmetryStyle) = s combine_styles(s::SymmetryStyle, ::EmptyCategory) = s combine_styles(::EmptyCategory, ::EmptyCategory) = EmptyCategory() diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 2647bd5bda..10475cc577 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,18 +1,6 @@ @eval module $(gensym()) using NDTensors.Sectors: - ×, - ⊗, - CategoryProduct, - Fib, - Ising, - SU, - SU2, - U1, - Z, - categories, - sector, - quantum_dimension, - trivial + ×, ⊗, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension, trivial using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange using Test: @inferred, @test, @testset, @test_broken, @test_throws @@ -26,7 +14,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == sector(U1(-1)) @test categories(s)[1] == U1(1) - @test (@inferred trivial(typeof((s)))) == sector(U1(0)) + @test (@inferred trivial(s)) == sector(U1(0)) s = sector(U1(1), U1(2)) @test length(categories(s)) == 2 @@ -34,7 +22,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test dual(s) == sector(U1(-1), U1(-2)) @test categories(s)[1] == U1(1) @test categories(s)[2] == U1(2) - @test (@inferred trivial(typeof((s)))) == sector(U1(0), U1(0)) + @test (@inferred trivial(s)) == sector(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) @test length(categories(s)) == 3 @@ -43,7 +31,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[1] == U1(1) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) - @test (@inferred trivial(typeof((s)))) == sector(U1(0), SU2(0), U1(0)) + @test (@inferred trivial(s)) == sector(U1(0), SU2(0), U1(0)) s = U1(3) × SU2(1//2) × Fib("τ") @test length(categories(s)) == 3 @@ -52,7 +40,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[1] == U1(3) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") - @test (@inferred trivial(typeof((s)))) == sector(U1(0), SU2(0), Fib("1")) + @test (@inferred trivial(s)) == sector(U1(0), SU2(0), Fib("1")) end @testset "Quantum dimension and GradedUnitRange" begin @@ -103,12 +91,27 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws end @testset "Empty category" begin - s = sector(()) + s = sector() @test (@inferred dual(s)) == s @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred trivial(typeof(s))) == s + @test (@inferred trivial(s)) == s + @test typeof(s) == typeof(sector(())) + @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} + + # Empty acts as trivial + @test (@inferred U1(1) ⊗ s) == U1(1) + @test (@inferred SU2(0) ⊗ s) == gradedrange([SU2(0) => 1]) + @test (@inferred Fib("τ") ⊗ s) == gradedrange([Fib("τ") => 1]) + @test (@inferred s ⊗ U1(1)) == U1(1) + @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) + @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) + + @test (@inferred sector(U1(1)) ⊗ s) == sector(U1(1)) + @test (@inferred sector(SU2(0)) ⊗ s) == gradedrange([sector(SU2(0)) => 1]) + @test (@inferred sector(Fib("τ"), SU2(1), U1(2)) ⊗ s) == + gradedrange([sector(Fib("τ"), SU2(1), U1(2)) => 1]) end @testset "Fusion of Abelian products" begin @@ -258,7 +261,7 @@ end @test categories(s)[:B] == Z{2}(0) @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == (A=U1(-1),) × (B=Z{2}(0),) - @test trivial(typeof(s)) == (A=U1(0),) × (B=Z{2}(0),) + @test trivial(s) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) @test length(categories(s)) == 2 @@ -266,7 +269,7 @@ end @test categories(s)[:B] == SU2(2) @test (@inferred quantum_dimension(s)) == 5 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) - @test trivial(typeof(s)) == (A=U1(0),) × (B=SU2(0),) + @test trivial(s) == (A=U1(0),) × (B=SU2(0),) s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 @@ -286,7 +289,7 @@ end @test s == sector(; A=U1(2)) @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == sector("A" => U1(-2)) - @test trivial(typeof(s)) == (A=U1(0),) + @test trivial(s) == sector(; A=U1(0)) s = sector("B" => Ising("ψ"), :C => Z{2}(1)) @test length(categories(s)) == 2 @@ -358,15 +361,6 @@ end @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end - @testset "Empty category" begin - s = sector() - @test (@inferred dual(s)) == s - @test (@inferred s × s) == s - @test (@inferred s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred trivial(typeof(s))) == s - end - @testset "Fusion of Abelian products" begin q00 = sector() q10 = sector(; A=U1(1)) From 326ec5fbd5d78fe4b9b0b17bc8c06d87f115fd3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 30 May 2024 17:40:14 -0400 Subject: [PATCH 061/194] define categories_trivial --- .../src/lib/Sectors/src/category_product.jl | 26 ++++++++++++------- .../lib/Sectors/test/test_category_product.jl | 10 +++---- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index dfe7ba65f5..70758e8eef 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -150,16 +150,26 @@ function fusion_rule(::AbelianGroup, c1::CategoryProduct, ::CategoryProduct{Tupl return c1 end +# =================================== shared ============================================= +# there are 2 implementations for CategoryProduct +# - ordered-like with a Tuple +# - dictionary-like with a NamedTuple +categories_type(::Type{<:CategoryProduct{T}}) where {T} = T + +function trivial(type::Type{<:CategoryProduct}) + return sector(categories_trivial(categories_type(type))) +end + +sector(args...; kws...) = CategoryProduct(args...; kws...) + # ============== Ordered implementation ================= CategoryProduct(t::Tuple) = _CategoryProduct(t) CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) -sector(args...; kws...) = CategoryProduct(args...; kws...) - -function trivial(::Type{<:CategoryProduct{T}}) where {T<:Tuple} - return sector(ntuple(i -> trivial(fieldtype(T, i)), fieldcount(T))) +function categories_trivial(type::Type{<:Tuple}) + return trivial.(fieldtypes(type)) end # allow additional categories at one end @@ -195,12 +205,8 @@ function categories_equal(A::NamedTuple, B::NamedTuple) return common_categories_match && unique_categories_zero end -function trivial(::Type{<:CategoryProduct{NT}}) where {Keys,NT<:NamedTuple{Keys}} - return reduce( - ×, - (ntuple(i -> (; Keys[i] => trivial(fieldtype(NT, i))), fieldcount(NT))); - init=sector(), - ) +function categories_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} + return NamedTuple{Keys}(trivial.(fieldtypes(type))) end # allow ⊗ for different types in NamedTuple diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 10475cc577..7db85b4ea1 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -14,7 +14,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred quantum_dimension(s)) == 1 @test dual(s) == sector(U1(-1)) @test categories(s)[1] == U1(1) - @test (@inferred trivial(s)) == sector(U1(0)) + @test trivial(s) == sector(U1(0)) # need julia 1.10 for type stability s = sector(U1(1), U1(2)) @test length(categories(s)) == 2 @@ -22,7 +22,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test dual(s) == sector(U1(-1), U1(-2)) @test categories(s)[1] == U1(1) @test categories(s)[2] == U1(2) - @test (@inferred trivial(s)) == sector(U1(0), U1(0)) + @test trivial(s) == sector(U1(0), U1(0)) # need julia 1.10 for type stability s = U1(1) × SU2(1//2) × U1(3) @test length(categories(s)) == 3 @@ -31,7 +31,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[1] == U1(1) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) - @test (@inferred trivial(s)) == sector(U1(0), SU2(0), U1(0)) + @test trivial(s) == sector(U1(0), SU2(0), U1(0)) # need julia 1.10 for type stability s = U1(3) × SU2(1//2) × Fib("τ") @test length(categories(s)) == 3 @@ -40,7 +40,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[1] == U1(3) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") - @test (@inferred trivial(s)) == sector(U1(0), SU2(0), Fib("1")) + @test trivial(s) == sector(U1(0), SU2(0), Fib("1")) # need julia 1.10 end @testset "Quantum dimension and GradedUnitRange" begin @@ -96,7 +96,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred trivial(s)) == s + @test trivial(s) == s # need julia 1.10 for type stability @test typeof(s) == typeof(sector(())) @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} From d3cced61727eda811993b6ff34bb57d7a5c18cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 30 May 2024 17:40:43 -0400 Subject: [PATCH 062/194] categories_isequal --- NDTensors/src/lib/Sectors/src/category_product.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 70758e8eef..a180f18f86 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -33,7 +33,7 @@ GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categ # ============== Base interface ================= function Base.:(==)(A::CategoryProduct, B::CategoryProduct) - return categories_equal(categories(A), categories(B)) + return categories_isequal(categories(A), categories(B)) end function Base.show(io::IO, s::CategoryProduct) @@ -166,7 +166,7 @@ sector(args...; kws...) = CategoryProduct(args...; kws...) CategoryProduct(t::Tuple) = _CategoryProduct(t) CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) -categories_equal(o1::Tuple, o2::Tuple) = (o1 == o2) +categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) function categories_trivial(type::Type{<:Tuple}) return trivial.(fieldtypes(type)) @@ -198,7 +198,7 @@ function CategoryProduct(pairs::Pair...) return CategoryProduct(NamedTuple{keys}(vals)) end -function categories_equal(A::NamedTuple, B::NamedTuple) +function categories_isequal(A::NamedTuple, B::NamedTuple) common_categories = zip(pairs(intersect_keys(A, B)), pairs(intersect_keys(B, A))) common_categories_match = all(nl -> (nl[1] == nl[2]), common_categories) unique_categories_zero = all(l -> istrivial(l), symdiff_keys(A, B)) From 00b2172389ded3608d397b22b859b1f8eb66a2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 30 May 2024 18:24:17 -0400 Subject: [PATCH 063/194] test sector() acts as trivial --- .../lib/Sectors/test/test_category_product.jl | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 7db85b4ea1..153933de84 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -90,30 +90,6 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end - @testset "Empty category" begin - s = sector() - @test (@inferred dual(s)) == s - @test (@inferred s × s) == s - @test (@inferred s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 1 - @test trivial(s) == s # need julia 1.10 for type stability - @test typeof(s) == typeof(sector(())) - @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} - - # Empty acts as trivial - @test (@inferred U1(1) ⊗ s) == U1(1) - @test (@inferred SU2(0) ⊗ s) == gradedrange([SU2(0) => 1]) - @test (@inferred Fib("τ") ⊗ s) == gradedrange([Fib("τ") => 1]) - @test (@inferred s ⊗ U1(1)) == U1(1) - @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) - @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) - - @test (@inferred sector(U1(1)) ⊗ s) == sector(U1(1)) - @test (@inferred sector(SU2(0)) ⊗ s) == gradedrange([sector(SU2(0)) => 1]) - @test (@inferred sector(Fib("τ"), SU2(1), U1(2)) ⊗ s) == - gradedrange([sector(Fib("τ"), SU2(1), U1(2)) => 1]) - end - @testset "Fusion of Abelian products" begin p1 = sector(U1(1)) p2 = sector(U1(2)) @@ -501,4 +477,40 @@ end @test gradedisequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) end end + +@testset "Empty category" begin + s = sector() + @test (@inferred dual(s)) == s + @test (@inferred s × s) == s + @test (@inferred s ⊗ s) == s + @test (@inferred quantum_dimension(s)) == 1 + @test trivial(s) == s # need julia 1.10 for type stability + @test typeof(s) == typeof(sector(())) + @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} + + @test s × U1(1) == sector(U1(1)) + @test s × sector(U1(1)) == sector(U1(1)) + @test s × sector(; A=U1(1)) == sector(; A=U1(1)) + @test U1(1) × s == sector(U1(1)) + @test sector(U1(1)) × s == sector(U1(1)) + @test sector(; A=U1(1)) × s == sector(; A=U1(1)) + + # Empty acts as trivial + @test (@inferred U1(1) ⊗ s) == U1(1) + @test (@inferred SU2(0) ⊗ s) == gradedrange([SU2(0) => 1]) + @test (@inferred Fib("τ") ⊗ s) == gradedrange([Fib("τ") => 1]) + @test (@inferred s ⊗ U1(1)) == U1(1) + @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) + @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) + + @test (@inferred sector(U1(1)) ⊗ s) == sector(U1(1)) + @test (@inferred sector(SU2(0)) ⊗ s) == gradedrange([sector(SU2(0)) => 1]) + @test (@inferred sector(Fib("τ"), SU2(1), U1(2)) ⊗ s) == + gradedrange([sector(Fib("τ"), SU2(1), U1(2)) => 1]) + + @test (@inferred sector(; A=U1(1)) ⊗ s) == sector(; A=U1(1)) + @test (@inferred sector(; A=SU2(0)) ⊗ s) == gradedrange([sector(; A=SU2(0)) => 1]) + @test (@inferred sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == + gradedrange([sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) +end end From d30c320e6a7ff4a60fea471b27a5eb58c047f80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 30 May 2024 19:00:44 -0400 Subject: [PATCH 064/194] fix new line in printing --- .../src/lib/Sectors/src/category_definitions/su.jl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 22f2039833..9937e1c362 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -54,13 +54,13 @@ function Base.show(io::IO, s::SU) end # display SU(N) irrep as a Young tableau with utf8 box char -function Base.show(io::IO, ::MIME"text/plain", s::SU{N}) where {N} - l = category_label(s) - if l[1] == 0 # singlet = no box - println(io, "●") - return nothing +function Base.show(io::IO, ::MIME"text/plain", s::SU) + if istrivial(s) # singlet = no box + return print(io, "●") end + N = groupdim(s) + l = category_label(s) println(io, "┌─" * "┬─"^(l[1] - 1) * "┐") i = 1 while i < N - 1 && l[i + 1] != 0 @@ -103,8 +103,7 @@ function Base.show(io::IO, s::SU{2}) end function Base.show(io::IO, ::MIME"text/plain", s::SU{2}) - print(io, "S = ", HalfIntegers.half(quantum_dimension(s) - 1)) - return nothing + return print(io, "S = ", HalfIntegers.half(quantum_dimension(s) - 1)) end # From fc9bacb44980e2a1200f44cef9e3a0a19dca9642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 3 Jun 2024 10:53:18 -0400 Subject: [PATCH 065/194] sector with Type arg --- NDTensors/src/lib/Sectors/src/category_product.jl | 2 ++ NDTensors/src/lib/Sectors/test/test_category_product.jl | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index a180f18f86..2f28e670a4 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -160,6 +160,8 @@ function trivial(type::Type{<:CategoryProduct}) return sector(categories_trivial(categories_type(type))) end +sector(T::Type{<:CategoryProduct}, cats::Tuple) = sector(categories_type(T), cats) +sector(T::Type, cats::Tuple) = sector(T(cats)) # recover NamedTuple sector(args...; kws...) = CategoryProduct(args...; kws...) # ============== Ordered implementation ================= diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 153933de84..4eb8a5cf25 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -32,6 +32,8 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) @test trivial(s) == sector(U1(0), SU2(0), U1(0)) # need julia 1.10 for type stability + @test (@inferred sector(typeof(categories(s)), categories(s))) == s + @test (@inferred sector(typeof(s), categories(s))) == s s = U1(3) × SU2(1//2) × Fib("τ") @test length(categories(s)) == 3 @@ -246,6 +248,8 @@ end @test (@inferred quantum_dimension(s)) == 5 @test dual(s) == (A=U1(-1),) × (B=SU2(2),) @test trivial(s) == (A=U1(0),) × (B=SU2(0),) + @test (@inferred sector(typeof(categories(s)), Tuple(categories(s)))) == s + @test (@inferred sector(typeof(s), Tuple(categories(s)))) == s s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 From 3bcde67f715c613b2517cdebbbcc50c577735723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 3 Jun 2024 11:28:16 -0400 Subject: [PATCH 066/194] reorder file --- .../src/lib/Sectors/src/category_product.jl | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 2f28e670a4..6ef6a74afb 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -1,7 +1,7 @@ # This files defines a structure for Cartesian product of 2 or more fusion categories # e.g. U(1)×U(1), U(1)×SU2(2)×SU(3) -# ============== Definition and getters ================= +# ===================================== Definition ======================================= struct CategoryProduct{Categories} <: AbstractCategory cats::Categories global _CategoryProduct(l) = new{typeof(l)}(l) @@ -11,7 +11,7 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -# ============== SymmetryStyle ============================== +# =================================== SymmetryStyle ====================================== function SymmetryStyle(c::CategoryProduct) return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategory()) end @@ -20,7 +20,7 @@ function SymmetryStyle(nt::NamedTuple) return reduce(combine_styles, map(SymmetryStyle, values(nt)); init=EmptyCategory()) end -# ============== Sector interface ================= +# ================================== Sector interface ==================================== function quantum_dimension(::NonAbelianGroup, s::CategoryProduct) return prod(map(quantum_dimension, categories(s))) end @@ -31,7 +31,11 @@ end GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) -# ============== Base interface ================= +function trivial(type::Type{<:CategoryProduct}) + return sector(categories_trivial(categories_type(type))) +end + +# =================================== Base interface ===================================== function Base.:(==)(A::CategoryProduct, B::CategoryProduct) return categories_isequal(categories(A), categories(B)) end @@ -48,7 +52,7 @@ function Base.show(io::IO, s::CategoryProduct) return print(io, ")") end -category_show(io::IO, k, v) = print(io, v) +category_show(io::IO, ::Int, v) = print(io, v) category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") @@ -58,7 +62,21 @@ function Base.isless(s1::C, s2::C) where {C<:CategoryProduct} ) end -# ============== Cartesian product ================= +# ======================================= shared ========================================= +# there are 2 implementations for CategoryProduct +# - ordered-like with a Tuple +# - dictionary-like with a NamedTuple + +categories_isequal(::Tuple, ::NamedTuple) = false +categories_isequal(::NamedTuple, ::Tuple) = false + +categories_type(::Type{<:CategoryProduct{T}}) where {T} = T + +sector(T::Type{<:CategoryProduct}, cats::Tuple) = sector(categories_type(T), cats) +sector(T::Type, cats::Tuple) = sector(T(cats)) # recover NamedTuple +sector(args...; kws...) = CategoryProduct(args...; kws...) + +# ================================= Cartesian Product ==================================== ×(c1::AbstractCategory, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) function ×(p1::CategoryProduct, p2::CategoryProduct) return CategoryProduct(categories_product(categories(p1), categories(p2))) @@ -98,7 +116,7 @@ function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) return GradedAxes.gradedrange(v) end -# =================== Fusion rules ==================== +# ==================================== Fusion rules ====================================== # generic case: fusion returns a GradedAxes, even for fusion with Empty function fusion_rule(::SymmetryStyle, s1::CategoryProduct, s2::CategoryProduct) return to_graded_axis(categories_fusion_rule(categories(s1), categories(s2))) @@ -150,21 +168,7 @@ function fusion_rule(::AbelianGroup, c1::CategoryProduct, ::CategoryProduct{Tupl return c1 end -# =================================== shared ============================================= -# there are 2 implementations for CategoryProduct -# - ordered-like with a Tuple -# - dictionary-like with a NamedTuple -categories_type(::Type{<:CategoryProduct{T}}) where {T} = T - -function trivial(type::Type{<:CategoryProduct}) - return sector(categories_trivial(categories_type(type))) -end - -sector(T::Type{<:CategoryProduct}, cats::Tuple) = sector(categories_type(T), cats) -sector(T::Type, cats::Tuple) = sector(T(cats)) # recover NamedTuple -sector(args...; kws...) = CategoryProduct(args...; kws...) - -# ============== Ordered implementation ================= +# =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) @@ -183,7 +187,7 @@ function categories_fusion_rule(cats1::Tuple, cats2::Tuple) return reduce(×, (shared..., sup1, sup2)) end -# ============== Dictionary-like implementation ================= +# =========================== Dictionary-like implementation ============================= function CategoryProduct(nt::NamedTuple) categories = sort_keys(nt) return _CategoryProduct(categories) From 9c968b033ff44365b0098c628feed2b5786d84d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 4 Jun 2024 17:07:12 -0400 Subject: [PATCH 067/194] use @inferred_latest --- .../lib/Sectors/test/test_category_product.jl | 66 +++++++++++++------ .../src/lib/Sectors/test/test_fusion_rules.jl | 8 +-- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 4eb8a5cf25..630bd15ff7 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -1,37 +1,53 @@ @eval module $(gensym()) using NDTensors.Sectors: - ×, ⊗, Fib, Ising, SU, SU2, U1, Z, categories, sector, quantum_dimension, trivial + ×, + ⊗, + Fib, + Ising, + SU, + SU2, + U1, + Z, + block_dimensions, + categories, + quantum_dimension, + sector, + trivial using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange -using Test: @inferred, @test, @testset, @test_broken, @test_throws +using Test: @inferred, @test, @testset, @test_throws -# some types are not correctly inferred on julia 1.6 -# every operation is type stable on julia 1.10 +macro inferred_latest(ex) + if VERSION < v"1.10" + return esc(:($ex)) + end + return esc(:(@inferred $ex)) +end @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin s = sector(U1(1)) @test length(categories(s)) == 1 @test (@inferred quantum_dimension(s)) == 1 - @test dual(s) == sector(U1(-1)) + @test (@inferred dual(s)) == sector(U1(-1)) @test categories(s)[1] == U1(1) - @test trivial(s) == sector(U1(0)) # need julia 1.10 for type stability + @test (@inferred_latest trivial(s)) == sector(U1(0)) s = sector(U1(1), U1(2)) @test length(categories(s)) == 2 @test (@inferred quantum_dimension(s)) == 1 - @test dual(s) == sector(U1(-1), U1(-2)) + @test (@inferred dual(s)) == sector(U1(-1), U1(-2)) @test categories(s)[1] == U1(1) @test categories(s)[2] == U1(2) - @test trivial(s) == sector(U1(0), U1(0)) # need julia 1.10 for type stability + @test (@inferred_latest trivial(s)) == sector(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) @test length(categories(s)) == 3 @test (@inferred quantum_dimension(s)) == 2 - @test dual(s) == U1(-1) × SU2(1//2) × U1(-3) + @test (@inferred dual(s)) == U1(-1) × SU2(1//2) × U1(-3) @test categories(s)[1] == U1(1) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) - @test trivial(s) == sector(U1(0), SU2(0), U1(0)) # need julia 1.10 for type stability + @test (@inferred_latest trivial(s)) == sector(U1(0), SU2(0), U1(0)) @test (@inferred sector(typeof(categories(s)), categories(s))) == s @test (@inferred sector(typeof(s), categories(s))) == s @@ -42,7 +58,7 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test categories(s)[1] == U1(3) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") - @test trivial(s) == sector(U1(0), SU2(0), Fib("1")) # need julia 1.10 + @test (@inferred_latest trivial(s)) == sector(U1(0), SU2(0), Fib("1")) end @testset "Quantum dimension and GradedUnitRange" begin @@ -56,12 +72,15 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws (SU2(1) × SU2(1)) => 1, ]) @test (@inferred quantum_dimension(g)) == 16 + @test (@inferred block_dimensions(g)) == [1, 3, 3, 9] # mixed group g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) @test (@inferred quantum_dimension(g)) == 4 + @test (@inferred block_dimensions(g)) == [1, 3] g = gradedrange([(SU2(0) × U1(0) × SU2(1//2)) => 1, (SU2(0) × U1(1) × SU2(1//2)) => 1]) @test (@inferred quantum_dimension(g)) == 4 + @test (@inferred block_dimensions(g)) == [2, 2] # NonGroupCategory g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) @@ -70,6 +89,8 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws @test (@inferred quantum_dimension(g_fib)) == 1.0 @test (@inferred quantum_dimension(g_ising)) == 1.0 @test (@inferred quantum_dimension((Ising("1") × Ising("1")))) == 1.0 + @test (@inferred block_dimensions(g_fib)) == [1.0] + @test (@inferred block_dimensions(g_ising)) == [1.0] @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 @@ -82,14 +103,17 @@ using Test: @inferred, @test, @testset, @test_broken, @test_throws (U1(2) × SU2(1) × Ising("ψ")) => 1, ]) @test (@inferred quantum_dimension(g)) == 8.0 + @test (@inferred block_dimensions(g)) == [1.0, 3.0, 1.0, 3.0] + ϕ = (1 + √5) / 2 g = gradedrange([ (Fib("1") × SU2(0) × U1(2)) => 1, (Fib("1") × SU2(1) × U1(2)) => 1, (Fib("τ") × SU2(0) × U1(2)) => 1, (Fib("τ") × SU2(1) × U1(2)) => 1, ]) - @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) + @test (@inferred quantum_dimension(g)) == 4.0 + 4.0ϕ + @test (@inferred block_dimensions(g)) == [1.0, 3.0, 1.0ϕ, 3.0ϕ] end @testset "Fusion of Abelian products" begin @@ -238,24 +262,24 @@ end @test categories(s)[:A] == U1(1) @test categories(s)[:B] == Z{2}(0) @test (@inferred quantum_dimension(s)) == 1 - @test dual(s) == (A=U1(-1),) × (B=Z{2}(0),) - @test trivial(s) == (A=U1(0),) × (B=Z{2}(0),) + @test (@inferred dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) + @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) @test length(categories(s)) == 2 @test categories(s)[:A] == U1(1) @test categories(s)[:B] == SU2(2) @test (@inferred quantum_dimension(s)) == 5 - @test dual(s) == (A=U1(-1),) × (B=SU2(2),) - @test trivial(s) == (A=U1(0),) × (B=SU2(0),) + @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) + @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=SU2(0),) @test (@inferred sector(typeof(categories(s)), Tuple(categories(s)))) == s @test (@inferred sector(typeof(s), Tuple(categories(s)))) == s s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 @test categories(s)[:C] == Ising("ψ") - @test quantum_dimension(s) == 5.0 # type not inferred for Julia 1.6 only - @test dual(s) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) + @test (@inferred_latest quantum_dimension(s)) == 5.0 + @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) s1 = (A=U1(1),) × (B=Z{2}(0),) s2 = (A=U1(1),) × (C=Z{2}(0),) @@ -268,8 +292,8 @@ end @test categories(s)[:A] == U1(2) @test s == sector(; A=U1(2)) @test (@inferred quantum_dimension(s)) == 1 - @test dual(s) == sector("A" => U1(-2)) - @test trivial(s) == sector(; A=U1(0)) + @test (@inferred dual(s)) == sector("A" => U1(-2)) + @test (@inferred_latest trivial(s)) == sector(; A=U1(0)) s = sector("B" => Ising("ψ"), :C => Z{2}(1)) @test length(categories(s)) == 2 @@ -330,7 +354,7 @@ end sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]) - @test quantum_dimension(g) == 8.0 + @test (@inferred_latest quantum_dimension(g)) == 8.0 g = gradedrange([ sector(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 39c910612f..d979794a9d 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -23,7 +23,7 @@ using Test: @inferred, @test, @testset, @test_throws @test gradedisequal(fusion_product(z0), gradedrange([z0 => 1])) @test gradedisequal(fusion_product(z0, z0, z0), gradedrange([z0 => 1])) @test gradedisequal(fusion_product(z0, z0, z0, z0), gradedrange([z0 => 1])) - @test block_dimensions(gradedrange([z1 => 1])) == [1] + @test (@inferred block_dimensions(gradedrange([z1 => 1]))) == [1] end @testset "U(1) fusion rules" begin q1 = U1(1) @@ -48,7 +48,7 @@ using Test: @inferred, @test, @testset, @test_throws @test gradedisequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 - @test block_dimensions(j1 ⊗ j2) == [2] + @test (@inferred block_dimensions(j1 ⊗ j2)) == [2] @test gradedisequal(fusion_product(j2), gradedrange([j2 => 1])) @test gradedisequal(fusion_product(j2, j1), gradedrange([j2 => 1])) @@ -97,7 +97,7 @@ end g2 = gradedrange([U1(-2) => 2, U1(0) => 1, U1(1) => 2]) @test gradedisequal(label_dual(g1), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) - @test block_dimensions(g1) == [1, 1, 2] + @test (@inferred block_dimensions(g1)) == [1, 1, 2] gt = gradedrange([ U1(-3) => 2, @@ -184,7 +184,7 @@ end (@inferred fusion_product(g3, g4)), gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), ) - @test block_dimensions(g3) == [1, 4, 3] + @test (@inferred block_dimensions(g3)) == [1, 4, 3] # test dual on non self-conjugate non-abelian representations s1 = SU{3}((0, 0)) From bf31a2e196f008410596ac845dbdac40081c0fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 4 Jun 2024 18:18:47 -0400 Subject: [PATCH 068/194] clean categories_fusion_rule --- .../src/lib/Sectors/src/category_product.jl | 34 +++++------ .../lib/Sectors/test/test_category_product.jl | 58 ++++++++++--------- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 6ef6a74afb..31a7849be0 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -219,26 +219,26 @@ end function categories_fusion_rule(cats1::NamedTuple, cats2::NamedTuple) diff_cat = CategoryProduct(symdiff_keys(cats1, cats2)) nt1 = intersect_keys(cats1, cats2) - shared1 = ntuple(i -> (; keys(nt1)[i] => values(nt1)[i]), length(nt1)) nt2 = intersect_keys(cats2, cats1) - shared2 = ntuple(i -> (; keys(nt2)[i] => values(nt2)[i]), length(nt2)) - return diff_cat × categories_fusion_rule(shared1, shared2) + fused = map(fusion_rule, values(nt1), values(nt2)) + return diff_cat × recover_key(typeof(nt1), fused) end -# abelian fusion of one category -function fusion_rule(::AbelianGroup, cats1::NT, cats2::NT) where {NT<:NamedTuple} - fused = fusion_rule(only(values(cats1)), only(values(cats2))) - return sector(only(keys(cats1)) => fused) +function recover_key(NT::Type, fused::Tuple{Vararg{<:AbstractCategory}}) + return sector(NT, fused) end -# generic fusion of one category -function fusion_rule(::SymmetryStyle, cats1::NT, cats2::NT) where {NT<:NamedTuple} - fused = fusion_rule(only(values(cats1)), only(values(cats2))) - key = only(keys(cats1)) - v = Vector{Pair{CategoryProduct{NT},Int64}}() - for la in BlockArrays.blocklengths(fused) - push!(v, sector(key => LabelledNumbers.label(la)) => LabelledNumbers.unlabel(la)) - end - g = GradedAxes.gradedrange(v) - return g +function recover_key(NT::Type, fused::AbstractCategory) + return recover_key(NT, (fused,)) +end + +function recover_key(NT::Type, fused::CategoryProduct) + return recover_key(NT, categories(fused)) +end + +function recover_key(NT::Type, fused::Tuple) + g0 = reduce(×, fused) + blocklabels_key = recover_key.(NT, GradedAxes.blocklabels(g0)) + pairs_key = blocklabels_key .=> LabelledNumbers.unlabel.(BlockArrays.blocklengths(g0)) + return GradedAxes.gradedrange(pairs_key) end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 630bd15ff7..b39f95d559 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -227,14 +227,17 @@ end @testset "Fusion of different length Categories" begin @test sector(U1(1) × U1(0)) ⊗ sector(U1(1)) == sector(U1(2) × U1(0)) @test gradedisequal( - sector(SU2(0) × SU2(0)) ⊗ sector(SU2(1)), gradedrange([sector(SU2(1) × SU2(0)) => 1]) + (@inferred sector(SU2(0) × SU2(0)) ⊗ sector(SU2(1))), + gradedrange([sector(SU2(1) × SU2(0)) => 1]), ) @test gradedisequal( - sector(SU2(1) × U1(1)) ⊗ sector(SU2(0)), gradedrange([sector(SU2(1) × U1(1)) => 1]) + (@inferred sector(SU2(1) × U1(1)) ⊗ sector(SU2(0))), + gradedrange([sector(SU2(1) × U1(1)) => 1]), ) @test gradedisequal( - sector(U1(1) × SU2(1)) ⊗ sector(U1(2)), gradedrange([sector(U1(3) × SU2(1)) => 1]) + (@inferred sector(U1(1) × SU2(1)) ⊗ sector(U1(2))), + gradedrange([sector(U1(3) × SU2(1)) => 1]), ) # check incompatible categories @@ -371,11 +374,11 @@ end q01 = sector(; B=U1(1)) q11 = sector(; A=U1(1), B=U1(1)) - @test q10 ⊗ q10 == sector(; A=U1(2)) + @test (@inferred q10 ⊗ q10) == sector(; A=U1(2)) @test (@inferred q01 ⊗ q00) == q01 @test (@inferred q00 ⊗ q01) == q01 @test (@inferred q10 ⊗ q01) == q11 - @test q11 ⊗ q11 == sector(; A=U1(2), B=U1(2)) + @test (@inferred q11 ⊗ q11) == sector(; A=U1(2), B=U1(2)) s11 = sector(; A=U1(1), B=Z{2}(1)) s10 = sector(; A=U1(1)) @@ -383,7 +386,7 @@ end @test (@inferred s01 ⊗ q00) == s01 @test (@inferred q00 ⊗ s01) == s01 @test (@inferred s10 ⊗ s01) == s11 - @test s11 ⊗ s11 == sector(; A=U1(2), B=Z{2}(0)) + @test (@inferred s11 ⊗ s11) == sector(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin @@ -393,14 +396,14 @@ end phab = sector(; A=SU2(1//2), B=SU2(1//2)) @test gradedisequal( - pha ⊗ pha, gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) + (@inferred pha ⊗ pha), gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) ) @test gradedisequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) @test gradedisequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) @test gradedisequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) @test gradedisequal( - phab ⊗ phab, + (@inferred phab ⊗ phab), gradedrange([ sector(; A=SU2(0), B=SU2(0)) => 1, sector(; A=SU2(1), B=SU2(0)) => 1, @@ -414,11 +417,11 @@ end ı = Fib("1") τ = Fib("τ") s = sector(; A=ı, B=ı) - @test gradedisequal(s ⊗ s, gradedrange([s => 1])) + @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) s = sector(; A=τ, B=τ) @test gradedisequal( - s ⊗ s, + (@inferred s ⊗ s), gradedrange([ sector(; A=ı, B=ı) => 1, sector(; A=τ, B=ı) => 1, @@ -436,7 +439,7 @@ end sector(; A=ı, B=ψ) => 1, sector(; A=τ, B=ψ) => 1, ]) - @test gradedisequal(s ⊗ s, g) + @test gradedisequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -450,16 +453,16 @@ end q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test gradedisequal(q1h ⊗ q1h, gradedrange([q20 => 1, q21 => 1])) - @test gradedisequal(q10 ⊗ q1h, gradedrange([q2h => 1])) - @test gradedisequal(q0h ⊗ q1h, gradedrange([q10 => 1, q11 => 1])) - @test gradedisequal(q11 ⊗ q11, gradedrange([q20 => 1, q21 => 1, q22 => 1])) + @test gradedisequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) + @test gradedisequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) + @test gradedisequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) + @test gradedisequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) end @testset "Fusion of fully mixed products" begin s = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) @test gradedisequal( - s ⊗ s, + (@inferred s ⊗ s), gradedrange([ sector(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, sector(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, @@ -472,7 +475,7 @@ end τ = Fib("τ") s = sector(; A=SU2(1//2), B=U1(1), C=τ) @test gradedisequal( - s ⊗ s, + (@inferred s ⊗ s), gradedrange([ sector(; A=SU2(0), B=U1(2), C=ı) => 1, sector(; A=SU2(1), B=U1(2), C=ı) => 1, @@ -483,7 +486,7 @@ end s = sector(; A=τ, B=U1(1), C=ı) @test gradedisequal( - s ⊗ s, + (@inferred s ⊗ s), gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]), ) end @@ -494,8 +497,9 @@ end g2 = gradedrange([s2 => 1]) s3 = sector(; A=U1(1), B=SU2(0), C=Ising("σ")) s4 = sector(; A=U1(1), B=SU2(1), C=Ising("σ")) - # type not inferred on julia 1.6 only - @test gradedisequal(fusion_product(g1, g2), gradedrange([s3 => 2, s4 => 2])) + @test gradedisequal( + (@inferred_latest fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2]) + ) sA = sector(; A=U1(1)) sB = sector(; B=SU2(1//2)) @@ -512,16 +516,16 @@ end @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 1 - @test trivial(s) == s # need julia 1.10 for type stability + @test (@inferred_latest trivial(s)) == s @test typeof(s) == typeof(sector(())) @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} - @test s × U1(1) == sector(U1(1)) - @test s × sector(U1(1)) == sector(U1(1)) - @test s × sector(; A=U1(1)) == sector(; A=U1(1)) - @test U1(1) × s == sector(U1(1)) - @test sector(U1(1)) × s == sector(U1(1)) - @test sector(; A=U1(1)) × s == sector(; A=U1(1)) + @test (@inferred s × U1(1)) == sector(U1(1)) + @test (@inferred s × sector(U1(1))) == sector(U1(1)) + @test (@inferred s × sector(; A=U1(1))) == sector(; A=U1(1)) + @test (@inferred U1(1) × s) == sector(U1(1)) + @test (@inferred sector(U1(1)) × s) == sector(U1(1)) + @test (@inferred sector(; A=U1(1)) × s) == sector(; A=U1(1)) # Empty acts as trivial @test (@inferred U1(1) ⊗ s) == U1(1) From 7d7e57d8eee5f09016d965462d4f7bebfbacf65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 5 Jun 2024 16:29:35 -0400 Subject: [PATCH 069/194] dual blocklabels --- NDTensors/src/lib/GradedAxes/src/unitrangedual.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index f504b2bee9..4637053926 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -65,7 +65,10 @@ BlockArrays.blockfirsts(a::UnitRangeDual) = label_dual.(blockfirsts(nondual(a))) BlockArrays.blocklasts(a::UnitRangeDual) = label_dual.(blocklasts(nondual(a))) BlockArrays.findblock(a::UnitRangeDual, index::Integer) = findblock(nondual(a), index) -blocklabels(a::UnitRangeDual) = blocklabels(nondual(a)) +blocklabels(a::UnitRangeDual) = dual.(blocklabels(nondual(a))) + +gradedisequal(a1::UnitRangeDual, a2::GradedUnitRange) = false +gradedisequal(a1::GradedUnitRange, a2::UnitRangeDual) = false function gradedisequal(a1::UnitRangeDual, a2::UnitRangeDual) return gradedisequal(nondual(a1), nondual(a2)) end From 4dae1971d1b7805bb1da095eba4419a858563d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 6 Jun 2024 12:00:42 -0400 Subject: [PATCH 070/194] fuse EmptyCategory --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index ba36dd982f..a80b49ebe5 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -83,6 +83,12 @@ function fusion_rule( return LabelledNumbers.LabelledInteger(l1 * l2, fused) end +function fusion_rule( + ::EmptyCategory, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger +) + return LabelledNumbers.LabelledInteger(l1 * l2, sector()) +end + # ============= fusion rule and gradedunitrange =================== # GradedAxes.tensor_product interface. Only for abelian groups. function GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) @@ -96,6 +102,9 @@ end function GradedAxes.fuse_labels(::AbelianGroup, c1::AbstractCategory, c2::AbstractCategory) return fusion_rule(c1, c2) end +function GradedAxes.fuse_labels(::EmptyCategory, c1::AbstractCategory, c2::AbstractCategory) + return fusion_rule(c1, c2) +end # cast to range to_graded_axis(c::AbstractCategory) = GradedAxes.gradedrange([c => 1]) From aff0006699987cd864672b851c2e9f461138875b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 6 Jun 2024 12:17:18 -0400 Subject: [PATCH 071/194] reorder file --- .../src/lib/Sectors/src/abstractcategory.jl | 22 +++++++++---------- .../src/lib/Sectors/src/category_product.jl | 7 +----- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index a80b49ebe5..963d61fcea 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -3,12 +3,12 @@ abstract type AbstractCategory end -# ============ Base interface ================= +# =================================== Base interface ===================================== function Base.isless(c1::C, c2::C) where {C<:AbstractCategory} return isless(category_label(c1), category_label(c2)) end -# ================= Misc ====================== +# ================================= Sectors interface ==================================== trivial(x) = trivial(typeof(x)) function trivial(axis_type::Type{<:AbstractUnitRange}) return GradedAxes.gradedrange([trivial(eltype(axis_type))]) # always returns nondual @@ -44,7 +44,7 @@ quantum_dimension(::EmptyCategory, ::AbstractCategory) = 1 quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) = sum(block_dimensions(g)) quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) -# ================ fusion rule interface ==================== +# =============================== Fusion rule interface ================================== ⊗(c1::AbstractCategory, c2::AbstractCategory) = fusion_rule(c1, c2) function fusion_rule(c1, c2) @@ -61,12 +61,6 @@ function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} return C(label_fusion_rule(C, category_label(c1), category_label(c2))) end -function label_fusion_rule(category_type::Type{<:AbstractCategory}, l1, l2) - return error("`label_fusion_rule` not defined for type $(category_type).") -end - -# convenient to define fusion rule for LabelledInteger too -# TBD expose this through ⊗? Currently not accessible. function fusion_rule( ::SymmetryStyle, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) @@ -89,7 +83,11 @@ function fusion_rule( return LabelledNumbers.LabelledInteger(l1 * l2, sector()) end -# ============= fusion rule and gradedunitrange =================== +function label_fusion_rule(category_type::Type{<:AbstractCategory}, ::Any, ::Any) + return error("`label_fusion_rule` not defined for type $(category_type).") +end + +# ================================ GradedAxes interface ================================== # GradedAxes.tensor_product interface. Only for abelian groups. function GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) return GradedAxes.fuse_labels( @@ -107,7 +105,7 @@ function GradedAxes.fuse_labels(::EmptyCategory, c1::AbstractCategory, c2::Abstr end # cast to range -to_graded_axis(c::AbstractCategory) = GradedAxes.gradedrange([c => 1]) +to_graded_axis(c::AbstractCategory) = to_graded_axis(LabelledNumbers.LabelledInteger(1, c)) to_graded_axis(l::LabelledNumbers.LabelledInteger) = GradedAxes.gradedrange([l]) to_graded_axis(g::AbstractUnitRange) = g @@ -124,7 +122,7 @@ GradedAxes.fusion_product(x) = GradedAxes.fusion_product(to_graded_axis(x)) GradedAxes.fusion_product(g::AbstractUnitRange) = GradedAxes.fusion_product(trivial(g), g) function GradedAxes.fusion_product( - g1::BlockArrays.BlockedUnitRange, g2::BlockArrays.BlockedUnitRange + g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange ) blocks12 = Vector{eltype(to_graded_axis(fusion_rule(first(g1), first(g2))))}() for l1 in BlockArrays.blocklengths(g1) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 31a7849be0..3c21eefd64 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -11,16 +11,11 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -# =================================== SymmetryStyle ====================================== +# ================================= Sectors interface ==================================== function SymmetryStyle(c::CategoryProduct) return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategory()) end -function SymmetryStyle(nt::NamedTuple) - return reduce(combine_styles, map(SymmetryStyle, values(nt)); init=EmptyCategory()) -end - -# ================================== Sector interface ==================================== function quantum_dimension(::NonAbelianGroup, s::CategoryProduct) return prod(map(quantum_dimension, categories(s))) end From 1aa13172e0f588e35746f0a563f076234a664b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 6 Jun 2024 12:52:05 -0400 Subject: [PATCH 072/194] replace for loops with Iterators --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 963d61fcea..968df7733b 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -124,12 +124,11 @@ GradedAxes.fusion_product(g::AbstractUnitRange) = GradedAxes.fusion_product(triv function GradedAxes.fusion_product( g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange ) - blocks12 = Vector{eltype(to_graded_axis(fusion_rule(first(g1), first(g2))))}() - for l1 in BlockArrays.blocklengths(g1) - for l2 in BlockArrays.blocklengths(g2) - append!(blocks12, BlockArrays.blocklengths(to_graded_axis(fusion_rule(l1, l2)))) - end - end + nested_blocks = map( + ((l1, l2),) -> to_graded_axis(fusion_rule(l1, l2)), + Iterators.product(BlockArrays.blocklengths(g1), BlockArrays.blocklengths(g2)), + ) + blocks12 = reduce(vcat, BlockArrays.blocklengths.(nested_blocks)) la3 = LabelledNumbers.label.(blocks12) pairs3 = [r => sum(blocks12[findall(==(r), la3)]; init=0) for r in sort(unique(la3))] out = GradedAxes.gradedrange(pairs3) From 18763f69a27fa15c04889b345fc21c4f139ad2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 6 Jun 2024 15:12:41 -0400 Subject: [PATCH 073/194] used labelled --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 14 ++++++++------ NDTensors/src/lib/Sectors/src/category_product.jl | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 968df7733b..90c9d04e97 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -53,7 +53,7 @@ end function fusion_rule(::SymmetryStyle, c1::C, c2::C) where {C<:AbstractCategory} degen, labels = label_fusion_rule(C, category_label(c1), category_label(c2)) - return GradedAxes.gradedrange(LabelledNumbers.LabelledInteger.(degen, C.(labels))) + return GradedAxes.gradedrange(LabelledNumbers.labelled.(degen, C.(labels))) end # abelian case: return Category @@ -64,9 +64,11 @@ end function fusion_rule( ::SymmetryStyle, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) - blocks12 = BlockArrays.blocklengths(LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2)) + fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) v = - LabelledNumbers.LabelledInteger.(l1 * l2 .* blocks12, LabelledNumbers.label.(blocks12)) + LabelledNumbers.labelled.( + l1 * l2 .* BlockArrays.blocklengths(fused), GradedAxes.blocklabels(fused) + ) return GradedAxes.gradedrange(v) end @@ -74,13 +76,13 @@ function fusion_rule( ::AbelianGroup, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) - return LabelledNumbers.LabelledInteger(l1 * l2, fused) + return LabelledNumbers.labelled(l1 * l2, fused) end function fusion_rule( ::EmptyCategory, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) - return LabelledNumbers.LabelledInteger(l1 * l2, sector()) + return LabelledNumbers.labelled(l1 * l2, sector()) end function label_fusion_rule(category_type::Type{<:AbstractCategory}, ::Any, ::Any) @@ -105,7 +107,7 @@ function GradedAxes.fuse_labels(::EmptyCategory, c1::AbstractCategory, c2::Abstr end # cast to range -to_graded_axis(c::AbstractCategory) = to_graded_axis(LabelledNumbers.LabelledInteger(1, c)) +to_graded_axis(c::AbstractCategory) = to_graded_axis(LabelledNumbers.labelled(1, c)) to_graded_axis(l::LabelledNumbers.LabelledInteger) = GradedAxes.gradedrange([l]) to_graded_axis(g::AbstractUnitRange) = g diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 3c21eefd64..5eee581277 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -98,7 +98,7 @@ categories_product(::Tuple{}, l2::NamedTuple) = l2 function ×(l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger) c3 = LabelledNumbers.label(l1) × LabelledNumbers.label(l2) m3 = LabelledNumbers.unlabel(l1) * LabelledNumbers.unlabel(l2) - return LabelledNumbers.LabelledInteger(m3, c3) + return LabelledNumbers.labelled(m3, c3) end function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) From 951f08eb663cf999a8a4e89ce114d9a2134e16ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 6 Jun 2024 15:58:20 -0400 Subject: [PATCH 074/194] use mapreduce --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 90c9d04e97..afbc4c009b 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -126,11 +126,11 @@ GradedAxes.fusion_product(g::AbstractUnitRange) = GradedAxes.fusion_product(triv function GradedAxes.fusion_product( g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange ) - nested_blocks = map( - ((l1, l2),) -> to_graded_axis(fusion_rule(l1, l2)), + blocks12 = mapreduce( + ((l1, l2),) -> BlockArrays.blocklengths(to_graded_axis(fusion_rule(l1, l2))), + vcat, Iterators.product(BlockArrays.blocklengths(g1), BlockArrays.blocklengths(g2)), ) - blocks12 = reduce(vcat, BlockArrays.blocklengths.(nested_blocks)) la3 = LabelledNumbers.label.(blocks12) pairs3 = [r => sum(blocks12[findall(==(r), la3)]; init=0) for r in sort(unique(la3))] out = GradedAxes.gradedrange(pairs3) From bd8e6e724d1a0ba0df35e54c11e39287640aa8d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 6 Jun 2024 21:09:40 -0400 Subject: [PATCH 075/194] define fusion_product for non-abelian groups --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 54 +++++++++++-------- .../src/lib/GradedAxes/test/test_dual.jl | 4 +- .../src/lib/Sectors/src/abstractcategory.jl | 49 ++++++----------- .../src/lib/Sectors/test/test_fusion_rules.jl | 16 +++++- 4 files changed, 65 insertions(+), 58 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 54c7308755..f6a0f0a22d 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -18,7 +18,7 @@ function tensor_product( return foldl(tensor_product, (a1, a2, a3, a_rest...)) end -function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) +function tensor_product(::AbstractUnitRange, ::AbstractUnitRange) return error("Not implemented yet.") end @@ -26,15 +26,15 @@ function tensor_product(a1::Base.OneTo, a2::Base.OneTo) return Base.OneTo(length(a1) * length(a2)) end -function tensor_product(a1::OneToOne, a2::AbstractUnitRange) +function tensor_product(::OneToOne, a2::AbstractUnitRange) return a2 end -function tensor_product(a1::AbstractUnitRange, a2::OneToOne) +function tensor_product(a1::AbstractUnitRange, ::OneToOne) return a1 end -function tensor_product(a1::OneToOne, a2::OneToOne) +function tensor_product(::OneToOne, ::OneToOne) return OneToOne() end @@ -66,11 +66,16 @@ function fuse_blocklengths(x::LabelledInteger, y::LabelledInteger) return labelled(unlabel(x) * unlabel(y), fuse_labels(label(x), label(y))) end +flatten_maybe_nested(v::Vector{<:Integer}) = v +flatten_maybe_nested(v::Vector{<:GradedUnitRange}) = reduce(vcat, blocklengths.(v)) + using BlockArrays: blockedrange, blocks function tensor_product(a1::BlockedUnitRange, a2::BlockedUnitRange) - blocklengths = map(vec(collect(Iterators.product(blocks(a1), blocks(a2))))) do x - return mapreduce(length, fuse_blocklengths, x) - end + maybe_nested = map( + it -> mapreduce(length, fuse_blocklengths, it), + Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),)), + ) + blocklengths = flatten_maybe_nested(maybe_nested) return blockedrange(blocklengths) end @@ -78,11 +83,9 @@ function blocksortperm(a::BlockedUnitRange) return Block.(sortperm(blocklabels(a))) end +# convention: sort UnitRangeDual according to nondual blocks function blocksortperm(a::UnitRangeDual) - # If it is dual, reverse the sorting so the sectors - # end up sorted in the same way whether or not the space - # is dual. - return Block.(sortperm(blocklabels(label_dual(dual(a))))) + return Block.(sortperm(blocklabels(nondual(a)))) end using BlockArrays: Block, BlockVector @@ -97,8 +100,6 @@ function groupsortperm(v; kwargs...) end # Used by `TensorAlgebra.splitdims` in `BlockSparseArraysGradedAxesExt`. -# Get the permutation for sorting, then group by common elements. -# groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]] function blockmergesortperm(a::BlockedUnitRange) return Block.(groupsortperm(blocklabels(a))) end @@ -107,18 +108,29 @@ end invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) function blockmergesortperm(a::UnitRangeDual) - # If it is dual, reverse the sorting so the sectors - # end up sorted in the same way whether or not the space - # is dual. - return Block.(groupsortperm(blocklabels(label_dual(dual(a))))) + return Block.(groupsortperm(blocklabels(nondual(a)))) end -# fusion_product generalizes tensor_product to non-abelian groups and fusion categories -# in the case of abelian groups, it is equivalent to tensor_product + applying blockmergesortperm -function fusion_product(::AbstractUnitRange, ::AbstractUnitRange) - return error("Not implemented") +function blockmergesort(g::GradedUnitRange) + glabels = blocklabels(g) + gblocklengths = blocklengths(g) + new_blocklengths = map( + la -> labelled(sum(gblocklengths[findall(==(la), glabels)]; init=0), la), + sort(unique(glabels)), + ) + return GradedAxes.gradedrange(new_blocklengths) end +blockmergesort(g::UnitRangeDual) = dual(blockmergesort(nondual(g))) + +# fusion_product produces a sorted, non-dual GradedUnitRange +function fusion_product(g1, g2) + return blockmergesort(tensor_product(g1, g2)) +end + +fusion_product(g::GradedUnitRange) = blockmergesort(g) +fusion_product(g::UnitRangeDual) = fusion_product(label_dual(nondual(g))) + # recursive fusion_product. Simpler than reduce + fix type stability issues with reduce function fusion_product(g1, g2, g3...) return fusion_product(fusion_product(g1, g2), g3...) diff --git a/NDTensors/src/lib/GradedAxes/test/test_dual.jl b/NDTensors/src/lib/GradedAxes/test/test_dual.jl index 4efd1ed9cc..a3486e5361 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_dual.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_dual.jl @@ -46,10 +46,10 @@ Base.isless(c1::U1, c2::U1) = c1.n < c2.n @test ad[[Block(2)[1:2], Block(1)[1:2]]][Block(1)] == 3:4 @test label(ad[[Block(2)[1:2], Block(1)[1:2]]][Block(1)]) == U1(-1) @test blocksortperm(a) == [Block(1), Block(2)] - @test blocksortperm(ad) == [Block(2), Block(1)] + @test blocksortperm(ad) == [Block(1), Block(2)] @test blocklength(blockmergesortperm(a)) == 2 @test blocklength(blockmergesortperm(ad)) == 2 @test blockmergesortperm(a) == [Block(1), Block(2)] - @test blockmergesortperm(ad) == [Block(2), Block(1)] + @test blockmergesortperm(ad) == [Block(1), Block(2)] end end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index afbc4c009b..95637ebe81 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -90,20 +90,12 @@ function label_fusion_rule(category_type::Type{<:AbstractCategory}, ::Any, ::Any end # ================================ GradedAxes interface ================================== -# GradedAxes.tensor_product interface. Only for abelian groups. -function GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) - return GradedAxes.fuse_labels( - combine_styles(SymmetryStyle(c1), SymmetryStyle(c2)), c1, c2 - ) -end -function GradedAxes.fuse_labels(::SymmetryStyle, c1::AbstractCategory, c2::AbstractCategory) - return error("`fuse_labels` is only defined for abelian groups") -end -function GradedAxes.fuse_labels(::AbelianGroup, c1::AbstractCategory, c2::AbstractCategory) - return fusion_rule(c1, c2) -end -function GradedAxes.fuse_labels(::EmptyCategory, c1::AbstractCategory, c2::AbstractCategory) - return fusion_rule(c1, c2) +# tensor_product interface +function GradedAxes.fuse_blocklengths( + l1::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, + l2::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, +) + return fusion_rule(l1, l2) end # cast to range @@ -112,27 +104,18 @@ to_graded_axis(l::LabelledNumbers.LabelledInteger) = GradedAxes.gradedrange([l]) to_graded_axis(g::AbstractUnitRange) = g # allow to fuse a category with a GradedUnitRange -function GradedAxes.fusion_product(a, b) - return GradedAxes.fusion_product(to_graded_axis(a), to_graded_axis(b)) +function GradedAxes.tensor_product(c::AbstractCategory, g::AbstractUnitRange) + return GradedAxes.tensor_product(to_graded_axis(c), g) end -# fusion_product with one input to be used in generic fusion_product(Tuple...) -# TBD define fusion_product() = gradedrange([sector(())=>1])? -GradedAxes.fusion_product(x) = GradedAxes.fusion_product(to_graded_axis(x)) +function GradedAxes.tensor_product(g::AbstractUnitRange, c::AbstractCategory) + return GradedAxes.tensor_product(c, g) +end -# product with trivial = easy handling of UnitRangeDual + sort and merge blocks -GradedAxes.fusion_product(g::AbstractUnitRange) = GradedAxes.fusion_product(trivial(g), g) +function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) + return GradedAxes.tensor_product(to_graded_axis(c1), to_graded_axis(c2)) +end -function GradedAxes.fusion_product( - g1::GradedAxes.GradedUnitRange, g2::GradedAxes.GradedUnitRange -) - blocks12 = mapreduce( - ((l1, l2),) -> BlockArrays.blocklengths(to_graded_axis(fusion_rule(l1, l2))), - vcat, - Iterators.product(BlockArrays.blocklengths(g1), BlockArrays.blocklengths(g2)), - ) - la3 = LabelledNumbers.label.(blocks12) - pairs3 = [r => sum(blocks12[findall(==(r), la3)]; init=0) for r in sort(unique(la3))] - out = GradedAxes.gradedrange(pairs3) - return out +function GradedAxes.fusion_product(c::AbstractCategory) + return GradedAxes.fusion_product(to_graded_axis(c)) end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index d979794a9d..6e759dc347 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -175,9 +175,21 @@ end @testset "GradedUnitRange non-abelian fusion rules" begin g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2, SU2(1) => 1]) g4 = gradedrange([SU2(1//2) => 1, SU2(1) => 2]) + g34 = gradedrange([ + SU2(1//2) => 1, + SU2(0) => 2, + SU2(1) => 2, + SU2(1//2) => 1, + SU2(3//2) => 1, + SU2(1) => 2, + SU2(1//2) => 4, + SU2(3//2) => 4, + SU2(0) => 2, + SU2(1) => 2, + SU2(2) => 2, + ]) - # test tensor_product not defined for abelian - @test_throws ErrorException tensor_product(g3, g4) + @test gradedisequal(tensor_product(g3, g4), g34) @test gradedisequal(label_dual(g3), g3) # trivial for SU(2) @test gradedisequal( From f366af7b47e717ec44323d269c14ee817c4edfaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 7 Jun 2024 10:56:28 -0400 Subject: [PATCH 076/194] add tests --- .../GradedAxes/test/test_tensor_product.jl | 29 +++++++++++++++++-- .../src/lib/Sectors/src/abstractcategory.jl | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl index 63a27a62e3..83bf038ce2 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl @@ -1,11 +1,36 @@ @eval module $(gensym()) -using NDTensors.GradedAxes: GradedAxes, GradedUnitRange, gradedrange, tensor_product +using NDTensors.GradedAxes: + GradedAxes, GradedUnitRange, fusion_product, gradedrange, gradedisequal, tensor_product +using BlockArrays: blocklength, blocklengths using Test: @test, @testset + @testset "GradedAxes.tensor_product" begin GradedAxes.fuse_labels(x::String, y::String) = x * y a = gradedrange(["x" => 2, "y" => 3]) b = tensor_product(a, a) - @test length(b) == 25 @test b isa GradedUnitRange + @test length(b) == 25 + @test blocklength(b) == 4 + @test blocklengths(b) == [4, 6, 6, 9] + @test gradedisequal(b, gradedrange(["xx" => 4, "yx" => 6, "xy" => 6, "yy" => 9])) + + c = tensor_product(a, a, a) + @test length(c) == 125 + @test c isa GradedUnitRange + @test blocklength(c) == 8 +end + +@testset "GradedAxes.fusion_product" begin + GradedAxes.fuse_labels(i::Int, j::Int) = i + j + a = gradedrange([1 => 1, 2 => 3, 1 => 1]) + + b = fusion_product(a) + @test gradedisequal(b, gradedrange([1 => 2, 2 => 3])) + + c = fusion_product(a, a) + @test gradedisequal(c, gradedrange([2 => 4, 3 => 12, 4 => 9])) + + d = fusion_product(a, a, a) + @test gradedisequal(d, gradedrange([3 => 8, 4 => 36, 5 => 54, 6 => 27])) end end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 95637ebe81..51311ab201 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -113,7 +113,7 @@ function GradedAxes.tensor_product(g::AbstractUnitRange, c::AbstractCategory) end function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) - return GradedAxes.tensor_product(to_graded_axis(c1), to_graded_axis(c2)) + return to_graded_axis(fusion_rule(c1, c2)) end function GradedAxes.fusion_product(c::AbstractCategory) From cbda58b3e8156f9e6afff135c4c5460c20de6e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 7 Jun 2024 11:15:12 -0400 Subject: [PATCH 077/194] support OneToOne --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 7 ++++++- .../lib/GradedAxes/test/test_tensor_product.jl | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index f6a0f0a22d..d38d4e3196 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -6,6 +6,10 @@ OneToOne() = OneToOne{Bool}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) +gradedisequal(::AbstractUnitRange, ::OneToOne) = false +gradedisequal(::OneToOne, ::AbstractUnitRange) = false +gradedisequal(::OneToOne, ::OneToOne) = true + # https://github.com/ITensor/ITensors.jl/blob/v0.3.57/NDTensors/src/lib/GradedAxes/src/tensor_product.jl # https://en.wikipedia.org/wiki/Tensor_product # https://github.com/KeitaNakamura/Tensorial.jl @@ -122,13 +126,14 @@ function blockmergesort(g::GradedUnitRange) end blockmergesort(g::UnitRangeDual) = dual(blockmergesort(nondual(g))) +blockmergesort(g::OneToOne) = g # fusion_product produces a sorted, non-dual GradedUnitRange function fusion_product(g1, g2) return blockmergesort(tensor_product(g1, g2)) end -fusion_product(g::GradedUnitRange) = blockmergesort(g) +fusion_product(g::AbstractUnitRange) = blockmergesort(g) fusion_product(g::UnitRangeDual) = fusion_product(label_dual(nondual(g))) # recursive fusion_product. Simpler than reduce + fix type stability issues with reduce diff --git a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl index 83bf038ce2..d3e2a35656 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl @@ -1,11 +1,21 @@ @eval module $(gensym()) using NDTensors.GradedAxes: - GradedAxes, GradedUnitRange, fusion_product, gradedrange, gradedisequal, tensor_product + GradedAxes, + GradedUnitRange, + OneToOne, + fusion_product, + gradedrange, + gradedisequal, + tensor_product using BlockArrays: blocklength, blocklengths using Test: @test, @testset @testset "GradedAxes.tensor_product" begin GradedAxes.fuse_labels(x::String, y::String) = x * y + + g0 = OneToOne() + @test gradedisequal(tensor_product(g0, g0), g0) + a = gradedrange(["x" => 2, "y" => 3]) b = tensor_product(a, a) @test b isa GradedUnitRange @@ -22,6 +32,10 @@ end @testset "GradedAxes.fusion_product" begin GradedAxes.fuse_labels(i::Int, j::Int) = i + j + + g0 = OneToOne() + @test gradedisequal(fusion_product(g0, g0), g0) + a = gradedrange([1 => 1, 2 => 3, 1 => 1]) b = fusion_product(a) From cdc40307b29565bb11e6c0af51ae03941bc414bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 7 Jun 2024 14:26:16 -0400 Subject: [PATCH 078/194] share more implementation --- .../src/lib/Sectors/src/category_product.jl | 81 +++++++++---------- .../lib/Sectors/test/test_category_product.jl | 10 +++ 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 5eee581277..6d75591e40 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -27,7 +27,8 @@ end GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) function trivial(type::Type{<:CategoryProduct}) - return sector(categories_trivial(categories_type(type))) + cat_type = categories_type(type) + return recover_key(cat_type, categories_trivial(cat_type)) end # =================================== Base interface ===================================== @@ -62,11 +63,34 @@ end # - ordered-like with a Tuple # - dictionary-like with a NamedTuple +function categories_fusion_rule(cats1, cats2) + diff_cat = CategoryProduct(find_diff(cats1, cats2)) + nt1, nt2 = find_common(cats1, cats2) + fused = map(fusion_rule, values(nt1), values(nt2)) + return recover_key(typeof(nt1), fused) × diff_cat +end + categories_isequal(::Tuple, ::NamedTuple) = false categories_isequal(::NamedTuple, ::Tuple) = false +categories_trivial(cat_type::Type) = trivial.(fieldtypes(cat_type)) + categories_type(::Type{<:CategoryProduct{T}}) where {T} = T +recover_key(T::Type, t::Tuple{Vararg{<:AbstractCategory}}) = sector(T, t) +recover_key(T::Type, c::AbstractCategory) = recover_key(T, (c,)) +recover_key(T::Type, c::CategoryProduct) = recover_key(T, categories(c)) + +function recover_key(T::Type, fused::Tuple) + # here fused contains at leat one GradedUnitRange + g0 = reduce(×, fused) + # convention: keep unsorted blocklabels as produced by F order loops in × + new_labels = recover_key.(T, GradedAxes.blocklabels(g0)) + new_blocklengths = + LabelledNumbers.labelled.(GradedAxes.unlabel.(BlockArrays.blocklengths(g0)), new_labels) + return GradedAxes.gradedrange(new_blocklengths) +end + sector(T::Type{<:CategoryProduct}, cats::Tuple) = sector(categories_type(T), cats) sector(T::Type, cats::Tuple) = sector(T(cats)) # recover NamedTuple sector(args...; kws...) = CategoryProduct(args...; kws...) @@ -169,19 +193,16 @@ CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) -function categories_trivial(type::Type{<:Tuple}) - return trivial.(fieldtypes(type)) +function find_common(t1::Tuple, t2::Tuple) + n = min(length(t1), length(t2)) + return t1[begin:n], t2[begin:n] end -# allow additional categories at one end -function categories_fusion_rule(cats1::Tuple, cats2::Tuple) - n = min(length(cats1), length(cats2)) - shared = map(fusion_rule, cats1[begin:n], cats2[begin:n]) - sup1 = CategoryProduct(cats1[(n + 1):end]) - sup2 = CategoryProduct(cats2[(n + 1):end]) - return reduce(×, (shared..., sup1, sup2)) +function find_diff(t1::Tuple, t2::Tuple) + n1 = length(t1) + n2 = length(t2) + return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] end - # =========================== Dictionary-like implementation ============================= function CategoryProduct(nt::NamedTuple) categories = sort_keys(nt) @@ -200,40 +221,14 @@ function CategoryProduct(pairs::Pair...) end function categories_isequal(A::NamedTuple, B::NamedTuple) - common_categories = zip(pairs(intersect_keys(A, B)), pairs(intersect_keys(B, A))) - common_categories_match = all(nl -> (nl[1] == nl[2]), common_categories) - unique_categories_zero = all(l -> istrivial(l), symdiff_keys(A, B)) + sharedA, sharedB = find_common(A, B) + common_categories_match = sharedA == sharedB + unique_categories_zero = all(map(istrivial, find_diff(A, B))) return common_categories_match && unique_categories_zero end -function categories_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} - return NamedTuple{Keys}(trivial.(fieldtypes(type))) -end - -# allow ⊗ for different types in NamedTuple -function categories_fusion_rule(cats1::NamedTuple, cats2::NamedTuple) - diff_cat = CategoryProduct(symdiff_keys(cats1, cats2)) - nt1 = intersect_keys(cats1, cats2) - nt2 = intersect_keys(cats2, cats1) - fused = map(fusion_rule, values(nt1), values(nt2)) - return diff_cat × recover_key(typeof(nt1), fused) -end - -function recover_key(NT::Type, fused::Tuple{Vararg{<:AbstractCategory}}) - return sector(NT, fused) -end - -function recover_key(NT::Type, fused::AbstractCategory) - return recover_key(NT, (fused,)) -end - -function recover_key(NT::Type, fused::CategoryProduct) - return recover_key(NT, categories(fused)) +function find_common(nt1::NamedTuple, nt2::NamedTuple) + return intersect_keys(nt1, nt2), intersect_keys(nt2, nt1) end -function recover_key(NT::Type, fused::Tuple) - g0 = reduce(×, fused) - blocklabels_key = recover_key.(NT, GradedAxes.blocklabels(g0)) - pairs_key = blocklabels_key .=> LabelledNumbers.unlabel.(BlockArrays.blocklengths(g0)) - return GradedAxes.gradedrange(pairs_key) -end +find_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index b39f95d559..722cc5e97e 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -59,6 +59,9 @@ end @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") @test (@inferred_latest trivial(s)) == sector(U1(0), SU2(0), Fib("1")) + + # convention: categories must have same length to evaluate as equal + @test sector(U1(1)) != sector(U1(1), U1(0)) end @testset "Quantum dimension and GradedUnitRange" begin @@ -306,6 +309,8 @@ end end @testset "Comparisons with unspecified labels" begin + # convention: categories evaluate as equal if unmatched labels are trivial + # this is different from ordered tuple convention q2 = sector(; N=U1(2)) q20 = (N=U1(2),) × (J=SU2(0),) @test q20 == q2 @@ -544,5 +549,10 @@ end @test (@inferred sector(; A=SU2(0)) ⊗ s) == gradedrange([sector(; A=SU2(0)) => 1]) @test (@inferred sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == gradedrange([sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) + + # Empty evaluates equal to itself only + @test s != U1(0) + @test s != sector(U1(0)) + @test s != sector(; A=U1(0)) end end From 1ef04c1f879b650526cc7c51744823dfefb2f25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 7 Jun 2024 15:24:30 -0400 Subject: [PATCH 079/194] reorder file --- .../src/lib/Sectors/src/category_product.jl | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 6d75591e40..41473e637b 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -49,7 +49,6 @@ function Base.show(io::IO, s::CategoryProduct) end category_show(io::IO, ::Int, v) = print(io, v) - category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") function Base.isless(s1::C, s2::C) where {C<:CategoryProduct} @@ -154,38 +153,24 @@ function fusion_rule( end # EmptyCategory acts as trivial on any AbstractCategory, not just CategoryProduct -function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c2::AbstractCategory) - return to_graded_axis(c2) +function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c::AbstractCategory) + return to_graded_axis(c) end - -function fusion_rule(::SymmetryStyle, c1::AbstractCategory, ::CategoryProduct{Tuple{}}) - return to_graded_axis(c1) +function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c::CategoryProduct) + return to_graded_axis(c) end - -function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c2::CategoryProduct) - return to_graded_axis(c2) +function fusion_rule(::SymmetryStyle, c::AbstractCategory, ::CategoryProduct{Tuple{}}) + return to_graded_axis(c) end - -function fusion_rule(::SymmetryStyle, c1::CategoryProduct, ::CategoryProduct{Tuple{}}) - return to_graded_axis(c1) +function fusion_rule(::SymmetryStyle, c::CategoryProduct, ::CategoryProduct{Tuple{}}) + return to_graded_axis(c) end # abelian case: return Category -function fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c2::AbstractCategory) - return c2 -end - -function fusion_rule(::AbelianGroup, c1::AbstractCategory, ::CategoryProduct{Tuple{}}) - return c1 -end - -function fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c2::CategoryProduct) - return c2 -end - -function fusion_rule(::AbelianGroup, c1::CategoryProduct, ::CategoryProduct{Tuple{}}) - return c1 -end +fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c::AbstractCategory) = c +fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c::CategoryProduct) = c +fusion_rule(::AbelianGroup, c::AbstractCategory, ::CategoryProduct{Tuple{}}) = c +fusion_rule(::AbelianGroup, c::CategoryProduct, ::CategoryProduct{Tuple{}}) = c # =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) @@ -203,6 +188,7 @@ function find_diff(t1::Tuple, t2::Tuple) n2 = length(t2) return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] end + # =========================== Dictionary-like implementation ============================= function CategoryProduct(nt::NamedTuple) categories = sort_keys(nt) From 2bfe8ddac7ddf951a28d838dbcb8aec76d3e5657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 7 Jun 2024 17:55:14 -0400 Subject: [PATCH 080/194] rigorous comparisons --- .../src/lib/Sectors/src/category_product.jl | 92 ++++++++++++------- .../lib/Sectors/test/test_category_product.jl | 30 +++++- 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 41473e637b..480661937a 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -26,10 +26,7 @@ end GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) -function trivial(type::Type{<:CategoryProduct}) - cat_type = categories_type(type) - return recover_key(cat_type, categories_trivial(cat_type)) -end +trivial(type::Type{<:CategoryProduct}) = sector(categories_trivial(categories_type(type))) # =================================== Base interface ===================================== function Base.:(==)(A::CategoryProduct, B::CategoryProduct) @@ -52,9 +49,10 @@ category_show(io::IO, ::Int, v) = print(io, v) category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") function Base.isless(s1::C, s2::C) where {C<:CategoryProduct} - return isless( - category_label.(values(categories(s1))), category_label.(values(categories(s2))) - ) + return categories_isless(categories(s1), categories(s2)) +end +function Base.isless(s1::CategoryProduct, s2::CategoryProduct) + return categories_isless(categories(s1), categories(s2)) end # ======================================= shared ========================================= @@ -64,22 +62,23 @@ end function categories_fusion_rule(cats1, cats2) diff_cat = CategoryProduct(find_diff(cats1, cats2)) - nt1, nt2 = find_common(cats1, cats2) - fused = map(fusion_rule, values(nt1), values(nt2)) - return recover_key(typeof(nt1), fused) × diff_cat + shared1, shared2 = find_common(cats1, cats2) + fused = map(fusion_rule, values(shared1), values(shared2)) + return recover_key(typeof(shared1), fused) × diff_cat end +# get clean results when mixing implementations categories_isequal(::Tuple, ::NamedTuple) = false categories_isequal(::NamedTuple, ::Tuple) = false -categories_trivial(cat_type::Type) = trivial.(fieldtypes(cat_type)) +categories_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) +categories_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) categories_type(::Type{<:CategoryProduct{T}}) where {T} = T recover_key(T::Type, t::Tuple{Vararg{<:AbstractCategory}}) = sector(T, t) recover_key(T::Type, c::AbstractCategory) = recover_key(T, (c,)) recover_key(T::Type, c::CategoryProduct) = recover_key(T, categories(c)) - function recover_key(T::Type, fused::Tuple) # here fused contains at leat one GradedUnitRange g0 = reduce(×, fused) @@ -100,18 +99,6 @@ function ×(p1::CategoryProduct, p2::CategoryProduct) return CategoryProduct(categories_product(categories(p1), categories(p2))) end -function categories_product(l1::NamedTuple, l2::NamedTuple) - if length(intersect_keys(l1, l2)) > 0 - throw(error("Cannot define product of shared keys")) - end - return union_keys(l1, l2) -end -categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) - -# edge cases -categories_product(l1::NamedTuple, ::Tuple{}) = l1 -categories_product(::Tuple{}, l2::NamedTuple) = l2 - ×(a, g::AbstractUnitRange) = ×(to_graded_axis(a), g) ×(g::AbstractUnitRange, b) = ×(g, to_graded_axis(b)) ×(nt1::NamedTuple, nt2::NamedTuple) = ×(CategoryProduct(nt1), CategoryProduct(nt2)) @@ -178,6 +165,13 @@ CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) +categories_isless(::Tuple, ::Tuple) = throw(ArgumentError("Not implemented")) +categories_isless(t1::T, t2::T) where {T<:Tuple} = t1 < t2 + +categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) + +categories_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) + function find_common(t1::Tuple, t2::Tuple) n = min(length(t1), length(t2)) return t1[begin:n], t2[begin:n] @@ -195,22 +189,58 @@ function CategoryProduct(nt::NamedTuple) return _CategoryProduct(categories) end +CategoryProduct(; kws...) = CategoryProduct((; kws...)) + # avoid having 2 different kinds of EmptyCategory: cast empty NamedTuple to Tuple{} CategoryProduct(::NamedTuple{()}) = CategoryProduct(()) -CategoryProduct(; kws...) = CategoryProduct((; kws...)) - function CategoryProduct(pairs::Pair...) keys = ntuple(n -> Symbol(pairs[n][1]), length(pairs)) vals = ntuple(n -> pairs[n][2], length(pairs)) return CategoryProduct(NamedTuple{keys}(vals)) end -function categories_isequal(A::NamedTuple, B::NamedTuple) - sharedA, sharedB = find_common(A, B) - common_categories_match = sharedA == sharedB - unique_categories_zero = all(map(istrivial, find_diff(A, B))) - return common_categories_match && unique_categories_zero +# sector() acts as empty NamedTuple +function categories_isequal(nt::NamedTuple, ::Tuple{}) + return categories_isequal(nt, categories_trivial(typeof(nt))) +end +function categories_isequal(::Tuple{}, nt::NamedTuple) + return categories_isequal(categories_trivial(typeof(nt)), nt) +end +function categories_isequal(nt1::NamedTuple, nt2::NamedTuple) + return ==(sym_categories_insert_unspecified(nt1, nt2)...) +end + +function categories_isless(nt::NamedTuple, ::Tuple{}) + return categories_isless(nt, categories_trivial(typeof(nt))) +end +function categories_isless(::Tuple{}, nt::NamedTuple) + return categories_isless(categories_trivial(typeof(nt)), nt) +end +function categories_isless(nt1::NamedTuple, nt2::NamedTuple) + return isless(sym_categories_insert_unspecified(nt1, nt2)...) +end + +function sym_categories_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) + return categories_insert_unspecified(nt1, nt2), categories_insert_unspecified(nt2, nt1) +end + +function categories_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) + diff1 = categories_trivial(typeof(setdiff_keys(nt2, nt1))) + return sort_keys(union_keys(nt1, diff1)) +end + +categories_product(l1::NamedTuple, ::Tuple{}) = l1 +categories_product(::Tuple{}, l2::NamedTuple) = l2 +function categories_product(l1::NamedTuple, l2::NamedTuple) + if length(intersect_keys(l1, l2)) > 0 + throw(ArgumentError("Cannot define product of shared keys")) + end + return union_keys(l1, l2) +end + +function categories_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} + return NamedTuple{Keys}(trivial.(fieldtypes(type))) end function find_common(nt1::NamedTuple, nt2::NamedTuple) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 722cc5e97e..8f70741730 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -59,9 +59,19 @@ end @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") @test (@inferred_latest trivial(s)) == sector(U1(0), SU2(0), Fib("1")) + end + @testset "Ordered comparisons" begin # convention: categories must have same length to evaluate as equal + @test sector(U1(1), SU2(1)) == sector(U1(1), SU2(1)) + @test sector(U1(1), SU2(0)) != sector(U1(1), SU2(1)) + @test sector(U1(0), SU2(1)) != sector(U1(1), SU2(1)) @test sector(U1(1)) != sector(U1(1), U1(0)) + + # convention: categories must have same length to be compared + @test sector(U1(0)) < sector((U1(1))) + @test sector(U1(0), U1(2)) < sector((U1(1)), U1(0)) + @test_throws ArgumentError sector(U1(0)) < sector(U1(1), U1(2)) end @testset "Quantum dimension and GradedUnitRange" begin @@ -289,7 +299,7 @@ end s1 = (A=U1(1),) × (B=Z{2}(0),) s2 = (A=U1(1),) × (C=Z{2}(0),) - @test_throws ErrorException s1 × s2 + @test_throws ArgumentError s1 × s2 end @testset "Construct from Pairs" begin @@ -314,9 +324,13 @@ end q2 = sector(; N=U1(2)) q20 = (N=U1(2),) × (J=SU2(0),) @test q20 == q2 + @test !(q20 < q2) + @test !(q2 < q20) q21 = (N=U1(2),) × (J=SU2(1),) @test q21 != q2 + @test q20 < q21 + @test q2 < q21 a = (A=U1(0),) × (B=U1(2),) b = (B=U1(2),) × (C=U1(0),) @@ -517,6 +531,7 @@ end @testset "Empty category" begin s = sector() + @test s == s @test (@inferred dual(s)) == s @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s @@ -550,9 +565,18 @@ end @test (@inferred sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == gradedrange([sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) - # Empty evaluates equal to itself only + # Empty behaves as empty NamedTuple @test s != U1(0) @test s != sector(U1(0)) - @test s != sector(; A=U1(0)) + @test s != sector(; A=U1(1)) + @test s == sector(; A=U1(0)) + @test sector(; A=U1(0)) == s + + @test !(s < s) + @test_throws ArgumentError s < sector(U1(0)) + @test s < sector(; A=U1(1)) + @test s > sector(; A=U1(-1)) + @test !(s < sector(; A=U1(0))) + @test !(s > sector(; A=U1(0))) end end From 3b4e3cbfd501993cce38f614ca69b3c6c2518550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 14 Jun 2024 11:48:30 -0400 Subject: [PATCH 081/194] fix tests --- NDTensors/src/lib/GradedAxes/src/dual.jl | 2 +- NDTensors/src/lib/GradedAxes/src/fusion.jl | 4 ++-- NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl | 5 ++++- NDTensors/src/lib/GradedAxes/src/unitrangedual.jl | 6 +++--- NDTensors/src/lib/GradedAxes/test/test_basics.jl | 4 +++- .../src/lib/GradedAxes/test/test_tensor_product.jl | 13 +++++++++---- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/dual.jl b/NDTensors/src/lib/GradedAxes/src/dual.jl index 087c0db336..9b5bb9db06 100644 --- a/NDTensors/src/lib/GradedAxes/src/dual.jl +++ b/NDTensors/src/lib/GradedAxes/src/dual.jl @@ -7,4 +7,4 @@ label_dual(::NotLabelled, x) = x label_dual(::IsLabelled, x) = labelled(unlabel(x), dual(label(x))) # TBD rename deepdual? yet another name? -label_dual(g::GradedUnitRange) = gradedrange(label_dual.(blocklengths(g))) +label_dual(g::AbstractGradedUnitRange) = gradedrange(label_dual.(blocklengths(g))) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 101b840b80..9e2062a5d9 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -71,7 +71,7 @@ function fuse_blocklengths(x::LabelledInteger, y::LabelledInteger) end flatten_maybe_nested(v::Vector{<:Integer}) = v -flatten_maybe_nested(v::Vector{<:GradedUnitRange}) = reduce(vcat, blocklengths.(v)) +flatten_maybe_nested(v::Vector{<:AbstractGradedUnitRange}) = reduce(vcat, blocklengths.(v)) using BlockArrays: blockedrange, blocks function tensor_product(a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRange) @@ -117,7 +117,7 @@ function blockmergesortperm(a::UnitRangeDual) return Block.(groupsortperm(blocklabels(nondual(a)))) end -function blockmergesort(g::GradedUnitRange) +function blockmergesort(g::AbstractGradedUnitRange) glabels = blocklabels(g) gblocklengths = blocklengths(g) new_blocklengths = map( diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index dc60959e4b..73708503c8 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -42,7 +42,10 @@ end # == is just a range comparison that ignores labels. Need dedicated function to check equality. function gradedisequal(a1::AbstractUnitRange, a2::AbstractUnitRange) - return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) + # TODO remove workaround once BlockArrays.blockisequal is generalized to Integer + blocka1 = BlockArrays.blockedrange(GradedAxes.unlabel.(BlockArrays.blocklengths(a1))) + blocka2 = BlockArrays.blockedrange(GradedAxes.unlabel.(BlockArrays.blocklengths(a2))) + return blockisequal(blocka1, blocka2) && (blocklabels(a1) == blocklabels(a2)) end # TODO: Use `TypeParameterAccessors`. diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index 9432a75faa..5e8f9c0ff3 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -7,7 +7,7 @@ dual(a::AbstractUnitRange) = UnitRangeDual(a) nondual(a::UnitRangeDual) = a.nondual_unitrange dual(a::UnitRangeDual) = nondual(a) nondual(a::AbstractUnitRange) = a -isdual(::GradedUnitRange) = false +isdual(::AbstractGradedUnitRange) = false isdual(::UnitRangeDual) = true ## TODO: Define this to instantiate a dual unit range. ## materialize_dual(a::UnitRangeDual) = materialize_dual(nondual(a)) @@ -78,8 +78,8 @@ BlockArrays.findblock(a::UnitRangeDual, index::Integer) = findblock(nondual(a), blocklabels(a::UnitRangeDual) = dual.(blocklabels(nondual(a))) -gradedisequal(a1::UnitRangeDual, a2::GradedUnitRange) = false -gradedisequal(a1::GradedUnitRange, a2::UnitRangeDual) = false +gradedisequal(::UnitRangeDual, ::AbstractGradedUnitRange) = false +gradedisequal(::AbstractGradedUnitRange, ::UnitRangeDual) = false function gradedisequal(a1::UnitRangeDual, a2::UnitRangeDual) return gradedisequal(nondual(a1), nondual(a2)) end diff --git a/NDTensors/src/lib/GradedAxes/test/test_basics.jl b/NDTensors/src/lib/GradedAxes/test/test_basics.jl index 87d6910550..e7fb1dfd1f 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_basics.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_basics.jl @@ -10,7 +10,8 @@ using BlockArrays: blocklengths, blocks using NDTensors.BlockSparseArrays: BlockSparseVector -using NDTensors.GradedAxes: GradedOneTo, GradedUnitRange, blocklabels, gradedrange +using NDTensors.GradedAxes: + GradedOneTo, GradedUnitRange, blocklabels, gradedisequal, gradedrange using NDTensors.LabelledNumbers: LabelledUnitRange, islabelled, label, labelled, unlabel using Test: @test, @test_broken, @testset @testset "GradedAxes basics" begin @@ -41,6 +42,7 @@ using Test: @test, @test_broken, @testset @test label(x) == "y" end @test isnothing(iterate(a, labelled(5, "y"))) + @test gradedisequal(a, a) @test length(a) == 5 @test step(a) == 1 @test !islabelled(step(a)) diff --git a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl index f28c33fc02..d99091508b 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl @@ -1,6 +1,12 @@ @eval module $(gensym()) -using NDTensors.GradedAxes: GradedAxes, GradedOneTo -GradedUnitRange, OneToOne, fusion_product, gradedrange, gradedisequal, tensor_product +using NDTensors.GradedAxes: + GradedAxes, + GradedOneTo, + OneToOne, + fusion_product, + gradedrange, + gradedisequal, + tensor_product using BlockArrays: blocklength, blocklengths using Test: @test, @testset @@ -12,7 +18,6 @@ using Test: @test, @testset a = gradedrange(["x" => 2, "y" => 3]) b = tensor_product(a, a) - @test b isa GradedUnitRange @test b isa GradedOneTo @test length(b) == 25 @test blocklength(b) == 4 @@ -20,8 +25,8 @@ using Test: @test, @testset @test gradedisequal(b, gradedrange(["xx" => 4, "yx" => 6, "xy" => 6, "yy" => 9])) c = tensor_product(a, a, a) + @test c isa GradedOneTo @test length(c) == 125 - @test c isa GradedUnitRange @test blocklength(c) == 8 end From d42c017ac1b11d99b381797f9fa632d1e32d5f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 14 Jun 2024 14:53:46 -0400 Subject: [PATCH 082/194] define flip --- NDTensors/src/lib/GradedAxes/src/dual.jl | 3 +- NDTensors/src/lib/GradedAxes/src/fusion.jl | 19 ++------ .../src/lib/GradedAxes/src/unitrangedual.jl | 1 + .../src/lib/GradedAxes/test/test_dual.jl | 43 +++++++++++++++++-- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/dual.jl b/NDTensors/src/lib/GradedAxes/src/dual.jl index 9b5bb9db06..ff11b1103c 100644 --- a/NDTensors/src/lib/GradedAxes/src/dual.jl +++ b/NDTensors/src/lib/GradedAxes/src/dual.jl @@ -6,5 +6,4 @@ label_dual(x) = label_dual(LabelledStyle(x), x) label_dual(::NotLabelled, x) = x label_dual(::IsLabelled, x) = labelled(unlabel(x), dual(label(x))) -# TBD rename deepdual? yet another name? -label_dual(g::AbstractGradedUnitRange) = gradedrange(label_dual.(blocklengths(g))) +flip(g::AbstractGradedUnitRange) = dual(gradedrange(label_dual.(blocklengths(g)))) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 9e2062a5d9..fdd6d03307 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -44,15 +44,15 @@ end # Handle dual. Always return a non-dual GradedUnitRange. function tensor_product(a1::AbstractUnitRange, a2::UnitRangeDual) - return tensor_product(a1, label_dual(dual(a2))) + return tensor_product(a1, flip(dual(a2))) end function tensor_product(a1::UnitRangeDual, a2::AbstractUnitRange) - return tensor_product(label_dual(dual(a1)), a2) + return tensor_product(flip(dual(a1)), a2) end function tensor_product(a1::UnitRangeDual, a2::UnitRangeDual) - return tensor_product(label_dual(dual(a1)), label_dual(dual(a2))) + return tensor_product(flip(dual(a1)), flip(dual(a2))) end function fuse_labels(x, y) @@ -136,20 +136,9 @@ function fusion_product(g1, g2) end fusion_product(g::AbstractUnitRange) = blockmergesort(g) -fusion_product(g::UnitRangeDual) = fusion_product(label_dual(nondual(g))) +fusion_product(g::UnitRangeDual) = fusion_product(flip(nondual(g))) # recursive fusion_product. Simpler than reduce + fix type stability issues with reduce function fusion_product(g1, g2, g3...) return fusion_product(fusion_product(g1, g2), g3...) end - -# Handle dual. Always return a non-dual GradedUnitRange. -function fusion_product(g1::UnitRangeDual, g2::AbstractUnitRange) - return fusion_product(label_dual(dual(g1)), g2) -end -function fusion_product(g1::AbstractUnitRange, g2::UnitRangeDual) - return fusion_product(g1, label_dual(dual(g2))) -end -function fusion_product(g1::UnitRangeDual, g2::UnitRangeDual) - return fusion_product(label_dual(dual(g1)), label_dual(dual(g2))) -end diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index 5e8f9c0ff3..2dde9079b0 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -6,6 +6,7 @@ UnitRangeDual(a::AbstractUnitRange) = UnitRangeDual{eltype(a),typeof(a)}(a) dual(a::AbstractUnitRange) = UnitRangeDual(a) nondual(a::UnitRangeDual) = a.nondual_unitrange dual(a::UnitRangeDual) = nondual(a) +flip(a::UnitRangeDual) = dual(flip(nondual(a))) nondual(a::AbstractUnitRange) = a isdual(::AbstractGradedUnitRange) = false isdual(::UnitRangeDual) = true diff --git a/NDTensors/src/lib/GradedAxes/test/test_dual.jl b/NDTensors/src/lib/GradedAxes/test/test_dual.jl index a3486e5361..0cce88a6dd 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_dual.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_dual.jl @@ -1,11 +1,15 @@ @eval module $(gensym()) -using BlockArrays: Block, blockaxes, blockfirsts, blocklasts, blocklength, blocks, findblock +using BlockArrays: + Block, blockaxes, blockfirsts, blocklasts, blocklength, blocklengths, blocks, findblock using NDTensors.GradedAxes: GradedAxes, UnitRangeDual, + blocklabels, blockmergesortperm, blocksortperm, dual, + flip, + gradedisequal, gradedrange, isdual, nondual @@ -20,11 +24,17 @@ Base.isless(c1::U1, c2::U1) = c1.n < c2.n a = gradedrange([U1(0) => 2, U1(1) => 3]) ad = dual(a) @test eltype(ad) == LabelledInteger{Int,U1} - @test dual(ad) == a - @test nondual(ad) == a - @test nondual(a) == a + + @test gradedisequal(dual(ad), a) + @test gradedisequal(nondual(ad), a) + @test gradedisequal(nondual(a), a) + @test gradedisequal(ad, ad) + @test !gradedisequal(a, ad) + @test !gradedisequal(ad, a) + @test isdual(ad) @test !isdual(a) + @test blockfirsts(ad) == [labelled(1, U1(0)), labelled(3, U1(-1))] @test blocklasts(ad) == [labelled(2, U1(0)), labelled(5, U1(-1))] @test findblock(ad, 4) == Block(2) @@ -52,4 +62,29 @@ Base.isless(c1::U1, c2::U1) = c1.n < c2.n @test blockmergesortperm(a) == [Block(1), Block(2)] @test blockmergesortperm(ad) == [Block(1), Block(2)] end + +@testset "flip" begin + a = gradedrange([U1(0) => 2, U1(1) => 3]) + ad = dual(a) + @test gradedisequal(flip(a), dual(gradedrange([U1(0) => 2, U1(-1) => 3]))) + @test gradedisequal(flip(ad), gradedrange([U1(0) => 2, U1(-1) => 3])) + + @test blocklabels(a) == [U1(0), U1(1)] + @test blocklabels(dual(a)) == [U1(0), U1(-1)] + @test blocklabels(flip(a)) == [U1(0), U1(1)] + @test blocklabels(flip(dual(a))) == [U1(0), U1(-1)] + @test blocklabels(dual(flip(a))) == [U1(0), U1(-1)] + + @test blocklengths(a) == [2, 3] + @test blocklengths(dual(a)) == [2, 3] + @test blocklengths(flip(a)) == [2, 3] + @test blocklengths(flip(dual(a))) == [2, 3] + @test blocklengths(dual(flip(a))) == [2, 3] + + @test !isdual(a) + @test isdual(dual(a)) + @test isdual(flip(a)) + @test !isdual(flip(dual(a))) + @test !isdual(dual(flip(a))) +end end From 45e8cc6eeb86144d0f73ed3156eb03890c61bc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 14 Jun 2024 15:22:21 -0400 Subject: [PATCH 083/194] show(::UnitRangeDual) --- NDTensors/src/lib/GradedAxes/src/unitrangedual.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index 2dde9079b0..83fe62282e 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -19,6 +19,16 @@ Base.step(a::UnitRangeDual) = label_dual(step(nondual(a))) Base.view(a::UnitRangeDual, index::Block{1}) = a[index] +function Base.show(io::IO, a::UnitRangeDual) + return print(io, UnitRangeDual, "(", blocklasts(a), ")") +end + +function Base.show(io::IO, mimetype::MIME"text/plain", a::UnitRangeDual) + return Base.invoke( + show, Tuple{typeof(io),MIME"text/plain",AbstractArray}, io, mimetype, a + ) +end + function Base.getindex(a::UnitRangeDual, indices::AbstractUnitRange{<:Integer}) return dual(getindex(nondual(a), indices)) end From 43c8b4d4c4bc5d5b212c08090d20f5b92f76918f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 14 Jun 2024 16:39:36 -0400 Subject: [PATCH 084/194] fix tensor_product(::dual) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 10 ++-- NDTensors/src/lib/GradedAxes/test/runtests.jl | 2 +- .../GradedAxes/test/test_tensor_product.jl | 49 ++++++++++++++++--- .../src/lib/Sectors/test/test_fusion_rules.jl | 8 +-- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index fdd6d03307..2e5513c7ee 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -44,15 +44,15 @@ end # Handle dual. Always return a non-dual GradedUnitRange. function tensor_product(a1::AbstractUnitRange, a2::UnitRangeDual) - return tensor_product(a1, flip(dual(a2))) + return tensor_product(a1, flip(a2)) end function tensor_product(a1::UnitRangeDual, a2::AbstractUnitRange) - return tensor_product(flip(dual(a1)), a2) + return tensor_product(flip(a1), a2) end function tensor_product(a1::UnitRangeDual, a2::UnitRangeDual) - return tensor_product(flip(dual(a1)), flip(dual(a2))) + return tensor_product(flip(a1), flip(a2)) end function fuse_labels(x, y) @@ -127,7 +127,7 @@ function blockmergesort(g::AbstractGradedUnitRange) return GradedAxes.gradedrange(new_blocklengths) end -blockmergesort(g::UnitRangeDual) = dual(blockmergesort(nondual(g))) +blockmergesort(g::UnitRangeDual) = dual(blockmergesort(flip(g))) blockmergesort(g::OneToOne) = g # fusion_product produces a sorted, non-dual GradedUnitRange @@ -136,7 +136,7 @@ function fusion_product(g1, g2) end fusion_product(g::AbstractUnitRange) = blockmergesort(g) -fusion_product(g::UnitRangeDual) = fusion_product(flip(nondual(g))) +fusion_product(g::UnitRangeDual) = fusion_product(flip(g)) # recursive fusion_product. Simpler than reduce + fix type stability issues with reduce function fusion_product(g1, g2, g3...) diff --git a/NDTensors/src/lib/GradedAxes/test/runtests.jl b/NDTensors/src/lib/GradedAxes/test/runtests.jl index 09335af5e8..c0fdca21be 100644 --- a/NDTensors/src/lib/GradedAxes/test/runtests.jl +++ b/NDTensors/src/lib/GradedAxes/test/runtests.jl @@ -2,7 +2,7 @@ using Test: @testset @testset "GradedAxes" begin include("test_basics.jl") - include("test_tensor_product.jl") include("test_dual.jl") + include("test_tensor_product.jl") end end diff --git a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl index d99091508b..7b533f79c5 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl @@ -1,14 +1,26 @@ @eval module $(gensym()) +using Test: @test, @testset + +using BlockArrays: blocklength, blocklengths + using NDTensors.GradedAxes: GradedAxes, GradedOneTo, OneToOne, + dual, fusion_product, + flip, gradedrange, gradedisequal, + isdual, tensor_product -using BlockArrays: blocklength, blocklengths -using Test: @test, @testset + +struct U1 + n::Int +end +GradedAxes.dual(c::U1) = U1(-c.n) +Base.isless(c1::U1, c2::U1) = c1.n < c2.n +GradedAxes.fuse_labels(x::U1, y::U1) = U1(x.n + y.n) @testset "GradedAxes.tensor_product" begin GradedAxes.fuse_labels(x::String, y::String) = x * y @@ -31,20 +43,41 @@ using Test: @test, @testset end @testset "GradedAxes.fusion_product" begin - GradedAxes.fuse_labels(i::Int, j::Int) = i + j - g0 = OneToOne() @test gradedisequal(fusion_product(g0, g0), g0) - a = gradedrange([1 => 1, 2 => 3, 1 => 1]) + a = gradedrange([U1(1) => 1, U1(2) => 3, U1(1) => 1]) b = fusion_product(a) - @test gradedisequal(b, gradedrange([1 => 2, 2 => 3])) + @test gradedisequal(b, gradedrange([U1(1) => 2, U1(2) => 3])) c = fusion_product(a, a) - @test gradedisequal(c, gradedrange([2 => 4, 3 => 12, 4 => 9])) + @test gradedisequal(c, gradedrange([U1(2) => 4, U1(3) => 12, U1(4) => 9])) d = fusion_product(a, a, a) - @test gradedisequal(d, gradedrange([3 => 8, 4 => 36, 5 => 54, 6 => 27])) + @test gradedisequal(d, gradedrange([U1(3) => 8, U1(4) => 36, U1(5) => 54, U1(6) => 27])) +end + +@testset "dual and tensor_product" begin + a = gradedrange([U1(1) => 1, U1(2) => 3, U1(1) => 1]) + ad = dual(a) + + b = fusion_product(ad) + @test b isa GradedOneTo + @test !isdual(b) + @test gradedisequal(b, gradedrange([U1(-2) => 3, U1(-1) => 2])) + + c = fusion_product(ad, ad) + @test c isa GradedOneTo + @test !isdual(c) + @test gradedisequal(c, gradedrange([U1(-4) => 9, U1(-3) => 12, U1(-2) => 4])) + + d = fusion_product(ad, a) + @test !isdual(d) + @test gradedisequal(d, gradedrange([U1(-1) => 6, U1(0) => 13, U1(1) => 6])) + + e = fusion_product(a, ad) + @test !isdual(d) + @test gradedisequal(e, d) end end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 6e759dc347..c689bc7c3e 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,6 +1,6 @@ @eval module $(gensym()) using NDTensors.GradedAxes: - dual, fusion_product, gradedisequal, gradedrange, label_dual, tensor_product + dual, fusion_product, gradedisequal, gradedrange, flip, tensor_product using NDTensors.Sectors: ⊗, Fib, Ising, SU, SU2, U1, Z, block_dimensions, quantum_dimension, trivial using Test: @inferred, @test, @testset, @test_throws @@ -96,7 +96,7 @@ end g1 = gradedrange([U1(-1) => 1, U1(0) => 1, U1(1) => 2]) g2 = gradedrange([U1(-2) => 2, U1(0) => 1, U1(1) => 2]) - @test gradedisequal(label_dual(g1), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) + @test gradedisequal(flip(dual(g1)), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) @test (@inferred block_dimensions(g1)) == [1, 1, 2] gt = gradedrange([ @@ -191,7 +191,7 @@ end @test gradedisequal(tensor_product(g3, g4), g34) - @test gradedisequal(label_dual(g3), g3) # trivial for SU(2) + @test gradedisequal(dual(flip(g3)), g3) # trivial for SU(2) @test gradedisequal( (@inferred fusion_product(g3, g4)), gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), @@ -206,7 +206,7 @@ end g5 = gradedrange([s1 => 1, f3 => 1]) g6 = gradedrange([s1 => 1, c3 => 1]) - @test gradedisequal(label_dual(g5), g6) + @test gradedisequal(dual(flip(g5)), g6) @test gradedisequal( fusion_product(g5, g6), gradedrange([s1 => 2, f3 => 1, c3 => 1, ad8 => 1]) ) From fb594996d00080c9bc2d30d032a1c302ad089c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 18 Jun 2024 10:06:30 -0400 Subject: [PATCH 085/194] adapt to BlockArrays 1.1 --- NDTensors/Project.toml | 2 +- NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/NDTensors/Project.toml b/NDTensors/Project.toml index ece7615a0d..2a6dd808a7 100644 --- a/NDTensors/Project.toml +++ b/NDTensors/Project.toml @@ -56,7 +56,7 @@ AMDGPU = "0.9" Accessors = "0.1.33" Adapt = "3.7, 4" ArrayLayouts = "1.4" -BlockArrays = "1" +BlockArrays = "1.1" CUDA = "5" Compat = "4.9" cuTENSOR = "2" diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index 73708503c8..dc60959e4b 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -42,10 +42,7 @@ end # == is just a range comparison that ignores labels. Need dedicated function to check equality. function gradedisequal(a1::AbstractUnitRange, a2::AbstractUnitRange) - # TODO remove workaround once BlockArrays.blockisequal is generalized to Integer - blocka1 = BlockArrays.blockedrange(GradedAxes.unlabel.(BlockArrays.blocklengths(a1))) - blocka2 = BlockArrays.blockedrange(GradedAxes.unlabel.(BlockArrays.blocklengths(a2))) - return blockisequal(blocka1, blocka2) && (blocklabels(a1) == blocklabels(a2)) + return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) end # TODO: Use `TypeParameterAccessors`. From b47fdedbac8efacde1ab2ef76f30bf195a8ae50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 18 Jun 2024 18:32:43 -0400 Subject: [PATCH 086/194] fix Vararg --- NDTensors/src/lib/Sectors/src/category_product.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 480661937a..c80c454547 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -76,7 +76,7 @@ categories_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented" categories_type(::Type{<:CategoryProduct{T}}) where {T} = T -recover_key(T::Type, t::Tuple{Vararg{<:AbstractCategory}}) = sector(T, t) +recover_key(T::Type, t::Tuple{Vararg{AbstractCategory}}) = sector(T, t) recover_key(T::Type, c::AbstractCategory) = recover_key(T, (c,)) recover_key(T::Type, c::CategoryProduct) = recover_key(T, categories(c)) function recover_key(T::Type, fused::Tuple) From 9a04046a4257683c09f01d10c8767acc51f2df5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 18 Jun 2024 19:36:50 -0400 Subject: [PATCH 087/194] remove unneeded method --- NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index dc60959e4b..f72f898d8c 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -35,11 +35,6 @@ function Base.OrdinalRange{T,T}(a::GradedOneTo{<:LabelledInteger{T}}) where {T} return unlabel_blocks(a) end -# TODO: See if this is needed. -function Base.AbstractUnitRange{T}(a::GradedOneTo{<:LabelledInteger{T}}) where {T} - return unlabel_blocks(a) -end - # == is just a range comparison that ignores labels. Need dedicated function to check equality. function gradedisequal(a1::AbstractUnitRange, a2::AbstractUnitRange) return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) From e2548ee31723d9e93801be2f3952e12d4128e40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 15 Aug 2024 12:35:23 -0400 Subject: [PATCH 088/194] add O(2) group --- NDTensors/src/lib/Sectors/src/Sectors.jl | 1 + .../Sectors/src/category_definitions/o2.jl | 64 +++++++++++++++++++ .../src/lib/Sectors/test/test_fusion_rules.jl | 25 +++++++- .../Sectors/test/test_simple_categories.jl | 33 +++++++++- 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 NDTensors/src/lib/Sectors/src/category_definitions/o2.jl diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index f851dc4002..f6c8bca92b 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -4,6 +4,7 @@ include("symmetry_style.jl") include("abstractcategory.jl") include("category_definitions/fib.jl") include("category_definitions/ising.jl") +include("category_definitions/o2.jl") include("category_definitions/su.jl") include("category_definitions/su2k.jl") include("category_definitions/u1.jl") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl new file mode 100644 index 0000000000..285e2cd2a2 --- /dev/null +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -0,0 +1,64 @@ +# +# Orthogonal group O(2) +# isomorphic to Z_2 ⋉ U(1) +# corresponds to to SU(2) subgroup with Sz conservation + Sz-reversal +# + +struct O2 <: AbstractCategory + l::HalfIntegers.Half{Int} +end + +SymmetryStyle(::O2) = NonAbelianGroup() + +category_label(s::O2) = s.l + +trivial(::Type{O2}) = O2(0) +zero_odd(::Type{O2}) = O2(-1) + +_iszero(s::O2) = _iszero(category_label(s)) +_iszero_even(s::O2) = _iszero_even(category_label(s)) +_iszero_odd(s::O2) = _iszero_odd(category_label(s)) + +_iszero(l::HalfIntegers.HalfInteger) = _iszero_even(l) || _iszero_odd(l) +_iszero_even(l::HalfIntegers.HalfInteger) = l == category_label(trivial(O2)) +_iszero_odd(l::HalfIntegers.HalfInteger) = l == category_label(zero_odd(O2)) + +quantum_dimension(::NonAbelianGroup, s::O2) = 2 - _iszero(s) + +GradedAxes.dual(s::O2) = s + +function Base.show(io::IO, s::O2) + if _iszero_odd(s) + disp = "0o" + elseif _iszero_even(s) + disp = "0e" + else + disp = "±" * string(category_label(s)) + end + return print(io, "O(2)[", disp, "]") +end + +function label_fusion_rule(::Type{O2}, l1, l2) + if _iszero(l1) + degens = [1] + if _iszero(l2) + labels = l1 == l2 ? [category_label(trivial(O2))] : [category_label(zero_odd(O2))] + else + labels = [l2] + end + else + if _iszero(l2) + degens = [1] + labels = [l1] + else + if l1 == l2 + degens = [1, 1, 1] + labels = [category_label(zero_odd(O2)), category_label(trivial(O2)), 2 * l1] + else + degens = [1, 1] + labels = [abs(l1 - l2), l1 + l2] + end + end + end + return degens, labels +end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index c689bc7c3e..bcefd2d851 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -2,7 +2,7 @@ using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange, flip, tensor_product using NDTensors.Sectors: - ⊗, Fib, Ising, SU, SU2, U1, Z, block_dimensions, quantum_dimension, trivial + ⊗, Fib, Ising, O2, SU, SU2, U1, Z, block_dimensions, quantum_dimension, trivial using Test: @inferred, @test, @testset, @test_throws @testset "Simple object fusion rules" begin @@ -35,6 +35,29 @@ using Test: @inferred, @test, @testset, @test_throws @test q2 ⊗ q1 == U1(3) @test (@inferred q1 ⊗ q2) == q3 # no better way, see Julia PR 23426 end + + @testset "O2 fusion rules" begin + s0e = O2(0) + s0o = O2(-1) + s12 = O2(1//2) + s1 = O2(1) + + @test gradedisequal((@inferred s0e ⊗ s0e), gradedrange([s0e => 1])) + @test gradedisequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) + @test gradedisequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) + @test gradedisequal((@inferred s0o ⊗ s0o), gradedrange([s0e => 1])) + + @test gradedisequal((@inferred s0e ⊗ s12), gradedrange([s12 => 1])) + @test gradedisequal((@inferred s0o ⊗ s12), gradedrange([s12 => 1])) + @test gradedisequal((@inferred s12 ⊗ s0e), gradedrange([s12 => 1])) + @test gradedisequal((@inferred s12 ⊗ s0o), gradedrange([s12 => 1])) + @test gradedisequal((@inferred s12 ⊗ s1), gradedrange([s12 => 1, O2(3//2) => 1])) + @test gradedisequal((@inferred s12 ⊗ s12), gradedrange([s0o => 1, s0e => 1, s1 => 1])) + + @test (@inferred quantum_dimension(s0o ⊗ s1)) == 2 + @test (@inferred block_dimensions(s0o ⊗ s1)) == [2] + end + @testset "SU2 fusion rules" begin j1 = SU2(0) j2 = SU2(1//2) diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index 7553367b87..de64ab31b0 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -1,7 +1,18 @@ @eval module $(gensym()) using NDTensors.GradedAxes: dual using NDTensors.Sectors: - Fib, Ising, SU, SU2, U1, Z, adjoint, quantum_dimension, fundamental, istrivial, trivial + Fib, + Ising, + O2, + SU, + SU2, + U1, + Z, + adjoint, + quantum_dimension, + fundamental, + istrivial, + trivial using Test: @inferred, @test, @testset, @test_throws @testset "Test Category Types" begin @testset "U(1)" begin @@ -41,6 +52,26 @@ using Test: @inferred, @test, @testset, @test_throws @test !isless(Z{2}(1), Z{2}(0)) end + @testset "O(2)" begin + s0e = O2(0) + s0o = O2(-1) + s12 = O2(1//2) + s1 = O2(1) + + @test trivial(O2) == s0e + @test istrivial(s0e) + + @test (@inferred quantum_dimension(s0e)) == 1 + @test (@inferred quantum_dimension(s0o)) == 1 + @test (@inferred quantum_dimension(s12)) == 2 + @test (@inferred quantum_dimension(s1)) == 2 + + @test (@inferred dual(s0e)) == s0e + @test (@inferred dual(s0o)) == s0o + @test (@inferred dual(s12)) == s12 + @test (@inferred dual(s1)) == s1 + end + @testset "SU2" begin j1 = SU2(0) j2 = SU2(1//2) # Rational will be cast to HalfInteger From fbf07346da6378097bba42fcdc5abe9c11d15031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 16 Sep 2024 19:44:40 -0400 Subject: [PATCH 089/194] remove gradedisequal(::OneToOne) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 2e5513c7ee..6f2dc6a580 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -6,8 +6,6 @@ OneToOne() = OneToOne{Bool}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) -gradedisequal(::AbstractUnitRange, ::OneToOne) = false -gradedisequal(::OneToOne, ::AbstractUnitRange) = false gradedisequal(::OneToOne, ::OneToOne) = true # https://github.com/ITensor/ITensors.jl/blob/v0.3.57/NDTensors/src/lib/GradedAxes/src/tensor_product.jl From d9cd48621cb54a212ec74bd8fdc955ed89be9662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 17 Sep 2024 15:38:15 -0400 Subject: [PATCH 090/194] avoid mixing fusion_rule and fuse_blocklengths interfaces --- .../src/lib/Sectors/src/abstractcategory.jl | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 51311ab201..e05a97d056 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -61,24 +61,6 @@ function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} return C(label_fusion_rule(C, category_label(c1), category_label(c2))) end -function fusion_rule( - ::SymmetryStyle, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger -) - fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) - v = - LabelledNumbers.labelled.( - l1 * l2 .* BlockArrays.blocklengths(fused), GradedAxes.blocklabels(fused) - ) - return GradedAxes.gradedrange(v) -end - -function fusion_rule( - ::AbelianGroup, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger -) - fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) - return LabelledNumbers.labelled(l1 * l2, fused) -end - function fusion_rule( ::EmptyCategory, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) @@ -95,7 +77,31 @@ function GradedAxes.fuse_blocklengths( l1::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, l2::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, ) - return fusion_rule(l1, l2) + return GradedAxes.fuse_blocklengths( + combine_styles(SymmetryStyle(l1), SymmetryStyle(l2)), l1, l2 + ) +end + +function GradedAxes.fuse_blocklengths( + ::SymmetryStyle, + l1::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, + l2::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, +) + fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) + v = + LabelledNumbers.labelled.( + l1 * l2 .* BlockArrays.blocklengths(fused), GradedAxes.blocklabels(fused) + ) + return GradedAxes.gradedrange(v) +end + +function GradedAxes.fuse_blocklengths( + ::AbelianGroup, + l1::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, + l2::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, +) + fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) + return LabelledNumbers.labelled(l1 * l2, fused) end # cast to range From de8759d2b9c625449feb5cb15f38da719b2f826c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 17 Sep 2024 16:45:37 -0400 Subject: [PATCH 091/194] fix fuse_blocklengths(::EmptyCategory) --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 14 ++++++++------ .../src/lib/Sectors/test/test_category_product.jl | 3 +++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index e05a97d056..374650ccc8 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -83,9 +83,7 @@ function GradedAxes.fuse_blocklengths( end function GradedAxes.fuse_blocklengths( - ::SymmetryStyle, - l1::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, - l2::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, + ::SymmetryStyle, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) v = @@ -96,14 +94,18 @@ function GradedAxes.fuse_blocklengths( end function GradedAxes.fuse_blocklengths( - ::AbelianGroup, - l1::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, - l2::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, + ::AbelianGroup, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger ) fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) return LabelledNumbers.labelled(l1 * l2, fused) end +function GradedAxes.fuse_blocklengths( + ::EmptyCategory, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger +) + return LabelledNumbers.labelled(l1 * l2, sector()) +end + # cast to range to_graded_axis(c::AbstractCategory) = to_graded_axis(LabelledNumbers.labelled(1, c)) to_graded_axis(l::LabelledNumbers.LabelledInteger) = GradedAxes.gradedrange([l]) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 8f70741730..772fe524c1 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -540,6 +540,9 @@ end @test typeof(s) == typeof(sector(())) @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} + g0 = gradedrange([s => 2]) + @test gradedisequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) + @test (@inferred s × U1(1)) == sector(U1(1)) @test (@inferred s × sector(U1(1))) == sector(U1(1)) @test (@inferred s × sector(; A=U1(1))) == sector(; A=U1(1)) From cd583215cd675c4331778db9e254f8646c80038b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 11:47:02 -0400 Subject: [PATCH 092/194] rm 'NDTensors/src/lib/Sectors/Project.toml' --- NDTensors/src/lib/Sectors/Project.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 NDTensors/src/lib/Sectors/Project.toml diff --git a/NDTensors/src/lib/Sectors/Project.toml b/NDTensors/src/lib/Sectors/Project.toml deleted file mode 100644 index e69de29bb2..0000000000 From f9f3b5b2115758e1ee80e13e56ef0b93501a771a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 11:49:31 -0400 Subject: [PATCH 093/194] use do-block syntax --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 6f2dc6a580..764ea2ea76 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -73,10 +73,9 @@ flatten_maybe_nested(v::Vector{<:AbstractGradedUnitRange}) = reduce(vcat, blockl using BlockArrays: blockedrange, blocks function tensor_product(a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRange) - maybe_nested = map( - it -> mapreduce(length, fuse_blocklengths, it), - Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),)), - ) + maybe_nested = map(Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),))) do it + return mapreduce(length, fuse_blocklengths, it) + end blocklengths = flatten_maybe_nested(maybe_nested) return blockedrange(blocklengths) end From a4954fb66a131712434650ca6c7c2e7c53e8d6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 12:29:08 -0400 Subject: [PATCH 094/194] remove GradedRange. --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 764ea2ea76..ba9fee0f6a 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -121,7 +121,7 @@ function blockmergesort(g::AbstractGradedUnitRange) la -> labelled(sum(gblocklengths[findall(==(la), glabels)]; init=0), la), sort(unique(glabels)), ) - return GradedAxes.gradedrange(new_blocklengths) + return gradedrange(new_blocklengths) end blockmergesort(g::UnitRangeDual) = dual(blockmergesort(flip(g))) From c7820aabfffac48ed9cf18e98f683ac02878c005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 12:38:34 -0400 Subject: [PATCH 095/194] isdual default to false --- NDTensors/src/lib/GradedAxes/src/unitrangedual.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index 3f5249728c..2f449349ac 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -8,7 +8,7 @@ nondual(a::UnitRangeDual) = a.nondual_unitrange dual(a::UnitRangeDual) = nondual(a) flip(a::UnitRangeDual) = dual(flip(nondual(a))) nondual(a::AbstractUnitRange) = a -isdual(::AbstractGradedUnitRange) = false +isdual(::AbstractUnitRange) = false isdual(::UnitRangeDual) = true ## TODO: Define this to instantiate a dual unit range. ## materialize_dual(a::UnitRangeDual) = materialize_dual(nondual(a)) From 3d65e280caa62162531c19e9f104604d286fbe57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 12:45:35 -0400 Subject: [PATCH 096/194] fix unlabel type in trivial --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 374650ccc8..981bc71df0 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -14,7 +14,9 @@ function trivial(axis_type::Type{<:AbstractUnitRange}) return GradedAxes.gradedrange([trivial(eltype(axis_type))]) # always returns nondual end function trivial(la_type::Type{<:LabelledNumbers.LabelledInteger}) - return la_type(1, trivial(LabelledNumbers.label_type(la_type))) + return LabelledNumbers.labelled( + one(LabelledNumbers.unlabel_type(la_type)), trivial(LabelledNumbers.label_type(la_type)) + ) end function trivial(type::Type) return error("`trivial` not defined for type $(type).") From f19dc444cc82a264fd69bdfa4c4bd261ed179b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 12:46:18 -0400 Subject: [PATCH 097/194] fix namespace --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 981bc71df0..da1c064b0f 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -29,7 +29,7 @@ function category_label(c::AbstractCategory) end block_dimensions(g::AbstractUnitRange) = block_dimensions(SymmetryStyle(g), g) -block_dimensions(::AbelianGroup, g) = GradedAxes.unlabel.(BlockArrays.blocklengths(g)) +block_dimensions(::AbelianGroup, g) = LabelledNumbers.unlabel.(BlockArrays.blocklengths(g)) function block_dimensions(::SymmetryStyle, g) return Sectors.quantum_dimension.(GradedAxes.blocklabels(g)) .* BlockArrays.blocklengths(g) From 2e29af1000ae0c2a227d047fc97a12117a4391a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 13:53:56 -0400 Subject: [PATCH 098/194] fix comment typo --- NDTensors/src/lib/Sectors/src/category_definitions/o2.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index 285e2cd2a2..8582592093 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -1,7 +1,7 @@ # # Orthogonal group O(2) # isomorphic to Z_2 ⋉ U(1) -# corresponds to to SU(2) subgroup with Sz conservation + Sz-reversal +# isomorphic to SU(2) subgroup with Sz conservation + Sz-reversal # struct O2 <: AbstractCategory From 5153333d84c805b7898992bf23419e2b2f638a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 14:11:39 -0400 Subject: [PATCH 099/194] add comments on O(2) --- .../src/lib/Sectors/src/category_definitions/o2.jl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index 8582592093..8be5eecd58 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -3,7 +3,16 @@ # isomorphic to Z_2 ⋉ U(1) # isomorphic to SU(2) subgroup with Sz conservation + Sz-reversal # +# O(2) has 3 kinds of irreps: +# - trivial irrep, or "0e", corresponds to Sz=0 and even under Sz-reversal +# - "zero odd", or "0o" irrep, corresponds to Sz=0 and odd under Sz-reversal +# - 2-dimensional Sz=±|m| irrep, with m a half integer +# +# here we use only one half-integer as label: +# - l=0 for trivial +# - l=-1 for zero odd +# - l=+|m| for Sz=±|m| struct O2 <: AbstractCategory l::HalfIntegers.Half{Int} end @@ -15,7 +24,7 @@ category_label(s::O2) = s.l trivial(::Type{O2}) = O2(0) zero_odd(::Type{O2}) = O2(-1) -_iszero(s::O2) = _iszero(category_label(s)) +_iszero(s::O2) = _iszero(category_label(s)) # matches both 0e and 0o _iszero_even(s::O2) = _iszero_even(category_label(s)) _iszero_odd(s::O2) = _iszero_odd(category_label(s)) From de29e17112aa25a71f595b636eb12ada277d85c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 14:27:33 -0400 Subject: [PATCH 100/194] avoid using implementation detail --- NDTensors/src/lib/Sectors/src/category_definitions/su.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 9937e1c362..33a2d88141 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -95,7 +95,7 @@ function label_fusion_rule(::Type{<:SU{2}}, s1, s2) end # define angular momentum-like interface using half-integers -SU2(h::Number) = SU{2,1}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) +SU2(h::Number) = SU{2}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) # display SU2 using half-integers function Base.show(io::IO, s::SU{2}) From f9e47edd50ee9dc0695ce4ffa075c8c0730a197b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 15:27:57 -0400 Subject: [PATCH 101/194] replace find_common/diff with categories_common/diff --- NDTensors/src/lib/Sectors/src/category_product.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index c80c454547..2b7d602538 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -61,8 +61,8 @@ end # - dictionary-like with a NamedTuple function categories_fusion_rule(cats1, cats2) - diff_cat = CategoryProduct(find_diff(cats1, cats2)) - shared1, shared2 = find_common(cats1, cats2) + diff_cat = CategoryProduct(categories_diff(cats1, cats2)) + shared1, shared2 = categories_common(cats1, cats2) fused = map(fusion_rule, values(shared1), values(shared2)) return recover_key(typeof(shared1), fused) × diff_cat end @@ -172,12 +172,12 @@ categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) categories_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) -function find_common(t1::Tuple, t2::Tuple) +function categories_common(t1::Tuple, t2::Tuple) n = min(length(t1), length(t2)) return t1[begin:n], t2[begin:n] end -function find_diff(t1::Tuple, t2::Tuple) +function categories_diff(t1::Tuple, t2::Tuple) n1 = length(t1) n2 = length(t2) return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] @@ -243,8 +243,8 @@ function categories_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} return NamedTuple{Keys}(trivial.(fieldtypes(type))) end -function find_common(nt1::NamedTuple, nt2::NamedTuple) +function categories_common(nt1::NamedTuple, nt2::NamedTuple) return intersect_keys(nt1, nt2), intersect_keys(nt2, nt1) end -find_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) +categories_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) From 7b617661222045ea9130ba602e778a8c8e97e9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 17:20:07 -0400 Subject: [PATCH 102/194] simplify categories_isequal and categories_isless --- NDTensors/src/lib/Sectors/src/category_product.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 2b7d602538..04a2d33df9 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -202,20 +202,20 @@ end # sector() acts as empty NamedTuple function categories_isequal(nt::NamedTuple, ::Tuple{}) - return categories_isequal(nt, categories_trivial(typeof(nt))) + return categories_isequal(nt, (;)) end function categories_isequal(::Tuple{}, nt::NamedTuple) - return categories_isequal(categories_trivial(typeof(nt)), nt) + return categories_isequal((;), nt) end function categories_isequal(nt1::NamedTuple, nt2::NamedTuple) return ==(sym_categories_insert_unspecified(nt1, nt2)...) end function categories_isless(nt::NamedTuple, ::Tuple{}) - return categories_isless(nt, categories_trivial(typeof(nt))) + return categories_isless(nt, (;)) end function categories_isless(::Tuple{}, nt::NamedTuple) - return categories_isless(categories_trivial(typeof(nt)), nt) + return categories_isless((;), nt) end function categories_isless(nt1::NamedTuple, nt2::NamedTuple) return isless(sym_categories_insert_unspecified(nt1, nt2)...) From ad12d603f0441527f142889c87ee107ad4fe3804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 25 Sep 2024 17:37:08 -0400 Subject: [PATCH 103/194] Revert "rm 'NDTensors/src/lib/Sectors/Project.toml'" This reverts commit cd583215cd675c4331778db9e254f8646c80038b. --- NDTensors/src/lib/Sectors/Project.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 NDTensors/src/lib/Sectors/Project.toml diff --git a/NDTensors/src/lib/Sectors/Project.toml b/NDTensors/src/lib/Sectors/Project.toml new file mode 100644 index 0000000000..e69de29bb2 From 8aeb0972e577a6e14e5aa32ce6f1f717ee14a1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 10:05:17 -0400 Subject: [PATCH 104/194] explicit imports --- NDTensors/src/lib/Sectors/src/Sectors.jl | 13 ++++ .../src/lib/Sectors/src/abstractcategory.jl | 60 ++++++++----------- .../Sectors/src/category_definitions/su2k.jl | 2 +- .../src/lib/Sectors/src/category_product.jl | 23 ++++--- .../src/lib/Sectors/src/symmetry_style.jl | 7 +-- 5 files changed, 50 insertions(+), 55 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index f6c8bca92b..9b99eeaba2 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -1,5 +1,18 @@ module Sectors +using BlockArrays: blocklengths + +using NDTensors.LabelledNumbers: + LabelledInteger, label, label_type, labelled, unlabel, unlabel_type +using NDTensors.GradedAxes: + GradedAxes, + blocklabels, + dual, + fuse_blocklengths, + fusion_product, + gradedrange, + tensor_product + include("symmetry_style.jl") include("abstractcategory.jl") include("category_definitions/fib.jl") diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index da1c064b0f..60443de5a7 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -11,12 +11,10 @@ end # ================================= Sectors interface ==================================== trivial(x) = trivial(typeof(x)) function trivial(axis_type::Type{<:AbstractUnitRange}) - return GradedAxes.gradedrange([trivial(eltype(axis_type))]) # always returns nondual + return gradedrange([trivial(eltype(axis_type))]) # always returns nondual end -function trivial(la_type::Type{<:LabelledNumbers.LabelledInteger}) - return LabelledNumbers.labelled( - one(LabelledNumbers.unlabel_type(la_type)), trivial(LabelledNumbers.label_type(la_type)) - ) +function trivial(la_type::Type{<:LabelledInteger}) + return labelled(one(unlabel_type(la_type)), trivial(label_type(la_type))) end function trivial(type::Type) return error("`trivial` not defined for type $(type).") @@ -29,10 +27,9 @@ function category_label(c::AbstractCategory) end block_dimensions(g::AbstractUnitRange) = block_dimensions(SymmetryStyle(g), g) -block_dimensions(::AbelianGroup, g) = LabelledNumbers.unlabel.(BlockArrays.blocklengths(g)) +block_dimensions(::AbelianGroup, g) = unlabel.(blocklengths(g)) function block_dimensions(::SymmetryStyle, g) - return Sectors.quantum_dimension.(GradedAxes.blocklabels(g)) .* - BlockArrays.blocklengths(g) + return quantum_dimension.(blocklabels(g)) .* blocklengths(g) end quantum_dimension(x) = quantum_dimension(SymmetryStyle(x), x) @@ -55,7 +52,7 @@ end function fusion_rule(::SymmetryStyle, c1::C, c2::C) where {C<:AbstractCategory} degen, labels = label_fusion_rule(C, category_label(c1), category_label(c2)) - return GradedAxes.gradedrange(LabelledNumbers.labelled.(degen, C.(labels))) + return gradedrange(labelled.(degen, C.(labels))) end # abelian case: return Category @@ -63,10 +60,8 @@ function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} return C(label_fusion_rule(C, category_label(c1), category_label(c2))) end -function fusion_rule( - ::EmptyCategory, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger -) - return LabelledNumbers.labelled(l1 * l2, sector()) +function fusion_rule(::EmptyCategory, l1::LabelledInteger, l2::LabelledInteger) + return labelled(l1 * l2, sector()) end function label_fusion_rule(category_type::Type{<:AbstractCategory}, ::Any, ::Any) @@ -76,50 +71,45 @@ end # ================================ GradedAxes interface ================================== # tensor_product interface function GradedAxes.fuse_blocklengths( - l1::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, - l2::LabelledNumbers.LabelledInteger{<:Integer,<:Sectors.AbstractCategory}, + l1::LabelledInteger{<:Integer,<:AbstractCategory}, + l2::LabelledInteger{<:Integer,<:AbstractCategory}, ) - return GradedAxes.fuse_blocklengths( - combine_styles(SymmetryStyle(l1), SymmetryStyle(l2)), l1, l2 - ) + return fuse_blocklengths(combine_styles(SymmetryStyle(l1), SymmetryStyle(l2)), l1, l2) end function GradedAxes.fuse_blocklengths( - ::SymmetryStyle, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger + ::SymmetryStyle, l1::LabelledInteger, l2::LabelledInteger ) - fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) - v = - LabelledNumbers.labelled.( - l1 * l2 .* BlockArrays.blocklengths(fused), GradedAxes.blocklabels(fused) - ) - return GradedAxes.gradedrange(v) + fused = label(l1) ⊗ label(l2) + v = labelled.(l1 * l2 .* blocklengths(fused), blocklabels(fused)) + return gradedrange(v) end function GradedAxes.fuse_blocklengths( - ::AbelianGroup, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger + ::AbelianGroup, l1::LabelledInteger, l2::LabelledInteger ) - fused = LabelledNumbers.label(l1) ⊗ LabelledNumbers.label(l2) - return LabelledNumbers.labelled(l1 * l2, fused) + fused = label(l1) ⊗ label(l2) + return labelled(l1 * l2, fused) end function GradedAxes.fuse_blocklengths( - ::EmptyCategory, l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger + ::EmptyCategory, l1::LabelledInteger, l2::LabelledInteger ) - return LabelledNumbers.labelled(l1 * l2, sector()) + return labelled(l1 * l2, sector()) end # cast to range -to_graded_axis(c::AbstractCategory) = to_graded_axis(LabelledNumbers.labelled(1, c)) -to_graded_axis(l::LabelledNumbers.LabelledInteger) = GradedAxes.gradedrange([l]) +to_graded_axis(c::AbstractCategory) = to_graded_axis(labelled(1, c)) +to_graded_axis(l::LabelledInteger) = gradedrange([l]) to_graded_axis(g::AbstractUnitRange) = g # allow to fuse a category with a GradedUnitRange function GradedAxes.tensor_product(c::AbstractCategory, g::AbstractUnitRange) - return GradedAxes.tensor_product(to_graded_axis(c), g) + return tensor_product(to_graded_axis(c), g) end function GradedAxes.tensor_product(g::AbstractUnitRange, c::AbstractCategory) - return GradedAxes.tensor_product(c, g) + return tensor_product(g, to_graded_axis(c)) end function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) @@ -127,5 +117,5 @@ function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) end function GradedAxes.fusion_product(c::AbstractCategory) - return GradedAxes.fusion_product(to_graded_axis(c)) + return to_graded_axis(c) end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index 736e9987b8..2f1c22538b 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -8,7 +8,7 @@ end SymmetryStyle(::su2) = NonGroupCategory() -dual(s::su2) = s +GradedAxes.dual(s::su2) = s category_label(s::su2) = s.j diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 04a2d33df9..f56d01a93f 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -24,7 +24,7 @@ function quantum_dimension(::NonGroupCategory, s::CategoryProduct) return prod(map(quantum_dimension, categories(s))) end -GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(GradedAxes.dual, categories(s))) +GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(dual, categories(s))) trivial(type::Type{<:CategoryProduct}) = sector(categories_trivial(categories_type(type))) @@ -83,10 +83,9 @@ function recover_key(T::Type, fused::Tuple) # here fused contains at leat one GradedUnitRange g0 = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × - new_labels = recover_key.(T, GradedAxes.blocklabels(g0)) - new_blocklengths = - LabelledNumbers.labelled.(GradedAxes.unlabel.(BlockArrays.blocklengths(g0)), new_labels) - return GradedAxes.gradedrange(new_blocklengths) + new_labels = recover_key.(T, blocklabels(g0)) + new_blocklengths = labelled.(unlabel.(blocklengths(g0)), new_labels) + return gradedrange(new_blocklengths) end sector(T::Type{<:CategoryProduct}, cats::Tuple) = sector(categories_type(T), cats) @@ -105,20 +104,18 @@ end ×(c1::NamedTuple, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) ×(c1::AbstractCategory, c2::NamedTuple) = ×(CategoryProduct(c1), CategoryProduct(c2)) -function ×(l1::LabelledNumbers.LabelledInteger, l2::LabelledNumbers.LabelledInteger) - c3 = LabelledNumbers.label(l1) × LabelledNumbers.label(l2) - m3 = LabelledNumbers.unlabel(l1) * LabelledNumbers.unlabel(l2) - return LabelledNumbers.labelled(m3, c3) +function ×(l1::LabelledInteger, l2::LabelledInteger) + c3 = label(l1) × label(l2) + m3 = unlabel(l1) * unlabel(l2) + return labelled(m3, c3) end function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) v = map( ((l1, l2),) -> l1 × l2, - Iterators.flatten(( - Iterators.product(BlockArrays.blocklengths(g1), BlockArrays.blocklengths(g2)), - ),), + Iterators.flatten((Iterators.product(blocklengths(g1), blocklengths(g2)),),), ) - return GradedAxes.gradedrange(v) + return gradedrange(v) end # ==================================== Fusion rules ====================================== diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index f65a65fc91..417c506c6d 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -1,11 +1,6 @@ # This file defines SymmetryStyle, a trait to distinguish abelian groups, non-abelian groups # and non-group fusion categories. -using BlockArrays - -using NDTensors.LabelledNumbers -using NDTensors.GradedAxes - abstract type SymmetryStyle end struct AbelianGroup <: SymmetryStyle end @@ -25,7 +20,7 @@ combine_styles(::EmptyCategory, s::SymmetryStyle) = s combine_styles(s::SymmetryStyle, ::EmptyCategory) = s combine_styles(::EmptyCategory, ::EmptyCategory) = EmptyCategory() -SymmetryStyle(l::LabelledNumbers.LabelledInteger) = SymmetryStyle(LabelledNumbers.label(l)) +SymmetryStyle(l::LabelledInteger) = SymmetryStyle(label(l)) # crash for empty g. Currently impossible to construct. SymmetryStyle(g::AbstractUnitRange) = SymmetryStyle(first(g)) From a4803f157723abb7e5704c82958ab8619d3be8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 10:36:57 -0400 Subject: [PATCH 105/194] explicit import HalfInteger --- NDTensors/src/lib/Sectors/src/Sectors.jl | 1 + .../src/lib/Sectors/src/category_definitions/ising.jl | 6 ++---- NDTensors/src/lib/Sectors/src/category_definitions/o2.jl | 8 ++++---- NDTensors/src/lib/Sectors/src/category_definitions/su.jl | 6 +++--- .../src/lib/Sectors/src/category_definitions/su2k.jl | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index 9b99eeaba2..94a32e3a1e 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -1,6 +1,7 @@ module Sectors using BlockArrays: blocklengths +using HalfIntegers: Half, HalfInteger, twice using NDTensors.LabelledNumbers: LabelledInteger, label, label_type, labelled, unlabel, unlabel_type diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index d390f524c9..1e7b51ab13 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -1,5 +1,3 @@ -using HalfIntegers: HalfIntegers - # # Ising category # @@ -7,7 +5,7 @@ using HalfIntegers: HalfIntegers # struct Ising <: AbstractCategory - l::HalfIntegers.Half{Int} + l::Half{Int} end # TODO: Use `Val` dispatch here? @@ -32,7 +30,7 @@ quantum_dimension(::NonGroupCategory, i::Ising) = (category_label(i) == 1//2) ? label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) # TODO: Use `Val` dispatch here? -label_to_str(i::Ising) = ("1", "σ", "ψ")[HalfIntegers.twice(category_label(i)) + 1] +label_to_str(i::Ising) = ("1", "σ", "ψ")[twice(category_label(i)) + 1] function Base.show(io::IO, f::Ising) return print(io, "Ising(", label_to_str(f), ")") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index 8be5eecd58..70e907d916 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -14,7 +14,7 @@ # - l=-1 for zero odd # - l=+|m| for Sz=±|m| struct O2 <: AbstractCategory - l::HalfIntegers.Half{Int} + l::Half{Int} end SymmetryStyle(::O2) = NonAbelianGroup() @@ -28,9 +28,9 @@ _iszero(s::O2) = _iszero(category_label(s)) # matches both 0e and 0o _iszero_even(s::O2) = _iszero_even(category_label(s)) _iszero_odd(s::O2) = _iszero_odd(category_label(s)) -_iszero(l::HalfIntegers.HalfInteger) = _iszero_even(l) || _iszero_odd(l) -_iszero_even(l::HalfIntegers.HalfInteger) = l == category_label(trivial(O2)) -_iszero_odd(l::HalfIntegers.HalfInteger) = l == category_label(zero_odd(O2)) +_iszero(l::HalfInteger) = _iszero_even(l) || _iszero_odd(l) +_iszero_even(l::HalfInteger) = l == category_label(trivial(O2)) +_iszero_odd(l::HalfInteger) = l == category_label(zero_odd(O2)) quantum_dimension(::NonAbelianGroup, s::O2) = 2 - _iszero(s) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 33a2d88141..c83a13a48c 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -95,15 +95,15 @@ function label_fusion_rule(::Type{<:SU{2}}, s1, s2) end # define angular momentum-like interface using half-integers -SU2(h::Number) = SU{2}((HalfIntegers.twice(HalfIntegers.HalfInteger(h)),)) +SU2(h::Number) = SU{2}((twice(HalfInteger(h)),)) # display SU2 using half-integers function Base.show(io::IO, s::SU{2}) - return print(io, "SU(2)[S=", HalfIntegers.half(quantum_dimension(s) - 1), "]") + return print(io, "SU(2)[S=", half(quantum_dimension(s) - 1), "]") end function Base.show(io::IO, ::MIME"text/plain", s::SU{2}) - return print(io, "S = ", HalfIntegers.half(quantum_dimension(s) - 1)) + return print(io, "S = ", half(quantum_dimension(s) - 1)) end # diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index 2f1c22538b..9570d7bcfa 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -3,7 +3,7 @@ # struct su2{k} <: AbstractCategory - j::HalfIntegers.Half{Int} + j::Half{Int} end SymmetryStyle(::su2) = NonGroupCategory() From e8624e364a9baf4b199a5dfa9837dad581582e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 10:41:19 -0400 Subject: [PATCH 106/194] is_zero_even_or_odd(O2) --- .../Sectors/src/category_definitions/o2.jl | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index 70e907d916..eb31227265 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -24,22 +24,21 @@ category_label(s::O2) = s.l trivial(::Type{O2}) = O2(0) zero_odd(::Type{O2}) = O2(-1) -_iszero(s::O2) = _iszero(category_label(s)) # matches both 0e and 0o -_iszero_even(s::O2) = _iszero_even(category_label(s)) -_iszero_odd(s::O2) = _iszero_odd(category_label(s)) +is_zero_even_or_odd(s::O2) = is_zero_even_or_odd(category_label(s)) +iszero_odd(s::O2) = iszero_odd(category_label(s)) -_iszero(l::HalfInteger) = _iszero_even(l) || _iszero_odd(l) -_iszero_even(l::HalfInteger) = l == category_label(trivial(O2)) -_iszero_odd(l::HalfInteger) = l == category_label(zero_odd(O2)) +is_zero_even_or_odd(l::HalfInteger) = iszero_even(l) || iszero_odd(l) +iszero_even(l::HalfInteger) = l == category_label(trivial(O2)) +iszero_odd(l::HalfInteger) = l == category_label(zero_odd(O2)) -quantum_dimension(::NonAbelianGroup, s::O2) = 2 - _iszero(s) +quantum_dimension(::NonAbelianGroup, s::O2) = 2 - is_zero_even_or_odd(s) GradedAxes.dual(s::O2) = s function Base.show(io::IO, s::O2) - if _iszero_odd(s) + if iszero_odd(s) disp = "0o" - elseif _iszero_even(s) + elseif istrivial(s) disp = "0e" else disp = "±" * string(category_label(s)) @@ -48,15 +47,15 @@ function Base.show(io::IO, s::O2) end function label_fusion_rule(::Type{O2}, l1, l2) - if _iszero(l1) + if is_zero_even_or_odd(l1) degens = [1] - if _iszero(l2) + if is_zero_even_or_odd(l2) labels = l1 == l2 ? [category_label(trivial(O2))] : [category_label(zero_odd(O2))] else labels = [l2] end else - if _iszero(l2) + if is_zero_even_or_odd(l2) degens = [1] labels = [l1] else From 9d54770f5b3843e7a96788f9f97e0d7fc8811fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 11:50:18 -0400 Subject: [PATCH 107/194] remove gradedisequal(OneToOne) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 -- NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index ba9fee0f6a..f413c6ee32 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -6,8 +6,6 @@ OneToOne() = OneToOne{Bool}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) -gradedisequal(::OneToOne, ::OneToOne) = true - # https://github.com/ITensor/ITensors.jl/blob/v0.3.57/NDTensors/src/lib/GradedAxes/src/tensor_product.jl # https://en.wikipedia.org/wiki/Tensor_product # https://github.com/KeitaNakamura/Tensorial.jl diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index 5961d4098d..4aabc927fe 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -39,6 +39,10 @@ end # == is just a range comparison that ignores labels. Need dedicated function to check equality. function gradedisequal(a1::AbstractUnitRange, a2::AbstractUnitRange) + return blockisequal(a1, a2) +end + +function gradedisequal(a1::AbstractGradedUnitRange, a2::AbstractGradedUnitRange) return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) end From f16506630ea55aacfc9f6cd53b3f948322287421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 11:54:10 -0400 Subject: [PATCH 108/194] add comment on convention --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index f413c6ee32..2a98554e4e 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -47,6 +47,7 @@ function tensor_product(a1::UnitRangeDual, a2::AbstractUnitRange) return tensor_product(flip(a1), a2) end +# TBD change convention to tensor(dual, dual) -> dual? function tensor_product(a1::UnitRangeDual, a2::UnitRangeDual) return tensor_product(flip(a1), flip(a2)) end From 94ae3701d22d6b9663c4b7168c7ffd0375a8eaee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 12:00:25 -0400 Subject: [PATCH 109/194] generic blockmergesort --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 2a98554e4e..3edd0a8f00 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -124,7 +124,7 @@ function blockmergesort(g::AbstractGradedUnitRange) end blockmergesort(g::UnitRangeDual) = dual(blockmergesort(flip(g))) -blockmergesort(g::OneToOne) = g +blockmergesort(g::AbstractUnitRange) = g # fusion_product produces a sorted, non-dual GradedUnitRange function fusion_product(g1, g2) From f417bec251a82ee2a30fd24806bd946d199e740e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 14:25:24 -0400 Subject: [PATCH 110/194] fix import --- NDTensors/src/lib/Sectors/src/Sectors.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index 94a32e3a1e..cd0a7afb1b 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -1,7 +1,7 @@ module Sectors using BlockArrays: blocklengths -using HalfIntegers: Half, HalfInteger, twice +using HalfIntegers: Half, HalfInteger, half, twice using NDTensors.LabelledNumbers: LabelledInteger, label, label_type, labelled, unlabel, unlabel_type From 26daedf6ebe97f627dd2725ed59631d2fbacf4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 14:27:49 -0400 Subject: [PATCH 111/194] define EmptyCategoryProduct alias --- .../src/lib/Sectors/src/category_product.jl | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index f56d01a93f..83fc3510d7 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -11,6 +11,8 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats +const EmptyCategoryProduct = CategoryProduct{Tuple{}} + # ================================= Sectors interface ==================================== function SymmetryStyle(c::CategoryProduct) return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategory()) @@ -130,31 +132,29 @@ function fusion_rule(::AbelianGroup, s1::CategoryProduct, s2::CategoryProduct) end # Empty case -function fusion_rule( - ::EmptyCategory, ::CategoryProduct{Tuple{}}, ::CategoryProduct{Tuple{}} -) +function fusion_rule(::EmptyCategory, ::EmptyCategoryProduct, ::EmptyCategoryProduct) return sector() end # EmptyCategory acts as trivial on any AbstractCategory, not just CategoryProduct -function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c::AbstractCategory) +function fusion_rule(::SymmetryStyle, ::EmptyCategoryProduct, c::AbstractCategory) return to_graded_axis(c) end -function fusion_rule(::SymmetryStyle, ::CategoryProduct{Tuple{}}, c::CategoryProduct) +function fusion_rule(::SymmetryStyle, ::EmptyCategoryProduct, c::CategoryProduct) return to_graded_axis(c) end -function fusion_rule(::SymmetryStyle, c::AbstractCategory, ::CategoryProduct{Tuple{}}) +function fusion_rule(::SymmetryStyle, c::AbstractCategory, ::EmptyCategoryProduct) return to_graded_axis(c) end -function fusion_rule(::SymmetryStyle, c::CategoryProduct, ::CategoryProduct{Tuple{}}) +function fusion_rule(::SymmetryStyle, c::CategoryProduct, ::EmptyCategoryProduct) return to_graded_axis(c) end # abelian case: return Category -fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c::AbstractCategory) = c -fusion_rule(::AbelianGroup, ::CategoryProduct{Tuple{}}, c::CategoryProduct) = c -fusion_rule(::AbelianGroup, c::AbstractCategory, ::CategoryProduct{Tuple{}}) = c -fusion_rule(::AbelianGroup, c::CategoryProduct, ::CategoryProduct{Tuple{}}) = c +fusion_rule(::AbelianGroup, ::EmptyCategoryProduct, c::AbstractCategory) = c +fusion_rule(::AbelianGroup, ::EmptyCategoryProduct, c::CategoryProduct) = c +fusion_rule(::AbelianGroup, c::AbstractCategory, ::EmptyCategoryProduct) = c +fusion_rule(::AbelianGroup, c::CategoryProduct, ::EmptyCategoryProduct) = c # =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) From c50f3cd5a4bc82fa5e38b1dc930d3993e2de47ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 14:31:53 -0400 Subject: [PATCH 112/194] fix typo in comment --- NDTensors/src/lib/Sectors/src/category_product.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 83fc3510d7..d34c7888e8 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -82,7 +82,7 @@ recover_key(T::Type, t::Tuple{Vararg{AbstractCategory}}) = sector(T, t) recover_key(T::Type, c::AbstractCategory) = recover_key(T, (c,)) recover_key(T::Type, c::CategoryProduct) = recover_key(T, categories(c)) function recover_key(T::Type, fused::Tuple) - # here fused contains at leat one GradedUnitRange + # here fused contains at least one GradedOneTo g0 = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × new_labels = recover_key.(T, blocklabels(g0)) From 6ea394c6ec566d4467c76216deed0585a4033ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 26 Sep 2024 19:04:44 -0400 Subject: [PATCH 113/194] generic blocksortperm/blockmergesortperm(a::AbstractUnitRange) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 3edd0a8f00..ad58381009 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -79,12 +79,8 @@ function tensor_product(a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRan return blockedrange(blocklengths) end -function blocksortperm(a::AbstractBlockedUnitRange) - return Block.(sortperm(blocklabels(a))) -end - # convention: sort UnitRangeDual according to nondual blocks -function blocksortperm(a::UnitRangeDual) +function blocksortperm(a::AbstractUnitRange) return Block.(sortperm(blocklabels(nondual(a)))) end @@ -102,17 +98,13 @@ end # Used by `TensorAlgebra.splitdims` in `BlockSparseArraysGradedAxesExt`. # Get the permutation for sorting, then group by common elements. # groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]] -function blockmergesortperm(a::AbstractBlockedUnitRange) - return Block.(groupsortperm(blocklabels(a))) +function blockmergesortperm(a::AbstractUnitRange) + return Block.(groupsortperm(blocklabels(nondual(a)))) end # Used by `TensorAlgebra.splitdims` in `BlockSparseArraysGradedAxesExt`. invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) -function blockmergesortperm(a::UnitRangeDual) - return Block.(groupsortperm(blocklabels(nondual(a)))) -end - function blockmergesort(g::AbstractGradedUnitRange) glabels = blocklabels(g) gblocklengths = blocklengths(g) From b94f4e80ff028b72608c9e34b88bb756726a3a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 27 Sep 2024 16:46:09 -0400 Subject: [PATCH 114/194] import at begining of file --- NDTensors/src/lib/Sectors/src/Sectors.jl | 14 -------------- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 4 ++++ .../lib/Sectors/src/category_definitions/fib.jl | 1 + .../lib/Sectors/src/category_definitions/ising.jl | 3 +++ .../src/lib/Sectors/src/category_definitions/o2.jl | 3 +++ .../src/lib/Sectors/src/category_definitions/su.jl | 3 +++ .../lib/Sectors/src/category_definitions/su2k.jl | 3 +++ .../src/lib/Sectors/src/category_definitions/u1.jl | 2 ++ .../src/lib/Sectors/src/category_definitions/zn.jl | 2 ++ NDTensors/src/lib/Sectors/src/category_product.jl | 4 ++++ NDTensors/src/lib/Sectors/src/symmetry_style.jl | 2 ++ 11 files changed, 27 insertions(+), 14 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index cd0a7afb1b..f6c8bca92b 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -1,19 +1,5 @@ module Sectors -using BlockArrays: blocklengths -using HalfIntegers: Half, HalfInteger, half, twice - -using NDTensors.LabelledNumbers: - LabelledInteger, label, label_type, labelled, unlabel, unlabel_type -using NDTensors.GradedAxes: - GradedAxes, - blocklabels, - dual, - fuse_blocklengths, - fusion_product, - gradedrange, - tensor_product - include("symmetry_style.jl") include("abstractcategory.jl") include("category_definitions/fib.jl") diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 60443de5a7..f650ab22c8 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -1,6 +1,10 @@ # This file defines the abstract type AbstractCategory # all fusion categories (Z{2}, SU2, Ising...) are subtypes of AbstractCategory +using BlockArrays: blocklengths +using ..LabelledNumbers: LabelledInteger, label, label_type, labelled, unlabel, unlabel_type +using ..GradedAxes: GradedAxes, blocklabels, fuse_blocklengths, gradedrange, tensor_product + abstract type AbstractCategory end # =================================== Base interface ===================================== diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index ff7f69795e..45b2180171 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -3,6 +3,7 @@ # # (same fusion rules as subcategory {0,1} of su2{3}) # +using ..GradedAxes: GradedAxes struct Fib <: AbstractCategory l::Int diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index 1e7b51ab13..7ce3bcef7d 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -4,6 +4,9 @@ # (same fusion rules as su2{2}) # +using HalfIntegers: Half, twice +using ..GradedAxes: GradedAxes + struct Ising <: AbstractCategory l::Half{Int} end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index eb31227265..872f8cdd42 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -9,6 +9,9 @@ # - 2-dimensional Sz=±|m| irrep, with m a half integer # +using HalfIntegers: Half, HalfInteger +using ..GradedAxes: GradedAxes + # here we use only one half-integer as label: # - l=0 for trivial # - l=-1 for zero odd diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index c83a13a48c..880a2780f0 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -2,6 +2,9 @@ # Special unitary group SU(N) # +using HalfIntegers: HalfInteger, half, twice +using ...GradedAxes: GradedAxes + struct SU{N,M} <: AbstractCategory # l is the first row of the # Gelfand-Tsetlin (GT) pattern describing diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index 9570d7bcfa..ffcf4c5550 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -2,6 +2,9 @@ # Quantum 'group' su2ₖ # +using HalfIntegers: Half +using ...GradedAxes: GradedAxes + struct su2{k} <: AbstractCategory j::Half{Int} end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 055b08f630..672fe0655d 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -2,6 +2,8 @@ # U₁ group (circle group, or particle number, total Sz etc.) # +using ...GradedAxes: GradedAxes + # Parametric type to allow both integer label as well as # HalfInteger for easy conversion to/from SU(2) struct U1{T} <: AbstractCategory diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 665080b73d..25299703e3 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -2,6 +2,8 @@ # Cyclic group Zₙ # +using ...GradedAxes: GradedAxes + struct Z{N} <: AbstractCategory m::Int Z{N}(m) where {N} = new{N}(m % N) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index d34c7888e8..309f8a512f 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -1,6 +1,10 @@ # This files defines a structure for Cartesian product of 2 or more fusion categories # e.g. U(1)×U(1), U(1)×SU2(2)×SU(3) +using BlockArrays: blocklengths +using ..LabelledNumbers: LabelledInteger, label, labelled, unlabel +using ..GradedAxes: GradedAxes, dual + # ===================================== Definition ======================================= struct CategoryProduct{Categories} <: AbstractCategory cats::Categories diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index 417c506c6d..944fc0fe28 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -1,6 +1,8 @@ # This file defines SymmetryStyle, a trait to distinguish abelian groups, non-abelian groups # and non-group fusion categories. +using ..LabelledNumbers: LabelledInteger, label + abstract type SymmetryStyle end struct AbelianGroup <: SymmetryStyle end From 26840051ecc719b757de9fe7ba133f4c3068e994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 27 Sep 2024 16:58:43 -0400 Subject: [PATCH 115/194] comment on map use --- NDTensors/src/lib/Sectors/src/category_product.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 309f8a512f..1468191039 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -30,6 +30,7 @@ function quantum_dimension(::NonGroupCategory, s::CategoryProduct) return prod(map(quantum_dimension, categories(s))) end +# use map instead of broadcast to support both Tuple and NamedTuple GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(dual, categories(s))) trivial(type::Type{<:CategoryProduct}) = sector(categories_trivial(categories_type(type))) From 0f485a260f860b70bf190ae1e68455d4564cb2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 27 Sep 2024 17:49:58 -0400 Subject: [PATCH 116/194] clean-up categories_fusion_rule --- .../src/lib/Sectors/src/category_product.jl | 53 +++++++++++++------ .../lib/Sectors/test/test_category_product.jl | 12 +++-- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 1468191039..de69e69088 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -3,7 +3,7 @@ using BlockArrays: blocklengths using ..LabelledNumbers: LabelledInteger, label, labelled, unlabel -using ..GradedAxes: GradedAxes, dual +using ..GradedAxes: AbstractGradedUnitRange, GradedAxes, dual # ===================================== Definition ======================================= struct CategoryProduct{Categories} <: AbstractCategory @@ -67,13 +67,6 @@ end # - ordered-like with a Tuple # - dictionary-like with a NamedTuple -function categories_fusion_rule(cats1, cats2) - diff_cat = CategoryProduct(categories_diff(cats1, cats2)) - shared1, shared2 = categories_common(cats1, cats2) - fused = map(fusion_rule, values(shared1), values(shared2)) - return recover_key(typeof(shared1), fused) × diff_cat -end - # get clean results when mixing implementations categories_isequal(::Tuple, ::NamedTuple) = false categories_isequal(::NamedTuple, ::Tuple) = false @@ -83,20 +76,50 @@ categories_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented" categories_type(::Type{<:CategoryProduct{T}}) where {T} = T -recover_key(T::Type, t::Tuple{Vararg{AbstractCategory}}) = sector(T, t) -recover_key(T::Type, c::AbstractCategory) = recover_key(T, (c,)) -recover_key(T::Type, c::CategoryProduct) = recover_key(T, categories(c)) -function recover_key(T::Type, fused::Tuple) +function categories_fusion_rule(cats1, cats2) + diff_cat = CategoryProduct(categories_diff(cats1, cats2)) + shared1, shared2 = categories_common(cats1, cats2) + fused = map(fusion_rule, values(shared1), values(shared2)) + factorized = factorize_gradedaxes(fused) + type_fixed = recover_category_product_type(typeof(shared1), factorized) + return type_fixed × diff_cat +end + +# abelian case: there is no gradedaxis +factorize_gradedaxes(fused::Tuple{Vararg{AbstractCategory}}) = fused + +# non-abelian case +function factorize_gradedaxes(fused::Tuple) # here fused contains at least one GradedOneTo g0 = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × - new_labels = recover_key.(T, blocklabels(g0)) + return g0 +end + +function recover_category_product_type(T::Type, g0::AbstractGradedUnitRange) + new_labels = recover_category_product_type.(T, blocklabels(g0)) new_blocklengths = labelled.(unlabel.(blocklengths(g0)), new_labels) return gradedrange(new_blocklengths) end -sector(T::Type{<:CategoryProduct}, cats::Tuple) = sector(categories_type(T), cats) -sector(T::Type, cats::Tuple) = sector(T(cats)) # recover NamedTuple +function recover_category_product_type(T::Type, c::AbstractCategory) + return recover_category_product_type(T, CategoryProduct(c)) +end + +function recover_category_product_type(T::Type, c::CategoryProduct) + return recover_category_product_type(T, categories(c)) +end + +function recover_category_product_type( + T::Type{<:CategoryProduct}, cats::Tuple{Vararg{AbstractCategory}} +) + return recover_category_product_type(categories_type(T), cats) +end + +function recover_category_product_type(T::Type, cats::Tuple{Vararg{AbstractCategory}}) + return CategoryProduct(T(cats)) +end + sector(args...; kws...) = CategoryProduct(args...; kws...) # ================================= Cartesian Product ==================================== diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 772fe524c1..f3b1bedce0 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -11,6 +11,7 @@ using NDTensors.Sectors: block_dimensions, categories, quantum_dimension, + recover_category_product_type, sector, trivial using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange @@ -48,8 +49,9 @@ end @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) @test (@inferred_latest trivial(s)) == sector(U1(0), SU2(0), U1(0)) - @test (@inferred sector(typeof(categories(s)), categories(s))) == s - @test (@inferred sector(typeof(s), categories(s))) == s + @test (@inferred recover_category_product_type(typeof(categories(s)), categories(s))) == + s + @test (@inferred recover_category_product_type(typeof(s), categories(s))) == s s = U1(3) × SU2(1//2) × Fib("τ") @test length(categories(s)) == 3 @@ -288,8 +290,10 @@ end @test (@inferred quantum_dimension(s)) == 5 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=SU2(0),) - @test (@inferred sector(typeof(categories(s)), Tuple(categories(s)))) == s - @test (@inferred sector(typeof(s), Tuple(categories(s)))) == s + @test (@inferred recover_category_product_type( + typeof(categories(s)), Tuple(categories(s)) + )) == s + @test (@inferred recover_category_product_type(typeof(s), Tuple(categories(s)))) == s s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 From 8abad018d9dc2b334ac7552b261bd88b2aff97c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 27 Sep 2024 18:20:50 -0400 Subject: [PATCH 117/194] split gradedisequal into labelled_isequal and space_isequal --- .../src/lib/GradedAxes/src/gradedunitrange.jl | 9 +- .../src/lib/GradedAxes/src/unitrangedual.jl | 5 - .../src/lib/GradedAxes/test/test_basics.jl | 4 +- .../src/lib/GradedAxes/test/test_dual.jl | 18 +-- .../GradedAxes/test/test_tensor_product.jl | 25 ++-- .../lib/Sectors/test/test_category_product.jl | 68 +++++----- .../src/lib/Sectors/test/test_fusion_rules.jl | 124 +++++++++--------- 7 files changed, 126 insertions(+), 127 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index 4aabc927fe..dc051b1634 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -38,12 +38,13 @@ function Base.OrdinalRange{T,T}(a::GradedOneTo{<:LabelledInteger{T}}) where {T} end # == is just a range comparison that ignores labels. Need dedicated function to check equality. -function gradedisequal(a1::AbstractUnitRange, a2::AbstractUnitRange) - return blockisequal(a1, a2) +blocklabels(::AbstractUnitRange) = nothing +function labelled_isequal(a1::AbstractUnitRange, a2::AbstractUnitRange) + return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) end -function gradedisequal(a1::AbstractGradedUnitRange, a2::AbstractGradedUnitRange) - return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) +function space_isequal(a1::AbstractUnitRange, a2::AbstractUnitRange) + return (isdual(a1) == isdual(a2)) && labelled_isequal(a1, a2) end # This is only needed in certain Julia versions below 1.10 diff --git a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl index 2f449349ac..8e9529e2d0 100644 --- a/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl +++ b/NDTensors/src/lib/GradedAxes/src/unitrangedual.jl @@ -108,11 +108,6 @@ BlockArrays.findblock(a::UnitRangeDual, index::Integer) = findblock(nondual(a), blocklabels(a::UnitRangeDual) = dual.(blocklabels(nondual(a))) -gradedisequal(::UnitRangeDual, ::AbstractGradedUnitRange) = false -gradedisequal(::AbstractGradedUnitRange, ::UnitRangeDual) = false -function gradedisequal(a1::UnitRangeDual, a2::UnitRangeDual) - return gradedisequal(nondual(a1), nondual(a2)) -end function BlockArrays.combine_blockaxes(a1::UnitRangeDual, a2::UnitRangeDual) return dual(combine_blockaxes(dual(a1), dual(a2))) end diff --git a/NDTensors/src/lib/GradedAxes/test/test_basics.jl b/NDTensors/src/lib/GradedAxes/test/test_basics.jl index 2f3cd03bc7..360c2cecff 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_basics.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_basics.jl @@ -10,7 +10,7 @@ using BlockArrays: blocklengths, blocks using NDTensors.GradedAxes: - GradedOneTo, GradedUnitRange, blocklabels, gradedisequal, gradedrange + GradedOneTo, GradedUnitRange, blocklabels, labelled_isequal, gradedrange using NDTensors.LabelledNumbers: LabelledUnitRange, islabelled, label, labelled, unlabel using Test: @test, @test_broken, @testset @testset "GradedAxes basics" begin @@ -41,7 +41,7 @@ using Test: @test, @test_broken, @testset @test label(x) == "y" end @test isnothing(iterate(a, labelled(5, "y"))) - @test gradedisequal(a, a) + @test labelled_isequal(a, a) @test length(a) == 5 @test step(a) == 1 @test !islabelled(step(a)) diff --git a/NDTensors/src/lib/GradedAxes/test/test_dual.jl b/NDTensors/src/lib/GradedAxes/test/test_dual.jl index 0cce88a6dd..dc7f740713 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_dual.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_dual.jl @@ -9,7 +9,7 @@ using NDTensors.GradedAxes: blocksortperm, dual, flip, - gradedisequal, + space_isequal, gradedrange, isdual, nondual @@ -25,12 +25,12 @@ Base.isless(c1::U1, c2::U1) = c1.n < c2.n ad = dual(a) @test eltype(ad) == LabelledInteger{Int,U1} - @test gradedisequal(dual(ad), a) - @test gradedisequal(nondual(ad), a) - @test gradedisequal(nondual(a), a) - @test gradedisequal(ad, ad) - @test !gradedisequal(a, ad) - @test !gradedisequal(ad, a) + @test space_isequal(dual(ad), a) + @test space_isequal(nondual(ad), a) + @test space_isequal(nondual(a), a) + @test space_isequal(ad, ad) + @test !space_isequal(a, ad) + @test !space_isequal(ad, a) @test isdual(ad) @test !isdual(a) @@ -66,8 +66,8 @@ end @testset "flip" begin a = gradedrange([U1(0) => 2, U1(1) => 3]) ad = dual(a) - @test gradedisequal(flip(a), dual(gradedrange([U1(0) => 2, U1(-1) => 3]))) - @test gradedisequal(flip(ad), gradedrange([U1(0) => 2, U1(-1) => 3])) + @test space_isequal(flip(a), dual(gradedrange([U1(0) => 2, U1(-1) => 3]))) + @test space_isequal(flip(ad), gradedrange([U1(0) => 2, U1(-1) => 3])) @test blocklabels(a) == [U1(0), U1(1)] @test blocklabels(dual(a)) == [U1(0), U1(-1)] diff --git a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl index 7b533f79c5..02435b5ba7 100644 --- a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl +++ b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl @@ -11,7 +11,8 @@ using NDTensors.GradedAxes: fusion_product, flip, gradedrange, - gradedisequal, + labelled_isequal, + space_isequal, isdual, tensor_product @@ -26,7 +27,7 @@ GradedAxes.fuse_labels(x::U1, y::U1) = U1(x.n + y.n) GradedAxes.fuse_labels(x::String, y::String) = x * y g0 = OneToOne() - @test gradedisequal(tensor_product(g0, g0), g0) + @test labelled_isequal(tensor_product(g0, g0), g0) a = gradedrange(["x" => 2, "y" => 3]) b = tensor_product(a, a) @@ -34,7 +35,7 @@ GradedAxes.fuse_labels(x::U1, y::U1) = U1(x.n + y.n) @test length(b) == 25 @test blocklength(b) == 4 @test blocklengths(b) == [4, 6, 6, 9] - @test gradedisequal(b, gradedrange(["xx" => 4, "yx" => 6, "xy" => 6, "yy" => 9])) + @test labelled_isequal(b, gradedrange(["xx" => 4, "yx" => 6, "xy" => 6, "yy" => 9])) c = tensor_product(a, a, a) @test c isa GradedOneTo @@ -44,18 +45,20 @@ end @testset "GradedAxes.fusion_product" begin g0 = OneToOne() - @test gradedisequal(fusion_product(g0, g0), g0) + @test labelled_isequal(fusion_product(g0, g0), g0) a = gradedrange([U1(1) => 1, U1(2) => 3, U1(1) => 1]) b = fusion_product(a) - @test gradedisequal(b, gradedrange([U1(1) => 2, U1(2) => 3])) + @test labelled_isequal(b, gradedrange([U1(1) => 2, U1(2) => 3])) c = fusion_product(a, a) - @test gradedisequal(c, gradedrange([U1(2) => 4, U1(3) => 12, U1(4) => 9])) + @test labelled_isequal(c, gradedrange([U1(2) => 4, U1(3) => 12, U1(4) => 9])) d = fusion_product(a, a, a) - @test gradedisequal(d, gradedrange([U1(3) => 8, U1(4) => 36, U1(5) => 54, U1(6) => 27])) + @test labelled_isequal( + d, gradedrange([U1(3) => 8, U1(4) => 36, U1(5) => 54, U1(6) => 27]) + ) end @testset "dual and tensor_product" begin @@ -65,19 +68,19 @@ end b = fusion_product(ad) @test b isa GradedOneTo @test !isdual(b) - @test gradedisequal(b, gradedrange([U1(-2) => 3, U1(-1) => 2])) + @test space_isequal(b, gradedrange([U1(-2) => 3, U1(-1) => 2])) c = fusion_product(ad, ad) @test c isa GradedOneTo @test !isdual(c) - @test gradedisequal(c, gradedrange([U1(-4) => 9, U1(-3) => 12, U1(-2) => 4])) + @test space_isequal(c, gradedrange([U1(-4) => 9, U1(-3) => 12, U1(-2) => 4])) d = fusion_product(ad, a) @test !isdual(d) - @test gradedisequal(d, gradedrange([U1(-1) => 6, U1(0) => 13, U1(1) => 6])) + @test space_isequal(d, gradedrange([U1(-1) => 6, U1(0) => 13, U1(1) => 6])) e = fusion_product(a, ad) @test !isdual(d) - @test gradedisequal(e, d) + @test space_isequal(e, d) end end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index f3b1bedce0..e651b9cd87 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -14,7 +14,7 @@ using NDTensors.Sectors: recover_category_product_type, sector, trivial -using NDTensors.GradedAxes: dual, fusion_product, gradedisequal, gradedrange +using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange using Test: @inferred, @test, @testset, @test_throws macro inferred_latest(ex) @@ -150,10 +150,10 @@ end @testset "Fusion of NonAbelian products" begin p0 = sector(SU2(0)) ph = sector(SU2(1//2)) - @test gradedisequal((@inferred p0 ⊗ ph), gradedrange([sector(SU2(1//2)) => 1])) + @test space_isequal((@inferred p0 ⊗ ph), gradedrange([sector(SU2(1//2)) => 1])) phh = SU2(1//2) × SU2(1//2) - @test gradedisequal( + @test space_isequal( phh ⊗ phh, gradedrange([ (SU2(0) × SU2(0)) => 1, @@ -162,7 +162,7 @@ end (SU2(1) × SU2(1)) => 1, ]), ) - @test gradedisequal( + @test space_isequal( (@inferred phh ⊗ phh), gradedrange([ (SU2(0) × SU2(0)) => 1, @@ -177,10 +177,10 @@ end ı = Fib("1") τ = Fib("τ") s = ı × ı - @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) + @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) s = τ × τ - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]), ) @@ -191,18 +191,18 @@ end g = gradedrange([ (ı × Ising("1")) => 1, (τ × Ising("1")) => 1, (ı × ψ) => 1, (τ × ψ) => 1 ]) - @test gradedisequal((@inferred s ⊗ s), g) + @test space_isequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) - @test gradedisequal( + @test space_isequal( (@inferred p2h ⊗ p1h), gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) ) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) - @test gradedisequal( + @test space_isequal( (@inferred p1h1 ⊗ p1h1), gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]), ) @@ -210,7 +210,7 @@ end @testset "Fusion of fully mixed products" begin s = U1(1) × SU2(1//2) × Ising("σ") - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([ (U1(2) × SU2(0) × Ising("1")) => 1, @@ -223,7 +223,7 @@ end ı = Fib("1") τ = Fib("τ") s = SU2(1//2) × U1(1) × τ - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([ (SU2(0) × U1(2) × ı) => 1, @@ -234,23 +234,23 @@ end ) s = U1(1) × ı × τ - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) ) end @testset "Fusion of different length Categories" begin @test sector(U1(1) × U1(0)) ⊗ sector(U1(1)) == sector(U1(2) × U1(0)) - @test gradedisequal( + @test space_isequal( (@inferred sector(SU2(0) × SU2(0)) ⊗ sector(SU2(1))), gradedrange([sector(SU2(1) × SU2(0)) => 1]), ) - @test gradedisequal( + @test space_isequal( (@inferred sector(SU2(1) × U1(1)) ⊗ sector(SU2(0))), gradedrange([sector(SU2(1) × U1(1)) => 1]), ) - @test gradedisequal( + @test space_isequal( (@inferred sector(U1(1) × SU2(1)) ⊗ sector(U1(2))), gradedrange([sector(U1(3) × SU2(1)) => 1]), ) @@ -266,7 +266,7 @@ end s2 = U1(0) × SU2(1//2) × Ising("1") g1 = gradedrange([s1 => 2]) g2 = gradedrange([s2 => 1]) - @test gradedisequal( + @test space_isequal( (@inferred fusion_product(g1, g2)), gradedrange([U1(1) × SU2(0) × Ising("σ") => 2, U1(1) × SU2(1) × Ising("σ") => 2]), ) @@ -418,14 +418,14 @@ end phb = sector(; B=SU2(1//2)) phab = sector(; A=SU2(1//2), B=SU2(1//2)) - @test gradedisequal( + @test space_isequal( (@inferred pha ⊗ pha), gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) ) - @test gradedisequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) - @test gradedisequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) - @test gradedisequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) + @test space_isequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) + @test space_isequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) + @test space_isequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) - @test gradedisequal( + @test space_isequal( (@inferred phab ⊗ phab), gradedrange([ sector(; A=SU2(0), B=SU2(0)) => 1, @@ -440,10 +440,10 @@ end ı = Fib("1") τ = Fib("τ") s = sector(; A=ı, B=ı) - @test gradedisequal((@inferred s ⊗ s), gradedrange([s => 1])) + @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) s = sector(; A=τ, B=τ) - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([ sector(; A=ı, B=ı) => 1, @@ -462,7 +462,7 @@ end sector(; A=ı, B=ψ) => 1, sector(; A=τ, B=ψ) => 1, ]) - @test gradedisequal((@inferred s ⊗ s), g) + @test space_isequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -476,15 +476,15 @@ end q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test gradedisequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) - @test gradedisequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) - @test gradedisequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) - @test gradedisequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) + @test space_isequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) + @test space_isequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) + @test space_isequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) + @test space_isequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) end @testset "Fusion of fully mixed products" begin s = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([ sector(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, @@ -497,7 +497,7 @@ end ı = Fib("1") τ = Fib("τ") s = sector(; A=SU2(1//2), B=U1(1), C=τ) - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([ sector(; A=SU2(0), B=U1(2), C=ı) => 1, @@ -508,7 +508,7 @@ end ) s = sector(; A=τ, B=U1(1), C=ı) - @test gradedisequal( + @test space_isequal( (@inferred s ⊗ s), gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]), ) @@ -520,7 +520,7 @@ end g2 = gradedrange([s2 => 1]) s3 = sector(; A=U1(1), B=SU2(0), C=Ising("σ")) s4 = sector(; A=U1(1), B=SU2(1), C=Ising("σ")) - @test gradedisequal( + @test space_isequal( (@inferred_latest fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2]) ) @@ -529,7 +529,7 @@ end sAB = sector(; A=U1(1), B=SU2(1//2)) gA = gradedrange([sA => 2]) gB = gradedrange([sB => 1]) - @test gradedisequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) + @test space_isequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) end end @@ -545,7 +545,7 @@ end @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} g0 = gradedrange([s => 2]) - @test gradedisequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) + @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) @test (@inferred s × U1(1)) == sector(U1(1)) @test (@inferred s × sector(U1(1))) == sector(U1(1)) diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index bcefd2d851..08d8d4d108 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -1,6 +1,6 @@ @eval module $(gensym()) using NDTensors.GradedAxes: - dual, fusion_product, gradedisequal, gradedrange, flip, tensor_product + dual, fusion_product, space_isequal, gradedrange, flip, tensor_product using NDTensors.Sectors: ⊗, Fib, Ising, O2, SU, SU2, U1, Z, block_dimensions, quantum_dimension, trivial using Test: @inferred, @test, @testset, @test_throws @@ -16,13 +16,13 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred z0 ⊗ z0) == z0 # no better way, see Julia PR 23426 # using GradedAxes interface - @test gradedisequal(fusion_product(z0, z0), gradedrange([z0 => 1])) - @test gradedisequal(fusion_product(z0, z1), gradedrange([z1 => 1])) + @test space_isequal(fusion_product(z0, z0), gradedrange([z0 => 1])) + @test space_isequal(fusion_product(z0, z1), gradedrange([z1 => 1])) # test different input number - @test gradedisequal(fusion_product(z0), gradedrange([z0 => 1])) - @test gradedisequal(fusion_product(z0, z0, z0), gradedrange([z0 => 1])) - @test gradedisequal(fusion_product(z0, z0, z0, z0), gradedrange([z0 => 1])) + @test space_isequal(fusion_product(z0), gradedrange([z0 => 1])) + @test space_isequal(fusion_product(z0, z0, z0), gradedrange([z0 => 1])) + @test space_isequal(fusion_product(z0, z0, z0, z0), gradedrange([z0 => 1])) @test (@inferred block_dimensions(gradedrange([z1 => 1]))) == [1] end @testset "U(1) fusion rules" begin @@ -42,17 +42,17 @@ using Test: @inferred, @test, @testset, @test_throws s12 = O2(1//2) s1 = O2(1) - @test gradedisequal((@inferred s0e ⊗ s0e), gradedrange([s0e => 1])) - @test gradedisequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) - @test gradedisequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) - @test gradedisequal((@inferred s0o ⊗ s0o), gradedrange([s0e => 1])) + @test space_isequal((@inferred s0e ⊗ s0e), gradedrange([s0e => 1])) + @test space_isequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) + @test space_isequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) + @test space_isequal((@inferred s0o ⊗ s0o), gradedrange([s0e => 1])) - @test gradedisequal((@inferred s0e ⊗ s12), gradedrange([s12 => 1])) - @test gradedisequal((@inferred s0o ⊗ s12), gradedrange([s12 => 1])) - @test gradedisequal((@inferred s12 ⊗ s0e), gradedrange([s12 => 1])) - @test gradedisequal((@inferred s12 ⊗ s0o), gradedrange([s12 => 1])) - @test gradedisequal((@inferred s12 ⊗ s1), gradedrange([s12 => 1, O2(3//2) => 1])) - @test gradedisequal((@inferred s12 ⊗ s12), gradedrange([s0o => 1, s0e => 1, s1 => 1])) + @test space_isequal((@inferred s0e ⊗ s12), gradedrange([s12 => 1])) + @test space_isequal((@inferred s0o ⊗ s12), gradedrange([s12 => 1])) + @test space_isequal((@inferred s12 ⊗ s0e), gradedrange([s12 => 1])) + @test space_isequal((@inferred s12 ⊗ s0o), gradedrange([s12 => 1])) + @test space_isequal((@inferred s12 ⊗ s1), gradedrange([s12 => 1, O2(3//2) => 1])) + @test space_isequal((@inferred s12 ⊗ s12), gradedrange([s0o => 1, s0e => 1, s1 => 1])) @test (@inferred quantum_dimension(s0o ⊗ s1)) == 2 @test (@inferred block_dimensions(s0o ⊗ s1)) == [2] @@ -65,27 +65,27 @@ using Test: @inferred, @test, @testset, @test_throws j4 = SU2(3//2) j5 = SU2(2) - @test gradedisequal(j1 ⊗ j2, gradedrange([j2 => 1])) - @test gradedisequal(j2 ⊗ j2, gradedrange([j1 => 1, j3 => 1])) - @test gradedisequal(j2 ⊗ j3, gradedrange([j2 => 1, j4 => 1])) - @test gradedisequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) - @test gradedisequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) + @test space_isequal(j1 ⊗ j2, gradedrange([j2 => 1])) + @test space_isequal(j2 ⊗ j2, gradedrange([j1 => 1, j3 => 1])) + @test space_isequal(j2 ⊗ j3, gradedrange([j2 => 1, j4 => 1])) + @test space_isequal(j3 ⊗ j3, gradedrange([j1 => 1, j3 => 1, j5 => 1])) + @test space_isequal((@inferred j1 ⊗ j2), gradedrange([j2 => 1])) @test (@inferred quantum_dimension(j1 ⊗ j2)) == 2 @test (@inferred block_dimensions(j1 ⊗ j2)) == [2] - @test gradedisequal(fusion_product(j2), gradedrange([j2 => 1])) - @test gradedisequal(fusion_product(j2, j1), gradedrange([j2 => 1])) - @test gradedisequal(fusion_product(j2, j1, j1), gradedrange([j2 => 1])) + @test space_isequal(fusion_product(j2), gradedrange([j2 => 1])) + @test space_isequal(fusion_product(j2, j1), gradedrange([j2 => 1])) + @test space_isequal(fusion_product(j2, j1, j1), gradedrange([j2 => 1])) end @testset "Fibonacci fusion rules" begin ı = Fib("1") τ = Fib("τ") - @test gradedisequal(ı ⊗ ı, gradedrange([ı => 1])) - @test gradedisequal(ı ⊗ τ, gradedrange([τ => 1])) - @test gradedisequal(τ ⊗ ı, gradedrange([τ => 1])) - @test gradedisequal((@inferred τ ⊗ τ), gradedrange([ı => 1, τ => 1])) + @test space_isequal(ı ⊗ ı, gradedrange([ı => 1])) + @test space_isequal(ı ⊗ τ, gradedrange([τ => 1])) + @test space_isequal(τ ⊗ ı, gradedrange([τ => 1])) + @test space_isequal((@inferred τ ⊗ τ), gradedrange([ı => 1, τ => 1])) @test (@inferred quantum_dimension(gradedrange([ı => 1, ı => 1]))) == 2.0 end @@ -94,16 +94,16 @@ using Test: @inferred, @test, @testset, @test_throws σ = Ising("σ") ψ = Ising("ψ") - @test gradedisequal(ı ⊗ ı, gradedrange([ı => 1])) - @test gradedisequal(ı ⊗ σ, gradedrange([σ => 1])) - @test gradedisequal(σ ⊗ ı, gradedrange([σ => 1])) - @test gradedisequal(ı ⊗ ψ, gradedrange([ψ => 1])) - @test gradedisequal(ψ ⊗ ı, gradedrange([ψ => 1])) - @test gradedisequal(σ ⊗ σ, gradedrange([ı => 1, ψ => 1])) - @test gradedisequal(σ ⊗ ψ, gradedrange([σ => 1])) - @test gradedisequal(ψ ⊗ σ, gradedrange([σ => 1])) - @test gradedisequal(ψ ⊗ ψ, gradedrange([ı => 1])) - @test gradedisequal((@inferred ψ ⊗ ψ), gradedrange([ı => 1])) + @test space_isequal(ı ⊗ ı, gradedrange([ı => 1])) + @test space_isequal(ı ⊗ σ, gradedrange([σ => 1])) + @test space_isequal(σ ⊗ ı, gradedrange([σ => 1])) + @test space_isequal(ı ⊗ ψ, gradedrange([ψ => 1])) + @test space_isequal(ψ ⊗ ı, gradedrange([ψ => 1])) + @test space_isequal(σ ⊗ σ, gradedrange([ı => 1, ψ => 1])) + @test space_isequal(σ ⊗ ψ, gradedrange([σ => 1])) + @test space_isequal(ψ ⊗ σ, gradedrange([σ => 1])) + @test space_isequal(ψ ⊗ ψ, gradedrange([ı => 1])) + @test space_isequal((@inferred ψ ⊗ ψ), gradedrange([ı => 1])) @test (@inferred quantum_dimension(σ ⊗ σ)) == 2.0 end end @@ -111,15 +111,15 @@ end @testset "Trivial GradedUnitRange" begin g1 = gradedrange([U1(0) => 1]) g2 = gradedrange([SU2(0) => 1]) - @test gradedisequal(trivial(g1), g1) - @test gradedisequal(trivial(dual(g1)), g1) # trivial returns nondual - @test gradedisequal(trivial(typeof(g2)), g2) + @test space_isequal(trivial(g1), g1) + @test space_isequal(trivial(dual(g1)), g1) # trivial returns nondual + @test space_isequal(trivial(typeof(g2)), g2) end @testset "GradedUnitRange abelian tensor/fusion product" begin g1 = gradedrange([U1(-1) => 1, U1(0) => 1, U1(1) => 2]) g2 = gradedrange([U1(-2) => 2, U1(0) => 1, U1(1) => 2]) - @test gradedisequal(flip(dual(g1)), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) + @test space_isequal(flip(dual(g1)), gradedrange([U1(1) => 1, U1(0) => 1, U1(-1) => 2])) @test (@inferred block_dimensions(g1)) == [1, 1, 2] gt = gradedrange([ @@ -136,8 +136,8 @@ end gf = gradedrange([ U1(-3) => 2, U1(-2) => 2, U1(-1) => 5, U1(0) => 3, U1(1) => 4, U1(2) => 4 ]) - @test gradedisequal((@inferred tensor_product(g1, g2)), gt) - @test gradedisequal((@inferred fusion_product(g1, g2)), gf) + @test space_isequal((@inferred tensor_product(g1, g2)), gt) + @test space_isequal((@inferred fusion_product(g1, g2)), gf) gtd1 = gradedrange([ U1(-1) => 2, @@ -153,8 +153,8 @@ end gfd1 = gradedrange([ U1(-3) => 4, U1(-2) => 2, U1(-1) => 4, U1(0) => 5, U1(1) => 3, U1(2) => 2 ]) - @test gradedisequal((@inferred tensor_product(dual(g1), g2)), gtd1) - @test gradedisequal((@inferred fusion_product(dual(g1), g2)), gfd1) + @test space_isequal((@inferred tensor_product(dual(g1), g2)), gtd1) + @test space_isequal((@inferred fusion_product(dual(g1), g2)), gfd1) gtd2 = gradedrange([ U1(1) => 2, @@ -170,8 +170,8 @@ end gfd2 = gradedrange([ U1(-2) => 2, U1(-1) => 3, U1(0) => 5, U1(1) => 4, U1(2) => 2, U1(3) => 4 ]) - @test gradedisequal((@inferred tensor_product(g1, dual(g2))), gtd2) - @test gradedisequal((@inferred fusion_product(g1, dual(g2))), gfd2) + @test space_isequal((@inferred tensor_product(g1, dual(g2))), gtd2) + @test space_isequal((@inferred fusion_product(g1, dual(g2))), gfd2) gtd = gradedrange([ U1(3) => 2, @@ -187,8 +187,8 @@ end gfd = gradedrange([ U1(-2) => 4, U1(-1) => 4, U1(0) => 3, U1(1) => 5, U1(2) => 2, U1(3) => 2 ]) - @test gradedisequal((@inferred tensor_product(dual(g1), dual(g2))), gtd) - @test gradedisequal((@inferred fusion_product(dual(g1), dual(g2))), gfd) + @test space_isequal((@inferred tensor_product(dual(g1), dual(g2))), gtd) + @test space_isequal((@inferred fusion_product(dual(g1), dual(g2))), gfd) # test different (non-product) categories cannot be fused @test_throws MethodError fusion_product(gradedrange([Z{2}(0) => 1]), g1) @@ -212,10 +212,10 @@ end SU2(2) => 2, ]) - @test gradedisequal(tensor_product(g3, g4), g34) + @test space_isequal(tensor_product(g3, g4), g34) - @test gradedisequal(dual(flip(g3)), g3) # trivial for SU(2) - @test gradedisequal( + @test space_isequal(dual(flip(g3)), g3) # trivial for SU(2) + @test space_isequal( (@inferred fusion_product(g3, g4)), gradedrange([SU2(0) => 4, SU2(1//2) => 6, SU2(1) => 6, SU2(3//2) => 5, SU2(2) => 2]), ) @@ -229,19 +229,19 @@ end g5 = gradedrange([s1 => 1, f3 => 1]) g6 = gradedrange([s1 => 1, c3 => 1]) - @test gradedisequal(dual(flip(g5)), g6) - @test gradedisequal( + @test space_isequal(dual(flip(g5)), g6) + @test space_isequal( fusion_product(g5, g6), gradedrange([s1 => 2, f3 => 1, c3 => 1, ad8 => 1]) ) - @test gradedisequal( + @test space_isequal( fusion_product(dual(g5), g6), gradedrange([s1 => 1, f3 => 1, c3 => 2, SU{3}((2, 2)) => 1]), ) - @test gradedisequal( + @test space_isequal( fusion_product(g5, dual(g6)), gradedrange([s1 => 1, f3 => 2, c3 => 1, SU{3}((2, 0)) => 1]), ) - @test gradedisequal( + @test space_isequal( fusion_product(dual(g5), dual(g6)), gradedrange([s1 => 2, f3 => 1, c3 => 1, ad8 => 1]) ) end @@ -249,13 +249,13 @@ end @testset "Mixed GradedUnitRange - Category fusion rules" begin g1 = gradedrange([U1(1) => 1, U1(2) => 2]) g2 = gradedrange([U1(2) => 1, U1(3) => 2]) - @test gradedisequal((@inferred fusion_product(g1, U1(1))), g2) - @test gradedisequal((@inferred fusion_product(U1(1), g1)), g2) + @test space_isequal((@inferred fusion_product(g1, U1(1))), g2) + @test space_isequal((@inferred fusion_product(U1(1), g1)), g2) g3 = gradedrange([SU2(0) => 1, SU2(1//2) => 2]) g4 = gradedrange([SU2(0) => 2, SU2(1//2) => 1, SU2(1) => 2]) - @test gradedisequal((@inferred fusion_product(g3, SU2(1//2))), g4) - @test gradedisequal((@inferred fusion_product(SU2(1//2), g3)), g4) + @test space_isequal((@inferred fusion_product(g3, SU2(1//2))), g4) + @test space_isequal((@inferred fusion_product(SU2(1//2), g3)), g4) # test different categories cannot be fused @test_throws MethodError fusion_product(g1, SU2(1)) From e93bfe20762084aac9b6ec958874e59bba564fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 10:47:59 -0400 Subject: [PATCH 118/194] rename to_graded_axis to_gradedrange --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 14 +++++++------- NDTensors/src/lib/Sectors/src/category_product.jl | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index f650ab22c8..b6e86685e6 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -103,23 +103,23 @@ function GradedAxes.fuse_blocklengths( end # cast to range -to_graded_axis(c::AbstractCategory) = to_graded_axis(labelled(1, c)) -to_graded_axis(l::LabelledInteger) = gradedrange([l]) -to_graded_axis(g::AbstractUnitRange) = g +to_gradedrange(c::AbstractCategory) = to_gradedrange(labelled(1, c)) +to_gradedrange(l::LabelledInteger) = gradedrange([l]) +to_gradedrange(g::AbstractUnitRange) = g # allow to fuse a category with a GradedUnitRange function GradedAxes.tensor_product(c::AbstractCategory, g::AbstractUnitRange) - return tensor_product(to_graded_axis(c), g) + return tensor_product(to_gradedrange(c), g) end function GradedAxes.tensor_product(g::AbstractUnitRange, c::AbstractCategory) - return tensor_product(g, to_graded_axis(c)) + return tensor_product(g, to_gradedrange(c)) end function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) - return to_graded_axis(fusion_rule(c1, c2)) + return to_gradedrange(fusion_rule(c1, c2)) end function GradedAxes.fusion_product(c::AbstractCategory) - return to_graded_axis(c) + return to_gradedrange(c) end diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index de69e69088..6e4738022d 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -128,8 +128,8 @@ function ×(p1::CategoryProduct, p2::CategoryProduct) return CategoryProduct(categories_product(categories(p1), categories(p2))) end -×(a, g::AbstractUnitRange) = ×(to_graded_axis(a), g) -×(g::AbstractUnitRange, b) = ×(g, to_graded_axis(b)) +×(a, g::AbstractUnitRange) = ×(to_gradedrange(a), g) +×(g::AbstractUnitRange, b) = ×(g, to_gradedrange(b)) ×(nt1::NamedTuple, nt2::NamedTuple) = ×(CategoryProduct(nt1), CategoryProduct(nt2)) ×(c1::NamedTuple, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) ×(c1::AbstractCategory, c2::NamedTuple) = ×(CategoryProduct(c1), CategoryProduct(c2)) @@ -151,7 +151,7 @@ end # ==================================== Fusion rules ====================================== # generic case: fusion returns a GradedAxes, even for fusion with Empty function fusion_rule(::SymmetryStyle, s1::CategoryProduct, s2::CategoryProduct) - return to_graded_axis(categories_fusion_rule(categories(s1), categories(s2))) + return to_gradedrange(categories_fusion_rule(categories(s1), categories(s2))) end # Abelian case: fusion returns CategoryProduct @@ -166,16 +166,16 @@ end # EmptyCategory acts as trivial on any AbstractCategory, not just CategoryProduct function fusion_rule(::SymmetryStyle, ::EmptyCategoryProduct, c::AbstractCategory) - return to_graded_axis(c) + return to_gradedrange(c) end function fusion_rule(::SymmetryStyle, ::EmptyCategoryProduct, c::CategoryProduct) - return to_graded_axis(c) + return to_gradedrange(c) end function fusion_rule(::SymmetryStyle, c::AbstractCategory, ::EmptyCategoryProduct) - return to_graded_axis(c) + return to_gradedrange(c) end function fusion_rule(::SymmetryStyle, c::CategoryProduct, ::EmptyCategoryProduct) - return to_graded_axis(c) + return to_gradedrange(c) end # abelian case: return Category From fe5237aa67e120d2fac68d8f411c0128f1d39ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 10:48:50 -0400 Subject: [PATCH 119/194] remove outdated comment --- NDTensors/src/lib/Sectors/src/category_product.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 6e4738022d..5b22e46f4e 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -225,7 +225,6 @@ function CategoryProduct(pairs::Pair...) return CategoryProduct(NamedTuple{keys}(vals)) end -# sector() acts as empty NamedTuple function categories_isequal(nt::NamedTuple, ::Tuple{}) return categories_isequal(nt, (;)) end From 4cb78740c0d05cf6cdb3006e1c336fc3914c25e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 10:51:35 -0400 Subject: [PATCH 120/194] implicit tuple in CategoryProduct --- NDTensors/src/lib/Sectors/src/category_product.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 5b22e46f4e..05832a0349 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -186,7 +186,7 @@ fusion_rule(::AbelianGroup, c::CategoryProduct, ::EmptyCategoryProduct) = c # =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) -CategoryProduct(cats::AbstractCategory...) = CategoryProduct((cats...,)) +CategoryProduct(cats::AbstractCategory...) = CategoryProduct(cats) categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) From 1855045ae4da3a19c09ada6d6a04eebfa8719efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 11:03:41 -0400 Subject: [PATCH 121/194] remove redundant isless --- NDTensors/src/lib/Sectors/src/category_product.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 05832a0349..24d7bf2c55 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -55,9 +55,6 @@ end category_show(io::IO, ::Int, v) = print(io, v) category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") -function Base.isless(s1::C, s2::C) where {C<:CategoryProduct} - return categories_isless(categories(s1), categories(s2)) -end function Base.isless(s1::CategoryProduct, s2::CategoryProduct) return categories_isless(categories(s1), categories(s2)) end From 737c5c27f8f7087762164b9a4a9af199a494be91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 11:10:03 -0400 Subject: [PATCH 122/194] use do-block syntax --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index ad58381009..b9793c18d9 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -108,10 +108,9 @@ invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) function blockmergesort(g::AbstractGradedUnitRange) glabels = blocklabels(g) gblocklengths = blocklengths(g) - new_blocklengths = map( - la -> labelled(sum(gblocklengths[findall(==(la), glabels)]; init=0), la), - sort(unique(glabels)), - ) + new_blocklengths = map(sort(unique(glabels))) do la + return labelled(sum(gblocklengths[findall(==(la), glabels)]; init=0), la) + end return gradedrange(new_blocklengths) end From 65dc71eb1a7c1226fb342cf37f60eda332914984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 11:16:07 -0400 Subject: [PATCH 123/194] fix blockmergesort(g::UnitRangeDual) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index b9793c18d9..8c6134feed 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -114,7 +114,7 @@ function blockmergesort(g::AbstractGradedUnitRange) return gradedrange(new_blocklengths) end -blockmergesort(g::UnitRangeDual) = dual(blockmergesort(flip(g))) +blockmergesort(g::UnitRangeDual) = flip(blockmergesort(flip(g))) blockmergesort(g::AbstractUnitRange) = g # fusion_product produces a sorted, non-dual GradedUnitRange From b4380b097dff8b24d0a99366fc46b655e80288de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 11:57:34 -0400 Subject: [PATCH 124/194] use NoLabel as default blocklabel --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 1 + NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 8c6134feed..754c3cd95e 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -5,6 +5,7 @@ struct OneToOne{T} <: AbstractUnitRange{T} end OneToOne() = OneToOne{Bool}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) +BlockArrays.blockaxes(::OneToOne{Bool}) = (Block.(OneToOne()),) # https://github.com/ITensor/ITensors.jl/blob/v0.3.57/NDTensors/src/lib/GradedAxes/src/tensor_product.jl # https://en.wikipedia.org/wiki/Tensor_product diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index dc051b1634..243853514b 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -19,6 +19,7 @@ using BlockArrays: findblockindex, mortar using Compat: allequal +using FillArrays: Fill using ..LabelledNumbers: LabelledNumbers, LabelledInteger, LabelledUnitRange, label, labelled, unlabel @@ -38,7 +39,9 @@ function Base.OrdinalRange{T,T}(a::GradedOneTo{<:LabelledInteger{T}}) where {T} end # == is just a range comparison that ignores labels. Need dedicated function to check equality. -blocklabels(::AbstractUnitRange) = nothing +struct NoLabel end +blocklabels(r::AbstractUnitRange) = Fill(NoLabel(), blocklength(r)) + function labelled_isequal(a1::AbstractUnitRange, a2::AbstractUnitRange) return blockisequal(a1, a2) && (blocklabels(a1) == blocklabels(a2)) end From a36f4d76c7e0f87f495cda7d71f1b66dc4e187fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 12:40:25 -0400 Subject: [PATCH 125/194] assert issorted --- NDTensors/src/lib/Sectors/src/category_product.jl | 3 +++ NDTensors/src/lib/Sectors/test/test_category_product.jl | 1 + 2 files changed, 4 insertions(+) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 24d7bf2c55..2ccc388364 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -265,6 +265,9 @@ function categories_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} end function categories_common(nt1::NamedTuple, nt2::NamedTuple) + # CategoryProduct(nt::NamedTuple) sorts keys at init + @assert issorted(keys(nt1)) + @assert issorted(keys(nt2)) return intersect_keys(nt1, nt2), intersect_keys(nt2, nt1) end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index e651b9cd87..ba0ca2c37f 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -294,6 +294,7 @@ end typeof(categories(s)), Tuple(categories(s)) )) == s @test (@inferred recover_category_product_type(typeof(s), Tuple(categories(s)))) == s + @test s == (B=SU2(2),) × (A=U1(1),) s = s × (C=Ising("ψ"),) @test length(categories(s)) == 3 From f32501118f6b16cabc6c28a0b82178e72cdf1068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 12:49:03 -0400 Subject: [PATCH 126/194] unify handling of tensor_product(dual) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 754c3cd95e..3560317c2f 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -19,8 +19,10 @@ function tensor_product( return foldl(tensor_product, (a1, a2, a3, a_rest...)) end -function tensor_product(::AbstractUnitRange, ::AbstractUnitRange) - return error("Not implemented yet.") +flip_dual(r::AbstractUnitRange) = r +flip_dual(r::UnitRangeDual) = flip(r) +function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) + return tensor_product(flip_dual(a1), flip_dual(a2)) end function tensor_product(a1::Base.OneTo, a2::Base.OneTo) @@ -39,20 +41,6 @@ function tensor_product(::OneToOne, ::OneToOne) return OneToOne() end -# Handle dual. Always return a non-dual GradedUnitRange. -function tensor_product(a1::AbstractUnitRange, a2::UnitRangeDual) - return tensor_product(a1, flip(a2)) -end - -function tensor_product(a1::UnitRangeDual, a2::AbstractUnitRange) - return tensor_product(flip(a1), a2) -end - -# TBD change convention to tensor(dual, dual) -> dual? -function tensor_product(a1::UnitRangeDual, a2::UnitRangeDual) - return tensor_product(flip(a1), flip(a2)) -end - function fuse_labels(x, y) return error( "`fuse_labels` not implemented for object of type `$(typeof(x))` and `$(typeof(y))`." From 77ef3077e567a17f96581f12c66ee2ba8c6ac134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 12:58:59 -0400 Subject: [PATCH 127/194] split shared and diff category --- NDTensors/src/lib/Sectors/src/category_product.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 2ccc388364..55bbdebec6 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -74,12 +74,16 @@ categories_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented" categories_type(::Type{<:CategoryProduct{T}}) where {T} = T function categories_fusion_rule(cats1, cats2) + shared_cat = shared_categories_fusion_rule(categories_common(cats1, cats2)...) diff_cat = CategoryProduct(categories_diff(cats1, cats2)) - shared1, shared2 = categories_common(cats1, cats2) + return shared_cat × diff_cat +end + +function shared_categories_fusion_rule(shared1, shared2) fused = map(fusion_rule, values(shared1), values(shared2)) factorized = factorize_gradedaxes(fused) type_fixed = recover_category_product_type(typeof(shared1), factorized) - return type_fixed × diff_cat + return type_fixed end # abelian case: there is no gradedaxis From b3e60081b49679ad307f92731f4e9fd17415a2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 18:26:24 -0400 Subject: [PATCH 128/194] generic blockaxes(::OneToOne) --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 3560317c2f..c13c4d2b3d 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -5,7 +5,7 @@ struct OneToOne{T} <: AbstractUnitRange{T} end OneToOne() = OneToOne{Bool}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) -BlockArrays.blockaxes(::OneToOne{Bool}) = (Block.(OneToOne()),) +BlockArrays.blockaxes(g::OneToOne) = (Block.(g),) # https://github.com/ITensor/ITensors.jl/blob/v0.3.57/NDTensors/src/lib/GradedAxes/src/tensor_product.jl # https://en.wikipedia.org/wiki/Tensor_product From 648bc2830c1177d8c520dc31fbb61e5aaeae0c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 2 Oct 2024 18:53:45 -0400 Subject: [PATCH 129/194] rename EmptyCategory to EmptyCategoryStyle --- .../src/lib/Sectors/src/abstractcategory.jl | 6 ++-- .../src/lib/Sectors/src/category_product.jl | 35 ++++++++++++------- .../src/lib/Sectors/src/symmetry_style.jl | 10 +++--- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index b6e86685e6..87d36959be 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -43,7 +43,7 @@ function quantum_dimension(::SymmetryStyle, c::AbstractCategory) end quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 -quantum_dimension(::EmptyCategory, ::AbstractCategory) = 1 +quantum_dimension(::EmptyCategoryStyle, ::AbstractCategory) = 1 quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) = sum(block_dimensions(g)) quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) @@ -64,7 +64,7 @@ function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} return C(label_fusion_rule(C, category_label(c1), category_label(c2))) end -function fusion_rule(::EmptyCategory, l1::LabelledInteger, l2::LabelledInteger) +function fusion_rule(::EmptyCategoryStyle, l1::LabelledInteger, l2::LabelledInteger) return labelled(l1 * l2, sector()) end @@ -97,7 +97,7 @@ function GradedAxes.fuse_blocklengths( end function GradedAxes.fuse_blocklengths( - ::EmptyCategory, l1::LabelledInteger, l2::LabelledInteger + ::EmptyCategoryStyle, l1::LabelledInteger, l2::LabelledInteger ) return labelled(l1 * l2, sector()) end diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 55bbdebec6..42dd006c34 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -15,11 +15,13 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -const EmptyCategoryProduct = CategoryProduct{Tuple{}} +const EmptyCategory = CategoryProduct{Tuple{}} # ================================= Sectors interface ==================================== function SymmetryStyle(c::CategoryProduct) - return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategory()) + return reduce( + combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategoryStyle() + ) end function quantum_dimension(::NonAbelianGroup, s::CategoryProduct) @@ -151,6 +153,13 @@ end # ==================================== Fusion rules ====================================== # generic case: fusion returns a GradedAxes, even for fusion with Empty +function fusion_rule(style::SymmetryStyle, c1::CategoryProduct, c2::AbstractCategory) + return fusion_rule(style, c1, CategoryProduct(c2)) +end +function fusion_rule(style::SymmetryStyle, c1::AbstractCategory, c2::CategoryProduct) + return fusion_rule(style, CategoryProduct(c1), c2) +end + function fusion_rule(::SymmetryStyle, s1::CategoryProduct, s2::CategoryProduct) return to_gradedrange(categories_fusion_rule(categories(s1), categories(s2))) end @@ -161,29 +170,29 @@ function fusion_rule(::AbelianGroup, s1::CategoryProduct, s2::CategoryProduct) end # Empty case -function fusion_rule(::EmptyCategory, ::EmptyCategoryProduct, ::EmptyCategoryProduct) +function fusion_rule(::EmptyCategoryStyle, ::EmptyCategory, ::EmptyCategory) return sector() end -# EmptyCategory acts as trivial on any AbstractCategory, not just CategoryProduct -function fusion_rule(::SymmetryStyle, ::EmptyCategoryProduct, c::AbstractCategory) +# EmptyCategoryStyle acts as trivial on any AbstractCategory, not just CategoryProduct +function fusion_rule(::SymmetryStyle, ::EmptyCategory, c::AbstractCategory) return to_gradedrange(c) end -function fusion_rule(::SymmetryStyle, ::EmptyCategoryProduct, c::CategoryProduct) +function fusion_rule(::SymmetryStyle, c::AbstractCategory, ::EmptyCategory) return to_gradedrange(c) end -function fusion_rule(::SymmetryStyle, c::AbstractCategory, ::EmptyCategoryProduct) +function fusion_rule(::SymmetryStyle, ::EmptyCategory, c::CategoryProduct) return to_gradedrange(c) end -function fusion_rule(::SymmetryStyle, c::CategoryProduct, ::EmptyCategoryProduct) +function fusion_rule(::SymmetryStyle, c::CategoryProduct, ::EmptyCategory) return to_gradedrange(c) end # abelian case: return Category -fusion_rule(::AbelianGroup, ::EmptyCategoryProduct, c::AbstractCategory) = c -fusion_rule(::AbelianGroup, ::EmptyCategoryProduct, c::CategoryProduct) = c -fusion_rule(::AbelianGroup, c::AbstractCategory, ::EmptyCategoryProduct) = c -fusion_rule(::AbelianGroup, c::CategoryProduct, ::EmptyCategoryProduct) = c +fusion_rule(::AbelianGroup, c::AbstractCategory, ::EmptyCategory) = c +fusion_rule(::AbelianGroup, ::EmptyCategory, c::AbstractCategory) = c +fusion_rule(::AbelianGroup, c::CategoryProduct, ::EmptyCategory) = c +fusion_rule(::AbelianGroup, ::EmptyCategory, c::CategoryProduct) = c # =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) @@ -217,7 +226,7 @@ end CategoryProduct(; kws...) = CategoryProduct((; kws...)) -# avoid having 2 different kinds of EmptyCategory: cast empty NamedTuple to Tuple{} +# avoid having 2 different kinds of EmptyCategoryStyle: cast empty NamedTuple to Tuple{} CategoryProduct(::NamedTuple{()}) = CategoryProduct(()) function CategoryProduct(pairs::Pair...) diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index 944fc0fe28..32ec4bbbe2 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -8,7 +8,7 @@ abstract type SymmetryStyle end struct AbelianGroup <: SymmetryStyle end struct NonAbelianGroup <: SymmetryStyle end struct NonGroupCategory <: SymmetryStyle end -struct EmptyCategory <: SymmetryStyle end # CategoryProduct with zero category inside +struct EmptyCategoryStyle <: SymmetryStyle end # CategoryProduct with zero category inside combine_styles(::AbelianGroup, ::AbelianGroup) = AbelianGroup() combine_styles(::AbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() @@ -17,10 +17,10 @@ combine_styles(::NonAbelianGroup, ::AbelianGroup) = NonAbelianGroup() combine_styles(::NonAbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() combine_styles(::NonAbelianGroup, ::NonGroupCategory) = NonGroupCategory() combine_styles(::NonGroupCategory, ::SymmetryStyle) = NonGroupCategory() -combine_styles(::NonGroupCategory, ::EmptyCategory) = NonGroupCategory() -combine_styles(::EmptyCategory, s::SymmetryStyle) = s -combine_styles(s::SymmetryStyle, ::EmptyCategory) = s -combine_styles(::EmptyCategory, ::EmptyCategory) = EmptyCategory() +combine_styles(::NonGroupCategory, ::EmptyCategoryStyle) = NonGroupCategory() +combine_styles(::EmptyCategoryStyle, s::SymmetryStyle) = s +combine_styles(s::SymmetryStyle, ::EmptyCategoryStyle) = s +combine_styles(::EmptyCategoryStyle, ::EmptyCategoryStyle) = EmptyCategoryStyle() SymmetryStyle(l::LabelledInteger) = SymmetryStyle(label(l)) From c14422a08bf5bad11c3d124f7f7c950d73873cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 10:21:52 -0400 Subject: [PATCH 130/194] use only 2 symmetry styles: Abelian and NotAbelian --- .../src/lib/Sectors/src/abstractcategory.jl | 33 ++++++---------- .../Sectors/src/category_definitions/fib.jl | 4 +- .../Sectors/src/category_definitions/ising.jl | 4 +- .../Sectors/src/category_definitions/o2.jl | 4 +- .../Sectors/src/category_definitions/su.jl | 4 +- .../Sectors/src/category_definitions/su2k.jl | 2 +- .../Sectors/src/category_definitions/u1.jl | 2 +- .../Sectors/src/category_definitions/zn.jl | 2 +- .../src/lib/Sectors/src/category_product.jl | 39 +++++++------------ .../src/lib/Sectors/src/symmetry_style.jl | 19 ++------- 10 files changed, 39 insertions(+), 74 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 87d36959be..4b30b689f3 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -31,43 +31,38 @@ function category_label(c::AbstractCategory) end block_dimensions(g::AbstractUnitRange) = block_dimensions(SymmetryStyle(g), g) -block_dimensions(::AbelianGroup, g) = unlabel.(blocklengths(g)) -function block_dimensions(::SymmetryStyle, g) +block_dimensions(::AbelianStyle, g) = unlabel.(blocklengths(g)) +function block_dimensions(::NotAbelianStyle, g) return quantum_dimension.(blocklabels(g)) .* blocklengths(g) end quantum_dimension(x) = quantum_dimension(SymmetryStyle(x), x) -function quantum_dimension(::SymmetryStyle, c::AbstractCategory) +function quantum_dimension(::NotAbelianStyle, c::AbstractCategory) return error("method `quantum_dimension` not defined for type $(typeof(c))") end -quantum_dimension(::AbelianGroup, ::AbstractCategory) = 1 -quantum_dimension(::EmptyCategoryStyle, ::AbstractCategory) = 1 -quantum_dimension(::SymmetryStyle, g::AbstractUnitRange) = sum(block_dimensions(g)) -quantum_dimension(::AbelianGroup, g::AbstractUnitRange) = length(g) +quantum_dimension(::AbelianStyle, ::AbstractCategory) = 1 +quantum_dimension(::AbelianStyle, g::AbstractUnitRange) = length(g) +quantum_dimension(::NotAbelianStyle, g::AbstractUnitRange) = sum(block_dimensions(g)) # =============================== Fusion rule interface ================================== ⊗(c1::AbstractCategory, c2::AbstractCategory) = fusion_rule(c1, c2) -function fusion_rule(c1, c2) +function fusion_rule(c1::AbstractCategory, c2::AbstractCategory) return fusion_rule(combine_styles(SymmetryStyle(c1), SymmetryStyle(c2)), c1, c2) end -function fusion_rule(::SymmetryStyle, c1::C, c2::C) where {C<:AbstractCategory} +function fusion_rule(::NotAbelianStyle, c1::C, c2::C) where {C<:AbstractCategory} degen, labels = label_fusion_rule(C, category_label(c1), category_label(c2)) return gradedrange(labelled.(degen, C.(labels))) end # abelian case: return Category -function fusion_rule(::AbelianGroup, c1::C, c2::C) where {C<:AbstractCategory} +function fusion_rule(::AbelianStyle, c1::C, c2::C) where {C<:AbstractCategory} return C(label_fusion_rule(C, category_label(c1), category_label(c2))) end -function fusion_rule(::EmptyCategoryStyle, l1::LabelledInteger, l2::LabelledInteger) - return labelled(l1 * l2, sector()) -end - function label_fusion_rule(category_type::Type{<:AbstractCategory}, ::Any, ::Any) return error("`label_fusion_rule` not defined for type $(category_type).") end @@ -82,7 +77,7 @@ function GradedAxes.fuse_blocklengths( end function GradedAxes.fuse_blocklengths( - ::SymmetryStyle, l1::LabelledInteger, l2::LabelledInteger + ::NotAbelianStyle, l1::LabelledInteger, l2::LabelledInteger ) fused = label(l1) ⊗ label(l2) v = labelled.(l1 * l2 .* blocklengths(fused), blocklabels(fused)) @@ -90,18 +85,12 @@ function GradedAxes.fuse_blocklengths( end function GradedAxes.fuse_blocklengths( - ::AbelianGroup, l1::LabelledInteger, l2::LabelledInteger + ::AbelianStyle, l1::LabelledInteger, l2::LabelledInteger ) fused = label(l1) ⊗ label(l2) return labelled(l1 * l2, fused) end -function GradedAxes.fuse_blocklengths( - ::EmptyCategoryStyle, l1::LabelledInteger, l2::LabelledInteger -) - return labelled(l1 * l2, sector()) -end - # cast to range to_gradedrange(c::AbstractCategory) = to_gradedrange(labelled(1, c)) to_gradedrange(l::LabelledInteger) = gradedrange([l]) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index 45b2180171..57a2bd9c81 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -19,7 +19,7 @@ function Fib(s::AbstractString) return error("Unrecognized input \"$s\" to Fib constructor") end -SymmetryStyle(::Fib) = NonGroupCategory() +SymmetryStyle(::Fib) = NotAbelianStyle() GradedAxes.dual(f::Fib) = f @@ -27,7 +27,7 @@ category_label(f::Fib) = f.l trivial(::Type{Fib}) = Fib(0) -quantum_dimension(::NonGroupCategory, f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) +quantum_dimension(::NotAbelianStyle, f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) # Fusion rules identical to su2₃ label_fusion_rule(::Type{Fib}, l1, l2) = label_fusion_rule(su2{3}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index 7ce3bcef7d..2dcf890ecc 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -19,7 +19,7 @@ function Ising(s::AbstractString) return error("Unrecognized input \"$s\" to Ising constructor") end -SymmetryStyle(::Ising) = NonGroupCategory() +SymmetryStyle(::Ising) = NotAbelianStyle() GradedAxes.dual(i::Ising) = i @@ -27,7 +27,7 @@ category_label(i::Ising) = i.l trivial(::Type{Ising}) = Ising(0) -quantum_dimension(::NonGroupCategory, i::Ising) = (category_label(i) == 1//2) ? √2 : 1.0 +quantum_dimension(::NotAbelianStyle, i::Ising) = (category_label(i) == 1//2) ? √2 : 1.0 # Fusion rules identical to su2₂ label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index 872f8cdd42..24433e03f5 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -20,7 +20,7 @@ struct O2 <: AbstractCategory l::Half{Int} end -SymmetryStyle(::O2) = NonAbelianGroup() +SymmetryStyle(::O2) = NotAbelianStyle() category_label(s::O2) = s.l @@ -34,7 +34,7 @@ is_zero_even_or_odd(l::HalfInteger) = iszero_even(l) || iszero_odd(l) iszero_even(l::HalfInteger) = l == category_label(trivial(O2)) iszero_odd(l::HalfInteger) = l == category_label(zero_odd(O2)) -quantum_dimension(::NonAbelianGroup, s::O2) = 2 - is_zero_even_or_odd(s) +quantum_dimension(::NotAbelianStyle, s::O2) = 2 - is_zero_even_or_odd(s) GradedAxes.dual(s::O2) = s diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 880a2780f0..4a9c41901f 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -23,7 +23,7 @@ end SU{N}(t::Tuple) where {N} = SU{N,length(t)}(t) SU(t::Tuple) = SU{length(t) + 1}(t) # infer N from tuple length -SymmetryStyle(::SU) = NonAbelianGroup() +SymmetryStyle(::SU) = NotAbelianStyle() category_label(s::SU) = s.l @@ -35,7 +35,7 @@ fundamental(::Type{<:SU{N}}) where {N} = SU{N}(ntuple(i -> i == 1, Val(N - 1))) adjoint(::Type{<:SU{N}}) where {N} = SU{N}((ntuple(i -> 1 + (i == 1), Val(N - 1)))) -function quantum_dimension(::NonAbelianGroup, s::SU) +function quantum_dimension(::NotAbelianStyle, s::SU) N = groupdim(s) l = (category_label(s)..., 0) d = 1 diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index ffcf4c5550..e428294834 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -9,7 +9,7 @@ struct su2{k} <: AbstractCategory j::Half{Int} end -SymmetryStyle(::su2) = NonGroupCategory() +SymmetryStyle(::su2) = NotAbelianStyle() GradedAxes.dual(s::su2) = s diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 672fe0655d..031e24724a 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -10,7 +10,7 @@ struct U1{T} <: AbstractCategory n::T end -SymmetryStyle(::U1) = AbelianGroup() +SymmetryStyle(::U1) = AbelianStyle() GradedAxes.dual(u::U1) = U1(-u.n) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 25299703e3..124d177401 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -9,7 +9,7 @@ struct Z{N} <: AbstractCategory Z{N}(m) where {N} = new{N}(m % N) end -SymmetryStyle(::Z) = AbelianGroup() +SymmetryStyle(::Z) = AbelianStyle() category_label(c::Z) = c.m modulus(::Type{Z{N}}) where {N} = N diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 42dd006c34..921bae1fc6 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -19,16 +19,10 @@ const EmptyCategory = CategoryProduct{Tuple{}} # ================================= Sectors interface ==================================== function SymmetryStyle(c::CategoryProduct) - return reduce( - combine_styles, map(SymmetryStyle, categories(c)); init=EmptyCategoryStyle() - ) -end - -function quantum_dimension(::NonAbelianGroup, s::CategoryProduct) - return prod(map(quantum_dimension, categories(s))) + return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=AbelianStyle()) end -function quantum_dimension(::NonGroupCategory, s::CategoryProduct) +function quantum_dimension(::NotAbelianStyle, s::CategoryProduct) return prod(map(quantum_dimension, categories(s))) end @@ -153,46 +147,39 @@ end # ==================================== Fusion rules ====================================== # generic case: fusion returns a GradedAxes, even for fusion with Empty -function fusion_rule(style::SymmetryStyle, c1::CategoryProduct, c2::AbstractCategory) - return fusion_rule(style, c1, CategoryProduct(c2)) -end -function fusion_rule(style::SymmetryStyle, c1::AbstractCategory, c2::CategoryProduct) - return fusion_rule(style, CategoryProduct(c1), c2) -end - -function fusion_rule(::SymmetryStyle, s1::CategoryProduct, s2::CategoryProduct) +function fusion_rule(::NotAbelianStyle, s1::CategoryProduct, s2::CategoryProduct) return to_gradedrange(categories_fusion_rule(categories(s1), categories(s2))) end # Abelian case: fusion returns CategoryProduct -function fusion_rule(::AbelianGroup, s1::CategoryProduct, s2::CategoryProduct) +function fusion_rule(::AbelianStyle, s1::CategoryProduct, s2::CategoryProduct) return categories_fusion_rule(categories(s1), categories(s2)) end # Empty case -function fusion_rule(::EmptyCategoryStyle, ::EmptyCategory, ::EmptyCategory) +function fusion_rule(::AbelianStyle, ::EmptyCategory, ::EmptyCategory) return sector() end # EmptyCategoryStyle acts as trivial on any AbstractCategory, not just CategoryProduct -function fusion_rule(::SymmetryStyle, ::EmptyCategory, c::AbstractCategory) +function fusion_rule(::NotAbelianStyle, ::EmptyCategory, c::AbstractCategory) return to_gradedrange(c) end -function fusion_rule(::SymmetryStyle, c::AbstractCategory, ::EmptyCategory) +function fusion_rule(::NotAbelianStyle, c::AbstractCategory, ::EmptyCategory) return to_gradedrange(c) end -function fusion_rule(::SymmetryStyle, ::EmptyCategory, c::CategoryProduct) +function fusion_rule(::NotAbelianStyle, ::EmptyCategory, c::CategoryProduct) return to_gradedrange(c) end -function fusion_rule(::SymmetryStyle, c::CategoryProduct, ::EmptyCategory) +function fusion_rule(::NotAbelianStyle, c::CategoryProduct, ::EmptyCategory) return to_gradedrange(c) end # abelian case: return Category -fusion_rule(::AbelianGroup, c::AbstractCategory, ::EmptyCategory) = c -fusion_rule(::AbelianGroup, ::EmptyCategory, c::AbstractCategory) = c -fusion_rule(::AbelianGroup, c::CategoryProduct, ::EmptyCategory) = c -fusion_rule(::AbelianGroup, ::EmptyCategory, c::CategoryProduct) = c +fusion_rule(::AbelianStyle, c::AbstractCategory, ::EmptyCategory) = c +fusion_rule(::AbelianStyle, ::EmptyCategory, c::AbstractCategory) = c +fusion_rule(::AbelianStyle, c::CategoryProduct, ::EmptyCategory) = c +fusion_rule(::AbelianStyle, ::EmptyCategory, c::CategoryProduct) = c # =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index 32ec4bbbe2..ddf5fb8e27 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -5,22 +5,11 @@ using ..LabelledNumbers: LabelledInteger, label abstract type SymmetryStyle end -struct AbelianGroup <: SymmetryStyle end -struct NonAbelianGroup <: SymmetryStyle end -struct NonGroupCategory <: SymmetryStyle end -struct EmptyCategoryStyle <: SymmetryStyle end # CategoryProduct with zero category inside +struct AbelianStyle <: SymmetryStyle end +struct NotAbelianStyle <: SymmetryStyle end -combine_styles(::AbelianGroup, ::AbelianGroup) = AbelianGroup() -combine_styles(::AbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() -combine_styles(::AbelianGroup, ::NonGroupCategory) = NonGroupCategory() -combine_styles(::NonAbelianGroup, ::AbelianGroup) = NonAbelianGroup() -combine_styles(::NonAbelianGroup, ::NonAbelianGroup) = NonAbelianGroup() -combine_styles(::NonAbelianGroup, ::NonGroupCategory) = NonGroupCategory() -combine_styles(::NonGroupCategory, ::SymmetryStyle) = NonGroupCategory() -combine_styles(::NonGroupCategory, ::EmptyCategoryStyle) = NonGroupCategory() -combine_styles(::EmptyCategoryStyle, s::SymmetryStyle) = s -combine_styles(s::SymmetryStyle, ::EmptyCategoryStyle) = s -combine_styles(::EmptyCategoryStyle, ::EmptyCategoryStyle) = EmptyCategoryStyle() +combine_styles(::AbelianStyle, ::AbelianStyle) = AbelianStyle() +combine_styles(::SymmetryStyle, ::SymmetryStyle) = NotAbelianStyle() SymmetryStyle(l::LabelledInteger) = SymmetryStyle(label(l)) From a566784d00cbeb37fdb84c53063280181d8ba880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 10:28:19 -0400 Subject: [PATCH 131/194] rename EmptyCategory to TrivialSector --- .../src/lib/Sectors/src/category_product.jl | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 921bae1fc6..f7129b3be5 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -15,7 +15,7 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -const EmptyCategory = CategoryProduct{Tuple{}} +const TrivialSector = CategoryProduct{Tuple{}} # ================================= Sectors interface ==================================== function SymmetryStyle(c::CategoryProduct) @@ -157,29 +157,29 @@ function fusion_rule(::AbelianStyle, s1::CategoryProduct, s2::CategoryProduct) end # Empty case -function fusion_rule(::AbelianStyle, ::EmptyCategory, ::EmptyCategory) +function fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) return sector() end -# EmptyCategoryStyle acts as trivial on any AbstractCategory, not just CategoryProduct -function fusion_rule(::NotAbelianStyle, ::EmptyCategory, c::AbstractCategory) +# TrivialSectorStyle acts as trivial on any AbstractCategory, not just CategoryProduct +function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::AbstractCategory) return to_gradedrange(c) end -function fusion_rule(::NotAbelianStyle, c::AbstractCategory, ::EmptyCategory) +function fusion_rule(::NotAbelianStyle, c::AbstractCategory, ::TrivialSector) return to_gradedrange(c) end -function fusion_rule(::NotAbelianStyle, ::EmptyCategory, c::CategoryProduct) +function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::CategoryProduct) return to_gradedrange(c) end -function fusion_rule(::NotAbelianStyle, c::CategoryProduct, ::EmptyCategory) +function fusion_rule(::NotAbelianStyle, c::CategoryProduct, ::TrivialSector) return to_gradedrange(c) end # abelian case: return Category -fusion_rule(::AbelianStyle, c::AbstractCategory, ::EmptyCategory) = c -fusion_rule(::AbelianStyle, ::EmptyCategory, c::AbstractCategory) = c -fusion_rule(::AbelianStyle, c::CategoryProduct, ::EmptyCategory) = c -fusion_rule(::AbelianStyle, ::EmptyCategory, c::CategoryProduct) = c +fusion_rule(::AbelianStyle, c::AbstractCategory, ::TrivialSector) = c +fusion_rule(::AbelianStyle, ::TrivialSector, c::AbstractCategory) = c +fusion_rule(::AbelianStyle, c::CategoryProduct, ::TrivialSector) = c +fusion_rule(::AbelianStyle, ::TrivialSector, c::CategoryProduct) = c # =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) @@ -213,7 +213,7 @@ end CategoryProduct(; kws...) = CategoryProduct((; kws...)) -# avoid having 2 different kinds of EmptyCategoryStyle: cast empty NamedTuple to Tuple{} +# avoid having 2 different kinds of TrivialSector: cast empty NamedTuple to Tuple{} CategoryProduct(::NamedTuple{()}) = CategoryProduct(()) function CategoryProduct(pairs::Pair...) From 3ba966e411ef3d69075e2030afd1f90115ff5a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 10:32:04 -0400 Subject: [PATCH 132/194] avoid sector --- NDTensors/src/lib/Sectors/src/category_product.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index f7129b3be5..b3d8d13424 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -29,7 +29,9 @@ end # use map instead of broadcast to support both Tuple and NamedTuple GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(dual, categories(s))) -trivial(type::Type{<:CategoryProduct}) = sector(categories_trivial(categories_type(type))) +function trivial(type::Type{<:CategoryProduct}) + return CategoryProduct(categories_trivial(categories_type(type))) +end # =================================== Base interface ===================================== function Base.:(==)(A::CategoryProduct, B::CategoryProduct) @@ -158,7 +160,7 @@ end # Empty case function fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) - return sector() + return CategoryProduct(()) end # TrivialSectorStyle acts as trivial on any AbstractCategory, not just CategoryProduct From a7a860def93676677114e719dbae7c4570cbe26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 11:16:17 -0400 Subject: [PATCH 133/194] add commennt --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index c13c4d2b3d..cf4b997488 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -5,7 +5,7 @@ struct OneToOne{T} <: AbstractUnitRange{T} end OneToOne() = OneToOne{Bool}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) -BlockArrays.blockaxes(g::OneToOne) = (Block.(g),) +BlockArrays.blockaxes(g::OneToOne) = (Block.(g),) # BlockArrays default crashes for OneToOne{Bool} # https://github.com/ITensor/ITensors.jl/blob/v0.3.57/NDTensors/src/lib/GradedAxes/src/tensor_product.jl # https://en.wikipedia.org/wiki/Tensor_product From 0089c587c54ae13ce64c00dc069bdbe935c37c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 11:27:05 -0400 Subject: [PATCH 134/194] use NotAbelianStyle in categories_fusion_rule --- .../src/lib/Sectors/src/category_product.jl | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index b3d8d13424..ee660afd1a 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -79,20 +79,23 @@ end function shared_categories_fusion_rule(shared1, shared2) fused = map(fusion_rule, values(shared1), values(shared2)) - factorized = factorize_gradedaxes(fused) - type_fixed = recover_category_product_type(typeof(shared1), factorized) - return type_fixed + return recover_style(typeof(shared1), fused) end -# abelian case: there is no gradedaxis -factorize_gradedaxes(fused::Tuple{Vararg{AbstractCategory}}) = fused +function recover_style(T::Type, fused) + style = reduce(combine_styles, SymmetryStyle.(fused); init=AbelianStyle()) + return recover_category_product_type(style, T, fused) +end -# non-abelian case -function factorize_gradedaxes(fused::Tuple) - # here fused contains at least one GradedOneTo - g0 = reduce(×, fused) +function recover_category_product_type(::AbelianStyle, T::Type, fused) + return recover_category_product_type(T, fused) +end + +function recover_category_product_type(::NotAbelianStyle, T::Type, fused) + factorized = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × - return g0 + type_fixed = recover_category_product_type(T, factorized) + return type_fixed end function recover_category_product_type(T::Type, g0::AbstractGradedUnitRange) From 168c1ceb273752c57f3ccd1fa52402f02ff48a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 11:47:42 -0400 Subject: [PATCH 135/194] better variable name --- NDTensors/src/lib/Sectors/src/category_product.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index ee660afd1a..9f83217cfc 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -92,9 +92,9 @@ function recover_category_product_type(::AbelianStyle, T::Type, fused) end function recover_category_product_type(::NotAbelianStyle, T::Type, fused) - factorized = reduce(×, fused) + g = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × - type_fixed = recover_category_product_type(T, factorized) + type_fixed = recover_category_product_type(T, g) return type_fixed end From 843fc2d5573bacf746f29f9e84cd64b034e3e72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 11:50:13 -0400 Subject: [PATCH 136/194] impose same type in shared --- NDTensors/src/lib/Sectors/src/category_product.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 9f83217cfc..b1e07fec68 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -77,9 +77,9 @@ function categories_fusion_rule(cats1, cats2) return shared_cat × diff_cat end -function shared_categories_fusion_rule(shared1, shared2) +function shared_categories_fusion_rule(shared1::T, shared2::T) where {T} fused = map(fusion_rule, values(shared1), values(shared2)) - return recover_style(typeof(shared1), fused) + return recover_style(T, fused) end function recover_style(T::Type, fused) From 7edaf038e89a5132ce0eb9ec11d6b05ea3c5786b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 12:02:03 -0400 Subject: [PATCH 137/194] remove sector, define TrivialSector() --- .../src/lib/Sectors/src/category_product.jl | 3 +- .../lib/Sectors/test/test_category_product.jl | 273 +++++++++--------- 2 files changed, 142 insertions(+), 134 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index b1e07fec68..a3f46853f0 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -16,6 +16,7 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats const TrivialSector = CategoryProduct{Tuple{}} +TrivialSector() = CategoryProduct(()) # ================================= Sectors interface ==================================== function SymmetryStyle(c::CategoryProduct) @@ -122,8 +123,6 @@ function recover_category_product_type(T::Type, cats::Tuple{Vararg{AbstractCateg return CategoryProduct(T(cats)) end -sector(args...; kws...) = CategoryProduct(args...; kws...) - # ================================= Cartesian Product ==================================== ×(c1::AbstractCategory, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) function ×(p1::CategoryProduct, p2::CategoryProduct) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index ba0ca2c37f..5edf8e4b24 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -2,17 +2,18 @@ using NDTensors.Sectors: ×, ⊗, + CategoryProduct, Fib, Ising, SU, SU2, U1, + TrivialSector, Z, block_dimensions, categories, quantum_dimension, recover_category_product_type, - sector, trivial using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange using Test: @inferred, @test, @testset, @test_throws @@ -26,20 +27,20 @@ end @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin - s = sector(U1(1)) + s = CategoryProduct(U1(1)) @test length(categories(s)) == 1 @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == sector(U1(-1)) + @test (@inferred dual(s)) == CategoryProduct(U1(-1)) @test categories(s)[1] == U1(1) - @test (@inferred_latest trivial(s)) == sector(U1(0)) + @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0)) - s = sector(U1(1), U1(2)) + s = CategoryProduct(U1(1), U1(2)) @test length(categories(s)) == 2 @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == sector(U1(-1), U1(-2)) + @test (@inferred dual(s)) == CategoryProduct(U1(-1), U1(-2)) @test categories(s)[1] == U1(1) @test categories(s)[2] == U1(2) - @test (@inferred_latest trivial(s)) == sector(U1(0), U1(0)) + @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) @test length(categories(s)) == 3 @@ -48,7 +49,7 @@ end @test categories(s)[1] == U1(1) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == U1(3) - @test (@inferred_latest trivial(s)) == sector(U1(0), SU2(0), U1(0)) + @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0), SU2(0), U1(0)) @test (@inferred recover_category_product_type(typeof(categories(s)), categories(s))) == s @test (@inferred recover_category_product_type(typeof(s), categories(s))) == s @@ -60,20 +61,20 @@ end @test categories(s)[1] == U1(3) @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") - @test (@inferred_latest trivial(s)) == sector(U1(0), SU2(0), Fib("1")) + @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0), SU2(0), Fib("1")) end @testset "Ordered comparisons" begin # convention: categories must have same length to evaluate as equal - @test sector(U1(1), SU2(1)) == sector(U1(1), SU2(1)) - @test sector(U1(1), SU2(0)) != sector(U1(1), SU2(1)) - @test sector(U1(0), SU2(1)) != sector(U1(1), SU2(1)) - @test sector(U1(1)) != sector(U1(1), U1(0)) + @test CategoryProduct(U1(1), SU2(1)) == CategoryProduct(U1(1), SU2(1)) + @test CategoryProduct(U1(1), SU2(0)) != CategoryProduct(U1(1), SU2(1)) + @test CategoryProduct(U1(0), SU2(1)) != CategoryProduct(U1(1), SU2(1)) + @test CategoryProduct(U1(1)) != CategoryProduct(U1(1), U1(0)) # convention: categories must have same length to be compared - @test sector(U1(0)) < sector((U1(1))) - @test sector(U1(0), U1(2)) < sector((U1(1)), U1(0)) - @test_throws ArgumentError sector(U1(0)) < sector(U1(1), U1(2)) + @test CategoryProduct(U1(0)) < CategoryProduct((U1(1))) + @test CategoryProduct(U1(0), U1(2)) < CategoryProduct((U1(1)), U1(0)) + @test_throws ArgumentError CategoryProduct(U1(0)) < CategoryProduct(U1(1), U1(2)) end @testset "Quantum dimension and GradedUnitRange" begin @@ -132,9 +133,9 @@ end end @testset "Fusion of Abelian products" begin - p1 = sector(U1(1)) - p2 = sector(U1(2)) - @test (@inferred p1 ⊗ p2) == sector(U1(3)) + p1 = CategoryProduct(U1(1)) + p2 = CategoryProduct(U1(2)) + @test (@inferred p1 ⊗ p2) == CategoryProduct(U1(3)) p11 = U1(1) × U1(1) @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) @@ -142,15 +143,15 @@ end p123 = U1(1) × U1(2) × U1(3) @test (@inferred p123 ⊗ p123) == U1(2) × U1(4) × U1(6) - s1 = sector(U1(1), Z{2}(1)) - s2 = sector(U1(0), Z{2}(0)) + s1 = CategoryProduct(U1(1), Z{2}(1)) + s2 = CategoryProduct(U1(0), Z{2}(0)) @test (@inferred s1 ⊗ s2) == U1(1) × Z{2}(1) end @testset "Fusion of NonAbelian products" begin - p0 = sector(SU2(0)) - ph = sector(SU2(1//2)) - @test space_isequal((@inferred p0 ⊗ ph), gradedrange([sector(SU2(1//2)) => 1])) + p0 = CategoryProduct(SU2(0)) + ph = CategoryProduct(SU2(1//2)) + @test space_isequal((@inferred p0 ⊗ ph), gradedrange([CategoryProduct(SU2(1//2)) => 1])) phh = SU2(1//2) × SU2(1//2) @test space_isequal( @@ -240,19 +241,20 @@ end end @testset "Fusion of different length Categories" begin - @test sector(U1(1) × U1(0)) ⊗ sector(U1(1)) == sector(U1(2) × U1(0)) + @test CategoryProduct(U1(1) × U1(0)) ⊗ CategoryProduct(U1(1)) == + CategoryProduct(U1(2) × U1(0)) @test space_isequal( - (@inferred sector(SU2(0) × SU2(0)) ⊗ sector(SU2(1))), - gradedrange([sector(SU2(1) × SU2(0)) => 1]), + (@inferred CategoryProduct(SU2(0) × SU2(0)) ⊗ CategoryProduct(SU2(1))), + gradedrange([CategoryProduct(SU2(1) × SU2(0)) => 1]), ) @test space_isequal( - (@inferred sector(SU2(1) × U1(1)) ⊗ sector(SU2(0))), - gradedrange([sector(SU2(1) × U1(1)) => 1]), + (@inferred CategoryProduct(SU2(1) × U1(1)) ⊗ CategoryProduct(SU2(0))), + gradedrange([CategoryProduct(SU2(1) × U1(1)) => 1]), ) @test space_isequal( - (@inferred sector(U1(1) × SU2(1)) ⊗ sector(U1(2))), - gradedrange([sector(U1(3) × SU2(1)) => 1]), + (@inferred CategoryProduct(U1(1) × SU2(1)) ⊗ CategoryProduct(U1(2))), + gradedrange([CategoryProduct(U1(3) × SU2(1)) => 1]), ) # check incompatible categories @@ -308,15 +310,15 @@ end end @testset "Construct from Pairs" begin - s = sector("A" => U1(2)) + s = CategoryProduct("A" => U1(2)) @test length(categories(s)) == 1 @test categories(s)[:A] == U1(2) - @test s == sector(; A=U1(2)) + @test s == CategoryProduct(; A=U1(2)) @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == sector("A" => U1(-2)) - @test (@inferred_latest trivial(s)) == sector(; A=U1(0)) + @test (@inferred dual(s)) == CategoryProduct("A" => U1(-2)) + @test (@inferred_latest trivial(s)) == CategoryProduct(; A=U1(0)) - s = sector("B" => Ising("ψ"), :C => Z{2}(1)) + s = CategoryProduct("B" => Ising("ψ"), :C => Z{2}(1)) @test length(categories(s)) == 2 @test categories(s)[:B] == Ising("ψ") @test categories(s)[:C] == Z{2}(1) @@ -326,7 +328,7 @@ end @testset "Comparisons with unspecified labels" begin # convention: categories evaluate as equal if unmatched labels are trivial # this is different from ordered tuple convention - q2 = sector(; N=U1(2)) + q2 = CategoryProduct(; N=U1(2)) q20 = (N=U1(2),) × (J=SU2(0),) @test q20 == q2 @test !(q20 < q2) @@ -345,82 +347,85 @@ end end @testset "Quantum dimension and GradedUnitRange" begin - g = gradedrange([sector(; A=U1(0), B=Z{2}(0)) => 1, sector(; A=U1(1), B=Z{2}(0)) => 2]) # abelian + g = gradedrange([ + CategoryProduct(; A=U1(0), B=Z{2}(0)) => 1, CategoryProduct(; A=U1(1), B=Z{2}(0)) => 2 + ]) # abelian @test (@inferred quantum_dimension(g)) == 3 g = gradedrange([ # non-abelian - sector(; A=SU2(0), B=SU2(0)) => 1, - sector(; A=SU2(1), B=SU2(0)) => 1, - sector(; A=SU2(0), B=SU2(1)) => 1, - sector(; A=SU2(1), B=SU2(1)) => 1, + CategoryProduct(; A=SU2(0), B=SU2(0)) => 1, + CategoryProduct(; A=SU2(1), B=SU2(0)) => 1, + CategoryProduct(; A=SU2(0), B=SU2(1)) => 1, + CategoryProduct(; A=SU2(1), B=SU2(1)) => 1, ]) @test (@inferred quantum_dimension(g)) == 16 # mixed group g = gradedrange([ - sector(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, - sector(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, + CategoryProduct(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, + CategoryProduct(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4 g = gradedrange([ - sector(; A=SU2(0), B=Z{2}(0), C=SU2(1//2)) => 1, - sector(; A=SU2(0), B=Z{2}(1), C=SU2(1//2)) => 1, + CategoryProduct(; A=SU2(0), B=Z{2}(0), C=SU2(1//2)) => 1, + CategoryProduct(; A=SU2(0), B=Z{2}(1), C=SU2(1//2)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4 # non group categories - g_fib = gradedrange([sector(; A=Fib("1"), B=Fib("1")) => 1]) - g_ising = gradedrange([sector(; A=Ising("1"), B=Ising("1")) => 1]) + g_fib = gradedrange([CategoryProduct(; A=Fib("1"), B=Fib("1")) => 1]) + g_ising = gradedrange([CategoryProduct(; A=Ising("1"), B=Ising("1")) => 1]) @test (@inferred quantum_dimension(g_fib)) == 1.0 @test (@inferred quantum_dimension(g_ising)) == 1.0 # mixed product Abelian / NonAbelian / NonGroup g = gradedrange([ - sector(; A=U1(2), B=SU2(0), C=Ising(1)) => 1, - sector(; A=U1(2), B=SU2(1), C=Ising(1)) => 1, - sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, - sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, + CategoryProduct(; A=U1(2), B=SU2(0), C=Ising(1)) => 1, + CategoryProduct(; A=U1(2), B=SU2(1), C=Ising(1)) => 1, + CategoryProduct(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, + CategoryProduct(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]) @test (@inferred_latest quantum_dimension(g)) == 8.0 g = gradedrange([ - sector(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, - sector(; A=Fib("1"), B=SU2(1), C=U1(2)) => 1, - sector(; A=Fib("τ"), B=SU2(0), C=U1(2)) => 1, - sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1, + CategoryProduct(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, + CategoryProduct(; A=Fib("1"), B=SU2(1), C=U1(2)) => 1, + CategoryProduct(; A=Fib("τ"), B=SU2(0), C=U1(2)) => 1, + CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end @testset "Fusion of Abelian products" begin - q00 = sector() - q10 = sector(; A=U1(1)) - q01 = sector(; B=U1(1)) - q11 = sector(; A=U1(1), B=U1(1)) + q00 = TrivialSector() + q10 = CategoryProduct(; A=U1(1)) + q01 = CategoryProduct(; B=U1(1)) + q11 = CategoryProduct(; A=U1(1), B=U1(1)) - @test (@inferred q10 ⊗ q10) == sector(; A=U1(2)) + @test (@inferred q10 ⊗ q10) == CategoryProduct(; A=U1(2)) @test (@inferred q01 ⊗ q00) == q01 @test (@inferred q00 ⊗ q01) == q01 @test (@inferred q10 ⊗ q01) == q11 - @test (@inferred q11 ⊗ q11) == sector(; A=U1(2), B=U1(2)) + @test (@inferred q11 ⊗ q11) == CategoryProduct(; A=U1(2), B=U1(2)) - s11 = sector(; A=U1(1), B=Z{2}(1)) - s10 = sector(; A=U1(1)) - s01 = sector(; B=Z{2}(1)) + s11 = CategoryProduct(; A=U1(1), B=Z{2}(1)) + s10 = CategoryProduct(; A=U1(1)) + s01 = CategoryProduct(; B=Z{2}(1)) @test (@inferred s01 ⊗ q00) == s01 @test (@inferred q00 ⊗ s01) == s01 @test (@inferred s10 ⊗ s01) == s11 - @test (@inferred s11 ⊗ s11) == sector(; A=U1(2), B=Z{2}(0)) + @test (@inferred s11 ⊗ s11) == CategoryProduct(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin - p0 = sector() - pha = sector(; A=SU2(1//2)) - phb = sector(; B=SU2(1//2)) - phab = sector(; A=SU2(1//2), B=SU2(1//2)) + p0 = TrivialSector() + pha = CategoryProduct(; A=SU2(1//2)) + phb = CategoryProduct(; B=SU2(1//2)) + phab = CategoryProduct(; A=SU2(1//2), B=SU2(1//2)) @test space_isequal( - (@inferred pha ⊗ pha), gradedrange([sector(; A=SU2(0)) => 1, sector(; A=SU2(1)) => 1]) + (@inferred pha ⊗ pha), + gradedrange([CategoryProduct(; A=SU2(0)) => 1, CategoryProduct(; A=SU2(1)) => 1]), ) @test space_isequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) @test space_isequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) @@ -429,10 +434,10 @@ end @test space_isequal( (@inferred phab ⊗ phab), gradedrange([ - sector(; A=SU2(0), B=SU2(0)) => 1, - sector(; A=SU2(1), B=SU2(0)) => 1, - sector(; A=SU2(0), B=SU2(1)) => 1, - sector(; A=SU2(1), B=SU2(1)) => 1, + CategoryProduct(; A=SU2(0), B=SU2(0)) => 1, + CategoryProduct(; A=SU2(1), B=SU2(0)) => 1, + CategoryProduct(; A=SU2(0), B=SU2(1)) => 1, + CategoryProduct(; A=SU2(1), B=SU2(1)) => 1, ]), ) end @@ -440,34 +445,34 @@ end @testset "Fusion of NonGroupCategory products" begin ı = Fib("1") τ = Fib("τ") - s = sector(; A=ı, B=ı) + s = CategoryProduct(; A=ı, B=ı) @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) - s = sector(; A=τ, B=τ) + s = CategoryProduct(; A=τ, B=τ) @test space_isequal( (@inferred s ⊗ s), gradedrange([ - sector(; A=ı, B=ı) => 1, - sector(; A=τ, B=ı) => 1, - sector(; A=ı, B=τ) => 1, - sector(; A=τ, B=τ) => 1, + CategoryProduct(; A=ı, B=ı) => 1, + CategoryProduct(; A=τ, B=ı) => 1, + CategoryProduct(; A=ı, B=τ) => 1, + CategoryProduct(; A=τ, B=τ) => 1, ]), ) σ = Ising("σ") ψ = Ising("ψ") - s = sector(; A=τ, B=σ) + s = CategoryProduct(; A=τ, B=σ) g = gradedrange([ - sector(; A=ı, B=Ising("1")) => 1, - sector(; A=τ, B=Ising("1")) => 1, - sector(; A=ı, B=ψ) => 1, - sector(; A=τ, B=ψ) => 1, + CategoryProduct(; A=ı, B=Ising("1")) => 1, + CategoryProduct(; A=τ, B=Ising("1")) => 1, + CategoryProduct(; A=ı, B=ψ) => 1, + CategoryProduct(; A=τ, B=ψ) => 1, ]) @test space_isequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin - q0h = sector(; J=SU2(1//2)) + q0h = CategoryProduct(; J=SU2(1//2)) q10 = (N=U1(1),) × (J=SU2(0),) # Put names in reverse order sometimes: q1h = (J=SU2(1//2),) × (N=U1(1),) @@ -484,50 +489,52 @@ end end @testset "Fusion of fully mixed products" begin - s = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) + s = CategoryProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) @test space_isequal( (@inferred s ⊗ s), gradedrange([ - sector(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, - sector(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, - sector(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, - sector(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, + CategoryProduct(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, + CategoryProduct(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, + CategoryProduct(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, + CategoryProduct(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]), ) ı = Fib("1") τ = Fib("τ") - s = sector(; A=SU2(1//2), B=U1(1), C=τ) + s = CategoryProduct(; A=SU2(1//2), B=U1(1), C=τ) @test space_isequal( (@inferred s ⊗ s), gradedrange([ - sector(; A=SU2(0), B=U1(2), C=ı) => 1, - sector(; A=SU2(1), B=U1(2), C=ı) => 1, - sector(; A=SU2(0), B=U1(2), C=τ) => 1, - sector(; A=SU2(1), B=U1(2), C=τ) => 1, + CategoryProduct(; A=SU2(0), B=U1(2), C=ı) => 1, + CategoryProduct(; A=SU2(1), B=U1(2), C=ı) => 1, + CategoryProduct(; A=SU2(0), B=U1(2), C=τ) => 1, + CategoryProduct(; A=SU2(1), B=U1(2), C=τ) => 1, ]), ) - s = sector(; A=τ, B=U1(1), C=ı) + s = CategoryProduct(; A=τ, B=U1(1), C=ı) @test space_isequal( (@inferred s ⊗ s), - gradedrange([sector(; B=U1(2), A=ı, C=ı) => 1, sector(; B=U1(2), A=τ, C=ı) => 1]), + gradedrange([ + CategoryProduct(; B=U1(2), A=ı, C=ı) => 1, CategoryProduct(; B=U1(2), A=τ, C=ı) => 1 + ]), ) end @testset "GradedUnitRange fusion rules" begin - s1 = sector(; A=U1(1), B=SU2(1//2), C=Ising("σ")) - s2 = sector(; A=U1(0), B=SU2(1//2), C=Ising("1")) + s1 = CategoryProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) + s2 = CategoryProduct(; A=U1(0), B=SU2(1//2), C=Ising("1")) g1 = gradedrange([s1 => 2]) g2 = gradedrange([s2 => 1]) - s3 = sector(; A=U1(1), B=SU2(0), C=Ising("σ")) - s4 = sector(; A=U1(1), B=SU2(1), C=Ising("σ")) + s3 = CategoryProduct(; A=U1(1), B=SU2(0), C=Ising("σ")) + s4 = CategoryProduct(; A=U1(1), B=SU2(1), C=Ising("σ")) @test space_isequal( (@inferred_latest fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2]) ) - sA = sector(; A=U1(1)) - sB = sector(; B=SU2(1//2)) - sAB = sector(; A=U1(1), B=SU2(1//2)) + sA = CategoryProduct(; A=U1(1)) + sB = CategoryProduct(; B=SU2(1//2)) + sAB = CategoryProduct(; A=U1(1), B=SU2(1//2)) gA = gradedrange([sA => 2]) gB = gradedrange([sB => 1]) @test space_isequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) @@ -535,25 +542,25 @@ end end @testset "Empty category" begin - s = sector() + s = TrivialSector() @test s == s @test (@inferred dual(s)) == s @test (@inferred s × s) == s @test (@inferred s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 1 @test (@inferred_latest trivial(s)) == s - @test typeof(s) == typeof(sector(())) - @test typeof(s) == typeof(sector((;))) # empty NamedTuple is cast to Tuple{} + @test typeof(s) == typeof(CategoryProduct(())) + @test typeof(s) == typeof(CategoryProduct((;))) # empty NamedTuple is cast to Tuple{} g0 = gradedrange([s => 2]) @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) - @test (@inferred s × U1(1)) == sector(U1(1)) - @test (@inferred s × sector(U1(1))) == sector(U1(1)) - @test (@inferred s × sector(; A=U1(1))) == sector(; A=U1(1)) - @test (@inferred U1(1) × s) == sector(U1(1)) - @test (@inferred sector(U1(1)) × s) == sector(U1(1)) - @test (@inferred sector(; A=U1(1)) × s) == sector(; A=U1(1)) + @test (@inferred s × U1(1)) == CategoryProduct(U1(1)) + @test (@inferred s × CategoryProduct(U1(1))) == CategoryProduct(U1(1)) + @test (@inferred s × CategoryProduct(; A=U1(1))) == CategoryProduct(; A=U1(1)) + @test (@inferred U1(1) × s) == CategoryProduct(U1(1)) + @test (@inferred CategoryProduct(U1(1)) × s) == CategoryProduct(U1(1)) + @test (@inferred CategoryProduct(; A=U1(1)) × s) == CategoryProduct(; A=U1(1)) # Empty acts as trivial @test (@inferred U1(1) ⊗ s) == U1(1) @@ -563,28 +570,30 @@ end @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) - @test (@inferred sector(U1(1)) ⊗ s) == sector(U1(1)) - @test (@inferred sector(SU2(0)) ⊗ s) == gradedrange([sector(SU2(0)) => 1]) - @test (@inferred sector(Fib("τ"), SU2(1), U1(2)) ⊗ s) == - gradedrange([sector(Fib("τ"), SU2(1), U1(2)) => 1]) + @test (@inferred CategoryProduct(U1(1)) ⊗ s) == CategoryProduct(U1(1)) + @test (@inferred CategoryProduct(SU2(0)) ⊗ s) == + gradedrange([CategoryProduct(SU2(0)) => 1]) + @test (@inferred CategoryProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == + gradedrange([CategoryProduct(Fib("τ"), SU2(1), U1(2)) => 1]) - @test (@inferred sector(; A=U1(1)) ⊗ s) == sector(; A=U1(1)) - @test (@inferred sector(; A=SU2(0)) ⊗ s) == gradedrange([sector(; A=SU2(0)) => 1]) - @test (@inferred sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == - gradedrange([sector(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) + @test (@inferred CategoryProduct(; A=U1(1)) ⊗ s) == CategoryProduct(; A=U1(1)) + @test (@inferred CategoryProduct(; A=SU2(0)) ⊗ s) == + gradedrange([CategoryProduct(; A=SU2(0)) => 1]) + @test (@inferred CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == + gradedrange([CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) # Empty behaves as empty NamedTuple @test s != U1(0) - @test s != sector(U1(0)) - @test s != sector(; A=U1(1)) - @test s == sector(; A=U1(0)) - @test sector(; A=U1(0)) == s + @test s != CategoryProduct(U1(0)) + @test s != CategoryProduct(; A=U1(1)) + @test s == CategoryProduct(; A=U1(0)) + @test CategoryProduct(; A=U1(0)) == s @test !(s < s) - @test_throws ArgumentError s < sector(U1(0)) - @test s < sector(; A=U1(1)) - @test s > sector(; A=U1(-1)) - @test !(s < sector(; A=U1(0))) - @test !(s > sector(; A=U1(0))) + @test_throws ArgumentError s < CategoryProduct(U1(0)) + @test s < CategoryProduct(; A=U1(1)) + @test s > CategoryProduct(; A=U1(-1)) + @test !(s < CategoryProduct(; A=U1(0))) + @test !(s > CategoryProduct(; A=U1(0))) end end From 7b548bfdf4476b64c179042c4a1c46ea5e955f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 12:08:09 -0400 Subject: [PATCH 138/194] do not assume Tuple category type --- NDTensors/src/lib/Sectors/src/category_product.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index a3f46853f0..80d1f11f1f 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -113,13 +113,11 @@ function recover_category_product_type(T::Type, c::CategoryProduct) return recover_category_product_type(T, categories(c)) end -function recover_category_product_type( - T::Type{<:CategoryProduct}, cats::Tuple{Vararg{AbstractCategory}} -) +function recover_category_product_type(T::Type{<:CategoryProduct}, cats) return recover_category_product_type(categories_type(T), cats) end -function recover_category_product_type(T::Type, cats::Tuple{Vararg{AbstractCategory}}) +function recover_category_product_type(T::Type, cats) return CategoryProduct(T(cats)) end From 74c6e765e65cf9a6527a39e1aa873a1457af5942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 12:19:06 -0400 Subject: [PATCH 139/194] split shared_categories_fusion_rule methods --- NDTensors/src/lib/Sectors/src/category_product.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 80d1f11f1f..8187fc86a9 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -78,11 +78,6 @@ function categories_fusion_rule(cats1, cats2) return shared_cat × diff_cat end -function shared_categories_fusion_rule(shared1::T, shared2::T) where {T} - fused = map(fusion_rule, values(shared1), values(shared2)) - return recover_style(T, fused) -end - function recover_style(T::Type, fused) style = reduce(combine_styles, SymmetryStyle.(fused); init=AbelianStyle()) return recover_category_product_type(style, T, fused) @@ -207,6 +202,11 @@ function categories_diff(t1::Tuple, t2::Tuple) return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] end +function shared_categories_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} + fused = map(fusion_rule, shared1, shared2) + return recover_style(T, fused) +end + # =========================== Dictionary-like implementation ============================= function CategoryProduct(nt::NamedTuple) categories = sort_keys(nt) @@ -274,3 +274,8 @@ function categories_common(nt1::NamedTuple, nt2::NamedTuple) end categories_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) + +function shared_categories_fusion_rule(shared1::T, shared2::T) where {T<:NamedTuple} + fused = map(fusion_rule, values(shared1), values(shared2)) + return recover_style(T, fused) +end From 8c19a1f136d1aec2e338d5e868ff2871086e69de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 12:28:30 -0400 Subject: [PATCH 140/194] use mapreduce --- NDTensors/src/lib/Sectors/src/category_product.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 8187fc86a9..00425e87e7 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -20,11 +20,11 @@ TrivialSector() = CategoryProduct(()) # ================================= Sectors interface ==================================== function SymmetryStyle(c::CategoryProduct) - return reduce(combine_styles, map(SymmetryStyle, categories(c)); init=AbelianStyle()) + return mapreduce(SymmetryStyle, combine_styles, categories(c); init=AbelianStyle()) end function quantum_dimension(::NotAbelianStyle, s::CategoryProduct) - return prod(map(quantum_dimension, categories(s))) + return mapreduce(quantum_dimension, *, categories(s)) end # use map instead of broadcast to support both Tuple and NamedTuple From 738d91220c423161816105cb964ac39b6304f1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 15:36:10 -0400 Subject: [PATCH 141/194] SymmetryStyle defined for types --- .../src/lib/Sectors/src/category_definitions/fib.jl | 2 +- .../lib/Sectors/src/category_definitions/ising.jl | 2 +- .../src/lib/Sectors/src/category_definitions/o2.jl | 2 +- .../src/lib/Sectors/src/category_definitions/su.jl | 2 +- .../src/lib/Sectors/src/category_definitions/su2k.jl | 2 +- .../src/lib/Sectors/src/category_definitions/u1.jl | 2 +- .../src/lib/Sectors/src/category_definitions/zn.jl | 2 +- NDTensors/src/lib/Sectors/src/category_product.jl | 12 +++++++++--- NDTensors/src/lib/Sectors/src/symmetry_style.jl | 12 ++++++------ 9 files changed, 22 insertions(+), 16 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index 57a2bd9c81..9a11a4d80f 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -19,7 +19,7 @@ function Fib(s::AbstractString) return error("Unrecognized input \"$s\" to Fib constructor") end -SymmetryStyle(::Fib) = NotAbelianStyle() +SymmetryStyle(::Type{Fib}) = NotAbelianStyle() GradedAxes.dual(f::Fib) = f diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index 2dcf890ecc..d904fc56e1 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -19,7 +19,7 @@ function Ising(s::AbstractString) return error("Unrecognized input \"$s\" to Ising constructor") end -SymmetryStyle(::Ising) = NotAbelianStyle() +SymmetryStyle(::Type{Ising}) = NotAbelianStyle() GradedAxes.dual(i::Ising) = i diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index 24433e03f5..8941da9406 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -20,7 +20,7 @@ struct O2 <: AbstractCategory l::Half{Int} end -SymmetryStyle(::O2) = NotAbelianStyle() +SymmetryStyle(::Type{O2}) = NotAbelianStyle() category_label(s::O2) = s.l diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index 4a9c41901f..d283800eda 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -23,7 +23,7 @@ end SU{N}(t::Tuple) where {N} = SU{N,length(t)}(t) SU(t::Tuple) = SU{length(t) + 1}(t) # infer N from tuple length -SymmetryStyle(::SU) = NotAbelianStyle() +SymmetryStyle(::Type{<:SU}) = NotAbelianStyle() category_label(s::SU) = s.l diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index e428294834..8ce8f24bf7 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -9,7 +9,7 @@ struct su2{k} <: AbstractCategory j::Half{Int} end -SymmetryStyle(::su2) = NotAbelianStyle() +SymmetryStyle(::Type{<:su2}) = NotAbelianStyle() GradedAxes.dual(s::su2) = s diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index 031e24724a..ff884ebca8 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -10,7 +10,7 @@ struct U1{T} <: AbstractCategory n::T end -SymmetryStyle(::U1) = AbelianStyle() +SymmetryStyle(::Type{<:U1}) = AbelianStyle() GradedAxes.dual(u::U1) = U1(-u.n) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 124d177401..271135f232 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -9,7 +9,7 @@ struct Z{N} <: AbstractCategory Z{N}(m) where {N} = new{N}(m % N) end -SymmetryStyle(::Z) = AbelianStyle() +SymmetryStyle(::Type{<:Z}) = AbelianStyle() category_label(c::Z) = c.m modulus(::Type{Z{N}}) where {N} = N diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 00425e87e7..38ec5e3ba6 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -19,9 +19,7 @@ const TrivialSector = CategoryProduct{Tuple{}} TrivialSector() = CategoryProduct(()) # ================================= Sectors interface ==================================== -function SymmetryStyle(c::CategoryProduct) - return mapreduce(SymmetryStyle, combine_styles, categories(c); init=AbelianStyle()) -end +SymmetryStyle(T::Type{<:CategoryProduct}) = SymmetryStyle(categories_type(T)) function quantum_dimension(::NotAbelianStyle, s::CategoryProduct) return mapreduce(quantum_dimension, *, categories(s)) @@ -182,6 +180,10 @@ fusion_rule(::AbelianStyle, ::TrivialSector, c::CategoryProduct) = c CategoryProduct(t::Tuple) = _CategoryProduct(t) CategoryProduct(cats::AbstractCategory...) = CategoryProduct(cats) +function SymmetryStyle(T::Type{<:Tuple}) + return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) +end + categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) categories_isless(::Tuple, ::Tuple) = throw(ArgumentError("Not implemented")) @@ -224,6 +226,10 @@ function CategoryProduct(pairs::Pair...) return CategoryProduct(NamedTuple{keys}(vals)) end +function SymmetryStyle(NT::Type{<:NamedTuple}) + return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) +end + function categories_isequal(nt::NamedTuple, ::Tuple{}) return categories_isequal(nt, (;)) end diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/Sectors/src/symmetry_style.jl index ddf5fb8e27..f3e443dce0 100644 --- a/NDTensors/src/lib/Sectors/src/symmetry_style.jl +++ b/NDTensors/src/lib/Sectors/src/symmetry_style.jl @@ -1,17 +1,17 @@ # This file defines SymmetryStyle, a trait to distinguish abelian groups, non-abelian groups # and non-group fusion categories. -using ..LabelledNumbers: LabelledInteger, label +using ..LabelledNumbers: LabelledInteger, label_type abstract type SymmetryStyle end struct AbelianStyle <: SymmetryStyle end struct NotAbelianStyle <: SymmetryStyle end +SymmetryStyle(x) = SymmetryStyle(typeof(x)) +SymmetryStyle(T::Type) = error("method `SymmetryStyle` not defined for type $(T)") +SymmetryStyle(L::Type{<:LabelledInteger}) = SymmetryStyle(label_type(L)) +SymmetryStyle(G::Type{<:AbstractUnitRange}) = SymmetryStyle(eltype(G)) + combine_styles(::AbelianStyle, ::AbelianStyle) = AbelianStyle() combine_styles(::SymmetryStyle, ::SymmetryStyle) = NotAbelianStyle() - -SymmetryStyle(l::LabelledInteger) = SymmetryStyle(label(l)) - -# crash for empty g. Currently impossible to construct. -SymmetryStyle(g::AbstractUnitRange) = SymmetryStyle(first(g)) From e63b3e0cd50229e9834da2330dcf2090a044008f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 15:47:54 -0400 Subject: [PATCH 142/194] fix test in Julia 1.6 --- .../lib/Sectors/test/test_category_product.jl | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 5edf8e4b24..a633d7a332 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -56,7 +56,7 @@ end s = U1(3) × SU2(1//2) × Fib("τ") @test length(categories(s)) == 3 - @test (@inferred quantum_dimension(s)) == 1.0 + √5 + @test (@inferred_latest quantum_dimension(s)) == 1.0 + √5 @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") @test categories(s)[1] == U1(3) @test categories(s)[2] == SU2(1//2) @@ -101,15 +101,15 @@ end # NonGroupCategory g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) g_ising = gradedrange([(Ising("1") × Ising("1")) => 1]) - @test (@inferred quantum_dimension((Fib("1") × Fib("1")))) == 1.0 - @test (@inferred quantum_dimension(g_fib)) == 1.0 - @test (@inferred quantum_dimension(g_ising)) == 1.0 - @test (@inferred quantum_dimension((Ising("1") × Ising("1")))) == 1.0 - @test (@inferred block_dimensions(g_fib)) == [1.0] - @test (@inferred block_dimensions(g_ising)) == [1.0] + @test (@inferred_latest quantum_dimension((Fib("1") × Fib("1")))) == 1.0 + @test (@inferred_latest quantum_dimension(g_fib)) == 1.0 + @test (@inferred_latest quantum_dimension(g_ising)) == 1.0 + @test (@inferred_latest quantum_dimension((Ising("1") × Ising("1")))) == 1.0 + @test (@inferred_latest block_dimensions(g_fib)) == [1.0] + @test (@inferred_latest block_dimensions(g_ising)) == [1.0] - @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 - @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 + @test (@inferred_latest quantum_dimension(U1(1) × Fib("1"))) == 1.0 + @test (@inferred_latest quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 # mixed product Abelian / NonAbelian / NonGroup g = gradedrange([ @@ -118,8 +118,8 @@ end (U1(2) × SU2(0) × Ising("ψ")) => 1, (U1(2) × SU2(1) × Ising("ψ")) => 1, ]) - @test (@inferred quantum_dimension(g)) == 8.0 - @test (@inferred block_dimensions(g)) == [1.0, 3.0, 1.0, 3.0] + @test (@inferred_latest quantum_dimension(g)) == 8.0 + @test (@inferred_latest block_dimensions(g)) == [1.0, 3.0, 1.0, 3.0] ϕ = (1 + √5) / 2 g = gradedrange([ @@ -128,24 +128,24 @@ end (Fib("τ") × SU2(0) × U1(2)) => 1, (Fib("τ") × SU2(1) × U1(2)) => 1, ]) - @test (@inferred quantum_dimension(g)) == 4.0 + 4.0ϕ - @test (@inferred block_dimensions(g)) == [1.0, 3.0, 1.0ϕ, 3.0ϕ] + @test (@inferred_latest quantum_dimension(g)) == 4.0 + 4.0ϕ + @test (@inferred_latest block_dimensions(g)) == [1.0, 3.0, 1.0ϕ, 3.0ϕ] end @testset "Fusion of Abelian products" begin p1 = CategoryProduct(U1(1)) p2 = CategoryProduct(U1(2)) - @test (@inferred p1 ⊗ p2) == CategoryProduct(U1(3)) + @test (@inferred_latest p1 ⊗ p2) == CategoryProduct(U1(3)) p11 = U1(1) × U1(1) - @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) + @test (@inferred_latest p11 ⊗ p11) == U1(2) × U1(2) p123 = U1(1) × U1(2) × U1(3) - @test (@inferred p123 ⊗ p123) == U1(2) × U1(4) × U1(6) + @test (@inferred_latest p123 ⊗ p123) == U1(2) × U1(4) × U1(6) s1 = CategoryProduct(U1(1), Z{2}(1)) s2 = CategoryProduct(U1(0), Z{2}(0)) - @test (@inferred s1 ⊗ s2) == U1(1) × Z{2}(1) + @test (@inferred_latest s1 ⊗ s2) == U1(1) × Z{2}(1) end @testset "Fusion of NonAbelian products" begin @@ -253,7 +253,7 @@ end gradedrange([CategoryProduct(SU2(1) × U1(1)) => 1]), ) @test space_isequal( - (@inferred CategoryProduct(U1(1) × SU2(1)) ⊗ CategoryProduct(U1(2))), + (@inferred_latest CategoryProduct(U1(1) × SU2(1)) ⊗ CategoryProduct(U1(2))), gradedrange([CategoryProduct(U1(3) × SU2(1)) => 1]), ) @@ -269,7 +269,7 @@ end g1 = gradedrange([s1 => 2]) g2 = gradedrange([s2 => 1]) @test space_isequal( - (@inferred fusion_product(g1, g2)), + (@inferred_latest fusion_product(g1, g2)), gradedrange([U1(1) × SU2(0) × Ising("σ") => 2, U1(1) × SU2(1) × Ising("σ") => 2]), ) end @@ -322,7 +322,7 @@ end @test length(categories(s)) == 2 @test categories(s)[:B] == Ising("ψ") @test categories(s)[:C] == Z{2}(1) - @test (@inferred quantum_dimension(s)) == 1.0 + @test (@inferred_latest quantum_dimension(s)) == 1.0 end @testset "Comparisons with unspecified labels" begin @@ -375,8 +375,8 @@ end # non group categories g_fib = gradedrange([CategoryProduct(; A=Fib("1"), B=Fib("1")) => 1]) g_ising = gradedrange([CategoryProduct(; A=Ising("1"), B=Ising("1")) => 1]) - @test (@inferred quantum_dimension(g_fib)) == 1.0 - @test (@inferred quantum_dimension(g_ising)) == 1.0 + @test (@inferred_latest quantum_dimension(g_fib)) == 1.0 + @test (@inferred_latest quantum_dimension(g_ising)) == 1.0 # mixed product Abelian / NonAbelian / NonGroup g = gradedrange([ @@ -393,7 +393,7 @@ end CategoryProduct(; A=Fib("τ"), B=SU2(0), C=U1(2)) => 1, CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1, ]) - @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) + @test (@inferred_latest quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end @testset "Fusion of Abelian products" begin @@ -402,19 +402,19 @@ end q01 = CategoryProduct(; B=U1(1)) q11 = CategoryProduct(; A=U1(1), B=U1(1)) - @test (@inferred q10 ⊗ q10) == CategoryProduct(; A=U1(2)) - @test (@inferred q01 ⊗ q00) == q01 - @test (@inferred q00 ⊗ q01) == q01 - @test (@inferred q10 ⊗ q01) == q11 - @test (@inferred q11 ⊗ q11) == CategoryProduct(; A=U1(2), B=U1(2)) + @test (@inferred_latest q10 ⊗ q10) == CategoryProduct(; A=U1(2)) + @test (@inferred_latest q01 ⊗ q00) == q01 + @test (@inferred_latest q00 ⊗ q01) == q01 + @test (@inferred_latest q10 ⊗ q01) == q11 + @test (@inferred_latest q11 ⊗ q11) == CategoryProduct(; A=U1(2), B=U1(2)) s11 = CategoryProduct(; A=U1(1), B=Z{2}(1)) s10 = CategoryProduct(; A=U1(1)) s01 = CategoryProduct(; B=Z{2}(1)) - @test (@inferred s01 ⊗ q00) == s01 - @test (@inferred q00 ⊗ s01) == s01 - @test (@inferred s10 ⊗ s01) == s11 - @test (@inferred s11 ⊗ s11) == CategoryProduct(; A=U1(2), B=Z{2}(0)) + @test (@inferred_latest s01 ⊗ q00) == s01 + @test (@inferred_latest q00 ⊗ s01) == s01 + @test (@inferred_latest s10 ⊗ s01) == s11 + @test (@inferred_latest s11 ⊗ s11) == CategoryProduct(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin @@ -427,9 +427,9 @@ end (@inferred pha ⊗ pha), gradedrange([CategoryProduct(; A=SU2(0)) => 1, CategoryProduct(; A=SU2(1)) => 1]), ) - @test space_isequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) - @test space_isequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) - @test space_isequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) + @test space_isequal((@inferred_latest pha ⊗ p0), gradedrange([pha => 1])) + @test space_isequal((@inferred_latest p0 ⊗ phb), gradedrange([phb => 1])) + @test space_isequal((@inferred_latest pha ⊗ phb), gradedrange([phab => 1])) @test space_isequal( (@inferred phab ⊗ phab), @@ -537,7 +537,7 @@ end sAB = CategoryProduct(; A=U1(1), B=SU2(1//2)) gA = gradedrange([sA => 2]) gB = gradedrange([sB => 1]) - @test space_isequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) + @test space_isequal((@inferred_latest fusion_product(gA, gB)), gradedrange([sAB => 2])) end end @@ -563,23 +563,23 @@ end @test (@inferred CategoryProduct(; A=U1(1)) × s) == CategoryProduct(; A=U1(1)) # Empty acts as trivial - @test (@inferred U1(1) ⊗ s) == U1(1) + @test (@inferred_latest U1(1) ⊗ s) == U1(1) @test (@inferred SU2(0) ⊗ s) == gradedrange([SU2(0) => 1]) @test (@inferred Fib("τ") ⊗ s) == gradedrange([Fib("τ") => 1]) - @test (@inferred s ⊗ U1(1)) == U1(1) + @test (@inferred_latest s ⊗ U1(1)) == U1(1) @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) - @test (@inferred CategoryProduct(U1(1)) ⊗ s) == CategoryProduct(U1(1)) - @test (@inferred CategoryProduct(SU2(0)) ⊗ s) == + @test (@inferred_latest CategoryProduct(U1(1)) ⊗ s) == CategoryProduct(U1(1)) + @test (@inferred_latest CategoryProduct(SU2(0)) ⊗ s) == gradedrange([CategoryProduct(SU2(0)) => 1]) - @test (@inferred CategoryProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == + @test (@inferred_latest CategoryProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == gradedrange([CategoryProduct(Fib("τ"), SU2(1), U1(2)) => 1]) - @test (@inferred CategoryProduct(; A=U1(1)) ⊗ s) == CategoryProduct(; A=U1(1)) - @test (@inferred CategoryProduct(; A=SU2(0)) ⊗ s) == + @test (@inferred_latest CategoryProduct(; A=U1(1)) ⊗ s) == CategoryProduct(; A=U1(1)) + @test (@inferred_latest CategoryProduct(; A=SU2(0)) ⊗ s) == gradedrange([CategoryProduct(; A=SU2(0)) => 1]) - @test (@inferred CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == + @test (@inferred_latest CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == gradedrange([CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) # Empty behaves as empty NamedTuple From 90cad20453b8f38c0a97269f233767b7431bd0a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 16:09:56 -0400 Subject: [PATCH 143/194] simplify reduce_style --- NDTensors/src/lib/Sectors/src/category_product.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 38ec5e3ba6..37a1c3b30d 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -77,7 +77,7 @@ function categories_fusion_rule(cats1, cats2) end function recover_style(T::Type, fused) - style = reduce(combine_styles, SymmetryStyle.(fused); init=AbelianStyle()) + style = SymmetryStyle(T) return recover_category_product_type(style, T, fused) end @@ -156,7 +156,7 @@ function fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) return CategoryProduct(()) end -# TrivialSectorStyle acts as trivial on any AbstractCategory, not just CategoryProduct +# TrivialSector acts as trivial on any AbstractCategory, not just CategoryProduct function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::AbstractCategory) return to_gradedrange(c) end From 22e9ac97196746fa7c89b820052f7a3b7406d325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 16:21:53 -0400 Subject: [PATCH 144/194] reorder file --- .../src/lib/Sectors/src/category_product.jl | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 37a1c3b30d..7c98a4e545 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -61,10 +61,33 @@ end # - ordered-like with a Tuple # - dictionary-like with a NamedTuple +categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) +function categories_isequal(nt::NamedTuple, ::Tuple{}) + return categories_isequal(nt, (;)) +end +function categories_isequal(::Tuple{}, nt::NamedTuple) + return categories_isequal((;), nt) +end +function categories_isequal(nt1::NamedTuple, nt2::NamedTuple) + return ==(sym_categories_insert_unspecified(nt1, nt2)...) +end + # get clean results when mixing implementations categories_isequal(::Tuple, ::NamedTuple) = false categories_isequal(::NamedTuple, ::Tuple) = false +categories_isless(::Tuple, ::Tuple) = throw(ArgumentError("Not implemented")) +categories_isless(t1::T, t2::T) where {T<:Tuple} = t1 < t2 +function categories_isless(nt::NamedTuple, ::Tuple{}) + return categories_isless(nt, (;)) +end +function categories_isless(::Tuple{}, nt::NamedTuple) + return categories_isless((;), nt) +end +function categories_isless(nt1::NamedTuple, nt2::NamedTuple) + return isless(sym_categories_insert_unspecified(nt1, nt2)...) +end + categories_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) categories_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) @@ -184,11 +207,6 @@ function SymmetryStyle(T::Type{<:Tuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) end -categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) - -categories_isless(::Tuple, ::Tuple) = throw(ArgumentError("Not implemented")) -categories_isless(t1::T, t2::T) where {T<:Tuple} = t1 < t2 - categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) categories_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) @@ -230,26 +248,6 @@ function SymmetryStyle(NT::Type{<:NamedTuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) end -function categories_isequal(nt::NamedTuple, ::Tuple{}) - return categories_isequal(nt, (;)) -end -function categories_isequal(::Tuple{}, nt::NamedTuple) - return categories_isequal((;), nt) -end -function categories_isequal(nt1::NamedTuple, nt2::NamedTuple) - return ==(sym_categories_insert_unspecified(nt1, nt2)...) -end - -function categories_isless(nt::NamedTuple, ::Tuple{}) - return categories_isless(nt, (;)) -end -function categories_isless(::Tuple{}, nt::NamedTuple) - return categories_isless((;), nt) -end -function categories_isless(nt1::NamedTuple, nt2::NamedTuple) - return isless(sym_categories_insert_unspecified(nt1, nt2)...) -end - function sym_categories_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) return categories_insert_unspecified(nt1, nt2), categories_insert_unspecified(nt2, nt1) end From 10ff55c045a4fa056fb41a89236a58004d1b8438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 17:23:59 -0400 Subject: [PATCH 145/194] do not forbid empty NamedTuple --- .../src/lib/Sectors/src/category_product.jl | 7 +- .../lib/Sectors/test/test_category_product.jl | 106 +++++++++--------- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 7c98a4e545..3b1506c60d 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -15,7 +15,7 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -const TrivialSector = CategoryProduct{Tuple{}} +const TrivialSector{Categories<:Union{Tuple{},NamedTuple{()}}} = CategoryProduct{Categories} TrivialSector() = CategoryProduct(()) # ================================= Sectors interface ==================================== @@ -207,6 +207,8 @@ function SymmetryStyle(T::Type{<:Tuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) end +categories_product(::NamedTuple{()}, l1::Tuple) = l1 +categories_product(l2::Tuple, ::NamedTuple{()}) = l2 categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) categories_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) @@ -235,9 +237,6 @@ end CategoryProduct(; kws...) = CategoryProduct((; kws...)) -# avoid having 2 different kinds of TrivialSector: cast empty NamedTuple to Tuple{} -CategoryProduct(::NamedTuple{()}) = CategoryProduct(()) - function CategoryProduct(pairs::Pair...) keys = ntuple(n -> Symbol(pairs[n][1]), length(pairs)) vals = ntuple(n -> pairs[n][2], length(pairs)) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index a633d7a332..6235e47a20 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -542,58 +542,58 @@ end end @testset "Empty category" begin - s = TrivialSector() - @test s == s - @test (@inferred dual(s)) == s - @test (@inferred s × s) == s - @test (@inferred s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred_latest trivial(s)) == s - @test typeof(s) == typeof(CategoryProduct(())) - @test typeof(s) == typeof(CategoryProduct((;))) # empty NamedTuple is cast to Tuple{} - - g0 = gradedrange([s => 2]) - @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) - - @test (@inferred s × U1(1)) == CategoryProduct(U1(1)) - @test (@inferred s × CategoryProduct(U1(1))) == CategoryProduct(U1(1)) - @test (@inferred s × CategoryProduct(; A=U1(1))) == CategoryProduct(; A=U1(1)) - @test (@inferred U1(1) × s) == CategoryProduct(U1(1)) - @test (@inferred CategoryProduct(U1(1)) × s) == CategoryProduct(U1(1)) - @test (@inferred CategoryProduct(; A=U1(1)) × s) == CategoryProduct(; A=U1(1)) - - # Empty acts as trivial - @test (@inferred_latest U1(1) ⊗ s) == U1(1) - @test (@inferred SU2(0) ⊗ s) == gradedrange([SU2(0) => 1]) - @test (@inferred Fib("τ") ⊗ s) == gradedrange([Fib("τ") => 1]) - @test (@inferred_latest s ⊗ U1(1)) == U1(1) - @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) - @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) - - @test (@inferred_latest CategoryProduct(U1(1)) ⊗ s) == CategoryProduct(U1(1)) - @test (@inferred_latest CategoryProduct(SU2(0)) ⊗ s) == - gradedrange([CategoryProduct(SU2(0)) => 1]) - @test (@inferred_latest CategoryProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == - gradedrange([CategoryProduct(Fib("τ"), SU2(1), U1(2)) => 1]) - - @test (@inferred_latest CategoryProduct(; A=U1(1)) ⊗ s) == CategoryProduct(; A=U1(1)) - @test (@inferred_latest CategoryProduct(; A=SU2(0)) ⊗ s) == - gradedrange([CategoryProduct(; A=SU2(0)) => 1]) - @test (@inferred_latest CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == - gradedrange([CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) - - # Empty behaves as empty NamedTuple - @test s != U1(0) - @test s != CategoryProduct(U1(0)) - @test s != CategoryProduct(; A=U1(1)) - @test s == CategoryProduct(; A=U1(0)) - @test CategoryProduct(; A=U1(0)) == s - - @test !(s < s) - @test_throws ArgumentError s < CategoryProduct(U1(0)) - @test s < CategoryProduct(; A=U1(1)) - @test s > CategoryProduct(; A=U1(-1)) - @test !(s < CategoryProduct(; A=U1(0))) - @test !(s > CategoryProduct(; A=U1(0))) + for s in (CategoryProduct(()), CategoryProduct((;))) + @test s == CategoryProduct(()) + @test s == CategoryProduct((;)) + @test (@inferred dual(s)) == s + @test (@inferred s × s) == s + @test (@inferred s ⊗ s) == s + @test (@inferred quantum_dimension(s)) == 1 + @test (@inferred_latest trivial(s)) == s + + g0 = gradedrange([s => 2]) + @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) + + @test (@inferred s × U1(1)) == CategoryProduct(U1(1)) + @test (@inferred s × CategoryProduct(U1(1))) == CategoryProduct(U1(1)) + @test (@inferred s × CategoryProduct(; A=U1(1))) == CategoryProduct(; A=U1(1)) + @test (@inferred U1(1) × s) == CategoryProduct(U1(1)) + @test (@inferred CategoryProduct(U1(1)) × s) == CategoryProduct(U1(1)) + @test (@inferred CategoryProduct(; A=U1(1)) × s) == CategoryProduct(; A=U1(1)) + + # Empty acts as trivial + @test (@inferred_latest U1(1) ⊗ s) == U1(1) + @test (@inferred SU2(0) ⊗ s) == gradedrange([SU2(0) => 1]) + @test (@inferred Fib("τ") ⊗ s) == gradedrange([Fib("τ") => 1]) + @test (@inferred_latest s ⊗ U1(1)) == U1(1) + @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) + @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) + + @test (@inferred_latest CategoryProduct(U1(1)) ⊗ s) == CategoryProduct(U1(1)) + @test (@inferred_latest CategoryProduct(SU2(0)) ⊗ s) == + gradedrange([CategoryProduct(SU2(0)) => 1]) + @test (@inferred_latest CategoryProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == + gradedrange([CategoryProduct(Fib("τ"), SU2(1), U1(2)) => 1]) + + @test (@inferred_latest CategoryProduct(; A=U1(1)) ⊗ s) == CategoryProduct(; A=U1(1)) + @test (@inferred_latest CategoryProduct(; A=SU2(0)) ⊗ s) == + gradedrange([CategoryProduct(; A=SU2(0)) => 1]) + @test (@inferred_latest CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == + gradedrange([CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) + + # Empty behaves as empty NamedTuple + @test s != U1(0) + @test s != CategoryProduct(U1(0)) + @test s != CategoryProduct(; A=U1(1)) + @test s == CategoryProduct(; A=U1(0)) + @test CategoryProduct(; A=U1(0)) == s + + @test !(s < s) + @test_throws ArgumentError s < CategoryProduct(U1(0)) + @test s < CategoryProduct(; A=U1(1)) + @test s > CategoryProduct(; A=U1(-1)) + @test !(s < CategoryProduct(; A=U1(0))) + @test !(s > CategoryProduct(; A=U1(0))) + end end end From d5630db882096bf1ed3f6c7d3ed91a2e6dba78c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 17:50:58 -0400 Subject: [PATCH 146/194] add comment --- NDTensors/src/lib/Sectors/src/category_product.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index 3b1506c60d..b231fd439a 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -109,6 +109,8 @@ function recover_category_product_type(::AbelianStyle, T::Type, fused) end function recover_category_product_type(::NotAbelianStyle, T::Type, fused) + # here fused contains at least one graded unit range. + # convert eg. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} g = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × type_fixed = recover_category_product_type(T, g) From 4294deab6a825fd9f4ee7f4ccb5babb91c6182c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 3 Oct 2024 18:24:45 -0400 Subject: [PATCH 147/194] refactor label_fusion_rule --- NDTensors/src/lib/Sectors/src/abstractcategory.jl | 7 ++++--- .../src/lib/Sectors/src/category_definitions/fib.jl | 6 +++++- .../lib/Sectors/src/category_definitions/ising.jl | 6 +++++- .../src/lib/Sectors/src/category_definitions/o2.jl | 2 +- .../src/lib/Sectors/src/category_definitions/su.jl | 12 ++++++------ .../src/lib/Sectors/src/category_definitions/su2k.jl | 3 ++- .../src/lib/Sectors/src/category_definitions/u1.jl | 2 +- .../src/lib/Sectors/src/category_definitions/zn.jl | 3 ++- 8 files changed, 26 insertions(+), 15 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 4b30b689f3..52b5546a50 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -54,13 +54,14 @@ function fusion_rule(c1::AbstractCategory, c2::AbstractCategory) end function fusion_rule(::NotAbelianStyle, c1::C, c2::C) where {C<:AbstractCategory} - degen, labels = label_fusion_rule(C, category_label(c1), category_label(c2)) - return gradedrange(labelled.(degen, C.(labels))) + degen, sectors = label_fusion_rule(C, category_label(c1), category_label(c2)) + return gradedrange(labelled.(degen, sectors)) end # abelian case: return Category function fusion_rule(::AbelianStyle, c1::C, c2::C) where {C<:AbstractCategory} - return C(label_fusion_rule(C, category_label(c1), category_label(c2))) + _, sectors = label_fusion_rule(C, category_label(c1), category_label(c2)) + return only(sectors) end function label_fusion_rule(category_type::Type{<:AbstractCategory}, ::Any, ::Any) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl index 9a11a4d80f..d0a7aba710 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl @@ -30,7 +30,11 @@ trivial(::Type{Fib}) = Fib(0) quantum_dimension(::NotAbelianStyle, f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) / 2) # Fusion rules identical to su2₃ -label_fusion_rule(::Type{Fib}, l1, l2) = label_fusion_rule(su2{3}, l1, l2) +function label_fusion_rule(::Type{Fib}, l1, l2) + degen, suk_sectors = label_fusion_rule(su2{3}, l1, l2) + sectors = Fib.(category_label.(suk_sectors)) + return degen, sectors +end label_to_str(f::Fib) = istrivial(f) ? "1" : "τ" diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl index d904fc56e1..1eb3f537da 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl @@ -30,7 +30,11 @@ trivial(::Type{Ising}) = Ising(0) quantum_dimension(::NotAbelianStyle, i::Ising) = (category_label(i) == 1//2) ? √2 : 1.0 # Fusion rules identical to su2₂ -label_fusion_rule(::Type{Ising}, l1, l2) = label_fusion_rule(su2{2}, l1, l2) +function label_fusion_rule(::Type{Ising}, l1, l2) + degen, suk_sectors = label_fusion_rule(su2{2}, l1, l2) + sectors = Ising.(category_label.(suk_sectors)) + return degen, sectors +end # TODO: Use `Val` dispatch here? label_to_str(i::Ising) = ("1", "σ", "ψ")[twice(category_label(i)) + 1] diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl index 8941da9406..48d208e189 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl @@ -71,5 +71,5 @@ function label_fusion_rule(::Type{O2}, l1, l2) end end end - return degens, labels + return degens, O2.(labels) end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl index d283800eda..6707c17dbc 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su.jl @@ -92,9 +92,9 @@ quantum_dimension(s::SU{2}) = category_label(s)[1] + 1 GradedAxes.dual(s::SU{2}) = s function label_fusion_rule(::Type{<:SU{2}}, s1, s2) - labels = collect((i,) for i in (abs(s1[1] - s2[1])):2:(s1[1] + s2[1])) - degen = ones(Int, length(labels)) - return degen, labels + irreps = collect(SU{2}((i,)) for i in (abs(s1[1] - s2[1])):2:(s1[1] + s2[1])) + degen = ones(Int, length(irreps)) + return degen, irreps end # define angular momentum-like interface using half-integers @@ -123,7 +123,7 @@ function label_fusion_rule(::Type{<:SU{3}}, left, right) end if right[1] == 0 # avoid issues with singlet - return [1], [left] + return [1], [SU{3}(left)] end left_row1 = left[1] @@ -172,6 +172,6 @@ function label_fusion_rule(::Type{<:SU{3}}, left, right) unique_labels = sort(unique(irreps)) degen = [count(==(irr), irreps) for irr in unique_labels] - - return degen, unique_labels + sectors = SU{3}.(unique_labels) + return degen, sectors end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl index 8ce8f24bf7..e90312c635 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl @@ -22,5 +22,6 @@ trivial(::Type{su2{k}}) where {k} = su2{k}(0) function label_fusion_rule(::Type{su2{k}}, j1, j2) where {k} labels = collect(abs(j1 - j2):min(k - j1 - j2, j1 + j2)) degen = ones(Int, length(labels)) - return degen, labels + sectors = su2{k}.(labels) + return degen, sectors end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl index ff884ebca8..4ebd70fb62 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl @@ -19,7 +19,7 @@ category_label(u::U1) = u.n trivial(::Type{U1}) = trivial(U1{Int}) trivial(::Type{U1{T}}) where {T} = U1(T(0)) -label_fusion_rule(::Type{<:U1}, n1, n2) = n1 + n2 +label_fusion_rule(T::Type{<:U1}, n1, n2) = [1], [T(n1 + n2)] # hide label type in printing function Base.show(io::IO, u::U1) diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl index 271135f232..ad94ba826c 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl @@ -19,7 +19,8 @@ modulus(c::Z) = modulus(typeof(c)) trivial(category_type::Type{<:Z}) = category_type(0) function label_fusion_rule(category_type::Type{<:Z}, n1, n2) - return (n1 + n2) % modulus(category_type) + irrep = category_type((n1 + n2) % modulus(category_type)) + return [1], [irrep] end GradedAxes.dual(c::Z) = typeof(c)(mod(-category_label(c), modulus(c))) From 6cc612f5ae3c7aff24ade74ac5ec5fe0adc70ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 4 Oct 2024 09:30:08 -0400 Subject: [PATCH 148/194] categories_symmetrystyle --- NDTensors/src/lib/Sectors/src/category_product.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index b231fd439a..b826eca778 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -19,7 +19,7 @@ const TrivialSector{Categories<:Union{Tuple{},NamedTuple{()}}} = CategoryProduct TrivialSector() = CategoryProduct(()) # ================================= Sectors interface ==================================== -SymmetryStyle(T::Type{<:CategoryProduct}) = SymmetryStyle(categories_type(T)) +SymmetryStyle(T::Type{<:CategoryProduct}) = categories_symmetrystyle(categories_type(T)) function quantum_dimension(::NotAbelianStyle, s::CategoryProduct) return mapreduce(quantum_dimension, *, categories(s)) @@ -100,7 +100,7 @@ function categories_fusion_rule(cats1, cats2) end function recover_style(T::Type, fused) - style = SymmetryStyle(T) + style = categories_symmetrystyle(T) return recover_category_product_type(style, T, fused) end @@ -205,7 +205,7 @@ fusion_rule(::AbelianStyle, ::TrivialSector, c::CategoryProduct) = c CategoryProduct(t::Tuple) = _CategoryProduct(t) CategoryProduct(cats::AbstractCategory...) = CategoryProduct(cats) -function SymmetryStyle(T::Type{<:Tuple}) +function categories_symmetrystyle(T::Type{<:Tuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) end @@ -245,7 +245,7 @@ function CategoryProduct(pairs::Pair...) return CategoryProduct(NamedTuple{keys}(vals)) end -function SymmetryStyle(NT::Type{<:NamedTuple}) +function categories_symmetrystyle(NT::Type{<:NamedTuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) end From f3f6b9614e2c626fe0ee5c8b5a6c7c7541202447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 4 Oct 2024 10:10:42 -0400 Subject: [PATCH 149/194] non-abelian interface in tensor_product --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 17 ++++++++--------- .../src/lib/Sectors/src/abstractcategory.jl | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index cf4b997488..e286260f18 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -1,4 +1,4 @@ -using BlockArrays: AbstractBlockedUnitRange +using BlockArrays: AbstractBlockedUnitRange, blocklengths # Represents the range `1:1` or `Base.OneTo(1)`. struct OneToOne{T} <: AbstractUnitRange{T} end @@ -48,24 +48,23 @@ function fuse_labels(x, y) end function fuse_blocklengths(x::Integer, y::Integer) - return x * y + # return blocked unit range to keep non-abelian interface + return blockedrange([x * y]) end using ..LabelledNumbers: LabelledInteger, label, labelled, unlabel function fuse_blocklengths(x::LabelledInteger, y::LabelledInteger) - return labelled(unlabel(x) * unlabel(y), fuse_labels(label(x), label(y))) + # return blocked unit range to keep non-abelian interface + return blockedrange([labelled(x * y, fuse_labels(label(x), label(y)))]) end -flatten_maybe_nested(v::Vector{<:Integer}) = v -flatten_maybe_nested(v::Vector{<:AbstractGradedUnitRange}) = reduce(vcat, blocklengths.(v)) - using BlockArrays: blockedrange, blocks function tensor_product(a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRange) - maybe_nested = map(Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),))) do it + nested = map(Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),))) do it return mapreduce(length, fuse_blocklengths, it) end - blocklengths = flatten_maybe_nested(maybe_nested) - return blockedrange(blocklengths) + new_blocklengths = reduce(vcat, blocklengths.(nested)) + return blockedrange(new_blocklengths) end # convention: sort UnitRangeDual according to nondual blocks diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractcategory.jl index 52b5546a50..ceec8e3814 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractcategory.jl @@ -89,7 +89,7 @@ function GradedAxes.fuse_blocklengths( ::AbelianStyle, l1::LabelledInteger, l2::LabelledInteger ) fused = label(l1) ⊗ label(l2) - return labelled(l1 * l2, fused) + return gradedrange([labelled(l1 * l2, fused)]) end # cast to range From 7f9818342c9c6d7199cef1b01d3814da4519b652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 4 Oct 2024 12:53:03 -0400 Subject: [PATCH 150/194] define TrivialSector --- NDTensors/src/lib/Sectors/src/Sectors.jl | 1 + .../src/category_definitions/trivial.jl | 34 +++++++++++++++ .../src/lib/Sectors/src/category_product.jl | 42 ++++++++----------- .../lib/Sectors/test/test_category_product.jl | 37 +++++++++++----- .../src/lib/Sectors/test/test_fusion_rules.jl | 22 +++++++++- .../Sectors/test/test_simple_categories.jl | 22 ++++++++++ 6 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 NDTensors/src/lib/Sectors/src/category_definitions/trivial.jl diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index f6c8bca92b..bfafd17a07 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -5,6 +5,7 @@ include("abstractcategory.jl") include("category_definitions/fib.jl") include("category_definitions/ising.jl") include("category_definitions/o2.jl") +include("category_definitions/trivial.jl") include("category_definitions/su.jl") include("category_definitions/su2k.jl") include("category_definitions/u1.jl") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/trivial.jl b/NDTensors/src/lib/Sectors/src/category_definitions/trivial.jl new file mode 100644 index 0000000000..1e97fcc9b1 --- /dev/null +++ b/NDTensors/src/lib/Sectors/src/category_definitions/trivial.jl @@ -0,0 +1,34 @@ +# +# Trivial sector +# + +using ...GradedAxes: GradedAxes + +# Trivial is special as it does not have a label +struct TrivialSector <: AbstractCategory end + +SymmetryStyle(::Type{TrivialSector}) = AbelianStyle() + +trivial(::Type{TrivialSector}) = TrivialSector() + +GradedAxes.dual(::TrivialSector) = TrivialSector() + +Base.isless(::TrivialSector, ::TrivialSector) = false # bypass default that calls label + +# TrivialSector acts as trivial on any AbstractCategory +function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::AbstractCategory) + return to_gradedrange(c) +end +function fusion_rule(::NotAbelianStyle, c::AbstractCategory, ::TrivialSector) + return to_gradedrange(c) +end + +# abelian case: return Category +fusion_rule(::AbelianStyle, c::AbstractCategory, ::TrivialSector) = c +fusion_rule(::AbelianStyle, ::TrivialSector, c::AbstractCategory) = c +fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) = TrivialSector() + +# any trivial sector equals TrivialSector +Base.:(==)(c::AbstractCategory, ::TrivialSector) = istrivial(c) +Base.:(==)(::TrivialSector, c::AbstractCategory) = istrivial(c) +Base.:(==)(::TrivialSector, ::TrivialSector) = true diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl index b826eca778..8c86951969 100644 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ b/NDTensors/src/lib/Sectors/src/category_product.jl @@ -15,9 +15,6 @@ CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) categories(s::CategoryProduct) = s.cats -const TrivialSector{Categories<:Union{Tuple{},NamedTuple{()}}} = CategoryProduct{Categories} -TrivialSector() = CategoryProduct(()) - # ================================= Sectors interface ==================================== SymmetryStyle(T::Type{<:CategoryProduct}) = categories_symmetrystyle(categories_type(T)) @@ -99,6 +96,12 @@ function categories_fusion_rule(cats1, cats2) return shared_cat × diff_cat end +# edge case with empty categories +categories_fusion_rule(cats::Tuple, ::NamedTuple{()}) = CategoryProduct(cats) +categories_fusion_rule(::NamedTuple{()}, cats::Tuple) = CategoryProduct(cats) +categories_fusion_rule(cats::NamedTuple, ::Tuple{}) = CategoryProduct(cats) +categories_fusion_rule(::Tuple{}, cats::NamedTuple) = CategoryProduct(cats) + function recover_style(T::Type, fused) style = categories_symmetrystyle(T) return recover_category_product_type(style, T, fused) @@ -166,6 +169,14 @@ function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) end # ==================================== Fusion rules ====================================== +# cast AbstractCategory to CategoryProduct +function fusion_rule(style::SymmetryStyle, c1::CategoryProduct, c2::AbstractCategory) + return fusion_rule(style, c1, CategoryProduct(c2)) +end +function fusion_rule(style::SymmetryStyle, c1::AbstractCategory, c2::CategoryProduct) + return fusion_rule(style, CategoryProduct(c1), c2) +end + # generic case: fusion returns a GradedAxes, even for fusion with Empty function fusion_rule(::NotAbelianStyle, s1::CategoryProduct, s2::CategoryProduct) return to_gradedrange(categories_fusion_rule(categories(s1), categories(s2))) @@ -176,30 +187,11 @@ function fusion_rule(::AbelianStyle, s1::CategoryProduct, s2::CategoryProduct) return categories_fusion_rule(categories(s1), categories(s2)) end -# Empty case -function fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) - return CategoryProduct(()) -end - -# TrivialSector acts as trivial on any AbstractCategory, not just CategoryProduct -function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::AbstractCategory) - return to_gradedrange(c) -end -function fusion_rule(::NotAbelianStyle, c::AbstractCategory, ::TrivialSector) - return to_gradedrange(c) -end -function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::CategoryProduct) - return to_gradedrange(c) -end -function fusion_rule(::NotAbelianStyle, c::CategoryProduct, ::TrivialSector) - return to_gradedrange(c) -end - -# abelian case: return Category -fusion_rule(::AbelianStyle, c::AbstractCategory, ::TrivialSector) = c -fusion_rule(::AbelianStyle, ::TrivialSector, c::AbstractCategory) = c +# lift ambiguities for TrivialSector fusion_rule(::AbelianStyle, c::CategoryProduct, ::TrivialSector) = c fusion_rule(::AbelianStyle, ::TrivialSector, c::CategoryProduct) = c +fusion_rule(::NotAbelianStyle, c::CategoryProduct, ::TrivialSector) = to_gradedrange(c) +fusion_rule(::NotAbelianStyle, ::TrivialSector, c::CategoryProduct) = to_gradedrange(c) # =============================== Ordered implementation ================================= CategoryProduct(t::Tuple) = _CategoryProduct(t) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 6235e47a20..28f839403b 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -7,8 +7,8 @@ using NDTensors.Sectors: Ising, SU, SU2, - U1, TrivialSector, + U1, Z, block_dimensions, categories, @@ -62,6 +62,13 @@ end @test categories(s)[2] == SU2(1//2) @test categories(s)[3] == Fib("τ") @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0), SU2(0), Fib("1")) + + s = TrivialSector() × U1(3) × SU2(1 / 2) + @test length(categories(s)) == 3 + @test (@inferred_latest quantum_dimension(s)) == 2 + @test dual(s) == TrivialSector() × U1(-3) × SU2(1//2) + @test (@inferred_latest trivial(s)) == CategoryProduct(TrivialSector(), U1(0), SU2(0)) + @test s > trivial(s) end @testset "Ordered comparisons" begin @@ -70,6 +77,7 @@ end @test CategoryProduct(U1(1), SU2(0)) != CategoryProduct(U1(1), SU2(1)) @test CategoryProduct(U1(0), SU2(1)) != CategoryProduct(U1(1), SU2(1)) @test CategoryProduct(U1(1)) != CategoryProduct(U1(1), U1(0)) + @test CategoryProduct(U1(0), SU2(0)) == TrivialSector() # convention: categories must have same length to be compared @test CategoryProduct(U1(0)) < CategoryProduct((U1(1))) @@ -135,6 +143,8 @@ end @testset "Fusion of Abelian products" begin p1 = CategoryProduct(U1(1)) p2 = CategoryProduct(U1(2)) + @test (@inferred p1 ⊗ TrivialSector()) == p1 + @test (@inferred TrivialSector() ⊗ p2) == p2 @test (@inferred_latest p1 ⊗ p2) == CategoryProduct(U1(3)) p11 = U1(1) × U1(1) @@ -151,7 +161,12 @@ end @testset "Fusion of NonAbelian products" begin p0 = CategoryProduct(SU2(0)) ph = CategoryProduct(SU2(1//2)) - @test space_isequal((@inferred p0 ⊗ ph), gradedrange([CategoryProduct(SU2(1//2)) => 1])) + @test space_isequal( + (@inferred p0 ⊗ TrivialSector()), gradedrange([CategoryProduct(SU2(0)) => 1]) + ) + @test space_isequal( + (@inferred TrivialSector() ⊗ ph), gradedrange([CategoryProduct(SU2(1//2)) => 1]) + ) phh = SU2(1//2) × SU2(1//2) @test space_isequal( @@ -397,7 +412,7 @@ end end @testset "Fusion of Abelian products" begin - q00 = TrivialSector() + q00 = CategoryProduct(;) q10 = CategoryProduct(; A=U1(1)) q01 = CategoryProduct(; B=U1(1)) q11 = CategoryProduct(; A=U1(1), B=U1(1)) @@ -418,7 +433,7 @@ end end @testset "Fusion of NonAbelian products" begin - p0 = TrivialSector() + p0 = CategoryProduct(;) pha = CategoryProduct(; A=SU2(1//2)) phb = CategoryProduct(; B=SU2(1//2)) phab = CategoryProduct(; A=SU2(1//2), B=SU2(1//2)) @@ -543,6 +558,7 @@ end @testset "Empty category" begin for s in (CategoryProduct(()), CategoryProduct((;))) + @test s == TrivialSector() @test s == CategoryProduct(()) @test s == CategoryProduct((;)) @test (@inferred dual(s)) == s @@ -561,13 +577,12 @@ end @test (@inferred CategoryProduct(U1(1)) × s) == CategoryProduct(U1(1)) @test (@inferred CategoryProduct(; A=U1(1)) × s) == CategoryProduct(; A=U1(1)) - # Empty acts as trivial - @test (@inferred_latest U1(1) ⊗ s) == U1(1) - @test (@inferred SU2(0) ⊗ s) == gradedrange([SU2(0) => 1]) - @test (@inferred Fib("τ") ⊗ s) == gradedrange([Fib("τ") => 1]) - @test (@inferred_latest s ⊗ U1(1)) == U1(1) - @test (@inferred s ⊗ SU2(0)) == gradedrange([SU2(0) => 1]) - @test (@inferred s ⊗ Fib("τ")) == gradedrange([Fib("τ") => 1]) + @test (@inferred_latest U1(1) ⊗ s) == CategoryProduct(U1(1)) + @test (@inferred SU2(0) ⊗ s) == gradedrange([CategoryProduct(SU2(0)) => 1]) + @test (@inferred Fib("τ") ⊗ s) == gradedrange([CategoryProduct(Fib("τ")) => 1]) + @test (@inferred_latest s ⊗ U1(1)) == CategoryProduct(U1(1)) + @test (@inferred s ⊗ SU2(0)) == gradedrange([CategoryProduct(SU2(0)) => 1]) + @test (@inferred s ⊗ Fib("τ")) == gradedrange([CategoryProduct(Fib("τ")) => 1]) @test (@inferred_latest CategoryProduct(U1(1)) ⊗ s) == CategoryProduct(U1(1)) @test (@inferred_latest CategoryProduct(SU2(0)) ⊗ s) == diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 08d8d4d108..4d8a26ca5a 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -2,7 +2,18 @@ using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange, flip, tensor_product using NDTensors.Sectors: - ⊗, Fib, Ising, O2, SU, SU2, U1, Z, block_dimensions, quantum_dimension, trivial + ⊗, + Fib, + Ising, + O2, + SU, + SU2, + TrivialSector, + U1, + Z, + block_dimensions, + quantum_dimension, + trivial using Test: @inferred, @test, @testset, @test_throws @testset "Simple object fusion rules" begin @@ -15,6 +26,11 @@ using Test: @inferred, @test, @testset, @test_throws @test z1 ⊗ z1 == z0 @test (@inferred z0 ⊗ z0) == z0 # no better way, see Julia PR 23426 + q = TrivialSector() + @test (@inferred q ⊗ q) == q + @test (@inferred q ⊗ z0) == z0 + @test (@inferred z1 ⊗ q) == z1 + # using GradedAxes interface @test space_isequal(fusion_product(z0, z0), gradedrange([z0 => 1])) @test space_isequal(fusion_product(z0, z1), gradedrange([z1 => 1])) @@ -42,6 +58,10 @@ using Test: @inferred, @test, @testset, @test_throws s12 = O2(1//2) s1 = O2(1) + q = TrivialSector() + @test space_isequal((@inferred s0e ⊗ q), gradedrange([s0e => 1])) + @test space_isequal((@inferred q ⊗ s0o), gradedrange([s0o => 1])) + @test space_isequal((@inferred s0e ⊗ s0e), gradedrange([s0e => 1])) @test space_isequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) @test space_isequal((@inferred s0o ⊗ s0e), gradedrange([s0o => 1])) diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl index de64ab31b0..85b44adffc 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_categories.jl @@ -6,6 +6,7 @@ using NDTensors.Sectors: O2, SU, SU2, + TrivialSector, U1, Z, adjoint, @@ -15,6 +16,18 @@ using NDTensors.Sectors: trivial using Test: @inferred, @test, @testset, @test_throws @testset "Test Category Types" begin + @testset "TrivialSector" begin + q = TrivialSector() + + @test (@inferred quantum_dimension(q)) == 1 + @test q == q + @test trivial(q) == q + @test istrivial(q) + + @test dual(q) == q + @test !isless(q, q) + end + @testset "U(1)" begin q1 = U1(1) q2 = U1(2) @@ -27,6 +40,8 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(q1) == U1(0) @test trivial(U1) == U1(0) @test istrivial(U1(0)) + @test U1(0) == TrivialSector() + @test TrivialSector() == U1(0) @test dual(U1(2)) == U1(-2) @test isless(U1(1), U1(2)) @@ -39,6 +54,7 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(Z{2}) == Z{2}(0) @test istrivial(Z{2}(0)) + @test Z{2}(0) == TrivialSector() @test quantum_dimension(z0) == 1 @test quantum_dimension(z1) == 1 @@ -60,6 +76,7 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(O2) == s0e @test istrivial(s0e) + @test s0e == TrivialSector() @test (@inferred quantum_dimension(s0e)) == 1 @test (@inferred quantum_dimension(s0o)) == 1 @@ -90,6 +107,7 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(SU{2}) == SU2(0) @test istrivial(SU2(0)) + @test SU2(0) == TrivialSector() @test fundamental(SU{2}) == SU2(1//2) @test adjoint(SU{2}) == SU2(1) @@ -116,6 +134,8 @@ using Test: @inferred, @test, @testset, @test_throws @test istrivial(SU{3}((0, 0))) @test trivial(SU{4}) == SU{4}((0, 0, 0)) @test istrivial(SU{4}((0, 0, 0))) + @test SU{3}((0, 0)) == TrivialSector() + @test SU{4}((0, 0, 0)) == TrivialSector() @test fundamental(SU{3}) == f3 @test adjoint(SU{3}) == ad3 @@ -144,6 +164,7 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(Fib) == ı @test istrivial(ı) + @test ı == TrivialSector() @test dual(ı) == ı @test dual(τ) == τ @@ -159,6 +180,7 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(Ising) == ı @test istrivial(ı) + @test ı == TrivialSector() @test dual(ı) == ı @test dual(σ) == σ From 9fac111862cefc80395ddc1aa6e919b6b9d50235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 4 Oct 2024 12:54:42 -0400 Subject: [PATCH 151/194] use mapreduce --- NDTensors/src/lib/GradedAxes/src/fusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index e286260f18..3c3b58bc91 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -63,7 +63,7 @@ function tensor_product(a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRan nested = map(Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),))) do it return mapreduce(length, fuse_blocklengths, it) end - new_blocklengths = reduce(vcat, blocklengths.(nested)) + new_blocklengths = mapreduce(blocklengths, vcat, nested) return blockedrange(new_blocklengths) end From 895e7ac5157f90e5cbda39df6c9a4c53afe24f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 4 Oct 2024 14:25:44 -0400 Subject: [PATCH 152/194] fix tests for julia 1.6 --- .../src/lib/Sectors/test/test_category_product.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_category_product.jl index 28f839403b..8b592db25a 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_category_product.jl @@ -143,8 +143,8 @@ end @testset "Fusion of Abelian products" begin p1 = CategoryProduct(U1(1)) p2 = CategoryProduct(U1(2)) - @test (@inferred p1 ⊗ TrivialSector()) == p1 - @test (@inferred TrivialSector() ⊗ p2) == p2 + @test (@inferred_latest p1 ⊗ TrivialSector()) == p1 + @test (@inferred_latest TrivialSector() ⊗ p2) == p2 @test (@inferred_latest p1 ⊗ p2) == CategoryProduct(U1(3)) p11 = U1(1) × U1(1) @@ -162,10 +162,11 @@ end p0 = CategoryProduct(SU2(0)) ph = CategoryProduct(SU2(1//2)) @test space_isequal( - (@inferred p0 ⊗ TrivialSector()), gradedrange([CategoryProduct(SU2(0)) => 1]) + (@inferred_latest p0 ⊗ TrivialSector()), gradedrange([CategoryProduct(SU2(0)) => 1]) ) @test space_isequal( - (@inferred TrivialSector() ⊗ ph), gradedrange([CategoryProduct(SU2(1//2)) => 1]) + (@inferred_latest TrivialSector() ⊗ ph), + gradedrange([CategoryProduct(SU2(1//2)) => 1]), ) phh = SU2(1//2) × SU2(1//2) @@ -563,12 +564,12 @@ end @test s == CategoryProduct((;)) @test (@inferred dual(s)) == s @test (@inferred s × s) == s - @test (@inferred s ⊗ s) == s + @test (@inferred_latest s ⊗ s) == s @test (@inferred quantum_dimension(s)) == 1 @test (@inferred_latest trivial(s)) == s g0 = gradedrange([s => 2]) - @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) + @test space_isequal((@inferred_latest fusion_product(g0, g0)), gradedrange([s => 4])) @test (@inferred s × U1(1)) == CategoryProduct(U1(1)) @test (@inferred s × CategoryProduct(U1(1))) == CategoryProduct(U1(1)) From d4794ed04c3be715e593bfa827c0ccd8add78928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 4 Oct 2024 15:14:43 -0400 Subject: [PATCH 153/194] rename Category to Sector --- NDTensors/src/lib/Sectors/src/Sectors.jl | 20 +- ...{abstractcategory.jl => abstractsector.jl} | 54 +-- .../src/lib/Sectors/src/category_product.jl | 278 ------------- .../fib.jl | 6 +- .../ising.jl | 10 +- .../o2.jl | 18 +- .../su.jl | 14 +- .../su2k.jl | 4 +- .../trivial.jl | 16 +- .../u1.jl | 6 +- .../zn.jl | 6 +- .../src/lib/Sectors/src/sector_product.jl | 278 +++++++++++++ .../src/lib/Sectors/test/test_fusion_rules.jl | 6 +- ...gory_product.jl => test_sector_product.jl} | 379 +++++++++--------- ...e_categories.jl => test_simple_sectors.jl} | 2 +- 15 files changed, 547 insertions(+), 550 deletions(-) rename NDTensors/src/lib/Sectors/src/{abstractcategory.jl => abstractsector.jl} (66%) delete mode 100644 NDTensors/src/lib/Sectors/src/category_product.jl rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/fib.jl (88%) rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/ising.jl (73%) rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/o2.jl (73%) rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/su.jl (95%) rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/su2k.jl (88%) rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/trivial.jl (59%) rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/u1.jl (81%) rename NDTensors/src/lib/Sectors/src/{category_definitions => sector_definitions}/zn.jl (76%) create mode 100644 NDTensors/src/lib/Sectors/src/sector_product.jl rename NDTensors/src/lib/Sectors/test/{test_category_product.jl => test_sector_product.jl} (54%) rename NDTensors/src/lib/Sectors/test/{test_simple_categories.jl => test_simple_sectors.jl} (99%) diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/Sectors/src/Sectors.jl index bfafd17a07..e76d487236 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/Sectors/src/Sectors.jl @@ -1,17 +1,17 @@ module Sectors include("symmetry_style.jl") -include("abstractcategory.jl") -include("category_definitions/fib.jl") -include("category_definitions/ising.jl") -include("category_definitions/o2.jl") -include("category_definitions/trivial.jl") -include("category_definitions/su.jl") -include("category_definitions/su2k.jl") -include("category_definitions/u1.jl") -include("category_definitions/zn.jl") +include("abstractsector.jl") +include("sector_definitions/fib.jl") +include("sector_definitions/ising.jl") +include("sector_definitions/o2.jl") +include("sector_definitions/trivial.jl") +include("sector_definitions/su.jl") +include("sector_definitions/su2k.jl") +include("sector_definitions/u1.jl") +include("sector_definitions/zn.jl") include("namedtuple_operations.jl") -include("category_product.jl") +include("sector_product.jl") end diff --git a/NDTensors/src/lib/Sectors/src/abstractcategory.jl b/NDTensors/src/lib/Sectors/src/abstractsector.jl similarity index 66% rename from NDTensors/src/lib/Sectors/src/abstractcategory.jl rename to NDTensors/src/lib/Sectors/src/abstractsector.jl index ceec8e3814..4088733ef7 100644 --- a/NDTensors/src/lib/Sectors/src/abstractcategory.jl +++ b/NDTensors/src/lib/Sectors/src/abstractsector.jl @@ -1,15 +1,15 @@ -# This file defines the abstract type AbstractCategory -# all fusion categories (Z{2}, SU2, Ising...) are subtypes of AbstractCategory +# This file defines the abstract type AbstractSector +# all fusion categories (Z{2}, SU2, Ising...) are subtypes of AbstractSector using BlockArrays: blocklengths using ..LabelledNumbers: LabelledInteger, label, label_type, labelled, unlabel, unlabel_type using ..GradedAxes: GradedAxes, blocklabels, fuse_blocklengths, gradedrange, tensor_product -abstract type AbstractCategory end +abstract type AbstractSector end # =================================== Base interface ===================================== -function Base.isless(c1::C, c2::C) where {C<:AbstractCategory} - return isless(category_label(c1), category_label(c2)) +function Base.isless(c1::C, c2::C) where {C<:AbstractSector} + return isless(sector_label(c1), sector_label(c2)) end # ================================= Sectors interface ==================================== @@ -24,10 +24,10 @@ function trivial(type::Type) return error("`trivial` not defined for type $(type).") end -istrivial(c::AbstractCategory) = (c == trivial(c)) +istrivial(c::AbstractSector) = (c == trivial(c)) -function category_label(c::AbstractCategory) - return error("method `category_label` not defined for type $(typeof(c))") +function sector_label(c::AbstractSector) + return error("method `sector_label` not defined for type $(typeof(c))") end block_dimensions(g::AbstractUnitRange) = block_dimensions(SymmetryStyle(g), g) @@ -38,41 +38,41 @@ end quantum_dimension(x) = quantum_dimension(SymmetryStyle(x), x) -function quantum_dimension(::NotAbelianStyle, c::AbstractCategory) +function quantum_dimension(::NotAbelianStyle, c::AbstractSector) return error("method `quantum_dimension` not defined for type $(typeof(c))") end -quantum_dimension(::AbelianStyle, ::AbstractCategory) = 1 +quantum_dimension(::AbelianStyle, ::AbstractSector) = 1 quantum_dimension(::AbelianStyle, g::AbstractUnitRange) = length(g) quantum_dimension(::NotAbelianStyle, g::AbstractUnitRange) = sum(block_dimensions(g)) # =============================== Fusion rule interface ================================== -⊗(c1::AbstractCategory, c2::AbstractCategory) = fusion_rule(c1, c2) +⊗(c1::AbstractSector, c2::AbstractSector) = fusion_rule(c1, c2) -function fusion_rule(c1::AbstractCategory, c2::AbstractCategory) +function fusion_rule(c1::AbstractSector, c2::AbstractSector) return fusion_rule(combine_styles(SymmetryStyle(c1), SymmetryStyle(c2)), c1, c2) end -function fusion_rule(::NotAbelianStyle, c1::C, c2::C) where {C<:AbstractCategory} - degen, sectors = label_fusion_rule(C, category_label(c1), category_label(c2)) +function fusion_rule(::NotAbelianStyle, c1::C, c2::C) where {C<:AbstractSector} + degen, sectors = label_fusion_rule(C, sector_label(c1), sector_label(c2)) return gradedrange(labelled.(degen, sectors)) end -# abelian case: return Category -function fusion_rule(::AbelianStyle, c1::C, c2::C) where {C<:AbstractCategory} - _, sectors = label_fusion_rule(C, category_label(c1), category_label(c2)) +# abelian case: return Sector +function fusion_rule(::AbelianStyle, c1::C, c2::C) where {C<:AbstractSector} + _, sectors = label_fusion_rule(C, sector_label(c1), sector_label(c2)) return only(sectors) end -function label_fusion_rule(category_type::Type{<:AbstractCategory}, ::Any, ::Any) - return error("`label_fusion_rule` not defined for type $(category_type).") +function label_fusion_rule(sector_type::Type{<:AbstractSector}, ::Any, ::Any) + return error("`label_fusion_rule` not defined for type $(sector_type).") end # ================================ GradedAxes interface ================================== # tensor_product interface function GradedAxes.fuse_blocklengths( - l1::LabelledInteger{<:Integer,<:AbstractCategory}, - l2::LabelledInteger{<:Integer,<:AbstractCategory}, + l1::LabelledInteger{<:Integer,<:AbstractSector}, + l2::LabelledInteger{<:Integer,<:AbstractSector}, ) return fuse_blocklengths(combine_styles(SymmetryStyle(l1), SymmetryStyle(l2)), l1, l2) end @@ -93,23 +93,23 @@ function GradedAxes.fuse_blocklengths( end # cast to range -to_gradedrange(c::AbstractCategory) = to_gradedrange(labelled(1, c)) +to_gradedrange(c::AbstractSector) = to_gradedrange(labelled(1, c)) to_gradedrange(l::LabelledInteger) = gradedrange([l]) to_gradedrange(g::AbstractUnitRange) = g -# allow to fuse a category with a GradedUnitRange -function GradedAxes.tensor_product(c::AbstractCategory, g::AbstractUnitRange) +# allow to fuse a Sector with a GradedUnitRange +function GradedAxes.tensor_product(c::AbstractSector, g::AbstractUnitRange) return tensor_product(to_gradedrange(c), g) end -function GradedAxes.tensor_product(g::AbstractUnitRange, c::AbstractCategory) +function GradedAxes.tensor_product(g::AbstractUnitRange, c::AbstractSector) return tensor_product(g, to_gradedrange(c)) end -function GradedAxes.tensor_product(c1::AbstractCategory, c2::AbstractCategory) +function GradedAxes.tensor_product(c1::AbstractSector, c2::AbstractSector) return to_gradedrange(fusion_rule(c1, c2)) end -function GradedAxes.fusion_product(c::AbstractCategory) +function GradedAxes.fusion_product(c::AbstractSector) return to_gradedrange(c) end diff --git a/NDTensors/src/lib/Sectors/src/category_product.jl b/NDTensors/src/lib/Sectors/src/category_product.jl deleted file mode 100644 index 8c86951969..0000000000 --- a/NDTensors/src/lib/Sectors/src/category_product.jl +++ /dev/null @@ -1,278 +0,0 @@ -# This files defines a structure for Cartesian product of 2 or more fusion categories -# e.g. U(1)×U(1), U(1)×SU2(2)×SU(3) - -using BlockArrays: blocklengths -using ..LabelledNumbers: LabelledInteger, label, labelled, unlabel -using ..GradedAxes: AbstractGradedUnitRange, GradedAxes, dual - -# ===================================== Definition ======================================= -struct CategoryProduct{Categories} <: AbstractCategory - cats::Categories - global _CategoryProduct(l) = new{typeof(l)}(l) -end - -CategoryProduct(c::CategoryProduct) = _CategoryProduct(categories(c)) - -categories(s::CategoryProduct) = s.cats - -# ================================= Sectors interface ==================================== -SymmetryStyle(T::Type{<:CategoryProduct}) = categories_symmetrystyle(categories_type(T)) - -function quantum_dimension(::NotAbelianStyle, s::CategoryProduct) - return mapreduce(quantum_dimension, *, categories(s)) -end - -# use map instead of broadcast to support both Tuple and NamedTuple -GradedAxes.dual(s::CategoryProduct) = CategoryProduct(map(dual, categories(s))) - -function trivial(type::Type{<:CategoryProduct}) - return CategoryProduct(categories_trivial(categories_type(type))) -end - -# =================================== Base interface ===================================== -function Base.:(==)(A::CategoryProduct, B::CategoryProduct) - return categories_isequal(categories(A), categories(B)) -end - -function Base.show(io::IO, s::CategoryProduct) - (length(categories(s)) < 2) && print(io, "sector") - print(io, "(") - symbol = "" - for p in pairs(categories(s)) - print(io, symbol) - category_show(io, p[1], p[2]) - symbol = " × " - end - return print(io, ")") -end - -category_show(io::IO, ::Int, v) = print(io, v) -category_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") - -function Base.isless(s1::CategoryProduct, s2::CategoryProduct) - return categories_isless(categories(s1), categories(s2)) -end - -# ======================================= shared ========================================= -# there are 2 implementations for CategoryProduct -# - ordered-like with a Tuple -# - dictionary-like with a NamedTuple - -categories_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) -function categories_isequal(nt::NamedTuple, ::Tuple{}) - return categories_isequal(nt, (;)) -end -function categories_isequal(::Tuple{}, nt::NamedTuple) - return categories_isequal((;), nt) -end -function categories_isequal(nt1::NamedTuple, nt2::NamedTuple) - return ==(sym_categories_insert_unspecified(nt1, nt2)...) -end - -# get clean results when mixing implementations -categories_isequal(::Tuple, ::NamedTuple) = false -categories_isequal(::NamedTuple, ::Tuple) = false - -categories_isless(::Tuple, ::Tuple) = throw(ArgumentError("Not implemented")) -categories_isless(t1::T, t2::T) where {T<:Tuple} = t1 < t2 -function categories_isless(nt::NamedTuple, ::Tuple{}) - return categories_isless(nt, (;)) -end -function categories_isless(::Tuple{}, nt::NamedTuple) - return categories_isless((;), nt) -end -function categories_isless(nt1::NamedTuple, nt2::NamedTuple) - return isless(sym_categories_insert_unspecified(nt1, nt2)...) -end - -categories_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) -categories_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) - -categories_type(::Type{<:CategoryProduct{T}}) where {T} = T - -function categories_fusion_rule(cats1, cats2) - shared_cat = shared_categories_fusion_rule(categories_common(cats1, cats2)...) - diff_cat = CategoryProduct(categories_diff(cats1, cats2)) - return shared_cat × diff_cat -end - -# edge case with empty categories -categories_fusion_rule(cats::Tuple, ::NamedTuple{()}) = CategoryProduct(cats) -categories_fusion_rule(::NamedTuple{()}, cats::Tuple) = CategoryProduct(cats) -categories_fusion_rule(cats::NamedTuple, ::Tuple{}) = CategoryProduct(cats) -categories_fusion_rule(::Tuple{}, cats::NamedTuple) = CategoryProduct(cats) - -function recover_style(T::Type, fused) - style = categories_symmetrystyle(T) - return recover_category_product_type(style, T, fused) -end - -function recover_category_product_type(::AbelianStyle, T::Type, fused) - return recover_category_product_type(T, fused) -end - -function recover_category_product_type(::NotAbelianStyle, T::Type, fused) - # here fused contains at least one graded unit range. - # convert eg. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} - g = reduce(×, fused) - # convention: keep unsorted blocklabels as produced by F order loops in × - type_fixed = recover_category_product_type(T, g) - return type_fixed -end - -function recover_category_product_type(T::Type, g0::AbstractGradedUnitRange) - new_labels = recover_category_product_type.(T, blocklabels(g0)) - new_blocklengths = labelled.(unlabel.(blocklengths(g0)), new_labels) - return gradedrange(new_blocklengths) -end - -function recover_category_product_type(T::Type, c::AbstractCategory) - return recover_category_product_type(T, CategoryProduct(c)) -end - -function recover_category_product_type(T::Type, c::CategoryProduct) - return recover_category_product_type(T, categories(c)) -end - -function recover_category_product_type(T::Type{<:CategoryProduct}, cats) - return recover_category_product_type(categories_type(T), cats) -end - -function recover_category_product_type(T::Type, cats) - return CategoryProduct(T(cats)) -end - -# ================================= Cartesian Product ==================================== -×(c1::AbstractCategory, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) -function ×(p1::CategoryProduct, p2::CategoryProduct) - return CategoryProduct(categories_product(categories(p1), categories(p2))) -end - -×(a, g::AbstractUnitRange) = ×(to_gradedrange(a), g) -×(g::AbstractUnitRange, b) = ×(g, to_gradedrange(b)) -×(nt1::NamedTuple, nt2::NamedTuple) = ×(CategoryProduct(nt1), CategoryProduct(nt2)) -×(c1::NamedTuple, c2::AbstractCategory) = ×(CategoryProduct(c1), CategoryProduct(c2)) -×(c1::AbstractCategory, c2::NamedTuple) = ×(CategoryProduct(c1), CategoryProduct(c2)) - -function ×(l1::LabelledInteger, l2::LabelledInteger) - c3 = label(l1) × label(l2) - m3 = unlabel(l1) * unlabel(l2) - return labelled(m3, c3) -end - -function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) - v = map( - ((l1, l2),) -> l1 × l2, - Iterators.flatten((Iterators.product(blocklengths(g1), blocklengths(g2)),),), - ) - return gradedrange(v) -end - -# ==================================== Fusion rules ====================================== -# cast AbstractCategory to CategoryProduct -function fusion_rule(style::SymmetryStyle, c1::CategoryProduct, c2::AbstractCategory) - return fusion_rule(style, c1, CategoryProduct(c2)) -end -function fusion_rule(style::SymmetryStyle, c1::AbstractCategory, c2::CategoryProduct) - return fusion_rule(style, CategoryProduct(c1), c2) -end - -# generic case: fusion returns a GradedAxes, even for fusion with Empty -function fusion_rule(::NotAbelianStyle, s1::CategoryProduct, s2::CategoryProduct) - return to_gradedrange(categories_fusion_rule(categories(s1), categories(s2))) -end - -# Abelian case: fusion returns CategoryProduct -function fusion_rule(::AbelianStyle, s1::CategoryProduct, s2::CategoryProduct) - return categories_fusion_rule(categories(s1), categories(s2)) -end - -# lift ambiguities for TrivialSector -fusion_rule(::AbelianStyle, c::CategoryProduct, ::TrivialSector) = c -fusion_rule(::AbelianStyle, ::TrivialSector, c::CategoryProduct) = c -fusion_rule(::NotAbelianStyle, c::CategoryProduct, ::TrivialSector) = to_gradedrange(c) -fusion_rule(::NotAbelianStyle, ::TrivialSector, c::CategoryProduct) = to_gradedrange(c) - -# =============================== Ordered implementation ================================= -CategoryProduct(t::Tuple) = _CategoryProduct(t) -CategoryProduct(cats::AbstractCategory...) = CategoryProduct(cats) - -function categories_symmetrystyle(T::Type{<:Tuple}) - return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) -end - -categories_product(::NamedTuple{()}, l1::Tuple) = l1 -categories_product(l2::Tuple, ::NamedTuple{()}) = l2 -categories_product(l1::Tuple, l2::Tuple) = (l1..., l2...) - -categories_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) - -function categories_common(t1::Tuple, t2::Tuple) - n = min(length(t1), length(t2)) - return t1[begin:n], t2[begin:n] -end - -function categories_diff(t1::Tuple, t2::Tuple) - n1 = length(t1) - n2 = length(t2) - return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] -end - -function shared_categories_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} - fused = map(fusion_rule, shared1, shared2) - return recover_style(T, fused) -end - -# =========================== Dictionary-like implementation ============================= -function CategoryProduct(nt::NamedTuple) - categories = sort_keys(nt) - return _CategoryProduct(categories) -end - -CategoryProduct(; kws...) = CategoryProduct((; kws...)) - -function CategoryProduct(pairs::Pair...) - keys = ntuple(n -> Symbol(pairs[n][1]), length(pairs)) - vals = ntuple(n -> pairs[n][2], length(pairs)) - return CategoryProduct(NamedTuple{keys}(vals)) -end - -function categories_symmetrystyle(NT::Type{<:NamedTuple}) - return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) -end - -function sym_categories_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) - return categories_insert_unspecified(nt1, nt2), categories_insert_unspecified(nt2, nt1) -end - -function categories_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) - diff1 = categories_trivial(typeof(setdiff_keys(nt2, nt1))) - return sort_keys(union_keys(nt1, diff1)) -end - -categories_product(l1::NamedTuple, ::Tuple{}) = l1 -categories_product(::Tuple{}, l2::NamedTuple) = l2 -function categories_product(l1::NamedTuple, l2::NamedTuple) - if length(intersect_keys(l1, l2)) > 0 - throw(ArgumentError("Cannot define product of shared keys")) - end - return union_keys(l1, l2) -end - -function categories_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} - return NamedTuple{Keys}(trivial.(fieldtypes(type))) -end - -function categories_common(nt1::NamedTuple, nt2::NamedTuple) - # CategoryProduct(nt::NamedTuple) sorts keys at init - @assert issorted(keys(nt1)) - @assert issorted(keys(nt2)) - return intersect_keys(nt1, nt2), intersect_keys(nt2, nt1) -end - -categories_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) - -function shared_categories_fusion_rule(shared1::T, shared2::T) where {T<:NamedTuple} - fused = map(fusion_rule, values(shared1), values(shared2)) - return recover_style(T, fused) -end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/fib.jl similarity index 88% rename from NDTensors/src/lib/Sectors/src/category_definitions/fib.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/fib.jl index d0a7aba710..a561c9ed5d 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/fib.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/fib.jl @@ -5,7 +5,7 @@ # using ..GradedAxes: GradedAxes -struct Fib <: AbstractCategory +struct Fib <: AbstractSector l::Int end @@ -23,7 +23,7 @@ SymmetryStyle(::Type{Fib}) = NotAbelianStyle() GradedAxes.dual(f::Fib) = f -category_label(f::Fib) = f.l +sector_label(f::Fib) = f.l trivial(::Type{Fib}) = Fib(0) @@ -32,7 +32,7 @@ quantum_dimension(::NotAbelianStyle, f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) # Fusion rules identical to su2₃ function label_fusion_rule(::Type{Fib}, l1, l2) degen, suk_sectors = label_fusion_rule(su2{3}, l1, l2) - sectors = Fib.(category_label.(suk_sectors)) + sectors = Fib.(sector_label.(suk_sectors)) return degen, sectors end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/ising.jl similarity index 73% rename from NDTensors/src/lib/Sectors/src/category_definitions/ising.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/ising.jl index 1eb3f537da..d055a2cba1 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/ising.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/ising.jl @@ -7,7 +7,7 @@ using HalfIntegers: Half, twice using ..GradedAxes: GradedAxes -struct Ising <: AbstractCategory +struct Ising <: AbstractSector l::Half{Int} end @@ -23,21 +23,21 @@ SymmetryStyle(::Type{Ising}) = NotAbelianStyle() GradedAxes.dual(i::Ising) = i -category_label(i::Ising) = i.l +sector_label(i::Ising) = i.l trivial(::Type{Ising}) = Ising(0) -quantum_dimension(::NotAbelianStyle, i::Ising) = (category_label(i) == 1//2) ? √2 : 1.0 +quantum_dimension(::NotAbelianStyle, i::Ising) = (sector_label(i) == 1//2) ? √2 : 1.0 # Fusion rules identical to su2₂ function label_fusion_rule(::Type{Ising}, l1, l2) degen, suk_sectors = label_fusion_rule(su2{2}, l1, l2) - sectors = Ising.(category_label.(suk_sectors)) + sectors = Ising.(sector_label.(suk_sectors)) return degen, sectors end # TODO: Use `Val` dispatch here? -label_to_str(i::Ising) = ("1", "σ", "ψ")[twice(category_label(i)) + 1] +label_to_str(i::Ising) = ("1", "σ", "ψ")[twice(sector_label(i)) + 1] function Base.show(io::IO, f::Ising) return print(io, "Ising(", label_to_str(f), ")") diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/o2.jl similarity index 73% rename from NDTensors/src/lib/Sectors/src/category_definitions/o2.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/o2.jl index 48d208e189..a5391cf1ad 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/o2.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/o2.jl @@ -16,23 +16,23 @@ using ..GradedAxes: GradedAxes # - l=0 for trivial # - l=-1 for zero odd # - l=+|m| for Sz=±|m| -struct O2 <: AbstractCategory +struct O2 <: AbstractSector l::Half{Int} end SymmetryStyle(::Type{O2}) = NotAbelianStyle() -category_label(s::O2) = s.l +sector_label(s::O2) = s.l trivial(::Type{O2}) = O2(0) zero_odd(::Type{O2}) = O2(-1) -is_zero_even_or_odd(s::O2) = is_zero_even_or_odd(category_label(s)) -iszero_odd(s::O2) = iszero_odd(category_label(s)) +is_zero_even_or_odd(s::O2) = is_zero_even_or_odd(sector_label(s)) +iszero_odd(s::O2) = iszero_odd(sector_label(s)) is_zero_even_or_odd(l::HalfInteger) = iszero_even(l) || iszero_odd(l) -iszero_even(l::HalfInteger) = l == category_label(trivial(O2)) -iszero_odd(l::HalfInteger) = l == category_label(zero_odd(O2)) +iszero_even(l::HalfInteger) = l == sector_label(trivial(O2)) +iszero_odd(l::HalfInteger) = l == sector_label(zero_odd(O2)) quantum_dimension(::NotAbelianStyle, s::O2) = 2 - is_zero_even_or_odd(s) @@ -44,7 +44,7 @@ function Base.show(io::IO, s::O2) elseif istrivial(s) disp = "0e" else - disp = "±" * string(category_label(s)) + disp = "±" * string(sector_label(s)) end return print(io, "O(2)[", disp, "]") end @@ -53,7 +53,7 @@ function label_fusion_rule(::Type{O2}, l1, l2) if is_zero_even_or_odd(l1) degens = [1] if is_zero_even_or_odd(l2) - labels = l1 == l2 ? [category_label(trivial(O2))] : [category_label(zero_odd(O2))] + labels = l1 == l2 ? [sector_label(trivial(O2))] : [sector_label(zero_odd(O2))] else labels = [l2] end @@ -64,7 +64,7 @@ function label_fusion_rule(::Type{O2}, l1, l2) else if l1 == l2 degens = [1, 1, 1] - labels = [category_label(zero_odd(O2)), category_label(trivial(O2)), 2 * l1] + labels = [sector_label(zero_odd(O2)), sector_label(trivial(O2)), 2 * l1] else degens = [1, 1] labels = [abs(l1 - l2), l1 + l2] diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/su.jl similarity index 95% rename from NDTensors/src/lib/Sectors/src/category_definitions/su.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/su.jl index 6707c17dbc..7174ce1b5c 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/su.jl @@ -5,7 +5,7 @@ using HalfIntegers: HalfInteger, half, twice using ...GradedAxes: GradedAxes -struct SU{N,M} <: AbstractCategory +struct SU{N,M} <: AbstractSector # l is the first row of the # Gelfand-Tsetlin (GT) pattern describing # an SU(N) irrep @@ -25,7 +25,7 @@ SU(t::Tuple) = SU{length(t) + 1}(t) # infer N from tuple length SymmetryStyle(::Type{<:SU}) = NotAbelianStyle() -category_label(s::SU) = s.l +sector_label(s::SU) = s.l groupdim(::SU{N}) where {N} = N @@ -37,7 +37,7 @@ adjoint(::Type{<:SU{N}}) where {N} = SU{N}((ntuple(i -> 1 + (i == 1), Val(N - 1) function quantum_dimension(::NotAbelianStyle, s::SU) N = groupdim(s) - l = (category_label(s)..., 0) + l = (sector_label(s)..., 0) d = 1 for k1 in 1:N, k2 in (k1 + 1):N d *= ((k2 - k1) + (l[k1] - l[k2]))//(k2 - k1) @@ -46,13 +46,13 @@ function quantum_dimension(::NotAbelianStyle, s::SU) end function GradedAxes.dual(s::SU) - l = category_label(s) + l = sector_label(s) nl = reverse(cumsum((l[begin:(end - 1)] .- l[(begin + 1):end]..., l[end]))) return typeof(s)(nl) end function Base.show(io::IO, s::SU) - disp = join([string(l) for l in category_label(s)], ", ") + disp = join([string(l) for l in sector_label(s)], ", ") return print(io, "SU(", groupdim(s), ")[", disp, "]") end @@ -63,7 +63,7 @@ function Base.show(io::IO, ::MIME"text/plain", s::SU) end N = groupdim(s) - l = category_label(s) + l = sector_label(s) println(io, "┌─" * "┬─"^(l[1] - 1) * "┐") i = 1 while i < N - 1 && l[i + 1] != 0 @@ -87,7 +87,7 @@ end # # optimize implementation -quantum_dimension(s::SU{2}) = category_label(s)[1] + 1 +quantum_dimension(s::SU{2}) = sector_label(s)[1] + 1 GradedAxes.dual(s::SU{2}) = s diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/su2k.jl similarity index 88% rename from NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/su2k.jl index e90312c635..c0b5e48d84 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/su2k.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/su2k.jl @@ -5,7 +5,7 @@ using HalfIntegers: Half using ...GradedAxes: GradedAxes -struct su2{k} <: AbstractCategory +struct su2{k} <: AbstractSector j::Half{Int} end @@ -13,7 +13,7 @@ SymmetryStyle(::Type{<:su2}) = NotAbelianStyle() GradedAxes.dual(s::su2) = s -category_label(s::su2) = s.j +sector_label(s::su2) = s.j level(::su2{k}) where {k} = k diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/trivial.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/trivial.jl similarity index 59% rename from NDTensors/src/lib/Sectors/src/category_definitions/trivial.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/trivial.jl index 1e97fcc9b1..0cc497960e 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/trivial.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/trivial.jl @@ -5,7 +5,7 @@ using ...GradedAxes: GradedAxes # Trivial is special as it does not have a label -struct TrivialSector <: AbstractCategory end +struct TrivialSector <: AbstractSector end SymmetryStyle(::Type{TrivialSector}) = AbelianStyle() @@ -15,20 +15,20 @@ GradedAxes.dual(::TrivialSector) = TrivialSector() Base.isless(::TrivialSector, ::TrivialSector) = false # bypass default that calls label -# TrivialSector acts as trivial on any AbstractCategory -function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::AbstractCategory) +# TrivialSector acts as trivial on any AbstractSector +function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::AbstractSector) return to_gradedrange(c) end -function fusion_rule(::NotAbelianStyle, c::AbstractCategory, ::TrivialSector) +function fusion_rule(::NotAbelianStyle, c::AbstractSector, ::TrivialSector) return to_gradedrange(c) end # abelian case: return Category -fusion_rule(::AbelianStyle, c::AbstractCategory, ::TrivialSector) = c -fusion_rule(::AbelianStyle, ::TrivialSector, c::AbstractCategory) = c +fusion_rule(::AbelianStyle, c::AbstractSector, ::TrivialSector) = c +fusion_rule(::AbelianStyle, ::TrivialSector, c::AbstractSector) = c fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) = TrivialSector() # any trivial sector equals TrivialSector -Base.:(==)(c::AbstractCategory, ::TrivialSector) = istrivial(c) -Base.:(==)(::TrivialSector, c::AbstractCategory) = istrivial(c) +Base.:(==)(c::AbstractSector, ::TrivialSector) = istrivial(c) +Base.:(==)(::TrivialSector, c::AbstractSector) = istrivial(c) Base.:(==)(::TrivialSector, ::TrivialSector) = true diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/u1.jl similarity index 81% rename from NDTensors/src/lib/Sectors/src/category_definitions/u1.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/u1.jl index 4ebd70fb62..599804767d 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/u1.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/u1.jl @@ -6,7 +6,7 @@ using ...GradedAxes: GradedAxes # Parametric type to allow both integer label as well as # HalfInteger for easy conversion to/from SU(2) -struct U1{T} <: AbstractCategory +struct U1{T} <: AbstractSector n::T end @@ -14,7 +14,7 @@ SymmetryStyle(::Type{<:U1}) = AbelianStyle() GradedAxes.dual(u::U1) = U1(-u.n) -category_label(u::U1) = u.n +sector_label(u::U1) = u.n trivial(::Type{U1}) = trivial(U1{Int}) trivial(::Type{U1{T}}) where {T} = U1(T(0)) @@ -23,5 +23,5 @@ label_fusion_rule(T::Type{<:U1}, n1, n2) = [1], [T(n1 + n2)] # hide label type in printing function Base.show(io::IO, u::U1) - return print(io, "U(1)[", category_label(u), "]") + return print(io, "U(1)[", sector_label(u), "]") end diff --git a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl b/NDTensors/src/lib/Sectors/src/sector_definitions/zn.jl similarity index 76% rename from NDTensors/src/lib/Sectors/src/category_definitions/zn.jl rename to NDTensors/src/lib/Sectors/src/sector_definitions/zn.jl index ad94ba826c..d30e7a3f23 100644 --- a/NDTensors/src/lib/Sectors/src/category_definitions/zn.jl +++ b/NDTensors/src/lib/Sectors/src/sector_definitions/zn.jl @@ -4,14 +4,14 @@ using ...GradedAxes: GradedAxes -struct Z{N} <: AbstractCategory +struct Z{N} <: AbstractSector m::Int Z{N}(m) where {N} = new{N}(m % N) end SymmetryStyle(::Type{<:Z}) = AbelianStyle() -category_label(c::Z) = c.m +sector_label(c::Z) = c.m modulus(::Type{Z{N}}) where {N} = N modulus(c::Z) = modulus(typeof(c)) @@ -23,4 +23,4 @@ function label_fusion_rule(category_type::Type{<:Z}, n1, n2) return [1], [irrep] end -GradedAxes.dual(c::Z) = typeof(c)(mod(-category_label(c), modulus(c))) +GradedAxes.dual(c::Z) = typeof(c)(mod(-sector_label(c), modulus(c))) diff --git a/NDTensors/src/lib/Sectors/src/sector_product.jl b/NDTensors/src/lib/Sectors/src/sector_product.jl new file mode 100644 index 0000000000..da84a34d35 --- /dev/null +++ b/NDTensors/src/lib/Sectors/src/sector_product.jl @@ -0,0 +1,278 @@ +# This files defines a structure for Cartesian product of 2 or more fusion sectors +# e.g. U(1)×U(1), U(1)×SU2(2)×SU(3) + +using BlockArrays: blocklengths +using ..LabelledNumbers: LabelledInteger, label, labelled, unlabel +using ..GradedAxes: AbstractGradedUnitRange, GradedAxes, dual + +# ===================================== Definition ======================================= +struct SectorProduct{Sectors} <: AbstractSector + sectors::Sectors + global _SectorProduct(l) = new{typeof(l)}(l) +end + +SectorProduct(c::SectorProduct) = _SectorProduct(sectors(c)) + +sectors(s::SectorProduct) = s.sectors + +# ================================= Sectors interface ==================================== +SymmetryStyle(T::Type{<:SectorProduct}) = sectors_symmetrystyle(sectors_type(T)) + +function quantum_dimension(::NotAbelianStyle, s::SectorProduct) + return mapreduce(quantum_dimension, *, sectors(s)) +end + +# use map instead of broadcast to support both Tuple and NamedTuple +GradedAxes.dual(s::SectorProduct) = SectorProduct(map(dual, sectors(s))) + +function trivial(type::Type{<:SectorProduct}) + return SectorProduct(sectors_trivial(sectors_type(type))) +end + +# =================================== Base interface ===================================== +function Base.:(==)(A::SectorProduct, B::SectorProduct) + return sectors_isequal(sectors(A), sectors(B)) +end + +function Base.show(io::IO, s::SectorProduct) + (length(sectors(s)) < 2) && print(io, "sector") + print(io, "(") + symbol = "" + for p in pairs(sectors(s)) + print(io, symbol) + sector_show(io, p[1], p[2]) + symbol = " × " + end + return print(io, ")") +end + +sector_show(io::IO, ::Int, v) = print(io, v) +sector_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") + +function Base.isless(s1::SectorProduct, s2::SectorProduct) + return sectors_isless(sectors(s1), sectors(s2)) +end + +# ======================================= shared ========================================= +# there are 2 implementations for SectorProduct +# - ordered-like with a Tuple +# - dictionary-like with a NamedTuple + +sectors_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) +function sectors_isequal(nt::NamedTuple, ::Tuple{}) + return sectors_isequal(nt, (;)) +end +function sectors_isequal(::Tuple{}, nt::NamedTuple) + return sectors_isequal((;), nt) +end +function sectors_isequal(nt1::NamedTuple, nt2::NamedTuple) + return ==(sym_sectors_insert_unspecified(nt1, nt2)...) +end + +# get clean results when mixing implementations +sectors_isequal(::Tuple, ::NamedTuple) = false +sectors_isequal(::NamedTuple, ::Tuple) = false + +sectors_isless(::Tuple, ::Tuple) = throw(ArgumentError("Not implemented")) +sectors_isless(t1::T, t2::T) where {T<:Tuple} = t1 < t2 +function sectors_isless(nt::NamedTuple, ::Tuple{}) + return sectors_isless(nt, (;)) +end +function sectors_isless(::Tuple{}, nt::NamedTuple) + return sectors_isless((;), nt) +end +function sectors_isless(nt1::NamedTuple, nt2::NamedTuple) + return isless(sym_sectors_insert_unspecified(nt1, nt2)...) +end + +sectors_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) +sectors_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) + +sectors_type(::Type{<:SectorProduct{T}}) where {T} = T + +function sectors_fusion_rule(sects1, sects2) + shared_sect = shared_sectors_fusion_rule(sectors_common(sects1, sects2)...) + diff_sect = SectorProduct(sectors_diff(sects1, sects2)) + return shared_sect × diff_sect +end + +# edge case with empty sectors +sectors_fusion_rule(sects::Tuple, ::NamedTuple{()}) = SectorProduct(sects) +sectors_fusion_rule(::NamedTuple{()}, sects::Tuple) = SectorProduct(sects) +sectors_fusion_rule(sects::NamedTuple, ::Tuple{}) = SectorProduct(sects) +sectors_fusion_rule(::Tuple{}, sects::NamedTuple) = SectorProduct(sects) + +function recover_style(T::Type, fused) + style = sectors_symmetrystyle(T) + return recover_sector_product_type(style, T, fused) +end + +function recover_sector_product_type(::AbelianStyle, T::Type, fused) + return recover_sector_product_type(T, fused) +end + +function recover_sector_product_type(::NotAbelianStyle, T::Type, fused) + # here fused contains at least one graded unit range. + # convert eg. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} + g = reduce(×, fused) + # convention: keep unsorted blocklabels as produced by F order loops in × + type_fixed = recover_sector_product_type(T, g) + return type_fixed +end + +function recover_sector_product_type(T::Type, g0::AbstractGradedUnitRange) + new_labels = recover_sector_product_type.(T, blocklabels(g0)) + new_blocklengths = labelled.(unlabel.(blocklengths(g0)), new_labels) + return gradedrange(new_blocklengths) +end + +function recover_sector_product_type(T::Type, c::AbstractSector) + return recover_sector_product_type(T, SectorProduct(c)) +end + +function recover_sector_product_type(T::Type, c::SectorProduct) + return recover_sector_product_type(T, sectors(c)) +end + +function recover_sector_product_type(T::Type{<:SectorProduct}, sects) + return recover_sector_product_type(sectors_type(T), sects) +end + +function recover_sector_product_type(T::Type, sects) + return SectorProduct(T(sects)) +end + +# ================================= Cartesian Product ==================================== +×(c1::AbstractSector, c2::AbstractSector) = ×(SectorProduct(c1), SectorProduct(c2)) +function ×(p1::SectorProduct, p2::SectorProduct) + return SectorProduct(sectors_product(sectors(p1), sectors(p2))) +end + +×(a, g::AbstractUnitRange) = ×(to_gradedrange(a), g) +×(g::AbstractUnitRange, b) = ×(g, to_gradedrange(b)) +×(nt1::NamedTuple, nt2::NamedTuple) = ×(SectorProduct(nt1), SectorProduct(nt2)) +×(c1::NamedTuple, c2::AbstractSector) = ×(SectorProduct(c1), SectorProduct(c2)) +×(c1::AbstractSector, c2::NamedTuple) = ×(SectorProduct(c1), SectorProduct(c2)) + +function ×(l1::LabelledInteger, l2::LabelledInteger) + c3 = label(l1) × label(l2) + m3 = unlabel(l1) * unlabel(l2) + return labelled(m3, c3) +end + +function ×(g1::AbstractUnitRange, g2::AbstractUnitRange) + v = map( + ((l1, l2),) -> l1 × l2, + Iterators.flatten((Iterators.product(blocklengths(g1), blocklengths(g2)),),), + ) + return gradedrange(v) +end + +# ==================================== Fusion rules ====================================== +# cast AbstractSector to SectorProduct +function fusion_rule(style::SymmetryStyle, c1::SectorProduct, c2::AbstractSector) + return fusion_rule(style, c1, SectorProduct(c2)) +end +function fusion_rule(style::SymmetryStyle, c1::AbstractSector, c2::SectorProduct) + return fusion_rule(style, SectorProduct(c1), c2) +end + +# generic case: fusion returns a GradedAxes, even for fusion with Empty +function fusion_rule(::NotAbelianStyle, s1::SectorProduct, s2::SectorProduct) + return to_gradedrange(sectors_fusion_rule(sectors(s1), sectors(s2))) +end + +# Abelian case: fusion returns SectorProduct +function fusion_rule(::AbelianStyle, s1::SectorProduct, s2::SectorProduct) + return sectors_fusion_rule(sectors(s1), sectors(s2)) +end + +# lift ambiguities for TrivialSector +fusion_rule(::AbelianStyle, c::SectorProduct, ::TrivialSector) = c +fusion_rule(::AbelianStyle, ::TrivialSector, c::SectorProduct) = c +fusion_rule(::NotAbelianStyle, c::SectorProduct, ::TrivialSector) = to_gradedrange(c) +fusion_rule(::NotAbelianStyle, ::TrivialSector, c::SectorProduct) = to_gradedrange(c) + +# =============================== Ordered implementation ================================= +SectorProduct(t::Tuple) = _SectorProduct(t) +SectorProduct(sects::AbstractSector...) = SectorProduct(sects) + +function sectors_symmetrystyle(T::Type{<:Tuple}) + return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) +end + +sectors_product(::NamedTuple{()}, l1::Tuple) = l1 +sectors_product(l2::Tuple, ::NamedTuple{()}) = l2 +sectors_product(l1::Tuple, l2::Tuple) = (l1..., l2...) + +sectors_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) + +function sectors_common(t1::Tuple, t2::Tuple) + n = min(length(t1), length(t2)) + return t1[begin:n], t2[begin:n] +end + +function sectors_diff(t1::Tuple, t2::Tuple) + n1 = length(t1) + n2 = length(t2) + return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] +end + +function shared_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} + fused = map(fusion_rule, shared1, shared2) + return recover_style(T, fused) +end + +# =========================== Dictionary-like implementation ============================= +function SectorProduct(nt::NamedTuple) + sectors = sort_keys(nt) + return _SectorProduct(sectors) +end + +SectorProduct(; kws...) = SectorProduct((; kws...)) + +function SectorProduct(pairs::Pair...) + keys = ntuple(n -> Symbol(pairs[n][1]), length(pairs)) + vals = ntuple(n -> pairs[n][2], length(pairs)) + return SectorProduct(NamedTuple{keys}(vals)) +end + +function sectors_symmetrystyle(NT::Type{<:NamedTuple}) + return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) +end + +function sym_sectors_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) + return sectors_insert_unspecified(nt1, nt2), sectors_insert_unspecified(nt2, nt1) +end + +function sectors_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) + diff1 = sectors_trivial(typeof(setdiff_keys(nt2, nt1))) + return sort_keys(union_keys(nt1, diff1)) +end + +sectors_product(l1::NamedTuple, ::Tuple{}) = l1 +sectors_product(::Tuple{}, l2::NamedTuple) = l2 +function sectors_product(l1::NamedTuple, l2::NamedTuple) + if length(intersect_keys(l1, l2)) > 0 + throw(ArgumentError("Cannot define product of shared keys")) + end + return union_keys(l1, l2) +end + +function sectors_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} + return NamedTuple{Keys}(trivial.(fieldtypes(type))) +end + +function sectors_common(nt1::NamedTuple, nt2::NamedTuple) + # SectorProduct(nt::NamedTuple) sorts keys at init + @assert issorted(keys(nt1)) + @assert issorted(keys(nt2)) + return intersect_keys(nt1, nt2), intersect_keys(nt2, nt1) +end + +sectors_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) + +function shared_sectors_fusion_rule(shared1::T, shared2::T) where {T<:NamedTuple} + fused = map(fusion_rule, values(shared1), values(shared2)) + return recover_style(T, fused) +end diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl index 4d8a26ca5a..d9dd06d5b2 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl @@ -210,7 +210,7 @@ end @test space_isequal((@inferred tensor_product(dual(g1), dual(g2))), gtd) @test space_isequal((@inferred fusion_product(dual(g1), dual(g2))), gfd) - # test different (non-product) categories cannot be fused + # test different (non-product) sectors cannot be fused @test_throws MethodError fusion_product(gradedrange([Z{2}(0) => 1]), g1) @test_throws MethodError tensor_product(gradedrange([Z{2}(0) => 1]), g2) end @@ -266,7 +266,7 @@ end ) end - @testset "Mixed GradedUnitRange - Category fusion rules" begin + @testset "Mixed GradedUnitRange - Sector fusion rules" begin g1 = gradedrange([U1(1) => 1, U1(2) => 2]) g2 = gradedrange([U1(2) => 1, U1(3) => 2]) @test space_isequal((@inferred fusion_product(g1, U1(1))), g2) @@ -277,7 +277,7 @@ end @test space_isequal((@inferred fusion_product(g3, SU2(1//2))), g4) @test space_isequal((@inferred fusion_product(SU2(1//2), g3)), g4) - # test different categories cannot be fused + # test different simple sectors cannot be fused @test_throws MethodError fusion_product(g1, SU2(1)) @test_throws MethodError fusion_product(U1(1), g3) end diff --git a/NDTensors/src/lib/Sectors/test/test_category_product.jl b/NDTensors/src/lib/Sectors/test/test_sector_product.jl similarity index 54% rename from NDTensors/src/lib/Sectors/test/test_category_product.jl rename to NDTensors/src/lib/Sectors/test/test_sector_product.jl index 8b592db25a..d002091e8c 100644 --- a/NDTensors/src/lib/Sectors/test/test_category_product.jl +++ b/NDTensors/src/lib/Sectors/test/test_sector_product.jl @@ -2,18 +2,18 @@ using NDTensors.Sectors: ×, ⊗, - CategoryProduct, Fib, Ising, + SectorProduct, SU, SU2, TrivialSector, U1, Z, block_dimensions, - categories, quantum_dimension, - recover_category_product_type, + recover_sector_product_type, + sectors, trivial using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange using Test: @inferred, @test, @testset, @test_throws @@ -27,62 +27,61 @@ end @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin - s = CategoryProduct(U1(1)) - @test length(categories(s)) == 1 + s = SectorProduct(U1(1)) + @test length(sectors(s)) == 1 @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == CategoryProduct(U1(-1)) - @test categories(s)[1] == U1(1) - @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0)) + @test (@inferred dual(s)) == SectorProduct(U1(-1)) + @test sectors(s)[1] == U1(1) + @test (@inferred_latest trivial(s)) == SectorProduct(U1(0)) - s = CategoryProduct(U1(1), U1(2)) - @test length(categories(s)) == 2 + s = SectorProduct(U1(1), U1(2)) + @test length(sectors(s)) == 2 @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == CategoryProduct(U1(-1), U1(-2)) - @test categories(s)[1] == U1(1) - @test categories(s)[2] == U1(2) - @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0), U1(0)) + @test (@inferred dual(s)) == SectorProduct(U1(-1), U1(-2)) + @test sectors(s)[1] == U1(1) + @test sectors(s)[2] == U1(2) + @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) - @test length(categories(s)) == 3 + @test length(sectors(s)) == 3 @test (@inferred quantum_dimension(s)) == 2 @test (@inferred dual(s)) == U1(-1) × SU2(1//2) × U1(-3) - @test categories(s)[1] == U1(1) - @test categories(s)[2] == SU2(1//2) - @test categories(s)[3] == U1(3) - @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0), SU2(0), U1(0)) - @test (@inferred recover_category_product_type(typeof(categories(s)), categories(s))) == - s - @test (@inferred recover_category_product_type(typeof(s), categories(s))) == s + @test sectors(s)[1] == U1(1) + @test sectors(s)[2] == SU2(1//2) + @test sectors(s)[3] == U1(3) + @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), SU2(0), U1(0)) + @test (@inferred recover_sector_product_type(typeof(sectors(s)), sectors(s))) == s + @test (@inferred recover_sector_product_type(typeof(s), sectors(s))) == s s = U1(3) × SU2(1//2) × Fib("τ") - @test length(categories(s)) == 3 + @test length(sectors(s)) == 3 @test (@inferred_latest quantum_dimension(s)) == 1.0 + √5 @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") - @test categories(s)[1] == U1(3) - @test categories(s)[2] == SU2(1//2) - @test categories(s)[3] == Fib("τ") - @test (@inferred_latest trivial(s)) == CategoryProduct(U1(0), SU2(0), Fib("1")) + @test sectors(s)[1] == U1(3) + @test sectors(s)[2] == SU2(1//2) + @test sectors(s)[3] == Fib("τ") + @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), SU2(0), Fib("1")) s = TrivialSector() × U1(3) × SU2(1 / 2) - @test length(categories(s)) == 3 + @test length(sectors(s)) == 3 @test (@inferred_latest quantum_dimension(s)) == 2 @test dual(s) == TrivialSector() × U1(-3) × SU2(1//2) - @test (@inferred_latest trivial(s)) == CategoryProduct(TrivialSector(), U1(0), SU2(0)) + @test (@inferred_latest trivial(s)) == SectorProduct(TrivialSector(), U1(0), SU2(0)) @test s > trivial(s) end @testset "Ordered comparisons" begin - # convention: categories must have same length to evaluate as equal - @test CategoryProduct(U1(1), SU2(1)) == CategoryProduct(U1(1), SU2(1)) - @test CategoryProduct(U1(1), SU2(0)) != CategoryProduct(U1(1), SU2(1)) - @test CategoryProduct(U1(0), SU2(1)) != CategoryProduct(U1(1), SU2(1)) - @test CategoryProduct(U1(1)) != CategoryProduct(U1(1), U1(0)) - @test CategoryProduct(U1(0), SU2(0)) == TrivialSector() - - # convention: categories must have same length to be compared - @test CategoryProduct(U1(0)) < CategoryProduct((U1(1))) - @test CategoryProduct(U1(0), U1(2)) < CategoryProduct((U1(1)), U1(0)) - @test_throws ArgumentError CategoryProduct(U1(0)) < CategoryProduct(U1(1), U1(2)) + # convention: sectors must have same length to evaluate as equal + @test SectorProduct(U1(1), SU2(1)) == SectorProduct(U1(1), SU2(1)) + @test SectorProduct(U1(1), SU2(0)) != SectorProduct(U1(1), SU2(1)) + @test SectorProduct(U1(0), SU2(1)) != SectorProduct(U1(1), SU2(1)) + @test SectorProduct(U1(1)) != SectorProduct(U1(1), U1(0)) + @test SectorProduct(U1(0), SU2(0)) == TrivialSector() + + # convention: sectors must have same length to be compared + @test SectorProduct(U1(0)) < SectorProduct((U1(1))) + @test SectorProduct(U1(0), U1(2)) < SectorProduct((U1(1)), U1(0)) + @test_throws ArgumentError SectorProduct(U1(0)) < SectorProduct(U1(1), U1(2)) end @testset "Quantum dimension and GradedUnitRange" begin @@ -141,11 +140,11 @@ end end @testset "Fusion of Abelian products" begin - p1 = CategoryProduct(U1(1)) - p2 = CategoryProduct(U1(2)) + p1 = SectorProduct(U1(1)) + p2 = SectorProduct(U1(2)) @test (@inferred_latest p1 ⊗ TrivialSector()) == p1 @test (@inferred_latest TrivialSector() ⊗ p2) == p2 - @test (@inferred_latest p1 ⊗ p2) == CategoryProduct(U1(3)) + @test (@inferred_latest p1 ⊗ p2) == SectorProduct(U1(3)) p11 = U1(1) × U1(1) @test (@inferred_latest p11 ⊗ p11) == U1(2) × U1(2) @@ -153,20 +152,19 @@ end p123 = U1(1) × U1(2) × U1(3) @test (@inferred_latest p123 ⊗ p123) == U1(2) × U1(4) × U1(6) - s1 = CategoryProduct(U1(1), Z{2}(1)) - s2 = CategoryProduct(U1(0), Z{2}(0)) + s1 = SectorProduct(U1(1), Z{2}(1)) + s2 = SectorProduct(U1(0), Z{2}(0)) @test (@inferred_latest s1 ⊗ s2) == U1(1) × Z{2}(1) end @testset "Fusion of NonAbelian products" begin - p0 = CategoryProduct(SU2(0)) - ph = CategoryProduct(SU2(1//2)) + p0 = SectorProduct(SU2(0)) + ph = SectorProduct(SU2(1//2)) @test space_isequal( - (@inferred_latest p0 ⊗ TrivialSector()), gradedrange([CategoryProduct(SU2(0)) => 1]) + (@inferred_latest p0 ⊗ TrivialSector()), gradedrange([SectorProduct(SU2(0)) => 1]) ) @test space_isequal( - (@inferred_latest TrivialSector() ⊗ ph), - gradedrange([CategoryProduct(SU2(1//2)) => 1]), + (@inferred_latest TrivialSector() ⊗ ph), gradedrange([SectorProduct(SU2(1//2)) => 1]) ) phh = SU2(1//2) × SU2(1//2) @@ -257,23 +255,23 @@ end end @testset "Fusion of different length Categories" begin - @test CategoryProduct(U1(1) × U1(0)) ⊗ CategoryProduct(U1(1)) == - CategoryProduct(U1(2) × U1(0)) + @test SectorProduct(U1(1) × U1(0)) ⊗ SectorProduct(U1(1)) == + SectorProduct(U1(2) × U1(0)) @test space_isequal( - (@inferred CategoryProduct(SU2(0) × SU2(0)) ⊗ CategoryProduct(SU2(1))), - gradedrange([CategoryProduct(SU2(1) × SU2(0)) => 1]), + (@inferred SectorProduct(SU2(0) × SU2(0)) ⊗ SectorProduct(SU2(1))), + gradedrange([SectorProduct(SU2(1) × SU2(0)) => 1]), ) @test space_isequal( - (@inferred CategoryProduct(SU2(1) × U1(1)) ⊗ CategoryProduct(SU2(0))), - gradedrange([CategoryProduct(SU2(1) × U1(1)) => 1]), + (@inferred SectorProduct(SU2(1) × U1(1)) ⊗ SectorProduct(SU2(0))), + gradedrange([SectorProduct(SU2(1) × U1(1)) => 1]), ) @test space_isequal( - (@inferred_latest CategoryProduct(U1(1) × SU2(1)) ⊗ CategoryProduct(U1(2))), - gradedrange([CategoryProduct(U1(3) × SU2(1)) => 1]), + (@inferred_latest SectorProduct(U1(1) × SU2(1)) ⊗ SectorProduct(U1(2))), + gradedrange([SectorProduct(U1(3) × SU2(1)) => 1]), ) - # check incompatible categories + # check incompatible sectors p12 = Z{2}(1) × U1(2) z12 = Z{2}(1) × Z{2}(1) @test_throws MethodError p12 ⊗ z12 @@ -291,32 +289,31 @@ end end end -@testset "Test Named Category Products" begin +@testset "Test Named Sector Products" begin @testset "Construct from × of NamedTuples" begin s = (A=U1(1),) × (B=Z{2}(0),) - @test length(categories(s)) == 2 - @test categories(s)[:A] == U1(1) - @test categories(s)[:B] == Z{2}(0) + @test length(sectors(s)) == 2 + @test sectors(s)[:A] == U1(1) + @test sectors(s)[:B] == Z{2}(0) @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) - @test length(categories(s)) == 2 - @test categories(s)[:A] == U1(1) - @test categories(s)[:B] == SU2(2) + @test length(sectors(s)) == 2 + @test sectors(s)[:A] == U1(1) + @test sectors(s)[:B] == SU2(2) @test (@inferred quantum_dimension(s)) == 5 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=SU2(0),) - @test (@inferred recover_category_product_type( - typeof(categories(s)), Tuple(categories(s)) - )) == s - @test (@inferred recover_category_product_type(typeof(s), Tuple(categories(s)))) == s + @test (@inferred recover_sector_product_type(typeof(sectors(s)), Tuple(sectors(s)))) == + s + @test (@inferred recover_sector_product_type(typeof(s), Tuple(sectors(s)))) == s @test s == (B=SU2(2),) × (A=U1(1),) s = s × (C=Ising("ψ"),) - @test length(categories(s)) == 3 - @test categories(s)[:C] == Ising("ψ") + @test length(sectors(s)) == 3 + @test sectors(s)[:C] == Ising("ψ") @test (@inferred_latest quantum_dimension(s)) == 5.0 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) @@ -326,25 +323,25 @@ end end @testset "Construct from Pairs" begin - s = CategoryProduct("A" => U1(2)) - @test length(categories(s)) == 1 - @test categories(s)[:A] == U1(2) - @test s == CategoryProduct(; A=U1(2)) + s = SectorProduct("A" => U1(2)) + @test length(sectors(s)) == 1 + @test sectors(s)[:A] == U1(2) + @test s == SectorProduct(; A=U1(2)) @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == CategoryProduct("A" => U1(-2)) - @test (@inferred_latest trivial(s)) == CategoryProduct(; A=U1(0)) + @test (@inferred dual(s)) == SectorProduct("A" => U1(-2)) + @test (@inferred_latest trivial(s)) == SectorProduct(; A=U1(0)) - s = CategoryProduct("B" => Ising("ψ"), :C => Z{2}(1)) - @test length(categories(s)) == 2 - @test categories(s)[:B] == Ising("ψ") - @test categories(s)[:C] == Z{2}(1) + s = SectorProduct("B" => Ising("ψ"), :C => Z{2}(1)) + @test length(sectors(s)) == 2 + @test sectors(s)[:B] == Ising("ψ") + @test sectors(s)[:C] == Z{2}(1) @test (@inferred_latest quantum_dimension(s)) == 1.0 end @testset "Comparisons with unspecified labels" begin - # convention: categories evaluate as equal if unmatched labels are trivial + # convention: sectors evaluate as equal if unmatched labels are trivial # this is different from ordered tuple convention - q2 = CategoryProduct(; N=U1(2)) + q2 = SectorProduct(; N=U1(2)) q20 = (N=U1(2),) × (J=SU2(0),) @test q20 == q2 @test !(q20 < q2) @@ -364,84 +361,84 @@ end @testset "Quantum dimension and GradedUnitRange" begin g = gradedrange([ - CategoryProduct(; A=U1(0), B=Z{2}(0)) => 1, CategoryProduct(; A=U1(1), B=Z{2}(0)) => 2 + SectorProduct(; A=U1(0), B=Z{2}(0)) => 1, SectorProduct(; A=U1(1), B=Z{2}(0)) => 2 ]) # abelian @test (@inferred quantum_dimension(g)) == 3 g = gradedrange([ # non-abelian - CategoryProduct(; A=SU2(0), B=SU2(0)) => 1, - CategoryProduct(; A=SU2(1), B=SU2(0)) => 1, - CategoryProduct(; A=SU2(0), B=SU2(1)) => 1, - CategoryProduct(; A=SU2(1), B=SU2(1)) => 1, + SectorProduct(; A=SU2(0), B=SU2(0)) => 1, + SectorProduct(; A=SU2(1), B=SU2(0)) => 1, + SectorProduct(; A=SU2(0), B=SU2(1)) => 1, + SectorProduct(; A=SU2(1), B=SU2(1)) => 1, ]) @test (@inferred quantum_dimension(g)) == 16 # mixed group g = gradedrange([ - CategoryProduct(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, - CategoryProduct(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, + SectorProduct(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, + SectorProduct(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4 g = gradedrange([ - CategoryProduct(; A=SU2(0), B=Z{2}(0), C=SU2(1//2)) => 1, - CategoryProduct(; A=SU2(0), B=Z{2}(1), C=SU2(1//2)) => 1, + SectorProduct(; A=SU2(0), B=Z{2}(0), C=SU2(1//2)) => 1, + SectorProduct(; A=SU2(0), B=Z{2}(1), C=SU2(1//2)) => 1, ]) @test (@inferred quantum_dimension(g)) == 4 - # non group categories - g_fib = gradedrange([CategoryProduct(; A=Fib("1"), B=Fib("1")) => 1]) - g_ising = gradedrange([CategoryProduct(; A=Ising("1"), B=Ising("1")) => 1]) + # non group sectors + g_fib = gradedrange([SectorProduct(; A=Fib("1"), B=Fib("1")) => 1]) + g_ising = gradedrange([SectorProduct(; A=Ising("1"), B=Ising("1")) => 1]) @test (@inferred_latest quantum_dimension(g_fib)) == 1.0 @test (@inferred_latest quantum_dimension(g_ising)) == 1.0 # mixed product Abelian / NonAbelian / NonGroup g = gradedrange([ - CategoryProduct(; A=U1(2), B=SU2(0), C=Ising(1)) => 1, - CategoryProduct(; A=U1(2), B=SU2(1), C=Ising(1)) => 1, - CategoryProduct(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, - CategoryProduct(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, + SectorProduct(; A=U1(2), B=SU2(0), C=Ising(1)) => 1, + SectorProduct(; A=U1(2), B=SU2(1), C=Ising(1)) => 1, + SectorProduct(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, + SectorProduct(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]) @test (@inferred_latest quantum_dimension(g)) == 8.0 g = gradedrange([ - CategoryProduct(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, - CategoryProduct(; A=Fib("1"), B=SU2(1), C=U1(2)) => 1, - CategoryProduct(; A=Fib("τ"), B=SU2(0), C=U1(2)) => 1, - CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1, + SectorProduct(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, + SectorProduct(; A=Fib("1"), B=SU2(1), C=U1(2)) => 1, + SectorProduct(; A=Fib("τ"), B=SU2(0), C=U1(2)) => 1, + SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1, ]) @test (@inferred_latest quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end @testset "Fusion of Abelian products" begin - q00 = CategoryProduct(;) - q10 = CategoryProduct(; A=U1(1)) - q01 = CategoryProduct(; B=U1(1)) - q11 = CategoryProduct(; A=U1(1), B=U1(1)) + q00 = SectorProduct(;) + q10 = SectorProduct(; A=U1(1)) + q01 = SectorProduct(; B=U1(1)) + q11 = SectorProduct(; A=U1(1), B=U1(1)) - @test (@inferred_latest q10 ⊗ q10) == CategoryProduct(; A=U1(2)) + @test (@inferred_latest q10 ⊗ q10) == SectorProduct(; A=U1(2)) @test (@inferred_latest q01 ⊗ q00) == q01 @test (@inferred_latest q00 ⊗ q01) == q01 @test (@inferred_latest q10 ⊗ q01) == q11 - @test (@inferred_latest q11 ⊗ q11) == CategoryProduct(; A=U1(2), B=U1(2)) + @test (@inferred_latest q11 ⊗ q11) == SectorProduct(; A=U1(2), B=U1(2)) - s11 = CategoryProduct(; A=U1(1), B=Z{2}(1)) - s10 = CategoryProduct(; A=U1(1)) - s01 = CategoryProduct(; B=Z{2}(1)) + s11 = SectorProduct(; A=U1(1), B=Z{2}(1)) + s10 = SectorProduct(; A=U1(1)) + s01 = SectorProduct(; B=Z{2}(1)) @test (@inferred_latest s01 ⊗ q00) == s01 @test (@inferred_latest q00 ⊗ s01) == s01 @test (@inferred_latest s10 ⊗ s01) == s11 - @test (@inferred_latest s11 ⊗ s11) == CategoryProduct(; A=U1(2), B=Z{2}(0)) + @test (@inferred_latest s11 ⊗ s11) == SectorProduct(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin - p0 = CategoryProduct(;) - pha = CategoryProduct(; A=SU2(1//2)) - phb = CategoryProduct(; B=SU2(1//2)) - phab = CategoryProduct(; A=SU2(1//2), B=SU2(1//2)) + p0 = SectorProduct(;) + pha = SectorProduct(; A=SU2(1//2)) + phb = SectorProduct(; B=SU2(1//2)) + phab = SectorProduct(; A=SU2(1//2), B=SU2(1//2)) @test space_isequal( (@inferred pha ⊗ pha), - gradedrange([CategoryProduct(; A=SU2(0)) => 1, CategoryProduct(; A=SU2(1)) => 1]), + gradedrange([SectorProduct(; A=SU2(0)) => 1, SectorProduct(; A=SU2(1)) => 1]), ) @test space_isequal((@inferred_latest pha ⊗ p0), gradedrange([pha => 1])) @test space_isequal((@inferred_latest p0 ⊗ phb), gradedrange([phb => 1])) @@ -450,10 +447,10 @@ end @test space_isequal( (@inferred phab ⊗ phab), gradedrange([ - CategoryProduct(; A=SU2(0), B=SU2(0)) => 1, - CategoryProduct(; A=SU2(1), B=SU2(0)) => 1, - CategoryProduct(; A=SU2(0), B=SU2(1)) => 1, - CategoryProduct(; A=SU2(1), B=SU2(1)) => 1, + SectorProduct(; A=SU2(0), B=SU2(0)) => 1, + SectorProduct(; A=SU2(1), B=SU2(0)) => 1, + SectorProduct(; A=SU2(0), B=SU2(1)) => 1, + SectorProduct(; A=SU2(1), B=SU2(1)) => 1, ]), ) end @@ -461,34 +458,34 @@ end @testset "Fusion of NonGroupCategory products" begin ı = Fib("1") τ = Fib("τ") - s = CategoryProduct(; A=ı, B=ı) + s = SectorProduct(; A=ı, B=ı) @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) - s = CategoryProduct(; A=τ, B=τ) + s = SectorProduct(; A=τ, B=τ) @test space_isequal( (@inferred s ⊗ s), gradedrange([ - CategoryProduct(; A=ı, B=ı) => 1, - CategoryProduct(; A=τ, B=ı) => 1, - CategoryProduct(; A=ı, B=τ) => 1, - CategoryProduct(; A=τ, B=τ) => 1, + SectorProduct(; A=ı, B=ı) => 1, + SectorProduct(; A=τ, B=ı) => 1, + SectorProduct(; A=ı, B=τ) => 1, + SectorProduct(; A=τ, B=τ) => 1, ]), ) σ = Ising("σ") ψ = Ising("ψ") - s = CategoryProduct(; A=τ, B=σ) + s = SectorProduct(; A=τ, B=σ) g = gradedrange([ - CategoryProduct(; A=ı, B=Ising("1")) => 1, - CategoryProduct(; A=τ, B=Ising("1")) => 1, - CategoryProduct(; A=ı, B=ψ) => 1, - CategoryProduct(; A=τ, B=ψ) => 1, + SectorProduct(; A=ı, B=Ising("1")) => 1, + SectorProduct(; A=τ, B=Ising("1")) => 1, + SectorProduct(; A=ı, B=ψ) => 1, + SectorProduct(; A=τ, B=ψ) => 1, ]) @test space_isequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin - q0h = CategoryProduct(; J=SU2(1//2)) + q0h = SectorProduct(; J=SU2(1//2)) q10 = (N=U1(1),) × (J=SU2(0),) # Put names in reverse order sometimes: q1h = (J=SU2(1//2),) × (N=U1(1),) @@ -505,52 +502,52 @@ end end @testset "Fusion of fully mixed products" begin - s = CategoryProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) + s = SectorProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) @test space_isequal( (@inferred s ⊗ s), gradedrange([ - CategoryProduct(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, - CategoryProduct(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, - CategoryProduct(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, - CategoryProduct(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, + SectorProduct(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, + SectorProduct(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, + SectorProduct(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, + SectorProduct(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]), ) ı = Fib("1") τ = Fib("τ") - s = CategoryProduct(; A=SU2(1//2), B=U1(1), C=τ) + s = SectorProduct(; A=SU2(1//2), B=U1(1), C=τ) @test space_isequal( (@inferred s ⊗ s), gradedrange([ - CategoryProduct(; A=SU2(0), B=U1(2), C=ı) => 1, - CategoryProduct(; A=SU2(1), B=U1(2), C=ı) => 1, - CategoryProduct(; A=SU2(0), B=U1(2), C=τ) => 1, - CategoryProduct(; A=SU2(1), B=U1(2), C=τ) => 1, + SectorProduct(; A=SU2(0), B=U1(2), C=ı) => 1, + SectorProduct(; A=SU2(1), B=U1(2), C=ı) => 1, + SectorProduct(; A=SU2(0), B=U1(2), C=τ) => 1, + SectorProduct(; A=SU2(1), B=U1(2), C=τ) => 1, ]), ) - s = CategoryProduct(; A=τ, B=U1(1), C=ı) + s = SectorProduct(; A=τ, B=U1(1), C=ı) @test space_isequal( (@inferred s ⊗ s), gradedrange([ - CategoryProduct(; B=U1(2), A=ı, C=ı) => 1, CategoryProduct(; B=U1(2), A=τ, C=ı) => 1 + SectorProduct(; B=U1(2), A=ı, C=ı) => 1, SectorProduct(; B=U1(2), A=τ, C=ı) => 1 ]), ) end @testset "GradedUnitRange fusion rules" begin - s1 = CategoryProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) - s2 = CategoryProduct(; A=U1(0), B=SU2(1//2), C=Ising("1")) + s1 = SectorProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) + s2 = SectorProduct(; A=U1(0), B=SU2(1//2), C=Ising("1")) g1 = gradedrange([s1 => 2]) g2 = gradedrange([s2 => 1]) - s3 = CategoryProduct(; A=U1(1), B=SU2(0), C=Ising("σ")) - s4 = CategoryProduct(; A=U1(1), B=SU2(1), C=Ising("σ")) + s3 = SectorProduct(; A=U1(1), B=SU2(0), C=Ising("σ")) + s4 = SectorProduct(; A=U1(1), B=SU2(1), C=Ising("σ")) @test space_isequal( (@inferred_latest fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2]) ) - sA = CategoryProduct(; A=U1(1)) - sB = CategoryProduct(; B=SU2(1//2)) - sAB = CategoryProduct(; A=U1(1), B=SU2(1//2)) + sA = SectorProduct(; A=U1(1)) + sB = SectorProduct(; B=SU2(1//2)) + sAB = SectorProduct(; A=U1(1), B=SU2(1//2)) gA = gradedrange([sA => 2]) gB = gradedrange([sB => 1]) @test space_isequal((@inferred_latest fusion_product(gA, gB)), gradedrange([sAB => 2])) @@ -558,10 +555,10 @@ end end @testset "Empty category" begin - for s in (CategoryProduct(()), CategoryProduct((;))) + for s in (SectorProduct(()), SectorProduct((;))) @test s == TrivialSector() - @test s == CategoryProduct(()) - @test s == CategoryProduct((;)) + @test s == SectorProduct(()) + @test s == SectorProduct((;)) @test (@inferred dual(s)) == s @test (@inferred s × s) == s @test (@inferred_latest s ⊗ s) == s @@ -571,45 +568,45 @@ end g0 = gradedrange([s => 2]) @test space_isequal((@inferred_latest fusion_product(g0, g0)), gradedrange([s => 4])) - @test (@inferred s × U1(1)) == CategoryProduct(U1(1)) - @test (@inferred s × CategoryProduct(U1(1))) == CategoryProduct(U1(1)) - @test (@inferred s × CategoryProduct(; A=U1(1))) == CategoryProduct(; A=U1(1)) - @test (@inferred U1(1) × s) == CategoryProduct(U1(1)) - @test (@inferred CategoryProduct(U1(1)) × s) == CategoryProduct(U1(1)) - @test (@inferred CategoryProduct(; A=U1(1)) × s) == CategoryProduct(; A=U1(1)) - - @test (@inferred_latest U1(1) ⊗ s) == CategoryProduct(U1(1)) - @test (@inferred SU2(0) ⊗ s) == gradedrange([CategoryProduct(SU2(0)) => 1]) - @test (@inferred Fib("τ") ⊗ s) == gradedrange([CategoryProduct(Fib("τ")) => 1]) - @test (@inferred_latest s ⊗ U1(1)) == CategoryProduct(U1(1)) - @test (@inferred s ⊗ SU2(0)) == gradedrange([CategoryProduct(SU2(0)) => 1]) - @test (@inferred s ⊗ Fib("τ")) == gradedrange([CategoryProduct(Fib("τ")) => 1]) - - @test (@inferred_latest CategoryProduct(U1(1)) ⊗ s) == CategoryProduct(U1(1)) - @test (@inferred_latest CategoryProduct(SU2(0)) ⊗ s) == - gradedrange([CategoryProduct(SU2(0)) => 1]) - @test (@inferred_latest CategoryProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == - gradedrange([CategoryProduct(Fib("τ"), SU2(1), U1(2)) => 1]) - - @test (@inferred_latest CategoryProduct(; A=U1(1)) ⊗ s) == CategoryProduct(; A=U1(1)) - @test (@inferred_latest CategoryProduct(; A=SU2(0)) ⊗ s) == - gradedrange([CategoryProduct(; A=SU2(0)) => 1]) - @test (@inferred_latest CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == - gradedrange([CategoryProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) + @test (@inferred s × U1(1)) == SectorProduct(U1(1)) + @test (@inferred s × SectorProduct(U1(1))) == SectorProduct(U1(1)) + @test (@inferred s × SectorProduct(; A=U1(1))) == SectorProduct(; A=U1(1)) + @test (@inferred U1(1) × s) == SectorProduct(U1(1)) + @test (@inferred SectorProduct(U1(1)) × s) == SectorProduct(U1(1)) + @test (@inferred SectorProduct(; A=U1(1)) × s) == SectorProduct(; A=U1(1)) + + @test (@inferred_latest U1(1) ⊗ s) == SectorProduct(U1(1)) + @test (@inferred SU2(0) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred Fib("τ") ⊗ s) == gradedrange([SectorProduct(Fib("τ")) => 1]) + @test (@inferred_latest s ⊗ U1(1)) == SectorProduct(U1(1)) + @test (@inferred s ⊗ SU2(0)) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred s ⊗ Fib("τ")) == gradedrange([SectorProduct(Fib("τ")) => 1]) + + @test (@inferred_latest SectorProduct(U1(1)) ⊗ s) == SectorProduct(U1(1)) + @test (@inferred_latest SectorProduct(SU2(0)) ⊗ s) == + gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred_latest SectorProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == + gradedrange([SectorProduct(Fib("τ"), SU2(1), U1(2)) => 1]) + + @test (@inferred_latest SectorProduct(; A=U1(1)) ⊗ s) == SectorProduct(; A=U1(1)) + @test (@inferred_latest SectorProduct(; A=SU2(0)) ⊗ s) == + gradedrange([SectorProduct(; A=SU2(0)) => 1]) + @test (@inferred_latest SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == + gradedrange([SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) # Empty behaves as empty NamedTuple @test s != U1(0) - @test s != CategoryProduct(U1(0)) - @test s != CategoryProduct(; A=U1(1)) - @test s == CategoryProduct(; A=U1(0)) - @test CategoryProduct(; A=U1(0)) == s + @test s != SectorProduct(U1(0)) + @test s != SectorProduct(; A=U1(1)) + @test s == SectorProduct(; A=U1(0)) + @test SectorProduct(; A=U1(0)) == s @test !(s < s) - @test_throws ArgumentError s < CategoryProduct(U1(0)) - @test s < CategoryProduct(; A=U1(1)) - @test s > CategoryProduct(; A=U1(-1)) - @test !(s < CategoryProduct(; A=U1(0))) - @test !(s > CategoryProduct(; A=U1(0))) + @test_throws ArgumentError s < SectorProduct(U1(0)) + @test s < SectorProduct(; A=U1(1)) + @test s > SectorProduct(; A=U1(-1)) + @test !(s < SectorProduct(; A=U1(0))) + @test !(s > SectorProduct(; A=U1(0))) end end end diff --git a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl b/NDTensors/src/lib/Sectors/test/test_simple_sectors.jl similarity index 99% rename from NDTensors/src/lib/Sectors/test/test_simple_categories.jl rename to NDTensors/src/lib/Sectors/test/test_simple_sectors.jl index 85b44adffc..ca27f220ec 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_categories.jl +++ b/NDTensors/src/lib/Sectors/test/test_simple_sectors.jl @@ -89,7 +89,7 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred dual(s1)) == s1 end - @testset "SU2" begin + @testset "SU(2)" begin j1 = SU2(0) j2 = SU2(1//2) # Rational will be cast to HalfInteger j3 = SU2(1) From 692e08a8eb3caaac539d7edfe84c38371d396b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 7 Oct 2024 16:08:50 -0400 Subject: [PATCH 154/194] fusion_rule(::AbelianStyle to call fusion_rule(::NotAbelianStyle --- NDTensors/src/lib/Sectors/src/abstractsector.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NDTensors/src/lib/Sectors/src/abstractsector.jl b/NDTensors/src/lib/Sectors/src/abstractsector.jl index 4088733ef7..3c25e622c5 100644 --- a/NDTensors/src/lib/Sectors/src/abstractsector.jl +++ b/NDTensors/src/lib/Sectors/src/abstractsector.jl @@ -60,8 +60,7 @@ end # abelian case: return Sector function fusion_rule(::AbelianStyle, c1::C, c2::C) where {C<:AbstractSector} - _, sectors = label_fusion_rule(C, sector_label(c1), sector_label(c2)) - return only(sectors) + return label(only(fusion_rule(NotAbelianStyle(), c1, c2))) end function label_fusion_rule(sector_type::Type{<:AbstractSector}, ::Any, ::Any) From d0b81d9cb53eb1b517222add34c4ebc82577efce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 7 Oct 2024 16:35:07 -0400 Subject: [PATCH 155/194] rename Sectors to SymmetrySectors --- NDTensors/src/imports.jl | 2 +- .../src/lib/{Sectors => SymmetrySectors}/.JuliaFormatter.toml | 0 NDTensors/src/lib/{Sectors => SymmetrySectors}/Project.toml | 0 .../src/Sectors.jl => SymmetrySectors/src/SymmetrySectors.jl} | 2 +- .../src/lib/{Sectors => SymmetrySectors}/src/abstractsector.jl | 0 .../{Sectors => SymmetrySectors}/src/namedtuple_operations.jl | 0 .../{Sectors => SymmetrySectors}/src/sector_definitions/fib.jl | 0 .../src/sector_definitions/ising.jl | 0 .../{Sectors => SymmetrySectors}/src/sector_definitions/o2.jl | 0 .../{Sectors => SymmetrySectors}/src/sector_definitions/su.jl | 0 .../{Sectors => SymmetrySectors}/src/sector_definitions/su2k.jl | 0 .../src/sector_definitions/trivial.jl | 0 .../{Sectors => SymmetrySectors}/src/sector_definitions/u1.jl | 0 .../{Sectors => SymmetrySectors}/src/sector_definitions/zn.jl | 0 .../src/lib/{Sectors => SymmetrySectors}/src/sector_product.jl | 0 .../src/lib/{Sectors => SymmetrySectors}/src/symmetry_style.jl | 0 NDTensors/src/lib/{Sectors => SymmetrySectors}/test/runtests.jl | 0 .../lib/{Sectors => SymmetrySectors}/test/test_fusion_rules.jl | 2 +- .../{Sectors => SymmetrySectors}/test/test_sector_product.jl | 2 +- .../{Sectors => SymmetrySectors}/test/test_simple_sectors.jl | 2 +- 20 files changed, 5 insertions(+), 5 deletions(-) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/.JuliaFormatter.toml (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/Project.toml (100%) rename NDTensors/src/lib/{Sectors/src/Sectors.jl => SymmetrySectors/src/SymmetrySectors.jl} (94%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/abstractsector.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/namedtuple_operations.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/fib.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/ising.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/o2.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/su.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/su2k.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/trivial.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/u1.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_definitions/zn.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/sector_product.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/src/symmetry_style.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/test/runtests.jl (100%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/test/test_fusion_rules.jl (99%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/test/test_sector_product.jl (99%) rename NDTensors/src/lib/{Sectors => SymmetrySectors}/test/test_simple_sectors.jl (99%) diff --git a/NDTensors/src/imports.jl b/NDTensors/src/imports.jl index 7453b1f20e..73d8ae3771 100644 --- a/NDTensors/src/imports.jl +++ b/NDTensors/src/imports.jl @@ -35,7 +35,7 @@ for lib in [ :RankFactorization, :LabelledNumbers, :GradedAxes, - :Sectors, + :SymmetrySectors, :TensorAlgebra, :SparseArrayInterface, :SparseArrayDOKs, diff --git a/NDTensors/src/lib/Sectors/.JuliaFormatter.toml b/NDTensors/src/lib/SymmetrySectors/.JuliaFormatter.toml similarity index 100% rename from NDTensors/src/lib/Sectors/.JuliaFormatter.toml rename to NDTensors/src/lib/SymmetrySectors/.JuliaFormatter.toml diff --git a/NDTensors/src/lib/Sectors/Project.toml b/NDTensors/src/lib/SymmetrySectors/Project.toml similarity index 100% rename from NDTensors/src/lib/Sectors/Project.toml rename to NDTensors/src/lib/SymmetrySectors/Project.toml diff --git a/NDTensors/src/lib/Sectors/src/Sectors.jl b/NDTensors/src/lib/SymmetrySectors/src/SymmetrySectors.jl similarity index 94% rename from NDTensors/src/lib/Sectors/src/Sectors.jl rename to NDTensors/src/lib/SymmetrySectors/src/SymmetrySectors.jl index e76d487236..93ecbda986 100644 --- a/NDTensors/src/lib/Sectors/src/Sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/SymmetrySectors.jl @@ -1,4 +1,4 @@ -module Sectors +module SymmetrySectors include("symmetry_style.jl") include("abstractsector.jl") diff --git a/NDTensors/src/lib/Sectors/src/abstractsector.jl b/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/abstractsector.jl rename to NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl diff --git a/NDTensors/src/lib/Sectors/src/namedtuple_operations.jl b/NDTensors/src/lib/SymmetrySectors/src/namedtuple_operations.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/namedtuple_operations.jl rename to NDTensors/src/lib/SymmetrySectors/src/namedtuple_operations.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/fib.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/fib.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/fib.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/fib.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/ising.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/ising.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/ising.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/ising.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/o2.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/o2.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/o2.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/o2.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/su.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/su.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/su2k.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su2k.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/su2k.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su2k.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/trivial.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/trivial.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/trivial.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/trivial.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/u1.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/u1.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_definitions/zn.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_definitions/zn.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl diff --git a/NDTensors/src/lib/Sectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/sector_product.jl rename to NDTensors/src/lib/SymmetrySectors/src/sector_product.jl diff --git a/NDTensors/src/lib/Sectors/src/symmetry_style.jl b/NDTensors/src/lib/SymmetrySectors/src/symmetry_style.jl similarity index 100% rename from NDTensors/src/lib/Sectors/src/symmetry_style.jl rename to NDTensors/src/lib/SymmetrySectors/src/symmetry_style.jl diff --git a/NDTensors/src/lib/Sectors/test/runtests.jl b/NDTensors/src/lib/SymmetrySectors/test/runtests.jl similarity index 100% rename from NDTensors/src/lib/Sectors/test/runtests.jl rename to NDTensors/src/lib/SymmetrySectors/test/runtests.jl diff --git a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl b/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl similarity index 99% rename from NDTensors/src/lib/Sectors/test/test_fusion_rules.jl rename to NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl index d9dd06d5b2..0b0a1a8e0d 100644 --- a/NDTensors/src/lib/Sectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange, flip, tensor_product -using NDTensors.Sectors: +using NDTensors.SymmetrySectors: ⊗, Fib, Ising, diff --git a/NDTensors/src/lib/Sectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl similarity index 99% rename from NDTensors/src/lib/Sectors/test/test_sector_product.jl rename to NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index d002091e8c..e161ca6e5c 100644 --- a/NDTensors/src/lib/Sectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -1,5 +1,5 @@ @eval module $(gensym()) -using NDTensors.Sectors: +using NDTensors.SymmetrySectors: ×, ⊗, Fib, diff --git a/NDTensors/src/lib/Sectors/test/test_simple_sectors.jl b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl similarity index 99% rename from NDTensors/src/lib/Sectors/test/test_simple_sectors.jl rename to NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl index ca27f220ec..8ea8c7b925 100644 --- a/NDTensors/src/lib/Sectors/test/test_simple_sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl @@ -1,6 +1,6 @@ @eval module $(gensym()) using NDTensors.GradedAxes: dual -using NDTensors.Sectors: +using NDTensors.SymmetrySectors: Fib, Ising, O2, From d84df4ea74a07abe16eed9bbdd96a2a5a1e14bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 7 Oct 2024 17:57:02 -0400 Subject: [PATCH 156/194] more tolerant on missing sectors --- .../lib/SymmetrySectors/src/sector_product.jl | 35 ++++++++++++++----- .../test/test_sector_product.jl | 17 +++++---- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index da84a34d35..4bcd013d13 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -58,31 +58,42 @@ end # - ordered-like with a Tuple # - dictionary-like with a NamedTuple -sectors_isequal(o1::Tuple, o2::Tuple) = (o1 == o2) +function sectors_isequal(s1, s2) + return ==(sym_sectors_insert_unspecified(s1, s2)...) +end + +# get clean results when mixing implementations function sectors_isequal(nt::NamedTuple, ::Tuple{}) return sectors_isequal(nt, (;)) end function sectors_isequal(::Tuple{}, nt::NamedTuple) return sectors_isequal((;), nt) end -function sectors_isequal(nt1::NamedTuple, nt2::NamedTuple) - return ==(sym_sectors_insert_unspecified(nt1, nt2)...) +function sectors_isequal(::NamedTuple{()}, t::Tuple) + return sectors_isequal((), t) end - -# get clean results when mixing implementations +function sectors_isequal(t::Tuple, ::NamedTuple{()}) + return sectors_isequal(t, ()) +end +sectors_isequal(::Tuple{}, ::NamedTuple{()}) = true +sectors_isequal(::NamedTuple{()}, ::Tuple{}) = true sectors_isequal(::Tuple, ::NamedTuple) = false sectors_isequal(::NamedTuple, ::Tuple) = false -sectors_isless(::Tuple, ::Tuple) = throw(ArgumentError("Not implemented")) -sectors_isless(t1::T, t2::T) where {T<:Tuple} = t1 < t2 function sectors_isless(nt::NamedTuple, ::Tuple{}) return sectors_isless(nt, (;)) end function sectors_isless(::Tuple{}, nt::NamedTuple) return sectors_isless((;), nt) end -function sectors_isless(nt1::NamedTuple, nt2::NamedTuple) - return isless(sym_sectors_insert_unspecified(nt1, nt2)...) +function sectors_isless(::NamedTuple{()}, t::Tuple) + return sectors_isless((), t) +end +function sectors_isless(t::Tuple, ::NamedTuple{()}) + return sectors_isless(t, ()) +end +function sectors_isless(s1, s2) + return isless(sym_sectors_insert_unspecified(s1, s2)...) end sectors_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) @@ -223,6 +234,12 @@ function shared_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} return recover_style(T, fused) end +function sym_sectors_insert_unspecified(t1::Tuple, t2::Tuple) + n1 = length(t1) + n2 = length(t2) + return (t1..., trivial.(t2[(n1 + 1):end])...), (t2..., trivial.(t1[(n2 + 1):end])...) +end + # =========================== Dictionary-like implementation ============================= function SectorProduct(nt::NamedTuple) sectors = sort_keys(nt) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index e161ca6e5c..56f84ac63a 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -71,17 +71,22 @@ end end @testset "Ordered comparisons" begin - # convention: sectors must have same length to evaluate as equal + # convention: missing sectors are filled with singlets @test SectorProduct(U1(1), SU2(1)) == SectorProduct(U1(1), SU2(1)) @test SectorProduct(U1(1), SU2(0)) != SectorProduct(U1(1), SU2(1)) @test SectorProduct(U1(0), SU2(1)) != SectorProduct(U1(1), SU2(1)) - @test SectorProduct(U1(1)) != SectorProduct(U1(1), U1(0)) + @test SectorProduct(U1(1)) != U1(1) + @test SectorProduct(U1(1)) == SectorProduct(U1(1), U1(0)) + @test SectorProduct(U1(1)) != SectorProduct(U1(1), U1(1)) @test SectorProduct(U1(0), SU2(0)) == TrivialSector() + @test SectorProduct(U1(0), SU2(0)) == SectorProduct(TrivialSector(), SU2(0)) + @test SectorProduct(U1(0), SU2(0)) == SectorProduct(U1(0), TrivialSector()) + @test SectorProduct(U1(0), SU2(0)) == SectorProduct(TrivialSector(), TrivialSector()) - # convention: sectors must have same length to be compared @test SectorProduct(U1(0)) < SectorProduct((U1(1))) @test SectorProduct(U1(0), U1(2)) < SectorProduct((U1(1)), U1(0)) - @test_throws ArgumentError SectorProduct(U1(0)) < SectorProduct(U1(1), U1(2)) + @test SectorProduct(U1(0)) < SectorProduct(U1(0), U1(1)) + @test SectorProduct(U1(0)) > SectorProduct(U1(0), U1(-1)) end @testset "Quantum dimension and GradedUnitRange" begin @@ -596,13 +601,13 @@ end # Empty behaves as empty NamedTuple @test s != U1(0) - @test s != SectorProduct(U1(0)) + @test s == SectorProduct(U1(0)) @test s != SectorProduct(; A=U1(1)) @test s == SectorProduct(; A=U1(0)) @test SectorProduct(; A=U1(0)) == s @test !(s < s) - @test_throws ArgumentError s < SectorProduct(U1(0)) + @test s < SectorProduct(U1(1)) @test s < SectorProduct(; A=U1(1)) @test s > SectorProduct(; A=U1(-1)) @test !(s < SectorProduct(; A=U1(0))) From 2fe8bbca60318be20fa90450e6b5c5e6e19f3e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 7 Oct 2024 18:08:33 -0400 Subject: [PATCH 157/194] factorize sym_sectors_insert_unspecified --- .../src/lib/SymmetrySectors/src/sector_product.jl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 4bcd013d13..4bd9e930c2 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -58,6 +58,10 @@ end # - ordered-like with a Tuple # - dictionary-like with a NamedTuple +function sym_sectors_insert_unspecified(s1, s2) + return sectors_insert_unspecified(s1, s2), sectors_insert_unspecified(s2, s1) +end + function sectors_isequal(s1, s2) return ==(sym_sectors_insert_unspecified(s1, s2)...) end @@ -234,10 +238,9 @@ function shared_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} return recover_style(T, fused) end -function sym_sectors_insert_unspecified(t1::Tuple, t2::Tuple) +function sectors_insert_unspecified(t1::Tuple, t2::Tuple) n1 = length(t1) - n2 = length(t2) - return (t1..., trivial.(t2[(n1 + 1):end])...), (t2..., trivial.(t1[(n2 + 1):end])...) + return (t1..., trivial.(t2[(n1 + 1):end])...) end # =========================== Dictionary-like implementation ============================= @@ -258,10 +261,6 @@ function sectors_symmetrystyle(NT::Type{<:NamedTuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) end -function sym_sectors_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) - return sectors_insert_unspecified(nt1, nt2), sectors_insert_unspecified(nt2, nt1) -end - function sectors_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) diff1 = sectors_trivial(typeof(setdiff_keys(nt2, nt1))) return sort_keys(union_keys(nt1, diff1)) From ddb62d524e4fa072694d8ee8ddbfd13cc4273716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 8 Oct 2024 19:10:51 -0400 Subject: [PATCH 158/194] rename sectors product_sectors --- .../lib/SymmetrySectors/src/sector_product.jl | 157 +++++++++--------- .../test/test_sector_product.jl | 73 ++++---- 2 files changed, 120 insertions(+), 110 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 4bd9e930c2..5651fa82ab 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -7,38 +7,40 @@ using ..GradedAxes: AbstractGradedUnitRange, GradedAxes, dual # ===================================== Definition ======================================= struct SectorProduct{Sectors} <: AbstractSector - sectors::Sectors + product_sectors::Sectors global _SectorProduct(l) = new{typeof(l)}(l) end -SectorProduct(c::SectorProduct) = _SectorProduct(sectors(c)) +SectorProduct(c::SectorProduct) = _SectorProduct(product_sectors(c)) -sectors(s::SectorProduct) = s.sectors +product_sectors(s::SectorProduct) = s.product_sectors # ================================= Sectors interface ==================================== -SymmetryStyle(T::Type{<:SectorProduct}) = sectors_symmetrystyle(sectors_type(T)) +function SymmetryStyle(T::Type{<:SectorProduct}) + return product_sectors_symmetrystyle(product_sectors_type(T)) +end function quantum_dimension(::NotAbelianStyle, s::SectorProduct) - return mapreduce(quantum_dimension, *, sectors(s)) + return mapreduce(quantum_dimension, *, product_sectors(s)) end # use map instead of broadcast to support both Tuple and NamedTuple -GradedAxes.dual(s::SectorProduct) = SectorProduct(map(dual, sectors(s))) +GradedAxes.dual(s::SectorProduct) = SectorProduct(map(dual, product_sectors(s))) function trivial(type::Type{<:SectorProduct}) - return SectorProduct(sectors_trivial(sectors_type(type))) + return SectorProduct(product_sectors_trivial(product_sectors_type(type))) end # =================================== Base interface ===================================== function Base.:(==)(A::SectorProduct, B::SectorProduct) - return sectors_isequal(sectors(A), sectors(B)) + return product_sectors_isequal(product_sectors(A), product_sectors(B)) end function Base.show(io::IO, s::SectorProduct) - (length(sectors(s)) < 2) && print(io, "sector") + (length(product_sectors(s)) < 2) && print(io, "sector") print(io, "(") symbol = "" - for p in pairs(sectors(s)) + for p in pairs(product_sectors(s)) print(io, symbol) sector_show(io, p[1], p[2]) symbol = " × " @@ -50,7 +52,7 @@ sector_show(io::IO, ::Int, v) = print(io, v) sector_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") function Base.isless(s1::SectorProduct, s2::SectorProduct) - return sectors_isless(sectors(s1), sectors(s2)) + return product_sectors_isless(product_sectors(s1), product_sectors(s2)) end # ======================================= shared ========================================= @@ -58,67 +60,70 @@ end # - ordered-like with a Tuple # - dictionary-like with a NamedTuple -function sym_sectors_insert_unspecified(s1, s2) - return sectors_insert_unspecified(s1, s2), sectors_insert_unspecified(s2, s1) +function sym_product_sectors_insert_unspecified(s1, s2) + return product_sectors_insert_unspecified(s1, s2), + product_sectors_insert_unspecified(s2, s1) end -function sectors_isequal(s1, s2) - return ==(sym_sectors_insert_unspecified(s1, s2)...) +function product_sectors_isequal(s1, s2) + return ==(sym_product_sectors_insert_unspecified(s1, s2)...) end # get clean results when mixing implementations -function sectors_isequal(nt::NamedTuple, ::Tuple{}) - return sectors_isequal(nt, (;)) +function product_sectors_isequal(nt::NamedTuple, ::Tuple{}) + return product_sectors_isequal(nt, (;)) end -function sectors_isequal(::Tuple{}, nt::NamedTuple) - return sectors_isequal((;), nt) +function product_sectors_isequal(::Tuple{}, nt::NamedTuple) + return product_sectors_isequal((;), nt) end -function sectors_isequal(::NamedTuple{()}, t::Tuple) - return sectors_isequal((), t) +function product_sectors_isequal(::NamedTuple{()}, t::Tuple) + return product_sectors_isequal((), t) end -function sectors_isequal(t::Tuple, ::NamedTuple{()}) - return sectors_isequal(t, ()) +function product_sectors_isequal(t::Tuple, ::NamedTuple{()}) + return product_sectors_isequal(t, ()) end -sectors_isequal(::Tuple{}, ::NamedTuple{()}) = true -sectors_isequal(::NamedTuple{()}, ::Tuple{}) = true -sectors_isequal(::Tuple, ::NamedTuple) = false -sectors_isequal(::NamedTuple, ::Tuple) = false +product_sectors_isequal(::Tuple{}, ::NamedTuple{()}) = true +product_sectors_isequal(::NamedTuple{()}, ::Tuple{}) = true +product_sectors_isequal(::Tuple, ::NamedTuple) = false +product_sectors_isequal(::NamedTuple, ::Tuple) = false -function sectors_isless(nt::NamedTuple, ::Tuple{}) - return sectors_isless(nt, (;)) +function product_sectors_isless(nt::NamedTuple, ::Tuple{}) + return product_sectors_isless(nt, (;)) end -function sectors_isless(::Tuple{}, nt::NamedTuple) - return sectors_isless((;), nt) +function product_sectors_isless(::Tuple{}, nt::NamedTuple) + return product_sectors_isless((;), nt) end -function sectors_isless(::NamedTuple{()}, t::Tuple) - return sectors_isless((), t) +function product_sectors_isless(::NamedTuple{()}, t::Tuple) + return product_sectors_isless((), t) end -function sectors_isless(t::Tuple, ::NamedTuple{()}) - return sectors_isless(t, ()) +function product_sectors_isless(t::Tuple, ::NamedTuple{()}) + return product_sectors_isless(t, ()) end -function sectors_isless(s1, s2) - return isless(sym_sectors_insert_unspecified(s1, s2)...) +function product_sectors_isless(s1, s2) + return isless(sym_product_sectors_insert_unspecified(s1, s2)...) end -sectors_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) -sectors_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) +product_sectors_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) +product_sectors_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) -sectors_type(::Type{<:SectorProduct{T}}) where {T} = T +product_sectors_type(::Type{<:SectorProduct{T}}) where {T} = T -function sectors_fusion_rule(sects1, sects2) - shared_sect = shared_sectors_fusion_rule(sectors_common(sects1, sects2)...) - diff_sect = SectorProduct(sectors_diff(sects1, sects2)) +function product_sectors_fusion_rule(sects1, sects2) + shared_sect = shared_product_sectors_fusion_rule( + product_sectors_common(sects1, sects2)... + ) + diff_sect = SectorProduct(product_sectors_diff(sects1, sects2)) return shared_sect × diff_sect end -# edge case with empty sectors -sectors_fusion_rule(sects::Tuple, ::NamedTuple{()}) = SectorProduct(sects) -sectors_fusion_rule(::NamedTuple{()}, sects::Tuple) = SectorProduct(sects) -sectors_fusion_rule(sects::NamedTuple, ::Tuple{}) = SectorProduct(sects) -sectors_fusion_rule(::Tuple{}, sects::NamedTuple) = SectorProduct(sects) +# edge case with empty product_sectors +product_sectors_fusion_rule(sects::Tuple, ::NamedTuple{()}) = SectorProduct(sects) +product_sectors_fusion_rule(::NamedTuple{()}, sects::Tuple) = SectorProduct(sects) +product_sectors_fusion_rule(sects::NamedTuple, ::Tuple{}) = SectorProduct(sects) +product_sectors_fusion_rule(::Tuple{}, sects::NamedTuple) = SectorProduct(sects) function recover_style(T::Type, fused) - style = sectors_symmetrystyle(T) + style = product_sectors_symmetrystyle(T) return recover_sector_product_type(style, T, fused) end @@ -146,11 +151,11 @@ function recover_sector_product_type(T::Type, c::AbstractSector) end function recover_sector_product_type(T::Type, c::SectorProduct) - return recover_sector_product_type(T, sectors(c)) + return recover_sector_product_type(T, product_sectors(c)) end function recover_sector_product_type(T::Type{<:SectorProduct}, sects) - return recover_sector_product_type(sectors_type(T), sects) + return recover_sector_product_type(product_sectors_type(T), sects) end function recover_sector_product_type(T::Type, sects) @@ -160,7 +165,7 @@ end # ================================= Cartesian Product ==================================== ×(c1::AbstractSector, c2::AbstractSector) = ×(SectorProduct(c1), SectorProduct(c2)) function ×(p1::SectorProduct, p2::SectorProduct) - return SectorProduct(sectors_product(sectors(p1), sectors(p2))) + return SectorProduct(product_sectors_product(product_sectors(p1), product_sectors(p2))) end ×(a, g::AbstractUnitRange) = ×(to_gradedrange(a), g) @@ -194,12 +199,14 @@ end # generic case: fusion returns a GradedAxes, even for fusion with Empty function fusion_rule(::NotAbelianStyle, s1::SectorProduct, s2::SectorProduct) - return to_gradedrange(sectors_fusion_rule(sectors(s1), sectors(s2))) + return to_gradedrange( + product_sectors_fusion_rule(product_sectors(s1), product_sectors(s2)) + ) end # Abelian case: fusion returns SectorProduct function fusion_rule(::AbelianStyle, s1::SectorProduct, s2::SectorProduct) - return sectors_fusion_rule(sectors(s1), sectors(s2)) + return product_sectors_fusion_rule(product_sectors(s1), product_sectors(s2)) end # lift ambiguities for TrivialSector @@ -212,41 +219,41 @@ fusion_rule(::NotAbelianStyle, ::TrivialSector, c::SectorProduct) = to_gradedran SectorProduct(t::Tuple) = _SectorProduct(t) SectorProduct(sects::AbstractSector...) = SectorProduct(sects) -function sectors_symmetrystyle(T::Type{<:Tuple}) +function product_sectors_symmetrystyle(T::Type{<:Tuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) end -sectors_product(::NamedTuple{()}, l1::Tuple) = l1 -sectors_product(l2::Tuple, ::NamedTuple{()}) = l2 -sectors_product(l1::Tuple, l2::Tuple) = (l1..., l2...) +product_sectors_product(::NamedTuple{()}, l1::Tuple) = l1 +product_sectors_product(l2::Tuple, ::NamedTuple{()}) = l2 +product_sectors_product(l1::Tuple, l2::Tuple) = (l1..., l2...) -sectors_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) +product_sectors_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) -function sectors_common(t1::Tuple, t2::Tuple) +function product_sectors_common(t1::Tuple, t2::Tuple) n = min(length(t1), length(t2)) return t1[begin:n], t2[begin:n] end -function sectors_diff(t1::Tuple, t2::Tuple) +function product_sectors_diff(t1::Tuple, t2::Tuple) n1 = length(t1) n2 = length(t2) return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] end -function shared_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} +function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} fused = map(fusion_rule, shared1, shared2) return recover_style(T, fused) end -function sectors_insert_unspecified(t1::Tuple, t2::Tuple) +function product_sectors_insert_unspecified(t1::Tuple, t2::Tuple) n1 = length(t1) return (t1..., trivial.(t2[(n1 + 1):end])...) end # =========================== Dictionary-like implementation ============================= function SectorProduct(nt::NamedTuple) - sectors = sort_keys(nt) - return _SectorProduct(sectors) + product_sectors = sort_keys(nt) + return _SectorProduct(product_sectors) end SectorProduct(; kws...) = SectorProduct((; kws...)) @@ -257,38 +264,38 @@ function SectorProduct(pairs::Pair...) return SectorProduct(NamedTuple{keys}(vals)) end -function sectors_symmetrystyle(NT::Type{<:NamedTuple}) +function product_sectors_symmetrystyle(NT::Type{<:NamedTuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) end -function sectors_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) - diff1 = sectors_trivial(typeof(setdiff_keys(nt2, nt1))) +function product_sectors_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) + diff1 = product_sectors_trivial(typeof(setdiff_keys(nt2, nt1))) return sort_keys(union_keys(nt1, diff1)) end -sectors_product(l1::NamedTuple, ::Tuple{}) = l1 -sectors_product(::Tuple{}, l2::NamedTuple) = l2 -function sectors_product(l1::NamedTuple, l2::NamedTuple) +product_sectors_product(l1::NamedTuple, ::Tuple{}) = l1 +product_sectors_product(::Tuple{}, l2::NamedTuple) = l2 +function product_sectors_product(l1::NamedTuple, l2::NamedTuple) if length(intersect_keys(l1, l2)) > 0 throw(ArgumentError("Cannot define product of shared keys")) end return union_keys(l1, l2) end -function sectors_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} +function product_sectors_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} return NamedTuple{Keys}(trivial.(fieldtypes(type))) end -function sectors_common(nt1::NamedTuple, nt2::NamedTuple) +function product_sectors_common(nt1::NamedTuple, nt2::NamedTuple) # SectorProduct(nt::NamedTuple) sorts keys at init @assert issorted(keys(nt1)) @assert issorted(keys(nt2)) return intersect_keys(nt1, nt2), intersect_keys(nt2, nt1) end -sectors_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) +product_sectors_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) -function shared_sectors_fusion_rule(shared1::T, shared2::T) where {T<:NamedTuple} +function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:NamedTuple} fused = map(fusion_rule, values(shared1), values(shared2)) return recover_style(T, fused) end diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 56f84ac63a..034dfc460a 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -13,7 +13,7 @@ using NDTensors.SymmetrySectors: block_dimensions, quantum_dimension, recover_sector_product_type, - sectors, + product_sectors, trivial using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange using Test: @inferred, @test, @testset, @test_throws @@ -28,42 +28,44 @@ end @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin s = SectorProduct(U1(1)) - @test length(sectors(s)) == 1 + @test length(product_sectors(s)) == 1 @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == SectorProduct(U1(-1)) - @test sectors(s)[1] == U1(1) + @test product_sectors(s)[1] == U1(1) @test (@inferred_latest trivial(s)) == SectorProduct(U1(0)) s = SectorProduct(U1(1), U1(2)) - @test length(sectors(s)) == 2 + @test length(product_sectors(s)) == 2 @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == SectorProduct(U1(-1), U1(-2)) - @test sectors(s)[1] == U1(1) - @test sectors(s)[2] == U1(2) + @test product_sectors(s)[1] == U1(1) + @test product_sectors(s)[2] == U1(2) @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) - @test length(sectors(s)) == 3 + @test length(product_sectors(s)) == 3 @test (@inferred quantum_dimension(s)) == 2 @test (@inferred dual(s)) == U1(-1) × SU2(1//2) × U1(-3) - @test sectors(s)[1] == U1(1) - @test sectors(s)[2] == SU2(1//2) - @test sectors(s)[3] == U1(3) + @test product_sectors(s)[1] == U1(1) + @test product_sectors(s)[2] == SU2(1//2) + @test product_sectors(s)[3] == U1(3) @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), SU2(0), U1(0)) - @test (@inferred recover_sector_product_type(typeof(sectors(s)), sectors(s))) == s - @test (@inferred recover_sector_product_type(typeof(s), sectors(s))) == s + @test (@inferred recover_sector_product_type( + typeof(product_sectors(s)), product_sectors(s) + )) == s + @test (@inferred recover_sector_product_type(typeof(s), product_sectors(s))) == s s = U1(3) × SU2(1//2) × Fib("τ") - @test length(sectors(s)) == 3 + @test length(product_sectors(s)) == 3 @test (@inferred_latest quantum_dimension(s)) == 1.0 + √5 @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") - @test sectors(s)[1] == U1(3) - @test sectors(s)[2] == SU2(1//2) - @test sectors(s)[3] == Fib("τ") + @test product_sectors(s)[1] == U1(3) + @test product_sectors(s)[2] == SU2(1//2) + @test product_sectors(s)[3] == Fib("τ") @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), SU2(0), Fib("1")) s = TrivialSector() × U1(3) × SU2(1 / 2) - @test length(sectors(s)) == 3 + @test length(product_sectors(s)) == 3 @test (@inferred_latest quantum_dimension(s)) == 2 @test dual(s) == TrivialSector() × U1(-3) × SU2(1//2) @test (@inferred_latest trivial(s)) == SectorProduct(TrivialSector(), U1(0), SU2(0)) @@ -71,7 +73,7 @@ end end @testset "Ordered comparisons" begin - # convention: missing sectors are filled with singlets + # convention: missing product_sectors are filled with singlets @test SectorProduct(U1(1), SU2(1)) == SectorProduct(U1(1), SU2(1)) @test SectorProduct(U1(1), SU2(0)) != SectorProduct(U1(1), SU2(1)) @test SectorProduct(U1(0), SU2(1)) != SectorProduct(U1(1), SU2(1)) @@ -297,28 +299,29 @@ end @testset "Test Named Sector Products" begin @testset "Construct from × of NamedTuples" begin s = (A=U1(1),) × (B=Z{2}(0),) - @test length(sectors(s)) == 2 - @test sectors(s)[:A] == U1(1) - @test sectors(s)[:B] == Z{2}(0) + @test length(product_sectors(s)) == 2 + @test product_sectors(s)[:A] == U1(1) + @test product_sectors(s)[:B] == Z{2}(0) @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) - @test length(sectors(s)) == 2 - @test sectors(s)[:A] == U1(1) - @test sectors(s)[:B] == SU2(2) + @test length(product_sectors(s)) == 2 + @test product_sectors(s)[:A] == U1(1) + @test product_sectors(s)[:B] == SU2(2) @test (@inferred quantum_dimension(s)) == 5 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=SU2(0),) - @test (@inferred recover_sector_product_type(typeof(sectors(s)), Tuple(sectors(s)))) == - s - @test (@inferred recover_sector_product_type(typeof(s), Tuple(sectors(s)))) == s + @test (@inferred recover_sector_product_type( + typeof(product_sectors(s)), Tuple(product_sectors(s)) + )) == s + @test (@inferred recover_sector_product_type(typeof(s), Tuple(product_sectors(s)))) == s @test s == (B=SU2(2),) × (A=U1(1),) s = s × (C=Ising("ψ"),) - @test length(sectors(s)) == 3 - @test sectors(s)[:C] == Ising("ψ") + @test length(product_sectors(s)) == 3 + @test product_sectors(s)[:C] == Ising("ψ") @test (@inferred_latest quantum_dimension(s)) == 5.0 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) @@ -329,22 +332,22 @@ end @testset "Construct from Pairs" begin s = SectorProduct("A" => U1(2)) - @test length(sectors(s)) == 1 - @test sectors(s)[:A] == U1(2) + @test length(product_sectors(s)) == 1 + @test product_sectors(s)[:A] == U1(2) @test s == SectorProduct(; A=U1(2)) @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == SectorProduct("A" => U1(-2)) @test (@inferred_latest trivial(s)) == SectorProduct(; A=U1(0)) s = SectorProduct("B" => Ising("ψ"), :C => Z{2}(1)) - @test length(sectors(s)) == 2 - @test sectors(s)[:B] == Ising("ψ") - @test sectors(s)[:C] == Z{2}(1) + @test length(product_sectors(s)) == 2 + @test product_sectors(s)[:B] == Ising("ψ") + @test product_sectors(s)[:C] == Z{2}(1) @test (@inferred_latest quantum_dimension(s)) == 1.0 end @testset "Comparisons with unspecified labels" begin - # convention: sectors evaluate as equal if unmatched labels are trivial + # convention: product_sectors evaluate as equal if unmatched labels are trivial # this is different from ordered tuple convention q2 = SectorProduct(; N=U1(2)) q20 = (N=U1(2),) × (J=SU2(0),) From d65bbbb498eab4c55253253362f8cf1839baa546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 9 Oct 2024 14:07:06 -0400 Subject: [PATCH 159/194] define abelian_label_fusion_rule --- NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl | 4 ++-- .../src/lib/SymmetrySectors/src/sector_definitions/u1.jl | 2 +- .../src/lib/SymmetrySectors/src/sector_definitions/zn.jl | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl b/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl index 3c25e622c5..ba6d04e91f 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl @@ -63,8 +63,8 @@ function fusion_rule(::AbelianStyle, c1::C, c2::C) where {C<:AbstractSector} return label(only(fusion_rule(NotAbelianStyle(), c1, c2))) end -function label_fusion_rule(sector_type::Type{<:AbstractSector}, ::Any, ::Any) - return error("`label_fusion_rule` not defined for type $(sector_type).") +function label_fusion_rule(sector_type::Type{<:AbstractSector}, l1, l2) + return [1], [abelian_label_fusion_rule(sector_type, l1, l2)] end # ================================ GradedAxes interface ================================== diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl index 599804767d..277b42e9ba 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl @@ -19,7 +19,7 @@ sector_label(u::U1) = u.n trivial(::Type{U1}) = trivial(U1{Int}) trivial(::Type{U1{T}}) where {T} = U1(T(0)) -label_fusion_rule(T::Type{<:U1}, n1, n2) = [1], [T(n1 + n2)] +abelian_label_fusion_rule(T::Type{<:U1}, n1, n2) = T(n1 + n2) # hide label type in printing function Base.show(io::IO, u::U1) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl index d30e7a3f23..2c98e75186 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl @@ -18,9 +18,8 @@ modulus(c::Z) = modulus(typeof(c)) trivial(category_type::Type{<:Z}) = category_type(0) -function label_fusion_rule(category_type::Type{<:Z}, n1, n2) - irrep = category_type((n1 + n2) % modulus(category_type)) - return [1], [irrep] +function abelian_label_fusion_rule(category_type::Type{<:Z}, n1, n2) + return category_type((n1 + n2) % modulus(category_type)) end GradedAxes.dual(c::Z) = typeof(c)(mod(-sector_label(c), modulus(c))) From 97f75224fe7d4a3cb5df56d8ba95b559587f6e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 9 Oct 2024 14:07:37 -0400 Subject: [PATCH 160/194] use list comprehension --- NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl index 7174ce1b5c..9500b349d9 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl @@ -92,7 +92,7 @@ quantum_dimension(s::SU{2}) = sector_label(s)[1] + 1 GradedAxes.dual(s::SU{2}) = s function label_fusion_rule(::Type{<:SU{2}}, s1, s2) - irreps = collect(SU{2}((i,)) for i in (abs(s1[1] - s2[1])):2:(s1[1] + s2[1])) + irreps = [SU{2}((i,)) for i in (abs(s1[1] - s2[1])):2:(s1[1] + s2[1])] degen = ones(Int, length(irreps)) return degen, irreps end From e21d7acc05b9c7dcfaf7dfadcc011a262f869bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 9 Oct 2024 15:48:24 -0400 Subject: [PATCH 161/194] simplify product_sectors_fusion_rule --- .../lib/SymmetrySectors/src/sector_product.jl | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 5651fa82ab..2d75e8c508 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -122,25 +122,27 @@ product_sectors_fusion_rule(::NamedTuple{()}, sects::Tuple) = SectorProduct(sect product_sectors_fusion_rule(sects::NamedTuple, ::Tuple{}) = SectorProduct(sects) product_sectors_fusion_rule(::Tuple{}, sects::NamedTuple) = SectorProduct(sects) -function recover_style(T::Type, fused) - style = product_sectors_symmetrystyle(T) - return recover_sector_product_type(style, T, fused) +function fix_fused_product_type(T::Type, fused) + return fix_fused_product_type(product_sectors_symmetrystyle(T), T, fused) end -function recover_sector_product_type(::AbelianStyle, T::Type, fused) +function fix_fused_product_type(::AbelianStyle, T::Type, fused) return recover_sector_product_type(T, fused) end -function recover_sector_product_type(::NotAbelianStyle, T::Type, fused) - # here fused contains at least one graded unit range. +function fix_fused_product_type(::NotAbelianStyle, T::Type, fused) + g = factorize_gradedaxis(fused) + return recover_gradedaxis_product_type(T, g) +end + +function factorize_gradedaxis(fused) # convert eg. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} g = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × - type_fixed = recover_sector_product_type(T, g) - return type_fixed + return g end -function recover_sector_product_type(T::Type, g0::AbstractGradedUnitRange) +function recover_gradedaxis_product_type(T::Type, g0::AbstractGradedUnitRange) new_labels = recover_sector_product_type.(T, blocklabels(g0)) new_blocklengths = labelled.(unlabel.(blocklengths(g0)), new_labels) return gradedrange(new_blocklengths) @@ -206,7 +208,7 @@ end # Abelian case: fusion returns SectorProduct function fusion_rule(::AbelianStyle, s1::SectorProduct, s2::SectorProduct) - return product_sectors_fusion_rule(product_sectors(s1), product_sectors(s2)) + return label(only(fusion_rule(NotAbelianStyle(), s1, s2))) end # lift ambiguities for TrivialSector @@ -242,7 +244,7 @@ end function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} fused = map(fusion_rule, shared1, shared2) - return recover_style(T, fused) + return fix_fused_product_type(T, fused) end function product_sectors_insert_unspecified(t1::Tuple, t2::Tuple) @@ -297,5 +299,5 @@ product_sectors_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:NamedTuple} fused = map(fusion_rule, values(shared1), values(shared2)) - return recover_style(T, fused) + return fix_fused_product_type(T, fused) end From 7461b113127f28ca5082941af9c91e8224acbdad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 9 Oct 2024 15:55:32 -0400 Subject: [PATCH 162/194] fix tests for julia 1.6 --- .../test/test_sector_product.jl | 133 +++++++++--------- 1 file changed, 69 insertions(+), 64 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 034dfc460a..671ead9db0 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -29,31 +29,31 @@ end @testset "Ordered Constructor" begin s = SectorProduct(U1(1)) @test length(product_sectors(s)) == 1 - @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == SectorProduct(U1(-1)) + @test (@inferred_latest quantum_dimension(s)) == 1 + @test (@inferred_latest dual(s)) == SectorProduct(U1(-1)) @test product_sectors(s)[1] == U1(1) @test (@inferred_latest trivial(s)) == SectorProduct(U1(0)) s = SectorProduct(U1(1), U1(2)) @test length(product_sectors(s)) == 2 - @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == SectorProduct(U1(-1), U1(-2)) + @test (@inferred_latest quantum_dimension(s)) == 1 + @test (@inferred_latest dual(s)) == SectorProduct(U1(-1), U1(-2)) @test product_sectors(s)[1] == U1(1) @test product_sectors(s)[2] == U1(2) @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) @test length(product_sectors(s)) == 3 - @test (@inferred quantum_dimension(s)) == 2 - @test (@inferred dual(s)) == U1(-1) × SU2(1//2) × U1(-3) + @test (@inferred_latest quantum_dimension(s)) == 2 + @test (@inferred_latest dual(s)) == U1(-1) × SU2(1//2) × U1(-3) @test product_sectors(s)[1] == U1(1) @test product_sectors(s)[2] == SU2(1//2) @test product_sectors(s)[3] == U1(3) @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), SU2(0), U1(0)) - @test (@inferred recover_sector_product_type( + @test (@inferred_latest recover_sector_product_type( typeof(product_sectors(s)), product_sectors(s) )) == s - @test (@inferred recover_sector_product_type(typeof(s), product_sectors(s))) == s + @test (@inferred_latest recover_sector_product_type(typeof(s), product_sectors(s))) == s s = U1(3) × SU2(1//2) × Fib("τ") @test length(product_sectors(s)) == 3 @@ -93,7 +93,7 @@ end @testset "Quantum dimension and GradedUnitRange" begin g = gradedrange([(U1(0) × Z{2}(0)) => 1, (U1(1) × Z{2}(0)) => 2]) # abelian - @test (@inferred quantum_dimension(g)) == 3 + @test (@inferred_latest quantum_dimension(g)) == 3 g = gradedrange([ # non-abelian (SU2(0) × SU2(0)) => 1, @@ -101,16 +101,16 @@ end (SU2(0) × SU2(1)) => 1, (SU2(1) × SU2(1)) => 1, ]) - @test (@inferred quantum_dimension(g)) == 16 - @test (@inferred block_dimensions(g)) == [1, 3, 3, 9] + @test (@inferred_latest quantum_dimension(g)) == 16 + @test (@inferred_latest block_dimensions(g)) == [1, 3, 3, 9] # mixed group g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) - @test (@inferred quantum_dimension(g)) == 4 - @test (@inferred block_dimensions(g)) == [1, 3] + @test (@inferred_latest quantum_dimension(g)) == 4 + @test (@inferred_latest block_dimensions(g)) == [1, 3] g = gradedrange([(SU2(0) × U1(0) × SU2(1//2)) => 1, (SU2(0) × U1(1) × SU2(1//2)) => 1]) - @test (@inferred quantum_dimension(g)) == 4 - @test (@inferred block_dimensions(g)) == [2, 2] + @test (@inferred_latest quantum_dimension(g)) == 4 + @test (@inferred_latest block_dimensions(g)) == [2, 2] # NonGroupCategory g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) @@ -185,7 +185,7 @@ end ]), ) @test space_isequal( - (@inferred phh ⊗ phh), + (@inferred_latest phh ⊗ phh), gradedrange([ (SU2(0) × SU2(0)) => 1, (SU2(1) × SU2(0)) => 1, @@ -199,11 +199,11 @@ end ı = Fib("1") τ = Fib("τ") s = ı × ı - @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) + @test space_isequal((@inferred_latest s ⊗ s), gradedrange([s => 1])) s = τ × τ @test space_isequal( - (@inferred s ⊗ s), + (@inferred_latest s ⊗ s), gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]), ) @@ -213,19 +213,20 @@ end g = gradedrange([ (ı × Ising("1")) => 1, (τ × Ising("1")) => 1, (ı × ψ) => 1, (τ × ψ) => 1 ]) - @test space_isequal((@inferred s ⊗ s), g) + @test space_isequal((@inferred_latest s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) @test space_isequal( - (@inferred p2h ⊗ p1h), gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) + (@inferred_latest p2h ⊗ p1h), + gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]), ) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) @test space_isequal( - (@inferred p1h1 ⊗ p1h1), + (@inferred_latest p1h1 ⊗ p1h1), gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]), ) end @@ -233,7 +234,7 @@ end @testset "Fusion of fully mixed products" begin s = U1(1) × SU2(1//2) × Ising("σ") @test space_isequal( - (@inferred s ⊗ s), + (@inferred_latest s ⊗ s), gradedrange([ (U1(2) × SU2(0) × Ising("1")) => 1, (U1(2) × SU2(1) × Ising("1")) => 1, @@ -246,7 +247,7 @@ end τ = Fib("τ") s = SU2(1//2) × U1(1) × τ @test space_isequal( - (@inferred s ⊗ s), + (@inferred_latest s ⊗ s), gradedrange([ (SU2(0) × U1(2) × ı) => 1, (SU2(1) × U1(2) × ı) => 1, @@ -257,7 +258,7 @@ end s = U1(1) × ı × τ @test space_isequal( - (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) + (@inferred_latest s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) ) end @@ -265,12 +266,12 @@ end @test SectorProduct(U1(1) × U1(0)) ⊗ SectorProduct(U1(1)) == SectorProduct(U1(2) × U1(0)) @test space_isequal( - (@inferred SectorProduct(SU2(0) × SU2(0)) ⊗ SectorProduct(SU2(1))), + (@inferred_latest SectorProduct(SU2(0) × SU2(0)) ⊗ SectorProduct(SU2(1))), gradedrange([SectorProduct(SU2(1) × SU2(0)) => 1]), ) @test space_isequal( - (@inferred SectorProduct(SU2(1) × U1(1)) ⊗ SectorProduct(SU2(0))), + (@inferred_latest SectorProduct(SU2(1) × U1(1)) ⊗ SectorProduct(SU2(0))), gradedrange([SectorProduct(SU2(1) × U1(1)) => 1]), ) @test space_isequal( @@ -302,28 +303,30 @@ end @test length(product_sectors(s)) == 2 @test product_sectors(s)[:A] == U1(1) @test product_sectors(s)[:B] == Z{2}(0) - @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) + @test (@inferred_latest quantum_dimension(s)) == 1 + @test (@inferred_latest dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) @test length(product_sectors(s)) == 2 @test product_sectors(s)[:A] == U1(1) @test product_sectors(s)[:B] == SU2(2) - @test (@inferred quantum_dimension(s)) == 5 - @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) + @test (@inferred_latest quantum_dimension(s)) == 5 + @test (@inferred_latest dual(s)) == (A=U1(-1),) × (B=SU2(2),) @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=SU2(0),) - @test (@inferred recover_sector_product_type( + @test (@inferred_latest recover_sector_product_type( typeof(product_sectors(s)), Tuple(product_sectors(s)) )) == s - @test (@inferred recover_sector_product_type(typeof(s), Tuple(product_sectors(s)))) == s + @test (@inferred_latest recover_sector_product_type( + typeof(s), Tuple(product_sectors(s)) + )) == s @test s == (B=SU2(2),) × (A=U1(1),) s = s × (C=Ising("ψ"),) @test length(product_sectors(s)) == 3 @test product_sectors(s)[:C] == Ising("ψ") @test (@inferred_latest quantum_dimension(s)) == 5.0 - @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) + @test (@inferred_latest dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) s1 = (A=U1(1),) × (B=Z{2}(0),) s2 = (A=U1(1),) × (C=Z{2}(0),) @@ -335,8 +338,8 @@ end @test length(product_sectors(s)) == 1 @test product_sectors(s)[:A] == U1(2) @test s == SectorProduct(; A=U1(2)) - @test (@inferred quantum_dimension(s)) == 1 - @test (@inferred dual(s)) == SectorProduct("A" => U1(-2)) + @test (@inferred_latest quantum_dimension(s)) == 1 + @test (@inferred_latest dual(s)) == SectorProduct("A" => U1(-2)) @test (@inferred_latest trivial(s)) == SectorProduct(; A=U1(0)) s = SectorProduct("B" => Ising("ψ"), :C => Z{2}(1)) @@ -371,7 +374,7 @@ end g = gradedrange([ SectorProduct(; A=U1(0), B=Z{2}(0)) => 1, SectorProduct(; A=U1(1), B=Z{2}(0)) => 2 ]) # abelian - @test (@inferred quantum_dimension(g)) == 3 + @test (@inferred_latest quantum_dimension(g)) == 3 g = gradedrange([ # non-abelian SectorProduct(; A=SU2(0), B=SU2(0)) => 1, @@ -379,19 +382,19 @@ end SectorProduct(; A=SU2(0), B=SU2(1)) => 1, SectorProduct(; A=SU2(1), B=SU2(1)) => 1, ]) - @test (@inferred quantum_dimension(g)) == 16 + @test (@inferred_latest quantum_dimension(g)) == 16 # mixed group g = gradedrange([ SectorProduct(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, SectorProduct(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, ]) - @test (@inferred quantum_dimension(g)) == 4 + @test (@inferred_latest quantum_dimension(g)) == 4 g = gradedrange([ SectorProduct(; A=SU2(0), B=Z{2}(0), C=SU2(1//2)) => 1, SectorProduct(; A=SU2(0), B=Z{2}(1), C=SU2(1//2)) => 1, ]) - @test (@inferred quantum_dimension(g)) == 4 + @test (@inferred_latest quantum_dimension(g)) == 4 # non group sectors g_fib = gradedrange([SectorProduct(; A=Fib("1"), B=Fib("1")) => 1]) @@ -445,7 +448,7 @@ end phab = SectorProduct(; A=SU2(1//2), B=SU2(1//2)) @test space_isequal( - (@inferred pha ⊗ pha), + (@inferred_latest pha ⊗ pha), gradedrange([SectorProduct(; A=SU2(0)) => 1, SectorProduct(; A=SU2(1)) => 1]), ) @test space_isequal((@inferred_latest pha ⊗ p0), gradedrange([pha => 1])) @@ -453,7 +456,7 @@ end @test space_isequal((@inferred_latest pha ⊗ phb), gradedrange([phab => 1])) @test space_isequal( - (@inferred phab ⊗ phab), + (@inferred_latest phab ⊗ phab), gradedrange([ SectorProduct(; A=SU2(0), B=SU2(0)) => 1, SectorProduct(; A=SU2(1), B=SU2(0)) => 1, @@ -467,11 +470,11 @@ end ı = Fib("1") τ = Fib("τ") s = SectorProduct(; A=ı, B=ı) - @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) + @test space_isequal((@inferred_latest s ⊗ s), gradedrange([s => 1])) s = SectorProduct(; A=τ, B=τ) @test space_isequal( - (@inferred s ⊗ s), + (@inferred_latest s ⊗ s), gradedrange([ SectorProduct(; A=ı, B=ı) => 1, SectorProduct(; A=τ, B=ı) => 1, @@ -489,7 +492,7 @@ end SectorProduct(; A=ı, B=ψ) => 1, SectorProduct(; A=τ, B=ψ) => 1, ]) - @test space_isequal((@inferred s ⊗ s), g) + @test space_isequal((@inferred_latest s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -503,16 +506,18 @@ end q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test space_isequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) - @test space_isequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) - @test space_isequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) - @test space_isequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) + @test space_isequal((@inferred_latest q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) + @test space_isequal((@inferred_latest q10 ⊗ q1h), gradedrange([q2h => 1])) + @test space_isequal((@inferred_latest q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) + @test space_isequal( + (@inferred_latest q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1]) + ) end @testset "Fusion of fully mixed products" begin s = SectorProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) @test space_isequal( - (@inferred s ⊗ s), + (@inferred_latest s ⊗ s), gradedrange([ SectorProduct(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, SectorProduct(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, @@ -525,7 +530,7 @@ end τ = Fib("τ") s = SectorProduct(; A=SU2(1//2), B=U1(1), C=τ) @test space_isequal( - (@inferred s ⊗ s), + (@inferred_latest s ⊗ s), gradedrange([ SectorProduct(; A=SU2(0), B=U1(2), C=ı) => 1, SectorProduct(; A=SU2(1), B=U1(2), C=ı) => 1, @@ -536,7 +541,7 @@ end s = SectorProduct(; A=τ, B=U1(1), C=ı) @test space_isequal( - (@inferred s ⊗ s), + (@inferred_latest s ⊗ s), gradedrange([ SectorProduct(; B=U1(2), A=ı, C=ı) => 1, SectorProduct(; B=U1(2), A=τ, C=ı) => 1 ]), @@ -567,28 +572,28 @@ end @test s == TrivialSector() @test s == SectorProduct(()) @test s == SectorProduct((;)) - @test (@inferred dual(s)) == s - @test (@inferred s × s) == s + @test (@inferred_latest dual(s)) == s + @test (@inferred_latest s × s) == s @test (@inferred_latest s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 1 + @test (@inferred_latest quantum_dimension(s)) == 1 @test (@inferred_latest trivial(s)) == s g0 = gradedrange([s => 2]) @test space_isequal((@inferred_latest fusion_product(g0, g0)), gradedrange([s => 4])) - @test (@inferred s × U1(1)) == SectorProduct(U1(1)) - @test (@inferred s × SectorProduct(U1(1))) == SectorProduct(U1(1)) - @test (@inferred s × SectorProduct(; A=U1(1))) == SectorProduct(; A=U1(1)) - @test (@inferred U1(1) × s) == SectorProduct(U1(1)) - @test (@inferred SectorProduct(U1(1)) × s) == SectorProduct(U1(1)) - @test (@inferred SectorProduct(; A=U1(1)) × s) == SectorProduct(; A=U1(1)) + @test (@inferred_latest s × U1(1)) == SectorProduct(U1(1)) + @test (@inferred_latest s × SectorProduct(U1(1))) == SectorProduct(U1(1)) + @test (@inferred_latest s × SectorProduct(; A=U1(1))) == SectorProduct(; A=U1(1)) + @test (@inferred_latest U1(1) × s) == SectorProduct(U1(1)) + @test (@inferred_latest SectorProduct(U1(1)) × s) == SectorProduct(U1(1)) + @test (@inferred_latest SectorProduct(; A=U1(1)) × s) == SectorProduct(; A=U1(1)) @test (@inferred_latest U1(1) ⊗ s) == SectorProduct(U1(1)) - @test (@inferred SU2(0) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) - @test (@inferred Fib("τ") ⊗ s) == gradedrange([SectorProduct(Fib("τ")) => 1]) + @test (@inferred_latest SU2(0) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred_latest Fib("τ") ⊗ s) == gradedrange([SectorProduct(Fib("τ")) => 1]) @test (@inferred_latest s ⊗ U1(1)) == SectorProduct(U1(1)) - @test (@inferred s ⊗ SU2(0)) == gradedrange([SectorProduct(SU2(0)) => 1]) - @test (@inferred s ⊗ Fib("τ")) == gradedrange([SectorProduct(Fib("τ")) => 1]) + @test (@inferred_latest s ⊗ SU2(0)) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred_latest s ⊗ Fib("τ")) == gradedrange([SectorProduct(Fib("τ")) => 1]) @test (@inferred_latest SectorProduct(U1(1)) ⊗ s) == SectorProduct(U1(1)) @test (@inferred_latest SectorProduct(SU2(0)) ⊗ s) == From 20acb548ecac52c40c89b7318c53ac88c7d4714d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 9 Oct 2024 21:35:35 -0400 Subject: [PATCH 163/194] explicit variable name --- .../src/lib/SymmetrySectors/src/sector_definitions/u1.jl | 2 +- .../src/lib/SymmetrySectors/src/sector_definitions/zn.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl index 277b42e9ba..d4e8d48869 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl @@ -19,7 +19,7 @@ sector_label(u::U1) = u.n trivial(::Type{U1}) = trivial(U1{Int}) trivial(::Type{U1{T}}) where {T} = U1(T(0)) -abelian_label_fusion_rule(T::Type{<:U1}, n1, n2) = T(n1 + n2) +abelian_label_fusion_rule(sector_type::Type{<:U1}, n1, n2) = sector_type(n1 + n2) # hide label type in printing function Base.show(io::IO, u::U1) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl index 2c98e75186..b5257fddef 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl @@ -16,10 +16,10 @@ modulus(::Type{Z{N}}) where {N} = N modulus(c::Z) = modulus(typeof(c)) -trivial(category_type::Type{<:Z}) = category_type(0) +trivial(sector_type::Type{<:Z}) = sector_type(0) -function abelian_label_fusion_rule(category_type::Type{<:Z}, n1, n2) - return category_type((n1 + n2) % modulus(category_type)) +function abelian_label_fusion_rule(sector_type::Type{<:Z}, n1, n2) + return sector_type((n1 + n2) % modulus(sector_type)) end GradedAxes.dual(c::Z) = typeof(c)(mod(-sector_label(c), modulus(c))) From 21df582c1587143a2da91167cccaf3bf4677cd84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 9 Oct 2024 21:57:31 -0400 Subject: [PATCH 164/194] inline factorize_gradedaxis --- NDTensors/src/lib/SymmetrySectors/src/sector_product.jl | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 2d75e8c508..61e081fe5f 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -131,15 +131,10 @@ function fix_fused_product_type(::AbelianStyle, T::Type, fused) end function fix_fused_product_type(::NotAbelianStyle, T::Type, fused) - g = factorize_gradedaxis(fused) - return recover_gradedaxis_product_type(T, g) -end - -function factorize_gradedaxis(fused) - # convert eg. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} + # convert e.g. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} g = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × - return g + return recover_gradedaxis_product_type(T, g) end function recover_gradedaxis_product_type(T::Type, g0::AbstractGradedUnitRange) From ed52101b1c7ca29f0b536ecbb778bd67cd6b6f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 10 Oct 2024 10:29:13 -0400 Subject: [PATCH 165/194] consistent type naming --- .../src/lib/SymmetrySectors/src/sector_product.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 61e081fe5f..66faff3eb8 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -224,7 +224,7 @@ product_sectors_product(::NamedTuple{()}, l1::Tuple) = l1 product_sectors_product(l2::Tuple, ::NamedTuple{()}) = l2 product_sectors_product(l1::Tuple, l2::Tuple) = (l1..., l2...) -product_sectors_trivial(type::Type{<:Tuple}) = trivial.(fieldtypes(type)) +product_sectors_trivial(T::Type{<:Tuple}) = trivial.(fieldtypes(T)) function product_sectors_common(t1::Tuple, t2::Tuple) n = min(length(t1), length(t2)) @@ -279,8 +279,8 @@ function product_sectors_product(l1::NamedTuple, l2::NamedTuple) return union_keys(l1, l2) end -function product_sectors_trivial(type::Type{<:NamedTuple{Keys}}) where {Keys} - return NamedTuple{Keys}(trivial.(fieldtypes(type))) +function product_sectors_trivial(NT::Type{<:NamedTuple{Keys}}) where {Keys} + return NamedTuple{Keys}(trivial.(fieldtypes(NT))) end function product_sectors_common(nt1::NamedTuple, nt2::NamedTuple) @@ -292,7 +292,7 @@ end product_sectors_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) -function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:NamedTuple} +function shared_product_sectors_fusion_rule(shared1::NT, shared2::NT) where {NT<:NamedTuple} fused = map(fusion_rule, values(shared1), values(shared2)) - return fix_fused_product_type(T, fused) + return fix_fused_product_type(NT, fused) end From ede71b59bd74cccaa0f48caa81e50e597a59e346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 10 Oct 2024 20:08:52 -0400 Subject: [PATCH 166/194] label_fusion_rule to return pair --- NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl | 6 +++--- .../src/lib/SymmetrySectors/src/sector_definitions/fib.jl | 6 ++++-- .../lib/SymmetrySectors/src/sector_definitions/ising.jl | 6 ++++-- .../src/lib/SymmetrySectors/src/sector_definitions/o2.jl | 2 +- .../src/lib/SymmetrySectors/src/sector_definitions/su.jl | 7 ++++--- .../src/lib/SymmetrySectors/src/sector_definitions/su2k.jl | 2 +- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl b/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl index ba6d04e91f..2257e9fb36 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/abstractsector.jl @@ -54,8 +54,8 @@ function fusion_rule(c1::AbstractSector, c2::AbstractSector) end function fusion_rule(::NotAbelianStyle, c1::C, c2::C) where {C<:AbstractSector} - degen, sectors = label_fusion_rule(C, sector_label(c1), sector_label(c2)) - return gradedrange(labelled.(degen, sectors)) + sector_degen_pairs = label_fusion_rule(C, sector_label(c1), sector_label(c2)) + return gradedrange(sector_degen_pairs) end # abelian case: return Sector @@ -64,7 +64,7 @@ function fusion_rule(::AbelianStyle, c1::C, c2::C) where {C<:AbstractSector} end function label_fusion_rule(sector_type::Type{<:AbstractSector}, l1, l2) - return [1], [abelian_label_fusion_rule(sector_type, l1, l2)] + return [abelian_label_fusion_rule(sector_type, l1, l2) => 1] end # ================================ GradedAxes interface ================================== diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/fib.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/fib.jl index a561c9ed5d..d7fded55f2 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/fib.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/fib.jl @@ -31,9 +31,11 @@ quantum_dimension(::NotAbelianStyle, f::Fib) = istrivial(f) ? 1.0 : ((1 + √5) # Fusion rules identical to su2₃ function label_fusion_rule(::Type{Fib}, l1, l2) - degen, suk_sectors = label_fusion_rule(su2{3}, l1, l2) + suk_sectors_degen = label_fusion_rule(su2{3}, l1, l2) + suk_sectors = first.(suk_sectors_degen) + degen = last.(suk_sectors_degen) sectors = Fib.(sector_label.(suk_sectors)) - return degen, sectors + return sectors .=> degen end label_to_str(f::Fib) = istrivial(f) ? "1" : "τ" diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/ising.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/ising.jl index d055a2cba1..d4a955069e 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/ising.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/ising.jl @@ -31,9 +31,11 @@ quantum_dimension(::NotAbelianStyle, i::Ising) = (sector_label(i) == 1//2) ? √ # Fusion rules identical to su2₂ function label_fusion_rule(::Type{Ising}, l1, l2) - degen, suk_sectors = label_fusion_rule(su2{2}, l1, l2) + suk_sectors_degen = label_fusion_rule(su2{2}, l1, l2) + suk_sectors = first.(suk_sectors_degen) + degen = last.(suk_sectors_degen) sectors = Ising.(sector_label.(suk_sectors)) - return degen, sectors + return sectors .=> degen end # TODO: Use `Val` dispatch here? diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/o2.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/o2.jl index a5391cf1ad..4a0dcf7646 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/o2.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/o2.jl @@ -71,5 +71,5 @@ function label_fusion_rule(::Type{O2}, l1, l2) end end end - return degens, O2.(labels) + return O2.(labels) .=> degens end diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl index 9500b349d9..a6c7471cc7 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl @@ -94,7 +94,7 @@ GradedAxes.dual(s::SU{2}) = s function label_fusion_rule(::Type{<:SU{2}}, s1, s2) irreps = [SU{2}((i,)) for i in (abs(s1[1] - s2[1])):2:(s1[1] + s2[1])] degen = ones(Int, length(irreps)) - return degen, irreps + return irreps .=> degen end # define angular momentum-like interface using half-integers @@ -123,7 +123,7 @@ function label_fusion_rule(::Type{<:SU{3}}, left, right) end if right[1] == 0 # avoid issues with singlet - return [1], [SU{3}(left)] + return [SU{3}(left) => 1] end left_row1 = left[1] @@ -173,5 +173,6 @@ function label_fusion_rule(::Type{<:SU{3}}, left, right) unique_labels = sort(unique(irreps)) degen = [count(==(irr), irreps) for irr in unique_labels] sectors = SU{3}.(unique_labels) - return degen, sectors + @show sectors, degen + return sectors .=> degen end diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su2k.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su2k.jl index c0b5e48d84..cdf9bfbb1a 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su2k.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su2k.jl @@ -23,5 +23,5 @@ function label_fusion_rule(::Type{su2{k}}, j1, j2) where {k} labels = collect(abs(j1 - j2):min(k - j1 - j2, j1 + j2)) degen = ones(Int, length(labels)) sectors = su2{k}.(labels) - return degen, sectors + return sectors .=> degen end From 7407e83d82a559ad89a3edf669788a8f00a42d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 10 Oct 2024 21:47:01 -0400 Subject: [PATCH 167/194] remove debug print --- NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl index a6c7471cc7..8c0c0402c0 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl @@ -173,6 +173,5 @@ function label_fusion_rule(::Type{<:SU{3}}, left, right) unique_labels = sort(unique(irreps)) degen = [count(==(irr), irreps) for irr in unique_labels] sectors = SU{3}.(unique_labels) - @show sectors, degen return sectors .=> degen end From 7a7f2b8e8bcb96b5e7a4dd7ff3d309639c8aa963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 11 Oct 2024 14:20:21 -0400 Subject: [PATCH 168/194] refactor recover_sector_product_type --- .../lib/SymmetrySectors/src/sector_product.jl | 34 +++++++------------ .../test/test_sector_product.jl | 4 --- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 66faff3eb8..8010c39b48 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -122,41 +122,31 @@ product_sectors_fusion_rule(::NamedTuple{()}, sects::Tuple) = SectorProduct(sect product_sectors_fusion_rule(sects::NamedTuple, ::Tuple{}) = SectorProduct(sects) product_sectors_fusion_rule(::Tuple{}, sects::NamedTuple) = SectorProduct(sects) -function fix_fused_product_type(T::Type, fused) - return fix_fused_product_type(product_sectors_symmetrystyle(T), T, fused) +function fix_fused_product_type(Sectors::Type, fused) + return fix_fused_product_type(product_sectors_symmetrystyle(Sectors), Sectors, fused) end -function fix_fused_product_type(::AbelianStyle, T::Type, fused) - return recover_sector_product_type(T, fused) +function fix_fused_product_type(::AbelianStyle, Sectors::Type, fused) + return recover_sector_product_type(Sectors, fused) end -function fix_fused_product_type(::NotAbelianStyle, T::Type, fused) +function fix_fused_product_type(::NotAbelianStyle, Sectors::Type, fused) # convert e.g. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} g = reduce(×, fused) # convention: keep unsorted blocklabels as produced by F order loops in × - return recover_gradedaxis_product_type(T, g) + return recover_gradedaxis_product_type(Sectors, g) end -function recover_gradedaxis_product_type(T::Type, g0::AbstractGradedUnitRange) - new_labels = recover_sector_product_type.(T, blocklabels(g0)) +function recover_gradedaxis_product_type(Sectors::Type, g0::AbstractGradedUnitRange) + old_labels = blocklabels(g0) + old_sects = product_sectors.(SectorProduct.(old_labels)) + new_labels = recover_sector_product_type.(Sectors, old_sects) new_blocklengths = labelled.(unlabel.(blocklengths(g0)), new_labels) return gradedrange(new_blocklengths) end -function recover_sector_product_type(T::Type, c::AbstractSector) - return recover_sector_product_type(T, SectorProduct(c)) -end - -function recover_sector_product_type(T::Type, c::SectorProduct) - return recover_sector_product_type(T, product_sectors(c)) -end - -function recover_sector_product_type(T::Type{<:SectorProduct}, sects) - return recover_sector_product_type(product_sectors_type(T), sects) -end - -function recover_sector_product_type(T::Type, sects) - return SectorProduct(T(sects)) +function recover_sector_product_type(Sectors::Type, sects) + return SectorProduct(Sectors(sects)) end # ================================= Cartesian Product ==================================== diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 671ead9db0..8cee6c2d3f 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -53,7 +53,6 @@ end @test (@inferred_latest recover_sector_product_type( typeof(product_sectors(s)), product_sectors(s) )) == s - @test (@inferred_latest recover_sector_product_type(typeof(s), product_sectors(s))) == s s = U1(3) × SU2(1//2) × Fib("τ") @test length(product_sectors(s)) == 3 @@ -317,9 +316,6 @@ end @test (@inferred_latest recover_sector_product_type( typeof(product_sectors(s)), Tuple(product_sectors(s)) )) == s - @test (@inferred_latest recover_sector_product_type( - typeof(s), Tuple(product_sectors(s)) - )) == s @test s == (B=SU2(2),) × (A=U1(1),) s = s × (C=Ising("ψ"),) From 77df75d79f92d3e1bf6ff794ae5e3cda698be371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Fri, 11 Oct 2024 14:25:06 -0400 Subject: [PATCH 169/194] remove julia1.6 specific @inferred_latest --- .../test/test_sector_product.jl | 269 +++++++++--------- 1 file changed, 128 insertions(+), 141 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 8cee6c2d3f..fe73afacc2 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -18,56 +18,49 @@ using NDTensors.SymmetrySectors: using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange using Test: @inferred, @test, @testset, @test_throws -macro inferred_latest(ex) - if VERSION < v"1.10" - return esc(:($ex)) - end - return esc(:(@inferred $ex)) -end - @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin s = SectorProduct(U1(1)) @test length(product_sectors(s)) == 1 - @test (@inferred_latest quantum_dimension(s)) == 1 - @test (@inferred_latest dual(s)) == SectorProduct(U1(-1)) + @test (@inferred quantum_dimension(s)) == 1 + @test (@inferred dual(s)) == SectorProduct(U1(-1)) @test product_sectors(s)[1] == U1(1) - @test (@inferred_latest trivial(s)) == SectorProduct(U1(0)) + @test (@inferred trivial(s)) == SectorProduct(U1(0)) s = SectorProduct(U1(1), U1(2)) @test length(product_sectors(s)) == 2 - @test (@inferred_latest quantum_dimension(s)) == 1 - @test (@inferred_latest dual(s)) == SectorProduct(U1(-1), U1(-2)) + @test (@inferred quantum_dimension(s)) == 1 + @test (@inferred dual(s)) == SectorProduct(U1(-1), U1(-2)) @test product_sectors(s)[1] == U1(1) @test product_sectors(s)[2] == U1(2) - @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), U1(0)) + @test (@inferred trivial(s)) == SectorProduct(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) @test length(product_sectors(s)) == 3 - @test (@inferred_latest quantum_dimension(s)) == 2 - @test (@inferred_latest dual(s)) == U1(-1) × SU2(1//2) × U1(-3) + @test (@inferred quantum_dimension(s)) == 2 + @test (@inferred dual(s)) == U1(-1) × SU2(1//2) × U1(-3) @test product_sectors(s)[1] == U1(1) @test product_sectors(s)[2] == SU2(1//2) @test product_sectors(s)[3] == U1(3) - @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), SU2(0), U1(0)) - @test (@inferred_latest recover_sector_product_type( + @test (@inferred trivial(s)) == SectorProduct(U1(0), SU2(0), U1(0)) + @test (@inferred recover_sector_product_type( typeof(product_sectors(s)), product_sectors(s) )) == s s = U1(3) × SU2(1//2) × Fib("τ") @test length(product_sectors(s)) == 3 - @test (@inferred_latest quantum_dimension(s)) == 1.0 + √5 + @test (@inferred quantum_dimension(s)) == 1.0 + √5 @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") @test product_sectors(s)[1] == U1(3) @test product_sectors(s)[2] == SU2(1//2) @test product_sectors(s)[3] == Fib("τ") - @test (@inferred_latest trivial(s)) == SectorProduct(U1(0), SU2(0), Fib("1")) + @test (@inferred trivial(s)) == SectorProduct(U1(0), SU2(0), Fib("1")) s = TrivialSector() × U1(3) × SU2(1 / 2) @test length(product_sectors(s)) == 3 - @test (@inferred_latest quantum_dimension(s)) == 2 + @test (@inferred quantum_dimension(s)) == 2 @test dual(s) == TrivialSector() × U1(-3) × SU2(1//2) - @test (@inferred_latest trivial(s)) == SectorProduct(TrivialSector(), U1(0), SU2(0)) + @test (@inferred trivial(s)) == SectorProduct(TrivialSector(), U1(0), SU2(0)) @test s > trivial(s) end @@ -92,7 +85,7 @@ end @testset "Quantum dimension and GradedUnitRange" begin g = gradedrange([(U1(0) × Z{2}(0)) => 1, (U1(1) × Z{2}(0)) => 2]) # abelian - @test (@inferred_latest quantum_dimension(g)) == 3 + @test (@inferred quantum_dimension(g)) == 3 g = gradedrange([ # non-abelian (SU2(0) × SU2(0)) => 1, @@ -100,29 +93,29 @@ end (SU2(0) × SU2(1)) => 1, (SU2(1) × SU2(1)) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 16 - @test (@inferred_latest block_dimensions(g)) == [1, 3, 3, 9] + @test (@inferred quantum_dimension(g)) == 16 + @test (@inferred block_dimensions(g)) == [1, 3, 3, 9] # mixed group g = gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]) - @test (@inferred_latest quantum_dimension(g)) == 4 - @test (@inferred_latest block_dimensions(g)) == [1, 3] + @test (@inferred quantum_dimension(g)) == 4 + @test (@inferred block_dimensions(g)) == [1, 3] g = gradedrange([(SU2(0) × U1(0) × SU2(1//2)) => 1, (SU2(0) × U1(1) × SU2(1//2)) => 1]) - @test (@inferred_latest quantum_dimension(g)) == 4 - @test (@inferred_latest block_dimensions(g)) == [2, 2] + @test (@inferred quantum_dimension(g)) == 4 + @test (@inferred block_dimensions(g)) == [2, 2] # NonGroupCategory g_fib = gradedrange([(Fib("1") × Fib("1")) => 1]) g_ising = gradedrange([(Ising("1") × Ising("1")) => 1]) - @test (@inferred_latest quantum_dimension((Fib("1") × Fib("1")))) == 1.0 - @test (@inferred_latest quantum_dimension(g_fib)) == 1.0 - @test (@inferred_latest quantum_dimension(g_ising)) == 1.0 - @test (@inferred_latest quantum_dimension((Ising("1") × Ising("1")))) == 1.0 - @test (@inferred_latest block_dimensions(g_fib)) == [1.0] - @test (@inferred_latest block_dimensions(g_ising)) == [1.0] + @test (@inferred quantum_dimension((Fib("1") × Fib("1")))) == 1.0 + @test (@inferred quantum_dimension(g_fib)) == 1.0 + @test (@inferred quantum_dimension(g_ising)) == 1.0 + @test (@inferred quantum_dimension((Ising("1") × Ising("1")))) == 1.0 + @test (@inferred block_dimensions(g_fib)) == [1.0] + @test (@inferred block_dimensions(g_ising)) == [1.0] - @test (@inferred_latest quantum_dimension(U1(1) × Fib("1"))) == 1.0 - @test (@inferred_latest quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 + @test (@inferred quantum_dimension(U1(1) × Fib("1"))) == 1.0 + @test (@inferred quantum_dimension(gradedrange([U1(1) × Fib("1") => 1]))) == 1.0 # mixed product Abelian / NonAbelian / NonGroup g = gradedrange([ @@ -131,8 +124,8 @@ end (U1(2) × SU2(0) × Ising("ψ")) => 1, (U1(2) × SU2(1) × Ising("ψ")) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 8.0 - @test (@inferred_latest block_dimensions(g)) == [1.0, 3.0, 1.0, 3.0] + @test (@inferred quantum_dimension(g)) == 8.0 + @test (@inferred block_dimensions(g)) == [1.0, 3.0, 1.0, 3.0] ϕ = (1 + √5) / 2 g = gradedrange([ @@ -141,36 +134,36 @@ end (Fib("τ") × SU2(0) × U1(2)) => 1, (Fib("τ") × SU2(1) × U1(2)) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 4.0 + 4.0ϕ - @test (@inferred_latest block_dimensions(g)) == [1.0, 3.0, 1.0ϕ, 3.0ϕ] + @test (@inferred quantum_dimension(g)) == 4.0 + 4.0ϕ + @test (@inferred block_dimensions(g)) == [1.0, 3.0, 1.0ϕ, 3.0ϕ] end @testset "Fusion of Abelian products" begin p1 = SectorProduct(U1(1)) p2 = SectorProduct(U1(2)) - @test (@inferred_latest p1 ⊗ TrivialSector()) == p1 - @test (@inferred_latest TrivialSector() ⊗ p2) == p2 - @test (@inferred_latest p1 ⊗ p2) == SectorProduct(U1(3)) + @test (@inferred p1 ⊗ TrivialSector()) == p1 + @test (@inferred TrivialSector() ⊗ p2) == p2 + @test (@inferred p1 ⊗ p2) == SectorProduct(U1(3)) p11 = U1(1) × U1(1) - @test (@inferred_latest p11 ⊗ p11) == U1(2) × U1(2) + @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) p123 = U1(1) × U1(2) × U1(3) - @test (@inferred_latest p123 ⊗ p123) == U1(2) × U1(4) × U1(6) + @test (@inferred p123 ⊗ p123) == U1(2) × U1(4) × U1(6) s1 = SectorProduct(U1(1), Z{2}(1)) s2 = SectorProduct(U1(0), Z{2}(0)) - @test (@inferred_latest s1 ⊗ s2) == U1(1) × Z{2}(1) + @test (@inferred s1 ⊗ s2) == U1(1) × Z{2}(1) end @testset "Fusion of NonAbelian products" begin p0 = SectorProduct(SU2(0)) ph = SectorProduct(SU2(1//2)) @test space_isequal( - (@inferred_latest p0 ⊗ TrivialSector()), gradedrange([SectorProduct(SU2(0)) => 1]) + (@inferred p0 ⊗ TrivialSector()), gradedrange([SectorProduct(SU2(0)) => 1]) ) @test space_isequal( - (@inferred_latest TrivialSector() ⊗ ph), gradedrange([SectorProduct(SU2(1//2)) => 1]) + (@inferred TrivialSector() ⊗ ph), gradedrange([SectorProduct(SU2(1//2)) => 1]) ) phh = SU2(1//2) × SU2(1//2) @@ -184,7 +177,7 @@ end ]), ) @test space_isequal( - (@inferred_latest phh ⊗ phh), + (@inferred phh ⊗ phh), gradedrange([ (SU2(0) × SU2(0)) => 1, (SU2(1) × SU2(0)) => 1, @@ -198,11 +191,11 @@ end ı = Fib("1") τ = Fib("τ") s = ı × ı - @test space_isequal((@inferred_latest s ⊗ s), gradedrange([s => 1])) + @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) s = τ × τ @test space_isequal( - (@inferred_latest s ⊗ s), + (@inferred s ⊗ s), gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]), ) @@ -212,20 +205,19 @@ end g = gradedrange([ (ı × Ising("1")) => 1, (τ × Ising("1")) => 1, (ı × ψ) => 1, (τ × ψ) => 1 ]) - @test space_isequal((@inferred_latest s ⊗ s), g) + @test space_isequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) @test space_isequal( - (@inferred_latest p2h ⊗ p1h), - gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]), + (@inferred p2h ⊗ p1h), gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) ) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) @test space_isequal( - (@inferred_latest p1h1 ⊗ p1h1), + (@inferred p1h1 ⊗ p1h1), gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]), ) end @@ -233,7 +225,7 @@ end @testset "Fusion of fully mixed products" begin s = U1(1) × SU2(1//2) × Ising("σ") @test space_isequal( - (@inferred_latest s ⊗ s), + (@inferred s ⊗ s), gradedrange([ (U1(2) × SU2(0) × Ising("1")) => 1, (U1(2) × SU2(1) × Ising("1")) => 1, @@ -246,7 +238,7 @@ end τ = Fib("τ") s = SU2(1//2) × U1(1) × τ @test space_isequal( - (@inferred_latest s ⊗ s), + (@inferred s ⊗ s), gradedrange([ (SU2(0) × U1(2) × ı) => 1, (SU2(1) × U1(2) × ı) => 1, @@ -257,7 +249,7 @@ end s = U1(1) × ı × τ @test space_isequal( - (@inferred_latest s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) + (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) ) end @@ -265,16 +257,16 @@ end @test SectorProduct(U1(1) × U1(0)) ⊗ SectorProduct(U1(1)) == SectorProduct(U1(2) × U1(0)) @test space_isequal( - (@inferred_latest SectorProduct(SU2(0) × SU2(0)) ⊗ SectorProduct(SU2(1))), + (@inferred SectorProduct(SU2(0) × SU2(0)) ⊗ SectorProduct(SU2(1))), gradedrange([SectorProduct(SU2(1) × SU2(0)) => 1]), ) @test space_isequal( - (@inferred_latest SectorProduct(SU2(1) × U1(1)) ⊗ SectorProduct(SU2(0))), + (@inferred SectorProduct(SU2(1) × U1(1)) ⊗ SectorProduct(SU2(0))), gradedrange([SectorProduct(SU2(1) × U1(1)) => 1]), ) @test space_isequal( - (@inferred_latest SectorProduct(U1(1) × SU2(1)) ⊗ SectorProduct(U1(2))), + (@inferred SectorProduct(U1(1) × SU2(1)) ⊗ SectorProduct(U1(2))), gradedrange([SectorProduct(U1(3) × SU2(1)) => 1]), ) @@ -290,7 +282,7 @@ end g1 = gradedrange([s1 => 2]) g2 = gradedrange([s2 => 1]) @test space_isequal( - (@inferred_latest fusion_product(g1, g2)), + (@inferred fusion_product(g1, g2)), gradedrange([U1(1) × SU2(0) × Ising("σ") => 2, U1(1) × SU2(1) × Ising("σ") => 2]), ) end @@ -302,18 +294,18 @@ end @test length(product_sectors(s)) == 2 @test product_sectors(s)[:A] == U1(1) @test product_sectors(s)[:B] == Z{2}(0) - @test (@inferred_latest quantum_dimension(s)) == 1 - @test (@inferred_latest dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) - @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=Z{2}(0),) + @test (@inferred quantum_dimension(s)) == 1 + @test (@inferred dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) + @test (@inferred trivial(s)) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) @test length(product_sectors(s)) == 2 @test product_sectors(s)[:A] == U1(1) @test product_sectors(s)[:B] == SU2(2) - @test (@inferred_latest quantum_dimension(s)) == 5 - @test (@inferred_latest dual(s)) == (A=U1(-1),) × (B=SU2(2),) - @test (@inferred_latest trivial(s)) == (A=U1(0),) × (B=SU2(0),) - @test (@inferred_latest recover_sector_product_type( + @test (@inferred quantum_dimension(s)) == 5 + @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) + @test (@inferred trivial(s)) == (A=U1(0),) × (B=SU2(0),) + @test (@inferred recover_sector_product_type( typeof(product_sectors(s)), Tuple(product_sectors(s)) )) == s @test s == (B=SU2(2),) × (A=U1(1),) @@ -321,8 +313,8 @@ end s = s × (C=Ising("ψ"),) @test length(product_sectors(s)) == 3 @test product_sectors(s)[:C] == Ising("ψ") - @test (@inferred_latest quantum_dimension(s)) == 5.0 - @test (@inferred_latest dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) + @test (@inferred quantum_dimension(s)) == 5.0 + @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) s1 = (A=U1(1),) × (B=Z{2}(0),) s2 = (A=U1(1),) × (C=Z{2}(0),) @@ -334,15 +326,15 @@ end @test length(product_sectors(s)) == 1 @test product_sectors(s)[:A] == U1(2) @test s == SectorProduct(; A=U1(2)) - @test (@inferred_latest quantum_dimension(s)) == 1 - @test (@inferred_latest dual(s)) == SectorProduct("A" => U1(-2)) - @test (@inferred_latest trivial(s)) == SectorProduct(; A=U1(0)) + @test (@inferred quantum_dimension(s)) == 1 + @test (@inferred dual(s)) == SectorProduct("A" => U1(-2)) + @test (@inferred trivial(s)) == SectorProduct(; A=U1(0)) s = SectorProduct("B" => Ising("ψ"), :C => Z{2}(1)) @test length(product_sectors(s)) == 2 @test product_sectors(s)[:B] == Ising("ψ") @test product_sectors(s)[:C] == Z{2}(1) - @test (@inferred_latest quantum_dimension(s)) == 1.0 + @test (@inferred quantum_dimension(s)) == 1.0 end @testset "Comparisons with unspecified labels" begin @@ -370,7 +362,7 @@ end g = gradedrange([ SectorProduct(; A=U1(0), B=Z{2}(0)) => 1, SectorProduct(; A=U1(1), B=Z{2}(0)) => 2 ]) # abelian - @test (@inferred_latest quantum_dimension(g)) == 3 + @test (@inferred quantum_dimension(g)) == 3 g = gradedrange([ # non-abelian SectorProduct(; A=SU2(0), B=SU2(0)) => 1, @@ -378,25 +370,25 @@ end SectorProduct(; A=SU2(0), B=SU2(1)) => 1, SectorProduct(; A=SU2(1), B=SU2(1)) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 16 + @test (@inferred quantum_dimension(g)) == 16 # mixed group g = gradedrange([ SectorProduct(; A=U1(2), B=SU2(0), C=Z{2}(0)) => 1, SectorProduct(; A=U1(2), B=SU2(1), C=Z{2}(0)) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 4 + @test (@inferred quantum_dimension(g)) == 4 g = gradedrange([ SectorProduct(; A=SU2(0), B=Z{2}(0), C=SU2(1//2)) => 1, SectorProduct(; A=SU2(0), B=Z{2}(1), C=SU2(1//2)) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 4 + @test (@inferred quantum_dimension(g)) == 4 # non group sectors g_fib = gradedrange([SectorProduct(; A=Fib("1"), B=Fib("1")) => 1]) g_ising = gradedrange([SectorProduct(; A=Ising("1"), B=Ising("1")) => 1]) - @test (@inferred_latest quantum_dimension(g_fib)) == 1.0 - @test (@inferred_latest quantum_dimension(g_ising)) == 1.0 + @test (@inferred quantum_dimension(g_fib)) == 1.0 + @test (@inferred quantum_dimension(g_ising)) == 1.0 # mixed product Abelian / NonAbelian / NonGroup g = gradedrange([ @@ -405,7 +397,7 @@ end SectorProduct(; A=U1(2), B=SU2(0), C=Ising("ψ")) => 1, SectorProduct(; A=U1(2), B=SU2(1), C=Ising("ψ")) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 8.0 + @test (@inferred quantum_dimension(g)) == 8.0 g = gradedrange([ SectorProduct(; A=Fib("1"), B=SU2(0), C=U1(2)) => 1, @@ -413,7 +405,7 @@ end SectorProduct(; A=Fib("τ"), B=SU2(0), C=U1(2)) => 1, SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1, ]) - @test (@inferred_latest quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) + @test (@inferred quantum_dimension(g)) == 4.0 + 4.0quantum_dimension(Fib("τ")) end @testset "Fusion of Abelian products" begin @@ -422,19 +414,19 @@ end q01 = SectorProduct(; B=U1(1)) q11 = SectorProduct(; A=U1(1), B=U1(1)) - @test (@inferred_latest q10 ⊗ q10) == SectorProduct(; A=U1(2)) - @test (@inferred_latest q01 ⊗ q00) == q01 - @test (@inferred_latest q00 ⊗ q01) == q01 - @test (@inferred_latest q10 ⊗ q01) == q11 - @test (@inferred_latest q11 ⊗ q11) == SectorProduct(; A=U1(2), B=U1(2)) + @test (@inferred q10 ⊗ q10) == SectorProduct(; A=U1(2)) + @test (@inferred q01 ⊗ q00) == q01 + @test (@inferred q00 ⊗ q01) == q01 + @test (@inferred q10 ⊗ q01) == q11 + @test (@inferred q11 ⊗ q11) == SectorProduct(; A=U1(2), B=U1(2)) s11 = SectorProduct(; A=U1(1), B=Z{2}(1)) s10 = SectorProduct(; A=U1(1)) s01 = SectorProduct(; B=Z{2}(1)) - @test (@inferred_latest s01 ⊗ q00) == s01 - @test (@inferred_latest q00 ⊗ s01) == s01 - @test (@inferred_latest s10 ⊗ s01) == s11 - @test (@inferred_latest s11 ⊗ s11) == SectorProduct(; A=U1(2), B=Z{2}(0)) + @test (@inferred s01 ⊗ q00) == s01 + @test (@inferred q00 ⊗ s01) == s01 + @test (@inferred s10 ⊗ s01) == s11 + @test (@inferred s11 ⊗ s11) == SectorProduct(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin @@ -444,15 +436,15 @@ end phab = SectorProduct(; A=SU2(1//2), B=SU2(1//2)) @test space_isequal( - (@inferred_latest pha ⊗ pha), + (@inferred pha ⊗ pha), gradedrange([SectorProduct(; A=SU2(0)) => 1, SectorProduct(; A=SU2(1)) => 1]), ) - @test space_isequal((@inferred_latest pha ⊗ p0), gradedrange([pha => 1])) - @test space_isequal((@inferred_latest p0 ⊗ phb), gradedrange([phb => 1])) - @test space_isequal((@inferred_latest pha ⊗ phb), gradedrange([phab => 1])) + @test space_isequal((@inferred pha ⊗ p0), gradedrange([pha => 1])) + @test space_isequal((@inferred p0 ⊗ phb), gradedrange([phb => 1])) + @test space_isequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) @test space_isequal( - (@inferred_latest phab ⊗ phab), + (@inferred phab ⊗ phab), gradedrange([ SectorProduct(; A=SU2(0), B=SU2(0)) => 1, SectorProduct(; A=SU2(1), B=SU2(0)) => 1, @@ -466,11 +458,11 @@ end ı = Fib("1") τ = Fib("τ") s = SectorProduct(; A=ı, B=ı) - @test space_isequal((@inferred_latest s ⊗ s), gradedrange([s => 1])) + @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) s = SectorProduct(; A=τ, B=τ) @test space_isequal( - (@inferred_latest s ⊗ s), + (@inferred s ⊗ s), gradedrange([ SectorProduct(; A=ı, B=ı) => 1, SectorProduct(; A=τ, B=ı) => 1, @@ -488,7 +480,7 @@ end SectorProduct(; A=ı, B=ψ) => 1, SectorProduct(; A=τ, B=ψ) => 1, ]) - @test space_isequal((@inferred_latest s ⊗ s), g) + @test space_isequal((@inferred s ⊗ s), g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -502,18 +494,16 @@ end q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test space_isequal((@inferred_latest q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) - @test space_isequal((@inferred_latest q10 ⊗ q1h), gradedrange([q2h => 1])) - @test space_isequal((@inferred_latest q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) - @test space_isequal( - (@inferred_latest q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1]) - ) + @test space_isequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) + @test space_isequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) + @test space_isequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) + @test space_isequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) end @testset "Fusion of fully mixed products" begin s = SectorProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) @test space_isequal( - (@inferred_latest s ⊗ s), + (@inferred s ⊗ s), gradedrange([ SectorProduct(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, SectorProduct(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, @@ -526,7 +516,7 @@ end τ = Fib("τ") s = SectorProduct(; A=SU2(1//2), B=U1(1), C=τ) @test space_isequal( - (@inferred_latest s ⊗ s), + (@inferred s ⊗ s), gradedrange([ SectorProduct(; A=SU2(0), B=U1(2), C=ı) => 1, SectorProduct(; A=SU2(1), B=U1(2), C=ı) => 1, @@ -537,7 +527,7 @@ end s = SectorProduct(; A=τ, B=U1(1), C=ı) @test space_isequal( - (@inferred_latest s ⊗ s), + (@inferred s ⊗ s), gradedrange([ SectorProduct(; B=U1(2), A=ı, C=ı) => 1, SectorProduct(; B=U1(2), A=τ, C=ı) => 1 ]), @@ -550,16 +540,14 @@ end g2 = gradedrange([s2 => 1]) s3 = SectorProduct(; A=U1(1), B=SU2(0), C=Ising("σ")) s4 = SectorProduct(; A=U1(1), B=SU2(1), C=Ising("σ")) - @test space_isequal( - (@inferred_latest fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2]) - ) + @test space_isequal((@inferred fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2])) sA = SectorProduct(; A=U1(1)) sB = SectorProduct(; B=SU2(1//2)) sAB = SectorProduct(; A=U1(1), B=SU2(1//2)) gA = gradedrange([sA => 2]) gB = gradedrange([sB => 1]) - @test space_isequal((@inferred_latest fusion_product(gA, gB)), gradedrange([sAB => 2])) + @test space_isequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) end end @@ -568,39 +556,38 @@ end @test s == TrivialSector() @test s == SectorProduct(()) @test s == SectorProduct((;)) - @test (@inferred_latest dual(s)) == s - @test (@inferred_latest s × s) == s - @test (@inferred_latest s ⊗ s) == s - @test (@inferred_latest quantum_dimension(s)) == 1 - @test (@inferred_latest trivial(s)) == s + @test (@inferred dual(s)) == s + @test (@inferred s × s) == s + @test (@inferred s ⊗ s) == s + @test (@inferred quantum_dimension(s)) == 1 + @test (@inferred trivial(s)) == s g0 = gradedrange([s => 2]) - @test space_isequal((@inferred_latest fusion_product(g0, g0)), gradedrange([s => 4])) - - @test (@inferred_latest s × U1(1)) == SectorProduct(U1(1)) - @test (@inferred_latest s × SectorProduct(U1(1))) == SectorProduct(U1(1)) - @test (@inferred_latest s × SectorProduct(; A=U1(1))) == SectorProduct(; A=U1(1)) - @test (@inferred_latest U1(1) × s) == SectorProduct(U1(1)) - @test (@inferred_latest SectorProduct(U1(1)) × s) == SectorProduct(U1(1)) - @test (@inferred_latest SectorProduct(; A=U1(1)) × s) == SectorProduct(; A=U1(1)) - - @test (@inferred_latest U1(1) ⊗ s) == SectorProduct(U1(1)) - @test (@inferred_latest SU2(0) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) - @test (@inferred_latest Fib("τ") ⊗ s) == gradedrange([SectorProduct(Fib("τ")) => 1]) - @test (@inferred_latest s ⊗ U1(1)) == SectorProduct(U1(1)) - @test (@inferred_latest s ⊗ SU2(0)) == gradedrange([SectorProduct(SU2(0)) => 1]) - @test (@inferred_latest s ⊗ Fib("τ")) == gradedrange([SectorProduct(Fib("τ")) => 1]) - - @test (@inferred_latest SectorProduct(U1(1)) ⊗ s) == SectorProduct(U1(1)) - @test (@inferred_latest SectorProduct(SU2(0)) ⊗ s) == - gradedrange([SectorProduct(SU2(0)) => 1]) - @test (@inferred_latest SectorProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == + @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) + + @test (@inferred s × U1(1)) == SectorProduct(U1(1)) + @test (@inferred s × SectorProduct(U1(1))) == SectorProduct(U1(1)) + @test (@inferred s × SectorProduct(; A=U1(1))) == SectorProduct(; A=U1(1)) + @test (@inferred U1(1) × s) == SectorProduct(U1(1)) + @test (@inferred SectorProduct(U1(1)) × s) == SectorProduct(U1(1)) + @test (@inferred SectorProduct(; A=U1(1)) × s) == SectorProduct(; A=U1(1)) + + @test (@inferred U1(1) ⊗ s) == SectorProduct(U1(1)) + @test (@inferred SU2(0) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred Fib("τ") ⊗ s) == gradedrange([SectorProduct(Fib("τ")) => 1]) + @test (@inferred s ⊗ U1(1)) == SectorProduct(U1(1)) + @test (@inferred s ⊗ SU2(0)) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred s ⊗ Fib("τ")) == gradedrange([SectorProduct(Fib("τ")) => 1]) + + @test (@inferred SectorProduct(U1(1)) ⊗ s) == SectorProduct(U1(1)) + @test (@inferred SectorProduct(SU2(0)) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred SectorProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == gradedrange([SectorProduct(Fib("τ"), SU2(1), U1(2)) => 1]) - @test (@inferred_latest SectorProduct(; A=U1(1)) ⊗ s) == SectorProduct(; A=U1(1)) - @test (@inferred_latest SectorProduct(; A=SU2(0)) ⊗ s) == + @test (@inferred SectorProduct(; A=U1(1)) ⊗ s) == SectorProduct(; A=U1(1)) + @test (@inferred SectorProduct(; A=SU2(0)) ⊗ s) == gradedrange([SectorProduct(; A=SU2(0)) => 1]) - @test (@inferred_latest SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == + @test (@inferred SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == gradedrange([SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) => 1]) # Empty behaves as empty NamedTuple From b322fcac3a81d1a122825aaaf183360887e58661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 14 Oct 2024 11:13:43 -0400 Subject: [PATCH 170/194] fix tests --- .../ext/TensorAlgebraGradedAxesExt/test/test_contract.jl | 2 +- NDTensors/test/lib/runtests.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_contract.jl b/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_contract.jl index 68e1374531..1ada7d3393 100644 --- a/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_contract.jl +++ b/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_contract.jl @@ -3,8 +3,8 @@ using BlockArrays: Block, blocksize using Compat: Returns using NDTensors.BlockSparseArrays: BlockSparseArray using NDTensors.GradedAxes: gradedrange -using NDTensors.Sectors: U1 using NDTensors.SparseArrayInterface: densearray +using NDTensors.SymmetrySectors: U1 using NDTensors.TensorAlgebra: contract using Random: randn! using Test: @test, @testset diff --git a/NDTensors/test/lib/runtests.jl b/NDTensors/test/lib/runtests.jl index b1732e2620..b1b47cf866 100644 --- a/NDTensors/test/lib/runtests.jl +++ b/NDTensors/test/lib/runtests.jl @@ -15,10 +15,10 @@ using Test: @testset "LabelledNumbers", "MetalExtensions", "NamedDimsArrays", - "Sectors", "SmallVectors", "SortedSets", "SparseArrayDOKs", + "SymmetrySectors", "TagSets", "TensorAlgebra", "TypeParameterAccessors", From df35a5fc7b61eb42490d43a4bae0c74ec9e6662e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 21 Oct 2024 09:25:28 -0400 Subject: [PATCH 171/194] sort TrivialSector as trivial for any Sector --- .../SymmetrySectors/src/sector_definitions/trivial.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/trivial.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/trivial.jl index 0cc497960e..27b3fec88e 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/trivial.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/trivial.jl @@ -1,5 +1,6 @@ # # Trivial sector +# acts as a trivial sector for any AbstractSector # using ...GradedAxes: GradedAxes @@ -13,8 +14,6 @@ trivial(::Type{TrivialSector}) = TrivialSector() GradedAxes.dual(::TrivialSector) = TrivialSector() -Base.isless(::TrivialSector, ::TrivialSector) = false # bypass default that calls label - # TrivialSector acts as trivial on any AbstractSector function fusion_rule(::NotAbelianStyle, ::TrivialSector, c::AbstractSector) return to_gradedrange(c) @@ -23,7 +22,7 @@ function fusion_rule(::NotAbelianStyle, c::AbstractSector, ::TrivialSector) return to_gradedrange(c) end -# abelian case: return Category +# abelian case: return Sector fusion_rule(::AbelianStyle, c::AbstractSector, ::TrivialSector) = c fusion_rule(::AbelianStyle, ::TrivialSector, c::AbstractSector) = c fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) = TrivialSector() @@ -32,3 +31,8 @@ fusion_rule(::AbelianStyle, ::TrivialSector, ::TrivialSector) = TrivialSector() Base.:(==)(c::AbstractSector, ::TrivialSector) = istrivial(c) Base.:(==)(::TrivialSector, c::AbstractSector) = istrivial(c) Base.:(==)(::TrivialSector, ::TrivialSector) = true + +# sorts as trivial for any Sector +Base.isless(c::AbstractSector, ::TrivialSector) = c < trivial(c) +Base.isless(::TrivialSector, c::AbstractSector) = trivial(c) < c +Base.isless(::TrivialSector, ::TrivialSector) = false # bypass default that calls label From 00fd5f82da7b30c163badccf9008297bc6a77f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 21 Oct 2024 09:27:34 -0400 Subject: [PATCH 172/194] use broadcast --- NDTensors/src/lib/SymmetrySectors/src/sector_product.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 8010c39b48..6a72caabca 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -246,8 +246,8 @@ end SectorProduct(; kws...) = SectorProduct((; kws...)) function SectorProduct(pairs::Pair...) - keys = ntuple(n -> Symbol(pairs[n][1]), length(pairs)) - vals = ntuple(n -> pairs[n][2], length(pairs)) + keys = Symbol.(first.(pairs)) + vals = last.(pairs) return SectorProduct(NamedTuple{keys}(vals)) end From 435f77572c8c5f829b29ba45ae1a268f1f2e65df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 21 Oct 2024 11:54:26 -0400 Subject: [PATCH 173/194] refactor shared_product_sectors_fusion_rule --- .../lib/SymmetrySectors/src/sector_product.jl | 44 ++++--------- .../test/test_sector_product.jl | 64 ++++++++----------- 2 files changed, 40 insertions(+), 68 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 6a72caabca..022d879c77 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -122,33 +122,6 @@ product_sectors_fusion_rule(::NamedTuple{()}, sects::Tuple) = SectorProduct(sect product_sectors_fusion_rule(sects::NamedTuple, ::Tuple{}) = SectorProduct(sects) product_sectors_fusion_rule(::Tuple{}, sects::NamedTuple) = SectorProduct(sects) -function fix_fused_product_type(Sectors::Type, fused) - return fix_fused_product_type(product_sectors_symmetrystyle(Sectors), Sectors, fused) -end - -function fix_fused_product_type(::AbelianStyle, Sectors::Type, fused) - return recover_sector_product_type(Sectors, fused) -end - -function fix_fused_product_type(::NotAbelianStyle, Sectors::Type, fused) - # convert e.g. Tuple{GradedUnitRange{SU2}, GradedUnitRange{SU2}} into GradedUnitRange{SU2×SU2} - g = reduce(×, fused) - # convention: keep unsorted blocklabels as produced by F order loops in × - return recover_gradedaxis_product_type(Sectors, g) -end - -function recover_gradedaxis_product_type(Sectors::Type, g0::AbstractGradedUnitRange) - old_labels = blocklabels(g0) - old_sects = product_sectors.(SectorProduct.(old_labels)) - new_labels = recover_sector_product_type.(Sectors, old_sects) - new_blocklengths = labelled.(unlabel.(blocklengths(g0)), new_labels) - return gradedrange(new_blocklengths) -end - -function recover_sector_product_type(Sectors::Type, sects) - return SectorProduct(Sectors(sects)) -end - # ================================= Cartesian Product ==================================== ×(c1::AbstractSector, c2::AbstractSector) = ×(SectorProduct(c1), SectorProduct(c2)) function ×(p1::SectorProduct, p2::SectorProduct) @@ -228,8 +201,13 @@ function product_sectors_diff(t1::Tuple, t2::Tuple) end function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} - fused = map(fusion_rule, shared1, shared2) - return fix_fused_product_type(T, fused) + return mapreduce( + to_gradedrange ∘ fusion_rule, + ×, + shared1, + shared2; + init=to_gradedrange(SectorProduct(())), + ) end function product_sectors_insert_unspecified(t1::Tuple, t2::Tuple) @@ -282,7 +260,11 @@ end product_sectors_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) +function map_blocklabels(f, r::AbstractUnitRange) + return gradedrange(labelled.(unlabel.(blocklengths(r)), f.(blocklabels(r)))) +end + function shared_product_sectors_fusion_rule(shared1::NT, shared2::NT) where {NT<:NamedTuple} - fused = map(fusion_rule, values(shared1), values(shared2)) - return fix_fused_product_type(NT, fused) + tuple_fused = shared_product_sectors_fusion_rule(values(shared1), values(shared2)) + return map_blocklabels(SectorProduct ∘ NT ∘ product_sectors ∘ SectorProduct, tuple_fused) end diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index fe73afacc2..27ffe1dc90 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -12,7 +12,6 @@ using NDTensors.SymmetrySectors: Z, block_dimensions, quantum_dimension, - recover_sector_product_type, product_sectors, trivial using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange @@ -43,9 +42,6 @@ using Test: @inferred, @test, @testset, @test_throws @test product_sectors(s)[2] == SU2(1//2) @test product_sectors(s)[3] == U1(3) @test (@inferred trivial(s)) == SectorProduct(U1(0), SU2(0), U1(0)) - @test (@inferred recover_sector_product_type( - typeof(product_sectors(s)), product_sectors(s) - )) == s s = U1(3) × SU2(1//2) × Fib("τ") @test length(product_sectors(s)) == 3 @@ -146,14 +142,14 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred p1 ⊗ p2) == SectorProduct(U1(3)) p11 = U1(1) × U1(1) - @test (@inferred p11 ⊗ p11) == U1(2) × U1(2) + @test p11 ⊗ p11 == U1(2) × U1(2) p123 = U1(1) × U1(2) × U1(3) - @test (@inferred p123 ⊗ p123) == U1(2) × U1(4) × U1(6) + @test p123 ⊗ p123 == U1(2) × U1(4) × U1(6) s1 = SectorProduct(U1(1), Z{2}(1)) s2 = SectorProduct(U1(0), Z{2}(0)) - @test (@inferred s1 ⊗ s2) == U1(1) × Z{2}(1) + @test s1 ⊗ s2 == U1(1) × Z{2}(1) end @testset "Fusion of NonAbelian products" begin @@ -177,7 +173,7 @@ using Test: @inferred, @test, @testset, @test_throws ]), ) @test space_isequal( - (@inferred phh ⊗ phh), + phh ⊗ phh, gradedrange([ (SU2(0) × SU2(0)) => 1, (SU2(1) × SU2(0)) => 1, @@ -191,12 +187,11 @@ using Test: @inferred, @test, @testset, @test_throws ı = Fib("1") τ = Fib("τ") s = ı × ı - @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) + @test space_isequal(s ⊗ s, gradedrange([s => 1])) s = τ × τ @test space_isequal( - (@inferred s ⊗ s), - gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]), + s ⊗ s, gradedrange([(ı × ı) => 1, (τ × ı) => 1, (ı × τ) => 1, (τ × τ) => 1]) ) σ = Ising("σ") @@ -205,19 +200,19 @@ using Test: @inferred, @test, @testset, @test_throws g = gradedrange([ (ı × Ising("1")) => 1, (τ × Ising("1")) => 1, (ı × ψ) => 1, (τ × ψ) => 1 ]) - @test space_isequal((@inferred s ⊗ s), g) + @test space_isequal(s ⊗ s, g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin p2h = U1(2) × SU2(1//2) p1h = U1(1) × SU2(1//2) @test space_isequal( - (@inferred p2h ⊗ p1h), gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) + p2h ⊗ p1h, gradedrange([(U1(3) × SU2(0)) => 1, (U1(3) × SU2(1)) => 1]) ) p1h1 = U1(1) × SU2(1//2) × Z{2}(1) @test space_isequal( - (@inferred p1h1 ⊗ p1h1), + p1h1 ⊗ p1h1, gradedrange([(U1(2) × SU2(0) × Z{2}(0)) => 1, (U1(2) × SU2(1) × Z{2}(0)) => 1]), ) end @@ -225,7 +220,7 @@ using Test: @inferred, @test, @testset, @test_throws @testset "Fusion of fully mixed products" begin s = U1(1) × SU2(1//2) × Ising("σ") @test space_isequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ (U1(2) × SU2(0) × Ising("1")) => 1, (U1(2) × SU2(1) × Ising("1")) => 1, @@ -238,7 +233,7 @@ using Test: @inferred, @test, @testset, @test_throws τ = Fib("τ") s = SU2(1//2) × U1(1) × τ @test space_isequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ (SU2(0) × U1(2) × ı) => 1, (SU2(1) × U1(2) × ı) => 1, @@ -248,9 +243,7 @@ using Test: @inferred, @test, @testset, @test_throws ) s = U1(1) × ı × τ - @test space_isequal( - (@inferred s ⊗ s), gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1]) - ) + @test space_isequal(s ⊗ s, gradedrange([(U1(2) × ı × ı) => 1, (U1(2) × ı × τ) => 1])) end @testset "Fusion of different length Categories" begin @@ -282,7 +275,7 @@ using Test: @inferred, @test, @testset, @test_throws g1 = gradedrange([s1 => 2]) g2 = gradedrange([s2 => 1]) @test space_isequal( - (@inferred fusion_product(g1, g2)), + fusion_product(g1, g2), gradedrange([U1(1) × SU2(0) × Ising("σ") => 2, U1(1) × SU2(1) × Ising("σ") => 2]), ) end @@ -305,9 +298,6 @@ end @test (@inferred quantum_dimension(s)) == 5 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) @test (@inferred trivial(s)) == (A=U1(0),) × (B=SU2(0),) - @test (@inferred recover_sector_product_type( - typeof(product_sectors(s)), Tuple(product_sectors(s)) - )) == s @test s == (B=SU2(2),) × (A=U1(1),) s = s × (C=Ising("ψ"),) @@ -418,7 +408,7 @@ end @test (@inferred q01 ⊗ q00) == q01 @test (@inferred q00 ⊗ q01) == q01 @test (@inferred q10 ⊗ q01) == q11 - @test (@inferred q11 ⊗ q11) == SectorProduct(; A=U1(2), B=U1(2)) + @test q11 ⊗ q11 == SectorProduct(; A=U1(2), B=U1(2)) s11 = SectorProduct(; A=U1(1), B=Z{2}(1)) s10 = SectorProduct(; A=U1(1)) @@ -426,7 +416,7 @@ end @test (@inferred s01 ⊗ q00) == s01 @test (@inferred q00 ⊗ s01) == s01 @test (@inferred s10 ⊗ s01) == s11 - @test (@inferred s11 ⊗ s11) == SectorProduct(; A=U1(2), B=Z{2}(0)) + @test s11 ⊗ s11 == SectorProduct(; A=U1(2), B=Z{2}(0)) end @testset "Fusion of NonAbelian products" begin @@ -444,7 +434,7 @@ end @test space_isequal((@inferred pha ⊗ phb), gradedrange([phab => 1])) @test space_isequal( - (@inferred phab ⊗ phab), + phab ⊗ phab, gradedrange([ SectorProduct(; A=SU2(0), B=SU2(0)) => 1, SectorProduct(; A=SU2(1), B=SU2(0)) => 1, @@ -458,11 +448,11 @@ end ı = Fib("1") τ = Fib("τ") s = SectorProduct(; A=ı, B=ı) - @test space_isequal((@inferred s ⊗ s), gradedrange([s => 1])) + @test space_isequal(s ⊗ s, gradedrange([s => 1])) s = SectorProduct(; A=τ, B=τ) @test space_isequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ SectorProduct(; A=ı, B=ı) => 1, SectorProduct(; A=τ, B=ı) => 1, @@ -480,7 +470,7 @@ end SectorProduct(; A=ı, B=ψ) => 1, SectorProduct(; A=τ, B=ψ) => 1, ]) - @test space_isequal((@inferred s ⊗ s), g) + @test space_isequal(s ⊗ s, g) end @testset "Fusion of mixed Abelian and NonAbelian products" begin @@ -494,16 +484,16 @@ end q21 = (N=U1(2),) × (J=SU2(1),) q22 = (N=U1(2),) × (J=SU2(2),) - @test space_isequal((@inferred q1h ⊗ q1h), gradedrange([q20 => 1, q21 => 1])) - @test space_isequal((@inferred q10 ⊗ q1h), gradedrange([q2h => 1])) + @test space_isequal(q1h ⊗ q1h, gradedrange([q20 => 1, q21 => 1])) + @test space_isequal(q10 ⊗ q1h, gradedrange([q2h => 1])) @test space_isequal((@inferred q0h ⊗ q1h), gradedrange([q10 => 1, q11 => 1])) - @test space_isequal((@inferred q11 ⊗ q11), gradedrange([q20 => 1, q21 => 1, q22 => 1])) + @test space_isequal(q11 ⊗ q11, gradedrange([q20 => 1, q21 => 1, q22 => 1])) end @testset "Fusion of fully mixed products" begin s = SectorProduct(; A=U1(1), B=SU2(1//2), C=Ising("σ")) @test space_isequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ SectorProduct(; A=U1(2), B=SU2(0), C=Ising("1")) => 1, SectorProduct(; A=U1(2), B=SU2(1), C=Ising("1")) => 1, @@ -516,7 +506,7 @@ end τ = Fib("τ") s = SectorProduct(; A=SU2(1//2), B=U1(1), C=τ) @test space_isequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ SectorProduct(; A=SU2(0), B=U1(2), C=ı) => 1, SectorProduct(; A=SU2(1), B=U1(2), C=ı) => 1, @@ -527,7 +517,7 @@ end s = SectorProduct(; A=τ, B=U1(1), C=ı) @test space_isequal( - (@inferred s ⊗ s), + s ⊗ s, gradedrange([ SectorProduct(; B=U1(2), A=ı, C=ı) => 1, SectorProduct(; B=U1(2), A=τ, C=ı) => 1 ]), @@ -540,14 +530,14 @@ end g2 = gradedrange([s2 => 1]) s3 = SectorProduct(; A=U1(1), B=SU2(0), C=Ising("σ")) s4 = SectorProduct(; A=U1(1), B=SU2(1), C=Ising("σ")) - @test space_isequal((@inferred fusion_product(g1, g2)), gradedrange([s3 => 2, s4 => 2])) + @test space_isequal(fusion_product(g1, g2), gradedrange([s3 => 2, s4 => 2])) sA = SectorProduct(; A=U1(1)) sB = SectorProduct(; B=SU2(1//2)) sAB = SectorProduct(; A=U1(1), B=SU2(1//2)) gA = gradedrange([sA => 2]) gB = gradedrange([sB => 1]) - @test space_isequal((@inferred fusion_product(gA, gB)), gradedrange([sAB => 2])) + @test space_isequal(fusion_product(gA, gB), gradedrange([sAB => 2])) end end From beba33289e83a8e9e3a5be24dd62e24262c74f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 22 Oct 2024 10:04:24 -0400 Subject: [PATCH 174/194] test more comparisons --- .../SymmetrySectors/test/test_fusion_rules.jl | 4 +-- .../test/test_sector_product.jl | 2 +- .../test/test_simple_sectors.jl | 29 +++++++++++++++---- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl b/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl index 0b0a1a8e0d..ab209a0b65 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl @@ -16,7 +16,7 @@ using NDTensors.SymmetrySectors: trivial using Test: @inferred, @test, @testset, @test_throws -@testset "Simple object fusion rules" begin +@testset "Simple SymmetrySector fusion rules" begin @testset "Z{2} fusion rules" begin z0 = Z{2}(0) z1 = Z{2}(1) @@ -127,7 +127,7 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred quantum_dimension(σ ⊗ σ)) == 2.0 end end -@testset "Reducible object fusion rules" begin +@testset "Gradedrange fusion rules" begin @testset "Trivial GradedUnitRange" begin g1 = gradedrange([U1(0) => 1]) g2 = gradedrange([SU2(0) => 1]) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 27ffe1dc90..5849e0232d 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -541,7 +541,7 @@ end end end -@testset "Empty category" begin +@testset "Empty SymmetrySector" begin for s in (SectorProduct(()), SectorProduct((;))) @test s == TrivialSector() @test s == SectorProduct(()) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl index 8ea8c7b925..8264e29864 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl @@ -15,7 +15,7 @@ using NDTensors.SymmetrySectors: istrivial, trivial using Test: @inferred, @test, @testset, @test_throws -@testset "Test Category Types" begin +@testset "Test SymmetrySectors Types" begin @testset "TrivialSector" begin q = TrivialSector() @@ -40,12 +40,15 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(q1) == U1(0) @test trivial(U1) == U1(0) @test istrivial(U1(0)) - @test U1(0) == TrivialSector() - @test TrivialSector() == U1(0) @test dual(U1(2)) == U1(-2) @test isless(U1(1), U1(2)) @test !isless(U1(2), U1(1)) + + @test U1(0) == TrivialSector() + @test TrivialSector() == U1(0) + @test U1(-1) < TrivialSector() + @test TrivialSector() < U1(1) end @testset "Z₂" begin @@ -54,7 +57,6 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(Z{2}) == Z{2}(0) @test istrivial(Z{2}(0)) - @test Z{2}(0) == TrivialSector() @test quantum_dimension(z0) == 1 @test quantum_dimension(z1) == 1 @@ -66,6 +68,9 @@ using Test: @inferred, @test, @testset, @test_throws @test dual(Z{2}(1)) == Z{2}(1) @test isless(Z{2}(0), Z{2}(1)) @test !isless(Z{2}(1), Z{2}(0)) + + @test Z{2}(0) == TrivialSector() + @test TrivialSector() < Z{2}(1) end @testset "O(2)" begin @@ -76,7 +81,6 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(O2) == s0e @test istrivial(s0e) - @test s0e == TrivialSector() @test (@inferred quantum_dimension(s0e)) == 1 @test (@inferred quantum_dimension(s0o)) == 1 @@ -87,6 +91,11 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred dual(s0o)) == s0o @test (@inferred dual(s12)) == s12 @test (@inferred dual(s1)) == s1 + + @test s0o < s0e < s12 < s1 + @test s0e == TrivialSector() + @test s0o < TrivialSector() + @test TrivialSector() < s12 end @testset "SU(2)" begin @@ -107,7 +116,6 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(SU{2}) == SU2(0) @test istrivial(SU2(0)) - @test SU2(0) == TrivialSector() @test fundamental(SU{2}) == SU2(1//2) @test adjoint(SU{2}) == SU2(1) @@ -122,6 +130,11 @@ using Test: @inferred, @test, @testset, @test_throws @test dual(j2) == j2 @test dual(j3) == j3 @test dual(j4) == j4 + + @test j1 < j2 < j3 < j4 + @test SU2(0) == TrivialSector() + @test !(j2 < TrivialSector()) + @test TrivialSector() < j2 end @testset "SU(N)" begin @@ -171,6 +184,8 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred quantum_dimension(ı)) == 1.0 @test (@inferred quantum_dimension(τ)) == ((1 + √5) / 2) + + @test ı < τ end @testset "Ising" begin @@ -189,6 +204,8 @@ using Test: @inferred, @test, @testset, @test_throws @test (@inferred quantum_dimension(ı)) == 1.0 @test (@inferred quantum_dimension(σ)) == √2 @test (@inferred quantum_dimension(ψ)) == 1.0 + + @test ı < σ < ψ end end end From 3f05a811e488cb05421586463a730482b3494802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 22 Oct 2024 10:07:37 -0400 Subject: [PATCH 175/194] remove conflicting adjoint --- .../src/lib/SymmetrySectors/src/sector_definitions/su.jl | 2 -- .../src/lib/SymmetrySectors/test/test_simple_sectors.jl | 5 ----- 2 files changed, 7 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl index 8c0c0402c0..b44ef4d6eb 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/su.jl @@ -33,8 +33,6 @@ trivial(::Type{<:SU{N}}) where {N} = SU{N}(ntuple(_ -> 0, Val(N - 1))) fundamental(::Type{<:SU{N}}) where {N} = SU{N}(ntuple(i -> i == 1, Val(N - 1))) -adjoint(::Type{<:SU{N}}) where {N} = SU{N}((ntuple(i -> 1 + (i == 1), Val(N - 1)))) - function quantum_dimension(::NotAbelianStyle, s::SU) N = groupdim(s) l = (sector_label(s)..., 0) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl index 8264e29864..45f52977fa 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl @@ -9,7 +9,6 @@ using NDTensors.SymmetrySectors: TrivialSector, U1, Z, - adjoint, quantum_dimension, fundamental, istrivial, @@ -116,9 +115,7 @@ using Test: @inferred, @test, @testset, @test_throws @test trivial(SU{2}) == SU2(0) @test istrivial(SU2(0)) - @test fundamental(SU{2}) == SU2(1//2) - @test adjoint(SU{2}) == SU2(1) @test quantum_dimension(j1) == 1 @test quantum_dimension(j2) == 2 @@ -151,9 +148,7 @@ using Test: @inferred, @test, @testset, @test_throws @test SU{4}((0, 0, 0)) == TrivialSector() @test fundamental(SU{3}) == f3 - @test adjoint(SU{3}) == ad3 @test fundamental(SU{4}) == f4 - @test adjoint(SU{4}) == ad4 @test dual(f3) == SU{3}((1, 1)) @test dual(f4) == SU{4}((1, 1, 1)) From 8901076bf60f4b1fb9374730194a28b76d76d0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 22 Oct 2024 11:46:59 -0400 Subject: [PATCH 176/194] enforce U1(Int32(1)) == U1(1) --- .../src/lib/SymmetrySectors/src/sector_definitions/u1.jl | 3 +++ .../src/lib/SymmetrySectors/test/test_simple_sectors.jl | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl index d4e8d48869..1463631a22 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl @@ -25,3 +25,6 @@ abelian_label_fusion_rule(sector_type::Type{<:U1}, n1, n2) = sector_type(n1 + n2 function Base.show(io::IO, u::U1) return print(io, "U(1)[", sector_label(u), "]") end + +# enforce U1(Int32(1)) == U1(1) +Base.:(==)(s1::U1, s2::U1) = sector_label(s1) == sector_label(s2) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl index 45f52977fa..4fd7682556 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl @@ -43,6 +43,8 @@ using Test: @inferred, @test, @testset, @test_throws @test dual(U1(2)) == U1(-2) @test isless(U1(1), U1(2)) @test !isless(U1(2), U1(1)) + @test U1(Int8(1)) == U1(1) + @test U1(UInt32(1)) == U1(1) @test U1(0) == TrivialSector() @test TrivialSector() == U1(0) @@ -70,6 +72,9 @@ using Test: @inferred, @test, @testset, @test_throws @test Z{2}(0) == TrivialSector() @test TrivialSector() < Z{2}(1) + @test Z{2}(0) != Z{2}(1) + @test Z{2}(0) != Z{3}(0) + @test Z{2}(0) != U1(0) end @testset "O(2)" begin From 8bd77e57f962325bd056b1fd823806e3a6d77a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 22 Oct 2024 11:50:53 -0400 Subject: [PATCH 177/194] fix isless(U1) --- NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl | 1 + NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl index 1463631a22..bd2051960c 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl @@ -28,3 +28,4 @@ end # enforce U1(Int32(1)) == U1(1) Base.:(==)(s1::U1, s2::U1) = sector_label(s1) == sector_label(s2) +Base.isless(s1::U1, s2::U1) = sector_label(s1) < sector_label(s2) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl index 4fd7682556..8d26c9e8a4 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl @@ -50,6 +50,7 @@ using Test: @inferred, @test, @testset, @test_throws @test TrivialSector() == U1(0) @test U1(-1) < TrivialSector() @test TrivialSector() < U1(1) + @test U1(Int8(1)) < U1(Int32(2)) end @testset "Z₂" begin From 0589a4e6fb785871c63147edd8a6103367b9ea81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 22 Oct 2024 17:05:26 -0400 Subject: [PATCH 178/194] style --- NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl index bd2051960c..a2081d05e0 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl @@ -17,7 +17,7 @@ GradedAxes.dual(u::U1) = U1(-u.n) sector_label(u::U1) = u.n trivial(::Type{U1}) = trivial(U1{Int}) -trivial(::Type{U1{T}}) where {T} = U1(T(0)) +trivial(::Type{U1{T}}) where {T} = U1(zero(T)) abelian_label_fusion_rule(sector_type::Type{<:U1}, n1, n2) = sector_type(n1 + n2) From abe14a4c70fec01e11a96024d6745acb9aa980ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 22 Oct 2024 17:24:38 -0400 Subject: [PATCH 179/194] defined set_sector_label --- .../SymmetrySectors/src/sector_definitions/u1.jl | 6 +++--- .../SymmetrySectors/src/sector_definitions/zn.jl | 14 +++++++------- .../SymmetrySectors/test/test_simple_sectors.jl | 2 ++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl index a2081d05e0..2d79796c34 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/u1.jl @@ -11,11 +11,11 @@ struct U1{T} <: AbstractSector end SymmetryStyle(::Type{<:U1}) = AbelianStyle() - -GradedAxes.dual(u::U1) = U1(-u.n) - sector_label(u::U1) = u.n +set_sector_label(s::U1, sector_label) = typeof(s)(sector_label) +GradedAxes.dual(s::U1) = set_sector_label(s, -sector_label(s)) + trivial(::Type{U1}) = trivial(U1{Int}) trivial(::Type{U1{T}}) where {T} = U1(zero(T)) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl index b5257fddef..8628288dc3 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_definitions/zn.jl @@ -6,20 +6,20 @@ using ...GradedAxes: GradedAxes struct Z{N} <: AbstractSector m::Int - Z{N}(m) where {N} = new{N}(m % N) + Z{N}(m) where {N} = new{N}(mod(m, N)) end -SymmetryStyle(::Type{<:Z}) = AbelianStyle() +modulus(::Type{Z{N}}) where {N} = N +modulus(c::Z) = modulus(typeof(c)) +SymmetryStyle(::Type{<:Z}) = AbelianStyle() sector_label(c::Z) = c.m -modulus(::Type{Z{N}}) where {N} = N -modulus(c::Z) = modulus(typeof(c)) +set_sector_label(s::Z, sector_label) = typeof(s)(sector_label) +GradedAxes.dual(s::Z) = set_sector_label(s, -sector_label(s)) trivial(sector_type::Type{<:Z}) = sector_type(0) function abelian_label_fusion_rule(sector_type::Type{<:Z}, n1, n2) - return sector_type((n1 + n2) % modulus(sector_type)) + return sector_type(n1 + n2) end - -GradedAxes.dual(c::Z) = typeof(c)(mod(-sector_label(c), modulus(c))) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl index 8d26c9e8a4..15cf068828 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl @@ -70,6 +70,8 @@ using Test: @inferred, @test, @testset, @test_throws @test dual(Z{2}(1)) == Z{2}(1) @test isless(Z{2}(0), Z{2}(1)) @test !isless(Z{2}(1), Z{2}(0)) + @test Z{2}(0) == z0 + @test Z{2}(-3) == z1 @test Z{2}(0) == TrivialSector() @test TrivialSector() < Z{2}(1) From 15e11872baa0d9e7ccbe9bb59e2a45830daebca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 22 Oct 2024 17:27:46 -0400 Subject: [PATCH 180/194] add test --- NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl index 15cf068828..a2b243c725 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_simple_sectors.jl @@ -75,6 +75,7 @@ using Test: @inferred, @test, @testset, @test_throws @test Z{2}(0) == TrivialSector() @test TrivialSector() < Z{2}(1) + @test_throws MethodError U1(0) < Z{2}(1) @test Z{2}(0) != Z{2}(1) @test Z{2}(0) != Z{3}(0) @test Z{2}(0) != U1(0) From 5260dbd448bff0b98617f829b5f9be9276dc8b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 10:25:56 -0400 Subject: [PATCH 181/194] inline empty edge cases --- .../lib/SymmetrySectors/src/sector_product.jl | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 022d879c77..23e68c9c8b 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -70,22 +70,15 @@ function product_sectors_isequal(s1, s2) end # get clean results when mixing implementations -function product_sectors_isequal(nt::NamedTuple, ::Tuple{}) - return product_sectors_isequal(nt, (;)) -end -function product_sectors_isequal(::Tuple{}, nt::NamedTuple) - return product_sectors_isequal((;), nt) -end -function product_sectors_isequal(::NamedTuple{()}, t::Tuple) - return product_sectors_isequal((), t) -end -function product_sectors_isequal(t::Tuple, ::NamedTuple{()}) - return product_sectors_isequal(t, ()) +product_sectors_isequal(nt::NamedTuple, t::Tuple) = product_sectors_isequal(t, nt) +function product_sectors_isequal(t::Tuple, nt::NamedTuple) + if isempty(t) + return product_sectors_isequal((;), nt) + elseif isempty(nt) + return product_sectors_isequal(t, ()) + end + return false end -product_sectors_isequal(::Tuple{}, ::NamedTuple{()}) = true -product_sectors_isequal(::NamedTuple{()}, ::Tuple{}) = true -product_sectors_isequal(::Tuple, ::NamedTuple) = false -product_sectors_isequal(::NamedTuple, ::Tuple) = false function product_sectors_isless(nt::NamedTuple, ::Tuple{}) return product_sectors_isless(nt, (;)) @@ -109,6 +102,8 @@ product_sectors_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not impleme product_sectors_type(::Type{<:SectorProduct{T}}) where {T} = T function product_sectors_fusion_rule(sects1, sects2) + isempty(sects1) && return SectorProduct(sects2) + isempty(sects2) && return SectorProduct(sects1) shared_sect = shared_product_sectors_fusion_rule( product_sectors_common(sects1, sects2)... ) @@ -116,12 +111,6 @@ function product_sectors_fusion_rule(sects1, sects2) return shared_sect × diff_sect end -# edge case with empty product_sectors -product_sectors_fusion_rule(sects::Tuple, ::NamedTuple{()}) = SectorProduct(sects) -product_sectors_fusion_rule(::NamedTuple{()}, sects::Tuple) = SectorProduct(sects) -product_sectors_fusion_rule(sects::NamedTuple, ::Tuple{}) = SectorProduct(sects) -product_sectors_fusion_rule(::Tuple{}, sects::NamedTuple) = SectorProduct(sects) - # ================================= Cartesian Product ==================================== ×(c1::AbstractSector, c2::AbstractSector) = ×(SectorProduct(c1), SectorProduct(c2)) function ×(p1::SectorProduct, p2::SectorProduct) From 5db50159cf8765bb0211e60fb7f478f3f1ef6ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 10:39:48 -0400 Subject: [PATCH 182/194] rename product_sectors to arguments --- .../lib/SymmetrySectors/src/sector_product.jl | 138 +++++++++--------- .../test/test_sector_product.jl | 60 ++++---- 2 files changed, 97 insertions(+), 101 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 23e68c9c8b..9d89619273 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -7,40 +7,40 @@ using ..GradedAxes: AbstractGradedUnitRange, GradedAxes, dual # ===================================== Definition ======================================= struct SectorProduct{Sectors} <: AbstractSector - product_sectors::Sectors + arguments::Sectors global _SectorProduct(l) = new{typeof(l)}(l) end -SectorProduct(c::SectorProduct) = _SectorProduct(product_sectors(c)) +SectorProduct(c::SectorProduct) = _SectorProduct(arguments(c)) -product_sectors(s::SectorProduct) = s.product_sectors +arguments(s::SectorProduct) = s.arguments # ================================= Sectors interface ==================================== function SymmetryStyle(T::Type{<:SectorProduct}) - return product_sectors_symmetrystyle(product_sectors_type(T)) + return arguments_symmetrystyle(arguments_type(T)) end function quantum_dimension(::NotAbelianStyle, s::SectorProduct) - return mapreduce(quantum_dimension, *, product_sectors(s)) + return mapreduce(quantum_dimension, *, arguments(s)) end # use map instead of broadcast to support both Tuple and NamedTuple -GradedAxes.dual(s::SectorProduct) = SectorProduct(map(dual, product_sectors(s))) +GradedAxes.dual(s::SectorProduct) = SectorProduct(map(dual, arguments(s))) function trivial(type::Type{<:SectorProduct}) - return SectorProduct(product_sectors_trivial(product_sectors_type(type))) + return SectorProduct(arguments_trivial(arguments_type(type))) end # =================================== Base interface ===================================== function Base.:(==)(A::SectorProduct, B::SectorProduct) - return product_sectors_isequal(product_sectors(A), product_sectors(B)) + return arguments_isequal(arguments(A), arguments(B)) end function Base.show(io::IO, s::SectorProduct) - (length(product_sectors(s)) < 2) && print(io, "sector") + (length(arguments(s)) < 2) && print(io, "sector") print(io, "(") symbol = "" - for p in pairs(product_sectors(s)) + for p in pairs(arguments(s)) print(io, symbol) sector_show(io, p[1], p[2]) symbol = " × " @@ -52,7 +52,7 @@ sector_show(io::IO, ::Int, v) = print(io, v) sector_show(io::IO, k::Symbol, v) = print(io, "($k=$v,)") function Base.isless(s1::SectorProduct, s2::SectorProduct) - return product_sectors_isless(product_sectors(s1), product_sectors(s2)) + return arguments_isless(arguments(s1), arguments(s2)) end # ======================================= shared ========================================= @@ -60,61 +60,55 @@ end # - ordered-like with a Tuple # - dictionary-like with a NamedTuple -function sym_product_sectors_insert_unspecified(s1, s2) - return product_sectors_insert_unspecified(s1, s2), - product_sectors_insert_unspecified(s2, s1) +arguments_type(::Type{<:SectorProduct{T}}) where {T} = T + +function sym_arguments_insert_unspecified(s1, s2) + return arguments_insert_unspecified(s1, s2), arguments_insert_unspecified(s2, s1) end -function product_sectors_isequal(s1, s2) - return ==(sym_product_sectors_insert_unspecified(s1, s2)...) +function arguments_isequal(s1, s2) + return ==(sym_arguments_insert_unspecified(s1, s2)...) end # get clean results when mixing implementations -product_sectors_isequal(nt::NamedTuple, t::Tuple) = product_sectors_isequal(t, nt) -function product_sectors_isequal(t::Tuple, nt::NamedTuple) +arguments_isequal(nt::NamedTuple, t::Tuple) = arguments_isequal(t, nt) +function arguments_isequal(t::Tuple, nt::NamedTuple) if isempty(t) - return product_sectors_isequal((;), nt) + return arguments_isequal((;), nt) elseif isempty(nt) - return product_sectors_isequal(t, ()) + return arguments_isequal(t, ()) end return false end -function product_sectors_isless(nt::NamedTuple, ::Tuple{}) - return product_sectors_isless(nt, (;)) +arguments_product(::NamedTuple{()}, t::Tuple) = t +arguments_product(t::Tuple, ::NamedTuple{()}) = t +arguments_product(::Tuple{}, nt::NamedTuple) = nt +arguments_product(nt::NamedTuple, ::Tuple{}) = nt + +function arguments_isless(nt::NamedTuple, ::Tuple{}) + return arguments_isless(nt, (;)) end -function product_sectors_isless(::Tuple{}, nt::NamedTuple) - return product_sectors_isless((;), nt) +function arguments_isless(::Tuple{}, nt::NamedTuple) + return arguments_isless((;), nt) end -function product_sectors_isless(::NamedTuple{()}, t::Tuple) - return product_sectors_isless((), t) +function arguments_isless(::NamedTuple{()}, t::Tuple) + return arguments_isless((), t) end -function product_sectors_isless(t::Tuple, ::NamedTuple{()}) - return product_sectors_isless(t, ()) +function arguments_isless(t::Tuple, ::NamedTuple{()}) + return arguments_isless(t, ()) end -function product_sectors_isless(s1, s2) - return isless(sym_product_sectors_insert_unspecified(s1, s2)...) +function arguments_isless(s1, s2) + return isless(sym_arguments_insert_unspecified(s1, s2)...) end -product_sectors_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) -product_sectors_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) - -product_sectors_type(::Type{<:SectorProduct{T}}) where {T} = T - -function product_sectors_fusion_rule(sects1, sects2) - isempty(sects1) && return SectorProduct(sects2) - isempty(sects2) && return SectorProduct(sects1) - shared_sect = shared_product_sectors_fusion_rule( - product_sectors_common(sects1, sects2)... - ) - diff_sect = SectorProduct(product_sectors_diff(sects1, sects2)) - return shared_sect × diff_sect -end +arguments_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) +arguments_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) # ================================= Cartesian Product ==================================== ×(c1::AbstractSector, c2::AbstractSector) = ×(SectorProduct(c1), SectorProduct(c2)) function ×(p1::SectorProduct, p2::SectorProduct) - return SectorProduct(product_sectors_product(product_sectors(p1), product_sectors(p2))) + return SectorProduct(arguments_product(arguments(p1), arguments(p2))) end ×(a, g::AbstractUnitRange) = ×(to_gradedrange(a), g) @@ -148,9 +142,7 @@ end # generic case: fusion returns a GradedAxes, even for fusion with Empty function fusion_rule(::NotAbelianStyle, s1::SectorProduct, s2::SectorProduct) - return to_gradedrange( - product_sectors_fusion_rule(product_sectors(s1), product_sectors(s2)) - ) + return to_gradedrange(arguments_fusion_rule(arguments(s1), arguments(s2))) end # Abelian case: fusion returns SectorProduct @@ -164,32 +156,38 @@ fusion_rule(::AbelianStyle, ::TrivialSector, c::SectorProduct) = c fusion_rule(::NotAbelianStyle, c::SectorProduct, ::TrivialSector) = to_gradedrange(c) fusion_rule(::NotAbelianStyle, ::TrivialSector, c::SectorProduct) = to_gradedrange(c) +function arguments_fusion_rule(sects1, sects2) + isempty(sects1) && return SectorProduct(sects2) + isempty(sects2) && return SectorProduct(sects1) + shared_sect = shared_arguments_fusion_rule(arguments_common(sects1, sects2)...) + diff_sect = SectorProduct(arguments_diff(sects1, sects2)) + return shared_sect × diff_sect +end + # =============================== Ordered implementation ================================= SectorProduct(t::Tuple) = _SectorProduct(t) SectorProduct(sects::AbstractSector...) = SectorProduct(sects) -function product_sectors_symmetrystyle(T::Type{<:Tuple}) +function arguments_symmetrystyle(T::Type{<:Tuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(T); init=AbelianStyle()) end -product_sectors_product(::NamedTuple{()}, l1::Tuple) = l1 -product_sectors_product(l2::Tuple, ::NamedTuple{()}) = l2 -product_sectors_product(l1::Tuple, l2::Tuple) = (l1..., l2...) +arguments_product(l1::Tuple, l2::Tuple) = (l1..., l2...) -product_sectors_trivial(T::Type{<:Tuple}) = trivial.(fieldtypes(T)) +arguments_trivial(T::Type{<:Tuple}) = trivial.(fieldtypes(T)) -function product_sectors_common(t1::Tuple, t2::Tuple) +function arguments_common(t1::Tuple, t2::Tuple) n = min(length(t1), length(t2)) return t1[begin:n], t2[begin:n] end -function product_sectors_diff(t1::Tuple, t2::Tuple) +function arguments_diff(t1::Tuple, t2::Tuple) n1 = length(t1) n2 = length(t2) return n1 < n2 ? t2[(n1 + 1):end] : t1[(n2 + 1):end] end -function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} +function shared_arguments_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} return mapreduce( to_gradedrange ∘ fusion_rule, ×, @@ -199,15 +197,15 @@ function shared_product_sectors_fusion_rule(shared1::T, shared2::T) where {T<:Tu ) end -function product_sectors_insert_unspecified(t1::Tuple, t2::Tuple) +function arguments_insert_unspecified(t1::Tuple, t2::Tuple) n1 = length(t1) return (t1..., trivial.(t2[(n1 + 1):end])...) end # =========================== Dictionary-like implementation ============================= function SectorProduct(nt::NamedTuple) - product_sectors = sort_keys(nt) - return _SectorProduct(product_sectors) + arguments = sort_keys(nt) + return _SectorProduct(arguments) end SectorProduct(; kws...) = SectorProduct((; kws...)) @@ -218,42 +216,40 @@ function SectorProduct(pairs::Pair...) return SectorProduct(NamedTuple{keys}(vals)) end -function product_sectors_symmetrystyle(NT::Type{<:NamedTuple}) +function arguments_symmetrystyle(NT::Type{<:NamedTuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) end -function product_sectors_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) - diff1 = product_sectors_trivial(typeof(setdiff_keys(nt2, nt1))) +function arguments_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) + diff1 = arguments_trivial(typeof(setdiff_keys(nt2, nt1))) return sort_keys(union_keys(nt1, diff1)) end -product_sectors_product(l1::NamedTuple, ::Tuple{}) = l1 -product_sectors_product(::Tuple{}, l2::NamedTuple) = l2 -function product_sectors_product(l1::NamedTuple, l2::NamedTuple) +function arguments_product(l1::NamedTuple, l2::NamedTuple) if length(intersect_keys(l1, l2)) > 0 throw(ArgumentError("Cannot define product of shared keys")) end return union_keys(l1, l2) end -function product_sectors_trivial(NT::Type{<:NamedTuple{Keys}}) where {Keys} +function arguments_trivial(NT::Type{<:NamedTuple{Keys}}) where {Keys} return NamedTuple{Keys}(trivial.(fieldtypes(NT))) end -function product_sectors_common(nt1::NamedTuple, nt2::NamedTuple) +function arguments_common(nt1::NamedTuple, nt2::NamedTuple) # SectorProduct(nt::NamedTuple) sorts keys at init @assert issorted(keys(nt1)) @assert issorted(keys(nt2)) return intersect_keys(nt1, nt2), intersect_keys(nt2, nt1) end -product_sectors_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) +arguments_diff(nt1::NamedTuple, nt2::NamedTuple) = symdiff_keys(nt1, nt2) function map_blocklabels(f, r::AbstractUnitRange) return gradedrange(labelled.(unlabel.(blocklengths(r)), f.(blocklabels(r)))) end -function shared_product_sectors_fusion_rule(shared1::NT, shared2::NT) where {NT<:NamedTuple} - tuple_fused = shared_product_sectors_fusion_rule(values(shared1), values(shared2)) - return map_blocklabels(SectorProduct ∘ NT ∘ product_sectors ∘ SectorProduct, tuple_fused) +function shared_arguments_fusion_rule(shared1::NT, shared2::NT) where {NT<:NamedTuple} + tuple_fused = shared_arguments_fusion_rule(values(shared1), values(shared2)) + return map_blocklabels(SectorProduct ∘ NT ∘ arguments ∘ SectorProduct, tuple_fused) end diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 5849e0232d..10f91509d1 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -12,7 +12,7 @@ using NDTensors.SymmetrySectors: Z, block_dimensions, quantum_dimension, - product_sectors, + arguments, trivial using NDTensors.GradedAxes: dual, fusion_product, space_isequal, gradedrange using Test: @inferred, @test, @testset, @test_throws @@ -20,40 +20,40 @@ using Test: @inferred, @test, @testset, @test_throws @testset "Test Ordered Products" begin @testset "Ordered Constructor" begin s = SectorProduct(U1(1)) - @test length(product_sectors(s)) == 1 + @test length(arguments(s)) == 1 @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == SectorProduct(U1(-1)) - @test product_sectors(s)[1] == U1(1) + @test arguments(s)[1] == U1(1) @test (@inferred trivial(s)) == SectorProduct(U1(0)) s = SectorProduct(U1(1), U1(2)) - @test length(product_sectors(s)) == 2 + @test length(arguments(s)) == 2 @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == SectorProduct(U1(-1), U1(-2)) - @test product_sectors(s)[1] == U1(1) - @test product_sectors(s)[2] == U1(2) + @test arguments(s)[1] == U1(1) + @test arguments(s)[2] == U1(2) @test (@inferred trivial(s)) == SectorProduct(U1(0), U1(0)) s = U1(1) × SU2(1//2) × U1(3) - @test length(product_sectors(s)) == 3 + @test length(arguments(s)) == 3 @test (@inferred quantum_dimension(s)) == 2 @test (@inferred dual(s)) == U1(-1) × SU2(1//2) × U1(-3) - @test product_sectors(s)[1] == U1(1) - @test product_sectors(s)[2] == SU2(1//2) - @test product_sectors(s)[3] == U1(3) + @test arguments(s)[1] == U1(1) + @test arguments(s)[2] == SU2(1//2) + @test arguments(s)[3] == U1(3) @test (@inferred trivial(s)) == SectorProduct(U1(0), SU2(0), U1(0)) s = U1(3) × SU2(1//2) × Fib("τ") - @test length(product_sectors(s)) == 3 + @test length(arguments(s)) == 3 @test (@inferred quantum_dimension(s)) == 1.0 + √5 @test dual(s) == U1(-3) × SU2(1//2) × Fib("τ") - @test product_sectors(s)[1] == U1(3) - @test product_sectors(s)[2] == SU2(1//2) - @test product_sectors(s)[3] == Fib("τ") + @test arguments(s)[1] == U1(3) + @test arguments(s)[2] == SU2(1//2) + @test arguments(s)[3] == Fib("τ") @test (@inferred trivial(s)) == SectorProduct(U1(0), SU2(0), Fib("1")) s = TrivialSector() × U1(3) × SU2(1 / 2) - @test length(product_sectors(s)) == 3 + @test length(arguments(s)) == 3 @test (@inferred quantum_dimension(s)) == 2 @test dual(s) == TrivialSector() × U1(-3) × SU2(1//2) @test (@inferred trivial(s)) == SectorProduct(TrivialSector(), U1(0), SU2(0)) @@ -61,7 +61,7 @@ using Test: @inferred, @test, @testset, @test_throws end @testset "Ordered comparisons" begin - # convention: missing product_sectors are filled with singlets + # convention: missing arguments are filled with singlets @test SectorProduct(U1(1), SU2(1)) == SectorProduct(U1(1), SU2(1)) @test SectorProduct(U1(1), SU2(0)) != SectorProduct(U1(1), SU2(1)) @test SectorProduct(U1(0), SU2(1)) != SectorProduct(U1(1), SU2(1)) @@ -284,25 +284,25 @@ end @testset "Test Named Sector Products" begin @testset "Construct from × of NamedTuples" begin s = (A=U1(1),) × (B=Z{2}(0),) - @test length(product_sectors(s)) == 2 - @test product_sectors(s)[:A] == U1(1) - @test product_sectors(s)[:B] == Z{2}(0) + @test length(arguments(s)) == 2 + @test arguments(s)[:A] == U1(1) + @test arguments(s)[:B] == Z{2}(0) @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == (A=U1(-1),) × (B=Z{2}(0),) @test (@inferred trivial(s)) == (A=U1(0),) × (B=Z{2}(0),) s = (A=U1(1),) × (B=SU2(2),) - @test length(product_sectors(s)) == 2 - @test product_sectors(s)[:A] == U1(1) - @test product_sectors(s)[:B] == SU2(2) + @test length(arguments(s)) == 2 + @test arguments(s)[:A] == U1(1) + @test arguments(s)[:B] == SU2(2) @test (@inferred quantum_dimension(s)) == 5 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) @test (@inferred trivial(s)) == (A=U1(0),) × (B=SU2(0),) @test s == (B=SU2(2),) × (A=U1(1),) s = s × (C=Ising("ψ"),) - @test length(product_sectors(s)) == 3 - @test product_sectors(s)[:C] == Ising("ψ") + @test length(arguments(s)) == 3 + @test arguments(s)[:C] == Ising("ψ") @test (@inferred quantum_dimension(s)) == 5.0 @test (@inferred dual(s)) == (A=U1(-1),) × (B=SU2(2),) × (C=Ising("ψ"),) @@ -313,22 +313,22 @@ end @testset "Construct from Pairs" begin s = SectorProduct("A" => U1(2)) - @test length(product_sectors(s)) == 1 - @test product_sectors(s)[:A] == U1(2) + @test length(arguments(s)) == 1 + @test arguments(s)[:A] == U1(2) @test s == SectorProduct(; A=U1(2)) @test (@inferred quantum_dimension(s)) == 1 @test (@inferred dual(s)) == SectorProduct("A" => U1(-2)) @test (@inferred trivial(s)) == SectorProduct(; A=U1(0)) s = SectorProduct("B" => Ising("ψ"), :C => Z{2}(1)) - @test length(product_sectors(s)) == 2 - @test product_sectors(s)[:B] == Ising("ψ") - @test product_sectors(s)[:C] == Z{2}(1) + @test length(arguments(s)) == 2 + @test arguments(s)[:B] == Ising("ψ") + @test arguments(s)[:C] == Z{2}(1) @test (@inferred quantum_dimension(s)) == 1.0 end @testset "Comparisons with unspecified labels" begin - # convention: product_sectors evaluate as equal if unmatched labels are trivial + # convention: arguments evaluate as equal if unmatched labels are trivial # this is different from ordered tuple convention q2 = SectorProduct(; N=U1(2)) q20 = (N=U1(2),) × (J=SU2(0),) From 6f6ae8245e330ab9de526a79a00f9a86a6cf64d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 10:55:06 -0400 Subject: [PATCH 183/194] more compact --- .../lib/SymmetrySectors/src/sector_product.jl | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 9d89619273..66bd11e759 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -86,22 +86,11 @@ arguments_product(t::Tuple, ::NamedTuple{()}) = t arguments_product(::Tuple{}, nt::NamedTuple) = nt arguments_product(nt::NamedTuple, ::Tuple{}) = nt -function arguments_isless(nt::NamedTuple, ::Tuple{}) - return arguments_isless(nt, (;)) -end -function arguments_isless(::Tuple{}, nt::NamedTuple) - return arguments_isless((;), nt) -end -function arguments_isless(::NamedTuple{()}, t::Tuple) - return arguments_isless((), t) -end -function arguments_isless(t::Tuple, ::NamedTuple{()}) - return arguments_isless(t, ()) -end -function arguments_isless(s1, s2) - return isless(sym_arguments_insert_unspecified(s1, s2)...) -end - +arguments_isless(s1, s2) = isless(sym_arguments_insert_unspecified(s1, s2)...) +arguments_isless(nt::NamedTuple, ::Tuple{}) = arguments_isless(nt, (;)) +arguments_isless(::Tuple{}, nt::NamedTuple) = arguments_isless((;), nt) +arguments_isless(::NamedTuple{()}, t::Tuple) = arguments_isless((), t) +arguments_isless(t::Tuple, ::NamedTuple{()}) = arguments_isless(t, ()) arguments_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) arguments_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) From 02442b2dbdbe361e5bbd2c044453cf1abdf11494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 11:05:19 -0400 Subject: [PATCH 184/194] add more tests --- .../lib/SymmetrySectors/test/test_fusion_rules.jl | 2 ++ .../lib/SymmetrySectors/test/test_sector_product.jl | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl b/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl index ab209a0b65..bd00abef86 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_fusion_rules.jl @@ -278,6 +278,8 @@ end @test space_isequal((@inferred fusion_product(SU2(1//2), g3)), g4) # test different simple sectors cannot be fused + @test_throws MethodError Z{2}(0) ⊗ U1(1) + @test_throws MethodError SU2(1) ⊗ U1(1) @test_throws MethodError fusion_product(g1, SU2(1)) @test_throws MethodError fusion_product(U1(1), g3) end diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 10f91509d1..effe165bd4 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -541,6 +541,17 @@ end end end +@testset "Mixing implementations" begin + s1 = SectorProduct(U1(1)) + sA = SectorProduct(; A=U1(1)) + + @test sA != s1 + @test_throws ArgumentError sA < s1 + @test_throws ArgumentError s1 < sA + @test_throws MethodError s1 ⊗ sA + @test_throws MethodError sA ⊗ s1 +end + @testset "Empty SymmetrySector" begin for s in (SectorProduct(()), SectorProduct((;))) @test s == TrivialSector() @@ -589,6 +600,7 @@ end @test !(s < s) @test s < SectorProduct(U1(1)) + @test SectorProduct(U1(-1)) < s @test s < SectorProduct(; A=U1(1)) @test s > SectorProduct(; A=U1(-1)) @test !(s < SectorProduct(; A=U1(0))) From 798bd101c4fd24afca266aae984aa1c5acb7c9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 11:11:11 -0400 Subject: [PATCH 185/194] compact arguments_isequal --- NDTensors/src/lib/SymmetrySectors/src/sector_product.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 66bd11e759..e769012432 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -73,11 +73,8 @@ end # get clean results when mixing implementations arguments_isequal(nt::NamedTuple, t::Tuple) = arguments_isequal(t, nt) function arguments_isequal(t::Tuple, nt::NamedTuple) - if isempty(t) - return arguments_isequal((;), nt) - elseif isempty(nt) - return arguments_isequal(t, ()) - end + isempty(t) && return arguments_isequal((;), nt) + isempty(nt) && return arguments_isequal(t, ()) return false end From 752efe0d07f59937a6656d0f740850b15c1c6a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 11:33:29 -0400 Subject: [PATCH 186/194] more tests --- .../test/test_sector_product.jl | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index effe165bd4..d1c8ffe9ae 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -542,17 +542,22 @@ end end @testset "Mixing implementations" begin - s1 = SectorProduct(U1(1)) - sA = SectorProduct(; A=U1(1)) - - @test sA != s1 - @test_throws ArgumentError sA < s1 - @test_throws ArgumentError s1 < sA - @test_throws MethodError s1 ⊗ sA - @test_throws MethodError sA ⊗ s1 + st1 = SectorProduct(U1(1)) + sA1 = SectorProduct(; A=U1(1)) + + @test sA1 != st1 + @test_throws ArgumentError sA1 < st1 + @test_throws ArgumentError st1 < sA1 + @test_throws MethodError st1 ⊗ sA1 + @test_throws MethodError sA1 ⊗ st1 + @test_throws MethodError st1 × sA1 + @test_throws MethodError sA1 × st1 end @testset "Empty SymmetrySector" begin + st1 = SectorProduct(U1(1)) + sA1 = SectorProduct(; A=U1(1)) + for s in (SectorProduct(()), SectorProduct((;))) @test s == TrivialSector() @test s == SectorProduct(()) @@ -566,26 +571,26 @@ end g0 = gradedrange([s => 2]) @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) - @test (@inferred s × U1(1)) == SectorProduct(U1(1)) - @test (@inferred s × SectorProduct(U1(1))) == SectorProduct(U1(1)) - @test (@inferred s × SectorProduct(; A=U1(1))) == SectorProduct(; A=U1(1)) - @test (@inferred U1(1) × s) == SectorProduct(U1(1)) - @test (@inferred SectorProduct(U1(1)) × s) == SectorProduct(U1(1)) - @test (@inferred SectorProduct(; A=U1(1)) × s) == SectorProduct(; A=U1(1)) + @test (@inferred s × U1(1)) == st1 + @test (@inferred U1(1) × s) == st1 + @test (@inferred s × st1) == st1 + @test (@inferred st1 × s) == st1 + @test (@inferred s × sA1) == sA1 + @test (@inferred sA1 × s) == sA1 - @test (@inferred U1(1) ⊗ s) == SectorProduct(U1(1)) + @test (@inferred U1(1) ⊗ s) == st1 + @test (@inferred s ⊗ U1(1)) == st1 @test (@inferred SU2(0) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) - @test (@inferred Fib("τ") ⊗ s) == gradedrange([SectorProduct(Fib("τ")) => 1]) - @test (@inferred s ⊗ U1(1)) == SectorProduct(U1(1)) @test (@inferred s ⊗ SU2(0)) == gradedrange([SectorProduct(SU2(0)) => 1]) + @test (@inferred Fib("τ") ⊗ s) == gradedrange([SectorProduct(Fib("τ")) => 1]) @test (@inferred s ⊗ Fib("τ")) == gradedrange([SectorProduct(Fib("τ")) => 1]) - @test (@inferred SectorProduct(U1(1)) ⊗ s) == SectorProduct(U1(1)) + @test (@inferred st1 ⊗ s) == st1 @test (@inferred SectorProduct(SU2(0)) ⊗ s) == gradedrange([SectorProduct(SU2(0)) => 1]) @test (@inferred SectorProduct(Fib("τ"), SU2(1), U1(2)) ⊗ s) == gradedrange([SectorProduct(Fib("τ"), SU2(1), U1(2)) => 1]) - @test (@inferred SectorProduct(; A=U1(1)) ⊗ s) == SectorProduct(; A=U1(1)) + @test (@inferred sA1 ⊗ s) == sA1 @test (@inferred SectorProduct(; A=SU2(0)) ⊗ s) == gradedrange([SectorProduct(; A=SU2(0)) => 1]) @test (@inferred SectorProduct(; A=Fib("τ"), B=SU2(1), C=U1(2)) ⊗ s) == @@ -594,14 +599,15 @@ end # Empty behaves as empty NamedTuple @test s != U1(0) @test s == SectorProduct(U1(0)) - @test s != SectorProduct(; A=U1(1)) @test s == SectorProduct(; A=U1(0)) @test SectorProduct(; A=U1(0)) == s + @test s != sA1 + @test s != st1 @test !(s < s) - @test s < SectorProduct(U1(1)) + @test s < st1 @test SectorProduct(U1(-1)) < s - @test s < SectorProduct(; A=U1(1)) + @test s < sA1 @test s > SectorProduct(; A=U1(-1)) @test !(s < SectorProduct(; A=U1(0))) @test !(s > SectorProduct(; A=U1(0))) From 46ffcfb2d9ed7c9e01c82447b6e31038d93b3a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 12:19:30 -0400 Subject: [PATCH 187/194] generic case --- .../lib/SymmetrySectors/src/sector_product.jl | 34 ++++++++++--------- .../test/test_sector_product.jl | 18 ++++++---- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index e769012432..0b2826373b 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -66,30 +66,32 @@ function sym_arguments_insert_unspecified(s1, s2) return arguments_insert_unspecified(s1, s2), arguments_insert_unspecified(s2, s1) end -function arguments_isequal(s1, s2) - return ==(sym_arguments_insert_unspecified(s1, s2)...) -end - -# get clean results when mixing implementations +arguments_isequal(s1, s2) = ==(sym_arguments_insert_unspecified(s1, s2)...) arguments_isequal(nt::NamedTuple, t::Tuple) = arguments_isequal(t, nt) function arguments_isequal(t::Tuple, nt::NamedTuple) - isempty(t) && return arguments_isequal((;), nt) isempty(nt) && return arguments_isequal(t, ()) + isempty(t) && return arguments_isequal((;), nt) return false end -arguments_product(::NamedTuple{()}, t::Tuple) = t -arguments_product(t::Tuple, ::NamedTuple{()}) = t -arguments_product(::Tuple{}, nt::NamedTuple) = nt -arguments_product(nt::NamedTuple, ::Tuple{}) = nt +arguments_product(nt::NamedTuple, t::Tuple) = arguments_product(t, nt) +function arguments_product(t::Tuple, nt::NamedTuple) + isempty(nt) && return t + isempty(t) && return nt + throw(ArgumentError("Mixing Tuple and NamedTuple is illegal")) +end arguments_isless(s1, s2) = isless(sym_arguments_insert_unspecified(s1, s2)...) -arguments_isless(nt::NamedTuple, ::Tuple{}) = arguments_isless(nt, (;)) -arguments_isless(::Tuple{}, nt::NamedTuple) = arguments_isless((;), nt) -arguments_isless(::NamedTuple{()}, t::Tuple) = arguments_isless((), t) -arguments_isless(t::Tuple, ::NamedTuple{()}) = arguments_isless(t, ()) -arguments_isless(::NamedTuple, ::Tuple) = throw(ArgumentError("Not implemented")) -arguments_isless(::Tuple, ::NamedTuple) = throw(ArgumentError("Not implemented")) +function arguments_isless(t::Tuple, nt::NamedTuple) + isempty(nt) && return arguments_isless(t, ()) + isempty(t) && return arguments_isless((;), nt) + throw(ArgumentError("Mixing Tuple and NamedTuple is illegal")) +end +function arguments_isless(nt::NamedTuple, t::Tuple) + isempty(nt) && return arguments_isless((), t) + isempty(t) && return arguments_isless(nt, (;)) + throw(ArgumentError("Mixing Tuple and NamedTuple is illegal")) +end # ================================= Cartesian Product ==================================== ×(c1::AbstractSector, c2::AbstractSector) = ×(SectorProduct(c1), SectorProduct(c2)) diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index d1c8ffe9ae..9a43e5d022 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -550,8 +550,8 @@ end @test_throws ArgumentError st1 < sA1 @test_throws MethodError st1 ⊗ sA1 @test_throws MethodError sA1 ⊗ st1 - @test_throws MethodError st1 × sA1 - @test_throws MethodError sA1 × st1 + @test_throws ArgumentError st1 × sA1 + @test_throws ArgumentError sA1 × st1 end @testset "Empty SymmetrySector" begin @@ -562,11 +562,18 @@ end @test s == TrivialSector() @test s == SectorProduct(()) @test s == SectorProduct((;)) + + @test !(s < SectorProduct()) + @test !(s < SectorProduct(;)) + + @test (@inferred s × SectorProduct(())) == s + @test (@inferred s × SectorProduct((;))) == s + @test (@inferred s ⊗ SectorProduct(())) == s + @test (@inferred s ⊗ SectorProduct((;))) == s + @test (@inferred dual(s)) == s - @test (@inferred s × s) == s - @test (@inferred s ⊗ s) == s - @test (@inferred quantum_dimension(s)) == 1 @test (@inferred trivial(s)) == s + @test (@inferred quantum_dimension(s)) == 1 g0 = gradedrange([s => 2]) @test space_isequal((@inferred fusion_product(g0, g0)), gradedrange([s => 4])) @@ -604,7 +611,6 @@ end @test s != sA1 @test s != st1 - @test !(s < s) @test s < st1 @test SectorProduct(U1(-1)) < s @test s < sA1 From 36c076fe6f0856d247e306cc224ebd9fed63c87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 12:24:17 -0400 Subject: [PATCH 188/194] generic arguments_product --- NDTensors/src/lib/SymmetrySectors/src/sector_product.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 0b2826373b..41906bb2dd 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -74,11 +74,10 @@ function arguments_isequal(t::Tuple, nt::NamedTuple) return false end -arguments_product(nt::NamedTuple, t::Tuple) = arguments_product(t, nt) -function arguments_product(t::Tuple, nt::NamedTuple) - isempty(nt) && return t - isempty(t) && return nt - throw(ArgumentError("Mixing Tuple and NamedTuple is illegal")) +function arguments_product(s1, s2) + isempty(s1) && return s2 + isempty(s2) && return s1 + throw(ArgumentError("Mixing implementations is illegal")) end arguments_isless(s1, s2) = isless(sym_arguments_insert_unspecified(s1, s2)...) From 65453b6dc950efe15392956e10d0042e531a2e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 12:29:44 -0400 Subject: [PATCH 189/194] better error messages --- NDTensors/src/lib/SymmetrySectors/src/sector_product.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 41906bb2dd..a98f9596e7 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -77,19 +77,19 @@ end function arguments_product(s1, s2) isempty(s1) && return s2 isempty(s2) && return s1 - throw(ArgumentError("Mixing implementations is illegal")) + throw(ArgumentError("Mixing non-empty storage types is illegal")) end arguments_isless(s1, s2) = isless(sym_arguments_insert_unspecified(s1, s2)...) function arguments_isless(t::Tuple, nt::NamedTuple) isempty(nt) && return arguments_isless(t, ()) isempty(t) && return arguments_isless((;), nt) - throw(ArgumentError("Mixing Tuple and NamedTuple is illegal")) + throw(ArgumentError("Mixing non-empty storage types is illegal")) end function arguments_isless(nt::NamedTuple, t::Tuple) isempty(nt) && return arguments_isless((), t) isempty(t) && return arguments_isless(nt, (;)) - throw(ArgumentError("Mixing Tuple and NamedTuple is illegal")) + throw(ArgumentError("Mixing non-empty storage types is illegal")) end # ================================= Cartesian Product ==================================== From c41c2fe1a69f8fa23595a997994517c8203907d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 12:52:39 -0400 Subject: [PATCH 190/194] style --- NDTensors/src/lib/SymmetrySectors/src/sector_product.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index a98f9596e7..205041a195 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -77,19 +77,19 @@ end function arguments_product(s1, s2) isempty(s1) && return s2 isempty(s2) && return s1 - throw(ArgumentError("Mixing non-empty storage types is illegal")) + return throw(ArgumentError("Mixing non-empty storage types is illegal")) end arguments_isless(s1, s2) = isless(sym_arguments_insert_unspecified(s1, s2)...) function arguments_isless(t::Tuple, nt::NamedTuple) isempty(nt) && return arguments_isless(t, ()) isempty(t) && return arguments_isless((;), nt) - throw(ArgumentError("Mixing non-empty storage types is illegal")) + return throw(ArgumentError("Mixing non-empty storage types is illegal")) end function arguments_isless(nt::NamedTuple, t::Tuple) isempty(nt) && return arguments_isless((), t) isempty(t) && return arguments_isless(nt, (;)) - throw(ArgumentError("Mixing non-empty storage types is illegal")) + return throw(ArgumentError("Mixing non-empty storage types is illegal")) end # ================================= Cartesian Product ==================================== From f130f346c546ec8afa77c626b98dfc4d134bcb44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 15:01:25 -0400 Subject: [PATCH 191/194] generic isless --- .../lib/SymmetrySectors/src/sector_product.jl | 21 ++++++++++--------- .../test/test_sector_product.jl | 4 ++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 205041a195..142a50fab1 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -80,16 +80,10 @@ function arguments_product(s1, s2) return throw(ArgumentError("Mixing non-empty storage types is illegal")) end -arguments_isless(s1, s2) = isless(sym_arguments_insert_unspecified(s1, s2)...) -function arguments_isless(t::Tuple, nt::NamedTuple) - isempty(nt) && return arguments_isless(t, ()) - isempty(t) && return arguments_isless((;), nt) - return throw(ArgumentError("Mixing non-empty storage types is illegal")) -end -function arguments_isless(nt::NamedTuple, t::Tuple) - isempty(nt) && return arguments_isless((), t) - isempty(t) && return arguments_isless(nt, (;)) - return throw(ArgumentError("Mixing non-empty storage types is illegal")) +function arguments_isless(a1, a2) + isempty(a1) && return _arguments_isless(empty(a2), a2) + isempty(a2) && return _arguments_isless(a1, empty(a1)) + return isless(sym_arguments_insert_unspecified(a1, a2)...) end # ================================= Cartesian Product ==================================== @@ -189,6 +183,9 @@ function arguments_insert_unspecified(t1::Tuple, t2::Tuple) return (t1..., trivial.(t2[(n1 + 1):end])...) end +function _arguments_isless(t1::Tuple, t2::Tuple) + return isless(sym_arguments_insert_unspecified(t1, t2)...) +end # =========================== Dictionary-like implementation ============================= function SectorProduct(nt::NamedTuple) arguments = sort_keys(nt) @@ -240,3 +237,7 @@ function shared_arguments_fusion_rule(shared1::NT, shared2::NT) where {NT<:Named tuple_fused = shared_arguments_fusion_rule(values(shared1), values(shared2)) return map_blocklabels(SectorProduct ∘ NT ∘ arguments ∘ SectorProduct, tuple_fused) end + +function _arguments_isless(nt1::NamedTuple, nt2::NamedTuple) + return isless(sym_arguments_insert_unspecified(nt1, nt2)...) +end diff --git a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl index 9a43e5d022..de046fc307 100644 --- a/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/test/test_sector_product.jl @@ -546,8 +546,8 @@ end sA1 = SectorProduct(; A=U1(1)) @test sA1 != st1 - @test_throws ArgumentError sA1 < st1 - @test_throws ArgumentError st1 < sA1 + @test_throws MethodError sA1 < st1 + @test_throws MethodError st1 < sA1 @test_throws MethodError st1 ⊗ sA1 @test_throws MethodError sA1 ⊗ st1 @test_throws ArgumentError st1 × sA1 From 900321aa9736bd8321348eb76c0ee44c5f3e07f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 15:22:26 -0400 Subject: [PATCH 192/194] generic isequal --- .../lib/SymmetrySectors/src/sector_product.jl | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 142a50fab1..7cb507e035 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -62,16 +62,20 @@ end arguments_type(::Type{<:SectorProduct{T}}) where {T} = T -function sym_arguments_insert_unspecified(s1, s2) - return arguments_insert_unspecified(s1, s2), arguments_insert_unspecified(s2, s1) +arguments_maybe_insert_unspecified(s1, ::Any) = s1 +function sym_arguments_maybe_insert_unspecified(s1, s2) + return arguments_maybe_insert_unspecified(s1, s2), + arguments_maybe_insert_unspecified(s2, s1) end -arguments_isequal(s1, s2) = ==(sym_arguments_insert_unspecified(s1, s2)...) -arguments_isequal(nt::NamedTuple, t::Tuple) = arguments_isequal(t, nt) -function arguments_isequal(t::Tuple, nt::NamedTuple) - isempty(nt) && return arguments_isequal(t, ()) - isempty(t) && return arguments_isequal((;), nt) - return false +function arguments_isequal(s1, s2) + isempty(s1) && return _arguments_isequal(empty(s2), s2) + isempty(s2) && return _arguments_isequal(s1, empty(s1)) + return _arguments_isequal(s1, s2) +end + +function _arguments_isequal(s1, s2) + return ==(sym_arguments_maybe_insert_unspecified(s1, s2)...) end function arguments_product(s1, s2) @@ -83,7 +87,7 @@ end function arguments_isless(a1, a2) isempty(a1) && return _arguments_isless(empty(a2), a2) isempty(a2) && return _arguments_isless(a1, empty(a1)) - return isless(sym_arguments_insert_unspecified(a1, a2)...) + return isless(sym_arguments_maybe_insert_unspecified(a1, a2)...) end # ================================= Cartesian Product ==================================== @@ -178,13 +182,13 @@ function shared_arguments_fusion_rule(shared1::T, shared2::T) where {T<:Tuple} ) end -function arguments_insert_unspecified(t1::Tuple, t2::Tuple) +function arguments_maybe_insert_unspecified(t1::Tuple, t2::Tuple) n1 = length(t1) return (t1..., trivial.(t2[(n1 + 1):end])...) end function _arguments_isless(t1::Tuple, t2::Tuple) - return isless(sym_arguments_insert_unspecified(t1, t2)...) + return isless(sym_arguments_maybe_insert_unspecified(t1, t2)...) end # =========================== Dictionary-like implementation ============================= function SectorProduct(nt::NamedTuple) @@ -204,7 +208,7 @@ function arguments_symmetrystyle(NT::Type{<:NamedTuple}) return mapreduce(SymmetryStyle, combine_styles, fieldtypes(NT); init=AbelianStyle()) end -function arguments_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) +function arguments_maybe_insert_unspecified(nt1::NamedTuple, nt2::NamedTuple) diff1 = arguments_trivial(typeof(setdiff_keys(nt2, nt1))) return sort_keys(union_keys(nt1, diff1)) end @@ -239,5 +243,5 @@ function shared_arguments_fusion_rule(shared1::NT, shared2::NT) where {NT<:Named end function _arguments_isless(nt1::NamedTuple, nt2::NamedTuple) - return isless(sym_arguments_insert_unspecified(nt1, nt2)...) + return isless(sym_arguments_maybe_insert_unspecified(nt1, nt2)...) end From 3ba3ba91f21f71978c143b79c64d1ae6b9b42679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 15:44:11 -0400 Subject: [PATCH 193/194] avoid _argument_isless --- .../src/lib/SymmetrySectors/src/sector_product.jl | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 7cb507e035..318b099475 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -84,10 +84,10 @@ function arguments_product(s1, s2) return throw(ArgumentError("Mixing non-empty storage types is illegal")) end -function arguments_isless(a1, a2) - isempty(a1) && return _arguments_isless(empty(a2), a2) - isempty(a2) && return _arguments_isless(a1, empty(a1)) - return isless(sym_arguments_maybe_insert_unspecified(a1, a2)...) +function arguments_isless(a1, b1) + a2 = isempty(a1) ? empty(b1) : a1 + b2 = isempty(b1) ? empty(a2) : b1 + return isless(sym_arguments_maybe_insert_unspecified(a2, b2)...) end # ================================= Cartesian Product ==================================== @@ -187,9 +187,6 @@ function arguments_maybe_insert_unspecified(t1::Tuple, t2::Tuple) return (t1..., trivial.(t2[(n1 + 1):end])...) end -function _arguments_isless(t1::Tuple, t2::Tuple) - return isless(sym_arguments_maybe_insert_unspecified(t1, t2)...) -end # =========================== Dictionary-like implementation ============================= function SectorProduct(nt::NamedTuple) arguments = sort_keys(nt) @@ -241,7 +238,3 @@ function shared_arguments_fusion_rule(shared1::NT, shared2::NT) where {NT<:Named tuple_fused = shared_arguments_fusion_rule(values(shared1), values(shared2)) return map_blocklabels(SectorProduct ∘ NT ∘ arguments ∘ SectorProduct, tuple_fused) end - -function _arguments_isless(nt1::NamedTuple, nt2::NamedTuple) - return isless(sym_arguments_maybe_insert_unspecified(nt1, nt2)...) -end From f8358feb52c68697035c936110b56258afe5d869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Thu, 24 Oct 2024 18:49:37 -0400 Subject: [PATCH 194/194] remove _arguments_isequal --- .../lib/SymmetrySectors/src/sector_product.jl | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl index 318b099475..12a72d022e 100644 --- a/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl +++ b/NDTensors/src/lib/SymmetrySectors/src/sector_product.jl @@ -68,14 +68,14 @@ function sym_arguments_maybe_insert_unspecified(s1, s2) arguments_maybe_insert_unspecified(s2, s1) end -function arguments_isequal(s1, s2) - isempty(s1) && return _arguments_isequal(empty(s2), s2) - isempty(s2) && return _arguments_isequal(s1, empty(s1)) - return _arguments_isequal(s1, s2) +function make_empty_match(a1, b1) + a2 = isempty(a1) ? empty(b1) : a1 + b2 = isempty(b1) ? empty(a2) : b1 + return a2, b2 end -function _arguments_isequal(s1, s2) - return ==(sym_arguments_maybe_insert_unspecified(s1, s2)...) +function arguments_isequal(a1, b1) + return ==(sym_arguments_maybe_insert_unspecified(make_empty_match(a1, b1)...)...) end function arguments_product(s1, s2) @@ -85,9 +85,7 @@ function arguments_product(s1, s2) end function arguments_isless(a1, b1) - a2 = isempty(a1) ? empty(b1) : a1 - b2 = isempty(b1) ? empty(a2) : b1 - return isless(sym_arguments_maybe_insert_unspecified(a2, b2)...) + return isless(sym_arguments_maybe_insert_unspecified(make_empty_match(a1, b1)...)...) end # ================================= Cartesian Product ====================================