diff --git a/NEWS.md b/NEWS.md index c314275602e40..24220600ada86 100644 --- a/NEWS.md +++ b/NEWS.md @@ -61,6 +61,7 @@ Standard library changes * Sparse vector outer products are more performant and maintain sparsity in products of the form `kron(u, v')`, `u * v'`, and `u .* v'` where `u` and `v` are sparse vectors or column views. ([#24980]) +* new `sizehint!(::SparseMatrixCSC, ::Integer)` method ([#30676]). #### Dates diff --git a/base/array.jl b/base/array.jl index 119acfd71ae70..0909bfb1bab9d 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1022,6 +1022,17 @@ function sizehint!(a::Vector, sz::Integer) a end +""" + capacity(s) + +Returns the allocated buffer capacity of s. +""" +function capacity end + +function capacity(a::Vector) + return Int(ccall(:jl_array_capacity, Csize_t, (Any,), a)) +end + """ pop!(collection) -> item diff --git a/base/exports.jl b/base/exports.jl index 8a26eb8fdc73f..77dde6d603989 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -477,6 +477,7 @@ export any!, any, firstindex, + capacity, collect, count, delete!, diff --git a/src/array.c b/src/array.c index 2744eafeab958..459b77f8e925b 100644 --- a/src/array.c +++ b/src/array.c @@ -1120,6 +1120,11 @@ JL_DLLEXPORT void jl_array_sizehint(jl_array_t *a, size_t sz) } } +JL_DLLEXPORT size_t jl_array_capacity(jl_array_t *a) +{ + return a->maxsize; +} + JL_DLLEXPORT jl_array_t *jl_array_copy(jl_array_t *ary) { size_t elsz = ary->elsize; diff --git a/stdlib/SparseArrays/src/higherorderfns.jl b/stdlib/SparseArrays/src/higherorderfns.jl index 66ab8b3f60e52..12674c6b13bc4 100644 --- a/stdlib/SparseArrays/src/higherorderfns.jl +++ b/stdlib/SparseArrays/src/higherorderfns.jl @@ -9,7 +9,8 @@ import Base: map, map!, broadcast, copy, copyto! using Base: front, tail, to_shape using ..SparseArrays: SparseVector, SparseMatrixCSC, AbstractSparseVector, AbstractSparseMatrix, AbstractSparseArray, indtype, nnz, nzrange, - SparseVectorUnion, AdjOrTransSparseVectorUnion, nonzeroinds, nonzeros + SparseVectorUnion, AdjOrTransSparseVectorUnion, nonzeroinds, nonzeros, + spzeros using Base.Broadcast: BroadcastStyle, Broadcasted, flatten using LinearAlgebra @@ -126,17 +127,26 @@ const SpBroadcasted2{Style<:SPVM,Axes,F,Args<:Tuple{SparseVecOrMat,SparseVecOrMa @inline storedvals(A::SparseVecOrMat) = A.nzval @inline setcolptr!(A::SparseVector, j, val) = val @inline setcolptr!(A::SparseMatrixCSC, j, val) = A.colptr[j] = val + function trimstorage!(A::SparseVecOrMat, maxstored) resize!(storedinds(A), maxstored) resize!(storedvals(A), maxstored) return maxstored end -function expandstorage!(A::SparseVecOrMat, maxstored) - length(storedinds(A)) < maxstored && resize!(storedinds(A), maxstored) - length(storedvals(A)) < maxstored && resize!(storedvals(A), maxstored) + +function expandstorage!(A::SparseVecOrMat, maxstored = capacity(storedinds(A))) + if length(storedinds(A)) < maxstored + resize!(storedinds(A), maxstored) + resize!(storedvals(A), maxstored) + end return maxstored end +_checkbuffers(S::SparseMatrixCSC) = (@assert length(S.colptr) == S.n + 1 && S.colptr[end] - 1 == length(S.rowval) == length(S.nzval); S) +_checkbuffers(S::SparseVector) = (@assert length(S.nzval) == length(S.nzind); S) + +_capacity(A::SparseVecOrMat) = capacity(storedinds(A)) + # (2) map[!] entry points map(f::Tf, A::SparseVector) where {Tf} = _noshapecheck_map(f, A) @@ -180,7 +190,7 @@ copy(bc::SpBroadcasted1) = _noshapecheck_map(bc.f, bc.args[1]) storedvals(C)[1] = fofnoargs broadcast!(f, view(storedvals(C), 2:length(storedvals(C)))) end - return C + return _checkbuffers(C) end function _diffshape_broadcast(f::Tf, A::SparseVecOrMat, Bs::Vararg{SparseVecOrMat,N}) where {Tf,N} @@ -223,22 +233,12 @@ _maxnnzfrom(shape::NTuple{2}, A::SparseMatrixCSC) = nnz(A) * div(shape[1], A.m) @inline _unchecked_maxnnzbcres(shape, As...) = _unchecked_maxnnzbcres(shape, As) @inline _checked_maxnnzbcres(shape::NTuple{1}, As...) = shape[1] != 0 ? _unchecked_maxnnzbcres(shape, As) : 0 @inline _checked_maxnnzbcres(shape::NTuple{2}, As...) = shape[1] != 0 && shape[2] != 0 ? _unchecked_maxnnzbcres(shape, As) : 0 -@inline function _allocres(shape::NTuple{1}, indextype, entrytype, maxnnz) - storedinds = Vector{indextype}(undef, maxnnz) - storedvals = Vector{entrytype}(undef, maxnnz) - return SparseVector(shape..., storedinds, storedvals) -end -@inline function _allocres(shape::NTuple{2}, indextype, entrytype, maxnnz) - pointers = Vector{indextype}(undef, shape[2] + 1) - storedinds = Vector{indextype}(undef, maxnnz) - storedvals = Vector{entrytype}(undef, maxnnz) - return SparseMatrixCSC(shape..., pointers, storedinds, storedvals) -end +@inline _allocres(shape::NTuple, indextype, entrytype, maxnnz) = sizehint!(spzeros(entrytype, indextype, shape...), maxnnz) # (4) _map_zeropres!/_map_notzeropres! specialized for a single sparse vector/matrix "Stores only the nonzero entries of `map(f, Array(A))` in `C`." function _map_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat) where Tf - spaceC::Int = min(length(storedinds(C)), length(storedvals(C))) + spaceC::Int = expandstorage!(C) Ck = 1 @inbounds for j in columns(C) setcolptr!(C, j, Ck) @@ -254,7 +254,7 @@ function _map_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat) where Tf end @inbounds setcolptr!(C, numcols(C) + 1, Ck) trimstorage!(C, Ck - 1) - return C + return _checkbuffers(C) end """ Densifies `C`, storing `fillvalue` in place of each unstored entry in `A` and @@ -273,7 +273,7 @@ function _map_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, A::SparseVecOrMa end # NOTE: Combining the fill! above into the loop above to avoid multiple sweeps over / # nonsequential access of storedvals(C) does not appear to improve performance. - return C + return _checkbuffers(C) end # helper functions for these methods and some of those below @inline _densecoloffsets(A::SparseVector) = 0 @@ -296,7 +296,7 @@ end # (5) _map_zeropres!/_map_notzeropres! specialized for a pair of sparse vectors/matrices function _map_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat, B::SparseVecOrMat) where Tf - spaceC::Int = min(length(storedinds(C)), length(storedvals(C))) + spaceC::Int = expandstorage!(C) rowsentinelA = convert(indtype(A), numrows(C) + 1) rowsentinelB = convert(indtype(B), numrows(C) + 1) Ck = 1 @@ -335,7 +335,7 @@ function _map_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat, B::SparseVe end @inbounds setcolptr!(C, numcols(C) + 1, Ck) trimstorage!(C, Ck - 1) - return C + return _checkbuffers(C) end function _map_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, A::SparseVecOrMat, B::SparseVecOrMat) where Tf # Build dense matrix structure in C, expanding storage if necessary @@ -367,13 +367,13 @@ function _map_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, A::SparseVecOrMa Cx != fillvalue && (storedvals(C)[jo + Ci] = Cx) end end - return C + return _checkbuffers(C) end # (6) _map_zeropres!/_map_notzeropres! for more than two sparse matrices / vectors function _map_zeropres!(f::Tf, C::SparseVecOrMat, As::Vararg{SparseVecOrMat,N}) where {Tf,N} - spaceC::Int = min(length(storedinds(C)), length(storedvals(C))) + spaceC::Int = expandstorage!(C) rowsentinel = numrows(C) + 1 Ck = 1 stopks = _colstartind_all(1, As) @@ -397,7 +397,7 @@ function _map_zeropres!(f::Tf, C::SparseVecOrMat, As::Vararg{SparseVecOrMat,N}) end @inbounds setcolptr!(C, numcols(C) + 1, Ck) trimstorage!(C, Ck - 1) - return C + return _checkbuffers(C) end function _map_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, As::Vararg{SparseVecOrMat,N}) where {Tf,N} # Build dense matrix structure in C, expanding storage if necessary @@ -420,7 +420,7 @@ function _map_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, As::Vararg{Spars activerow = min(rows...) end end - return C + return _checkbuffers(C) end # helper methods for map/map! methods just above @@ -461,7 +461,7 @@ end # (7) _broadcast_zeropres!/_broadcast_notzeropres! specialized for a single (input) sparse vector/matrix function _broadcast_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat) where Tf isempty(C) && return _finishempty!(C) - spaceC::Int = min(length(storedinds(C)), length(storedvals(C))) + spaceC::Int = expandstorage!(C) # C and A cannot have the same shape, as we directed that case to map in broadcast's # entry point; here we need efficiently handle only heterogeneous C-A combinations where # one or both of C and A has at least one singleton dimension. @@ -508,7 +508,7 @@ function _broadcast_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat) where end @inbounds setcolptr!(C, numcols(C) + 1, Ck) trimstorage!(C, Ck - 1) - return C + return _checkbuffers(C) end function _broadcast_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, A::SparseVecOrMat) where Tf # For information on this code, see comments in similar code in _broadcast_zeropres! above @@ -539,14 +539,14 @@ function _broadcast_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, A::SparseV end end end - return C + return _checkbuffers(C) end # (8) _broadcast_zeropres!/_broadcast_notzeropres! specialized for a pair of (input) sparse vectors/matrices function _broadcast_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat, B::SparseVecOrMat) where Tf isempty(C) && return _finishempty!(C) - spaceC::Int = min(length(storedinds(C)), length(storedvals(C))) + spaceC::Int = expandstorage!(C) rowsentinelA = convert(indtype(A), numrows(C) + 1) rowsentinelB = convert(indtype(B), numrows(C) + 1) # C, A, and B cannot all have the same shape, as we directed that case to map in broadcast's @@ -710,7 +710,7 @@ function _broadcast_zeropres!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat, B::Sp end @inbounds setcolptr!(C, numcols(C) + 1, Ck) trimstorage!(C, Ck - 1) - return C + return _checkbuffers(C) end function _broadcast_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, A::SparseVecOrMat, B::SparseVecOrMat) where Tf # For information on this code, see comments in similar code in _broadcast_zeropres! above @@ -809,7 +809,7 @@ function _broadcast_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, A::SparseV end end end - return C + return _checkbuffers(C) end _finishempty!(C::SparseVector) = C _finishempty!(C::SparseMatrixCSC) = (fill!(C.colptr, 1); C) @@ -860,7 +860,7 @@ end # (9) _broadcast_zeropres!/_broadcast_notzeropres! for more than two (input) sparse vectors/matrices function _broadcast_zeropres!(f::Tf, C::SparseVecOrMat, As::Vararg{SparseVecOrMat,N}) where {Tf,N} isempty(C) && return _finishempty!(C) - spaceC::Int = min(length(storedinds(C)), length(storedvals(C))) + spaceC::Int = expandstorage!(C) expandsverts = _expandsvert_all(C, As) expandshorzs = _expandshorz_all(C, As) rowsentinel = numrows(C) + 1 @@ -908,7 +908,7 @@ function _broadcast_zeropres!(f::Tf, C::SparseVecOrMat, As::Vararg{SparseVecOrMa end @inbounds setcolptr!(C, numcols(C) + 1, Ck) trimstorage!(C, Ck - 1) - return C + return _checkbuffers(C) end function _broadcast_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, As::Vararg{SparseVecOrMat,N}) where {Tf,N} isempty(C) && return _finishempty!(C) @@ -949,7 +949,7 @@ function _broadcast_notzeropres!(f::Tf, fillvalue, C::SparseVecOrMat, As::Vararg end end end - return C + return _checkbuffers(C) end # helper method for broadcast/broadcast! methods just above diff --git a/stdlib/SparseArrays/src/linalg.jl b/stdlib/SparseArrays/src/linalg.jl index 45864311c936e..31558d36cb311 100644 --- a/stdlib/SparseArrays/src/linalg.jl +++ b/stdlib/SparseArrays/src/linalg.jl @@ -809,16 +809,15 @@ function triu(S::SparseMatrixCSC{Tv,Ti}, k::Integer=0) where {Tv,Ti} end rowval = Vector{Ti}(undef, nnz) nzval = Vector{Tv}(undef, nnz) - A = SparseMatrixCSC(m, n, colptr, rowval, nzval) for col = max(k+1,1) : n c1 = S.colptr[col] - for c2 = A.colptr[col] : A.colptr[col+1]-1 - A.rowval[c2] = S.rowval[c1] - A.nzval[c2] = S.nzval[c1] + for c2 = colptr[col] : colptr[col+1]-1 + rowval[c2] = S.rowval[c1] + nzval[c2] = S.nzval[c1] c1 += 1 end end - A + SparseMatrixCSC(m, n, colptr, rowval, nzval) end function tril(S::SparseMatrixCSC{Tv,Ti}, k::Integer=0) where {Tv,Ti} @@ -839,17 +838,16 @@ function tril(S::SparseMatrixCSC{Tv,Ti}, k::Integer=0) where {Tv,Ti} end rowval = Vector{Ti}(undef, nnz) nzval = Vector{Tv}(undef, nnz) - A = SparseMatrixCSC(m, n, colptr, rowval, nzval) for col = 1 : min(n, m+k) c1 = S.colptr[col+1]-1 - l2 = A.colptr[col+1]-1 - for c2 = 0 : l2 - A.colptr[col] - A.rowval[l2 - c2] = S.rowval[c1] - A.nzval[l2 - c2] = S.nzval[c1] + l2 = colptr[col+1]-1 + for c2 = 0 : l2 - colptr[col] + rowval[l2 - c2] = S.rowval[c1] + nzval[l2 - c2] = S.nzval[c1] c1 -= 1 end end - A + SparseMatrixCSC(m, n, colptr, rowval, nzval) end ## diff diff --git a/stdlib/SparseArrays/src/sparsematrix.jl b/stdlib/SparseArrays/src/sparsematrix.jl index 359ea3a692d02..28489ba17c73f 100644 --- a/stdlib/SparseArrays/src/sparsematrix.jl +++ b/stdlib/SparseArrays/src/sparsematrix.jl @@ -22,8 +22,8 @@ struct SparseMatrixCSC{Tv,Ti<:Integer} <: AbstractSparseMatrix{Tv,Ti} function SparseMatrixCSC{Tv,Ti}(m::Integer, n::Integer, colptr::Vector{Ti}, rowval::Vector{Ti}, nzval::Vector{Tv}) where {Tv,Ti<:Integer} - sparse_check_Ti(m, n, Ti) + _goodbuffers(Int(m), Int(n), colptr, rowval, nzval) || throw(ArgumentError("Illegal buffers for SparseMatrixCSC construction $n $colptr $rowval $nzval")) new(Int(m), Int(n), colptr, rowval, nzval) end end @@ -34,16 +34,27 @@ function SparseMatrixCSC(m::Integer, n::Integer, colptr::Vector, rowval::Vector, end function sparse_check_Ti(m::Integer, n::Integer, Ti::Type) - @noinline throwsz(str, lbl, k) = - throw(ArgumentError("number of $str ($lbl) must be ≥ 0, got $k")) - @noinline throwTi(str, lbl, k) = - throw(ArgumentError("$str ($lbl = $k) does not fit in Ti = $(Ti)")) - m < 0 && throwsz("rows", 'm', m) - n < 0 && throwsz("columns", 'n', n) - !isbitstype(Ti) || m ≤ typemax(Ti) || throwTi("number of rows", "m", m) - !isbitstype(Ti) || n ≤ typemax(Ti) || throwTi("number of columns", "n", n) - !isbitstype(Ti) || n*m+1 ≤ typemax(Ti) || throwTi("maximal nnz+1", "m*n+1", n*m+1) + @noinline throwsz(str, lbl, k) = + throw(ArgumentError("number of $str ($lbl) must be ≥ 0, got $k")) + @noinline throwTi(str, lbl, k) = + throw(ArgumentError("$str ($lbl = $k) does not fit in Ti = $(Ti)")) + m < 0 && throwsz("rows", 'm', m) + n < 0 && throwsz("columns", 'n', n) + !isbitstype(Ti) || m ≤ typemax(Ti) || throwTi("number of rows", "m", m) + !isbitstype(Ti) || n ≤ typemax(Ti) || throwTi("number of columns", "n", n) + !isbitstype(Ti) || n*m+1 ≤ typemax(Ti) || throwTi("maximal nnz+1", "m*n+1", n*m+1) +end + +function _goodbuffers(m, n, colptr, rowval, nzval) + (length(colptr) == n + 1 && colptr[end] - 1 == length(rowval) == length(nzval)) + # stronger check for debugging purposes + # && all(issorted(@view rowval[colptr[i]:colptr[i+1]-1]) for i=1:n) end + +_goodbuffers(S::SparseMatrixCSC) = _goodbuffers(S.m, S.n, S.colptr, S.rowval, S.nzval) +_checkbuffers(S::SparseMatrixCSC) = (@assert _goodbuffers(S); S) + + size(S::SparseMatrixCSC) = (S.m, S.n) # Define an alias for views of a SparseMatrixCSC which include all rows and a unit range of the columns. @@ -161,6 +172,7 @@ nzrange(S::SparseMatrixCSC, col::Integer) = S.colptr[col]:(S.colptr[col+1]-1) nzrange(S::SparseMatrixCSCView, col::Integer) = nzrange(S.parent, S.indices[2][col]) function Base.show(io::IO, ::MIME"text/plain", S::SparseMatrixCSC) + _checkbuffers(S) xnnz = nnz(S) print(io, S.m, "×", S.n, " ", typeof(S), " with ", xnnz, " stored ", xnnz == 1 ? "entry" : "entries") @@ -172,6 +184,7 @@ end Base.show(io::IO, S::SparseMatrixCSC) = Base.show(convert(IOContext, io), S::SparseMatrixCSC) function Base.show(io::IOContext, S::SparseMatrixCSC) + _checkbuffers(S) if nnz(S) == 0 return show(io, MIME("text/plain"), S) end @@ -225,7 +238,7 @@ end ## Reshape -function sparse_compute_reshaped_colptr_and_rowval(colptrS::Vector{Ti}, rowvalS::Vector{Ti}, +function sparse_compute_reshaped_colptr_and_rowval!(colptrS::Vector{Ti}, rowvalS::Vector{Ti}, mS::Int, nS::Int, colptrA::Vector{Ti}, rowvalA::Vector{Ti}, mA::Int, nA::Int) where Ti lrowvalA = length(rowvalA) @@ -268,7 +281,7 @@ function copy(ra::ReshapedArray{<:Any,2,<:SparseMatrixCSC}) rowval = similar(a.rowval) nzval = copy(a.nzval) - sparse_compute_reshaped_colptr_and_rowval(colptr, rowval, mS, nS, a.colptr, a.rowval, mA, nA) + sparse_compute_reshaped_colptr_and_rowval!(colptr, rowval, mS, nS, a.colptr, a.rowval, mA, nA) return SparseMatrixCSC(mS, nS, colptr, rowval, nzval) end @@ -295,7 +308,7 @@ function copyto!(A::SparseMatrixCSC, B::SparseMatrixCSC) copyto!(A.rowval, B.rowval) else # This is like a "reshape B into A". - sparse_compute_reshaped_colptr_and_rowval(A.colptr, A.rowval, A.m, A.n, B.colptr, B.rowval, B.m, B.n) + sparse_compute_reshaped_colptr_and_rowval!(A.colptr, A.rowval, A.m, A.n, B.colptr, B.rowval, B.m, B.n) end else length(A) >= length(B) || throw(BoundsError()) @@ -325,10 +338,10 @@ function copyto!(A::SparseMatrixCSC, B::SparseMatrixCSC) @inbounds for i in 2:length(A.colptr) A.colptr[i] += nnzB - lastmodptrA end - sparse_compute_reshaped_colptr_and_rowval(A.colptr, A.rowval, A.m, lastmodcolA-1, B.colptr, B.rowval, B.m, B.n) + sparse_compute_reshaped_colptr_and_rowval!(A.colptr, A.rowval, A.m, lastmodcolA-1, B.colptr, B.rowval, B.m, B.n) end copyto!(A.nzval, B.nzval) - return A + return _checkbuffers(A) end ## similar @@ -339,9 +352,9 @@ function _sparsesimilar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}) where newrowval = copyto!(similar(S.rowval, TiNew), S.rowval) return SparseMatrixCSC(S.m, S.n, newcolptr, newrowval, similar(S.nzval, TvNew)) end -# parent methods for similar that preserves only storage space (for when new and old dims differ) +# parent methods for similar that preserves only storage space allocation (for when new and old dims differ) _sparsesimilar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, dims::Dims{2}) where {TvNew,TiNew} = - SparseMatrixCSC(dims..., fill(one(TiNew), last(dims)+1), similar(S.rowval, TiNew), similar(S.nzval, TvNew)) + sizehint!(spzeros(TvNew, TiNew, dims...), length(S.nzval)) # parent method for similar that allocates an empty sparse vector (when new dims are single) _sparsesimilar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, dims::Dims{1}) where {TvNew,TiNew} = SparseVector(dims..., similar(S.rowval, TiNew, 0), similar(S.nzval, TvNew, 0)) @@ -366,6 +379,12 @@ similar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, m::Integer) where {TvN similar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, m::Integer, n::Integer) where {TvNew,TiNew} = _sparsesimilar(S, TvNew, TiNew, (m, n)) +function Base.sizehint!(S::SparseMatrixCSC, n::Integer) + nhint = min(n, length(S)) + sizehint!(S.rowval, nhint) + sizehint!(S.nzval, nhint) + return S +end # converting between SparseMatrixCSC types SparseMatrixCSC(S::SparseMatrixCSC) = copy(S) @@ -434,6 +453,7 @@ SparseMatrixCSC{Tv,Ti}(M::Transpose{<:Any,SparseMatrixCSC}) where {Tv,Ti} = Spar # converting from SparseMatrixCSC to other matrix types function Matrix(S::SparseMatrixCSC{Tv}) where Tv # Handle cases where zero(Tv) is not defined but the array is dense. + _checkbuffers(S) A = length(S) == nnz(S) ? Matrix{Tv}(undef, S.m, S.n) : zeros(Tv, S.m, S.n) for Sj in 1:S.n for Sk in nzrange(S, Sj) @@ -804,6 +824,8 @@ respectively. Simultaneously fixes the one-position-forward shift in `X.colptr`. """ @noinline function _distributevals_halfperm!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}, q::AbstractVector{<:Integer}, f::Function) where {Tv,Ti} + resize!(X.rowval, nnz(A)) + resize!(X.nzval, nnz(A)) @inbounds for Xi in 1:A.n Aj = q[Xi] for Ak in nzrange(A, Aj) @@ -825,14 +847,6 @@ function ftranspose!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}, f::Fu elseif X.m != A.n throw(DimensionMismatch(string("destination argument `X`'s row count, `X.m (= $(X.m))`, must match source argument `A`'s column count, `A.n (= $(A.n))`"))) - elseif length(X.rowval) < nnz(A) - throw(ArgumentError(string("the length of destination argument `X`'s `rowval` ", - "array, `length(X.rowval) (= $(length(X.rowval)))`, must be greater than or ", - "equal to source argument `A`'s allocated entry count, `nnz(A) (= $(nnz(A)))`"))) - elseif length(X.nzval) < nnz(A) - throw(ArgumentError(string("the length of destination argument `X`'s `nzval` ", - "array, `length(X.nzval) (= $(length(X.nzval)))`, must be greater than or ", - "equal to source argument `A`'s allocated entry count, `nnz(A) (= $(nnz(A)))`"))) end halfperm!(X, A, 1:A.n, f) end @@ -840,11 +854,7 @@ transpose!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = adjoint!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, conj) function ftranspose(A::SparseMatrixCSC{Tv,Ti}, f::Function) where {Tv,Ti} - X = SparseMatrixCSC(A.n, A.m, - Vector{Ti}(undef, A.m+1), - Vector{Ti}(undef, nnz(A)), - Vector{Tv}(undef, nnz(A))) - halfperm!(X, A, 1:A.n, f) + halfperm!(sizehint!(spzeros(Tv, Ti, A.n, A.m), nnz(A)), A, 1:A.n, f) end adjoint(A::SparseMatrixCSC) = Adjoint(A) transpose(A::SparseMatrixCSC) = Transpose(A) @@ -1059,10 +1069,7 @@ function permute!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer}) where {Tv,Ti} _checkargs_sourcecompatdest_permute!(A, X) _checkargs_sourcecompatperms_permute!(A, p, q) - C = SparseMatrixCSC(A.n, A.m, - Vector{Ti}(undef, A.m + 1), - Vector{Ti}(undef, nnz(A)), - Vector{Tv}(undef, nnz(A))) + C = sizehint!(spzeros(Tv,Ti,A.n,A.m), nnz(A)) _checkargs_permutationsvalid_permute!(p, C.colptr, q, X.colptr) unchecked_noalias_permute!(X, A, p, q, C) end @@ -1078,10 +1085,7 @@ end function permute!(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer}) where {Tv,Ti} _checkargs_sourcecompatperms_permute!(A, p, q) - C = SparseMatrixCSC(A.n, A.m, - Vector{Ti}(undef, A.m + 1), - Vector{Ti}(undef, nnz(A)), - Vector{Tv}(undef, nnz(A))) + C = sizehint!(spzeros(Tv,Ti, A.n, A.m), nnz(A)) workcolptr = Vector{Ti}(undef, A.n + 1) _checkargs_permutationsvalid_permute!(p, C.colptr, q, workcolptr) unchecked_aliasing_permute!(A, p, q, C, workcolptr) @@ -1149,14 +1153,8 @@ julia> permute(A, [1, 2, 3, 4], [4, 3, 2, 1]) function permute(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer}) where {Tv,Ti} _checkargs_sourcecompatperms_permute!(A, p, q) - X = SparseMatrixCSC(A.m, A.n, - Vector{Ti}(undef, A.n + 1), - Vector{Ti}(undef, nnz(A)), - Vector{Tv}(undef, nnz(A))) - C = SparseMatrixCSC(A.n, A.m, - Vector{Ti}(undef, A.m + 1), - Vector{Ti}(undef, nnz(A)), - Vector{Tv}(undef, nnz(A))) + X = sizehint!(spzeros(Tv, Ti, A.m, A.n), nnz(A)) + C = sizehint!(spzeros(Tv, Ti, A.n, A.m), nnz(A)) _checkargs_permutationsvalid_permute!(p, C.colptr, q, X.colptr) unchecked_noalias_permute!(X, A, p, q, C) end @@ -1250,6 +1248,7 @@ For an out-of-place version, see [`dropzeros`](@ref). For algorithmic information, see `fkeep!`. """ dropzeros!(A::SparseMatrixCSC; trim::Bool = true) = fkeep!(A, (i, j, x) -> x != 0, trim) + """ dropzeros(A::SparseMatrixCSC; trim::Bool = true) @@ -2228,7 +2227,7 @@ function permute_rows!(S::SparseMatrixCSC{Tv,Ti}, pI::Vector{Int}) where {Tv,Ti} k += 1 end end - S + _checkbuffers(S) end function getindex_general(A::SparseMatrixCSC, I::AbstractVector, J::AbstractVector) @@ -2365,6 +2364,7 @@ function Base.fill!(V::SubArray{Tv, <:Any, <:SparseMatrixCSC, Tuple{Vararg{Union else _spsetnz_setindex!(A, convert(Tv, x), I, J) end + _checkbuffers(A) end """ Helper method for immediately preceding setindex! method. For all (i,j) such that i in I and @@ -2644,7 +2644,7 @@ function setindex!(A::SparseMatrixCSC{Tv,Ti}, V::AbstractVecOrMat, Ix::Union{Int deleteat!(rowvalA, colptrA[end]:length(rowvalA)) deleteat!(nzvalA, colptrA[end]:length(nzvalA)) - return A + return _checkbuffers(A) end # Logical setindex! @@ -2753,7 +2753,7 @@ function setindex!(A::SparseMatrixCSC, x::AbstractArray, I::AbstractMatrix{Bool} deleteat!(rowvalB, bidx:n) end end - A + _checkbuffers(A) end function setindex!(A::SparseMatrixCSC, x::AbstractArray, Ix::AbstractVector{<:Integer}) @@ -2864,7 +2864,7 @@ function setindex!(A::SparseMatrixCSC, x::AbstractArray, Ix::AbstractVector{<:In deleteat!(rowvalB, bidx:n) end end - A + _checkbuffers(A) end ## dropstored! methods @@ -2899,7 +2899,7 @@ function dropstored!(A::SparseMatrixCSC, i::Integer, j::Integer) @inbounds A.colptr[m] -= 1 end end - return A + return _checkbuffers(A) end """ dropstored!(A::SparseMatrixCSC, I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer}) @@ -2985,7 +2985,7 @@ function dropstored!(A::SparseMatrixCSC, deleteat!(rowvalA, rowidx:nnzA) deleteat!(nzvalA, rowidx:nnzA) end - return A + return _checkbuffers(A) end dropstored!(A::SparseMatrixCSC, i::Integer, J::AbstractVector{<:Integer}) = dropstored!(A, [i], J) dropstored!(A::SparseMatrixCSC, I::AbstractVector{<:Integer}, j::Integer) = dropstored!(A, I, [j]) @@ -3357,73 +3357,6 @@ function tr(A::SparseMatrixCSC{Tv}) where Tv end -# Sort all the indices in each column of a CSC sparse matrix -# sortSparseMatrixCSC!(A, sortindices = :sortcols) # Sort each column with sort() -# sortSparseMatrixCSC!(A, sortindices = :doubletranspose) # Sort with a double transpose -function sortSparseMatrixCSC!(A::SparseMatrixCSC{Tv,Ti}; sortindices::Symbol = :sortcols) where {Tv,Ti} - if sortindices == :doubletranspose - nB, mB = size(A) - B = SparseMatrixCSC(mB, nB, Vector{Ti}(undef, nB+1), similar(A.rowval), similar(A.nzval)) - transpose!(B, A) - transpose!(A, B) - return A - end - - m, n = size(A) - colptr = A.colptr; rowval = A.rowval; nzval = A.nzval - - index = zeros(Ti, m) - row = zeros(Ti, m) - val = zeros(Tv, m) - - perm = Base.Perm(Base.ord(isless, identity, false, Base.Order.Forward), row) - - @inbounds for i = 1:n - nzr = nzrange(A, i) - numrows = length(nzr) - if numrows <= 1 - continue - elseif numrows == 2 - f = first(nzr) - s = f+1 - if rowval[f] > rowval[s] - rowval[f], rowval[s] = rowval[s], rowval[f] - nzval[f], nzval[s] = nzval[s], nzval[f] - end - continue - end - resize!(row, numrows) - resize!(index, numrows) - - jj = 1 - @simd for j = nzr - row[jj] = rowval[j] - val[jj] = nzval[j] - jj += 1 - end - - if numrows <= 16 - alg = Base.Sort.InsertionSort - else - alg = Base.Sort.QuickSort - end - - # Reset permutation - index .= 1:numrows - - sort!(index, alg, perm) - - jj = 1 - @simd for j = nzr - rowval[j] = row[index[jj]] - nzval[j] = val[index[jj]] - jj += 1 - end - end - - return A -end - ## rotations function rot180(A::SparseMatrixCSC) @@ -3499,7 +3432,7 @@ function circshift!(O::SparseMatrixCSC, X::SparseMatrixCSC, (r,c)::Base.DimsInte @inbounds for i=1:O.n subvector_shifter!(O.rowval, O.nzval, O.colptr[i], O.colptr[i+1]-1, O.m, r) end - return O + return _checkbuffers(O) end circshift!(O::SparseMatrixCSC, X::SparseMatrixCSC, (r,)::Base.DimsInteger{1}) = circshift!(O, X, (r,0)) diff --git a/stdlib/SparseArrays/src/sparsevector.jl b/stdlib/SparseArrays/src/sparsevector.jl index f4796d2fced47..2d997a22edf67 100644 --- a/stdlib/SparseArrays/src/sparsevector.jl +++ b/stdlib/SparseArrays/src/sparsevector.jl @@ -65,6 +65,13 @@ function nnz(x::SparseColumnView) return length(nzrange(parent(x), colidx)) end + +function Base.sizehint!(v::SparseVector, newlen::Integer) + sizehint!(v.nzind, newlen) + sizehint!(v.nzval, newlen) + return v +end + ## similar # # parent method for similar that preserves stored-entry structure (for when new and old dims match) @@ -74,8 +81,10 @@ _sparsesimilar(S::SparseVector, ::Type{TvNew}, ::Type{TiNew}) where {TvNew,TiNew _sparsesimilar(S::SparseVector, ::Type{TvNew}, ::Type{TiNew}, dims::Dims{1}) where {TvNew,TiNew} = SparseVector(dims..., similar(S.nzind, TiNew, 0), similar(S.nzval, TvNew, 0)) # parent method for similar that preserves storage space (for old and new dims differ, and new is 2d) -_sparsesimilar(S::SparseVector, ::Type{TvNew}, ::Type{TiNew}, dims::Dims{2}) where {TvNew,TiNew} = - SparseMatrixCSC(dims..., fill(one(TiNew), last(dims)+1), similar(S.nzind, TiNew), similar(S.nzval, TvNew)) +function _sparsesimilar(S::SparseVector, ::Type{TvNew}, ::Type{TiNew}, dims::Dims{2}) where {TvNew,TiNew} + S1 = SparseMatrixCSC(dims..., fill(one(TiNew), last(dims)+1), similar(S.nzind, TiNew, 0), similar(S.nzval, TvNew, 0)) + return sizehint!(S1, min(length(S1), length(S.nzind))) +end # The following methods hook into the AbstractArray similar hierarchy. The first method # covers similar(A[, Tv]) calls, which preserve stored-entry structure, and the latter # methods cover similar(A[, Tv], shape...) calls, which preserve nothing if the dims diff --git a/stdlib/SparseArrays/test/sparse.jl b/stdlib/SparseArrays/test/sparse.jl index afadd1540e984..75101e74e1f27 100644 --- a/stdlib/SparseArrays/test/sparse.jl +++ b/stdlib/SparseArrays/test/sparse.jl @@ -25,7 +25,7 @@ end S = sparse(I, 3, 3) fill!(S, 0) @test iszero(S) # test success with stored zeros via fill! - @test iszero(SparseMatrixCSC(2, 2, [1,2,3], [1,2], [0,0,1])) # test success with nonzeros beyond data range + @test_throws ArgumentError iszero(SparseMatrixCSC(2, 2, [1,2,3], [1,2], [0,0,1])) # test failure with nonzeros beyond data range end @testset "isone specialization for SparseMatrixCSC" begin @test isone(sparse(I, 3, 3)) # test success @@ -522,8 +522,6 @@ end @testset "common error checking of [c]transpose! methods (ftranspose!)" begin @test_throws DimensionMismatch transpose!(A[:, 1:(smalldim - 1)], A) @test_throws DimensionMismatch transpose!(A[1:(smalldim - 1), 1], A) - @test_throws ArgumentError transpose!((B = similar(A); resize!(B.rowval, nnz(A) - 1); B), A) - @test_throws ArgumentError transpose!((B = similar(A); resize!(B.nzval, nnz(A) - 1); B), A) end @testset "common error checking of permute[!] methods / source-perm compat" begin @test_throws DimensionMismatch permute(A, p[1:(end - 1)], q) @@ -2117,19 +2115,6 @@ end @test repr("text/plain", sparse([true true])) == "1×2 SparseArrays.SparseMatrixCSC{Bool,$Int} with 2 stored entries:\n [1, 1] = 1\n [1, 2] = 1" end -@testset "check buffers" for n in 1:3 - local A - rowval = [1,2,3] - nzval1 = Int[] - nzval2 = [1,1,1] - A = SparseMatrixCSC(n, n, [1:n+1;], rowval, nzval1) - @test nnz(A) == n - @test_throws BoundsError A[n,n] - A = SparseMatrixCSC(n, n, [1:n+1;], rowval, nzval2) - @test nnz(A) == n - @test A == Matrix(I, n, n) -end - @testset "reverse search direction if step < 0 #21986" begin local A, B A = guardseed(1234) do @@ -2188,22 +2173,22 @@ end @test typeof(simA) == typeof(A) @test size(simA) == (6,6) @test simA.colptr == fill(1, 6+1) - @test length(simA.rowval) == length(A.rowval) - @test length(simA.nzval) == length(A.nzval) + @test length(simA.rowval) == 0 + @test length(simA.nzval) == 0 # test similar with entry type and Dims{2} specification (preserves storage space only) simA = similar(A, Float32, (6,6)) @test typeof(simA) == SparseMatrixCSC{Float32,eltype(A.colptr)} @test size(simA) == (6,6) @test simA.colptr == fill(1, 6+1) - @test length(simA.rowval) == length(A.rowval) - @test length(simA.nzval) == length(A.nzval) + @test length(simA.rowval) == 0 + @test length(simA.nzval) == 0 # test similar with entry type, index type, and Dims{2} specification (preserves storage space only) simA = similar(A, Float32, Int8, (6,6)) @test typeof(simA) == SparseMatrixCSC{Float32, Int8} @test size(simA) == (6,6) @test simA.colptr == fill(1, 6+1) - @test length(simA.rowval) == length(A.rowval) - @test length(simA.nzval) == length(A.nzval) + @test length(simA.rowval) == 0 + @test length(simA.nzval) == 0 # test similar with Dims{1} specification (preserves nothing) simA = similar(A, (6,)) @test typeof(simA) == SparseVector{eltype(A.nzval),eltype(A.colptr)} @@ -2231,8 +2216,6 @@ end # count should throw for sparse arrays for which zero(eltype) does not exist @test_throws MethodError count(SparseMatrixCSC(2, 2, Int[1, 2, 3], Int[1, 2], Any[true, true])) @test_throws MethodError count(SparseVector(2, Int[1], Any[true])) - # count should run only over S.nzval[1:nnz(S)], not S.nzval in full - @test count(SparseMatrixCSC(2, 2, Int[1, 2, 3], Int[1, 2], Bool[true, true, true])) == 2 end @testset "sparse findprev/findnext operations" begin @@ -2280,15 +2263,6 @@ end @test sum(s, dims=2) == reshape([1, 2, 3], 3, 1) end -@testset "mapreduce of sparse matrices with trailing elements in nzval #26534" begin - B = SparseMatrixCSC{Int,Int}(2, 3, - [1, 3, 4, 5], - [1, 2, 1, 2, 999, 999, 999, 999], - [1, 2, 3, 6, 999, 999, 999, 999] - ) - @test maximum(B) == 6 -end - _length_or_count_or_five(::Colon) = 5 _length_or_count_or_five(x::AbstractVector{Bool}) = count(x) _length_or_count_or_five(x) = length(x) @@ -2554,16 +2528,4 @@ end @test_throws ArgumentError sparse(I1, J1, zero(length(I1)zero(length(I1)))) end -@testset "unary operations on matrices where length(nzval)>nnz" begin - # this should create a sparse matrix with length(nzval)>nnz - A = SparseMatrixCSC(Complex{BigInt}[1+im 2+2im]')'[1:1, 2:2] - # ...ensure it does! If necessary, the test needs to be updated to use - # another mechanism to create a suitable A. - @assert length(A.nzval) > nnz(A) - @test -A == fill(-2-2im, 1, 1) - @test conj(A) == fill(2-2im, 1, 1) - conj!(A) - @test A == fill(2-2im, 1, 1) -end - end # module diff --git a/stdlib/SparseArrays/test/sparsevector.jl b/stdlib/SparseArrays/test/sparsevector.jl index 836bce0b9eb7a..650671ea4e8ed 100644 --- a/stdlib/SparseArrays/test/sparsevector.jl +++ b/stdlib/SparseArrays/test/sparsevector.jl @@ -1243,27 +1243,27 @@ end # test entry points to similar with entry type, index type, and non-Dims shape specification @test similar(A, Float32, Int8, 6, 6) == similar(A, Float32, Int8, (6, 6)) @test similar(A, Float32, Int8, 6) == similar(A, Float32, Int8, (6,)) - # test similar with Dims{2} specification (preserves storage space only, not stored-entry structure) + # test similar with Dims{2} specification (preserves allocated storage space only, not stored-entry structure) simA = similar(A, (6,6)) @test typeof(simA) == SparseMatrixCSC{eltype(A.nzval),eltype(A.nzind)} @test size(simA) == (6,6) @test simA.colptr == fill(1, 6+1) - @test length(simA.rowval) == length(A.nzind) - @test length(simA.nzval) == length(A.nzval) - # test similar with entry type and Dims{2} specification (preserves storage space only) + @test length(simA.rowval) == 0 + @test length(simA.nzval) == 0 + # test similar with entry type and Dims{2} specification (preserves allocated storage space only) simA = similar(A, Float32, (6,6)) @test typeof(simA) == SparseMatrixCSC{Float32,eltype(A.nzind)} @test size(simA) == (6,6) @test simA.colptr == fill(1, 6+1) - @test length(simA.rowval) == length(A.nzind) - @test length(simA.nzval) == length(A.nzval) - # test similar with entry type, index type, and Dims{2} specification (preserves storage space only) + @test length(simA.rowval) == 0 + @test length(simA.nzval) == 0 + # test similar with entry type, index type, and Dims{2} specification (preserves allocated storage space only) simA = similar(A, Float32, Int8, (6,6)) @test typeof(simA) == SparseMatrixCSC{Float32, Int8} @test size(simA) == (6,6) @test simA.colptr == fill(1, 6+1) - @test length(simA.rowval) == length(A.nzind) - @test length(simA.nzval) == length(A.nzval) + @test length(simA.rowval) == 0 + @test length(simA.nzval) == 0 end @testset "Fast operations on full column views" begin diff --git a/stdlib/SuiteSparse/src/cholmod.jl b/stdlib/SuiteSparse/src/cholmod.jl index a3f2cc67267cf..d07c8618eb8ca 100644 --- a/stdlib/SuiteSparse/src/cholmod.jl +++ b/stdlib/SuiteSparse/src/cholmod.jl @@ -973,60 +973,52 @@ function Vector{T}(D::Dense{T}) where T end Vector(D::Dense{T}) where {T} = Vector{T}(D) +function _extract_args(s) + return (s.nrow, s.ncol, increment(unsafe_wrap(Array, s.p, (s.ncol + 1,), own = false)), + increment(unsafe_wrap(Array, s.i, (s.nzmax,), own = false)), + copy(unsafe_wrap(Array, s.x, (s.nzmax,), own = false))) +end + +# Trim extra elements in rowval and nzval left around sometimes by CHOLMOD rutines +function _trim_nz_builder!(m, n, colptr, rowval, nzval) + l = colptr[end] - 1 + resize!(rowval, l) + resize!(nzval, l) + SparseMatrixCSC(m, n, colptr, rowval, nzval) +end + function SparseMatrixCSC{Tv,SuiteSparse_long}(A::Sparse{Tv}) where Tv s = unsafe_load(pointer(A)) if s.stype != 0 throw(ArgumentError("matrix has stype != 0. Convert to matrix " * "with stype == 0 before converting to SparseMatrixCSC")) end - - B = SparseMatrixCSC(s.nrow, s.ncol, - increment(unsafe_wrap(Array, s.p, (s.ncol + 1,), own = false)), - increment(unsafe_wrap(Array, s.i, (s.nzmax,), own = false)), - copy(unsafe_wrap(Array, s.x, (s.nzmax,), own = false))) - - if s.sorted == 0 - return SparseArrays.sortSparseMatrixCSC!(B) - else - return B - end + args = _extract_args(s) + s.sorted == 0 && sortBuffers!(args...); + return _trim_nz_builder!(args...) end -function (::Type{Symmetric{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}})(A::Sparse{Float64}) +function Symmetric{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}(A::Sparse{Float64}) s = unsafe_load(pointer(A)) - if !issymmetric(A) - throw(ArgumentError("matrix is not symmetric")) - end - - B = Symmetric(SparseMatrixCSC(s.nrow, s.ncol, - increment(unsafe_wrap(Array, s.p, (s.ncol + 1,), own = false)), - increment(unsafe_wrap(Array, s.i, (s.nzmax,), own = false)), - copy(unsafe_wrap(Array, s.x, (s.nzmax,), own = false))), s.stype > 0 ? :U : :L) - + issymmetric(A) || throw(ArgumentError("matrix is not symmetric")) + args = _extract_args(s) if s.sorted == 0 - return SparseArrays.sortSparseMatrixCSC!(B.data) - else - return B + sortBuffers!(args...) + return _trim_nz_builder!(args...) end + return Symmetric(_trim_nz_builder!(args...), s.stype > 0 ? :U : :L) end convert(T::Type{Symmetric{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}}, A::Sparse{Float64}) = T(A) function Hermitian{Tv,SparseMatrixCSC{Tv,SuiteSparse_long}}(A::Sparse{Tv}) where Tv<:VTypes s = unsafe_load(pointer(A)) - if !ishermitian(A) - throw(ArgumentError("matrix is not Hermitian")) - end - - B = Hermitian(SparseMatrixCSC(s.nrow, s.ncol, - increment(unsafe_wrap(Array, s.p, (s.ncol + 1,), own = false)), - increment(unsafe_wrap(Array, s.i, (s.nzmax,), own = false)), - copy(unsafe_wrap(Array, s.x, (s.nzmax,), own = false))), s.stype > 0 ? :U : :L) - + ishermitian(A) || throw(ArgumentError("matrix is not symmetric")) + args = _extract_args(s) if s.sorted == 0 - return SparseArrays.sortSparseMatrixCSC!(B.data) - else - return B + sortBuffers!(args...) + return _trim_nz_builder!(args...) end + return Hermitian(_trim_nz_builder!(args...), s.stype > 0 ? :U : :L) end convert(T::Type{Hermitian{Tv,SparseMatrixCSC{Tv,SuiteSparse_long}}}, A::Sparse{Tv}) where {Tv<:VTypes} = T(A) @@ -1054,7 +1046,8 @@ function sparse(F::Factor) L, d = getLd!(LD) A = (L * Diagonal(d)) * L' end - SparseArrays.sortSparseMatrixCSC!(A) + # no need to sort buffers here, as A isa SparseMatrixCSC + # and it is taken care in sparse p = get_perm(F) if p != [1:s.n;] pinv = Vector{Int}(undef, length(p)) @@ -1836,4 +1829,58 @@ end (*)(A::SparseVecOrMat{Float64,Ti}, B::Hermitian{Float64,SparseMatrixCSC{Float64,Ti}}) where {Ti} = sparse(Sparse(A)*Sparse(B)) +# Sort all the indices in each column for the construction of a CSC sparse matrix +# sortBuffers!(A, sortindices = :sortcols) # Sort each column with sort() +function sortBuffers!(m, n, colptr::Vector{Ti}, rowval::Vector{Ti}, nzval::Vector{Tv}) where {Ti <: Integer, Tv} + index = Base.zeros(Ti, m) + row = Base.zeros(Ti, m) + val = Base.zeros(Tv, m) + + perm = Base.Perm(Base.ord(isless, identity, false, Base.Order.Forward), row) + + @inbounds for i = 1:n + nzr = colptr[i]:colptr[i+1]-1 + numrows = length(nzr) + if numrows <= 1 + continue + elseif numrows == 2 + f = first(nzr) + s = f+1 + if rowval[f] > rowval[s] + rowval[f], rowval[s] = rowval[s], rowval[f] + nzval[f], nzval[s] = nzval[s], nzval[f] + end + continue + end + resize!(row, numrows) + resize!(index, numrows) + + jj = 1 + @simd for j = nzr + row[jj] = rowval[j] + val[jj] = nzval[j] + jj += 1 + end + + if numrows <= 16 + alg = Base.Sort.InsertionSort + else + alg = Base.Sort.QuickSort + end + + # Reset permutation + index .= 1:numrows + + Base.sort!(index, alg, perm) + + jj = 1 + @simd for j = nzr + rowval[j] = row[index[jj]] + nzval[j] = val[index[jj]] + jj += 1 + end + end +end + + end #module diff --git a/stdlib/SuiteSparse/test/cholmod.jl b/stdlib/SuiteSparse/test/cholmod.jl index 7c220267a1d59..c8d312489f306 100644 --- a/stdlib/SuiteSparse/test/cholmod.jl +++ b/stdlib/SuiteSparse/test/cholmod.jl @@ -763,13 +763,13 @@ end end end -@testset "Check inputs to Sparse. Related to #20024" for A_ in ( - SparseMatrixCSC(2, 2, [1, 2], CHOLMOD.SuiteSparse_long[], Float64[]), - SparseMatrixCSC(2, 2, [1, 2, 3], CHOLMOD.SuiteSparse_long[1], Float64[]), - SparseMatrixCSC(2, 2, [1, 2, 3], CHOLMOD.SuiteSparse_long[], Float64[1.0]), - SparseMatrixCSC(2, 2, [1, 2, 3], CHOLMOD.SuiteSparse_long[1], Float64[1.0])) - @test_throws ArgumentError CHOLMOD.Sparse(size(A_)..., A_.colptr .- 1, A_.rowval .- 1, A_.nzval) - @test_throws ArgumentError CHOLMOD.Sparse(A_) +@testset "Check inputs to Sparse. Related to #20024" for t_ in ( + (2, 2, [1, 2], CHOLMOD.SuiteSparse_long[], Float64[]), + (2, 2, [1, 2, 3], CHOLMOD.SuiteSparse_long[1], Float64[]), + (2, 2, [1, 2, 3], CHOLMOD.SuiteSparse_long[], Float64[1.0]), + (2, 2, [1, 2, 3], CHOLMOD.SuiteSparse_long[1], Float64[1.0])) + @test_throws ArgumentError SparseMatrixCSC(t_...) + @test_throws ArgumentError CHOLMOD.Sparse(t_[1], t_[2], t_[3] .- 1, t_[4] .- 1, t_[5]) end @testset "sparse right multiplication of Symmetric and Hermitian matrices #21431" begin