Skip to content

Commit db3658e

Browse files
committed
add sparse_*cat
1 parent cd1a928 commit db3658e

File tree

6 files changed

+102
-28
lines changed

6 files changed

+102
-28
lines changed

NEWS.md

+6
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ Standard library changes
132132

133133
#### SparseArrays
134134

135+
* New sparse concatenation functions `sparse_hcat`, `sparse_vcat`, and `sparse_hvcat` return
136+
`SparseMatrixCSC` output independent from the types of the input arguments. They make
137+
concatenation behavior available, in which the presence of some special "sparse" matrix
138+
argument resulted in sparse output by multiple dispatch. This is no longer possible after
139+
making `LinearAlgebra.jl` independent from `SparseArrays.jl` ([#43127]).
140+
135141
#### Dates
136142

137143
#### Downloads

stdlib/LinearAlgebra/src/uniformscaling.jl

+5-6
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ for (f, _f, dim, name) in ((:hcat, :_hcat, 1, "rows"), (:vcat, :_vcat, 2, "cols"
407407
@eval begin
408408
@inline $f(A::Union{AbstractVecOrMat,UniformScaling}...) = $_f(A...)
409409
@inline $f(A::Union{AbstractVecOrMat,UniformScaling,Number}...) = $_f(A...)
410-
function $_f(A::Union{AbstractVecOrMat,UniformScaling,Number}...)
410+
function $_f(A::Union{AbstractVecOrMat,UniformScaling,Number}...; array_type = promote_to_array_type(A))
411411
n = -1
412412
for a in A
413413
if !isa(a, UniformScaling)
@@ -420,14 +420,14 @@ for (f, _f, dim, name) in ((:hcat, :_hcat, 1, "rows"), (:vcat, :_vcat, 2, "cols"
420420
end
421421
end
422422
n == -1 && throw(ArgumentError($("$f of only UniformScaling objects cannot determine the matrix size")))
423-
return cat(promote_to_arrays(fill(n, length(A)), 1, promote_to_array_type(A), A...)..., dims=Val(3-$dim))
423+
return cat(promote_to_arrays(fill(n, length(A)), 1, array_type, A...)..., dims=Val(3-$dim))
424424
end
425425
end
426426
end
427427

428428
hvcat(rows::Tuple{Vararg{Int}}, A::Union{AbstractVecOrMat,UniformScaling}...) = _hvcat(rows, A...)
429429
hvcat(rows::Tuple{Vararg{Int}}, A::Union{AbstractVecOrMat,UniformScaling,Number}...) = _hvcat(rows, A...)
430-
function _hvcat(rows::Tuple{Vararg{Int}}, A::Union{AbstractVecOrMat,UniformScaling,Number}...)
430+
function _hvcat(rows::Tuple{Vararg{Int}}, A::Union{AbstractVecOrMat,UniformScaling,Number}...; array_type = promote_to_array_type(A))
431431
require_one_based_indexing(A...)
432432
nr = length(rows)
433433
sum(rows) == length(A) || throw(ArgumentError("mismatch between row sizes and number of arguments"))
@@ -479,14 +479,13 @@ function _hvcat(rows::Tuple{Vararg{Int}}, A::Union{AbstractVecOrMat,UniformScali
479479
j += rows[i]
480480
end
481481
end
482-
Atyp = promote_to_array_type(A)
483-
Amat = promote_to_arrays(n, 1, Atyp, A...)
482+
Amat = promote_to_arrays(n, 1, array_type, A...)
484483
# We have two methods for promote_to_array_type, one returning Matrix and
485484
# another one returning SparseMatrixCSC (in SparseArrays.jl). In the dense
486485
# case, we cannot call hvcat for the promoted UniformScalings because this
487486
# causes a stack overflow. In the sparse case, however, we cannot call
488487
# typed_hvcat because we need a sparse output.
489-
if Atyp == Matrix
488+
if array_type == Matrix
490489
return typed_hvcat(promote_eltype(Amat...), rows, Amat...)
491490
else
492491
return hvcat(rows, Amat...)

stdlib/SparseArrays/docs/src/index.md

+3
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ SparseArrays.nnz
214214
SparseArrays.findnz
215215
SparseArrays.spzeros
216216
SparseArrays.spdiagm
217+
SparseArrays.sparse_hcat
218+
SparseArrays.sparse_vcat
219+
SparseArrays.sparse_hvcat
217220
SparseArrays.blockdiag
218221
SparseArrays.sprand
219222
SparseArrays.sprandn

stdlib/SparseArrays/src/SparseArrays.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ using Random: default_rng, AbstractRNG, randsubseq, randsubseq!
2727
export AbstractSparseArray, AbstractSparseMatrix, AbstractSparseVector,
2828
SparseMatrixCSC, SparseVector, blockdiag, droptol!, dropzeros!, dropzeros,
2929
issparse, nonzeros, nzrange, rowvals, sparse, sparsevec, spdiagm,
30-
sprand, sprandn, spzeros, nnz, permute, findnz
30+
sprand, sprandn, spzeros, nnz, permute, findnz,
31+
sparse_hcat, sparse_vcat, sparse_hvcat
3132

3233
include("abstractsparse.jl")
3334
include("sparsematrix.jl")

stdlib/SparseArrays/src/sparsevector.jl

+49
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,55 @@ _hvcat_rows(::Tuple{}, X::_SparseConcatGroup...) = ()
11121112
promote_to_array_type(A::Tuple{Vararg{Union{_SparseConcatGroup,UniformScaling}}}) = SparseMatrixCSC
11131113
promote_to_arrays_(n::Int, ::Type{SparseMatrixCSC}, J::UniformScaling) = sparse(J, n, n)
11141114

1115+
"""
1116+
sparse_hcat(A...)
1117+
1118+
Concatenate along dimension 2. Return a SparseMatrixCSC object.
1119+
1120+
!!! compat "Julia 1.8"
1121+
This method was added in Julia 1.8. It mimicks previous concatenation behavior, where
1122+
the concatenation with specialized "sparse" matrix types from LinearAlgebra.jl
1123+
automatically yielded sparse output even in the absence of any SparseArray argument.
1124+
"""
1125+
sparse_hcat(Xin::Union{AbstractVecOrMat,Number}...) = cat(map(_makesparse, Xin)..., dims=Val(2))
1126+
function sparse_hcat(X::Union{AbstractVecOrMat,UniformScaling,Number}...)
1127+
LinearAlgebra._hcat(X...; array_type = SparseMatrixCSC)
1128+
end
1129+
1130+
"""
1131+
sparse_vcat(A...)
1132+
1133+
Concatenate along dimension 1. Return a SparseMatrixCSC object.
1134+
1135+
!!! compat "Julia 1.8"
1136+
This method was added in Julia 1.8. It mimicks previous concatenation behavior, where
1137+
the concatenation with specialized "sparse" matrix types from LinearAlgebra.jl
1138+
automatically yielded sparse output even in the absence of any SparseArray argument.
1139+
"""
1140+
sparse_vcat(Xin::Union{AbstractVecOrMat,Number}...) = cat(map(_makesparse, Xin)..., dims=Val(1))
1141+
function sparse_vcat(X::Union{AbstractVecOrMat,UniformScaling,Number}...)
1142+
LinearAlgebra._vcat(X...; array_type = SparseMatrixCSC)
1143+
end
1144+
1145+
"""
1146+
sparse_hvcat(rows::Tuple{Vararg{Int}}, values...)
1147+
1148+
Sparse horizontal and vertical concatenation in one call. This function is called
1149+
for block matrix syntax. The first argument specifies the number of
1150+
arguments to concatenate in each block row.
1151+
1152+
!!! compat "Julia 1.8"
1153+
This method was added in Julia 1.8. It mimicks previous concatenation behavior, where
1154+
the concatenation with specialized "sparse" matrix types from LinearAlgebra.jl
1155+
automatically yielded sparse output even in the absence of any SparseArray argument.
1156+
"""
1157+
function sparse_hvcat(rows::Tuple{Vararg{Int}}, Xin::Union{AbstractVecOrMat,Number}...)
1158+
hvcat(rows, map(_makesparse, Xin)...)
1159+
end
1160+
function sparse_hvcat(rows::Tuple{Vararg{Int}}, X::Union{AbstractVecOrMat,UniformScaling,Number}...)
1161+
LinearAlgebra._hvcat(rows, X...; array_type = SparseMatrixCSC)
1162+
end
1163+
11151164
### math functions
11161165

11171166
### Unary Map

stdlib/SparseArrays/test/sparse.jl

+37-21
Original file line numberDiff line numberDiff line change
@@ -262,19 +262,27 @@ end
262262
# Test concatenating pairwise combinations of special matrices with sparse matrices,
263263
# dense matrices, or dense vectors
264264
spmat = spdiagm(0 => fill(1., N))
265+
dmat = Array(spmat)
265266
spvec = sparse(fill(1., N))
267+
dvec = Array(spvec)
266268
for specialmat in specialmats
267269
# --> Tests applicable only to pairs of matrices
268270
@test issparse(vcat(specialmat, spmat))
269271
@test issparse(vcat(spmat, specialmat))
272+
@test sparse_vcat(specialmat, dmat)::SparseMatrixCSC == vcat(specialmat, spmat)
273+
@test sparse_vcat(dmat, specialmat)::SparseMatrixCSC == vcat(spmat, specialmat)
270274
# --> Tests applicable also to pairs including vectors
271-
for specialmat in specialmats, othermatorvec in (spmat, spvec)
272-
@test issparse(hcat(specialmat, othermatorvec))
273-
@test issparse(hcat(othermatorvec, specialmat))
274-
@test issparse(hvcat((2,), specialmat, othermatorvec))
275-
@test issparse(hvcat((2,), othermatorvec, specialmat))
276-
@test issparse(cat(specialmat, othermatorvec; dims=(1,2)))
277-
@test issparse(cat(othermatorvec, specialmat; dims=(1,2)))
275+
for specialmat in specialmats, (smatorvec, dmatorvec) in ((spmat, dmat), (spvec, dvec))
276+
@test issparse(hcat(specialmat, smatorvec))
277+
@test sparse_hcat(specialmat, dmatorvec)::SparseMatrixCSC == hcat(specialmat, smatorvec)
278+
@test issparse(hcat(smatorvec, specialmat))
279+
@test sparse_hcat(dmatorvec, specialmat)::SparseMatrixCSC == hcat(smatorvec, specialmat)
280+
@test issparse(hvcat((2,), specialmat, smatorvec))
281+
@test sparse_hvcat((2,), specialmat, dmatorvec)::SparseMatrixCSC == hvcat((2,), specialmat, smatorvec)
282+
@test issparse(hvcat((2,), smatorvec, specialmat))
283+
@test sparse_hvcat((2,), dmatorvec, specialmat)::SparseMatrixCSC == hvcat((2,), smatorvec, specialmat)
284+
@test issparse(cat(specialmat, smatorvec; dims=(1,2)))
285+
@test issparse(cat(smatorvec, specialmat; dims=(1,2)))
278286
end
279287
end
280288
end
@@ -300,8 +308,8 @@ end
300308
symtridiagmat = SymTridiagonal(1:N, 1:(N-1))
301309
sparseconcatmats = testfull ? (spmat, diagmat, bidiagmat, tridiagmat, symtridiagmat) : (spmat, diagmat)
302310
# Concatenations involving strictly these types, un/annotated, should yield dense arrays
303-
densevec = fill(1., N)
304-
densemat = fill(1., N, N)
311+
densevec = Array(spvec)
312+
densemat = Array(spmat)
305313
# Annotated collections
306314
annodmats = [annot(densemat) for annot in annotations]
307315
annospcmats = [annot(spmat) for annot in annotations]
@@ -321,6 +329,14 @@ end
321329
@test issparse(vcat(annospcmat, othermat))
322330
@test issparse(vcat(othermat, annospcmat))
323331
end
332+
for (smat, dmat) in zip(annospcmats, annodmats), specialmat in sparseconcatmats
333+
@test sparse_hcat(dmat, specialmat)::SparseMatrixCSC == hcat(smat, specialmat)
334+
@test sparse_hcat(specialmat, dmat)::SparseMatrixCSC == hcat(specialmat, smat)
335+
@test sparse_vcat(dmat, specialmat)::SparseMatrixCSC == vcat(smat, specialmat)
336+
@test sparse_vcat(specialmat, dmat)::SparseMatrixCSC == vcat(specialmat, smat)
337+
@test sparse_hvcat((2,), dmat, specialmat)::SparseMatrixCSC == hvcat((2,), smat, specialmat)
338+
@test sparse_hvcat((2,), specialmat, dmat)::SparseMatrixCSC == hvcat((2,), specialmat, smat)
339+
end
324340
# --> Tests applicable to pairs including other vectors or matrices
325341
for other in (spvec, densevec, densemat, annodmats..., sparseconcatmats...)
326342
@test issparse(hcat(annospcmat, other))
@@ -357,30 +373,30 @@ end
357373
E = SparseMatrixCSC(rand(1,3))
358374
F = SparseMatrixCSC(rand(3,1))
359375
α = rand()
360-
@test (hcat(A, 2I))::SparseMatrixCSC == hcat(A, Matrix(2I, 3, 3))
376+
@test (hcat(A, 2I, I(3)))::SparseMatrixCSC == hcat(A, Matrix(2I, 3, 3), Matrix(I, 3, 3))
361377
@test (hcat(E, α))::SparseMatrixCSC == hcat(E, [α])
362378
@test (hcat(E, α, 2I))::SparseMatrixCSC == hcat(E, [α], fill(2, 1, 1))
363-
@test (vcat(A, 2I))::SparseMatrixCSC == vcat(A, Matrix(2I, 4, 4))
379+
@test (vcat(A, 2I))::SparseMatrixCSC == (vcat(A, 2I(4)))::SparseMatrixCSC == vcat(A, Matrix(2I, 4, 4))
364380
@test (vcat(F, α))::SparseMatrixCSC == vcat(F, [α])
365-
@test (vcat(F, α, 2I))::SparseMatrixCSC == vcat(F, [α], fill(2, 1, 1))
381+
@test (vcat(F, α, 2I))::SparseMatrixCSC == (vcat(F, α, 2I(1)))::SparseMatrixCSC == vcat(F, [α], fill(2, 1, 1))
366382
@test (hcat(C, 2I))::SparseMatrixCSC == C
367383
@test_throws DimensionMismatch hcat(C, α)
368384
@test (vcat(D, 2I))::SparseMatrixCSC == D
369385
@test_throws DimensionMismatch vcat(D, α)
370386
@test (hcat(I, 3I, A, 2I))::SparseMatrixCSC == hcat(Matrix(I, 3, 3), Matrix(3I, 3, 3), A, Matrix(2I, 3, 3))
371387
@test (vcat(I, 3I, A, 2I))::SparseMatrixCSC == vcat(Matrix(I, 4, 4), Matrix(3I, 4, 4), A, Matrix(2I, 4, 4))
372-
@test (hvcat((2,1,2), B, 2I, I, 3I, 4I))::SparseMatrixCSC ==
388+
@test (hvcat((2,1,2), B, 2I, I(6), 3I, 4I))::SparseMatrixCSC ==
373389
hvcat((2,1,2), B, Matrix(2I, 3, 3), Matrix(I, 6, 6), Matrix(3I, 3, 3), Matrix(4I, 3, 3))
374-
@test hvcat((3,1), C, C, I, 3I)::SparseMatrixCSC == hvcat((2,1), C, C, Matrix(3I, 6,6))
390+
@test hvcat((3,1), C, C, I, 3I)::SparseMatrixCSC == hvcat((2,1), C, C, Matrix(3I, 6, 6))
375391
@test hvcat((2,2,2), I, 2I, 3I, 4I, C, C)::SparseMatrixCSC ==
376-
hvcat((2,2,2), Matrix(I, 3, 3), Matrix(2I, 3,3 ), Matrix(3I, 3,3), Matrix(4I, 3,3), C, C)
377-
@test hvcat((2,2,4), C, C, I, 2I, 3I, 4I, 5I, D)::SparseMatrixCSC ==
378-
hvcat((2,2,4), C, C, Matrix(I, 3, 3), Matrix(2I,3,3),
379-
Matrix(3I, 2, 2), Matrix(4I, 2, 2), Matrix(5I,2,2), D)
380-
@test (hvcat((2,3,2), B, 2I, C, C, I, 3I, 4I))::SparseMatrixCSC ==
392+
hvcat((2,2,2), Matrix(I, 3, 3), Matrix(2I, 3, 3), Matrix(3I, 3, 3), Matrix(4I, 3, 3), C, C)
393+
@test hvcat((2,2,4), C, C, I(3), 2I, 3I, 4I, 5I, D)::SparseMatrixCSC ==
394+
hvcat((2,2,4), C, C, Matrix(I, 3, 3), Matrix(2I, 3, 3),
395+
Matrix(3I, 2, 2), Matrix(4I, 2, 2), Matrix(5I, 2, 2), D)
396+
@test (hvcat((2,3,2), B, 2I(3), C, C, I, 3I, 4I))::SparseMatrixCSC ==
381397
hvcat((2,2,2), B, Matrix(2I, 3, 3), C, C, Matrix(3I, 3, 3), Matrix(4I, 3, 3))
382-
@test hvcat((3,2,1), C, C, I, B ,3I, 2I)::SparseMatrixCSC ==
383-
hvcat((2,2,1), C, C, B, Matrix(3I,3,3), Matrix(2I,6,6))
398+
@test hvcat((3,2,1), C, C, I, B, 3I(3), 2I)::SparseMatrixCSC ==
399+
hvcat((2,2,1), C, C, B, Matrix(3I, 3, 3), Matrix(2I, 6, 6))
384400
@test (hvcat((1,2), A, E, α))::SparseMatrixCSC == hvcat((1,2), A, E, [α]) == hvcat((1,2), A, E, α*I)
385401
@test (hvcat((2,2), α, E, F, 3I))::SparseMatrixCSC == hvcat((2,2), [α], E, F, Matrix(3I, 3, 3))
386402
@test (hvcat((2,2), 3I, F, E, α))::SparseMatrixCSC == hvcat((2,2), Matrix(3I, 3, 3), F, E, [α])

0 commit comments

Comments
 (0)