I’m looking for the function that takes a (m,n) matrix of (k,l) matrices and makes a (m*k, n*l) matrix? And likewise for other dimensions. This works for 2d but it’s a little verbose. Is there a simpler and more general way to write this?
Perhaps you’re looking for hvcat? It takes arguments in row-major order, like matrix syntax ([a b; c d]), so to flatten an existing block matrix you need to apply permutedims before splatting:
I think the general case can be written with hvncat.
It’s possible that Base ought to have such a function… it could replace & generalise reduce(vcat, A), similar to how stack replaces/generalises reduce(hcat, A).
I wrote a version of this function here. It obeys these rules but should be faster than hvcat & hvncat:
concatenate(A::AbstractVector{<:AbstractVecOrMat}) = reduce(vcat, A)
concatenate(A::AbstractMatrix{<:AbstractVecOrMat}) = hvcat(size(A,2), permutedims(A)...)
concatenate(A::AbstractArray{<:AbstractArray}) = hvncat(size(A), false, A...)
concatenate([[1,2], [3,4,5], [6]]) == 1:6 # vector of vectors -> 6-element Vector
mats = [fill(10i+j, i, j) for i in 1:2, j in 3:5];
concatenate(mats) # 3×12 block matrix
concatenate([rand(2,2,2,2) for _ in 1:3, _ in 1:4, _ in 1:5]) |> size # 6×8×10×2 Array
Usually hvcat / hvncat are fairly slow, they exist only to parse literal [a;;;b;;;c] input.
Note BTW that reduce(hcat, reduce(vcat, u) for u in eachcol(b)) misses the fast method reduce(hcat, xs), so it applies hcat pairwise. The fact that this is such an easy mistake to make is why we should not rely on these magic fast paths, and have explicit functions.
Timing almost all of them, on a slightly bigger example:
julia> conc_jar(b) = reduce(hcat, map(splat(vcat), eachslice(b; dims=2))); # question
julia> conc_RG(b) = reduce(hcat, reduce(vcat, u) for u in eachcol(b)); # rafael.guerra
julia> conc_RG2(b) = reduce(hcat, [reduce(vcat, u) for u in eachcol(b)]); # avoiding pairwise hcat
julia> conc_hvcat(A::AbstractMatrix{<:AbstractVecOrMat}) = hvcat(size(A,2), permutedims(A)...);
julia> import LazyStack, BlockArrays
julia> let A = [rand(10, 10) for _ in 1:10, _ in 1:10]
@btime conc_jar($A)
@btime conc_hvcat($A)
@btime Matrix(mortar($A))
println("RG:")
@btime conc_RG($A)
@btime conc_RG2($A)
println("MA:")
@btime LazyStack.concatenate($A)
end;
23.708 μs (283 allocations: 166.91 KiB)
15.250 μs (13 allocations: 81.22 KiB)
11.958 μs (13 allocations: 80.41 KiB)
RG:
28.500 μs (57 allocations: 502.36 KiB)
9.583 μs (35 allocations: 157.88 KiB)
MA:
7.500 μs (3 allocations: 78.20 KiB)