diff --git a/Project.toml b/Project.toml index c80be7b..d171c5b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "BlockSparseArrays" uuid = "2c9a651f-6452-4ace-a6ac-809f4280fbb4" authors = ["ITensor developers and contributors"] -version = "0.3.9" +version = "0.4.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" @@ -12,7 +12,6 @@ DiagonalArrays = "74fd4be6-21e2-4f6f-823a-4360d37c7a77" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" -GradedUnitRanges = "e2de450a-8a67-46c7-b59c-01d5a3d041c5" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" MapBroadcast = "ebd9b9da-f48d-417c-9660-449667d60261" @@ -22,11 +21,9 @@ TypeParameterAccessors = "7e5a90cf-f82e-492e-a09b-e3e26432c138" [weakdeps] TensorAlgebra = "68bd88dc-f39d-4e12-b2ca-f046b68fcc6a" -TensorProducts = "decf83d6-1968-43f4-96dc-fdb3fe15fc6d" [extensions] -BlockSparseArraysGradedUnitRangesExt = "GradedUnitRanges" -BlockSparseArraysTensorAlgebraExt = ["TensorProducts", "TensorAlgebra"] +BlockSparseArraysTensorAlgebraExt = "TensorAlgebra" [compat] Adapt = "4.1.1" @@ -38,14 +35,12 @@ DiagonalArrays = "0.3" Dictionaries = "0.4.3" FillArrays = "1.13.0" GPUArraysCore = "0.1.0, 0.2" -GradedUnitRanges = "0.2.2" LinearAlgebra = "1.10" MacroTools = "0.5.13" MapBroadcast = "0.1.5" SparseArraysBase = "0.5" SplitApplyCombine = "1.2.3" TensorAlgebra = "0.2.4" -TensorProducts = "0.1.2" Test = "1.10" TypeParameterAccessors = "0.2.0, 0.3" julia = "1.10" diff --git a/docs/Project.toml b/docs/Project.toml index 1c7114e..a5c0d2e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,6 +6,6 @@ Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" [compat] BlockArrays = "1" -BlockSparseArrays = "0.3" +BlockSparseArrays = "0.4" Documenter = "1" Literate = "2" diff --git a/examples/Project.toml b/examples/Project.toml index cf876f1..a847c67 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -5,5 +5,5 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] BlockArrays = "1" -BlockSparseArrays = "0.3" +BlockSparseArrays = "0.4" Test = "1" diff --git a/ext/BlockSparseArraysGradedUnitRangesExt/BlockSparseArraysGradedUnitRangesExt.jl b/ext/BlockSparseArraysGradedUnitRangesExt/BlockSparseArraysGradedUnitRangesExt.jl deleted file mode 100644 index 49762ed..0000000 --- a/ext/BlockSparseArraysGradedUnitRangesExt/BlockSparseArraysGradedUnitRangesExt.jl +++ /dev/null @@ -1,105 +0,0 @@ -module BlockSparseArraysGradedUnitRangesExt - -using BlockSparseArrays: AnyAbstractBlockSparseArray, BlockSparseArray, blocktype -using GradedUnitRanges: AbstractGradedUnitRange -using TypeParameterAccessors: similartype, unwrap_array_type - -# A block spare array similar to the input (dense) array. -# TODO: Make `BlockSparseArrays.blocksparse_similar` more general and use that, -# and also turn it into an DerivableInterfaces.jl-based interface function. -function similar_blocksparse( - a::AbstractArray, - elt::Type, - axes::Tuple{AbstractGradedUnitRange,Vararg{AbstractGradedUnitRange}}, -) - # TODO: Probably need to unwrap the type of `a` in certain cases - # to make a proper block type. - return BlockSparseArray{ - elt,length(axes),similartype(unwrap_array_type(blocktype(a)), elt, axes) - }( - undef, axes - ) -end - -function Base.similar( - a::AbstractArray, - elt::Type, - axes::Tuple{AbstractGradedUnitRange,Vararg{AbstractGradedUnitRange}}, -) - return similar_blocksparse(a, elt, axes) -end - -# Fix ambiguity error with `BlockArrays.jl`. -function Base.similar( - a::StridedArray, - elt::Type, - axes::Tuple{AbstractGradedUnitRange,Vararg{AbstractGradedUnitRange}}, -) - return similar_blocksparse(a, elt, axes) -end - -# Fix ambiguity error with `BlockArrays.jl`. -function Base.similar( - a::StridedArray, - elt::Type, - axes::Tuple{ - AbstractGradedUnitRange,AbstractGradedUnitRange,Vararg{AbstractGradedUnitRange} - }, -) - return similar_blocksparse(a, elt, axes) -end - -# Fix ambiguity error with `BlockSparseArrays.jl`. -function Base.similar( - a::AnyAbstractBlockSparseArray, - elt::Type, - axes::Tuple{AbstractGradedUnitRange,Vararg{AbstractGradedUnitRange}}, -) - return similar_blocksparse(a, elt, axes) -end -function Base.similar( - a::AnyAbstractBlockSparseArray, - elt::Type, - axes::Tuple{ - AbstractGradedUnitRange,AbstractGradedUnitRange,Vararg{AbstractGradedUnitRange} - }, -) - return similar_blocksparse(a, elt, axes) -end - -function getindex_blocksparse(a::AbstractArray, I::AbstractUnitRange...) - a′ = similar(a, only.(axes.(I))...) - a′ .= a - return a′ -end - -function Base.getindex( - a::AbstractArray, I1::AbstractGradedUnitRange, I_rest::AbstractGradedUnitRange... -) - return getindex_blocksparse(a, I1, I_rest...) -end - -# Fix ambiguity error with Base. -function Base.getindex(a::Vector, I::AbstractGradedUnitRange) - return getindex_blocksparse(a, I) -end - -# Fix ambiguity error with BlockSparseArrays.jl. -function Base.getindex( - a::AnyAbstractBlockSparseArray, - I1::AbstractGradedUnitRange, - I_rest::AbstractGradedUnitRange..., -) - return getindex_blocksparse(a, I1, I_rest...) -end - -# Fix ambiguity error with BlockSparseArrays.jl. -function Base.getindex( - a::AnyAbstractBlockSparseArray{<:Any,2}, - I1::AbstractGradedUnitRange, - I2::AbstractGradedUnitRange, -) - return getindex_blocksparse(a, I1, I2) -end - -end diff --git a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl index c8fc4df..9de224f 100644 --- a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl +++ b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl @@ -1,10 +1,8 @@ module BlockSparseArraysTensorAlgebraExt -using BlockArrays: AbstractBlockedUnitRange - -using TensorAlgebra: TensorAlgebra, FusionStyle, BlockReshapeFusion -using TensorProducts: OneToOne +using BlockArrays: AbstractBlockedUnitRange using BlockSparseArrays: AbstractBlockSparseArray, blockreshape +using TensorAlgebra: TensorAlgebra, FusionStyle, BlockReshapeFusion TensorAlgebra.FusionStyle(::AbstractBlockedUnitRange) = BlockReshapeFusion() @@ -20,99 +18,4 @@ function TensorAlgebra.splitdims( return blockreshape(a, axes) end -using BlockArrays: - AbstractBlockVector, - AbstractBlockedUnitRange, - Block, - BlockIndexRange, - blockedrange, - blocks -using BlockSparseArrays: - BlockSparseArrays, - AbstractBlockSparseArray, - AbstractBlockSparseArrayInterface, - AbstractBlockSparseMatrix, - BlockSparseArray, - BlockSparseArrayInterface, - BlockSparseMatrix, - BlockSparseVector, - block_merge -using DerivableInterfaces: @interface -using GradedUnitRanges: - GradedUnitRanges, - AbstractGradedUnitRange, - blockmergesortperm, - blocksortperm, - dual, - invblockperm, - nondual, - unmerged_tensor_product -using LinearAlgebra: Adjoint, Transpose -using TensorAlgebra: - TensorAlgebra, FusionStyle, BlockReshapeFusion, SectorFusion, fusedims, splitdims - -# TODO: Make a `ReduceWhile` library. -include("reducewhile.jl") - -TensorAlgebra.FusionStyle(::AbstractGradedUnitRange) = SectorFusion() - -# TODO: Need to implement this! Will require implementing -# `block_merge(a::AbstractUnitRange, blockmerger::BlockedUnitRange)`. -function BlockSparseArrays.block_merge( - a::AbstractGradedUnitRange, blockmerger::AbstractBlockedUnitRange -) - return a -end - -# Sort the blocks by sector and then merge the common sectors. -function block_mergesort(a::AbstractArray) - I = blockmergesortperm.(axes(a)) - return a[I...] -end - -function TensorAlgebra.fusedims( - ::SectorFusion, a::AbstractArray, merged_axes::AbstractUnitRange... -) - # First perform a fusion using a block reshape. - # TODO avoid groupreducewhile. Require refactor of fusedims. - unmerged_axes = groupreducewhile( - unmerged_tensor_product, axes(a), length(merged_axes); init=OneToOne() - ) do i, axis - return length(axis) ≤ length(merged_axes[i]) - end - - a_reshaped = fusedims(BlockReshapeFusion(), a, unmerged_axes...) - # Sort the blocks by sector and merge the equivalent sectors. - return block_mergesort(a_reshaped) -end - -function TensorAlgebra.splitdims( - ::SectorFusion, a::AbstractArray, split_axes::AbstractUnitRange... -) - # First, fuse axes to get `blockmergesortperm`. - # Then unpermute the blocks. - axes_prod = groupreducewhile( - unmerged_tensor_product, split_axes, ndims(a); init=OneToOne() - ) do i, axis - return length(axis) ≤ length(axes(a, i)) - end - blockperms = blocksortperm.(axes_prod) - sorted_axes = map((r, I) -> only(axes(r[I])), axes_prod, blockperms) - - # TODO: This is doing extra copies of the blocks, - # use `@view a[axes_prod...]` instead. - # That will require implementing some reindexing logic - # for this combination of slicing. - a_unblocked = a[sorted_axes...] - a_blockpermed = a_unblocked[invblockperm.(blockperms)...] - return splitdims(BlockReshapeFusion(), a_blockpermed, split_axes...) -end - -# TODO: Handle this through some kind of trait dispatch, maybe -# a `SymmetryStyle`-like trait to check if the block sparse -# matrix has graded axes. -function Base.axes(a::Adjoint{<:Any,<:AbstractBlockSparseMatrix}) - return dual.(reverse(axes(a'))) -end - end diff --git a/ext/BlockSparseArraysTensorAlgebraExt/reducewhile.jl b/ext/BlockSparseArraysTensorAlgebraExt/reducewhile.jl deleted file mode 100644 index 5eb42b1..0000000 --- a/ext/BlockSparseArraysTensorAlgebraExt/reducewhile.jl +++ /dev/null @@ -1,34 +0,0 @@ -#= - reducewhile(f, op, collection, state) - -reducewhile(x -> length(x) < 3, vcat, ["a", "b", "c", "d"], 2; init=String[]) == - (["b", "c"], 4) -=# -function reducewhile(f, op, collection, state; init) - prev_result = init - prev_state = state - result = prev_result - while f(result) - prev_result = result - prev_state = state - value_and_state = iterate(collection, state) - isnothing(value_and_state) && break - value, state = value_and_state - result = op(result, value) - end - return prev_result, prev_state -end - -#= - groupreducewhile(f, op, collection, ngroups) - -groupreducewhile((i, x) -> length(x) ≤ i, vcat, ["a", "b", "c", "d", "e", "f"], 3; init=String[]) == - (["a"], ["b", "c"], ["d", "e", "f"]) -=# -function groupreducewhile(f, op, collection, ngroups; init) - state = firstindex(collection) - return ntuple(ngroups) do group_number - result, state = reducewhile(x -> f(group_number, x), op, collection, state; init) - return result - end -end diff --git a/src/BlockArraysExtensions/BlockArraysExtensions.jl b/src/BlockArraysExtensions/BlockArraysExtensions.jl index e0a3c7a..535bf52 100644 --- a/src/BlockArraysExtensions/BlockArraysExtensions.jl +++ b/src/BlockArraysExtensions/BlockArraysExtensions.jl @@ -20,7 +20,6 @@ using BlockArrays: findblock, findblockindex using Dictionaries: Dictionary, Indices -using GradedUnitRanges: blockedunitrange_getindices, to_blockindices using SparseArraysBase: SparseArraysBase, eachstoredindex, diff --git a/src/BlockArraysExtensions/blockedunitrange.jl b/src/BlockArraysExtensions/blockedunitrange.jl new file mode 100644 index 0000000..b6edef2 --- /dev/null +++ b/src/BlockArraysExtensions/blockedunitrange.jl @@ -0,0 +1,184 @@ +using BlockArrays: + BlockArrays, + AbstractBlockedUnitRange, + AbstractBlockVector, + Block, + BlockIndex, + BlockIndexRange, + BlockRange, + BlockSlice, + BlockVector, + block, + blockindex, + findblock, + findblockindex, + mortar + +# 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 + +# TODO: Move this to a `BlockArraysExtensions` library. +# TODO: Rename this. `BlockArrays.findblock(a, k)` finds the +# block of the value `k`, while this finds the block of the index `k`. +# This could make use of the `BlockIndices` object, i.e. `block(BlockIndices(a)[index])`. +function blockedunitrange_findblock(a::AbstractBlockedUnitRange, index::Integer) + @boundscheck index in 1:length(a) || throw(BoundsError(a, index)) + return @inbounds findblock(a, index + first(a) - 1) +end + +# TODO: Move this to a `BlockArraysExtensions` library. +# TODO: Rename this. `BlockArrays.findblockindex(a, k)` finds the +# block index of the value `k`, while this finds the block index of the index `k`. +# This could make use of the `BlockIndices` object, i.e. `BlockIndices(a)[index]`. +function blockedunitrange_findblockindex(a::AbstractBlockedUnitRange, index::Integer) + @boundscheck index in 1:length(a) || throw(BoundsError()) + return @inbounds findblockindex(a, index + first(a) - 1) +end + +function blockedunitrange_getindices(a::AbstractUnitRange, indices) + return a[indices] +end + +# TODO: Move this to a `BlockArraysExtensions` library. +# Like `a[indices]` but preserves block structure. +# TODO: Consider calling this something else, for example +# `blocked_getindex`. See the discussion here: +# https://github.com/JuliaArrays/BlockArrays.jl/issues/347 +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, 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 + +# TODO: Make sure this handles block labels (AbstractGradedUnitRange) correctly. +# TODO: Make a special case for `BlockedVector{<:Block{1},<:BlockRange{1}}`? +# For example: +# ```julia +# blocklengths = map(bs -> sum(b -> length(a[b]), bs), blocks(indices)) +# return blockedrange(blocklengths) +# ``` +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, indices::AbstractBlockVector{<:Block{1}} +) + blks = map(bs -> mortar(map(b -> a[b], bs)), blocks(indices)) + # We pass `length.(blks)` to `mortar` in order + # to pass block labels to the axes of the output, + # if they exist. This makes it so that + # `only(axes(a[indices])) isa `GradedUnitRange` + # if `a isa `GradedUnitRange`, for example. + # Note there is a more specialized definition: + # ```julia + # function blockedunitrange_getindices( + # a::AbstractGradedUnitRange, indices::AbstractBlockVector{<:Block{1}} + # ) + # ``` + # that does a better job of preserving labels, since `length` + # may drop labels for certain block types. + return mortar(blks, length.(blks)) +end + +# TODO: Move this to a `BlockArraysExtensions` library. +function blockedunitrange_getindices(a::AbstractBlockedUnitRange, indices::BlockIndexRange) + return a[block(indices)][only(indices.indices)] +end + +# TODO: Move this to a `BlockArraysExtensions` library. +function blockedunitrange_getindices(a::AbstractBlockedUnitRange, indices::BlockSlice) + # TODO: Is this a good definition? It ignores `indices.indices`. + return a[indices.block] +end + +# TODO: Move this to a `BlockArraysExtensions` library. +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, indices::Vector{<:Integer} +) + return map(index -> a[index], indices) +end + +# TODO: Move this to a `BlockArraysExtensions` library. +# TODO: Make a special definition for `BlockedVector{<:Block{1}}` in order +# to merge blocks. +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, indices::AbstractVector{<:Union{Block{1},BlockIndexRange{1}}} +) + # Without converting `indices` to `Vector`, + # mapping `indices` outputs a `BlockVector` + # which is harder to reason about. + blocks = map(index -> a[index], Vector(indices)) + # We pass `length.(blocks)` to `mortar` in order + # to pass block labels to the axes of the output, + # if they exist. This makes it so that + # `only(axes(a[indices])) isa `GradedUnitRange` + # if `a isa `GradedUnitRange`, for example. + return mortar(blocks, length.(blocks)) +end + +# TODO: Move this to a `BlockArraysExtensions` library. +function blockedunitrange_getindices(a::AbstractBlockedUnitRange, indices::Block{1}) + return a[indices] +end + +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, + indices::BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexRange{1}}}, +) + return mortar(map(b -> a[b], blocks(indices))) +end + +# TODO: Move this to a `BlockArraysExtensions` library. +function blockedunitrange_getindices(a::AbstractBlockedUnitRange, 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 + +# Slice `a` by `I`, returning a: +# `BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexRange{1}}}` +# with the `BlockIndex{1}` corresponding to each value of `I`. +function to_blockindices(a::AbstractBlockedUnitRange{<:Integer}, I::UnitRange{<:Integer}) + return mortar( + map(blocks(blockedunitrange_getindices(a, I))) do r + bi_first = findblockindex(a, first(r)) + bi_last = findblockindex(a, last(r)) + @assert block(bi_first) == block(bi_last) + return block(bi_first)[blockindex(bi_first):blockindex(bi_last)] + end, + ) +end + +# This handles non-blocked slices. +# For example: +# a = BlockSparseArray{Float64}([2, 2, 2, 2]) +# I = BlockedVector(Block.(1:4), [2, 2]) +# @views a[I][Block(1)] +to_blockindices(a::Base.OneTo{<:Integer}, I::UnitRange{<:Integer}) = I diff --git a/src/BlockSparseArrays.jl b/src/BlockSparseArrays.jl index a350dff..ff2ed6e 100644 --- a/src/BlockSparseArrays.jl +++ b/src/BlockSparseArrays.jl @@ -12,6 +12,7 @@ export BlockSparseArray, include("factorizations/svd.jl") # possible upstream contributions +include("BlockArraysExtensions/blockedunitrange.jl") include("BlockArraysExtensions/BlockArraysExtensions.jl") # interface functions that don't have to specialize diff --git a/test/Project.toml b/test/Project.toml index ffc9aee..6e5ad33 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -26,7 +26,7 @@ Adapt = "4" Aqua = "0.8" ArrayLayouts = "1" BlockArrays = "1" -BlockSparseArrays = "0.3" +BlockSparseArrays = "0.4" DiagonalArrays = "0.3" GPUArraysCore = "0.2" GradedUnitRanges = "0.2.2" diff --git a/test/test_gradedunitrangesext.jl b/test/test_gradedunitrangesext.jl deleted file mode 100644 index 06a9a8c..0000000 --- a/test/test_gradedunitrangesext.jl +++ /dev/null @@ -1,405 +0,0 @@ -using Test: @test, @testset -using BlockArrays: - AbstractBlockArray, Block, BlockedOneTo, blockedrange, blocklengths, blocksize -using BlockSparseArrays: - BlockSparseArray, BlockSparseMatrix, BlockSparseVector, blockstoredlength -using GradedUnitRanges: - GradedUnitRanges, - GradedOneTo, - GradedUnitRange, - GradedUnitRangeDual, - blocklabels, - dag, - dual, - gradedrange, - isdual -using LabelledNumbers: label -using SparseArraysBase: storedlength -using SymmetrySectors: U1 -using TensorAlgebra: fusedims, splitdims -using LinearAlgebra: adjoint -using Random: randn! - -function randn_blockdiagonal(elt::Type, axes::Tuple) - a = BlockSparseArray{elt}(undef, axes) - blockdiaglength = minimum(blocksize(a)) - for i in 1:blockdiaglength - b = Block(ntuple(Returns(i), ndims(a))) - a[b] = randn!(a[b]) - end - return a -end - -const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) -@testset "BlockSparseArraysGradedUnitRangesExt (eltype=$elt)" for elt in elts - @testset "map" begin - d1 = gradedrange([U1(0) => 2, U1(1) => 2]) - d2 = gradedrange([U1(0) => 2, U1(1) => 2]) - a = randn_blockdiagonal(elt, (d1, d2, d1, d2)) - @test axes(a, 1) isa GradedOneTo - @test axes(view(a, 1:4, 1:4, 1:4, 1:4), 1) isa GradedOneTo - - d1 = gradedrange([U1(0) => 2, U1(1) => 2]) - d2 = gradedrange([U1(0) => 2, U1(1) => 2]) - a = randn_blockdiagonal(elt, (d1, d2, d1, d2)) - for b in (a + a, 2 * a) - @test size(b) == (4, 4, 4, 4) - @test blocksize(b) == (2, 2, 2, 2) - @test blocklengths.(axes(b)) == ([2, 2], [2, 2], [2, 2], [2, 2]) - @test storedlength(b) == 32 - @test blockstoredlength(b) == 2 - for i in 1:ndims(a) - @test axes(b, i) isa GradedOneTo - end - @test label(axes(b, 1)[Block(1)]) == U1(0) - @test label(axes(b, 1)[Block(2)]) == U1(1) - @test Array(b) isa Array{elt} - @test Array(b) == b - @test 2 * Array(a) == b - end - - d1 = gradedrange([U1(0) => 2, U1(1) => 2]) - d2 = gradedrange([U1(0) => 2, U1(1) => 2]) - a = randn_blockdiagonal(elt, (d1, d2, d1, d2)) - b = similar(a, ComplexF64) - @test b isa BlockSparseArray{ComplexF64} - @test eltype(b) === ComplexF64 - - a = BlockSparseVector{Float64}(undef, gradedrange([U1(0) => 1, U1(1) => 1])) - b = similar(a, Float32) - @test b isa BlockSparseVector{Float32} - @test eltype(b) == Float32 - - # Test mixing graded axes and dense axes - # in addition/broadcasting. - d1 = gradedrange([U1(0) => 2, U1(1) => 2]) - d2 = gradedrange([U1(0) => 2, U1(1) => 2]) - a = randn_blockdiagonal(elt, (d1, d2, d1, d2)) - for b in (a + Array(a), Array(a) + a) - @test size(b) == (4, 4, 4, 4) - @test blocksize(b) == (2, 2, 2, 2) - @test blocklengths.(axes(b)) == ([2, 2], [2, 2], [2, 2], [2, 2]) - @test storedlength(b) == 256 - @test blockstoredlength(b) == 16 - for i in 1:ndims(a) - @test axes(b, i) isa BlockedOneTo{Int} - end - @test Array(a) isa Array{elt} - @test Array(a) == a - @test 2 * Array(a) == b - end - - d1 = gradedrange([U1(0) => 2, U1(1) => 2]) - d2 = gradedrange([U1(0) => 2, U1(1) => 2]) - a = randn_blockdiagonal(elt, (d1, d2, d1, d2)) - b = a[2:3, 2:3, 2:3, 2:3] - @test size(b) == (2, 2, 2, 2) - @test blocksize(b) == (2, 2, 2, 2) - @test storedlength(b) == 2 - @test blockstoredlength(b) == 2 - for i in 1:ndims(a) - @test axes(b, i) isa GradedOneTo - end - @test label(axes(b, 1)[Block(1)]) == U1(0) - @test label(axes(b, 1)[Block(2)]) == U1(1) - @test Array(a) isa Array{elt} - @test Array(a) == a - end - # TODO: Add tests for various slicing operations. - @testset "fusedims" begin - d1 = gradedrange([U1(0) => 1, U1(1) => 1]) - d2 = gradedrange([U1(0) => 1, U1(1) => 1]) - a = randn_blockdiagonal(elt, (d1, d2, d1, d2)) - m = fusedims(a, (1, 2), (3, 4)) - for ax in axes(m) - @test ax isa GradedOneTo - @test blocklabels(ax) == [U1(0), U1(1), U1(2)] - end - for I in CartesianIndices(m) - if I ∈ CartesianIndex.([(1, 1), (4, 4)]) - @test !iszero(m[I]) - else - @test iszero(m[I]) - end - end - @test a[1, 1, 1, 1] == m[1, 1] - @test a[2, 2, 2, 2] == m[4, 4] - @test blocksize(m) == (3, 3) - @test a == splitdims(m, (d1, d2), (d1, d2)) - - # check block fusing and splitting - d = gradedrange([U1(0) => 2, U1(1) => 1]) - a = randn_blockdiagonal(elt, (d, d, dual(d), dual(d))) - @test splitdims(fusedims(a, (1, 2), (3, 4)), axes(a)...) == a - end - - @testset "dual axes" begin - r = gradedrange([U1(0) => 2, U1(1) => 2]) - for ax in ((r, r), (dual(r), r), (r, dual(r)), (dual(r), dual(r))) - a = BlockSparseArray{elt}(undef, ax...) - @views for b in [Block(1, 1), Block(2, 2)] - a[b] = randn(elt, size(a[b])) - end - for dim in 1:ndims(a) - @test typeof(ax[dim]) === typeof(axes(a, dim)) - @test isdual(ax[dim]) == isdual(axes(a, dim)) - end - @test @view(a[Block(1, 1)])[1, 1] == a[1, 1] - @test @view(a[Block(1, 1)])[2, 1] == a[2, 1] - @test @view(a[Block(1, 1)])[1, 2] == a[1, 2] - @test @view(a[Block(1, 1)])[2, 2] == a[2, 2] - @test @view(a[Block(2, 2)])[1, 1] == a[3, 3] - @test @view(a[Block(2, 2)])[2, 1] == a[4, 3] - @test @view(a[Block(2, 2)])[1, 2] == a[3, 4] - @test @view(a[Block(2, 2)])[2, 2] == a[4, 4] - @test @view(a[Block(1, 1)])[1:2, 1:2] == a[1:2, 1:2] - @test @view(a[Block(2, 2)])[1:2, 1:2] == a[3:4, 3:4] - a_dense = Array(a) - @test eachindex(a) == CartesianIndices(size(a)) - for I in eachindex(a) - @test a[I] == a_dense[I] - end - @test axes(a') == dual.(reverse(axes(a))) - - @test isdual(axes(a', 1)) ≠ isdual(axes(a, 2)) - @test isdual(axes(a', 2)) ≠ isdual(axes(a, 1)) - @test isnothing(show(devnull, MIME("text/plain"), a)) - - # Check preserving dual in tensor algebra. - for b in (a + a, 2 * a, 3 * a - a) - @test Array(b) ≈ 2 * Array(a) - for dim in 1:ndims(a) - @test isdual(axes(b, dim)) == isdual(axes(a, dim)) - end - end - - @test isnothing(show(devnull, MIME("text/plain"), @view(a[Block(1, 1)]))) - @test @view(a[Block(1, 1)]) == a[Block(1, 1)] - end - - @testset "GradedOneTo" begin - r = gradedrange([U1(0) => 2, U1(1) => 2]) - a = BlockSparseArray{elt}(undef, r, r) - @views for i in [Block(1, 1), Block(2, 2)] - a[i] = randn(elt, size(a[i])) - end - b = 2 * a - @test blockstoredlength(b) == 2 - @test Array(b) == 2 * Array(a) - for i in 1:2 - @test axes(b, i) isa GradedOneTo - @test axes(a[:, :], i) isa GradedOneTo - end - - I = [Block(1)[1:1]] - @test a[I, :] isa AbstractBlockArray - @test a[:, I] isa AbstractBlockArray - @test size(a[I, I]) == (1, 1) - @test !isdual(axes(a[I, I], 1)) - end - - @testset "GradedUnitRange" begin - r = gradedrange([U1(0) => 2, U1(1) => 2])[1:3] - a = BlockSparseArray{elt}(undef, r, r) - @views for i in [Block(1, 1), Block(2, 2)] - a[i] = randn(elt, size(a[i])) - end - b = 2 * a - @test blockstoredlength(b) == 2 - @test Array(b) == 2 * Array(a) - for i in 1:2 - @test axes(b, i) isa GradedUnitRange - @test axes(a[:, :], i) isa GradedUnitRange - end - - I = [Block(1)[1:1]] - @test a[I, :] isa AbstractBlockArray - @test axes(a[I, :], 1) isa GradedOneTo - @test axes(a[I, :], 2) isa GradedUnitRange - - @test a[:, I] isa AbstractBlockArray - @test axes(a[:, I], 2) isa GradedOneTo - @test axes(a[:, I], 1) isa GradedUnitRange - @test size(a[I, I]) == (1, 1) - @test !isdual(axes(a[I, I], 1)) - end - - # Test case when all axes are dual. - @testset "dual GradedOneTo" begin - r = gradedrange([U1(-1) => 2, U1(1) => 2]) - a = BlockSparseArray{elt}(undef, dual(r), dual(r)) - @views for i in [Block(1, 1), Block(2, 2)] - a[i] = randn(elt, size(a[i])) - end - b = 2 * a - @test blockstoredlength(b) == 2 - @test Array(b) == 2 * Array(a) - for i in 1:2 - @test axes(b, i) isa GradedUnitRangeDual - @test axes(a[:, :], i) isa GradedUnitRangeDual - end - I = [Block(1)[1:1]] - @test a[I, :] isa AbstractBlockArray - @test a[:, I] isa AbstractBlockArray - @test size(a[I, I]) == (1, 1) - @test isdual(axes(a[I, :], 2)) - @test isdual(axes(a[:, I], 1)) - @test isdual(axes(a[I, :], 1)) - @test isdual(axes(a[:, I], 2)) - @test isdual(axes(a[I, I], 1)) - @test isdual(axes(a[I, I], 2)) - end - - @testset "dual GradedUnitRange" begin - r = gradedrange([U1(0) => 2, U1(1) => 2])[1:3] - a = BlockSparseArray{elt}(undef, dual(r), dual(r)) - @views for i in [Block(1, 1), Block(2, 2)] - a[i] = randn(elt, size(a[i])) - end - b = 2 * a - @test blockstoredlength(b) == 2 - @test Array(b) == 2 * Array(a) - for i in 1:2 - @test axes(b, i) isa GradedUnitRangeDual - @test axes(a[:, :], i) isa GradedUnitRangeDual - end - - I = [Block(1)[1:1]] - @test a[I, :] isa AbstractBlockArray - @test a[:, I] isa AbstractBlockArray - @test size(a[I, I]) == (1, 1) - @test isdual(axes(a[I, :], 2)) - @test isdual(axes(a[:, I], 1)) - @test isdual(axes(a[I, :], 1)) - @test isdual(axes(a[:, I], 2)) - @test isdual(axes(a[I, I], 1)) - @test isdual(axes(a[I, I], 2)) - end - - @testset "dual BlockedUnitRange" begin # self dual - r = blockedrange([2, 2]) - a = BlockSparseArray{elt}(undef, dual(r), dual(r)) - @views for i in [Block(1, 1), Block(2, 2)] - a[i] = randn(elt, size(a[i])) - end - b = 2 * a - @test blockstoredlength(b) == 2 - @test Array(b) == 2 * Array(a) - @test a[:, :] isa BlockSparseArray - for i in 1:2 - @test axes(b, i) isa BlockedOneTo - @test axes(a[:, :], i) isa BlockedOneTo - end - - I = [Block(1)[1:1]] - @test a[I, :] isa BlockSparseArray - @test a[:, I] isa BlockSparseArray - @test size(a[I, I]) == (1, 1) - @test !isdual(axes(a[I, I], 1)) - end - - # Test case when all axes are dual from taking the adjoint. - for r in ( - gradedrange([U1(0) => 2, U1(1) => 2]), - gradedrange([U1(0) => 2, U1(1) => 2])[begin:end], - ) - a = BlockSparseArray{elt}(undef, r, r) - @views for i in [Block(1, 1), Block(2, 2)] - a[i] = randn(elt, size(a[i])) - end - b = 2 * a' - @test blockstoredlength(b) == 2 - @test Array(b) == 2 * Array(a)' - for ax in axes(b) - @test ax isa typeof(dual(r)) - end - - @test !isdual(axes(a, 1)) - @test !isdual(axes(a, 2)) - @test isdual(axes(a', 1)) - @test isdual(axes(a', 2)) - @test isdual(axes(b, 1)) - @test isdual(axes(b, 2)) - @test isdual(axes(copy(a'), 1)) - @test isdual(axes(copy(a'), 2)) - - I = [Block(1)[1:1]] - @test size(b[I, :]) == (1, 4) - @test size(b[:, I]) == (4, 1) - @test size(b[I, I]) == (1, 1) - end - end - @testset "Matrix multiplication" begin - r = gradedrange([U1(0) => 2, U1(1) => 3]) - a1 = BlockSparseArray{elt}(undef, dual(r), r) - a1[Block(1, 2)] = randn(elt, size(@view(a1[Block(1, 2)]))) - a1[Block(2, 1)] = randn(elt, size(@view(a1[Block(2, 1)]))) - a2 = BlockSparseArray{elt}(undef, dual(r), r) - a2[Block(1, 2)] = randn(elt, size(@view(a2[Block(1, 2)]))) - a2[Block(2, 1)] = randn(elt, size(@view(a2[Block(2, 1)]))) - @test Array(a1 * a2) ≈ Array(a1) * Array(a2) - @test Array(a1' * a2') ≈ Array(a1') * Array(a2') - - a2 = BlockSparseArray{elt}(undef, r, dual(r)) - a2[Block(1, 2)] = randn(elt, size(@view(a2[Block(1, 2)]))) - a2[Block(2, 1)] = randn(elt, size(@view(a2[Block(2, 1)]))) - @test Array(a1' * a2) ≈ Array(a1') * Array(a2) - @test Array(a1 * a2') ≈ Array(a1) * Array(a2') - end - @testset "Construct from dense" begin - r = gradedrange([U1(0) => 2, U1(1) => 3]) - a1 = randn(elt, 2, 2) - a2 = randn(elt, 3, 3) - a = cat(a1, a2; dims=(1, 2)) - b = a[r, dual(r)] - @test eltype(b) === elt - @test b isa BlockSparseMatrix{elt} - @test blockstoredlength(b) == 2 - @test b[Block(1, 1)] == a1 - @test iszero(b[Block(2, 1)]) - @test iszero(b[Block(1, 2)]) - @test b[Block(2, 2)] == a2 - @test all(GradedUnitRanges.space_isequal.(axes(b), (r, dual(r)))) - - # Regression test for Vector, which caused - # an ambiguity error with Base. - r = gradedrange([U1(0) => 2, U1(1) => 3]) - a1 = randn(elt, 2) - a2 = zeros(elt, 3) - a = vcat(a1, a2) - b = a[r] - @test eltype(b) === elt - @test b isa BlockSparseVector{elt} - @test blockstoredlength(b) == 1 - @test b[Block(1)] == a1 - @test iszero(b[Block(2)]) - @test all(GradedUnitRanges.space_isequal.(axes(b), (r,))) - - # Regression test for BitArray - r = gradedrange([U1(0) => 2, U1(1) => 3]) - a1 = trues(2, 2) - a2 = trues(3, 3) - a = cat(a1, a2; dims=(1, 2)) - b = a[r, dual(r)] - @test eltype(b) === Bool - @test b isa BlockSparseMatrix{Bool} - @test blockstoredlength(b) == 2 - @test b[Block(1, 1)] == a1 - @test iszero(b[Block(2, 1)]) - @test iszero(b[Block(1, 2)]) - @test b[Block(2, 2)] == a2 - @test all(GradedUnitRanges.space_isequal.(axes(b), (r, dual(r)))) - end -end - -@testset "dag" begin - elt = ComplexF64 - r = gradedrange([U1(0) => 2, U1(1) => 3]) - a = BlockSparseArray{elt}(undef, r, dual(r)) - a[Block(1, 1)] = randn(elt, 2, 2) - a[Block(2, 2)] = randn(elt, 3, 3) - @test isdual.(axes(a)) == (false, true) - ad = dag(a) - @test Array(ad) == conj(Array(a)) - @test isdual.(axes(ad)) == (true, false) -end diff --git a/test/test_tensoralgebraext.jl b/test/test_tensoralgebraext.jl index 3fe0b86..30b90a2 100644 --- a/test/test_tensoralgebraext.jl +++ b/test/test_tensoralgebraext.jl @@ -1,12 +1,8 @@ -using Random: randn! -using Test: @test, @test_broken, @testset - using BlockArrays: Block, BlockArray, BlockedArray, blockedrange, blocksize - using BlockSparseArrays: BlockSparseArray -using GradedUnitRanges: dual, gradedrange -using SymmetrySectors: U1 +using Random: randn! using TensorAlgebra: contract +using Test: @test, @test_broken, @testset function randn_blockdiagonal(elt::Type, axes::Tuple) a = BlockSparseArray{elt}(undef, axes) @@ -73,58 +69,4 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @test a_dest isa BlockSparseArray @test a_dest ≈ a_dest_dense end - - @testset "GradedOneTo with U(1)" begin - d = gradedrange([U1(0) => 2, U1(1) => 3]) - a1 = randn_blockdiagonal(elt, (d, d, dual(d), dual(d))) - a2 = randn_blockdiagonal(elt, (d, d, dual(d), dual(d))) - a3 = randn_blockdiagonal(elt, (d, dual(d))) - a1_dense = convert(Array, a1) - a2_dense = convert(Array, a2) - a3_dense = convert(Array, a3) - - # matrix matrix - a_dest, dimnames_dest = contract(a1, (1, -1, 2, -2), a2, (2, -3, 1, -4)) - a_dest_dense, dimnames_dest_dense = contract( - a1_dense, (1, -1, 2, -2), a2_dense, (2, -3, 1, -4) - ) - @test dimnames_dest == dimnames_dest_dense - @test size(a_dest) == size(a_dest_dense) - @test a_dest isa BlockSparseArray - @test a_dest ≈ a_dest_dense - - # matrix vector - @test_broken a_dest, dimnames_dest = contract(a1, (2, -1, -2, 1), a3, (1, 2)) - #= - a_dest_dense, dimnames_dest_dense = contract(a1_dense, (2, -1, -2, 1), a3_dense, (1, 2)) - @test dimnames_dest == dimnames_dest_dense - @test size(a_dest) == size(a_dest_dense) - @test a_dest isa BlockSparseArray - @test a_dest ≈ a_dest_dense - =# - - # vector matrix - a_dest, dimnames_dest = contract(a3, (1, 2), a1, (2, -1, -2, 1)) - a_dest_dense, dimnames_dest_dense = contract(a3_dense, (1, 2), a1_dense, (2, -1, -2, 1)) - @test dimnames_dest == dimnames_dest_dense - @test size(a_dest) == size(a_dest_dense) - @test a_dest isa BlockSparseArray - @test a_dest ≈ a_dest_dense - - # vector vector - a_dest, dimnames_dest = contract(a3, (1, 2), a3, (2, 1)) - a_dest_dense, dimnames_dest_dense = contract(a3_dense, (1, 2), a3_dense, (2, 1)) - @test dimnames_dest == dimnames_dest_dense - @test size(a_dest) == size(a_dest_dense) - @test a_dest isa BlockSparseArray{elt,0} - @test a_dest ≈ a_dest_dense - - # outer product - a_dest, dimnames_dest = contract(a3, (1, 2), a3, (3, 4)) - a_dest_dense, dimnames_dest_dense = contract(a3_dense, (1, 2), a3_dense, (3, 4)) - @test dimnames_dest == dimnames_dest_dense - @test size(a_dest) == size(a_dest_dense) - @test a_dest isa BlockSparseArray - @test a_dest ≈ a_dest_dense - end end