From 888672311f525a0186a5fa86ab9fc641f5004b8c Mon Sep 17 00:00:00 2001 From: mtfishman Date: Fri, 15 Mar 2024 13:21:00 -0400 Subject: [PATCH 1/5] Remove GradedAxesNext --- NDTensors/src/imports.jl | 1 - .../src/BlockSparseArraysGradedAxesExt.jl | 13 +- NDTensors/src/lib/GradedAxes/Project.toml | 2 - .../ext/GradedAxesSectorsExt/Project.toml | 2 - .../src/GradedAxesSectorsExt.jl | 8 - .../GradedAxesSectorsExt/test/Project.toml | 3 - .../ext/GradedAxesSectorsExt/test/runtests.jl | 15 -- .../src/lib/GradedAxes/src/GradedAxes.jl | 5 +- .../GradedAxes/src/abstractgradedunitrange.jl | 150 ----------- .../src/{tensor_product.jl => fusion.jl} | 35 ++- .../src/lib/GradedAxes/src/gradedunitrange.jl | 254 ++++++++++++++++-- .../src/lib/GradedAxes/src/groupsortperm.jl | 13 - .../src/lib/GradedAxes/test/Project.toml | 1 + NDTensors/src/lib/GradedAxes/test/runtests.jl | 106 +++++++- .../src/lib/GradedAxes/test/test_basics.jl | 144 ---------- .../lib/GradedAxesNext/.JuliaFormatter.toml | 2 - .../lib/GradedAxesNext/src/GradedAxesNext.jl | 3 - .../lib/GradedAxesNext/src/gradedunitrange.jl | 245 ----------------- .../src/lib/GradedAxesNext/test/Project.toml | 4 - .../src/lib/GradedAxesNext/test/runtests.jl | 107 -------- .../src/TensorAlgebraGradedAxesExt.jl | 4 +- NDTensors/test/lib/runtests.jl | 1 - 22 files changed, 369 insertions(+), 749 deletions(-) delete mode 100644 NDTensors/src/lib/GradedAxes/Project.toml 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 delete mode 100644 NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl rename NDTensors/src/lib/GradedAxes/src/{tensor_product.jl => fusion.jl} (52%) delete mode 100644 NDTensors/src/lib/GradedAxes/src/groupsortperm.jl delete mode 100644 NDTensors/src/lib/GradedAxes/test/test_basics.jl delete mode 100644 NDTensors/src/lib/GradedAxesNext/.JuliaFormatter.toml delete mode 100644 NDTensors/src/lib/GradedAxesNext/src/GradedAxesNext.jl delete mode 100644 NDTensors/src/lib/GradedAxesNext/src/gradedunitrange.jl delete mode 100644 NDTensors/src/lib/GradedAxesNext/test/Project.toml delete mode 100644 NDTensors/src/lib/GradedAxesNext/test/runtests.jl diff --git a/NDTensors/src/imports.jl b/NDTensors/src/imports.jl index bf8069142b..baec08cc45 100644 --- a/NDTensors/src/imports.jl +++ b/NDTensors/src/imports.jl @@ -37,7 +37,6 @@ for lib in [ :RankFactorization, :Sectors, :LabelledNumbers, - :GradedAxesNext, :GradedAxes, :TensorAlgebra, :SparseArrayInterface, diff --git a/NDTensors/src/lib/BlockSparseArrays/ext/BlockSparseArraysGradedAxesExt/src/BlockSparseArraysGradedAxesExt.jl b/NDTensors/src/lib/BlockSparseArrays/ext/BlockSparseArraysGradedAxesExt/src/BlockSparseArraysGradedAxesExt.jl index 028ad0b85e..b69314bff9 100644 --- a/NDTensors/src/lib/BlockSparseArrays/ext/BlockSparseArraysGradedAxesExt/src/BlockSparseArraysGradedAxesExt.jl +++ b/NDTensors/src/lib/BlockSparseArrays/ext/BlockSparseArraysGradedAxesExt/src/BlockSparseArraysGradedAxesExt.jl @@ -2,25 +2,18 @@ module BlockSparseArraysGradedAxesExt using BlockArrays: AbstractBlockVector, Block, BlockedUnitRange using ..BlockSparseArrays: BlockSparseArrays, block_merge using ...GradedAxes: - AbstractGradedUnitRange, - OneToOne, - blockmergesortperm, - blocksortperm, - invblockperm, - tensor_product + GradedUnitRange, OneToOne, blockmergesortperm, blocksortperm, invblockperm, tensor_product using ...TensorAlgebra: TensorAlgebra, FusionStyle, BlockReshapeFusion, SectorFusion, fusedims, splitdims # TODO: Make a `ReduceWhile` library. include("reducewhile.jl") -TensorAlgebra.FusionStyle(::AbstractGradedUnitRange) = SectorFusion() +TensorAlgebra.FusionStyle(::GradedUnitRange) = SectorFusion() # TODO: Need to implement this! Will require implementing # `block_merge(a::AbstractUnitRange, blockmerger::BlockedUnitRange)`. -function BlockSparseArrays.block_merge( - a::AbstractGradedUnitRange, blockmerger::BlockedUnitRange -) +function BlockSparseArrays.block_merge(a::GradedUnitRange, blockmerger::BlockedUnitRange) return a end diff --git a/NDTensors/src/lib/GradedAxes/Project.toml b/NDTensors/src/lib/GradedAxes/Project.toml deleted file mode 100644 index 448bef8f4f..0000000000 --- a/NDTensors/src/lib/GradedAxes/Project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" 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 448bef8f4f..0000000000 --- a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" 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 cac1288499..0000000000 --- a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl +++ /dev/null @@ -1,8 +0,0 @@ -module GradedAxesSectorsExt -using ..GradedAxes: GradedAxes -using ...Sectors: Sectors, AbstractCategory, ⊗, dual - -GradedAxes.fuse(c1::AbstractCategory, c2::AbstractCategory) = only(c1 ⊗ c2) - -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 20fe1257d0..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 -using NDTensors.Sectors: U1, Z -using Test: @test, @testset - -@testset "GradedAxesSectorsExt" begin - @test fuse(U1(1), U1(2)) == U1(3) - @test dual(U1(2)) == U1(-2) - - @test fuse(Z{2}(1), Z{2}(1)) == Z{2}(0) - @test fuse(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 diff --git a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl index 626d917277..e3f5d81614 100644 --- a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl +++ b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl @@ -1,7 +1,4 @@ module GradedAxes -include("groupsortperm.jl") -include("tensor_product.jl") -include("abstractgradedunitrange.jl") include("gradedunitrange.jl") -include("../ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl") +include("fusion.jl") end diff --git a/NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl deleted file mode 100644 index 8782ddfd61..0000000000 --- a/NDTensors/src/lib/GradedAxes/src/abstractgradedunitrange.jl +++ /dev/null @@ -1,150 +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 Base.getindex(a::AbstractGradedUnitRange, I::AbstractVector{<:Block}) - 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, 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 diff --git a/NDTensors/src/lib/GradedAxes/src/tensor_product.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl similarity index 52% rename from NDTensors/src/lib/GradedAxes/src/tensor_product.jl rename to NDTensors/src/lib/GradedAxes/src/fusion.jl index f03d3e6dcf..e7d2c71979 100644 --- a/NDTensors/src/lib/GradedAxes/src/tensor_product.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -1,4 +1,13 @@ -using BlockArrays: BlockedUnitRange, blocks +using BlockArrays: BlockedUnitRange +# TODO: Implement these. +function blocksortperm end +function blockmergesortperm(::BlockedUnitRange) + return error("Not implemented yet.") +end +function dual end +function fuse end +function invblockperm end +function sector end # Represents the range `1:1` or `Base.OneTo(1)`. struct OneToOne{T} <: AbstractUnitRange{T} end @@ -6,17 +15,24 @@ OneToOne() = OneToOne{Bool}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) +# 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 -tensor_product(a1, a2, a3, as...) = foldl(tensor_product, (a1, a2, a3, as...)) -tensor_product(a1, a2) = error("Not implemented for $(typeof(a1)) and $(typeof(a2)).") +function tensor_product( + a1::AbstractUnitRange, + a2::AbstractUnitRange, + a3::AbstractUnitRange, + a_rest::Vararg{AbstractUnitRange}, +) + return foldl(tensor_product, (a1, a2, a3, a_rest...)) +end -function tensor_product(a1::Base.OneTo, a2::Base.OneTo) - return Base.OneTo(length(a1) * length(a2)) +function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) + return error("Not implemented yet.") end -function tensor_product(a1::BlockedUnitRange, a2::BlockedUnitRange) - return blockedrange(prod.(length, vec(collect(Iterators.product(blocks.((a1, a2))...))))) +function tensor_product(a1::Base.OneTo, a2::Base.OneTo) + return Base.OneTo(length(a1) * length(a2)) end function tensor_product(a1::OneToOne, a2::AbstractUnitRange) @@ -30,3 +46,8 @@ end function tensor_product(a1::OneToOne, a2::OneToOne) return OneToOne() end + +using BlockArrays: blockedrange, blocks +function tensor_product(a1::BlockedUnitRange, a2::BlockedUnitRange) + return blockedrange(prod.(length, vec(collect(Iterators.product(blocks.((a1, a2))...))))) +end diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index 256ddf74b9..04cd88d339 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -1,37 +1,245 @@ using BlockArrays: - BlockArrays, Block, BlockRange, BlockedUnitRange, blockedrange, blocklength + BlockArrays, + Block, + BlockedUnitRange, + BlockRange, + BlockVector, + blockedrange, + BlockIndexRange, + blockfirsts, + blocklasts, + blocklengths, + findblock, + findblockindex, + mortar +using ..LabelledNumbers: LabelledNumbers, LabelledInteger, label, labelled, unlabel -struct GradedUnitRange{T,S} <: AbstractGradedUnitRange{T,S} - blockedrange::BlockedUnitRange{T} - nondual_sectors::Vector{S} - isdual::Bool +# Custom `BlockedUnitRange` constructor that takes a unit range +# and a set of block lengths, similar to `BlockArray(::AbstractArray, blocklengths...)`. +function blockedunitrange(a::AbstractUnitRange, blocklengths) + blocklengths_shifted = copy(blocklengths) + blocklengths_shifted[1] += (first(a) - 1) + blocklasts = cumsum(blocklengths_shifted) + return BlockArrays._BlockedUnitRange(first(a), blocklasts) end -BlockArrays.blockedrange(s::GradedUnitRange) = s.blockedrange -nondual_sectors(s::GradedUnitRange) = s.nondual_sectors -isdual(s::GradedUnitRange) = s.isdual -dual(s::GradedUnitRange) = GradedUnitRange(blockedrange(s), nondual_sectors(s), !isdual(s)) +# Circumvents issue in `findblock` that assumes the `BlockedUnitRange` +# starts at 1. +# TODO: Raise an issue with `BlockArrays`. +function blockedunitrange_findblock(a::BlockedUnitRange, index::Integer) + @boundscheck index in 1:length(a) || throw(BoundsError(a, index)) + return @inbounds findblock(a, index + first(a) - 1) +end + +# Circumvents issue in `findblockindex` that assumes the `BlockedUnitRange` +# starts at 1. +# TODO: Raise an issue with `BlockArrays`. +function blockedunitrange_findblockindex(a::BlockedUnitRange, index::Integer) + @boundscheck index in 1:length(a) || throw(BoundsError()) + return @inbounds findblockindex(a, index + first(a) - 1) +end + +const GradedUnitRange{BlockLasts<:Vector{<:LabelledInteger}} = BlockedUnitRange{BlockLasts} + +function gradedrange(lblocklengths::AbstractVector{<:LabelledInteger}) + brange = blockedrange(unlabel.(lblocklengths)) + lblocklasts = labelled.(blocklasts(brange), label.(lblocklengths)) + # TODO: `first` is forced to be `Int` in `BlockArrays.BlockedUnitRange`, + # so this doesn't do anything right now. Make a PR to generalize it. + firstlength = first(lblocklengths) + lfirst = oneunit(firstlength) + return BlockArrays._BlockedUnitRange(lfirst, lblocklasts) +end + +Base.last(a::GradedUnitRange) = isempty(a.lasts) ? first(a) - 1 : last(a.lasts) + +function gradedrange(lblocklengths::AbstractVector{<:Pair{<:Any,<:Integer}}) + return gradedrange(labelled.(last.(lblocklengths), first.(lblocklengths))) +end + +function labelled_blocks(a::BlockedUnitRange, labels) + return BlockArrays._BlockedUnitRange(a.first, labelled.(a.lasts, labels)) +end + +function BlockArrays.findblock(a::GradedUnitRange, index::Integer) + return blockedunitrange_findblock(unlabel_blocks(a), index) +end + +function blockedunitrange_findblock(a::GradedUnitRange, index::Integer) + return blockedunitrange_findblock(unlabel_blocks(a), index) +end + +function blockedunitrange_findblockindex(a::GradedUnitRange, index::Integer) + return blockedunitrange_findblockindex(unlabel_blocks(a), index) +end + +function BlockArrays.findblockindex(a::GradedUnitRange, index::Integer) + return blockedunitrange_findblockindex(unlabel_blocks(a), index) +end + +## Block label interface + +# Internal function +function get_label(a::BlockedUnitRange, index::Block{1}) + return label(blocklasts(a)[Int(index)]) +end + +# Internal function +function get_label(a::BlockedUnitRange, index::Integer) + return get_label(a, blockedunitrange_findblock(a, index)) +end + +function blocklabels(a::BlockVector) + return map(BlockRange(a)) do block + return label(@view(a[block])) + end +end + +function blocklabels(a::BlockedUnitRange) + # Using `a.lasts` here since that is what is stored + # inside of `BlockedUnitRange`, maybe change that. + # For example, it could be something like: + # + # map(BlockRange(a)) do block + # return label(@view(a[block])) + # end + # + return label.(a.lasts) +end + +# TODO: This relies on internals of `BlockArrays`, maybe redesign +# to try to avoid that. +# TODO: Define `set_grades`, `set_sector_labels`, `set_labels`. +function unlabel_blocks(a::BlockedUnitRange) + return BlockArrays._BlockedUnitRange(a.first, unlabel.(a.lasts)) +end + +## BlockedUnitRage interface + +function Base.axes(ga::GradedUnitRange) + return map(axes(unlabel_blocks(ga))) do a + return labelled_blocks(a, blocklabels(ga)) + end +end -function gradedrange(nondual_sectors::Vector, blocklengths::Vector{Int}, isdual=false) - if length(nondual_sectors) != length(blocklengths) - throw(DomainError("Sector and block lengths do not match")) +function BlockArrays.blockfirsts(a::GradedUnitRange) + return labelled.(blockfirsts(unlabel_blocks(a)), blocklabels(a)) +end + +function BlockArrays.blocklasts(a::GradedUnitRange) + return labelled.(blocklasts(unlabel_blocks(a)), blocklabels(a)) +end + +function BlockArrays.blocklengths(a::GradedUnitRange) + return labelled.(blocklengths(unlabel_blocks(a)), blocklabels(a)) +end + +function Base.first(a::GradedUnitRange) + return labelled(first(unlabel_blocks(a)), label(a[Block(1)])) +end + +function firstblockindices(a::GradedUnitRange) + return labelled.(firstblockindices(unlabel_blocks(a)), blocklabels(a)) +end + +function blockedunitrange_getindex(a::GradedUnitRange, index) + # This uses `blocklasts` since that is what is stored + # in `BlockedUnitRange`, maybe abstract that away. + return labelled(unlabel_blocks(a)[index], get_label(a, index)) +end + +# Like `a[indices]` but preserves block structure. +using BlockArrays: block, blockindex +function blockedunitrange_getindices( + a::BlockedUnitRange, indices::AbstractUnitRange{<:Integer} +) + first_blockindex = blockedunitrange_findblockindex(a, first(indices)) + last_blockindex = blockedunitrange_findblockindex(a, last(indices)) + first_block = block(first_blockindex) + last_block = block(last_blockindex) + blocklengths = if first_block == last_block + [length(indices)] + else + map(first_block:last_block) do block + if block == first_block + return length(a[first_block]) - blockindex(first_blockindex) + 1 + end + if block == last_block + return blockindex(last_blockindex) + end + return length(a[block]) + end end - return GradedUnitRange(blockedrange(blocklengths), nondual_sectors, isdual) + return blockedunitrange(indices .+ (first(a) - 1), blocklengths) +end + +function blockedunitrange_getindices(a::BlockedUnitRange, indices::BlockIndexRange) + return a[block(indices)][only(indices.indices)] +end + +function blockedunitrange_getindices(a::BlockedUnitRange, indices::Vector{<:Integer}) + return map(index -> a[index], indices) +end + +function blockedunitrange_getindices( + a::BlockedUnitRange, indices::Vector{<:Union{Block{1},BlockIndexRange{1}}} +) + return mortar(map(index -> a[index], indices)) end -function gradedrange(sectors_lengths::Vector{<:Pair{<:Any,Int}}, isdual=false) - return gradedrange(first.(sectors_lengths), last.(sectors_lengths), isdual) +function blockedunitrange_getindices(a::BlockedUnitRange, indices) + return error("Not implemented.") end -function gradedrange(nondual_sectors::Vector, a::BlockedUnitRange, isdual=false) - if length(nondual_sectors) != blocklength(a) - throw(DomainError("Number of sectors and number of blocks do not match")) +# The blocks of the corresponding slice. +_blocks(a::AbstractUnitRange, indices) = error("Not implemented") +function _blocks(a::AbstractUnitRange, indices::AbstractUnitRange) + return findblock(a, first(indices)):findblock(a, last(indices)) +end +function _blocks(a::AbstractUnitRange, indices::BlockRange) + return indices +end + +# The block labels of the corresponding slice. +function blocklabels(a::AbstractUnitRange, indices) + return map(_blocks(a, indices)) do block + return label(a[block]) end - return GradedUnitRange(a, nondual_sectors, isdual) end -# BlockArrays block axis interface -# Used in printing -function Base.getindex(a::GradedUnitRange, I::BlockRange{1,Tuple{Base.OneTo{Int}}}) - return GradedUnitRange(blockedrange(a)[I], nondual_sectors(a)[only(I.indices)], isdual(a)) +function blockedunitrange_getindices( + ga::GradedUnitRange, indices::AbstractUnitRange{<:Integer} +) + a_indices = blockedunitrange_getindices(unlabel_blocks(ga), indices) + return labelled_blocks(a_indices, blocklabels(ga, indices)) +end + +function blockedunitrange_getindices(ga::GradedUnitRange, indices::BlockRange) + return labelled_blocks(unlabel_blocks(ga)[indices], blocklabels(ga, indices)) +end + +function Base.getindex(a::GradedUnitRange, index::Integer) + return blockedunitrange_getindex(a, index) +end + +function Base.getindex(a::GradedUnitRange, index::Block{1}) + return blockedunitrange_getindex(a, index) +end + +function Base.getindex(a::GradedUnitRange, indices::BlockIndexRange) + return blockedunitrange_getindices(a, indices) +end + +function Base.getindex( + a::GradedUnitRange, indices::BlockRange{1,<:Tuple{AbstractUnitRange{Int}}} +) + return blockedunitrange_getindices(a, indices) +end + +function Base.getindex(a::GradedUnitRange, indices) + return blockedunitrange_getindices(a, indices) +end + +function Base.getindex(a::GradedUnitRange, indices::AbstractUnitRange{<:Integer}) + return blockedunitrange_getindices(a, indices) end diff --git a/NDTensors/src/lib/GradedAxes/src/groupsortperm.jl b/NDTensors/src/lib/GradedAxes/src/groupsortperm.jl deleted file mode 100644 index 4194b48b0e..0000000000 --- a/NDTensors/src/lib/GradedAxes/src/groupsortperm.jl +++ /dev/null @@ -1,13 +0,0 @@ -using BlockArrays: Block, BlockVector -using SplitApplyCombine: groupcount - -invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) - -# Get the permutation for sorting, then group by common elements. -# groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]] -function groupsortperm(v; kwargs...) - perm = sortperm(v; kwargs...) - v_sorted = @view v[perm] - group_lengths = collect(groupcount(identity, v_sorted)) - return BlockVector(perm, group_lengths) -end diff --git a/NDTensors/src/lib/GradedAxes/test/Project.toml b/NDTensors/src/lib/GradedAxes/test/Project.toml index 2238172387..d1bf575ce0 100644 --- a/NDTensors/src/lib/GradedAxes/test/Project.toml +++ b/NDTensors/src/lib/GradedAxes/test/Project.toml @@ -1,3 +1,4 @@ [deps] BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" NDTensors = "23ae76d9-e61a-49c4-8f12-3f1a16adf9cf" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/NDTensors/src/lib/GradedAxes/test/runtests.jl b/NDTensors/src/lib/GradedAxes/test/runtests.jl index 32f5fcdddc..2cbdcbcb5d 100644 --- a/NDTensors/src/lib/GradedAxes/test/runtests.jl +++ b/NDTensors/src/lib/GradedAxes/test/runtests.jl @@ -1,7 +1,107 @@ @eval module $(gensym()) -using Test: @testset +using BlockArrays: + Block, BlockVector, blockedrange, blockfirsts, blocklasts, blocklength, blocklengths +using NDTensors.GradedAxes: GradedUnitRange, blocklabels, gradedrange +using NDTensors.LabelledNumbers: LabelledUnitRange, label, unlabel +using Test: @test, @test_broken, @testset @testset "GradedAxes" begin - include("test_basics.jl") - include("../ext/GradedAxesSectorsExt/test/runtests.jl") + a = gradedrange(["x" => 2, "y" => 3]) + @test a isa GradedUnitRange + @test length(a) == 5 + @test a[Block(2)] == 3:5 + @test label(a[Block(2)]) == "y" + @test a[Block(2)] isa LabelledUnitRange + @test a[4] == 4 + @test label(a[4]) == "y" + @test unlabel(a[4]) == 4 + @test blocklengths(a) == [2, 3] + @test blocklabels(a) == ["x", "y"] + @test label.(blocklengths(a)) == ["x", "y"] + @test blockfirsts(a) == [1, 3] + @test label.(blockfirsts(a)) == ["x", "y"] + @test first(a) == 1 + @test label(first(a)) == "x" + @test blocklasts(a) == [2, 5] + @test label.(blocklasts(a)) == ["x", "y"] + @test last(a) == 5 + @test label(last(a)) == "y" + @test a[Block(2)] == 3:5 + @test label(a[Block(2)]) == "y" + @test length(a[Block(2)]) == 3 + @test blocklengths(only(axes(a))) == blocklengths(a) + @test blocklabels(only(axes(a))) == blocklabels(a) + + # Slicing operations + x = gradedrange(["x" => 2, "y" => 3]) + a = x[2:4] + @test a isa GradedUnitRange + @test length(a) == 3 + @test blocklength(a) == 2 + @test a[Block(1)] == 2:2 + @test label(a[Block(1)]) == "x" + @test a[Block(2)] == 3:4 + @test label(a[Block(2)]) == "y" + @test isone(first(only(axes(a)))) + @test length(only(axes(a))) == length(a) + @test blocklengths(only(axes(a))) == blocklengths(a) + + x = gradedrange(["x" => 2, "y" => 3]) + a = x[3:4] + @test a isa GradedUnitRange + @test length(a) == 2 + @test blocklength(a) == 1 + @test a[Block(1)] == 3:4 + @test label(a[Block(1)]) == "y" + + x = gradedrange(["x" => 2, "y" => 3]) + a = x[2:4][1:2] + @test a isa GradedUnitRange + @test length(a) == 2 + @test blocklength(a) == 2 + @test a[Block(1)] == 2:2 + @test label(a[Block(1)]) == "x" + @test a[Block(2)] == 3:3 + @test label(a[Block(2)]) == "y" + + x = gradedrange(["x" => 2, "y" => 3]) + a = x[Block(2)[2:3]] + @test a isa LabelledUnitRange + @test length(a) == 2 + @test a == 4:5 + @test label(a) == "y" + + x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) + a = x[Block(2):Block(3)] + @test a isa GradedUnitRange + @test length(a) == 7 + @test blocklength(a) == 2 + @test blocklengths(a) == [3, 4] + @test blocklabels(a) == ["y", "z"] + @test a[Block(1)] == 3:5 + @test a[Block(2)] == 6:9 + + x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) + a = x[[Block(3), Block(2)]] + @test a isa BlockVector + @test length(a) == 7 + @test blocklength(a) == 2 + # TODO: `BlockArrays` doesn't define `blocklengths` + # for `BlockVector`, should it? + @test_broken blocklengths(a) == [4, 3] + @test blocklabels(a) == ["z", "y"] + @test a[Block(1)] == 6:9 + @test a[Block(2)] == 3:5 + + x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) + a = x[[Block(3)[2:3], Block(2)[2:3]]] + @test a isa BlockVector + @test length(a) == 4 + @test blocklength(a) == 2 + # TODO: `BlockArrays` doesn't define `blocklengths` + # for `BlockVector`, should it? + @test_broken blocklengths(a) == [2, 2] + @test blocklabels(a) == ["z", "y"] + @test a[Block(1)] == 7:8 + @test a[Block(2)] == 4:5 end end diff --git a/NDTensors/src/lib/GradedAxes/test/test_basics.jl b/NDTensors/src/lib/GradedAxes/test/test_basics.jl deleted file mode 100644 index 0c612c684d..0000000000 --- a/NDTensors/src/lib/GradedAxes/test/test_basics.jl +++ /dev/null @@ -1,144 +0,0 @@ -@eval module $(gensym()) -using BlockArrays: Block, BlockVector, blockedrange, blocklength, blocklengths, findblock -using NDTensors.GradedAxes: - GradedAxes, - blockmergesortperm, - dual, - fuse, - gradedrange, - isdual, - sector, - sectors, - tensor_product -using Test: @test, @testset, @test_throws - -struct U1 - dim::Int -end -Base.isless(l1::U1, l2::U1) = isless(l1.dim, l2.dim) -GradedAxes.fuse(l1::U1, l2::U1) = U1(l1.dim + l2.dim) -GradedAxes.dual(l::U1) = U1(-l.dim) - -@testset "Basics" begin - a = gradedrange([U1(0), U1(1)], [2, 3]) - @test a isa GradedAxes.GradedUnitRange - @test a == gradedrange([U1(0) => 2, U1(1) => 3]) - @test a == gradedrange([U1(0), U1(1)], blockedrange([2, 3])) - @test length(a) == 5 - @test a == 1:5 - @test a[Block(1)] == 1:2 - @test a[Block(2)] == 3:5 - @test blocklength(a) == 2 # Number of sectors - @test blocklengths(a) == [2, 3] - # TODO: Maybe rename to `labels`, `label`. - @test sectors(a) == [U1(0), U1(1)] - @test sector(a, Block(1)) == U1(0) - @test sector(a, Block(2)) == U1(1) - @test findblock(a, 1) == Block(1) - @test findblock(a, 2) == Block(1) - @test findblock(a, 3) == Block(2) - @test findblock(a, 4) == Block(2) - @test findblock(a, 5) == Block(2) - @test sector(a, 1) == U1(0) - @test sector(a, 2) == U1(0) - @test sector(a, 3) == U1(1) - @test sector(a, 4) == U1(1) - @test sector(a, 5) == U1(1) - - # test error for invalid input - @test_throws DomainError gradedrange([U1(0), U1(1)], [2, 3, 4]) - @test_throws DomainError gradedrange([U1(0), U1(1)], blockedrange([2, 3, 4])) - - # Naive tensor product, no sorting and merging - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = tensor_product(a, a) - @test a2 isa GradedAxes.GradedUnitRange - @test a2 == gradedrange([U1(0) => 4, U1(1) => 6, U1(1) => 6, U1(2) => 9]) - @test length(a2) == 25 - @test a2 == 1:25 - @test blocklength(a2) == 4 - @test blocklengths(a2) == [4, 6, 6, 9] - @test sectors(a2) == [U1(0), U1(1), U1(1), U1(2)] - @test sector(a2, Block(1)) == U1(0) - @test sector(a2, Block(2)) == U1(1) - @test sector(a2, Block(3)) == U1(1) - @test sector(a2, Block(4)) == U1(2) - - # Fusion tensor product, with sorting and merging - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(a, a) - @test a2 isa GradedAxes.GradedUnitRange - @test a2 == gradedrange([U1(0) => 4, U1(1) => 12, U1(2) => 9]) - @test length(a2) == 25 - @test a2 == 1:25 - @test blocklength(a2) == 3 - @test blocklengths(a2) == [4, 12, 9] - @test sectors(a2) == [U1(0), U1(1), U1(2)] - @test sector(a2, Block(1)) == U1(0) - @test sector(a2, Block(2)) == U1(1) - @test sector(a2, Block(3)) == U1(2) - - # The partitioned permutation needed to sort - # and merge an unsorted graded space - a = gradedrange([U1(0), U1(1)], [2, 3]) - perm_a = blockmergesortperm(tensor_product(a, a)) - @test perm_a == BlockVector([Block(1), Block(2), Block(3), Block(4)], [1, 2, 1]) - @test tensor_product(a, a)[perm_a] == fuse(a, a) - - a = gradedrange([U1(0), U1(1)], [2, 3]) - @test !isdual(a) - a = dual(a) - @test isdual(a) - @test sectors(a) == [U1(0), U1(-1)] - @test sector(a, Block(1)) == U1(0) - @test sector(a, Block(2)) == U1(-1) - - # Permute blocks - a = gradedrange([U1(0), U1(1), U1(2)], [2, 3, 4]) - perm_a = a[[Block(3), Block(1), Block(2)]] - @test perm_a == gradedrange([U1(2), U1(0), U1(1)], [4, 2, 3]) - @test sectors(perm_a) == [U1(2), U1(0), U1(1)] - @test blocklengths(perm_a) == [4, 2, 3] - - # Test fusion with dual spaces - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(a, a) - @test !isdual(a2) - @test sectors(a2) == [U1(0), U1(1), U1(2)] - - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(a, a; isdual=true) - @test isdual(a2) - @test sectors(a2) == [U1(0), U1(1), U1(2)] - - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(dual(a), dual(a)) - @test isdual(a2) - @test sectors(a2) == [U1(-2), U1(-1), U1(0)] - - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(dual(a), dual(a); isdual=false) - @test !isdual(a2) - @test sectors(a2) == [U1(-2), U1(-1), U1(0)] - - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(a, dual(a)) - @test !isdual(a2) - @test sectors(a2) == [U1(-1), U1(0), U1(1)] - - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(a, dual(a); isdual=true) - @test isdual(a2) - @test sectors(a2) == [U1(-1), U1(0), U1(1)] - - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(dual(a), a) - @test !isdual(a2) - @test sectors(a2) == [U1(-1), U1(0), U1(1)] - - a = gradedrange([U1(0), U1(1)], [2, 3]) - a2 = fuse(dual(a), a; isdual=true) - @test isdual(a2) - @test sectors(a2) == [U1(-1), U1(0), U1(1)] -end -end diff --git a/NDTensors/src/lib/GradedAxesNext/.JuliaFormatter.toml b/NDTensors/src/lib/GradedAxesNext/.JuliaFormatter.toml deleted file mode 100644 index 08f664cdb9..0000000000 --- a/NDTensors/src/lib/GradedAxesNext/.JuliaFormatter.toml +++ /dev/null @@ -1,2 +0,0 @@ -style = "blue" -indent = 2 diff --git a/NDTensors/src/lib/GradedAxesNext/src/GradedAxesNext.jl b/NDTensors/src/lib/GradedAxesNext/src/GradedAxesNext.jl deleted file mode 100644 index afd781e186..0000000000 --- a/NDTensors/src/lib/GradedAxesNext/src/GradedAxesNext.jl +++ /dev/null @@ -1,3 +0,0 @@ -module GradedAxesNext -include("gradedunitrange.jl") -end diff --git a/NDTensors/src/lib/GradedAxesNext/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxesNext/src/gradedunitrange.jl deleted file mode 100644 index 04cd88d339..0000000000 --- a/NDTensors/src/lib/GradedAxesNext/src/gradedunitrange.jl +++ /dev/null @@ -1,245 +0,0 @@ -using BlockArrays: - BlockArrays, - Block, - BlockedUnitRange, - BlockRange, - BlockVector, - blockedrange, - BlockIndexRange, - blockfirsts, - blocklasts, - blocklengths, - findblock, - findblockindex, - mortar -using ..LabelledNumbers: LabelledNumbers, LabelledInteger, label, labelled, unlabel - -# Custom `BlockedUnitRange` constructor that takes a unit range -# and a set of block lengths, similar to `BlockArray(::AbstractArray, blocklengths...)`. -function blockedunitrange(a::AbstractUnitRange, blocklengths) - blocklengths_shifted = copy(blocklengths) - blocklengths_shifted[1] += (first(a) - 1) - blocklasts = cumsum(blocklengths_shifted) - return BlockArrays._BlockedUnitRange(first(a), blocklasts) -end - -# Circumvents issue in `findblock` that assumes the `BlockedUnitRange` -# starts at 1. -# TODO: Raise an issue with `BlockArrays`. -function blockedunitrange_findblock(a::BlockedUnitRange, index::Integer) - @boundscheck index in 1:length(a) || throw(BoundsError(a, index)) - return @inbounds findblock(a, index + first(a) - 1) -end - -# Circumvents issue in `findblockindex` that assumes the `BlockedUnitRange` -# starts at 1. -# TODO: Raise an issue with `BlockArrays`. -function blockedunitrange_findblockindex(a::BlockedUnitRange, index::Integer) - @boundscheck index in 1:length(a) || throw(BoundsError()) - return @inbounds findblockindex(a, index + first(a) - 1) -end - -const GradedUnitRange{BlockLasts<:Vector{<:LabelledInteger}} = BlockedUnitRange{BlockLasts} - -function gradedrange(lblocklengths::AbstractVector{<:LabelledInteger}) - brange = blockedrange(unlabel.(lblocklengths)) - lblocklasts = labelled.(blocklasts(brange), label.(lblocklengths)) - # TODO: `first` is forced to be `Int` in `BlockArrays.BlockedUnitRange`, - # so this doesn't do anything right now. Make a PR to generalize it. - firstlength = first(lblocklengths) - lfirst = oneunit(firstlength) - return BlockArrays._BlockedUnitRange(lfirst, lblocklasts) -end - -Base.last(a::GradedUnitRange) = isempty(a.lasts) ? first(a) - 1 : last(a.lasts) - -function gradedrange(lblocklengths::AbstractVector{<:Pair{<:Any,<:Integer}}) - return gradedrange(labelled.(last.(lblocklengths), first.(lblocklengths))) -end - -function labelled_blocks(a::BlockedUnitRange, labels) - return BlockArrays._BlockedUnitRange(a.first, labelled.(a.lasts, labels)) -end - -function BlockArrays.findblock(a::GradedUnitRange, index::Integer) - return blockedunitrange_findblock(unlabel_blocks(a), index) -end - -function blockedunitrange_findblock(a::GradedUnitRange, index::Integer) - return blockedunitrange_findblock(unlabel_blocks(a), index) -end - -function blockedunitrange_findblockindex(a::GradedUnitRange, index::Integer) - return blockedunitrange_findblockindex(unlabel_blocks(a), index) -end - -function BlockArrays.findblockindex(a::GradedUnitRange, index::Integer) - return blockedunitrange_findblockindex(unlabel_blocks(a), index) -end - -## Block label interface - -# Internal function -function get_label(a::BlockedUnitRange, index::Block{1}) - return label(blocklasts(a)[Int(index)]) -end - -# Internal function -function get_label(a::BlockedUnitRange, index::Integer) - return get_label(a, blockedunitrange_findblock(a, index)) -end - -function blocklabels(a::BlockVector) - return map(BlockRange(a)) do block - return label(@view(a[block])) - end -end - -function blocklabels(a::BlockedUnitRange) - # Using `a.lasts` here since that is what is stored - # inside of `BlockedUnitRange`, maybe change that. - # For example, it could be something like: - # - # map(BlockRange(a)) do block - # return label(@view(a[block])) - # end - # - return label.(a.lasts) -end - -# TODO: This relies on internals of `BlockArrays`, maybe redesign -# to try to avoid that. -# TODO: Define `set_grades`, `set_sector_labels`, `set_labels`. -function unlabel_blocks(a::BlockedUnitRange) - return BlockArrays._BlockedUnitRange(a.first, unlabel.(a.lasts)) -end - -## BlockedUnitRage interface - -function Base.axes(ga::GradedUnitRange) - return map(axes(unlabel_blocks(ga))) do a - return labelled_blocks(a, blocklabels(ga)) - end -end - -function BlockArrays.blockfirsts(a::GradedUnitRange) - return labelled.(blockfirsts(unlabel_blocks(a)), blocklabels(a)) -end - -function BlockArrays.blocklasts(a::GradedUnitRange) - return labelled.(blocklasts(unlabel_blocks(a)), blocklabels(a)) -end - -function BlockArrays.blocklengths(a::GradedUnitRange) - return labelled.(blocklengths(unlabel_blocks(a)), blocklabels(a)) -end - -function Base.first(a::GradedUnitRange) - return labelled(first(unlabel_blocks(a)), label(a[Block(1)])) -end - -function firstblockindices(a::GradedUnitRange) - return labelled.(firstblockindices(unlabel_blocks(a)), blocklabels(a)) -end - -function blockedunitrange_getindex(a::GradedUnitRange, index) - # This uses `blocklasts` since that is what is stored - # in `BlockedUnitRange`, maybe abstract that away. - return labelled(unlabel_blocks(a)[index], get_label(a, index)) -end - -# Like `a[indices]` but preserves block structure. -using BlockArrays: block, blockindex -function blockedunitrange_getindices( - a::BlockedUnitRange, indices::AbstractUnitRange{<:Integer} -) - first_blockindex = blockedunitrange_findblockindex(a, first(indices)) - last_blockindex = blockedunitrange_findblockindex(a, last(indices)) - first_block = block(first_blockindex) - last_block = block(last_blockindex) - blocklengths = if first_block == last_block - [length(indices)] - else - map(first_block:last_block) do block - if block == first_block - return length(a[first_block]) - blockindex(first_blockindex) + 1 - end - if block == last_block - return blockindex(last_blockindex) - end - return length(a[block]) - end - end - return blockedunitrange(indices .+ (first(a) - 1), blocklengths) -end - -function blockedunitrange_getindices(a::BlockedUnitRange, indices::BlockIndexRange) - return a[block(indices)][only(indices.indices)] -end - -function blockedunitrange_getindices(a::BlockedUnitRange, indices::Vector{<:Integer}) - return map(index -> a[index], indices) -end - -function blockedunitrange_getindices( - a::BlockedUnitRange, indices::Vector{<:Union{Block{1},BlockIndexRange{1}}} -) - return mortar(map(index -> a[index], indices)) -end - -function blockedunitrange_getindices(a::BlockedUnitRange, indices) - return error("Not implemented.") -end - -# The blocks of the corresponding slice. -_blocks(a::AbstractUnitRange, indices) = error("Not implemented") -function _blocks(a::AbstractUnitRange, indices::AbstractUnitRange) - return findblock(a, first(indices)):findblock(a, last(indices)) -end -function _blocks(a::AbstractUnitRange, indices::BlockRange) - return indices -end - -# The block labels of the corresponding slice. -function blocklabels(a::AbstractUnitRange, indices) - return map(_blocks(a, indices)) do block - return label(a[block]) - end -end - -function blockedunitrange_getindices( - ga::GradedUnitRange, indices::AbstractUnitRange{<:Integer} -) - a_indices = blockedunitrange_getindices(unlabel_blocks(ga), indices) - return labelled_blocks(a_indices, blocklabels(ga, indices)) -end - -function blockedunitrange_getindices(ga::GradedUnitRange, indices::BlockRange) - return labelled_blocks(unlabel_blocks(ga)[indices], blocklabels(ga, indices)) -end - -function Base.getindex(a::GradedUnitRange, index::Integer) - return blockedunitrange_getindex(a, index) -end - -function Base.getindex(a::GradedUnitRange, index::Block{1}) - return blockedunitrange_getindex(a, index) -end - -function Base.getindex(a::GradedUnitRange, indices::BlockIndexRange) - return blockedunitrange_getindices(a, indices) -end - -function Base.getindex( - a::GradedUnitRange, indices::BlockRange{1,<:Tuple{AbstractUnitRange{Int}}} -) - return blockedunitrange_getindices(a, indices) -end - -function Base.getindex(a::GradedUnitRange, indices) - return blockedunitrange_getindices(a, indices) -end - -function Base.getindex(a::GradedUnitRange, indices::AbstractUnitRange{<:Integer}) - return blockedunitrange_getindices(a, indices) -end diff --git a/NDTensors/src/lib/GradedAxesNext/test/Project.toml b/NDTensors/src/lib/GradedAxesNext/test/Project.toml deleted file mode 100644 index d1bf575ce0..0000000000 --- a/NDTensors/src/lib/GradedAxesNext/test/Project.toml +++ /dev/null @@ -1,4 +0,0 @@ -[deps] -BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" -NDTensors = "23ae76d9-e61a-49c4-8f12-3f1a16adf9cf" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/NDTensors/src/lib/GradedAxesNext/test/runtests.jl b/NDTensors/src/lib/GradedAxesNext/test/runtests.jl deleted file mode 100644 index 72ba59ad7c..0000000000 --- a/NDTensors/src/lib/GradedAxesNext/test/runtests.jl +++ /dev/null @@ -1,107 +0,0 @@ -@eval module $(gensym()) -using BlockArrays: - Block, BlockVector, blockedrange, blockfirsts, blocklasts, blocklength, blocklengths -using NDTensors.GradedAxesNext: GradedUnitRange, blocklabels, gradedrange -using NDTensors.LabelledNumbers: LabelledUnitRange, label, unlabel -using Test: @test, @test_broken, @testset -@testset "GradedAxes" begin - a = gradedrange(["x" => 2, "y" => 3]) - @test a isa GradedUnitRange - @test length(a) == 5 - @test a[Block(2)] == 3:5 - @test label(a[Block(2)]) == "y" - @test a[Block(2)] isa LabelledUnitRange - @test a[4] == 4 - @test label(a[4]) == "y" - @test unlabel(a[4]) == 4 - @test blocklengths(a) == [2, 3] - @test blocklabels(a) == ["x", "y"] - @test label.(blocklengths(a)) == ["x", "y"] - @test blockfirsts(a) == [1, 3] - @test label.(blockfirsts(a)) == ["x", "y"] - @test first(a) == 1 - @test label(first(a)) == "x" - @test blocklasts(a) == [2, 5] - @test label.(blocklasts(a)) == ["x", "y"] - @test last(a) == 5 - @test label(last(a)) == "y" - @test a[Block(2)] == 3:5 - @test label(a[Block(2)]) == "y" - @test length(a[Block(2)]) == 3 - @test blocklengths(only(axes(a))) == blocklengths(a) - @test blocklabels(only(axes(a))) == blocklabels(a) - - # Slicing operations - x = gradedrange(["x" => 2, "y" => 3]) - a = x[2:4] - @test a isa GradedUnitRange - @test length(a) == 3 - @test blocklength(a) == 2 - @test a[Block(1)] == 2:2 - @test label(a[Block(1)]) == "x" - @test a[Block(2)] == 3:4 - @test label(a[Block(2)]) == "y" - @test isone(first(only(axes(a)))) - @test length(only(axes(a))) == length(a) - @test blocklengths(only(axes(a))) == blocklengths(a) - - x = gradedrange(["x" => 2, "y" => 3]) - a = x[3:4] - @test a isa GradedUnitRange - @test length(a) == 2 - @test blocklength(a) == 1 - @test a[Block(1)] == 3:4 - @test label(a[Block(1)]) == "y" - - x = gradedrange(["x" => 2, "y" => 3]) - a = x[2:4][1:2] - @test a isa GradedUnitRange - @test length(a) == 2 - @test blocklength(a) == 2 - @test a[Block(1)] == 2:2 - @test label(a[Block(1)]) == "x" - @test a[Block(2)] == 3:3 - @test label(a[Block(2)]) == "y" - - x = gradedrange(["x" => 2, "y" => 3]) - a = x[Block(2)[2:3]] - @test a isa LabelledUnitRange - @test length(a) == 2 - @test a == 4:5 - @test label(a) == "y" - - x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) - a = x[Block(2):Block(3)] - @test a isa GradedUnitRange - @test length(a) == 7 - @test blocklength(a) == 2 - @test blocklengths(a) == [3, 4] - @test blocklabels(a) == ["y", "z"] - @test a[Block(1)] == 3:5 - @test a[Block(2)] == 6:9 - - x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) - a = x[[Block(3), Block(2)]] - @test a isa BlockVector - @test length(a) == 7 - @test blocklength(a) == 2 - # TODO: `BlockArrays` doesn't define `blocklengths` - # for `BlockVector`, should it? - @test_broken blocklengths(a) == [4, 3] - @test blocklabels(a) == ["z", "y"] - @test a[Block(1)] == 6:9 - @test a[Block(2)] == 3:5 - - x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) - a = x[[Block(3)[2:3], Block(2)[2:3]]] - @test a isa BlockVector - @test length(a) == 4 - @test blocklength(a) == 2 - # TODO: `BlockArrays` doesn't define `blocklengths` - # for `BlockVector`, should it? - @test_broken blocklengths(a) == [2, 2] - @test blocklabels(a) == ["z", "y"] - @test a[Block(1)] == 7:8 - @test a[Block(2)] == 4:5 -end -end diff --git a/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/src/TensorAlgebraGradedAxesExt.jl b/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/src/TensorAlgebraGradedAxesExt.jl index 94d1bbb638..2b66b688cb 100644 --- a/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/src/TensorAlgebraGradedAxesExt.jl +++ b/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/src/TensorAlgebraGradedAxesExt.jl @@ -1,8 +1,8 @@ module TensorAlgebraGradedAxesExt -using ...GradedAxes: AbstractGradedUnitRange, tensor_product +using ...GradedAxes: GradedUnitRange, tensor_product using ..TensorAlgebra: TensorAlgebra -function TensorAlgebra.:⊗(a1::AbstractGradedUnitRange, a2::AbstractGradedUnitRange) +function TensorAlgebra.:⊗(a1::GradedUnitRange, a2::GradedUnitRange) return tensor_product(a1, a2) end end diff --git a/NDTensors/test/lib/runtests.jl b/NDTensors/test/lib/runtests.jl index cb5d298c8f..090546de91 100644 --- a/NDTensors/test/lib/runtests.jl +++ b/NDTensors/test/lib/runtests.jl @@ -10,7 +10,6 @@ using Test: @testset "CUDAExtensions", "DiagonalArrays", "GradedAxes", - "GradedAxesNext", "GPUArraysCoreExtensions", "LabelledNumbers", "MetalExtensions", From 980ea3fc10a7c5cd40597d0337fd7a3abb2bd3be Mon Sep 17 00:00:00 2001 From: mtfishman Date: Fri, 15 Mar 2024 17:05:22 -0400 Subject: [PATCH 2/5] Get some basic graded axes fusion working --- .../ext/GradedAxesSectorsExt/Project.toml | 2 + .../src/GradedAxesSectorsExt.jl | 9 ++ .../GradedAxesSectorsExt/test/Project.toml | 3 + .../ext/GradedAxesSectorsExt/test/runtests.jl | 15 +++ .../src/lib/GradedAxes/src/GradedAxes.jl | 1 + NDTensors/src/lib/GradedAxes/src/fusion.jl | 53 +++++++-- .../src/lib/GradedAxes/src/gradedunitrange.jl | 5 + NDTensors/src/lib/GradedAxes/test/runtests.jl | 106 +---------------- .../src/lib/GradedAxes/test/test_basics.jl | 112 ++++++++++++++++++ .../GradedAxes/test/test_tensor_product.jl | 11 ++ .../LabelledNumbers/src/labelledinteger.jl | 6 + 11 files changed, 213 insertions(+), 110 deletions(-) create mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml create mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl create mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/Project.toml create mode 100644 NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/runtests.jl create mode 100644 NDTensors/src/lib/GradedAxes/test/test_basics.jl create mode 100644 NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl diff --git a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml new file mode 100644 index 0000000000..9b1d5ccd25 --- /dev/null +++ b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/Project.toml @@ -0,0 +1,2 @@ +[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 new file mode 100644 index 0000000000..a742f04b92 --- /dev/null +++ b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl @@ -0,0 +1,9 @@ +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 new file mode 100644 index 0000000000..ef491a529c --- /dev/null +++ b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/Project.toml @@ -0,0 +1,3 @@ +[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 new file mode 100644 index 0000000000..371e7e57cd --- /dev/null +++ b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/test/runtests.jl @@ -0,0 +1,15 @@ +@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 diff --git a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl index e3f5d81614..7af606ab1a 100644 --- a/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl +++ b/NDTensors/src/lib/GradedAxes/src/GradedAxes.jl @@ -1,4 +1,5 @@ module GradedAxes include("gradedunitrange.jl") include("fusion.jl") +include("../ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl") end diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index e7d2c71979..8d50b2832b 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -1,12 +1,9 @@ using BlockArrays: BlockedUnitRange -# TODO: Implement these. -function blocksortperm end -function blockmergesortperm(::BlockedUnitRange) - return error("Not implemented yet.") -end + +# TODO: Implement or delete these, these are from the old +# version of `GradedAxes`. function dual end function fuse end -function invblockperm end function sector end # Represents the range `1:1` or `Base.OneTo(1)`. @@ -47,7 +44,49 @@ function tensor_product(a1::OneToOne, a2::OneToOne) return OneToOne() end +function fuse_labels(x, y) + return error( + "`fuse_labels` not implemented for object of type `$(typeof(x))` and `$(typeof(y))`." + ) +end + +function fuse_blocklengths(x::Integer, y::Integer) + return x * y +end + +using ..LabelledNumbers: LabelledInteger, label, labelled, unlabel +function fuse_blocklengths(x::LabelledInteger, y::LabelledInteger) + return labelled(unlabel(x) * unlabel(y), fuse_labels(label(x), label(y))) +end + using BlockArrays: blockedrange, blocks function tensor_product(a1::BlockedUnitRange, a2::BlockedUnitRange) - return blockedrange(prod.(length, vec(collect(Iterators.product(blocks.((a1, a2))...))))) + blocklengths = map(vec(collect(Iterators.product(blocks(a1), blocks(a2))))) do x + return mapreduce(length, fuse_blocklengths, x) + end + return blockedrange(blocklengths) +end + +using BlockArrays: Block, BlockVector +using SplitApplyCombine: groupcount + +function blocksortperm end + +invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) + +# Get the permutation for sorting, then group by common elements. +# groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]] +function groupsortperm(v; kwargs...) + perm = sortperm(v; kwargs...) + v_sorted = @view v[perm] + group_lengths = collect(groupcount(identity, v_sorted)) + return BlockVector(perm, group_lengths) +end + +function blockmergesortperm(a::GradedUnitRange) + # 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(blocklabels(a))) end diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index 04cd88d339..d1ce1f442f 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -51,6 +51,11 @@ function gradedrange(lblocklengths::AbstractVector{<:LabelledInteger}) return BlockArrays._BlockedUnitRange(lfirst, lblocklasts) end +# To help with generic code. +function BlockArrays.blockedrange(lblocklengths::AbstractVector{<:LabelledInteger}) + return gradedrange(lblocklengths) +end + Base.last(a::GradedUnitRange) = isempty(a.lasts) ? first(a) - 1 : last(a.lasts) function gradedrange(lblocklengths::AbstractVector{<:Pair{<:Any,<:Integer}}) diff --git a/NDTensors/src/lib/GradedAxes/test/runtests.jl b/NDTensors/src/lib/GradedAxes/test/runtests.jl index 2cbdcbcb5d..5d2b677248 100644 --- a/NDTensors/src/lib/GradedAxes/test/runtests.jl +++ b/NDTensors/src/lib/GradedAxes/test/runtests.jl @@ -1,107 +1,7 @@ @eval module $(gensym()) -using BlockArrays: - Block, BlockVector, blockedrange, blockfirsts, blocklasts, blocklength, blocklengths -using NDTensors.GradedAxes: GradedUnitRange, blocklabels, gradedrange -using NDTensors.LabelledNumbers: LabelledUnitRange, label, unlabel -using Test: @test, @test_broken, @testset +using Test: @testset @testset "GradedAxes" begin - a = gradedrange(["x" => 2, "y" => 3]) - @test a isa GradedUnitRange - @test length(a) == 5 - @test a[Block(2)] == 3:5 - @test label(a[Block(2)]) == "y" - @test a[Block(2)] isa LabelledUnitRange - @test a[4] == 4 - @test label(a[4]) == "y" - @test unlabel(a[4]) == 4 - @test blocklengths(a) == [2, 3] - @test blocklabels(a) == ["x", "y"] - @test label.(blocklengths(a)) == ["x", "y"] - @test blockfirsts(a) == [1, 3] - @test label.(blockfirsts(a)) == ["x", "y"] - @test first(a) == 1 - @test label(first(a)) == "x" - @test blocklasts(a) == [2, 5] - @test label.(blocklasts(a)) == ["x", "y"] - @test last(a) == 5 - @test label(last(a)) == "y" - @test a[Block(2)] == 3:5 - @test label(a[Block(2)]) == "y" - @test length(a[Block(2)]) == 3 - @test blocklengths(only(axes(a))) == blocklengths(a) - @test blocklabels(only(axes(a))) == blocklabels(a) - - # Slicing operations - x = gradedrange(["x" => 2, "y" => 3]) - a = x[2:4] - @test a isa GradedUnitRange - @test length(a) == 3 - @test blocklength(a) == 2 - @test a[Block(1)] == 2:2 - @test label(a[Block(1)]) == "x" - @test a[Block(2)] == 3:4 - @test label(a[Block(2)]) == "y" - @test isone(first(only(axes(a)))) - @test length(only(axes(a))) == length(a) - @test blocklengths(only(axes(a))) == blocklengths(a) - - x = gradedrange(["x" => 2, "y" => 3]) - a = x[3:4] - @test a isa GradedUnitRange - @test length(a) == 2 - @test blocklength(a) == 1 - @test a[Block(1)] == 3:4 - @test label(a[Block(1)]) == "y" - - x = gradedrange(["x" => 2, "y" => 3]) - a = x[2:4][1:2] - @test a isa GradedUnitRange - @test length(a) == 2 - @test blocklength(a) == 2 - @test a[Block(1)] == 2:2 - @test label(a[Block(1)]) == "x" - @test a[Block(2)] == 3:3 - @test label(a[Block(2)]) == "y" - - x = gradedrange(["x" => 2, "y" => 3]) - a = x[Block(2)[2:3]] - @test a isa LabelledUnitRange - @test length(a) == 2 - @test a == 4:5 - @test label(a) == "y" - - x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) - a = x[Block(2):Block(3)] - @test a isa GradedUnitRange - @test length(a) == 7 - @test blocklength(a) == 2 - @test blocklengths(a) == [3, 4] - @test blocklabels(a) == ["y", "z"] - @test a[Block(1)] == 3:5 - @test a[Block(2)] == 6:9 - - x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) - a = x[[Block(3), Block(2)]] - @test a isa BlockVector - @test length(a) == 7 - @test blocklength(a) == 2 - # TODO: `BlockArrays` doesn't define `blocklengths` - # for `BlockVector`, should it? - @test_broken blocklengths(a) == [4, 3] - @test blocklabels(a) == ["z", "y"] - @test a[Block(1)] == 6:9 - @test a[Block(2)] == 3:5 - - x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) - a = x[[Block(3)[2:3], Block(2)[2:3]]] - @test a isa BlockVector - @test length(a) == 4 - @test blocklength(a) == 2 - # TODO: `BlockArrays` doesn't define `blocklengths` - # for `BlockVector`, should it? - @test_broken blocklengths(a) == [2, 2] - @test blocklabels(a) == ["z", "y"] - @test a[Block(1)] == 7:8 - @test a[Block(2)] == 4:5 + include("test_basics.jl") + include("test_tensor_product.jl") end end diff --git a/NDTensors/src/lib/GradedAxes/test/test_basics.jl b/NDTensors/src/lib/GradedAxes/test/test_basics.jl new file mode 100644 index 0000000000..33d837aad5 --- /dev/null +++ b/NDTensors/src/lib/GradedAxes/test/test_basics.jl @@ -0,0 +1,112 @@ +@eval module $(gensym()) +using BlockArrays: + Block, BlockVector, blockedrange, blockfirsts, blocklasts, blocklength, blocklengths +using NDTensors.GradedAxes: GradedUnitRange, blocklabels, gradedrange +using NDTensors.LabelledNumbers: LabelledUnitRange, label, labelled, unlabel +using Test: @test, @test_broken, @testset +@testset "GradedAxes basics" begin + for a in ( + blockedrange([labelled(2, "x"), labelled(3, "y")]), + gradedrange([labelled(2, "x"), labelled(3, "y")]), + gradedrange(["x" => 2, "y" => 3]), + ) + @test a isa GradedUnitRange + @test length(a) == 5 + @test a[Block(2)] == 3:5 + @test label(a[Block(2)]) == "y" + @test a[Block(2)] isa LabelledUnitRange + @test a[4] == 4 + @test label(a[4]) == "y" + @test unlabel(a[4]) == 4 + @test blocklengths(a) == [2, 3] + @test blocklabels(a) == ["x", "y"] + @test label.(blocklengths(a)) == ["x", "y"] + @test blockfirsts(a) == [1, 3] + @test label.(blockfirsts(a)) == ["x", "y"] + @test first(a) == 1 + @test label(first(a)) == "x" + @test blocklasts(a) == [2, 5] + @test label.(blocklasts(a)) == ["x", "y"] + @test last(a) == 5 + @test label(last(a)) == "y" + @test a[Block(2)] == 3:5 + @test label(a[Block(2)]) == "y" + @test length(a[Block(2)]) == 3 + @test blocklengths(only(axes(a))) == blocklengths(a) + @test blocklabels(only(axes(a))) == blocklabels(a) + end + + # Slicing operations + x = gradedrange(["x" => 2, "y" => 3]) + a = x[2:4] + @test a isa GradedUnitRange + @test length(a) == 3 + @test blocklength(a) == 2 + @test a[Block(1)] == 2:2 + @test label(a[Block(1)]) == "x" + @test a[Block(2)] == 3:4 + @test label(a[Block(2)]) == "y" + @test isone(first(only(axes(a)))) + @test length(only(axes(a))) == length(a) + @test blocklengths(only(axes(a))) == blocklengths(a) + + x = gradedrange(["x" => 2, "y" => 3]) + a = x[3:4] + @test a isa GradedUnitRange + @test length(a) == 2 + @test blocklength(a) == 1 + @test a[Block(1)] == 3:4 + @test label(a[Block(1)]) == "y" + + x = gradedrange(["x" => 2, "y" => 3]) + a = x[2:4][1:2] + @test a isa GradedUnitRange + @test length(a) == 2 + @test blocklength(a) == 2 + @test a[Block(1)] == 2:2 + @test label(a[Block(1)]) == "x" + @test a[Block(2)] == 3:3 + @test label(a[Block(2)]) == "y" + + x = gradedrange(["x" => 2, "y" => 3]) + a = x[Block(2)[2:3]] + @test a isa LabelledUnitRange + @test length(a) == 2 + @test a == 4:5 + @test label(a) == "y" + + x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) + a = x[Block(2):Block(3)] + @test a isa GradedUnitRange + @test length(a) == 7 + @test blocklength(a) == 2 + @test blocklengths(a) == [3, 4] + @test blocklabels(a) == ["y", "z"] + @test a[Block(1)] == 3:5 + @test a[Block(2)] == 6:9 + + x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) + a = x[[Block(3), Block(2)]] + @test a isa BlockVector + @test length(a) == 7 + @test blocklength(a) == 2 + # TODO: `BlockArrays` doesn't define `blocklengths` + # for `BlockVector`, should it? + @test_broken blocklengths(a) == [4, 3] + @test blocklabels(a) == ["z", "y"] + @test a[Block(1)] == 6:9 + @test a[Block(2)] == 3:5 + + x = gradedrange(["x" => 2, "y" => 3, "z" => 4]) + a = x[[Block(3)[2:3], Block(2)[2:3]]] + @test a isa BlockVector + @test length(a) == 4 + @test blocklength(a) == 2 + # TODO: `BlockArrays` doesn't define `blocklengths` + # for `BlockVector`, should it? + @test_broken blocklengths(a) == [2, 2] + @test blocklabels(a) == ["z", "y"] + @test a[Block(1)] == 7:8 + @test a[Block(2)] == 4:5 +end +end diff --git a/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl new file mode 100644 index 0000000000..63a27a62e3 --- /dev/null +++ b/NDTensors/src/lib/GradedAxes/test/test_tensor_product.jl @@ -0,0 +1,11 @@ +@eval module $(gensym()) +using NDTensors.GradedAxes: GradedAxes, GradedUnitRange, gradedrange, tensor_product +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 +end +end diff --git a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl index 8b7e6cbb6e..8572636e1b 100644 --- a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl +++ b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl @@ -3,6 +3,7 @@ struct LabelledInteger{Value<:Integer,Label} <: Integer label::Label end LabelledStyle(::Type{<:LabelledInteger}) = IsLabelled() +# TODO: Define `set_value` and `set_label`? label(lobject::LabelledInteger) = lobject.label # TODO: Use `TypeParameterAccessors`. label_type(::Type{<:LabelledInteger{<:Any,Label}}) where {Label} = Label @@ -10,6 +11,11 @@ labelled(object::Integer, label) = LabelledInteger(object, label) unlabel(lobject::LabelledInteger) = lobject.value unlabel_type(::Type{<:LabelledInteger{Value}}) where {Value} = Value +# When using as shapes of arrays. +# TODO: Preserve the label? For example: +# labelled(Base.to_shape(unlabel(x)), label(x)) +Base.to_shape(x::LabelledInteger) = Base.to_shape(unlabel(x)) + # TODO: Define `labelled_convert`. Base.convert(type::Type{<:Number}, x::LabelledInteger) = type(unlabel(x)) # TODO: This is only needed for older Julia versions, like Julia 1.6. From 341e03fd49558883e7ce28a5e58a282306571a23 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Sun, 17 Mar 2024 17:55:08 -0400 Subject: [PATCH 3/5] Fix tests --- .../blocksparsearrayinterface.jl | 4 +- .../src/GradedAxesSectorsExt.jl | 4 +- NDTensors/src/lib/GradedAxes/src/fusion.jl | 38 ++++++++++++++----- .../src/lib/GradedAxes/src/gradedunitrange.jl | 14 +++++++ .../LabelledNumbers/src/labelledinteger.jl | 10 +++++ .../test/test_basics.jl | 17 +++++---- 6 files changed, 67 insertions(+), 20 deletions(-) diff --git a/NDTensors/src/lib/BlockSparseArrays/src/blocksparsearrayinterface/blocksparsearrayinterface.jl b/NDTensors/src/lib/BlockSparseArrays/src/blocksparsearrayinterface/blocksparsearrayinterface.jl index 81362fb648..8b3e4d283f 100644 --- a/NDTensors/src/lib/BlockSparseArrays/src/blocksparsearrayinterface/blocksparsearrayinterface.jl +++ b/NDTensors/src/lib/BlockSparseArrays/src/blocksparsearrayinterface/blocksparsearrayinterface.jl @@ -22,6 +22,7 @@ end # TODO: Implement as `copy(@view a[I...])`, which is then implemented # through `ArrayLayouts.sub_materialize`. +using ..SparseArrayInterface: set_getindex_zero_function function blocksparse_getindex( a::AbstractArray{<:Any,N}, I::Vararg{AbstractVector{<:Block{1}},N} ) where {N} @@ -30,8 +31,9 @@ function blocksparse_getindex( CI = map(i -> Int.(i), I) subblocks_a = blocks_a[CI...] subaxes = ntuple(ndims(a)) do i - return axes(a, i)[I[i]] + return only(axes(axes(a, i)[I[i]])) end + subblocks_a = set_getindex_zero_function(subblocks_a, BlockZero(subaxes)) return typeof(a)(subblocks_a, subaxes) end diff --git a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl index a742f04b92..aa3056438e 100644 --- a/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl +++ b/NDTensors/src/lib/GradedAxes/ext/GradedAxesSectorsExt/src/GradedAxesSectorsExt.jl @@ -1,9 +1,9 @@ module GradedAxesSectorsExt using ..GradedAxes: GradedAxes -using ...Sectors: Sectors, AbstractCategory, ⊗, dual +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) +## GradedAxes.dual(c::AbstractCategory) = dual(c) end diff --git a/NDTensors/src/lib/GradedAxes/src/fusion.jl b/NDTensors/src/lib/GradedAxes/src/fusion.jl index 8d50b2832b..062d6a883d 100644 --- a/NDTensors/src/lib/GradedAxes/src/fusion.jl +++ b/NDTensors/src/lib/GradedAxes/src/fusion.jl @@ -1,10 +1,8 @@ using BlockArrays: BlockedUnitRange -# TODO: Implement or delete these, these are from the old -# version of `GradedAxes`. -function dual end -function fuse end -function sector end +# TODO: Decide what to do about `dual`. Should there just +# be a version in `Sectors`? +## function dual end # Represents the range `1:1` or `Base.OneTo(1)`. struct OneToOne{T} <: AbstractUnitRange{T} end @@ -67,13 +65,15 @@ function tensor_product(a1::BlockedUnitRange, a2::BlockedUnitRange) return blockedrange(blocklengths) 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 + using BlockArrays: Block, BlockVector using SplitApplyCombine: groupcount - -function blocksortperm end - -invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) - # Get the permutation for sorting, then group by common elements. # groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]] function groupsortperm(v; kwargs...) @@ -83,10 +83,28 @@ function groupsortperm(v; kwargs...) return BlockVector(perm, group_lengths) 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) + # 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 + +# Used by `TensorAlgebra.splitdims` in `BlockSparseArraysGradedAxesExt`. +invblockperm(a::Vector{<:Block{1}}) = Block.(invperm(Int.(a))) + +# Used by `TensorAlgebra.fusedims` in `BlockSparseArraysGradedAxesExt`. function blockmergesortperm(a::GradedUnitRange) # 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))) end diff --git a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl index d1ce1f442f..21d327a269 100644 --- a/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl +++ b/NDTensors/src/lib/GradedAxes/src/gradedunitrange.jl @@ -2,6 +2,7 @@ using BlockArrays: BlockArrays, Block, BlockedUnitRange, + BlockIndex, BlockRange, BlockVector, blockedrange, @@ -223,6 +224,10 @@ function blockedunitrange_getindices(ga::GradedUnitRange, indices::BlockRange) return labelled_blocks(unlabel_blocks(ga)[indices], blocklabels(ga, indices)) end +function blockedunitrange_getindices(a::GradedUnitRange, indices::BlockIndex{1}) + return a[block(indices)][blockindex(indices)] +end + function Base.getindex(a::GradedUnitRange, index::Integer) return blockedunitrange_getindex(a, index) end @@ -241,6 +246,15 @@ function Base.getindex( return blockedunitrange_getindices(a, indices) end +# Fixes ambiguity error with `BlockArrays`. +function Base.getindex(a::GradedUnitRange, indices::BlockRange{1,Tuple{Base.OneTo{Int}}}) + return blockedunitrange_getindices(a, indices) +end + +function Base.getindex(a::GradedUnitRange, indices::BlockIndex{1}) + return blockedunitrange_getindices(a, indices) +end + function Base.getindex(a::GradedUnitRange, indices) return blockedunitrange_getindices(a, indices) end diff --git a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl index 8572636e1b..2a5f20f3e1 100644 --- a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl +++ b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl @@ -34,6 +34,16 @@ function Base.promote_rule(type1::Type{<:LabelledInteger}, type2::Type{<:Number} return promote_type(unlabel_type(type1), type2) end +# Used by `Base.hash(::Integer)`. +# TODO: Define `labelled_trailing_zeros` to be used by other +# labelled number types. +Base.trailing_zeros(x::LabelledInteger) = trailing_zeros(unlabel(x)) + +# Used by `Base.hash(::Integer)`. +# TODO: Define `labelled_righ_bit_shift` to be used by other +# labelled number types. +Base.:>>(x::LabelledInteger, y::Int) = >>(unlabel(x), y) + Base.:(==)(x::LabelledInteger, y::LabelledInteger) = labelled_isequal(x, y) Base.:<(x::LabelledInteger, y::LabelledInteger) = labelled_isless(x, y) # TODO: Define `labelled_colon`. diff --git a/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_basics.jl b/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_basics.jl index 9d9672a16e..1ee0a45fbe 100644 --- a/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_basics.jl +++ b/NDTensors/src/lib/TensorAlgebra/ext/TensorAlgebraGradedAxesExt/test/test_basics.jl @@ -1,24 +1,27 @@ @eval module $(gensym()) using BlockArrays: Block using NDTensors.TensorAlgebra: ⊗ -using NDTensors.GradedAxes: GradedAxes, gradedrange, sector +using NDTensors.GradedAxes: GradedAxes, gradedrange, label using Test: @test, @testset struct U1 dim::Int end Base.isless(l1::U1, l2::U1) = isless(l1.dim, l2.dim) -GradedAxes.fuse(l1::U1, l2::U1) = U1(l1.dim + l2.dim) -GradedAxes.dual(l::U1) = U1(-l.dim) +GradedAxes.fuse_labels(l1::U1, l2::U1) = U1(l1.dim + l2.dim) + +## TODO: This should need to get implemented, but `dual` +## isn't being used right now in `GradedAxes`. +## GradedAxes.dual(l::U1) = U1(-l.dim) @testset "TensorAlgebraGradedAxesExt" begin a1 = gradedrange([U1(0) => 2, U1(1) => 3]) a2 = gradedrange([U1(2) => 3, U1(3) => 4]) a = a1 ⊗ a2 - @test sector(a, Block(1)) == U1(2) - @test sector(a, Block(2)) == U1(3) - @test sector(a, Block(3)) == U1(3) - @test sector(a, Block(4)) == U1(4) + @test label(a[Block(1)]) == U1(2) + @test label(a[Block(2)]) == U1(3) + @test label(a[Block(3)]) == U1(3) + @test label(a[Block(4)]) == U1(4) @test a[Block(1)] == 1:6 @test a[Block(2)] == 7:15 @test a[Block(3)] == 16:23 From ca201fbc064ec67311a6ab701a93a7c8ec3d2117 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Sun, 17 Mar 2024 20:01:13 -0400 Subject: [PATCH 4/5] Fix for Julia 1.6 --- .../src/lib/LabelledNumbers/src/labelled_interface.jl | 7 +++++++ NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl | 8 +++++++- NDTensors/src/lib/LabelledNumbers/test/runtests.jl | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl b/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl index ce745226a5..084da62f4c 100644 --- a/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl +++ b/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl @@ -25,9 +25,16 @@ unlabel_type(object) = typeof(unlabel(object)) labelled_mul(x, y) = labelled_mul(LabelledStyle(x), x, LabelledStyle(y), y) labelled_mul(::IsLabelled, x, ::IsLabelled, y) = unlabel(x) * unlabel(y) +# TODO: Define in terms of `set_value`? labelled_mul(::IsLabelled, x, ::NotLabelled, y) = labelled(unlabel(x) * y, label(x)) +# TODO: Define in terms of `set_value`? labelled_mul(::NotLabelled, x, ::IsLabelled, y) = labelled(x * unlabel(y), label(y)) +# TODO: This is only needed for older Julia versions, like Julia 1.6. +# Delete once we drop support for older Julia versions. +# TODO: Define in terms of `set_value`? +labelled_minus(x) = labelled(-unlabel(x), label(x)) + for (f, labelled_f) in [(:div, :labelled_div), (:/, :labelled_division)] @eval begin $labelled_f(x, y) = $labelled_f(LabelledStyle(x), x, LabelledStyle(y), y) diff --git a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl index 2a5f20f3e1..febf1d6aa6 100644 --- a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl +++ b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl @@ -40,7 +40,7 @@ end Base.trailing_zeros(x::LabelledInteger) = trailing_zeros(unlabel(x)) # Used by `Base.hash(::Integer)`. -# TODO: Define `labelled_righ_bit_shift` to be used by other +# TODO: Define `labelled_right_bit_shift` to be used by other # labelled number types. Base.:>>(x::LabelledInteger, y::Int) = >>(unlabel(x), y) @@ -63,3 +63,9 @@ Base.:*(x::Integer, y::LabelledInteger) = labelled_mul(x, y) Base.:/(x::LabelledInteger, y::Number) = labelled_division(x, y) Base.div(x::LabelledInteger, y::Number) = labelled_div(x, y) + +# TODO: This is only needed for older Julia versions, like Julia 1.6. +# Delete once we drop support for older Julia versions. +# TODO: Define in terms of a generic `labelled_minus` function. +# TODO: Define in terms of `set_value`? +Base.:-(x::LabelledInteger) = labelled_minus(x) diff --git a/NDTensors/src/lib/LabelledNumbers/test/runtests.jl b/NDTensors/src/lib/LabelledNumbers/test/runtests.jl index c59597a9dc..aa37a26ef3 100644 --- a/NDTensors/src/lib/LabelledNumbers/test/runtests.jl +++ b/NDTensors/src/lib/LabelledNumbers/test/runtests.jl @@ -17,6 +17,7 @@ using Test: @test, @testset @test label(x / 2) == "x" @test x ÷ 2 == 1 @test label(x ÷ 2) == "x" + @test -x == -2 end @testset "Labelled array ($a)" for a in (collect(2:5), 2:5) x = labelled(a, "x") From 790f85355966a7a5adc9a977e5b61ba935e0a9d6 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Sun, 17 Mar 2024 20:50:37 -0400 Subject: [PATCH 5/5] More Julia 1.6 fixes --- .../src/lib/LabelledNumbers/src/labelled_interface.jl | 4 ++++ .../src/lib/LabelledNumbers/src/labelledinteger.jl | 4 ++++ .../src/lib/LabelledNumbers/src/labellednumber.jl | 10 ++++++++++ NDTensors/src/lib/LabelledNumbers/test/runtests.jl | 1 + 4 files changed, 19 insertions(+) diff --git a/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl b/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl index 084da62f4c..d58fbaed6f 100644 --- a/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl +++ b/NDTensors/src/lib/LabelledNumbers/src/labelled_interface.jl @@ -35,6 +35,10 @@ labelled_mul(::NotLabelled, x, ::IsLabelled, y) = labelled(x * unlabel(y), label # TODO: Define in terms of `set_value`? labelled_minus(x) = labelled(-unlabel(x), label(x)) +# TODO: This is only needed for older Julia versions, like Julia 1.6. +# Delete once we drop support for older Julia versions. +labelled_hash(x, h::UInt64) = hash(unlabel(x), h) + for (f, labelled_f) in [(:div, :labelled_div), (:/, :labelled_division)] @eval begin $labelled_f(x, y) = $labelled_f(LabelledStyle(x), x, LabelledStyle(y), y) diff --git a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl index febf1d6aa6..0923970e68 100644 --- a/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl +++ b/NDTensors/src/lib/LabelledNumbers/src/labelledinteger.jl @@ -69,3 +69,7 @@ Base.div(x::LabelledInteger, y::Number) = labelled_div(x, y) # TODO: Define in terms of a generic `labelled_minus` function. # TODO: Define in terms of `set_value`? Base.:-(x::LabelledInteger) = labelled_minus(x) + +# TODO: This is only needed for older Julia versions, like Julia 1.6. +# Delete once we drop support for older Julia versions. +Base.hash(x::LabelledInteger, h::UInt64) = labelled_hash(x, h) diff --git a/NDTensors/src/lib/LabelledNumbers/src/labellednumber.jl b/NDTensors/src/lib/LabelledNumbers/src/labellednumber.jl index c0e928ae40..ad9c082bc2 100644 --- a/NDTensors/src/lib/LabelledNumbers/src/labellednumber.jl +++ b/NDTensors/src/lib/LabelledNumbers/src/labellednumber.jl @@ -38,3 +38,13 @@ Base.:*(x::Number, y::LabelledNumber) = labelled_mul(x, y) Base.:/(x::LabelledNumber, y::Number) = labelled_division(x, y) Base.div(x::LabelledNumber, y::Number) = labelled_div(x, y) + +# TODO: This is only needed for older Julia versions, like Julia 1.6. +# Delete once we drop support for older Julia versions. +# TODO: Define in terms of a generic `labelled_minus` function. +# TODO: Define in terms of `set_value`? +Base.:-(x::LabelledNumber) = labelled_minus(x) + +# TODO: This is only needed for older Julia versions, like Julia 1.6. +# Delete once we drop support for older Julia versions. +Base.hash(x::LabelledNumber, h::UInt64) = labelled_hash(x, h) diff --git a/NDTensors/src/lib/LabelledNumbers/test/runtests.jl b/NDTensors/src/lib/LabelledNumbers/test/runtests.jl index aa37a26ef3..57b234c1ea 100644 --- a/NDTensors/src/lib/LabelledNumbers/test/runtests.jl +++ b/NDTensors/src/lib/LabelledNumbers/test/runtests.jl @@ -18,6 +18,7 @@ using Test: @test, @testset @test x ÷ 2 == 1 @test label(x ÷ 2) == "x" @test -x == -2 + @test hash(x) == hash(2) end @testset "Labelled array ($a)" for a in (collect(2:5), 2:5) x = labelled(a, "x")