From 3b8987f361d67cb7e2cf598b649c6f71a23e35b5 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 16:17:42 -0400 Subject: [PATCH 01/12] Generalize BlockIndex and BlockIndexRange --- src/blockindices.jl | 24 ++++++++++++------------ test/test_blockindices.jl | 8 ++++++++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/blockindices.jl b/src/blockindices.jl index 1226e8af..b0bb6577 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -140,7 +140,7 @@ julia> a[BlockIndex((2,2), (2,3))] 20 ``` """ -struct BlockIndex{N,TI<:Tuple{Vararg{Integer,N}},Tα<:Tuple{Vararg{Integer,N}}} +struct BlockIndex{N,TI<:Tuple{Vararg{Any,N}},Tα<:Tuple{Vararg{Any,N}}} I::TI α::Tα end @@ -148,15 +148,15 @@ end @inline BlockIndex(a::NTuple{N,Block{1}}, b::Tuple) where N = BlockIndex(Int.(a), b) @inline BlockIndex(::Tuple{}, b::Tuple{}) = BlockIndex{0,Tuple{},Tuple{}}((), ()) -@inline BlockIndex(a::Integer, b::Integer) = BlockIndex((a,), (b,)) -@inline BlockIndex(a::Tuple, b::Integer) = BlockIndex(a, (b,)) -@inline BlockIndex(a::Integer, b::Tuple) = BlockIndex((a,), b) +@inline BlockIndex(a, b) = BlockIndex((a,), (b,)) +@inline BlockIndex(a::Tuple, b) = BlockIndex(a, (b,)) +@inline BlockIndex(a, b::Tuple) = BlockIndex((a,), b) @inline BlockIndex() = BlockIndex((), ()) @inline BlockIndex(a::Block, b::Tuple) = BlockIndex(a.n, b) -@inline BlockIndex(a::Block, b::Integer) = BlockIndex(a, (b,)) +@inline BlockIndex(a::Block, b) = BlockIndex(a, (b,)) -@inline function BlockIndex(I::Tuple{Vararg{Integer,N}}, α::Tuple{Vararg{Integer,M}}) where {M,N} +@inline function BlockIndex(I::Tuple{Vararg{Any,N}}, α::Tuple{Vararg{Any,M}}) where {M,N} M <= N || throw(ArgumentError("number of indices must not exceed the number of blocks")) α2 = ntuple(k -> k <= M ? α[k] : 1, N) BlockIndex(I, α2) @@ -182,10 +182,10 @@ end checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::AbstractVector{<:BlockIndex{N}}) where N = all(checkbounds.(Bool, Ref(A), I)) -struct BlockIndexRange{N,R<:Tuple{Vararg{AbstractUnitRange{<:Integer},N}},I<:Tuple{Vararg{Integer,N}},BI<:Integer} <: AbstractArray{BlockIndex{N,NTuple{N,BI},I},N} +struct BlockIndexRange{N,R<:Tuple{Vararg{AbstractVector,N}},I<:Tuple{Vararg{Any,N}},BI} <: AbstractArray{BlockIndex{N,NTuple{N,BI},I},N} block::Block{N,BI} indices::R - function BlockIndexRange(block::Block{N,BI}, inds::R) where {N,BI<:Integer,R<:Tuple{Vararg{AbstractUnitRange{<:Integer},N}}} + function BlockIndexRange(block::Block{N,BI}, inds::R) where {N,BI<:Integer,R<:Tuple{Vararg{AbstractVector,N}}} I = Tuple{eltype.(inds)...} return new{N,R,I,BI}(block,inds) end @@ -198,7 +198,7 @@ represents a cartesian range inside a block. """ BlockIndexRange -BlockIndexRange(block::Block{N}, inds::Vararg{AbstractUnitRange{<:Integer},N}) where {N} = +BlockIndexRange(block::Block{N}, inds::Vararg{AbstractVector,N}) where {N} = BlockIndexRange(block,inds) block(R::BlockIndexRange) = R.block @@ -206,12 +206,12 @@ block(R::BlockIndexRange) = R.block copy(R::BlockIndexRange) = BlockIndexRange(R.block, map(copy, R.indices)) getindex(::Block{0}) = BlockIndex() -getindex(B::Block{N}, inds::Vararg{Integer,N}) where N = BlockIndex(B,inds) -getindex(B::Block{N}, inds::Vararg{AbstractUnitRange{<:Integer},N}) where N = BlockIndexRange(B,inds) +getindex(B::Block{N}, inds::Vararg{Any,N}) where N = BlockIndex(B,inds) +getindex(B::Block{N}, inds::Vararg{AbstractVector,N}) where N = BlockIndexRange(B,inds) getindex(B::Block{1}, inds::Colon) = B getindex(B::Block{1}, inds::Base.Slice) = B -@propagate_inbounds getindex(B::BlockIndexRange{1}, kr::AbstractUnitRange{<:Integer}) = BlockIndexRange(B.block, B.indices[1][kr]) +@propagate_inbounds getindex(B::BlockIndexRange{1}, kr::AbstractVector) = BlockIndexRange(B.block, B.indices[1][kr]) @propagate_inbounds getindex(B::BlockIndexRange{N}, inds::Vararg{Int,N}) where N = B.block[Base.reindex(B.indices, inds)...] eltype(R::BlockIndexRange) = eltype(typeof(R)) diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl index b57512be..0d44469e 100644 --- a/test/test_blockindices.jl +++ b/test/test_blockindices.jl @@ -86,16 +86,24 @@ import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice @testset "BlockIndex" begin @test Block()[] == BlockIndex() @test Block(1)[1] == BlockIndex((1,),(1,)) + @test Block(1)[Block(1)] == BlockIndex((1,),(Block(1),)) @test Block(1)[1:2] == BlockIndexRange(Block(1),(1:2,)) + @test Block(1)[[1,3]] == BlockIndexRange(Block(1),([1,3],)) @test Block(1,1)[1,1] == BlockIndex((1,1),(1,1)) == BlockIndex((1,1),(1,)) @test Block(1,1)[1:2,1:2] == BlockIndexRange(Block(1,1),(1:2,1:2)) + @test Block(1,1)[[1,3],1:2] == BlockIndexRange(Block(1,1),([1,3],1:2)) @test Block(1)[1:3][1:2] == BlockIndexRange(Block(1),1:2) + @test Block(1)[[1,3,5]][2:3] == BlockIndexRange(Block(1),[3,5]) + @test Block(1)[2:4][[1,3]] == BlockIndexRange(Block(1),[2,4]) @test BlockIndex((2,2,2),(2,)) == BlockIndex((2,2,2),(2,1,)) == BlockIndex((2,2,2),(2,1,1)) @test BlockIndex(2,(2,)) === BlockIndex((2,),(2,)) @test BlockIndex(UInt(2),(2,)) === BlockIndex((UInt(2),),(2,)) @test BlockIndex(Block(2),2) === BlockIndex(Block(2),(2,)) @test BlockIndex(Block(2),UInt(2)) === BlockIndex(Block(2),(UInt(2),)) + @test BlockIndex(Block(2),Block(2)) === BlockIndex(Block(2),(Block(2),)) @test copy(Block(1)[1:2]) === BlockIndexRange(Block(1),1:2) + @test copy(Block(1)[[1,3]]) == BlockIndexRange(Block(1),[1,3]) + @test copy(Block(1)[[1,3]]) ≢ BlockIndexRange(Block(1),[1,3]) end @testset "BlockRange" begin From 08b78eedd47caaaaad03113f334ab143afa6fc7c Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 16:30:52 -0400 Subject: [PATCH 02/12] Generalize slicing into BlockIndexRange --- src/blockindices.jl | 2 +- test/test_blockindices.jl | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blockindices.jl b/src/blockindices.jl index b0bb6577..bf0904b1 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -211,7 +211,7 @@ getindex(B::Block{N}, inds::Vararg{AbstractVector,N}) where N = BlockIndexRange( getindex(B::Block{1}, inds::Colon) = B getindex(B::Block{1}, inds::Base.Slice) = B -@propagate_inbounds getindex(B::BlockIndexRange{1}, kr::AbstractVector) = BlockIndexRange(B.block, B.indices[1][kr]) +@propagate_inbounds getindex(B::BlockIndexRange{N}, kr::Vararg{AbstractVector,N}) where N = BlockIndexRange(B.block, map(getindex, B.indices, kr)) @propagate_inbounds getindex(B::BlockIndexRange{N}, inds::Vararg{Int,N}) where N = B.block[Base.reindex(B.indices, inds)...] eltype(R::BlockIndexRange) = eltype(typeof(R)) diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl index 0d44469e..56d4bc00 100644 --- a/test/test_blockindices.jl +++ b/test/test_blockindices.jl @@ -93,8 +93,11 @@ import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice @test Block(1,1)[1:2,1:2] == BlockIndexRange(Block(1,1),(1:2,1:2)) @test Block(1,1)[[1,3],1:2] == BlockIndexRange(Block(1,1),([1,3],1:2)) @test Block(1)[1:3][1:2] == BlockIndexRange(Block(1),1:2) + @test Block(1)[[1,3,5]][[1,3]] == BlockIndexRange(Block(1),[1,5]) @test Block(1)[[1,3,5]][2:3] == BlockIndexRange(Block(1),[3,5]) @test Block(1)[2:4][[1,3]] == BlockIndexRange(Block(1),[2,4]) + @test Block(1,1)[1:3,1:3][1:2,1:2] == BlockIndexRange(Block(1,1),1:2,1:2) + @test Block(1,1)[1:3,1:3][1:2,[1,3]] == BlockIndexRange(Block(1,1),1:2,[1,3]) @test BlockIndex((2,2,2),(2,)) == BlockIndex((2,2,2),(2,1,)) == BlockIndex((2,2,2),(2,1,1)) @test BlockIndex(2,(2,)) === BlockIndex((2,),(2,)) @test BlockIndex(UInt(2),(2,)) === BlockIndex((UInt(2),),(2,)) @@ -574,6 +577,7 @@ end b = blockedrange([1,2,3]) @test b[Block(3)[2]] == b[Block(3)][2] == 5 @test b[Block(3)[2:3]] == b[Block(3)][2:3] == 5:6 + @test b[Block(3)[[3,2]]] == b[Block(3)][[3,2]] == [6,5] end @testset "BlockRange indexing" begin From 48d1d44e7557d221b28e3e4b249188e2a290fde6 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 17:27:10 -0400 Subject: [PATCH 03/12] More general blockwise slicing --- Project.toml | 2 +- src/blockindices.jl | 41 ++++++++++++++++++++++++++++++++++++--- src/views.jl | 5 +++-- test/test_blockarrays.jl | 16 +++++++++++++++ test/test_blockindices.jl | 12 +++++++++++- test/test_blockviews.jl | 19 +++++++++++++++++- 6 files changed, 87 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index edf66627..1935c38a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "BlockArrays" uuid = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" -version = "1.6.3" +version = "1.7.0" [deps] ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" diff --git a/src/blockindices.jl b/src/blockindices.jl index bf0904b1..01d5edb6 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -256,10 +256,11 @@ Block(bs::BlockIndexRange) = bs.block """ BlockSlice(block, indices) -Represent an AbstractUnitRange{<:Integer} of indices that attaches a block. +Represents an AbstractUnitRange{<:Integer} of indices attached to a block, +a subblock, or a range of blocks. Upon calling `to_indices()`, Blocks are converted to BlockSlice objects to represent -the indices over which the Block spans. +the indices over which the block, subblock, or range of blocks spans. This mimics the relationship between `Colon` and `Base.Slice`. """ @@ -282,7 +283,7 @@ _indices(B) = B @propagate_inbounds getindex(S::BlockSlice, i::Integer) = getindex(S.indices, i) @propagate_inbounds getindex(S::BlockSlice{<:Block{1}}, k::AbstractUnitRange{<:Integer}) = BlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) -@propagate_inbounds getindex(S::BlockSlice{<:BlockIndexRange{1}}, k::AbstractUnitRange{<:Integer}) = +@propagate_inbounds getindex(S::BlockSlice{<:BlockIndexRange{1,<:Tuple{AbstractUnitRange{<:Integer}}}}, k::AbstractUnitRange{<:Integer}) = BlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) # Avoid creating a SubArray wrapper in certain non-allocating cases @@ -290,12 +291,46 @@ _indices(B) = B Block(bs::BlockSlice{<:BlockIndexRange}) = Block(bs.block) +""" + NoncontiguousBlockSlice(blocks, indices) + +Represents an AbstractVector of indices attached to a (potentially non-contiguous) subblock, +set of blocks, or set of subblocks. This is the generalization of `BlockSlice` to +non-contiguous slices. + +Upon calling `to_indices()`, a collection of blocks are converted to NoncontiguousBlockSlice objects to represent +the indices over which the blocks span. + +This mimics the relationship between `Colon` and `Base.Slice`, `Block` and `BlockSlice`, etc. +""" +struct NoncontiguousBlockSlice{BB,T,INDS<:AbstractVector{T}} <: AbstractVector{T} + block::BB + indices::INDS +end + +Block(bs::NoncontiguousBlockSlice{<:Block}) = bs.block + +for f in (:axes, :unsafe_indices, :axes1, :first, :last, :size, :length, + :unsafe_length, :start) + @eval $f(S::NoncontiguousBlockSlice) = $f(S.indices) +end + +_indices(B::NoncontiguousBlockSlice) = B.indices + +@propagate_inbounds getindex(S::NoncontiguousBlockSlice, i::Integer) = getindex(S.indices, i) +@propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:Block{1}}, k::AbstractVector{<:Integer}) = + NoncontiguousBlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) +@propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:BlockIndexRange{1,Tuple{AbstractVector}}}, k::AbstractVector{<:Integer}) = + NoncontiguousBlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) +@propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}, k::Block{1}) = + BlockSlice(S.block[Int(k)], getindex(S.indices, k)) struct BlockRange{N,R<:NTuple{N,AbstractUnitRange{<:Integer}}} <: AbstractArray{Block{N,Int},N} indices::R BlockRange{N,R}(inds::R) where {N,R} = new{N,R}(inds) end +Block(bs::NoncontiguousBlockSlice{<:BlockIndexRange}) = Block(bs.block) # The following is adapted from Julia v0.7 base/multidimensional.jl # definition of CartesianRange diff --git a/src/views.jl b/src/views.jl index ec8a9952..97e884cb 100644 --- a/src/views.jl +++ b/src/views.jl @@ -11,7 +11,7 @@ function unblock(A, inds, I) end _blockslice(B, a::AbstractUnitRange) = BlockSlice(B, a) -_blockslice(B, a) = a # drop block structure +_blockslice(B, a) = NoncontiguousBlockSlice(B, a) # Allow `ones(2)[Block(1)[1:1], Block(1)[1:1]]` which is # similar to `ones(2)[1:1, 1:1]`. @@ -150,10 +150,11 @@ block(A::Block) = A @inline view(block_arr::AbstractBlockArray{<:Any,N}, blocks::Vararg{BlockSlice1, N}) where N = view(block_arr, map(block,blocks)...) -const BlockSlices = Union{Base.Slice,BlockSlice{<:BlockRange{1}}} +const BlockSlices = Union{Base.Slice,BlockSlice{<:BlockRange{1}},NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}} # view(V::SubArray{<:Any,N,NTuple{N,BlockSlices}}, _block_reindex(b::BlockSlice, i::Block{1}) = b.block[Int(i)] +_block_reindex(b::NoncontiguousBlockSlice, i::Block{1}) = b.block[Int(i)] _block_reindex(b::Slice, i::Block{1}) = i @inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,BlockSlices}}, block::Block{N}) where N = diff --git a/test/test_blockarrays.jl b/test/test_blockarrays.jl index 30457f2b..7afa2cb9 100644 --- a/test/test_blockarrays.jl +++ b/test/test_blockarrays.jl @@ -861,6 +861,22 @@ end @test A[Block(1)[1], Block(1)[1:1]] == BlockArray(A)[Block(1)[1], Block(1)[1:1]] == A[1,1:1] end + @testset "Non-contiguous BlockIndexRange" begin + a = BlockedArray(randn(5), [2,3]) + @test a[Block(2)[[1,3]]] == a[[3,5]] + A = BlockedArray(randn(5,5), [2,3], [2,3]) + @test A[Block(2,2)[[1,3],[2,3]]] == A[[3,5],[4,5]] + @test A[Block(2,2)[[1,3],1:2]] == A[[3,5],3:4] + end + + @testset "Nested block indexing" begin + a = BlockedArray(randn(4), [2,2]) + b = BlockedArray(randn(4), [2,2]) + A = mortar([a,b]) + @test A[Block(2)[Block(1)]] == A[Block(2)][Block(1)] == b[Block(1)] + @test A[Block(2)[Block(1)[2]]] == A[Block(2)][Block(1)[2]] == b[Block(1)[2]] + end + @testset "BlockIndexRange blocks" begin a = mortar([Block(1)[1:2], Block(3)[2:3]]) @test a[Block(1)] === Block(1)[1:2] diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl index 56d4bc00..17acb867 100644 --- a/test/test_blockindices.jl +++ b/test/test_blockindices.jl @@ -2,7 +2,7 @@ module TestBlockIndices using BlockArrays, FillArrays, Test, StaticArrays, ArrayLayouts using OffsetArrays -import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice +import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice, NoncontiguousBlockSlice @testset "Blocks" begin @test Int(Block(2)) === Integer(Block(2)) === Number(Block(2)) === 2 @@ -843,6 +843,16 @@ end end end +@testset "NoncontiguousBlockSlice" begin + b = NoncontiguousBlockSlice([Block(2), Block(1)], mortar([3:5, 1:2])) + @test length(b) == 5 + for i in eachindex(b.indices) + @test b[i] === b.indices[i] + end + @test b[Block(1)] === BlockSlice(Block(2), 3:5) + @test b[Block(2)] === BlockSlice(Block(1), 1:2) +end + #= [1,1 1,2] | [1,3 1,4 1,5] -------------------------- diff --git a/test/test_blockviews.jl b/test/test_blockviews.jl index f701b7b5..7deed82a 100644 --- a/test/test_blockviews.jl +++ b/test/test_blockviews.jl @@ -2,6 +2,7 @@ module TestBlockViews using BlockArrays, ArrayLayouts, Test using FillArrays +import BlockArrays: NoncontiguousBlockSlice # useds to force SubArray return bview(a, b) = Base.invoke(view, Tuple{AbstractArray,Any}, a, b) @@ -217,6 +218,22 @@ bview(a, b) = Base.invoke(view, Tuple{AbstractArray,Any}, a, b) @test A[Block.(2:3)] == A[2:end] end + @testset "getindex and view with Block-vector" begin + A = BlockArray(reshape(collect(1:(6*12)),6,12), 1:3, 3:5) + V = view(A, [Block(3),Block(2)], [Block(3),Block(2)]) + @test V[Block(1,1)] == A[Block(3,3)] + @test V[Block(2,1)] == A[Block(2,3)] + @test V[Block(1,2)] == A[Block(3,2)] + @test V[Block(2,2)] == A[Block(2,2)] + I = parentindices(V) + @test I[1] isa NoncontiguousBlockSlice{<:Vector{<:Block{1}}} + @test I[2] isa NoncontiguousBlockSlice{<:Vector{<:Block{1}}} + @test view(V, Block(1,1)) === view(A, Block(3,3)) + @test view(V, Block(2,1)) === view(A, Block(2,3)) + @test view(V, Block(1,2)) === view(A, Block(3,2)) + @test view(V, Block(2,2)) === view(A, Block(2,2)) + end + @testset "non-allocation blocksize" begin A = BlockArray(randn(5050), 1:100) @test blocksize(A) == (100,) @@ -314,7 +331,7 @@ bview(a, b) = Base.invoke(view, Tuple{AbstractArray,Any}, a, b) @test a[Block(1)[1:3]] ≡ view(a,Block(1)[1:3]) ≡ view(v,Block(1)[1:3]) ≡ 7:9 end - @testset "blockrange-of-blockreange" begin + @testset "blockrange-of-blockrange" begin a = mortar([7:9,5:6]) v = view(a,Block.(1:2)) @test view(v, Block(1)) ≡ 7:9 From c0d59c1818769d56140ab3ae54e267747db9e5e3 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 17:39:52 -0400 Subject: [PATCH 04/12] Small reorganization --- src/blockindices.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/blockindices.jl b/src/blockindices.jl index 01d5edb6..5a8309d9 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -270,6 +270,7 @@ struct BlockSlice{BB,T<:Integer,INDS<:AbstractUnitRange{T}} <: AbstractUnitRange end Block(bs::BlockSlice{<:Block}) = bs.block +Block(bs::BlockSlice{<:BlockIndexRange}) = Block(bs.block) for f in (:axes, :unsafe_indices, :axes1, :first, :last, :size, :length, @@ -289,8 +290,6 @@ _indices(B) = B # Avoid creating a SubArray wrapper in certain non-allocating cases @propagate_inbounds view(C::CartesianIndices{N}, bs::Vararg{BlockSlice,N}) where {N} = view(C, map(x->x.indices, bs)...) -Block(bs::BlockSlice{<:BlockIndexRange}) = Block(bs.block) - """ NoncontiguousBlockSlice(blocks, indices) @@ -309,6 +308,7 @@ struct NoncontiguousBlockSlice{BB,T,INDS<:AbstractVector{T}} <: AbstractVector{T end Block(bs::NoncontiguousBlockSlice{<:Block}) = bs.block +Block(bs::NoncontiguousBlockSlice{<:BlockIndexRange}) = Block(bs.block) for f in (:axes, :unsafe_indices, :axes1, :first, :last, :size, :length, :unsafe_length, :start) @@ -330,8 +330,6 @@ struct BlockRange{N,R<:NTuple{N,AbstractUnitRange{<:Integer}}} <: AbstractArray{ BlockRange{N,R}(inds::R) where {N,R} = new{N,R}(inds) end -Block(bs::NoncontiguousBlockSlice{<:BlockIndexRange}) = Block(bs.block) - # The following is adapted from Julia v0.7 base/multidimensional.jl # definition of CartesianRange From e383129ed35f7e7278c5fe011331fc3c5336964b Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 19:04:33 -0400 Subject: [PATCH 05/12] Improve code coverage --- test/test_blockindices.jl | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl index 17acb867..c912d0b3 100644 --- a/test/test_blockindices.jl +++ b/test/test_blockindices.jl @@ -826,6 +826,10 @@ end @test b[1:2] ≡ b[1:2][1:2] ≡ BlockSlice(Block(5)[1:2],1:2) @test Block(b) ≡ Block(5) + bi = BlockSlice(Block(2)[2:4],3:5) + @test Block(bi) ≡ Block(2) + @test bi[2:3] ≡ BlockSlice(Block(2)[3:4],4:5) + @testset "OneTo converts" begin for b in (BlockSlice(Block(1), 1:1), BlockSlice(Block.(1:1), 1:1), BlockSlice(Block(1)[1:1], 1:1)) @test convert(typeof(b), Base.OneTo(1)) ≡ b @@ -844,13 +848,26 @@ end end @testset "NoncontiguousBlockSlice" begin - b = NoncontiguousBlockSlice([Block(2), Block(1)], mortar([3:5, 1:2])) + b = NoncontiguousBlockSlice([Block(2),Block(1)], mortar([3:5,1:2])) @test length(b) == 5 for i in eachindex(b.indices) @test b[i] === b.indices[i] end @test b[Block(1)] === BlockSlice(Block(2), 3:5) @test b[Block(2)] === BlockSlice(Block(1), 1:2) + @test BlockArrays._indices(b) == mortar([3:5,1:2]) + + b = NoncontiguousBlockSlice(Block(3), 2:4) + @test b[2:3] == NoncontiguousBlockSlice(Block(3)[3:4], 3:4) + @test b[[1,3]] == NoncontiguousBlockSlice(Block(3)[[1,3]], [2,4]) + @test Block(b) === Block(3) + @test BlockArrays._indices(b) === 2:4 + + b = NoncontiguousBlockSlice(Block(3)[[2,4,6]], [3,5,7]) + @test b[2:3] == NoncontiguousBlockSlice(Block(3)[[4,6]], [5,7]) + @test b[[1,3]] == NoncontiguousBlockSlice(Block(3)[[2,6]], [3,7]) + @test Block(b) === Block(3) + @test BlockArrays._indices(b) == [3,5,7] end #= From d5555b0183a9b6d3628fe0900629bf342487b7b1 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 23 Jun 2025 22:02:23 -0400 Subject: [PATCH 06/12] Fix type constraint --- src/blockindices.jl | 2 +- test/test_blockindices.jl | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/blockindices.jl b/src/blockindices.jl index 5a8309d9..5cf7c518 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -320,7 +320,7 @@ _indices(B::NoncontiguousBlockSlice) = B.indices @propagate_inbounds getindex(S::NoncontiguousBlockSlice, i::Integer) = getindex(S.indices, i) @propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:Block{1}}, k::AbstractVector{<:Integer}) = NoncontiguousBlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) -@propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:BlockIndexRange{1,Tuple{AbstractVector}}}, k::AbstractVector{<:Integer}) = +@propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:BlockIndexRange{1,<:Tuple{AbstractVector}}}, k::AbstractVector{<:Integer}) = NoncontiguousBlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) @propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}, k::Block{1}) = BlockSlice(S.block[Int(k)], getindex(S.indices, k)) diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl index c912d0b3..cc0ed845 100644 --- a/test/test_blockindices.jl +++ b/test/test_blockindices.jl @@ -864,10 +864,18 @@ end @test BlockArrays._indices(b) === 2:4 b = NoncontiguousBlockSlice(Block(3)[[2,4,6]], [3,5,7]) - @test b[2:3] == NoncontiguousBlockSlice(Block(3)[[4,6]], [5,7]) - @test b[[1,3]] == NoncontiguousBlockSlice(Block(3)[[2,6]], [3,7]) + @test b isa NoncontiguousBlockSlice{<:BlockIndexRange{1}} @test Block(b) === Block(3) @test BlockArrays._indices(b) == [3,5,7] + @test b[2:3] == NoncontiguousBlockSlice(Block(3)[[4,6]], [5,7]) + @test b[2:3] isa NoncontiguousBlockSlice{<:BlockIndexRange{1}} + @test Block(b) === Block(3) + @test Block(b[2:3]) === Block(3) + @test BlockArrays._indices(b[2:3]) == [5,7] + @test b[[1,3]] == NoncontiguousBlockSlice(Block(3)[[2,6]], [3,7]) + @test b[[1,3]] isa NoncontiguousBlockSlice{<:BlockIndexRange{1}} + @test Block(b[[1,3]]) === Block(3) + @test BlockArrays._indices(b[[1,3]]) == [3,7] end #= From 796b8643ecc0d574f517c43e4c349d96377a4496 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Tue, 1 Jul 2025 11:32:03 -0400 Subject: [PATCH 07/12] Clean up docstring, fix tests --- src/blockindices.jl | 1 - test/test_blockrange.jl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/blockindices.jl b/src/blockindices.jl index dfe91c06..86acd747 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -307,7 +307,6 @@ _indices(B) = B """ NoncontiguousBlockSlice(blocks, indices) -<<<<<<< HEAD Represents an AbstractVector of indices attached to a (potentially non-contiguous) subblock, set of blocks, or set of subblocks. This is the generalization of `BlockSlice` to non-contiguous slices. diff --git a/test/test_blockrange.jl b/test/test_blockrange.jl index a5a1b179..ffb2ba2b 100644 --- a/test/test_blockrange.jl +++ b/test/test_blockrange.jl @@ -32,7 +32,7 @@ using BlockArrays, Test V = view(A, [Block(3), Block(2)]) @test V == [4, 5, 6, 2, 3] I = parentindices(V)[1] - @test I isa BlockArrays.BlockedSlice{<:Vector{<:Block{1}}} + @test I isa BlockArrays.NoncontiguousBlockSlice{<:Vector{<:Block{1}}} @test V[Block(1)] == 4:6 @test V[Block(2)] == 2:3 @test view(V, Block(1)) === view(A, Block(3)) From b424e2a83a14d341b65539a5ddec06e4860aad58 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Wed, 9 Jul 2025 14:21:45 -0400 Subject: [PATCH 08/12] Rename BlockSlices to AnyBlockSlice --- src/views.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/views.jl b/src/views.jl index 9d439316..8d1c56dd 100644 --- a/src/views.jl +++ b/src/views.jl @@ -119,7 +119,7 @@ end end -# BlockSlices map the blocks and the indices +# AnyBlockSlice map the blocks and the indices # this is loosely based on Slice reindex in subarray.jl @propagate_inbounds reindex(idxs::Tuple{BlockSlice{<:BlockRange}, Vararg{Any}}, subidxs::Tuple{BlockSlice{<:BlockRange}, Vararg{Any}}) = @@ -150,18 +150,18 @@ block(A::Block) = A @inline view(block_arr::AbstractBlockArray{<:Any,N}, blocks::Vararg{BlockSlice1, N}) where N = view(block_arr, map(block,blocks)...) -const BlockSlices = Union{Base.Slice,BlockSlice{<:BlockRange{1}},NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}} -# view(V::SubArray{<:Any,N,NTuple{N,BlockSlices}}, +const AnyBlockSlice = Union{Base.Slice,BlockSlice{<:BlockRange{1}},NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}} +# view(V::SubArray{<:Any,N,NTuple{N,AnyBlockSlice}}, _block_reindex(b::BlockSlice, i::Block{1}) = b.block[Int(i)] _block_reindex(b::NoncontiguousBlockSlice, i::Block{1}) = b.block[Int(i)] _block_reindex(b::Slice, i::Block{1}) = i -@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,BlockSlices}}, block::Block{N}) where N = +@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,AnyBlockSlice}}, block::Block{N}) where N = view(parent(V), _block_reindex.(parentindices(V), Block.(block.n))...) -@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,BlockSlices}}, block::Vararg{Block{1},N}) where N = +@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,AnyBlockSlice}}, block::Vararg{Block{1},N}) where N = view(parent(V), _block_reindex.(parentindices(V), block)...) -@inline view(V::SubArray{<:Any,1,<:AbstractBlockArray,<:Tuple{BlockSlices}}, block::Block{1}) = +@inline view(V::SubArray{<:Any,1,<:AbstractBlockArray,<:Tuple{AnyBlockSlice}}, block::Block{1}) = view(parent(V), _block_reindex(parentindices(V)[1], block)) From dbc2aece7f6a02aa9fb3c1c8afdae529e842e5ae Mon Sep 17 00:00:00 2001 From: mtfishman Date: Wed, 9 Jul 2025 14:38:46 -0400 Subject: [PATCH 09/12] Change name back --- src/views.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/views.jl b/src/views.jl index 8d1c56dd..9d439316 100644 --- a/src/views.jl +++ b/src/views.jl @@ -119,7 +119,7 @@ end end -# AnyBlockSlice map the blocks and the indices +# BlockSlices map the blocks and the indices # this is loosely based on Slice reindex in subarray.jl @propagate_inbounds reindex(idxs::Tuple{BlockSlice{<:BlockRange}, Vararg{Any}}, subidxs::Tuple{BlockSlice{<:BlockRange}, Vararg{Any}}) = @@ -150,18 +150,18 @@ block(A::Block) = A @inline view(block_arr::AbstractBlockArray{<:Any,N}, blocks::Vararg{BlockSlice1, N}) where N = view(block_arr, map(block,blocks)...) -const AnyBlockSlice = Union{Base.Slice,BlockSlice{<:BlockRange{1}},NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}} -# view(V::SubArray{<:Any,N,NTuple{N,AnyBlockSlice}}, +const BlockSlices = Union{Base.Slice,BlockSlice{<:BlockRange{1}},NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}} +# view(V::SubArray{<:Any,N,NTuple{N,BlockSlices}}, _block_reindex(b::BlockSlice, i::Block{1}) = b.block[Int(i)] _block_reindex(b::NoncontiguousBlockSlice, i::Block{1}) = b.block[Int(i)] _block_reindex(b::Slice, i::Block{1}) = i -@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,AnyBlockSlice}}, block::Block{N}) where N = +@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,BlockSlices}}, block::Block{N}) where N = view(parent(V), _block_reindex.(parentindices(V), Block.(block.n))...) -@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,AnyBlockSlice}}, block::Vararg{Block{1},N}) where N = +@inline view(V::SubArray{<:Any,N,<:AbstractBlockArray,<:NTuple{N,BlockSlices}}, block::Vararg{Block{1},N}) where N = view(parent(V), _block_reindex.(parentindices(V), block)...) -@inline view(V::SubArray{<:Any,1,<:AbstractBlockArray,<:Tuple{AnyBlockSlice}}, block::Block{1}) = +@inline view(V::SubArray{<:Any,1,<:AbstractBlockArray,<:Tuple{BlockSlices}}, block::Block{1}) = view(parent(V), _block_reindex(parentindices(V)[1], block)) From 39555bdbb2108947c442396543f029c0bcd561dc Mon Sep 17 00:00:00 2001 From: mtfishman Date: Wed, 9 Jul 2025 18:21:32 -0400 Subject: [PATCH 10/12] Introduce BlockIndices as non-contiguous version of BlockIndexRange, BlockIndexRange as alias --- docs/src/lib/internals.md | 2 ++ src/BlockArrays.jl | 2 +- src/blockaxis.jl | 12 +++---- src/blockbroadcast.jl | 2 +- src/blockindices.jl | 70 +++++++++++++++++++++++---------------- src/show.jl | 4 +-- src/views.jl | 30 ++++++++--------- test/test_blockindices.jl | 24 +++++++------- 8 files changed, 81 insertions(+), 65 deletions(-) diff --git a/docs/src/lib/internals.md b/docs/src/lib/internals.md index 780cafaa..6b0a2264 100644 --- a/docs/src/lib/internals.md +++ b/docs/src/lib/internals.md @@ -23,7 +23,9 @@ BlockedOneTo BlockedUnitRange BlockRange BlockIndexRange +BlockIndices BlockSlice +NoncontiguousBlockSlice unblock SubBlockIterator blockcheckbounds_indices diff --git a/src/BlockArrays.jl b/src/BlockArrays.jl index 15341192..f113f427 100644 --- a/src/BlockArrays.jl +++ b/src/BlockArrays.jl @@ -4,7 +4,7 @@ using LinearAlgebra, ArrayLayouts, FillArrays # AbstractBlockArray interface exports export AbstractBlockArray, AbstractBlockMatrix, AbstractBlockVector, AbstractBlockVecOrMat export Block, getblock, getblock!, setblock!, eachblock, blocks -export blockaxes, blocksize, blocklength, blockcheckbounds, BlockBoundsError, BlockIndex +export blockaxes, blocksize, blocklength, blockcheckbounds, BlockBoundsError, BlockIndex, BlockIndexRange, BlockIndices export blocksizes, blocklengths, blocklasts, blockfirsts, blockisequal export BlockRange, blockedrange, BlockedUnitRange, BlockedOneTo diff --git a/src/blockaxis.jl b/src/blockaxis.jl index b6c151ae..7bd20814 100644 --- a/src/blockaxis.jl +++ b/src/blockaxis.jl @@ -7,9 +7,9 @@ @propagate_inbounds getindex(b::AbstractArray, K::BlockIndex{1}, J::BlockIndex{1}...) = b[BlockIndex(tuple(K, J...))] -@propagate_inbounds getindex(b::AbstractArray{T,N}, K::BlockIndexRange{N}) where {T,N} = b[block(K)][K.indices...] -@propagate_inbounds getindex(b::LayoutArray{T,N}, K::BlockIndexRange{N}) where {T,N} = b[block(K)][K.indices...] -@propagate_inbounds getindex(b::LayoutArray{T,1}, K::BlockIndexRange{1}) where {T} = b[block(K)][K.indices...] +@propagate_inbounds getindex(b::AbstractArray{T,N}, K::BlockIndices{N}) where {T,N} = b[block(K)][K.indices...] +@propagate_inbounds getindex(b::LayoutArray{T,N}, K::BlockIndices{N}) where {T,N} = b[block(K)][K.indices...] +@propagate_inbounds getindex(b::LayoutArray{T,1}, K::BlockIndices{1}) where {T} = b[block(K)][K.indices...] function findblockindex(b::AbstractVector, k::Integer) @boundscheck k in b || throw(BoundsError()) @@ -406,9 +406,9 @@ end @propagate_inbounds getindex(b::AbstractBlockedUnitRange, KR::BlockSlice) = b[KR.block] getindex(b::AbstractBlockedUnitRange, KR::AbstractVector{<:Block{1}}) = mortar([b[K] for K in KR]) -getindex(b::AbstractBlockedUnitRange, KR::AbstractVector{<:BlockIndexRange{1}}) = mortar([b[K] for K in KR]) +getindex(b::AbstractBlockedUnitRange, KR::AbstractVector{<:BlockIndices{1}}) = mortar([b[K] for K in KR]) getindex(b::AbstractBlockedUnitRange, KR::AbstractVector{<:BlockIndex{1}}) = [b[K] for K in KR] -getindex(b::AbstractBlockedUnitRange, Kkr::BlockIndexRange{1}) = b[block(Kkr)][Kkr.indices...] +getindex(b::AbstractBlockedUnitRange, Kkr::BlockIndices{1}) = b[block(Kkr)][Kkr.indices...] _searchsortedfirst(a::AbstractVector, k) = searchsortedfirst(a, k) function _searchsortedfirst(a::Tuple, k) @@ -431,7 +431,7 @@ Base.dataids(b::AbstractBlockedUnitRange) = Base.dataids(blocklasts(b)) Base.checkindex(::Type{Bool}, b::BlockRange, K::Integer) = checkindex(Bool, Integer.(b), K) Base.checkindex(::Type{Bool}, b::AbstractUnitRange{<:Integer}, K::Block{1}) = checkindex(Bool, blockaxes(b,1), Integer(K)) -function Base.checkindex(::Type{Bool}, axis::AbstractBlockedUnitRange, ind::BlockIndexRange{1}) +function Base.checkindex(::Type{Bool}, axis::AbstractBlockedUnitRange, ind::BlockIndices{1}) checkindex(Bool, axis, first(ind)) && checkindex(Bool, axis, last(ind)) end function Base.checkindex(::Type{Bool}, axis::AbstractBlockedUnitRange, ind::BlockIndex{1}) diff --git a/src/blockbroadcast.jl b/src/blockbroadcast.jl index d386b313..b936a80b 100644 --- a/src/blockbroadcast.jl +++ b/src/blockbroadcast.jl @@ -87,7 +87,7 @@ julia> itr = SubBlockIterator(subblock_lasts, block_lasts) SubBlockIterator([1, 3, 6], [1, 3, 4, 6]) julia> collect(itr) -4-element Vector{BlockArrays.BlockIndexRange{1, Tuple{UnitRange{Int64}}, Tuple{Int64}, Int64}}: +4-element Vector{BlockIndexRange{1, Tuple{UnitRange{Int64}}, Tuple{Int64}, Int64}}: Block(1)[1:1] Block(2)[1:2] Block(3)[1:1] diff --git a/src/blockindices.jl b/src/blockindices.jl index 86acd747..59b44dd9 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -184,72 +184,86 @@ end checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::AbstractArray{<:BlockIndex{N}}) where N = all(i -> checkbounds(Bool, A, i), I) -struct BlockIndexRange{N,R<:Tuple{Vararg{AbstractVector,N}},I<:Tuple{Vararg{Any,N}},BI} <: AbstractArray{BlockIndex{N,NTuple{N,BI},I},N} +struct BlockIndices{N,R<:Tuple{Vararg{AbstractVector,N}},I<:Tuple{Vararg{Any,N}},BI} <: AbstractArray{BlockIndex{N,NTuple{N,BI},I},N} block::Block{N,BI} indices::R - function BlockIndexRange(block::Block{N,BI}, inds::R) where {N,BI<:Integer,R<:Tuple{Vararg{AbstractVector,N}}} + function BlockIndices(block::Block{N,BI}, inds::R) where {N,BI<:Integer,R<:Tuple{Vararg{AbstractVector,N}}} I = Tuple{eltype.(inds)...} return new{N,R,I,BI}(block,inds) end end +""" + BlockIndices(block, startind:stopind) + +Represents a cartesian product of indices inside a block. +""" +BlockIndices + +BlockIndices(block::Block{N}, inds::Vararg{AbstractVector,N}) where {N} = + BlockIndices(block,inds) + +const BlockIndexRange{N,R<:Tuple{Vararg{AbstractUnitRange{<:Integer},N}},I<:Tuple{Vararg{Any,N}},BI} = BlockIndices{N,R,I,BI} + """ BlockIndexRange(block, startind:stopind) -represents a cartesian range inside a block. +Represents a cartesian range inside a block. Type alias for `BlockIndices` with +the indices constrained to ranges. """ BlockIndexRange -BlockIndexRange(block::Block{N}, inds::Vararg{AbstractVector,N}) where {N} = - BlockIndexRange(block,inds) +BlockIndexRange(block::Block{N}, inds::Tuple{Vararg{AbstractUnitRange{<:Integer},N}}) where {N} = + BlockIndices(block, inds) +BlockIndexRange(block::Block{N}, inds::Vararg{AbstractUnitRange{<:Integer},N}) where {N} = + BlockIndices(block, inds) -block(R::BlockIndexRange) = R.block +block(R::BlockIndices) = R.block -copy(R::BlockIndexRange) = BlockIndexRange(R.block, map(copy, R.indices)) +copy(R::BlockIndices) = BlockIndices(R.block, map(copy, R.indices)) getindex(::Block{0}) = BlockIndex() getindex(B::Block{N}, inds::Vararg{Any,N}) where N = BlockIndex(B,inds) -getindex(B::Block{N}, inds::Vararg{AbstractVector,N}) where N = BlockIndexRange(B,inds) +getindex(B::Block{N}, inds::Vararg{AbstractVector,N}) where N = BlockIndices(B,inds) getindex(B::Block{1}, inds::Colon) = B getindex(B::Block{1}, inds::Base.Slice) = B +getindex(B::BlockIndices{0}) = B.block[] +@propagate_inbounds getindex(B::BlockIndices{N}, kr::Vararg{AbstractVector,N}) where N = BlockIndices(B.block, map(getindex, B.indices, kr)) +@propagate_inbounds getindex(B::BlockIndices{N}, inds::Vararg{Int,N}) where N = B.block[Base.reindex(B.indices, inds)...] -getindex(B::BlockIndexRange{0}) = B.block[] -@propagate_inbounds getindex(B::BlockIndexRange{N}, kr::Vararg{AbstractVector,N}) where N = BlockIndexRange(B.block, map(getindex, B.indices, kr)) -@propagate_inbounds getindex(B::BlockIndexRange{N}, inds::Vararg{Int,N}) where N = B.block[Base.reindex(B.indices, inds)...] - -eltype(R::BlockIndexRange) = eltype(typeof(R)) -eltype(::Type{BlockIndexRange{N}}) where {N} = BlockIndex{N} -eltype(::Type{BlockIndexRange{N,R,I,BI}}) where {N,R,I,BI} = BlockIndex{N,NTuple{N,BI},I} -IteratorSize(::Type{<:BlockIndexRange}) = Base.HasShape{1}() +eltype(R::BlockIndices) = eltype(typeof(R)) +eltype(::Type{BlockIndices{N}}) where {N} = BlockIndex{N} +eltype(::Type{BlockIndices{N,R,I,BI}}) where {N,R,I,BI} = BlockIndex{N,NTuple{N,BI},I} +IteratorSize(::Type{<:BlockIndices}) = Base.HasShape{1}() -first(iter::BlockIndexRange) = BlockIndex(iter.block.n, map(first, iter.indices)) -last(iter::BlockIndexRange) = BlockIndex(iter.block.n, map(last, iter.indices)) +first(iter::BlockIndices) = BlockIndex(iter.block.n, map(first, iter.indices)) +last(iter::BlockIndices) = BlockIndex(iter.block.n, map(last, iter.indices)) -@inline function iterate(iter::BlockIndexRange) +@inline function iterate(iter::BlockIndices) iterfirst, iterlast = first(iter), last(iter) if any(map(>, iterfirst.α, iterlast.α)) return nothing end iterfirst, iterfirst end -@inline function iterate(iter::BlockIndexRange, state) +@inline function iterate(iter::BlockIndices, state) nextstate = BlockIndex(state.I, inc(state.α, first(iter).α, last(iter).α)) nextstate.α[end] > last(iter.indices[end]) && return nothing nextstate, nextstate end -size(iter::BlockIndexRange) = map(dimlength, first(iter).α, last(iter).α) -length(iter::BlockIndexRange) = prod(size(iter)) +size(iter::BlockIndices) = map(dimlength, first(iter).α, last(iter).α) +length(iter::BlockIndices) = prod(size(iter)) -Block(bs::BlockIndexRange) = bs.block +Block(bs::BlockIndices) = bs.block ## # checkindex ## -function checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::BlockIndexRange{N}) where N +function checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::BlockIndices{N}) where N bl = block(I) checkbounds(Bool, A, bl) || return false # TODO: Replace with `eachblockaxes(A)[bl]` once that is defined. @@ -284,7 +298,7 @@ struct BlockSlice{BB,T<:Integer,INDS<:AbstractUnitRange{T}} <: AbstractUnitRange end Block(bs::BlockSlice{<:Block}) = bs.block -Block(bs::BlockSlice{<:BlockIndexRange}) = Block(bs.block) +Block(bs::BlockSlice{<:BlockIndices}) = Block(bs.block) for f in (:axes, :unsafe_indices, :axes1, :first, :last, :size, :length, @@ -298,7 +312,7 @@ _indices(B) = B @propagate_inbounds getindex(S::BlockSlice, i::Integer) = getindex(S.indices, i) @propagate_inbounds getindex(S::BlockSlice{<:Block{1}}, k::AbstractUnitRange{<:Integer}) = BlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) -@propagate_inbounds getindex(S::BlockSlice{<:BlockIndexRange{1,<:Tuple{AbstractUnitRange{<:Integer}}}}, k::AbstractUnitRange{<:Integer}) = +@propagate_inbounds getindex(S::BlockSlice{<:BlockIndexRange{1}}, k::AbstractUnitRange{<:Integer}) = BlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) # Avoid creating a SubArray wrapper in certain non-allocating cases @@ -322,7 +336,7 @@ struct NoncontiguousBlockSlice{BB,T,INDS<:AbstractVector{T}} <: AbstractVector{T end Block(bs::NoncontiguousBlockSlice{<:Block}) = bs.block -Block(bs::NoncontiguousBlockSlice{<:BlockIndexRange}) = Block(bs.block) +Block(bs::NoncontiguousBlockSlice{<:BlockIndices}) = Block(bs.block) for f in (:axes, :unsafe_indices, :axes1, :first, :last, :size, :length, :unsafe_length, :start) @@ -334,7 +348,7 @@ _indices(B::NoncontiguousBlockSlice) = B.indices @propagate_inbounds getindex(S::NoncontiguousBlockSlice, i::Integer) = getindex(S.indices, i) @propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:Block{1}}, k::AbstractVector{<:Integer}) = NoncontiguousBlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) -@propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:BlockIndexRange{1,<:Tuple{AbstractVector}}}, k::AbstractVector{<:Integer}) = +@propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:BlockIndices{1,<:Tuple{AbstractVector}}}, k::AbstractVector{<:Integer}) = NoncontiguousBlockSlice(S.block[_indices(k)], S.indices[_indices(k)]) @propagate_inbounds getindex(S::NoncontiguousBlockSlice{<:AbstractVector{<:Block{1}}}, k::Block{1}) = BlockSlice(S.block[Int(k)], getindex(S.indices, k)) diff --git a/src/show.jl b/src/show.jl index 1a66ef88..2544e94d 100644 --- a/src/show.jl +++ b/src/show.jl @@ -186,13 +186,13 @@ function Base.show(io::IO, B::BlockIndex) print(io, "]") end -function Base.show(io::IO, B::BlockIndexRange) +function Base.show(io::IO, B::BlockIndices) show(io, Block(B)) print(io, "[") print_tuple_elements(io, B.indices) print(io, "]") end -Base.show(io::IO, ::MIME"text/plain", B::BlockIndexRange) = show(io, B) +Base.show(io::IO, ::MIME"text/plain", B::BlockIndices) = show(io, B) Base.show(io::IO, mimetype::MIME"text/plain", a::AbstractBlockedUnitRange) = Base.invoke(show, Tuple{typeof(io),MIME"text/plain",AbstractArray},io, mimetype, a) diff --git a/src/views.jl b/src/views.jl index 9d439316..9c8ebcad 100644 --- a/src/views.jl +++ b/src/views.jl @@ -20,7 +20,7 @@ unblock(A, ::Tuple{}, I) = BlockSlice(first(I),Base.OneTo(length(I[1]))) to_index(::Block) = throw(ArgumentError("Block must be converted by to_indices(...)")) to_index(::BlockIndex) = throw(ArgumentError("BlockIndex must be converted by to_indices(...)")) -to_index(::BlockIndexRange) = throw(ArgumentError("BlockIndexRange must be converted by to_indices(...)")) +to_index(::BlockIndices) = throw(ArgumentError("BlockIndices must be converted by to_indices(...)")) to_index(::BlockRange) = throw(ArgumentError("BlockRange must be converted by to_indices(...)")) @@ -30,13 +30,13 @@ to_index(::BlockRange) = throw(ArgumentError("BlockRange must be converted by to (unblock(A, inds, I), to_indices(A, _maybetail(inds), tail(I))...) @inline to_indices(A, inds, I::Tuple{BlockIndex{1}, Vararg{Any}}) = (inds[1][I[1]], to_indices(A, _maybetail(inds), tail(I))...) -@inline to_indices(A, inds, I::Tuple{BlockIndexRange{1,R}, Vararg{Any}}) where R = +@inline to_indices(A, inds, I::Tuple{BlockIndices{1,R}, Vararg{Any}}) where R = (unblock(A, inds, I), to_indices(A, _maybetail(inds), tail(I))...) @inline to_indices(A, inds, I::Tuple{AbstractVector{Block{1,R}}, Vararg{Any}}) where R = (unblock(A, inds, I), to_indices(A, _maybetail(inds), tail(I))...) @inline to_indices(A, inds, I::Tuple{AbstractVector{<:BlockIndex{1}}, Vararg{Any}}) = (unblock(A, inds, I), to_indices(A, _maybetail(inds), tail(I))...) -@inline to_indices(A, inds, I::Tuple{AbstractVector{<:BlockIndexRange{1}}, Vararg{Any}}) = +@inline to_indices(A, inds, I::Tuple{AbstractVector{<:BlockIndices{1}}, Vararg{Any}}) = (unblock(A, inds, I), to_indices(A, _maybetail(inds), tail(I))...) @@ -46,22 +46,22 @@ to_index(::BlockRange) = throw(ArgumentError("BlockRange must be converted by to to_indices(A, inds, (Block.(I[1].n)..., tail(I)...)) @inline to_indices(A, inds, I::Tuple{BlockIndex, Vararg{Any}}) = to_indices(A, inds, (BlockIndex.(I[1].I, I[1].α)..., tail(I)...)) -@inline to_indices(A, inds, I::Tuple{BlockIndexRange, Vararg{Any}}) = - to_indices(A, inds, (BlockIndexRange.(Block.(I[1].block.n), tuple.(I[1].indices))..., tail(I)...)) +@inline to_indices(A, inds, I::Tuple{BlockIndices, Vararg{Any}}) = + to_indices(A, inds, (BlockIndices.(Block.(I[1].block.n), tuple.(I[1].indices))..., tail(I)...)) @inline to_indices(A, inds, I::Tuple{BlockRange, Vararg{Any}}) = to_indices(A, inds, (BlockRange.(tuple.(I[1].indices))..., tail(I)...)) # In 0.7, we need to override to_indices to avoid calling linearindices -@inline to_indices(A, I::Tuple{BlockIndexRange, Vararg{Any}}) = to_indices(A, axes(A), I) +@inline to_indices(A, I::Tuple{BlockIndices, Vararg{Any}}) = to_indices(A, axes(A), I) @inline to_indices(A, I::Tuple{Block, Vararg{Any}}) = to_indices(A, axes(A), I) @inline to_indices(A, I::Tuple{BlockRange, Vararg{Any}}) = to_indices(A, axes(A), I) @inline to_indices(A, I::Tuple{AbstractVector{<:Block{1}}, Vararg{Any}}) = to_indices(A, axes(A), I) @inline to_indices(A, I::Tuple{AbstractVector{<:BlockIndex{1}}, Vararg{Any}}) = to_indices(A, axes(A), I) -@inline to_indices(A, I::Tuple{AbstractVector{<:BlockIndexRange{1}}, Vararg{Any}}) = to_indices(A, axes(A), I) +@inline to_indices(A, I::Tuple{AbstractVector{<:BlockIndices{1}}, Vararg{Any}}) = to_indices(A, axes(A), I) @propagate_inbounds reindex(idxs::Tuple{BlockSlice{<:BlockRange}, Vararg{Any}}, - subidxs::Tuple{BlockSlice{<:BlockIndexRange}, Vararg{Any}}) = - (BlockSlice(BlockIndexRange(Block(idxs[1].block.indices[1][Int(subidxs[1].block.block)]), + subidxs::Tuple{BlockSlice{<:BlockIndices}, Vararg{Any}}) = + (BlockSlice(BlockIndices(Block(idxs[1].block.indices[1][Int(subidxs[1].block.block)]), subidxs[1].block.indices), idxs[1].indices[subidxs[1].indices]), reindex(tail(idxs), tail(subidxs))...) @@ -84,28 +84,28 @@ _splatmap(f, t::Tuple) = (f(t[1])..., _splatmap(f, tail(t))...) # path in `AbstractBlockStyle` broadcasting. @propagate_inbounds function Base.unsafe_view( A::BlockArray{<:Any, N}, - I::Vararg{BlockSlice{<:BlockIndexRange{1}}, N}) where {N} + I::Vararg{BlockSlice{<:BlockIndices{1}}, N}) where {N} B = view(A, map(block, I)...) return view(B, _splatmap(x -> x.block.indices, I)...) end @propagate_inbounds function Base.unsafe_view( A::BlockedArray{<:Any, N}, - I::Vararg{BlockSlice{<:BlockIndexRange{1}}, N}) where {N} + I::Vararg{BlockSlice{<:BlockIndices{1}}, N}) where {N} return view(A.blocks, map(x -> x.indices, I)...) end @propagate_inbounds function Base.unsafe_view( A::ReshapedArray{<:Any, N, <:AbstractBlockArray{<:Any, M}}, - I::Vararg{BlockSlice{<:BlockIndexRange{1}}, N}) where {N, M} + I::Vararg{BlockSlice{<:BlockIndices{1}}, N}) where {N, M} # Note: assuming that I[M+1:end] are verified to be singletons return reshape(view(A.parent, I[1:M]...), Val(N)) end @propagate_inbounds function Base.unsafe_view( A::Array, - I1::BlockSlice{<:BlockIndexRange{1}}, - Is::Vararg{BlockSlice{<:BlockIndexRange{1}}}, + I1::BlockSlice{<:BlockIndices{1}}, + Is::Vararg{BlockSlice{<:BlockIndices{1}}}, ) I = (I1, Is...) @assert ndims(A) == length(I) @@ -113,7 +113,7 @@ end end # make sure we reindex correctrly -@inline function Base._maybe_reindex(V, I::Tuple{BlockSlice{<:BlockIndexRange{1}}, Vararg{Any}}, ::Tuple{}) +@inline function Base._maybe_reindex(V, I::Tuple{BlockSlice{<:BlockIndices{1}}, Vararg{Any}}, ::Tuple{}) @inbounds idxs = to_indices(V.parent, reindex(V.indices, I)) view(V.parent, idxs...) end diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl index cb70b8c5..10ce6a32 100644 --- a/test/test_blockindices.jl +++ b/test/test_blockindices.jl @@ -2,7 +2,7 @@ module TestBlockIndices using BlockArrays, FillArrays, Test, StaticArrays, ArrayLayouts using OffsetArrays -import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice, NoncontiguousBlockSlice +import BlockArrays: BlockIndex, BlockIndexRange, BlockIndices, BlockSlice, NoncontiguousBlockSlice @testset "Blocks" begin @test Int(Block(2)) === Integer(Block(2)) === Number(Block(2)) === 2 @@ -88,16 +88,16 @@ import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice, NoncontiguousBlockS @test Block(1)[1] == BlockIndex((1,),(1,)) @test Block(1)[Block(1)] == BlockIndex((1,),(Block(1),)) @test Block(1)[1:2] == BlockIndexRange(Block(1),(1:2,)) - @test Block(1)[[1,3]] == BlockIndexRange(Block(1),([1,3],)) + @test Block(1)[[1,3]] == BlockIndices(Block(1),([1,3],)) @test Block(1,1)[1,1] == BlockIndex((1,1),(1,1)) == BlockIndex((1,1),(1,)) @test Block(1,1)[1:2,1:2] == BlockIndexRange(Block(1,1),(1:2,1:2)) - @test Block(1,1)[[1,3],1:2] == BlockIndexRange(Block(1,1),([1,3],1:2)) + @test Block(1,1)[[1,3],1:2] == BlockIndices(Block(1,1),([1,3],1:2)) @test Block(1)[1:3][1:2] == BlockIndexRange(Block(1),1:2) - @test Block(1)[[1,3,5]][[1,3]] == BlockIndexRange(Block(1),[1,5]) - @test Block(1)[[1,3,5]][2:3] == BlockIndexRange(Block(1),[3,5]) - @test Block(1)[2:4][[1,3]] == BlockIndexRange(Block(1),[2,4]) + @test Block(1)[[1,3,5]][[1,3]] == BlockIndices(Block(1),[1,5]) + @test Block(1)[[1,3,5]][2:3] == BlockIndices(Block(1),[3,5]) + @test Block(1)[2:4][[1,3]] == BlockIndices(Block(1),[2,4]) @test Block(1,1)[1:3,1:3][1:2,1:2] == BlockIndexRange(Block(1,1),1:2,1:2) - @test Block(1,1)[1:3,1:3][1:2,[1,3]] == BlockIndexRange(Block(1,1),1:2,[1,3]) + @test Block(1,1)[1:3,1:3][1:2,[1,3]] == BlockIndices(Block(1,1),1:2,[1,3]) @test Block(1,1)[2:4,2:4][2:3,2:3] == BlockIndexRange(Block(1,1),(3:4,3:4)) @test BlockIndexRange(Block(),())[] == BlockIndex() @test BlockIndex((2,2,2),(2,)) == BlockIndex((2,2,2),(2,1,)) == BlockIndex((2,2,2),(2,1,1)) @@ -107,8 +107,8 @@ import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice, NoncontiguousBlockS @test BlockIndex(Block(2),UInt(2)) === BlockIndex(Block(2),(UInt(2),)) @test BlockIndex(Block(2),Block(2)) === BlockIndex(Block(2),(Block(2),)) @test copy(Block(1)[1:2]) === BlockIndexRange(Block(1),1:2) - @test copy(Block(1)[[1,3]]) == BlockIndexRange(Block(1),[1,3]) - @test copy(Block(1)[[1,3]]) ≢ BlockIndexRange(Block(1),[1,3]) + @test copy(Block(1)[[1,3]]) == BlockIndices(Block(1),[1,3]) + @test copy(Block(1)[[1,3]]) ≢ BlockIndices(Block(1),[1,3]) end @testset "BlockRange" begin @@ -878,16 +878,16 @@ end @test BlockArrays._indices(b) === 2:4 b = NoncontiguousBlockSlice(Block(3)[[2,4,6]], [3,5,7]) - @test b isa NoncontiguousBlockSlice{<:BlockIndexRange{1}} + @test b isa NoncontiguousBlockSlice{<:BlockIndices{1}} @test Block(b) === Block(3) @test BlockArrays._indices(b) == [3,5,7] @test b[2:3] == NoncontiguousBlockSlice(Block(3)[[4,6]], [5,7]) - @test b[2:3] isa NoncontiguousBlockSlice{<:BlockIndexRange{1}} + @test b[2:3] isa NoncontiguousBlockSlice{<:BlockIndices{1}} @test Block(b) === Block(3) @test Block(b[2:3]) === Block(3) @test BlockArrays._indices(b[2:3]) == [5,7] @test b[[1,3]] == NoncontiguousBlockSlice(Block(3)[[2,6]], [3,7]) - @test b[[1,3]] isa NoncontiguousBlockSlice{<:BlockIndexRange{1}} + @test b[[1,3]] isa NoncontiguousBlockSlice{<:BlockIndices{1}} @test Block(b[[1,3]]) === Block(3) @test BlockArrays._indices(b[[1,3]]) == [3,7] end From afb3ddd56824ddc29738f2706ec47664a6548b72 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Wed, 9 Jul 2025 19:01:58 -0400 Subject: [PATCH 11/12] Merge BlockIndices --- src/blockindices.jl | 39 +++++++++++++++++++++++++++++++++++++++ test/test_blockindices.jl | 1 + 2 files changed, 40 insertions(+) diff --git a/src/blockindices.jl b/src/blockindices.jl index 7f7b0afd..d1c6372e 100644 --- a/src/blockindices.jl +++ b/src/blockindices.jl @@ -220,11 +220,50 @@ end BlockIndices(block, startind:stopind) Represents a cartesian product of indices inside a block. + +It can be constructed and used to index into `BlockArrays` in the following manner: + +```jldoctest +julia> BlockIndices(Block(1,2), ([1,3],[2,4])) +Block(1, 2)[[1, 3], [2, 4]] + +julia> Block(1)[[1,3]] == BlockIndices(Block(1), [1,3]) +true + +julia> Block(1,2)[[1,3],[2,4]] == BlockIndices(Block(1,2), ([1,3],[2,4])) +true + +julia> BlockIndices((Block(1)[[1,3]], Block(2)[[2,4]])) +Block(1, 2)[[1, 3], [2, 4]] + +julia> arr = Array(reshape(1:25, (5,5))); + +julia> a = BlockedArray(arr, [3,2], [1,4]) +2×2-blocked 5×5 BlockedMatrix{Int64}: + 1 │ 6 11 16 21 + 2 │ 7 12 17 22 + 3 │ 8 13 18 23 + ───┼──────────────── + 4 │ 9 14 19 24 + 5 │ 10 15 20 25 + +julia> a[Block(1,2)[[1,3],[2,4]]] +2×2 Matrix{Int64}: + 11 21 + 13 23 + +julia> a[Block(2,2)[[2],[2,4]]] +1×2 Matrix{Int64}: + 15 25 +``` """ BlockIndices BlockIndices(block::Block{N}, inds::Vararg{AbstractVector,N}) where {N} = BlockIndices(block,inds) +function BlockIndices(inds::Tuple{BlockIndices{1},Vararg{BlockIndices{1}}}) + BlockIndices(Block(block.(inds)), map(ind -> ind.indices[1], inds)) +end const BlockIndexRange{N,R<:Tuple{Vararg{AbstractUnitRange{<:Integer},N}},I<:Tuple{Vararg{Any,N}},BI} = BlockIndices{N,R,I,BI} diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl index db9f2133..e201c00b 100644 --- a/test/test_blockindices.jl +++ b/test/test_blockindices.jl @@ -93,6 +93,7 @@ import BlockArrays: BlockIndex, BlockIndexRange, BlockIndices, BlockSlice, Nonco @test Block(1,1)[1:2,1:2] == BlockIndexRange(Block(1,1),(1:2,1:2)) @test Block(1,1)[[1,3],1:2] == BlockIndices(Block(1,1),([1,3],1:2)) @test BlockIndexRange((Block(1)[1:2],Block(1)[1:2])) == BlockIndexRange(Block(1,1),(1:2,1:2)) + @test BlockIndices((Block(1)[[1,3]],Block(1)[[2,4]])) == BlockIndices(Block(1,1),([1,3],[2,4])) @test Block(1)[1:3][1:2] == BlockIndexRange(Block(1),1:2) @test Block(1)[[1,3,5]][[1,3]] == BlockIndices(Block(1),[1,5]) @test Block(1)[[1,3,5]][2:3] == BlockIndices(Block(1),[3,5]) From 8d8dbb23c895485dcc5a4376c7e320b1a21ee856 Mon Sep 17 00:00:00 2001 From: Matt Fishman Date: Thu, 10 Jul 2025 10:02:30 -0400 Subject: [PATCH 12/12] Delete repeated `to_indices` definition --- src/views.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/views.jl b/src/views.jl index 2d9bbb8f..6a33b292 100644 --- a/src/views.jl +++ b/src/views.jl @@ -62,8 +62,6 @@ to_index(::BlockRange) = throw(ArgumentError("BlockRange must be converted by to to_indices(A, inds, (BlockIndex.(I[1].I, I[1].α)..., tail(I)...)) @inline to_indices(A, inds, I::Tuple{BlockIndices, Vararg{Any}}) = to_indices(A, inds, (BlockIndices.(Block.(I[1].block.n), tuple.(I[1].indices))..., tail(I)...)) -@inline to_indices(A, inds, I::Tuple{BlockRange, Vararg{Any}}) = - to_indices(A, inds, (BlockRange.(tuple.(I[1].indices))..., tail(I)...)) # In 0.7, we need to override to_indices to avoid calling linearindices @inline to_indices(A, I::Tuple{BlockIndices, Vararg{Any}}) = to_indices(A, axes(A), I)