Skip to content

Commit

Permalink
let spdiagm preserve sparsity (#37684)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarrasch authored Sep 28, 2020
1 parent b89c4a3 commit c4d1cd9
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 15 deletions.
7 changes: 6 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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

Expand Down
76 changes: 62 additions & 14 deletions stdlib/SparseArrays/src/sparsematrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

"""
Expand All @@ -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"))
Expand Down
12 changes: 12 additions & 0 deletions stdlib/SparseArrays/test/sparse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit c4d1cd9

Please sign in to comment.