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 01/87] 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 02/87] 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 03/87] 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 04/87] 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 05/87] 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 06/87] 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 07/87] 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 08/87] 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 09/87] 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 10/87] =?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 11/87] 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 12/87] 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 13/87] 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 14/87] 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 15/87] 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 16/87] 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 17/87] 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 18/87] 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 19/87] 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 20/87] 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 21/87] 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 22/87] 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 23/87] 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 24/87] 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 25/87] 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 26/87] 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 27/87] 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 28/87] 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 29/87] 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 30/87] 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 31/87] 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 32/87] 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 33/87] 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 34/87] 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 35/87] 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 36/87] 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 37/87] 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 38/87] 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 39/87] 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 40/87] 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 41/87] 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 42/87] 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 43/87] 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 44/87] 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 45/87] 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 46/87] 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 47/87] 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 48/87] 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 49/87] 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 50/87] 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 51/87] 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 52/87] 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 53/87] 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 54/87] 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 55/87] 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 56/87] 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 57/87] 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 58/87] 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 59/87] 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 60/87] 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 61/87] 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 62/87] 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 63/87] 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 64/87] 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 65/87] 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 66/87] 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 67/87] 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 68/87] 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 69/87] 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 70/87] 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 71/87] 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 72/87] 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 73/87] 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 74/87] 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 75/87] 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 76/87] 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 77/87] 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 78/87] 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 79/87] 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 80/87] 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 81/87] 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 82/87] 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 83/87] 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 84/87] 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 85/87] 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 86/87] 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 87/87] 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))