diff --git a/NEWS.md b/NEWS.md index f05f2c669ab16..785dc5be73f3e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -163,9 +163,13 @@ Standard library changes #### SparseArrays -* Display large sparse matrices with a Unicode "spy" plot of their nonzero patterns, and display small sparse matrices by an `Matrix`-like 2d layout of their contents. +* Display large sparse matrices with a Unicode "spy" plot of their nonzero patterns, + and display small sparse matrices by an `Matrix`-like 2d layout of their contents. +* New convenient `spdiagm([m, n,] v::AbstractVector)` methods which call + `spdiagm([m, n,] 0 => v)`, consistently with their dense `diagm` counterparts. ([#37684]) #### Dates + * `Quarter` period is defined ([#35519]). * Zero-valued `FixedPeriod`s and `OtherPeriod`s now compare equal, e.g., `Year(0) == Day(0)`. The behavior of non-zero `Period`s is not changed. ([#37486]) @@ -180,6 +184,7 @@ Standard library changes #### UUIDs + * Change `uuid1` and `uuid4` to use `Random.RandomDevice()` as default random number generator ([#35872]). * Added `parse(::Type{UUID}, ::AbstractString)` method diff --git a/stdlib/SparseArrays/src/sparsematrix.jl b/stdlib/SparseArrays/src/sparsematrix.jl index fbb2adf3b66a1..7eb7f0dc529cf 100644 --- a/stdlib/SparseArrays/src/sparsematrix.jl +++ b/stdlib/SparseArrays/src/sparsematrix.jl @@ -3491,37 +3491,55 @@ function istril(A::AbstractSparseMatrixCSC) return true end +_nnz(v::AbstractSparseVector) = nnz(v) +_nnz(v::AbstractVector) = length(v) + +function _indices(v::AbstractSparseVector, row, col) + ix = nonzeroinds(v) + return (row .+ ix, col .+ ix) +end +function _indices(v::AbstractVector, row, col) + veclen = length(v) + return (row+1:row+veclen, col+1:col+veclen) +end + +_nzvals(v::AbstractSparseVector) = nonzeros(v) +_nzvals(v::AbstractVector) = v function spdiagm_internal(kv::Pair{<:Integer,<:AbstractVector}...) ncoeffs = 0 for p in kv - ncoeffs += length(p.second) + ncoeffs += _nnz(p.second) end I = Vector{Int}(undef, ncoeffs) J = Vector{Int}(undef, ncoeffs) V = Vector{promote_type(map(x -> eltype(x.second), kv)...)}(undef, ncoeffs) i = 0 + m = 0 + n = 0 for p in kv - dia = p.first - vect = p.second - numel = length(vect) - if dia < 0 - row = -dia + k = p.first + v = p.second + if k < 0 + row = -k col = 0 - elseif dia > 0 + elseif k > 0 row = 0 - col = dia + col = k else row = 0 col = 0 end + numel = _nnz(v) r = 1+i:numel+i - I[r] = row+1:row+numel - J[r] = col+1:col+numel - copyto!(view(V, r), vect) + I[r], J[r] = _indices(v, row, col) + copyto!(view(V, r), _nzvals(v)) + veclen = length(v) + m = max(m, row + veclen) + n = max(n, col + veclen) i += numel end - return I, J, V + return I, J, V, m, n end """ @@ -3547,9 +3565,39 @@ julia> spdiagm(-1 => [1,2,3,4], 1 => [4,3,2,1]) """ spdiagm(kv::Pair{<:Integer,<:AbstractVector}...) = _spdiagm(nothing, kv...) spdiagm(m::Integer, n::Integer, kv::Pair{<:Integer,<:AbstractVector}...) = _spdiagm((Int(m),Int(n)), kv...) + +""" + spdiagm(v::AbstractVector) + spdiagm(m::Integer, n::Integer, v::AbstractVector) + +Construct a sparse matrix with elements of the vector as diagonal elements. +By default (no given `m` and `n`), the matrix is square and its size is given +by `length(v)`, but a non-square size `m`×`n` can be specified by passing `m` +and `n` as the first arguments. + +!!! compat "Julia 1.6" + These functions require at least Julia 1.6. + +# Examples +```jldoctest +julia> spdiagm([1,2,3]) +3×3 SparseMatrixCSC{Int64, Int64} with 3 stored entries: + 1 ⋅ ⋅ + ⋅ 2 ⋅ + ⋅ ⋅ 3 + +julia> spdiagm(sparse([1,0,3])) +3×3 SparseMatrixCSC{Int64, Int64} with 2 stored entries: + 1 ⋅ ⋅ + ⋅ ⋅ ⋅ + ⋅ ⋅ 3 +``` +""" +spdiagm(v::AbstractVector) = _spdiagm(nothing, 0 => v) +spdiagm(m::Integer, n::Integer, v::AbstractVector) = _spdiagm((Int(m), Int(n)), 0 => v) + function _spdiagm(size, kv::Pair{<:Integer,<:AbstractVector}...) - I, J, V = spdiagm_internal(kv...) - mmax, nmax = dimlub(I), dimlub(J) + I, J, V, mmax, nmax = spdiagm_internal(kv...) mnmax = max(mmax, nmax) m, n = something(size, (mnmax,mnmax)) (m ≥ mmax && n ≥ nmax) || throw(DimensionMismatch("invalid size=$size")) diff --git a/stdlib/SparseArrays/test/sparse.jl b/stdlib/SparseArrays/test/sparse.jl index 79e5a98d6bc22..7c228fd7f619d 100644 --- a/stdlib/SparseArrays/test/sparse.jl +++ b/stdlib/SparseArrays/test/sparse.jl @@ -1750,6 +1750,13 @@ end # promotion @test spdiagm(0 => [1,2], 1 => [3.5], -1 => [4+5im]) == [1 3.5; 4+5im 2] + # convenience constructor + @test spdiagm(x)::SparseMatrixCSC == diagm(x) + @test nnz(spdiagm(x)) == count(!iszero, x) + @test nnz(spdiagm(sparse([x; 0]))) == 2 + @test spdiagm(3, 4, x)::SparseMatrixCSC == diagm(3, 4, x) + @test nnz(spdiagm(3, 4, sparse([x; 0]))) == 2 + # non-square: for m=1:4, n=2:4 if m < 2 || n < 3 @@ -1760,6 +1767,11 @@ end @test spdiagm(m,n, 0 => x, 1 => x) == M end end + + # sparsity-preservation + x = sprand(10, 0.2); y = ones(9) + @test spdiagm(0 => x, 1 => y) == diagm(0 => x, 1 => y) + @test nnz(spdiagm(0 => x, 1 => y)) == length(y) + nnz(x) end @testset "diag" begin