Skip to content

Commit 42ae241

Browse files
authored
New interface blocks(a) for array-of-arrays view (#117)
* Add blocks(a) view * Run tests * Fix setindex! * Add blocks(::Adjoint) and blocks(::Transpose) * Test blocks(view(...)) * Add a special handling for SubArray * Import `only` from Compat * Trying to improve coverage * Avoid shadowing `blocks` in doctest
1 parent 5e1effa commit 42ae241

File tree

8 files changed

+232
-3
lines changed

8 files changed

+232
-3
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ version = "0.12.7"
44

55
[deps]
66
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
7+
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
78
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
89

910
[compat]
1011
ArrayLayouts = "0.3"
12+
Compat = "2.2, 3"
1113
julia = "1.1"
1214

1315
[extras]

docs/src/lib/public.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ blocksize
3636
blockfirsts
3737
blocklasts
3838
blocklengths
39+
blocks
3940
eachblock
4041
getblock
4142
getblock!

src/BlockArrays.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ using LinearAlgebra, ArrayLayouts
44

55
# AbstractBlockArray interface exports
66
export AbstractBlockArray, AbstractBlockMatrix, AbstractBlockVector, AbstractBlockVecOrMat
7-
export Block, getblock, getblock!, setblock!, eachblock
7+
export Block, getblock, getblock!, setblock!, eachblock, blocks
88
export blockaxes, blocksize, blocklength, blockcheckbounds, BlockBoundsError, BlockIndex
99
export blocklengths, blocklasts, blockfirsts, blockisequal
1010
export BlockRange, blockedrange, BlockedUnitRange
@@ -39,13 +39,18 @@ import ArrayLayouts: _fill_lmul!, MatMulVecAdd, MatMulMatAdd, MatLmulVec, MatLdi
3939
materialize!, MemoryLayout, sublayout, transposelayout, conjlayout,
4040
triangularlayout, triangulardata, _inv
4141

42+
if !@isdefined(only)
43+
using Compat: only
44+
end
45+
4246
include("blockindices.jl")
4347
include("blockaxis.jl")
4448
include("abstractblockarray.jl")
4549
include("blockarray.jl")
4650
include("pseudo_blockarray.jl")
4751
include("views.jl")
4852
include("show.jl")
53+
include("blocks.jl")
4954
include("blockarrayinterface.jl")
5055
include("blockbroadcast.jl")
5156
include("blocklinalg.jl")

src/blockarray.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,17 +216,19 @@ BlockMatrix{T}(λ::UniformScaling, block_sizes::Vararg{AbstractVector{Int},2}) w
216216
Construct a `BlockArray` from `blocks`. `block_sizes` is computed from
217217
`blocks` if it is not given.
218218
219+
This is an "inverse" of [`blocks`](@ref).
220+
219221
# Examples
220222
```jldoctest; setup = quote using BlockArrays end
221-
julia> blocks = permutedims(reshape([
223+
julia> arrays = permutedims(reshape([
222224
1ones(1, 3), 2ones(1, 2),
223225
3ones(2, 3), 4ones(2, 2),
224226
], (2, 2)))
225227
2×2 Array{Array{Float64,2},2}:
226228
[1.0 1.0 1.0] [2.0 2.0]
227229
[3.0 3.0 3.0; 3.0 3.0 3.0] [4.0 4.0; 4.0 4.0]
228230
229-
julia> mortar(blocks)
231+
julia> mortar(arrays)
230232
2×2-blocked 3×5 BlockArray{Float64,2}:
231233
1.0 1.0 1.0 │ 2.0 2.0
232234
───────────────┼──────────

src/blocks.jl

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""
2+
blocks(a::AbstractArray{T,N}) :: AbstractArray{<:AbstractArray{T,N},N}
3+
4+
Return the array-of-arrays view to `a` such that
5+
6+
```
7+
blocks(a)[i₁, i₂, ..., iₙ] == a[Block(i₁), Block(i₂), ..., Block(iₙ)]
8+
```
9+
10+
This function does not copy the blocks and give a mutable viwe to the original
11+
array. This is an "inverse" of [`mortar`](@ref).
12+
13+
# Examples
14+
```jldoctest; setup = quote using BlockArrays end
15+
julia> bs1 = permutedims(reshape([
16+
1ones(1, 3), 2ones(1, 2),
17+
3ones(2, 3), 4ones(2, 2),
18+
], (2, 2)))
19+
2×2 Array{Array{Float64,2},2}:
20+
[1.0 1.0 1.0] [2.0 2.0]
21+
[3.0 3.0 3.0; 3.0 3.0 3.0] [4.0 4.0; 4.0 4.0]
22+
23+
julia> a = mortar(bs1)
24+
2×2-blocked 3×5 BlockArray{Float64,2}:
25+
1.0 1.0 1.0 │ 2.0 2.0
26+
───────────────┼──────────
27+
3.0 3.0 3.0 │ 4.0 4.0
28+
3.0 3.0 3.0 │ 4.0 4.0
29+
30+
julia> bs2 = blocks(a)
31+
2×2 Array{Array{Float64,2},2}:
32+
[1.0 1.0 1.0] [2.0 2.0]
33+
[3.0 3.0 3.0; 3.0 3.0 3.0] [4.0 4.0; 4.0 4.0]
34+
35+
julia> bs1 == bs2
36+
true
37+
38+
julia> bs2[1, 1] .*= 100;
39+
40+
julia> a # in-place mutation is reflected to the block array
41+
2×2-blocked 3×5 BlockArray{Float64,2}:
42+
100.0 100.0 100.0 │ 2.0 2.0
43+
─────────────────────┼──────────
44+
3.0 3.0 3.0 │ 4.0 4.0
45+
3.0 3.0 3.0 │ 4.0 4.0
46+
```
47+
"""
48+
blocks(a::AbstractArray) = blocks(PseudoBlockArray(a, axes(a)))
49+
blocks(a::AbstractBlockArray) = BlocksView(a)
50+
blocks(a::BlockArray) = a.blocks
51+
blocks(A::Adjoint) = adjoint(blocks(parent(A)))
52+
blocks(A::Transpose) = transpose(blocks(parent(A)))
53+
54+
# convert a tuple of BlockRange to a tuple of `AbstractUnitRange{Int}`
55+
_blockrange2int() = ()
56+
_blockrange2int(A, B...) = tuple(Int.(A.block), _blockrange2int(B...)...)
57+
58+
blocks(A::SubArray{<:Any,N,<:Any,<:NTuple{N,BlockSlice}}) where N =
59+
view(blocks(parent(A)), _blockrange2int(parentindices(A)...)...)
60+
61+
struct BlocksView{
62+
S, # eltype(eltype(BlocksView(...)))
63+
N, # ndims
64+
T<:AbstractArray{S,N}, # eltype(BlocksView(...)), i.e., block type
65+
B<:AbstractBlockArray{S,N}, # array to be wrapped
66+
} <: AbstractArray{T,N}
67+
array::B
68+
end
69+
70+
BlocksView(a::AbstractBlockArray{S,N}) where {S,N} =
71+
BlocksView{S,N,AbstractArray{eltype(a),N},typeof(a)}(a)
72+
# Note: deciding concrete eltype of `BlocksView` requires some extra
73+
# interface for `AbstractBlockArray`.
74+
75+
Base.IteratorEltype(::Type{<:BlocksView}) = Base.EltypeUnknown()
76+
77+
Base.size(a::BlocksView) = blocksize(a.array)
78+
Base.axes(a::BlocksView) = map(br -> only(br.indices), blockaxes(a.array))
79+
80+
@propagate_inbounds _view(a::PseudoBlockArray, i::Block) = a[i]
81+
@propagate_inbounds _view(a::AbstractBlockArray, i::Block) = view(a, i)
82+
83+
#=
84+
This is broken for now. See: https://github.com/JuliaArrays/BlockArrays.jl/issues/120
85+
# IndexLinear implementations
86+
@propagate_inbounds Base.getindex(a::BlocksView, i::Int) = _view(a.array, Block(i))
87+
@propagate_inbounds Base.setindex!(a::BlocksView, b, i::Int) = copyto!(a[i], b)
88+
=#
89+
90+
# IndexCartesian implementations
91+
@propagate_inbounds Base.getindex(a::BlocksView{T,N}, i::Vararg{Int,N}) where {T,N} =
92+
_view(a.array, Block(i...))
93+
@propagate_inbounds Base.setindex!(a::BlocksView{T,N}, b, i::Vararg{Int,N}) where {T,N} =
94+
copyto!(a[i...], b)
95+
96+
function Base.showarg(io::IO, a::BlocksView, toplevel::Bool)
97+
if toplevel
98+
print(io, "blocks of ")
99+
Base.showarg(io, a.array, true)
100+
else
101+
print(io, "::BlocksView{…,")
102+
Base.showarg(io, a.array, false)
103+
print(io, '}')
104+
end
105+
end

src/pseudo_blockarray.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,17 @@ Base.reshape(parent::PseudoBlockArray, shp::Tuple{Union{Integer,Base.OneTo}, Var
302302
Base.reshape(parent::PseudoBlockArray, dims::Tuple{Int,Vararg{Int}}) =
303303
Base._reshape(parent, dims)
304304

305+
function Base.showarg(io::IO, A::PseudoBlockArray, toplevel::Bool)
306+
if toplevel
307+
print(io, "PseudoBlockArray of ")
308+
Base.showarg(io, A.blocks, true)
309+
else
310+
print(io, "::PseudoBlockArray{…,")
311+
Base.showarg(io, A.blocks, false)
312+
print(io, '}')
313+
end
314+
end
315+
305316

306317
###########################
307318
# Strided Array interface #

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using BlockArrays, LinearAlgebra, Test
44
include("test_blockindices.jl")
55
include("test_blockarrays.jl")
66
include("test_blockviews.jl")
7+
include("test_blocks.jl")
78
include("test_blockrange.jl")
89
include("test_blockarrayinterface.jl")
910
include("test_blockbroadcast.jl")

test/test_blocks.jl

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using Test, BlockArrays
2+
3+
@testset "blocks(::BlockVector)" begin
4+
vector_blocks = [[1, 2], [3, 4, 5], Int[]]
5+
@test blocks(mortar(vector_blocks)) === vector_blocks
6+
@test collect(blocks(mortar(vector_blocks))) == vector_blocks
7+
end
8+
9+
@testset "blocks(::BlockMatrix)" begin
10+
matrix_blocks = permutedims(reshape([
11+
1ones(1, 3), 2ones(1, 2),
12+
3ones(2, 3), 4ones(2, 2),
13+
], (2, 2)))
14+
@test blocks(mortar(matrix_blocks)) === matrix_blocks
15+
end
16+
17+
@testset "blocks(::PseudoBlockVector)" begin
18+
v0 = rand(3)
19+
vb = PseudoBlockArray(v0, [1, 2])
20+
@test size(blocks(vb)) == (2,)
21+
blocks(vb)[1] = [123]
22+
@test v0[1] == 123
23+
@test parent(blocks(vb)[1]) === v0
24+
25+
# toplevel = true:
26+
str = sprint(show, "text/plain", blocks(vb))
27+
@test occursin("blocks of PseudoBlockArray of", str)
28+
29+
# toplevel = false:
30+
str = sprint(show, "text/plain", view(blocks(vb), 1:1))
31+
@test occursin("::BlocksView{…,::PseudoBlockArray{…,", str)
32+
end
33+
34+
@testset "blocks(::PseudoBlockMatrix)" begin
35+
m0 = rand(2, 4)
36+
mb = PseudoBlockArray(m0, [1, 1], [2, 1, 1])
37+
@test size(blocks(mb)) == (2, 3)
38+
blocks(mb)[1, 1] = [123 456]
39+
@test m0[1, 1] == 123
40+
@test m0[1, 2] == 456
41+
@test parent(blocks(mb)[1, 1]) === m0
42+
43+
# linear indexing
44+
@test blocks(mb)[1] == m0[1:1, 1:2]
45+
blocks(mb)[1] = [111 222]
46+
@test mb[Block(1, 1)] == [111 222]
47+
48+
# toplevel = true:
49+
str = sprint(show, "text/plain", blocks(mb))
50+
@test occursin("blocks of PseudoBlockArray of", str)
51+
52+
# toplevel = false:
53+
str = sprint(show, "text/plain", view(blocks(mb), 1:1, 1:1))
54+
@test occursin("::BlocksView{…,::PseudoBlockArray{…,", str)
55+
end
56+
57+
@testset "blocks(::Vector)" begin
58+
v = rand(3)
59+
@test size(blocks(v)) == (1,)
60+
blocks(v)[1][1] = 123
61+
@test v[1] == 123
62+
@test parent(blocks(v)[1]) === v
63+
end
64+
65+
@testset "blocks(::Matrix)" begin
66+
m = rand(2, 4)
67+
@test size(blocks(m)) == (1, 1)
68+
blocks(m)[1, 1][1, 1] = 123
69+
@test m[1, 1] == 123
70+
@test parent(blocks(m)[1, 1]) === m
71+
end
72+
73+
@testset "blocks(::Adjoint|Transpose)" begin
74+
m = BlockArray([rand(ComplexF64, 2, 2) for _ in 1:3, _ in 1:5], [1, 2], [2, 3])
75+
@testset for i in 1:2, j in 1:2
76+
@test blocks(m')[i, j] == m'[Block(i), Block(j)]
77+
@test blocks(transpose(m))[i, j] == transpose(m)[Block(i), Block(j)]
78+
end
79+
end
80+
81+
@testset "blocks(::SubArray)" begin
82+
vector_blocks = [[1, 2], [3, 4, 5], Int[]]
83+
b = view(mortar(vector_blocks), Block(1):Block(2))
84+
v = blocks(b)
85+
@test v == vector_blocks[1:2]
86+
v[1][1] = 111
87+
@test b[1] == 111
88+
@test parent(v) === parent(b).blocks # special path works
89+
end
90+
91+
@testset "blocks(::SubArray)" begin
92+
matrix_blocks = permutedims(reshape([
93+
1ones(1, 3), 2ones(1, 2),
94+
3ones(2, 3), 4ones(2, 2),
95+
], (2, 2)))
96+
b = view(mortar(matrix_blocks), Block(1):Block(2), Block(2):Block(2))
97+
m = blocks(b)
98+
@test m == matrix_blocks[1:2, 2:2]
99+
m[1, 1][1, 1] = 111
100+
@test b[1, 1] == 111
101+
@test parent(m) === parent(b).blocks # special path works
102+
end

0 commit comments

Comments
 (0)