Skip to content

Commit e9c28d4

Browse files
More general checkbounds and blockcheckbounds (#469)
This adds support for more bounds checking, such as `checkbounds(A, Block.(2:3), Block.(2:3))`, `checkbounds(A, Block(2, 2)[1:3, 1:3])`, etc. It also fixes #458. --------- Co-authored-by: Sheehan Olver <[email protected]>
1 parent fe6b909 commit e9c28d4

File tree

4 files changed

+186
-14
lines changed

4 files changed

+186
-14
lines changed

src/abstractblockarray.jl

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ ERROR: BlockBoundsError: attempt to access 2×2-blocked 2×3 BlockMatrix{Float64
8888
"""
8989
@inline function blockcheckbounds(A::AbstractArray, i...)
9090
blockcheckbounds(Bool, A, i...) || throw(BlockBoundsError(A, i))
91+
nothing
9192
end
9293

9394
# linear block indexing
@@ -99,15 +100,29 @@ end
99100
blockcheckbounds_indices(Bool, blockaxes(A), i)
100101
end
101102

102-
blockcheckbounds(A::AbstractArray{T, N}, i::Block{N}) where {T,N} = blockcheckbounds(A, i.n...)
103-
blockcheckbounds(A::AbstractArray{T, N}, i::Vararg{Block{1},N}) where {T,N} = blockcheckbounds(A, Int.(i)...)
104-
blockcheckbounds(::AbstractArray{T, 0}) where {T} = true
105-
blockcheckbounds(A::AbstractVector{T}, i::Block{1}) where {T} = blockcheckbounds(A, Int(i))
103+
# Used to ensure a `BlockBoundsError` is thrown instead of a `BoundsError`,
104+
# see https://github.com/JuliaArrays/BlockArrays.jl/issues/458
105+
checkbounds(A::AbstractArray{T, N}, I::Block{N}) where {T,N} = blockcheckbounds(A, I)
106+
checkbounds(A::AbstractArray{T}, I1::Block{1}, Irest::Block{1}...) where {T} = blockcheckbounds(A, I1, Irest...)
107+
checkbounds(A::AbstractArray{T}, I1::AbstractVector{<:Block{1}}, Irest::AbstractVector{<:Block{1}}...) where {T} =
108+
blockcheckbounds(A, I1, Irest...)
109+
110+
blockcheckbounds(::Type{Bool}, A::AbstractArray{T, N}, I::Block{N}) where {T,N} = blockcheckbounds(Bool, A, Int.(Tuple(I))...)
111+
blockcheckbounds(::Type{Bool}, A::AbstractArray{T, N}, I::Vararg{Block{1},N}) where {T,N} =
112+
blockcheckbounds(Bool, A, Int.(I)...)
113+
blockcheckbounds(::Type{Bool}, ::AbstractArray{T, 0}) where {T} = true
114+
blockcheckbounds(::Type{Bool}, A::AbstractVector{T}, I::Block{1}) where {T} = blockcheckbounds(Bool, A, Int(I))
115+
blockcheckbounds(::Type{Bool}, A::AbstractArray{T,N}, I::Vararg{AbstractVector{<:Block{1}},N}) where {T,N} =
116+
blockcheckbounds(Bool, A, map(i -> Int.(i), I)...)
117+
118+
blockcheckbounds(::Type{Bool}, A::AbstractArray{T,N}, I::Vararg{BlockRange{1},N}) where {T,N} =
119+
blockcheckbounds(Bool, A, map(i -> Int.(i), I)...)
120+
blockcheckbounds(::Type{Bool}, A::AbstractArray{T,N}, I::BlockRange{N}) where {T,N} = blockcheckbounds(Bool, A, I.indices...)
106121

107122
"""
108-
blockcheckbounds_indices(Bool, IA::Tuple{Vararg{BlockRange{1}}}, I::Tuple{Vararg{Integer}})
123+
blockcheckbounds_indices(Bool, IA::Tuple{Vararg{BlockRange{1}}}, I::Tuple)
109124
110-
Return true if the "requested" indices in the tuple `Block.(I)` fall within the bounds of the "permitted"
125+
Return true if the "requested" indices in the tuple `map(i -> Block.(i), I)` fall within the bounds of the "permitted"
111126
indices specified by the tuple `IA`. This function recursively consumes elements of these tuples
112127
in a 1-for-1 fashion.
113128
@@ -125,6 +140,12 @@ true
125140
126141
julia> BlockArrays.blockcheckbounds_indices(Bool, blockaxes(B), (4,1))
127142
false
143+
144+
julia> BlockArrays.blockcheckbounds_indices(Bool, blockaxes(B), (1:2,2:3))
145+
true
146+
147+
julia> BlockArrays.blockcheckbounds_indices(Bool, blockaxes(B), (1:2,2:4))
148+
false
128149
```
129150
"""
130151
@inline blockcheckbounds_indices(::Type{Bool}, ::Tuple{}, ::Tuple{}) = true
@@ -143,9 +164,9 @@ end
143164
end
144165

145166
"""
146-
blockcheckindex(Bool, inds::BlockRange{1}, index::Integer)
167+
blockcheckindex(Bool, inds::BlockRange{1}, index)
147168
148-
Return `true` if `Block(index)` is within the bounds of `inds`.
169+
Return `true` if `Block.(index)` is within the bounds of `inds`.
149170
150171
# Examples
151172
```jldoctest
@@ -154,9 +175,15 @@ true
154175
155176
julia> BlockArrays.blockcheckindex(Bool, BlockRange(1:2), 3)
156177
false
178+
179+
julia> BlockArrays.blockcheckindex(Bool, BlockRange(1:3), 2:3)
180+
true
181+
182+
julia> BlockArrays.blockcheckindex(Bool, BlockRange(1:3), 2:4)
183+
false
157184
```
158185
"""
159-
@inline blockcheckindex(::Type{Bool}, inds::BlockRange{1}, i::Integer) = Block(i) in inds
186+
@inline blockcheckindex(::Type{Bool}, inds::BlockRange{1}, i) = checkindex(Bool, Int.(inds), i)
160187

161188
@propagate_inbounds setindex!(block_arr::AbstractBlockArray{T,N}, v, block::Block{N}) where {T,N} =
162189
setindex!(block_arr, v, Block.(block.n)...)

src/blockindices.jl

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ end
164164

165165
block(b::BlockIndex) = Block(b.I...)
166166
blockindex(b::BlockIndex{1}) = b.α[1]
167+
blockindex(b::BlockIndex) = CartesianIndex(b.α)
167168

168169
BlockIndex(indcs::Tuple{Vararg{BlockIndex{1},N}}) where N = BlockIndex(block.(indcs), blockindex.(indcs))
169170

@@ -172,15 +173,16 @@ BlockIndex(indcs::Tuple{Vararg{BlockIndex{1},N}}) where N = BlockIndex(block.(in
172173
##
173174

174175
@inline checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::Block{N}) where N = blockcheckbounds(Bool, A, I.n...)
176+
175177
@inline function checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::BlockIndex{N}) where N
176178
bl = block(I)
177179
checkbounds(Bool, A, bl) || return false
178-
B = A[bl]
179-
checkbounds(Bool, B, blockindex(I)...)
180+
# TODO: Replace with `eachblockaxes(A)[bl]` once that is defined.
181+
binds = map(Base.axes1 getindex, axes(A), Tuple(bl))
182+
Base.checkbounds_indices(Bool, binds, (blockindex(I),))
180183
end
181-
182-
checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::AbstractVector{<:BlockIndex{N}}) where N =
183-
all(checkbounds.(Bool, Ref(A), I))
184+
checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::AbstractArray{<:BlockIndex{N}}) where N =
185+
all(i -> checkbounds(Bool, A, i), I)
184186

185187
struct BlockIndexRange{N,R<:Tuple{Vararg{AbstractUnitRange{<:Integer},N}},I<:Tuple{Vararg{Integer,N}},BI<:Integer} <: AbstractArray{BlockIndex{N,NTuple{N,BI},I},N}
186188
block::Block{N,BI}
@@ -243,6 +245,17 @@ length(iter::BlockIndexRange) = prod(size(iter))
243245

244246
Block(bs::BlockIndexRange) = bs.block
245247

248+
##
249+
# checkindex
250+
##
251+
252+
function checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::BlockIndexRange{N}) where N
253+
bl = block(I)
254+
checkbounds(Bool, A, bl) || return false
255+
# TODO: Replace with `eachblockaxes(A)[bl]` once that is defined.
256+
binds = map(Base.axes1 getindex, axes(A), Tuple(bl))
257+
Base.checkbounds_indices(Bool, binds, I.indices)
258+
end
246259

247260
# #################
248261
# # support for pointers
@@ -432,6 +445,22 @@ _in(b, ::Tuple{}, ::Tuple{}, ::Tuple{}) = b
432445
# We sometimes need intersection of BlockRange to return a BlockRange
433446
intersect(a::BlockRange{1}, b::BlockRange{1}) = BlockRange((intersect(a.indices[1], b.indices[1]),))
434447

448+
##
449+
# checkindex
450+
##
451+
452+
# Used to ensure a `BlockBoundsError` is thrown instead of a `BoundsError`,
453+
# see https://github.com/JuliaArrays/BlockArrays.jl/issues/458
454+
checkbounds(A::AbstractArray{<:Any,N}, I::BlockRange{N}) where N = blockcheckbounds(A, I)
455+
checkbounds(A::AbstractArray, I1::BlockRange{1}, Irest::BlockRange{1}...) =
456+
blockcheckbounds(A, I1, Irest...)
457+
458+
# Convert Block inputs to integers.
459+
checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::BlockRange{N}) where N =
460+
blockcheckbounds(Bool, A, I.indices...)
461+
checkbounds(::Type{Bool}, A::AbstractArray, I1::AbstractVector{<:Block{1}}, Irest::AbstractVector{<:Block{1}}...) =
462+
blockcheckbounds(Bool, A, map(I -> Int.(I), (I1, Irest...))...)
463+
435464
# needed for scalar-like broadcasting
436465

437466
BlockSlice{Block{1,BT},T,RT}(a::Base.OneTo) where {BT,T,RT<:AbstractUnitRange} =

test/test_blockarrays.jl

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,60 @@ end
467467
BA_1 = BlockArray(undef_blocks, Vector{Float64}, [1,2,3])
468468
@test Base.IndexStyle(typeof(BA_1)) == IndexCartesian()
469469

470+
@test checkbounds(Bool, BA_1, Block(1))
471+
@test isnothing(checkbounds(BA_1, Block(1)))
472+
@test blockcheckbounds(Bool, BA_1, 1)
473+
@test isnothing(blockcheckbounds(BA_1, 1))
474+
@test checkbounds(Bool, BA_1, Block(2))
475+
@test isnothing(checkbounds(BA_1, Block(2)))
476+
@test blockcheckbounds(Bool, BA_1, 2)
477+
@test isnothing(blockcheckbounds(BA_1, 2))
478+
@test checkbounds(Bool, BA_1, Block(3))
479+
@test isnothing(checkbounds(BA_1, Block(3)))
480+
@test blockcheckbounds(Bool, BA_1, 3)
481+
@test isnothing(blockcheckbounds(BA_1, 3))
482+
@test !checkbounds(Bool, BA_1, Block(4))
483+
@test_throws BlockBoundsError checkbounds(BA_1, Block(4))
484+
@test !blockcheckbounds(Bool, BA_1, 4)
485+
@test_throws BlockBoundsError blockcheckbounds(BA_1, 4)
486+
487+
@test checkbounds(Bool, BA_1, Block.(1:3))
488+
@test isnothing(checkbounds(BA_1, Block.(1:3)))
489+
@test blockcheckbounds(Bool, BA_1, 1:3)
490+
@test isnothing(blockcheckbounds(BA_1, 1:3))
491+
@test !checkbounds(Bool, BA_1, Block.(1:4))
492+
@test_throws BlockBoundsError checkbounds(BA_1, Block.(1:4))
493+
@test !blockcheckbounds(Bool, BA_1, 1:4)
494+
@test_throws BlockBoundsError blockcheckbounds(BA_1, 1:4)
495+
496+
@test checkbounds(Bool, BA_1, [Block(1), Block(3)])
497+
@test isnothing(checkbounds(BA_1, [Block(1), Block(3)]))
498+
@test blockcheckbounds(Bool, BA_1, [1, 3])
499+
@test isnothing(blockcheckbounds(BA_1, [1, 3]))
500+
@test !checkbounds(Bool, BA_1, [Block(1), Block(4)])
501+
@test_throws BlockBoundsError checkbounds(BA_1, [Block(1), Block(4)])
502+
@test !blockcheckbounds(Bool, BA_1, [1, 4])
503+
@test_throws BlockBoundsError blockcheckbounds(BA_1, [1, 4])
504+
505+
@test checkbounds(Bool, BA_1, Block(2)[2])
506+
@test isnothing(checkbounds(BA_1, Block(2)[2]))
507+
@test !checkbounds(Bool, BA_1, Block(2)[3])
508+
@test_throws BoundsError checkbounds(BA_1, Block(2)[3])
509+
@test !checkbounds(Bool, BA_1, Block(4)[2])
510+
@test_throws BoundsError checkbounds(BA_1, Block(4)[2])
511+
512+
@test checkbounds(Bool, BA_1, Block(2)[1:2])
513+
@test isnothing(checkbounds(BA_1, Block(2)[1:2]))
514+
@test !checkbounds(Bool, BA_1, Block(2)[1:3])
515+
@test_throws BoundsError checkbounds(BA_1, Block(2)[1:3])
516+
517+
@test checkbounds(Bool, BA_1, [Block(2)[2], Block(3)[3]])
518+
@test isnothing(checkbounds(BA_1, [Block(2)[2], Block(3)[3]]))
519+
@test !checkbounds(Bool, BA_1, [Block(2)[2], Block(2)[3]])
520+
@test_throws BoundsError checkbounds(BA_1, [Block(2)[2], Block(2)[3]])
521+
@test !checkbounds(Bool, BA_1, [Block(2)[2], Block(4)[2]])
522+
@test_throws BoundsError checkbounds(BA_1, [Block(2)[2], Block(4)[2]])
523+
470524
a_1 = rand(2)
471525
BA_1[Block(2)] = a_1
472526
@test BA_1[BlockIndex(2, 1)] == a_1[1]
@@ -491,6 +545,60 @@ end
491545
@test BA_2[Block(1,2)] == a_2
492546
BA_2[Block(1,2)] = a_2
493547

548+
@test checkbounds(Bool, BA_2, Block(1,1))
549+
@test isnothing(checkbounds(BA_2, Block(1,1)))
550+
@test checkbounds(Bool, BA_2, Block(1), Block(1))
551+
@test isnothing(checkbounds(BA_2, Block(1), Block(1)))
552+
@test !checkbounds(Bool, BA_2, Block(1,3))
553+
@test_throws BlockBoundsError checkbounds(BA_2, Block(1,3))
554+
@test !checkbounds(Bool, BA_2, Block(1), Block(3))
555+
@test_throws BlockBoundsError checkbounds(BA_2, Block(1), Block(3))
556+
557+
@test checkbounds(Bool, BA_2, BlockRange(1:2,1:2))
558+
@test blockcheckbounds(Bool, BA_2, BlockRange(1:2,1:2))
559+
@test isnothing(checkbounds(BA_2, BlockRange(1:2,1:2)))
560+
@test isnothing(blockcheckbounds(BA_2, BlockRange(1:2,1:2)))
561+
@test checkbounds(Bool, BA_2, Block.(1:2), Block.(1:2))
562+
@test checkbounds(Bool, BA_2, Block.([1,2]), Block.([1,2]))
563+
@test isnothing(checkbounds(BA_2, Block.(1:2), Block.(1:2)))
564+
@test isnothing(checkbounds(BA_2, Block.([1,2]), Block.([1,2])))
565+
@test blockcheckbounds(Bool, BA_2, 1:2, 1:2)
566+
@test blockcheckbounds(Bool, BA_2, [1,2], [1,2])
567+
@test isnothing(blockcheckbounds(BA_2, 1:2, 1:2))
568+
@test isnothing(blockcheckbounds(BA_2, Block.(1:2), Block.(1:2)))
569+
@test isnothing(blockcheckbounds(BA_2, [1,2], [1,2]))
570+
@test isnothing(blockcheckbounds(BA_2, Block.([1,2]), Block.([1,2])))
571+
572+
@test !checkbounds(Bool, BA_2, BlockRange(1:2,1:3))
573+
@test !blockcheckbounds(Bool, BA_2, BlockRange(1:2,1:3))
574+
@test_throws BlockBoundsError checkbounds(BA_2, BlockRange(1:2,1:3))
575+
@test_throws BlockBoundsError blockcheckbounds(BA_2, BlockRange(1:2,1:3))
576+
@test !checkbounds(Bool, BA_2, Block.(1:2), Block.(1:3))
577+
@test !checkbounds(Bool, BA_2, Block.([1,2]), Block.([1,3]))
578+
@test_throws BlockBoundsError checkbounds(BA_2, Block.(1:2), Block.(1:3))
579+
@test_throws BlockBoundsError checkbounds(BA_2, Block.([1,2]), Block.([1,3]))
580+
@test !blockcheckbounds(Bool, BA_2, 1:2, 1:3)
581+
@test !blockcheckbounds(Bool, BA_2, [1,2], [1,3])
582+
@test_throws BlockBoundsError blockcheckbounds(BA_2, 1:2, 1:3)
583+
@test_throws BlockBoundsError blockcheckbounds(BA_2, Block.(1:2), Block.(1:3))
584+
@test_throws BlockBoundsError blockcheckbounds(BA_2, [1,2], [1,3])
585+
@test_throws BlockBoundsError blockcheckbounds(BA_2, Block.([1,2]), Block.([1,3]))
586+
587+
@test checkbounds(Bool, BA_2, Block(1,1)[1,1])
588+
@test isnothing(checkbounds(BA_2, Block(1,1)[1,1]))
589+
@test checkbounds(Bool, BA_2, Block(1)[1], Block(1)[1])
590+
@test checkbounds(Bool, BA_2, Block(1,2)[1,2])
591+
@test !checkbounds(Bool, BA_2, Block(1,2)[2,2])
592+
@test checkbounds(Bool, BA_2, Block(2,2)[1:1,1:2])
593+
@test checkbounds(Bool, BA_2, Matrix(Block(2,2)[1:1,1:2]))
594+
@test isnothing(checkbounds(BA_2, Matrix(Block(2,2)[1:1,1:2])))
595+
@test checkbounds(Bool, BA_2, Block(1,2)[1:1,1:2])
596+
@test checkbounds(Bool, BA_2, Block(1)[1:1], Block(2)[1:2])
597+
@test !checkbounds(Bool, BA_2, Block(2,2)[1:3,1:2])
598+
@test !checkbounds(Bool, BA_2, Block(2)[1:3], Block(2)[1:2])
599+
@test checkbounds(Bool, BA_2, [Block(2)[1], Block(2)[2]], Block(2)[1:2])
600+
@test !checkbounds(Bool, BA_2, [Block(2)[1], Block(2)[3]], Block(2)[1:2])
601+
494602
@test BA_2[1,5] == a_2[2]
495603
@test_throws DimensionMismatch BA_2[Block(1,2)] = rand(1,5)
496604

test/test_blockindices.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,14 @@ end
380380
@test !checkbounds(Bool, b, Block(3)[4])
381381
@test !checkbounds(Bool, b, Block(0)[1])
382382
@test !checkbounds(Bool, b, Block(1)[0])
383+
@test checkbounds(Bool, b, Block(1))
384+
@test checkbounds(Bool, b, Block(2))
385+
@test checkbounds(Bool, b, Block(3))
386+
@test !checkbounds(Bool, b, Block(4))
387+
@test checkbounds(Bool, b, Block.(1:3))
388+
@test !checkbounds(Bool, b, Block.(1:4))
389+
@test_throws BlockBoundsError checkbounds(b, Block(4))
390+
@test_throws BlockBoundsError checkbounds(b, Block.(1:4))
383391
# treat b as the axis
384392
@test checkindex(Bool, b, Block(1)[1])
385393
@test checkindex(Bool, b, Block(1)[1:1])

0 commit comments

Comments
 (0)