From 247cacc337b0de69d495d401b27d1777b387b341 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Sun, 22 Jun 2025 21:19:29 -0400 Subject: [PATCH 1/6] [WIP] Generalize blockwise slicing --- Project.toml | 2 +- .../BlockArraysExtensions.jl | 7 +++++++ src/BlockArraysExtensions/blockedunitrange.jl | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6b542ee..17a00d0 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.7.20" +version = "0.7.21" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" diff --git a/src/BlockArraysExtensions/BlockArraysExtensions.jl b/src/BlockArraysExtensions/BlockArraysExtensions.jl index c4d034d..0fff7b4 100644 --- a/src/BlockArraysExtensions/BlockArraysExtensions.jl +++ b/src/BlockArraysExtensions/BlockArraysExtensions.jl @@ -392,6 +392,13 @@ function blockrange( return map(Block, blocks(r)) end +function blockrange( + axis::AbstractUnitRange, + r::BlockVector{<:GenericBlockIndex{1},<:AbstractVector{<:BlockIndexVector}}, +) + return map(Block, blocks(r)) +end + function blockrange(axis::AbstractUnitRange, r) return error("Slicing not implemented for range of type `$(typeof(r))`.") end diff --git a/src/BlockArraysExtensions/blockedunitrange.jl b/src/BlockArraysExtensions/blockedunitrange.jl index 0856238..bc045c2 100644 --- a/src/BlockArraysExtensions/blockedunitrange.jl +++ b/src/BlockArraysExtensions/blockedunitrange.jl @@ -244,6 +244,15 @@ BlockArrays.blockindex(b::GenericBlockIndex{1}) = b.α[1] function GenericBlockIndex(indcs::Tuple{Vararg{GenericBlockIndex{1},N}}) where {N} GenericBlockIndex(block.(indcs), blockindex.(indcs)) end + +function Base.checkindex( + ::Type{Bool}, axis::AbstractBlockedUnitRange, ind::GenericBlockIndex{1} +) + return checkindex(Bool, axis, block(ind)) && + checkbounds(Bool, axis[block(ind)], blockindex(ind)) +end +Base.to_index(i::GenericBlockIndex) = i + function print_tuple_elements(io::IO, @nospecialize(t)) if !isempty(t) print(io, t[1]) @@ -261,6 +270,13 @@ function Base.show(io::IO, B::GenericBlockIndex) return nothing end +# https://github.com/JuliaArrays/BlockArrays.jl/blob/v1.6.3/src/views.jl#L31-L32 +_maybetail(::Tuple{}) = () +_maybetail(t::Tuple) = Base.tail(t) +@inline function Base.to_indices(A, inds, I::Tuple{GenericBlockIndex{1},Vararg{Any}}) + return (inds[1][I[1]], to_indices(A, _maybetail(inds), Base.tail(I))...) +end + using Base: @propagate_inbounds @propagate_inbounds function Base.getindex(b::AbstractVector, K::GenericBlockIndex{1}) return b[Block(K.I[1])][K.α[1]] From 485185d2e89dcc77f7e18ee258729494a9c66594 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Sun, 22 Jun 2025 21:43:48 -0400 Subject: [PATCH 2/6] More generalizations --- src/BlockArraysExtensions/blockedunitrange.jl | 13 +++++++++++ .../wrappedabstractblocksparsearray.jl | 23 +++++++++++++++++++ .../blocksparsearrayinterface.jl | 17 ++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/BlockArraysExtensions/blockedunitrange.jl b/src/BlockArraysExtensions/blockedunitrange.jl index bc045c2..006b616 100644 --- a/src/BlockArraysExtensions/blockedunitrange.jl +++ b/src/BlockArraysExtensions/blockedunitrange.jl @@ -176,6 +176,19 @@ function blockedunitrange_getindices( return mortar(map(b -> a[b], blocks(indices))) end +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, + indices::BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexVector{1}}}, +) + return mortar(map(b -> a[b], blocks(indices))) +end +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, + indices::BlockVector{<:GenericBlockIndex{1},<:Vector{<:BlockIndexVector{1}}}, +) + return mortar(map(b -> a[b], blocks(indices))) +end + function blockedunitrange_getindices( a::AbstractBlockedUnitRange, indices::AbstractVector{Bool} ) diff --git a/src/abstractblocksparsearray/wrappedabstractblocksparsearray.jl b/src/abstractblocksparsearray/wrappedabstractblocksparsearray.jl index c7d2888..207d210 100644 --- a/src/abstractblocksparsearray/wrappedabstractblocksparsearray.jl +++ b/src/abstractblocksparsearray/wrappedabstractblocksparsearray.jl @@ -78,6 +78,22 @@ function Base.to_indices( return @interface interface(a) to_indices(a, inds, I) end +# a[mortar([Block(1)[[1, 2]], Block(2)[[1, 3]]])] +function Base.to_indices( + a::AnyAbstractBlockSparseArray, + inds, + I::Tuple{BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexVector{1}}},Vararg{Any}}, +) + return @interface interface(a) to_indices(a, inds, I) +end +function Base.to_indices( + a::AnyAbstractBlockSparseArray, + inds, + I::Tuple{BlockVector{<:GenericBlockIndex{1},<:Vector{<:BlockIndexVector{1}}},Vararg{Any}}, +) + return @interface interface(a) to_indices(a, inds, I) +end + # a[[Block(1)[1:2], Block(2)[1:2]], [Block(1)[1:2], Block(2)[1:2]]] function Base.to_indices( a::AnyAbstractBlockSparseArray, inds, I::Tuple{Vector{<:BlockIndexRange{1}},Vararg{Any}} @@ -85,6 +101,13 @@ function Base.to_indices( return to_indices(a, inds, (mortar(I[1]), Base.tail(I)...)) end +# a[[Block(1)[[1, 2]], Block(2)[[1, 2]]], [Block(1)[[1, 2]], Block(2)[[1, 2]]]] +function Base.to_indices( + a::AnyAbstractBlockSparseArray, inds, I::Tuple{Vector{<:BlockIndexVector{1}},Vararg{Any}} +) + return to_indices(a, inds, (mortar(I[1]), Base.tail(I)...)) +end + # BlockArrays `AbstractBlockArray` interface function BlockArrays.blocks(a::AnyAbstractBlockSparseArray) @interface interface(a) blocks(a) diff --git a/src/blocksparsearrayinterface/blocksparsearrayinterface.jl b/src/blocksparsearrayinterface/blocksparsearrayinterface.jl index 9492bac..29d8324 100644 --- a/src/blocksparsearrayinterface/blocksparsearrayinterface.jl +++ b/src/blocksparsearrayinterface/blocksparsearrayinterface.jl @@ -229,6 +229,23 @@ end return (I1, to_indices(a, Base.tail(inds), Base.tail(I))...) end +@interface ::AbstractBlockSparseArrayInterface function Base.to_indices( + a, + inds, + I::Tuple{BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexVector{1}}},Vararg{Any}}, +) + I1 = BlockIndices(I[1], blockedunitrange_getindices(inds[1], I[1])) + return (I1, to_indices(a, Base.tail(inds), Base.tail(I))...) +end +@interface ::AbstractBlockSparseArrayInterface function Base.to_indices( + a, + inds, + I::Tuple{BlockVector{<:GenericBlockIndex{1},<:Vector{<:BlockIndexVector{1}}},Vararg{Any}}, +) + I1 = BlockIndices(I[1], blockedunitrange_getindices(inds[1], I[1])) + return (I1, to_indices(a, Base.tail(inds), Base.tail(I))...) +end + # a[BlockVector([Block(2), Block(1)], [2]), BlockVector([Block(2), Block(1)], [2])] # Permute and merge blocks. # TODO: This isn't merging blocks yet, that needs to be implemented that. From 7ca38015e355c46c7eeedd0f1f4227c3254e26ea Mon Sep 17 00:00:00 2001 From: mtfishman Date: Sun, 22 Jun 2025 22:08:47 -0400 Subject: [PATCH 3/6] More progress --- .../BlockArraysExtensions.jl | 8 +++++- src/BlockArraysExtensions/blockedunitrange.jl | 26 +++++++++---------- src/abstractblocksparsearray/views.jl | 10 ++++++- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/BlockArraysExtensions/BlockArraysExtensions.jl b/src/BlockArraysExtensions/BlockArraysExtensions.jl index 0fff7b4..7fb5ea4 100644 --- a/src/BlockArraysExtensions/BlockArraysExtensions.jl +++ b/src/BlockArraysExtensions/BlockArraysExtensions.jl @@ -167,8 +167,14 @@ const BlockIndexRangeSlices = BlockIndices{ const BlockIndexVectorSlices = BlockIndices{ <:BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexVector}} } +const GenericBlockIndexVectorSlices = BlockIndices{ + <:BlockVector{<:GenericBlockIndex{1},<:Vector{<:BlockIndexVector}} +} const SubBlockSliceCollection = Union{ - BlockIndexRangeSlice,BlockIndexRangeSlices,BlockIndexVectorSlices + BlockIndexRangeSlice, + BlockIndexRangeSlices, + BlockIndexVectorSlices, + GenericBlockIndexVectorSlices, } # TODO: This is type piracy. This is used in `reindex` when making diff --git a/src/BlockArraysExtensions/blockedunitrange.jl b/src/BlockArraysExtensions/blockedunitrange.jl index 006b616..892e4f1 100644 --- a/src/BlockArraysExtensions/blockedunitrange.jl +++ b/src/BlockArraysExtensions/blockedunitrange.jl @@ -176,19 +176,6 @@ function blockedunitrange_getindices( return mortar(map(b -> a[b], blocks(indices))) end -function blockedunitrange_getindices( - a::AbstractBlockedUnitRange, - indices::BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexVector{1}}}, -) - return mortar(map(b -> a[b], blocks(indices))) -end -function blockedunitrange_getindices( - a::AbstractBlockedUnitRange, - indices::BlockVector{<:GenericBlockIndex{1},<:Vector{<:BlockIndexVector{1}}}, -) - return mortar(map(b -> a[b], blocks(indices))) -end - function blockedunitrange_getindices( a::AbstractBlockedUnitRange, indices::AbstractVector{Bool} ) @@ -345,6 +332,19 @@ using ArrayLayouts: LayoutArray K )][K.indices...] +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, + indices::BlockVector{<:BlockIndex{1},<:Vector{<:BlockIndexVector{1}}}, +) + return mortar(map(b -> a[b], blocks(indices))) +end +function blockedunitrange_getindices( + a::AbstractBlockedUnitRange, + indices::BlockVector{<:GenericBlockIndex{1},<:Vector{<:BlockIndexVector{1}}}, +) + return mortar(map(b -> a[b], blocks(indices))) +end + function to_blockindices(a::AbstractBlockedUnitRange{<:Integer}, I::AbstractArray{Bool}) I_blocks = blocks(BlockedVector(I, blocklengths(a))) I′_blocks = map(eachindex(I_blocks)) do b diff --git a/src/abstractblocksparsearray/views.jl b/src/abstractblocksparsearray/views.jl index 1bfe48f..2098233 100644 --- a/src/abstractblocksparsearray/views.jl +++ b/src/abstractblocksparsearray/views.jl @@ -166,7 +166,7 @@ function BlockArrays.viewblock( <:AbstractBlockSparseArray{T,N}, <:Tuple{Vararg{Union{BlockSliceCollection,SubBlockSliceCollection},N}}, }, - block::Union{Block{N},BlockIndexRange{N}}, + block::Union{Block{N},BlockIndexRange{N},BlockIndexVector{N}}, ) where {T,N} return viewblock(a, to_tuple(block)...) end @@ -228,6 +228,14 @@ function to_blockindexrange( # work right now. return blocks(a.blocks)[Int(I)] end +function to_blockindexrange( + a::BlockIndices{<:BlockVector{<:GenericBlockIndex{1},<:Vector{<:BlockIndexVector}}}, + I::Block{1}, +) + # TODO: Ideally we would just use `a.blocks[I]` but that doesn't + # work right now. + return blocks(a.blocks)[Int(I)] +end function to_blockindexrange( a::Base.Slice{<:AbstractBlockedUnitRange{<:Integer}}, I::Block{1} ) From 18687b2fe67f5eb2eb647e886deb27817f866f73 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 11:08:01 -0400 Subject: [PATCH 4/6] Start adding tests --- src/BlockArraysExtensions/blockedunitrange.jl | 45 +++++-- src/abstractblocksparsearray/views.jl | 4 +- test/test_genericblockindex.jl | 127 ++++++++++++++++++ 3 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 test/test_genericblockindex.jl diff --git a/src/BlockArraysExtensions/blockedunitrange.jl b/src/BlockArraysExtensions/blockedunitrange.jl index 892e4f1..0fce533 100644 --- a/src/BlockArraysExtensions/blockedunitrange.jl +++ b/src/BlockArraysExtensions/blockedunitrange.jl @@ -292,35 +292,54 @@ end return b[GenericBlockIndex(tuple(K, J...))] end -function blockindextype(TB::Type{<:Integer}, TI::Vararg{Type{<:Integer},N}) where {N} - return BlockIndex{N,NTuple{N,TB},Tuple{TI...}} -end -function blockindextype(TB::Type{<:Integer}, TI::Vararg{Type,N}) where {N} - return GenericBlockIndex{N,NTuple{N,TB},Tuple{TI...}} -end - -struct BlockIndexVector{N,I<:NTuple{N,AbstractVector},TB<:Integer,BT} <: AbstractArray{BT,N} +# Work around the fact that it is type piracy to define +# `Base.getindex(a::Block, b...)`. +_getindex(a::Block{N}, b::Vararg{Any,N}) where {N} = GenericBlockIndex(a, b) +_getindex(a::Block{N}, b::Vararg{Integer,N}) where {N} = a[b...] +# Fix ambiguity. +_getindex(a::Block{0}) = a[] + +## function blockindextype(TB::Type{<:Integer}, TI::Vararg{Type{<:Integer},N}) where {N} +## return BlockIndex{N,NTuple{N,TB},Tuple{TI...}} +## end +## function blockindextype(TB::Type{<:Integer}, TI::Vararg{Type,N}) where {N} +## return GenericBlockIndex{N,NTuple{N,TB},Tuple{TI...}} +## end + +struct BlockIndexVector{N,BT,I<:NTuple{N,AbstractVector},TB<:Integer} <: AbstractArray{BT,N} block::Block{N,TB} indices::I - function BlockIndexVector( + function BlockIndexVector{N,BT}( block::Block{N,TB}, indices::I - ) where {N,I<:NTuple{N,AbstractVector},TB<:Integer} - BT = blockindextype(TB, eltype.(indices)...) - return new{N,I,TB,BT}(block, indices) + ) where {N,BT,I<:NTuple{N,AbstractVector},TB<:Integer} + return new{N,BT,I,TB}(block, indices) end end +function BlockIndexVector{1,BT}(block::Block{1}, indices::AbstractVector) where {BT} + return BlockIndexVector{1,BT}(block, (indices,)) +end +function BlockIndexVector( + block::Block{N,TB}, indices::NTuple{N,AbstractVector} +) where {N,TB<:Integer} + BT = Base.promote_op(_getindex, typeof(block), eltype.(indices)...) + return BlockIndexVector{N,BT}(block, indices) +end function BlockIndexVector(block::Block{1}, indices::AbstractVector) return BlockIndexVector(block, (indices,)) end Base.size(a::BlockIndexVector) = length.(a.indices) function Base.getindex(a::BlockIndexVector{N}, I::Vararg{Integer,N}) where {N} - return a.block[map((r, i) -> r[i], a.indices, I)...] + return _getindex(Block(a), getindex.(a.indices, I)...) end BlockArrays.block(b::BlockIndexVector) = b.block BlockArrays.Block(b::BlockIndexVector) = b.block Base.copy(a::BlockIndexVector) = BlockIndexVector(a.block, copy.(a.indices)) +function Base.getindex(b::AbstractBlockedUnitRange, Kkr::BlockIndexVector{1}) + b[block(Kkr)][Kkr.indices...] +end + using ArrayLayouts: LayoutArray @propagate_inbounds Base.getindex(b::AbstractArray{T,N}, K::BlockIndexVector{N}) where {T,N} = b[block( K diff --git a/src/abstractblocksparsearray/views.jl b/src/abstractblocksparsearray/views.jl index 2098233..084cb2c 100644 --- a/src/abstractblocksparsearray/views.jl +++ b/src/abstractblocksparsearray/views.jl @@ -92,10 +92,10 @@ end # TODO: Move to `GradedUnitRanges` or `BlockArraysExtensions`. to_block(I::Block{1}) = I to_block(I::BlockIndexRange{1}) = Block(I) -to_block(I::BlockIndexVector) = Block(I) +to_block(I::BlockIndexVector{1}) = Block(I) to_block_indices(I::Block{1}) = Colon() to_block_indices(I::BlockIndexRange{1}) = only(I.indices) -to_block_indices(I::BlockIndexVector) = only(I.indices) +to_block_indices(I::BlockIndexVector{1}) = only(I.indices) function Base.view( a::AbstractBlockSparseArray{<:Any,N}, diff --git a/test/test_genericblockindex.jl b/test/test_genericblockindex.jl new file mode 100644 index 0000000..28ce6b4 --- /dev/null +++ b/test/test_genericblockindex.jl @@ -0,0 +1,127 @@ +using BlockArrays: Block, BlockIndex, BlockedVector, block, blockindex +using BlockSparseArrays: BlockSparseArrays, BlockIndexVector, GenericBlockIndex +using Test: @test, @test_broken, @testset + +# blockrange +# checkindex +# to_indices +# to_index +# blockedunitrange_getindices +# viewblock +# to_blockindexrange + +@testset "GenericBlockIndex" begin + i1 = GenericBlockIndex(Block(1), ("x",)) + i2 = GenericBlockIndex(Block(2), ("y",)) + i = GenericBlockIndex(Block(1, 2), ("x", "y")) + @test sprint(show, i) == "Block(1, 2)[x, y]" + @test i isa GenericBlockIndex{2,Tuple{Int64,Int64},Tuple{String,String}} + @test GenericBlockIndex(Block(1), "x") === i1 + @test GenericBlockIndex(1, "x") === i1 + @test GenericBlockIndex(1, ("x",)) === i1 + @test GenericBlockIndex((1,), "x") === i1 + @test GenericBlockIndex((1, 2), ("x", "y")) === i + @test GenericBlockIndex((Block(1), Block(2)), ("x", "y")) === i + @test GenericBlockIndex((i1, i2)) === i + @test block(i1) == Block(1) + @test block(i) == Block(1, 2) + @test blockindex(i1) == "x" + @test GenericBlockIndex((), ()) == GenericBlockIndex(Block(), ()) + @test GenericBlockIndex(Block(1, 2), ("x",)) == GenericBlockIndex(Block(1, 2), ("x", 1)) + + i1 = GenericBlockIndex(Block(1), (1,)) + i2 = GenericBlockIndex(Block(2), (2,)) + i = GenericBlockIndex(Block(1, 2), (1, 2)) + v = BlockedVector(["a", "b", "c", "d"], [2, 2]) + @test v[i1] == "a" + @test v[i2] == "d" + + a = collect(Iterators.product(v, v)) + @test a[i1, i1] == ("a", "a") + @test a[i2, i1] == ("d", "a") + @test a[i1, i2] == ("a", "d") + @test a[i] == ("a", "d") + @test a[i2, i2] == ("d", "d") + + I = BlockIndexVector(Block(1), [1, 2]) + @test eltype(I) === BlockIndex{1,Tuple{Int},Tuple{Int}} + @test ndims(I) === 1 + @test length(I) === 2 + @test size(I) === (2,) + @test I[1] === Block(1)[1] + @test I[2] === Block(1)[2] + @test block(I) === Block(1) + @test Block(I) === Block(1) + @test copy(I) == BlockIndexVector(Block(1), [1, 2]) + + I = BlockIndexVector(Block(1, 2), ([1, 2], [3, 4])) + @test eltype(I) === BlockIndex{2,Tuple{Int,Int},Tuple{Int,Int}} + @test ndims(I) === 2 + @test length(I) === 4 + @test size(I) === (2, 2) + @test I[1, 1] === Block(1, 2)[1, 3] + @test I[2, 1] === Block(1, 2)[2, 3] + @test I[1, 2] === Block(1, 2)[1, 4] + @test I[2, 2] === Block(1, 2)[2, 4] + @test block(I) === Block(1, 2) + @test Block(I) === Block(1, 2) + @test copy(I) == BlockIndexVector(Block(1, 2), ([1, 2], [3, 4])) + + I = BlockIndexVector(Block(1), ["x", "y"]) + @test eltype(I) === GenericBlockIndex{1,Tuple{Int},Tuple{String}} + @test ndims(I) === 1 + @test length(I) === 2 + @test size(I) === (2,) + @test I[1] === GenericBlockIndex(Block(1), "x") + @test I[2] === GenericBlockIndex(Block(1), "y") + @test block(I) === Block(1) + @test Block(I) === Block(1) + @test copy(I) == BlockIndexVector(Block(1), ["x", "y"]) + + I = BlockIndexVector(Block(1, 2), (["x", "y"], ["z", "w"])) + @test eltype(I) === GenericBlockIndex{2,Tuple{Int,Int},Tuple{String,String}} + @test ndims(I) === 2 + @test length(I) === 4 + @test size(I) === (2, 2) + @test I[1, 1] === GenericBlockIndex(Block(1, 2), ("x", "z")) + @test I[2, 1] === GenericBlockIndex(Block(1, 2), ("y", "z")) + @test I[1, 2] === GenericBlockIndex(Block(1, 2), ("x", "w")) + @test I[2, 2] === GenericBlockIndex(Block(1, 2), ("y", "w")) + @test block(I) === Block(1, 2) + @test Block(I) === Block(1, 2) + @test copy(I) == BlockIndexVector(Block(1, 2), (["x", "y"], ["z", "w"])) + + v = BlockedVector(["a", "b", "c", "d"], [2, 2]) + i = BlockIndexVector(Block(1), [2, 1]) + @test v[i] == ["b", "a"] + i = BlockIndexVector(Block(2), [2, 1]) + @test v[i] == ["d", "c"] + + v = BlockedVector(["a", "b", "c", "d"], [2, 2]) + i = BlockIndexVector{1,GenericBlockIndex{1,Tuple{Int},Tuple{String}}}(Block(1), [2, 1]) + @test v[i] == ["b", "a"] + i = BlockIndexVector(Block(2), [2, 1]) + @test v[i] == ["d", "c"] + + a = collect(Iterators.product(v, v)) + i1 = BlockIndexVector(Block(1), [2, 1]) + i2 = BlockIndexVector(Block(2), [1, 2]) + i = BlockIndexVector(Block(1, 2), ([2, 1], [1, 2])) + @test a[i1, i1] == [("b", "b") ("b", "a"); ("a", "b") ("a", "a")] + @test a[i2, i1] == [("c", "b") ("c", "a"); ("d", "b") ("d", "a")] + @test a[i1, i2] == [("b", "c") ("b", "d"); ("a", "c") ("a", "d")] + @test a[i] == [("b", "c") ("b", "d"); ("a", "c") ("a", "d")] + @test a[i2, i2] == [("c", "c") ("c", "d"); ("d", "c") ("d", "d")] + + a = collect(Iterators.product(v, v)) + i1 = BlockIndexVector{1,GenericBlockIndex{1,Tuple{Int},Tuple{String}}}(Block(1), [2, 1]) + i2 = BlockIndexVector{1,GenericBlockIndex{1,Tuple{Int},Tuple{String}}}(Block(2), [1, 2]) + i = BlockIndexVector{2,GenericBlockIndex{2,Tuple{Int,Int},Tuple{String,String}}}( + Block(1, 2), ([2, 1], [1, 2]) + ) + @test a[i1, i1] == [("b", "b") ("b", "a"); ("a", "b") ("a", "a")] + @test a[i2, i1] == [("c", "b") ("c", "a"); ("d", "b") ("d", "a")] + @test a[i1, i2] == [("b", "c") ("b", "d"); ("a", "c") ("a", "d")] + @test a[i] == [("b", "c") ("b", "d"); ("a", "c") ("a", "d")] + @test a[i2, i2] == [("c", "c") ("c", "d"); ("d", "c") ("d", "d")] +end From d9c8b0294fb743e3033b064c577c8ca8ad1554aa Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 11:25:14 -0400 Subject: [PATCH 5/6] More tests --- src/BlockArraysExtensions/blockedunitrange.jl | 9 +---- test/test_genericblockindex.jl | 35 ++++++++++++++++++- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/BlockArraysExtensions/blockedunitrange.jl b/src/BlockArraysExtensions/blockedunitrange.jl index 0fce533..f4cdcd7 100644 --- a/src/BlockArraysExtensions/blockedunitrange.jl +++ b/src/BlockArraysExtensions/blockedunitrange.jl @@ -299,13 +299,6 @@ _getindex(a::Block{N}, b::Vararg{Integer,N}) where {N} = a[b...] # Fix ambiguity. _getindex(a::Block{0}) = a[] -## function blockindextype(TB::Type{<:Integer}, TI::Vararg{Type{<:Integer},N}) where {N} -## return BlockIndex{N,NTuple{N,TB},Tuple{TI...}} -## end -## function blockindextype(TB::Type{<:Integer}, TI::Vararg{Type,N}) where {N} -## return GenericBlockIndex{N,NTuple{N,TB},Tuple{TI...}} -## end - struct BlockIndexVector{N,BT,I<:NTuple{N,AbstractVector},TB<:Integer} <: AbstractArray{BT,N} block::Block{N,TB} indices::I @@ -337,7 +330,7 @@ BlockArrays.Block(b::BlockIndexVector) = b.block Base.copy(a::BlockIndexVector) = BlockIndexVector(a.block, copy.(a.indices)) function Base.getindex(b::AbstractBlockedUnitRange, Kkr::BlockIndexVector{1}) - b[block(Kkr)][Kkr.indices...] + return b[block(Kkr)][Kkr.indices...] end using ArrayLayouts: LayoutArray diff --git a/test/test_genericblockindex.jl b/test/test_genericblockindex.jl index 28ce6b4..6e9274e 100644 --- a/test/test_genericblockindex.jl +++ b/test/test_genericblockindex.jl @@ -1,4 +1,5 @@ -using BlockArrays: Block, BlockIndex, BlockedVector, block, blockindex +using BlockArrays: + Block, BlockIndex, BlockedArray, BlockedVector, block, blockedrange, blockindex, mortar using BlockSparseArrays: BlockSparseArrays, BlockIndexVector, GenericBlockIndex using Test: @test, @test_broken, @testset @@ -124,4 +125,36 @@ using Test: @test, @test_broken, @testset @test a[i1, i2] == [("b", "c") ("b", "d"); ("a", "c") ("a", "d")] @test a[i] == [("b", "c") ("b", "d"); ("a", "c") ("a", "d")] @test a[i2, i2] == [("c", "c") ("c", "d"); ("d", "c") ("d", "d")] + + r = blockedrange([2, 3]) + i = mortar([BlockIndexVector(Block(2), [1]), BlockIndexVector(Block(1), [1, 2])]) + # TODO: Check the indices make sense and are in bounds. + @test BlockSparseArrays.blockrange(r, i) == [Block(2), Block(1)] + + r = blockedrange([2, 3]) + i = mortar([BlockIndexVector(Block(2), ["x"]), BlockIndexVector(Block(1), ["y", "z"])]) + # TODO: Check the indices make sense and are in bounds. + @test BlockSparseArrays.blockrange(r, i) == [Block(2), Block(1)] + + i = GenericBlockIndex(Block(2), 1) + # TODO: Is this a good definition? + @test Base.to_index(i) === i + + r = blockedrange([2, 3]) + @test checkindex(Bool, r, GenericBlockIndex(Block(1), 1)) + @test checkindex(Bool, r, GenericBlockIndex(Block(1), 2)) + @test !checkindex(Bool, r, GenericBlockIndex(Block(1), 3)) + @test checkindex(Bool, r, GenericBlockIndex(Block(2), 1)) + @test checkindex(Bool, r, GenericBlockIndex(Block(2), 2)) + @test checkindex(Bool, r, GenericBlockIndex(Block(2), 3)) + @test !checkindex(Bool, r, GenericBlockIndex(Block(2), 4)) + @test !checkindex(Bool, r, GenericBlockIndex(Block(3), 1)) + + a = BlockedArray(randn(5, 5), [2, 3], [2, 3]) + i = GenericBlockIndex(Block(1), 1) + @test to_indices(a, (i, i)) == (1, 1) + @test to_indices(a, axes(a), (i, i)) == (1, 1) + i = GenericBlockIndex(Block(2), 2) + @test to_indices(a, (i, i)) == (4, 4) + @test to_indices(a, axes(a), (i, i)) == (4, 4) end From 21c1476d4cb53c86904af82f37168b28da63ff24 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 13:09:53 -0400 Subject: [PATCH 6/6] Add more tests --- .../BlockArraysExtensions.jl | 12 +- src/BlockArraysExtensions/blockedunitrange.jl | 29 ++++ test/test_genericblockindex.jl | 128 +++++++++++++++++- 3 files changed, 165 insertions(+), 4 deletions(-) diff --git a/src/BlockArraysExtensions/BlockArraysExtensions.jl b/src/BlockArraysExtensions/BlockArraysExtensions.jl index 7fb5ea4..c458978 100644 --- a/src/BlockArraysExtensions/BlockArraysExtensions.jl +++ b/src/BlockArraysExtensions/BlockArraysExtensions.jl @@ -68,12 +68,20 @@ for f in (:axes, :unsafe_indices, :axes1, :first, :last, :size, :length, :unsafe end Base.getindex(S::BlockIndices, i::Integer) = getindex(S.indices, i) -function _blockslice(x, y::AbstractUnitRange{<:Integer}) +# Generalization of to `BlockArrays._blockslice`: +# https://github.com/JuliaArrays/BlockArrays.jl/blob/v1.6.3/src/views.jl#L13-L14 +# Used by `BlockArrays.unblock`, which is used in `to_indices` +# to convert relative blockwise slices to absolute slices, but in a way +# that preserves the original relative blockwise slice information. +# TODO: Ideally this would be handled in BlockArrays.jl +# once slicing like `A[Block(1)[[1, 2]]]` is supported. +function _blockslice(x, y::AbstractUnitRange) return BlockSlice(x, y) end -function _blockslice(x, y::AbstractVector{<:Integer}) +function _blockslice(x, y::AbstractVector) return BlockIndices(x, y) end + function Base.getindex(S::BlockIndices, i::BlockSlice{<:Block{1}}) # TODO: Check that `i.indices` is consistent with `S.indices`. # It seems like this isn't handling the case where `i` is a diff --git a/src/BlockArraysExtensions/blockedunitrange.jl b/src/BlockArraysExtensions/blockedunitrange.jl index f4cdcd7..2adf198 100644 --- a/src/BlockArraysExtensions/blockedunitrange.jl +++ b/src/BlockArraysExtensions/blockedunitrange.jl @@ -292,6 +292,24 @@ end return b[GenericBlockIndex(tuple(K, J...))] end +# TODO: Delete this once `BlockArrays.BlockIndex` is generalized. +@inline function Base.to_indices( + A, inds, I::Tuple{AbstractVector{<:GenericBlockIndex{1}},Vararg{Any}} +) + return (unblock(A, inds, I), to_indices(A, _maybetail(inds), Base.tail(I))...) +end + +# This is a specialization of `BlockArrays.unblock`: +# https://github.com/JuliaArrays/BlockArrays.jl/blob/v1.6.3/src/views.jl#L8-L11 +# that is used in the `to_indices` logic for blockwise slicing in +# BlockArrays.jl. +# TODO: Ideally this would be defined in BlockArrays.jl once the slicing +# there is made more generic. +function BlockArrays.unblock(A, inds, I::Tuple{GenericBlockIndex{1},Vararg{Any}}) + B = first(I) + return _blockslice(B, inds[1][B]) +end + # Work around the fact that it is type piracy to define # `Base.getindex(a::Block, b...)`. _getindex(a::Block{N}, b::Vararg{Any,N}) where {N} = GenericBlockIndex(a, b) @@ -357,6 +375,17 @@ function blockedunitrange_getindices( return mortar(map(b -> a[b], blocks(indices))) end +# This is a specialization of `BlockArrays.unblock`: +# https://github.com/JuliaArrays/BlockArrays.jl/blob/v1.6.3/src/views.jl#L8-L11 +# that is used in the `to_indices` logic for blockwise slicing in +# BlockArrays.jl. +# TODO: Ideally this would be defined in BlockArrays.jl once the slicing +# there is made more generic. +function BlockArrays.unblock(A, inds, I::Tuple{BlockIndexVector{1},Vararg{Any}}) + B = first(I) + return _blockslice(B, inds[1][B]) +end + function to_blockindices(a::AbstractBlockedUnitRange{<:Integer}, I::AbstractArray{Bool}) I_blocks = blocks(BlockedVector(I, blocklengths(a))) I′_blocks = map(eachindex(I_blocks)) do b diff --git a/test/test_genericblockindex.jl b/test/test_genericblockindex.jl index 6e9274e..cc159d9 100644 --- a/test/test_genericblockindex.jl +++ b/test/test_genericblockindex.jl @@ -1,6 +1,23 @@ using BlockArrays: - Block, BlockIndex, BlockedArray, BlockedVector, block, blockedrange, blockindex, mortar -using BlockSparseArrays: BlockSparseArrays, BlockIndexVector, GenericBlockIndex + Block, + BlockIndex, + BlockSlice, + BlockedArray, + BlockedVector, + block, + blockedrange, + blockindex, + mortar +using BlockSparseArrays: + BlockSparseArrays, + BlockIndexVector, + BlockIndices, + GenericBlockIndex, + blocksparsezeros, + blockedunitrange_getindices, + to_block, + to_block_indices, + to_blockindexrange using Test: @test, @test_broken, @testset # blockrange @@ -157,4 +174,111 @@ using Test: @test, @test_broken, @testset i = GenericBlockIndex(Block(2), 2) @test to_indices(a, (i, i)) == (4, 4) @test to_indices(a, axes(a), (i, i)) == (4, 4) + + r = blockedrange([2, 3]) + i = mortar([BlockIndexVector(Block(2), [1, 3]), BlockIndexVector(Block(1), [2])]) + @test blockedunitrange_getindices(r, i) == mortar([[3, 5], [2]]) + + r = blockedrange([2, 3]) + T = GenericBlockIndex{1,Tuple{Int},Tuple{Int}} + i = mortar([ + BlockIndexVector{1,T}(Block(2), [1, 3]), BlockIndexVector{1,T}(Block(1), [2]) + ]) + @test blockedunitrange_getindices(r, i) == mortar([[3, 5], [2]]) + + # Internal functions. + @test to_block(Block(2)) === Block(2) + @test to_block(Block(2)[2:3]) === Block(2) + @test to_block(BlockIndexVector(Block(2), [1, 3])) === Block(2) + @test to_block_indices(Block(2)) === (:) + @test to_block_indices(Block(2)[2:3]) === 2:3 + @test to_block_indices(BlockIndexVector(Block(2), [1, 3])) == [1, 3] + + a = blocksparsezeros([2, 3], [2, 3]) + i = Block(2)[2:3] + @test to_indices(a, (i, i)) === + to_indices(a, axes(a), (i, i)) === + (BlockSlice(i, 4:5), BlockSlice(i, 4:5)) + + a = blocksparsezeros([2, 3], [2, 3]) + i = mortar([Block(2)[2:3], Block(1)[2:2]]) + for I in (to_indices(a, (i, i)), to_indices(a, axes(a), (i, i))) + for Iⱼ in I + @test Iⱼ == BlockIndices(i, mortar([4:5, 2:2])) + @test Iⱼ isa BlockIndices + @test Iⱼ.blocks == i + @test Iⱼ.indices == mortar([4:5, 2:2]) + end + end + + a = blocksparsezeros([2, 3], [2, 3]) + i = BlockIndexVector(Block(2), [1, 3]) + for I in (to_indices(a, (i, i)), to_indices(a, axes(a), (i, i))) + for Iⱼ in I + @test Iⱼ == BlockIndices(i, [3, 5]) + @test Iⱼ isa BlockIndices + @test Iⱼ.blocks == i + @test Iⱼ.indices == [3, 5] + end + end + + a = blocksparsezeros([2, 3], [2, 3]) + T = GenericBlockIndex{1,Tuple{Int},Tuple{Int}} + i = BlockIndexVector{1,T}(Block(2), [1, 3]) + for I in (to_indices(a, (i, i)), to_indices(a, axes(a), (i, i))) + for Iⱼ in I + @test Iⱼ == BlockIndices(i, [3, 5]) + @test Iⱼ isa BlockIndices + @test Iⱼ.blocks == i + @test Iⱼ.indices == [3, 5] + end + end + + a = blocksparsezeros([2, 3], [2, 3]) + i = mortar([BlockIndexVector(Block(2), [1, 3]), BlockIndexVector(Block(1), [2])]) + for I in (to_indices(a, (i, i)), to_indices(a, axes(a), (i, i))) + for Iⱼ in I + @test Iⱼ == BlockIndices(i, mortar([[3, 5], [2]])) + @test Iⱼ isa BlockIndices + @test Iⱼ.blocks == i + @test Iⱼ.indices == mortar([[3, 5], [2]]) + end + end + + a = blocksparsezeros([2, 3], [2, 3]) + i = [BlockIndexVector(Block(2), [1, 3]), BlockIndexVector(Block(1), [2])] + for I in (to_indices(a, (i, i)), to_indices(a, axes(a), (i, i))) + for Iⱼ in I + @test Iⱼ == BlockIndices(mortar(i), mortar([[3, 5], [2]])) + @test Iⱼ isa BlockIndices + @test Iⱼ.blocks == mortar(i) + @test Iⱼ.indices == mortar([[3, 5], [2]]) + end + end + + a = blocksparsezeros([2, 3], [2, 3]) + T = GenericBlockIndex{1,Tuple{Int},Tuple{Int}} + i = mortar([ + BlockIndexVector{1,T}(Block(2), [1, 3]), BlockIndexVector{1,T}(Block(1), [2]) + ]) + for I in (to_indices(a, (i, i)), to_indices(a, axes(a), (i, i))) + for Iⱼ in I + @test Iⱼ == BlockIndices(i, mortar([[3, 5], [2]])) + @test Iⱼ isa BlockIndices + @test Iⱼ.blocks == i + @test Iⱼ.indices == mortar([[3, 5], [2]]) + end + end + + a = blocksparsezeros([2, 3], [2, 3]) + T = GenericBlockIndex{1,Tuple{Int},Tuple{Int}} + i = [BlockIndexVector{1,T}(Block(2), [1, 3]), BlockIndexVector{1,T}(Block(1), [2])] + for I in (to_indices(a, (i, i)), to_indices(a, axes(a), (i, i))) + for Iⱼ in I + @test Iⱼ == BlockIndices(mortar(i), mortar([[3, 5], [2]])) + @test Iⱼ isa BlockIndices + @test Iⱼ.blocks == mortar(i) + @test Iⱼ.indices == mortar([[3, 5], [2]]) + end + end end