Skip to content

Commit 0c0d813

Browse files
authored
Introduce eachblockaxes, more general blocklengths (#476)
Introduces and exports `eachblockaxes`, which is analogous to the redefinition of `blocksizes` introduced in #399. It outputs a (lazy) array with a size `blocksize(A)` that when you index into it it returns the axes of the corresponding block of `A`. It also defines a private function `BlockArrays.eachblockaxes1` analogous to `Base.axes1` for getting the axes of the blocks in the first dimension. The motivation for this is related to the discussion in #446, this helps with use cases where the blocks of a block array have non-trivial axes, for example they themselves might be blocked, have a Kronecker structure, have extra information like symmetry sector information (which is an application of [graded vector spaces](https://en.wikipedia.org/wiki/Graded_vector_space)), have labels that need to be preserved, etc. It also introduces a generalization of [`blocklengths`](https://juliaarrays.github.io/BlockArrays.jl/v1.6/lib/public/#BlockArrays.blocklengths) beyond `AbstractUnitRange{<:Integer}` to any `AbstractArray` by outputting a (lazy) array that when you index into it returns the length of the corresponding block. This PR also changes the implementation of `blocksizes`. Before, it was implemented as a lazy version of `size.(blocks(A))`. Now, it is based on a new `ProductArray` type, where the `blocklengths` of the axes in each dimension are stored and accessed. Then, `ProductArray` can be shared with and used for the implementation of `eachblockaxes`. I think that design is easier to reason about and in the case of `eachblockaxes` it is easier to make the element type concrete. Related to #369 and #446.
1 parent cc32221 commit 0c0d813

File tree

4 files changed

+167
-21
lines changed

4 files changed

+167
-21
lines changed

docs/src/lib/public.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ blockfirsts
3737
blocklasts
3838
blocklengths
3939
blocksizes
40+
eachblockaxes
4041
blocks
4142
eachblock
4243
blockcheckbounds

src/BlockArrays.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ using LinearAlgebra, ArrayLayouts, FillArrays
55
export AbstractBlockArray, AbstractBlockMatrix, AbstractBlockVector, AbstractBlockVecOrMat
66
export Block, getblock, getblock!, setblock!, eachblock, blocks
77
export blockaxes, blocksize, blocklength, blockcheckbounds, BlockBoundsError, BlockIndex, BlockIndexRange
8-
export blocksizes, blocklengths, blocklasts, blockfirsts, blockisequal
8+
export blocksizes, blocklengths, eachblockaxes, blocklasts, blockfirsts, blockisequal
99
export BlockRange, blockedrange, BlockedUnitRange, BlockedOneTo
1010

1111
export BlockArray, BlockMatrix, BlockVector, BlockVecOrMat, mortar

src/blocks.jl

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@ This is broken for now. See: https://github.com/JuliaArrays/BlockArrays.jl/issue
9393
a
9494
end
9595

96+
# AbstractArray version of `Iterators.product`.
97+
# https://en.wikipedia.org/wiki/Cartesian_product
98+
# https://github.com/lazyLibraries/ProductArrays.jl
99+
# https://github.com/JuliaData/SplitApplyCombine.jl#productviewf-a-b
100+
# https://github.com/JuliaArrays/MappedArrays.jl/pull/42
101+
struct ProductArray{T,N,V<:Tuple{Vararg{AbstractVector,N}}} <: AbstractArray{T,N}
102+
vectors::V
103+
end
104+
ProductArray(vectors::Vararg{AbstractVector,N}) where {N} =
105+
ProductArray{Tuple{map(eltype, vectors)...},N,typeof(vectors)}(vectors)
106+
Base.size(p::ProductArray) = map(length, p.vectors)
107+
Base.axes(p::ProductArray) = map(Base.axes1, p.vectors)
108+
@propagate_inbounds getindex(p::ProductArray{T,N}, I::Vararg{Int,N}) where {T,N} =
109+
map((v, i) -> v[i], p.vectors, I)
110+
96111
"""
97112
blocksizes(A::AbstractArray)
98113
blocksizes(A::AbstractArray, d::Integer)
@@ -110,7 +125,7 @@ julia> A = BlockArray(ones(3,3),[2,1],[1,1,1])
110125
1.0 │ 1.0 │ 1.0
111126
112127
julia> blocksizes(A)
113-
2×3 BlockArrays.BlockSizes{Tuple{Int64, Int64}, 2, BlockMatrix{Float64, Matrix{Matrix{Float64}}, Tuple{BlockedOneTo{Int64, Vector{Int64}}, BlockedOneTo{Int64, Vector{Int64}}}}}:
128+
2×3 BlockArrays.ProductArray{Tuple{Int64, Int64}, 2, Tuple{Vector{Int64}, Vector{Int64}}}:
114129
(2, 1) (2, 1) (2, 1)
115130
(1, 1) (1, 1) (1, 1)
116131
@@ -124,16 +139,80 @@ julia> blocksizes(A,2)
124139
1
125140
```
126141
"""
127-
blocksizes(A::AbstractArray) = BlockSizes(A)
142+
blocksizes(A::AbstractArray) = ProductArray(map(blocklengths, axes(A))...)
128143
@inline blocksizes(A::AbstractArray, d::Integer) = blocklengths(axes(A, d))
129144

130-
struct BlockSizes{T,N,A<:AbstractArray{<:Any,N}} <: AbstractArray{T,N}
145+
"""
146+
blocklengths(A::AbstractArray)
147+
148+
Return an iterator over the lengths of each block.
149+
See also blocksizes.
150+
151+
# Examples
152+
```jldoctest
153+
julia> A = BlockArray(ones(3,3),[2,1],[1,1,1])
154+
2×3-blocked 3×3 BlockMatrix{Float64}:
155+
1.0 │ 1.0 │ 1.0
156+
1.0 │ 1.0 │ 1.0
157+
─────┼───────┼─────
158+
1.0 │ 1.0 │ 1.0
159+
160+
julia> blocklengths(A)
161+
2×3 BlockArrays.BlockLengths{Int64, 2, BlockMatrix{Float64, Matrix{Matrix{Float64}}, Tuple{BlockedOneTo{Int64, Vector{Int64}}, BlockedOneTo{Int64, Vector{Int64}}}}}:
162+
2 2 2
163+
1 1 1
164+
165+
julia> blocklengths(A)[1,2]
166+
2
167+
```
168+
"""
169+
blocklengths(A::AbstractArray) = BlockLengths(A)
170+
blocklengths(A::AbstractVector) = map(length, blocks(A))
171+
172+
struct BlockLengths{T,N,A<:AbstractArray{<:Any,N}} <: AbstractArray{T,N}
131173
array::A
132174
end
133-
BlockSizes(a::AbstractArray{<:Any,N}) where {N} =
134-
BlockSizes{Tuple{eltype.(axes(a))...},N,typeof(a)}(a)
175+
BlockLengths(a::AbstractArray{<:Any,N}) where {N} =
176+
BlockLengths{typeof(length(a)),N,typeof(a)}(a)
135177

136-
size(bs::BlockSizes) = blocksize(bs.array)
137-
axes(bs::BlockSizes) = map(br -> Int.(br), blockaxes(bs.array))
138-
@propagate_inbounds getindex(a::BlockSizes{T,N}, i::Vararg{Int,N}) where {T,N} =
139-
size(view(a.array, Block.(i)...))
178+
size(bs::BlockLengths) = blocksize(bs.array)
179+
axes(bs::BlockLengths) = map(br -> Int.(br), blockaxes(bs.array))
180+
@propagate_inbounds getindex(a::BlockLengths{T,N}, i::Vararg{Int,N}) where {T,N} =
181+
length(view(a.array, Block.(i)...))
182+
183+
"""
184+
eachblockaxes(A::AbstractArray)
185+
eachblockaxes(A::AbstractArray, d::Integer)
186+
187+
Return an iterator over the axes of each block.
188+
See also blocksizes and blocklengths.
189+
190+
# Examples
191+
```jldoctest
192+
julia> A = BlockArray(ones(3,3),[2,1],[1,1,1])
193+
2×3-blocked 3×3 BlockMatrix{Float64}:
194+
1.0 │ 1.0 │ 1.0
195+
1.0 │ 1.0 │ 1.0
196+
─────┼───────┼─────
197+
1.0 │ 1.0 │ 1.0
198+
199+
julia> eachblockaxes(A)
200+
2×3 BlockArrays.ProductArray{Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, 2, Tuple{Vector{Base.OneTo{Int64}}, Vector{Base.OneTo{Int64}}}}:
201+
(Base.OneTo(2), Base.OneTo(1)) … (Base.OneTo(2), Base.OneTo(1))
202+
(Base.OneTo(1), Base.OneTo(1)) (Base.OneTo(1), Base.OneTo(1))
203+
204+
julia> eachblockaxes(A)[1,2]
205+
(Base.OneTo(2), Base.OneTo(1))
206+
207+
julia> eachblockaxes(A,2)
208+
3-element Vector{Base.OneTo{Int64}}:
209+
Base.OneTo(1)
210+
Base.OneTo(1)
211+
Base.OneTo(1)
212+
```
213+
"""
214+
eachblockaxes(A::AbstractArray) =
215+
ProductArray(map(ax -> map(Base.axes1, blocks(ax)), axes(A))...)
216+
eachblockaxes(A::AbstractVector) = map(axes, blocks(Base.axes1(A)))
217+
eachblockaxes(A::AbstractArray, d::Integer) = map(Base.axes1, blocks(axes(A, d)))
218+
eachblockaxes1(A::AbstractArray) = map(Base.axes1, blocks(Base.axes1(A)))

test/test_blocks.jl

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module TestBlocks
22

33
using Test, BlockArrays
4+
import BlockArrays: eachblockaxes1
45

56
@testset "blocks" begin
67
@testset "blocks(::BlockVector)" begin
@@ -143,18 +144,18 @@ end
143144
@test @inferred(size(bs)) == (2, 2)
144145
@test @inferred(length(bs)) == 4
145146
@test @inferred(axes(bs)) == (1:2, 1:2)
146-
@test @inferred(eltype(bs)) === Tuple{Int,Int}
147+
@test @inferred(eltype(bs)) Tuple{Int,Int}
147148
@test bs == [(2, 3) (2, 1); (3, 3) (3, 1)]
148-
@test @inferred(bs[1, 1]) == (2, 3)
149-
@test @inferred(bs[2, 1]) == (3, 3)
150-
@test @inferred(bs[1, 2]) == (2, 1)
151-
@test @inferred(bs[2, 2]) == (3, 1)
152-
@test @inferred(bs[1]) == (2, 3)
153-
@test @inferred(bs[2]) == (3, 3)
154-
@test @inferred(bs[3]) == (2, 1)
155-
@test @inferred(bs[4]) == (3, 1)
156-
@test blocksizes(A, 1) == [2, 3]
157-
@test blocksizes(A, 2) == [3, 1]
149+
@test @inferred(bs[1, 1]) (2, 3)
150+
@test @inferred(bs[2, 1]) (3, 3)
151+
@test @inferred(bs[1, 2]) (2, 1)
152+
@test @inferred(bs[2, 2]) (3, 1)
153+
@test @inferred(bs[1]) (2, 3)
154+
@test @inferred(bs[2]) (3, 3)
155+
@test @inferred(bs[3]) (2, 1)
156+
@test @inferred(bs[4]) (3, 1)
157+
@test @inferred((x -> blocksizes(x, 1))(A)) == [2, 3]
158+
@test @inferred((x -> blocksizes(x, 2))(A)) == [3, 1]
158159
end
159160

160161
@testset "Inference: issue #425" begin
@@ -166,4 +167,69 @@ end
166167
end
167168
end
168169

170+
@testset "blocklengths" begin
171+
v = Array(reshape(1:20, (5, 4)))
172+
A = BlockArray(v, [2, 3], [3, 1])
173+
bls = @inferred(blocklengths(A))
174+
@test bls == [6 2; 9 3]
175+
@test @inferred(length(bls)) 4
176+
@test @inferred(size(bls)) (2, 2)
177+
@test @inferred(eltype(bls)) Int
178+
@test @inferred(bls[1, 1]) 6
179+
@test @inferred(bls[2, 1]) 9
180+
@test @inferred(bls[1, 2]) 2
181+
@test @inferred(bls[2, 2]) 3
182+
@test @inferred(bls[1]) 6
183+
@test @inferred(bls[2]) 9
184+
@test @inferred(bls[3]) 2
185+
@test @inferred(bls[4]) 3
186+
end
187+
188+
@testset "eachblockaxes" begin
189+
v = Array(reshape(1:20, (5, 4)))
190+
A = BlockArray(v, [2, 3], [3, 1])
191+
bas = @inferred(eachblockaxes(A))
192+
@test bas == [(Base.OneTo(2), Base.OneTo(3)) (Base.OneTo(2), Base.OneTo(1)); (Base.OneTo(3), Base.OneTo(3)) (Base.OneTo(3), Base.OneTo(1))]
193+
@test @inferred(length(bas)) 4
194+
@test @inferred(size(bas)) (2, 2)
195+
@test @inferred(eltype(bas)) Tuple{Base.OneTo{Int},Base.OneTo{Int}}
196+
@test @inferred(bas[1, 1]) (Base.OneTo(2), Base.OneTo(3))
197+
@test @inferred(bas[2, 1]) (Base.OneTo(3), Base.OneTo(3))
198+
@test @inferred(bas[1, 2]) (Base.OneTo(2), Base.OneTo(1))
199+
@test @inferred(bas[2, 2]) (Base.OneTo(3), Base.OneTo(1))
200+
@test @inferred(bas[1]) (Base.OneTo(2), Base.OneTo(3))
201+
@test @inferred(bas[2]) (Base.OneTo(3), Base.OneTo(3))
202+
@test @inferred(bas[3]) (Base.OneTo(2), Base.OneTo(1))
203+
@test @inferred(bas[4]) (Base.OneTo(3), Base.OneTo(1))
204+
205+
bas2 = @inferred (x -> eachblockaxes(x, 2))(A)
206+
@test bas2 == [Base.OneTo(3), Base.OneTo(1)]
207+
@test length(bas2) 2
208+
@test size(bas2) (2,)
209+
@test @inferred(eltype(bas2)) Base.OneTo{Int}
210+
@test @inferred(bas2[1]) Base.OneTo(3)
211+
@test @inferred(bas2[2]) Base.OneTo(1)
212+
@test @inferred((x -> eachblockaxes(x, 3))(A)) == [Base.OneTo(1)]
213+
214+
V = mortar([[2, 3], [4, 5, 6]])
215+
@test @inferred(eachblockaxes(V)) == [(Base.OneTo(2),), (Base.OneTo(3),)]
216+
@test @inferred((x -> eachblockaxes(x, 1))(V)) == [Base.OneTo(2), Base.OneTo(3)]
217+
@test @inferred((x -> eachblockaxes(x, 2))(V)) == [Base.OneTo(1)]
218+
end
219+
220+
@testset "eachblockaxes1" begin
221+
v = Array(reshape(1:20, (5, 4)))
222+
A = BlockArray(v, [2, 3], [3, 1])
223+
bas = @inferred(eachblockaxes1(A))
224+
@test bas == [Base.OneTo(2), Base.OneTo(3)]
225+
@test @inferred(length(bas)) 2
226+
@test @inferred(size(bas)) (2,)
227+
@test @inferred(eltype(bas)) Base.OneTo{Int}
228+
@test @inferred(bas[1]) Base.OneTo(2)
229+
@test @inferred(bas[2]) Base.OneTo(3)
230+
231+
@test @inferred(eachblockaxes1(mortar([[2, 3], [4, 5, 6]]))) == [Base.OneTo(2), Base.OneTo(3)]
232+
@test @inferred(eachblockaxes1(fill(2))) == [Base.OneTo(1)]
233+
end
234+
169235
end # module

0 commit comments

Comments
 (0)