# This file is a part of Julia. License is MIT: https://julialang.org/license ### Common definitions import Base: scalarmax, scalarmin, sort, find, findnz import Base.LinAlg: promote_to_array_type, promote_to_arrays_ ### The SparseVector ### Types """ SparseVector{Tv,Ti<:Integer} <: AbstractSparseVector{Tv,Ti} Vector type for storing sparse vectors. """ struct SparseVector{Tv,Ti<:Integer} <: AbstractSparseVector{Tv,Ti} n::Int # Length of the sparse vector nzind::Vector{Ti} # Indices of stored values nzval::Vector{Tv} # Stored values, typically nonzeros function SparseVector{Tv,Ti}(n::Integer, nzind::Vector{Ti}, nzval::Vector{Tv}) where {Tv,Ti<:Integer} n >= 0 || throw(ArgumentError("The number of elements must be non-negative.")) length(nzind) == length(nzval) || throw(ArgumentError("index and value vectors must be the same length")) new(convert(Int, n), nzind, nzval) end end SparseVector(n::Integer, nzind::Vector{Ti}, nzval::Vector{Tv}) where {Tv,Ti} = SparseVector{Tv,Ti}(n, nzind, nzval) ### Basic properties length(x::SparseVector) = x.n size(x::SparseVector) = (x.n,) nnz(x::SparseVector) = length(x.nzval) countnz(x::SparseVector) = countnz(x.nzval) count(x::SparseVector) = count(x.nzval) nonzeros(x::SparseVector) = x.nzval nonzeroinds(x::SparseVector) = x.nzind similar(x::SparseVector, Tv::Type=eltype(x)) = SparseVector(x.n, copy(x.nzind), Vector{Tv}(length(x.nzval))) function similar(x::SparseVector, ::Type{Tv}, ::Type{Ti}) where {Tv,Ti} return SparseVector(x.n, copy!(similar(x.nzind, Ti), x.nzind), copy!(similar(x.nzval, Tv), x.nzval)) end similar(x::SparseVector, ::Type{T}, D::Dims) where {T} = spzeros(T, D...) ### Construct empty sparse vector spzeros(len::Integer) = spzeros(Float64, len) spzeros(::Type{T}, len::Integer) where {T} = SparseVector(len, Int[], T[]) spzeros(::Type{Tv}, ::Type{Ti}, len::Integer) where {Tv,Ti<:Integer} = SparseVector(len, Ti[], Tv[]) # Construction of same structure, but with all ones spones(x::SparseVector{T}) where {T} = SparseVector(x.n, copy(x.nzind), ones(T, length(x.nzval))) ### Construction from lists of indices and values function _sparsevector!(I::Vector{<:Integer}, V::Vector, len::Integer) # pre-condition: no duplicate indexes in I if !isempty(I) p = sortperm(I) permute!(I, p) permute!(V, p) end SparseVector(len, I, V) end function _sparsevector!(I::Vector{<:Integer}, V::Vector, len::Integer, combine::Function) if !isempty(I) p = sortperm(I) permute!(I, p) permute!(V, p) m = length(I) r = 1 l = 1 # length of processed part i = I[r] # row-index of current element # main loop while r < m r += 1 i2 = I[r] if i2 == i # accumulate r-th to the l-th entry V[l] = combine(V[l], V[r]) else # advance l, and move r-th to l-th pv = V[l] l += 1 i = i2 if l < r I[l] = i; V[l] = V[r] end end end if l < m resize!(I, l) resize!(V, l) end end SparseVector(len, I, V) end """ sparsevec(I, V, [m, combine]) Create a sparse vector `S` of length `m` such that `S[I[k]] = V[k]`. Duplicates are combined using the `combine` function, which defaults to `+` if no `combine` argument is provided, unless the elements of `V` are Booleans in which case `combine` defaults to `|`. ```jldoctest julia> II = [1, 3, 3, 5]; V = [0.1, 0.2, 0.3, 0.2]; julia> sparsevec(II, V) 5-element SparseVector{Float64,Int64} with 3 stored entries: [1] = 0.1 [3] = 0.5 [5] = 0.2 julia> sparsevec(II, V, 8, -) 8-element SparseVector{Float64,Int64} with 3 stored entries: [1] = 0.1 [3] = -0.1 [5] = 0.2 julia> sparsevec([1, 3, 1, 2, 2], [true, true, false, false, false]) 3-element SparseVector{Bool,Int64} with 3 stored entries: [1] = true [2] = false [3] = true ``` """ function sparsevec(I::AbstractVector{<:Integer}, V::AbstractVector, combine::Function) length(I) == length(V) || throw(ArgumentError("index and value vectors must be the same length")) len = 0 for i in I i >= 1 || error("Index must be positive.") if i > len len = i end end _sparsevector!(collect(I), collect(V), len, combine) end function sparsevec(I::AbstractVector{<:Integer}, V::AbstractVector, len::Integer, combine::Function) length(I) == length(V) || throw(ArgumentError("index and value vectors must be the same length")) for i in I 1 <= i <= len || throw(ArgumentError("An index is out of bound.")) end _sparsevector!(collect(I), collect(V), len, combine) end sparsevec(I::AbstractVector, V::Union{Number, AbstractVector}, args...) = sparsevec(Vector{Int}(I), V, args...) sparsevec(I::AbstractVector, V::Union{Number, AbstractVector}) = sparsevec(I, V, +) sparsevec(I::AbstractVector, V::Union{Number, AbstractVector}, len::Integer) = sparsevec(I, V, len, +) sparsevec(I::AbstractVector, V::Union{Bool, AbstractVector{Bool}}) = sparsevec(I, V, |) sparsevec(I::AbstractVector, V::Union{Bool, AbstractVector{Bool}}, len::Integer) = sparsevec(I, V, len, |) sparsevec(I::AbstractVector, v::Number, combine::Function) = sparsevec(I, fill(v, length(I)), combine) sparsevec(I::AbstractVector, v::Number, len::Integer, combine::Function) = sparsevec(I, fill(v, length(I)), len, combine) ### Construction from dictionary """ sparsevec(d::Dict, [m]) Create a sparse vector of length `m` where the nonzero indices are keys from the dictionary, and the nonzero values are the values from the dictionary. ```jldoctest julia> sparsevec(Dict(1 => 3, 2 => 2)) 2-element SparseVector{Int64,Int64} with 2 stored entries: [1] = 3 [2] = 2 ``` """ function sparsevec(dict::Associative{Ti,Tv}) where {Tv,Ti<:Integer} m = length(dict) nzind = Vector{Ti}(m) nzval = Vector{Tv}(m) cnt = 0 len = zero(Ti) for (k, v) in dict k >= 1 || throw(ArgumentError("index must be positive.")) if k > len len = k end cnt += 1 @inbounds nzind[cnt] = k @inbounds nzval[cnt] = v end resize!(nzind, cnt) resize!(nzval, cnt) _sparsevector!(nzind, nzval, len) end function sparsevec(dict::Associative{Ti,Tv}, len::Integer) where {Tv,Ti<:Integer} m = length(dict) nzind = Vector{Ti}(m) nzval = Vector{Tv}(m) cnt = 0 maxk = convert(Ti, len) for (k, v) in dict 1 <= k <= maxk || throw(ArgumentError("an index (key) is out of bound.")) cnt += 1 @inbounds nzind[cnt] = k @inbounds nzval[cnt] = v end resize!(nzind, cnt) resize!(nzval, cnt) _sparsevector!(nzind, nzval, len) end ### Element access function setindex!(x::SparseVector{Tv,Ti}, v::Tv, i::Ti) where {Tv,Ti<:Integer} checkbounds(x, i) nzind = nonzeroinds(x) nzval = nonzeros(x) m = length(nzind) k = searchsortedfirst(nzind, i) if 1 <= k <= m && nzind[k] == i # i found nzval[k] = v else # i not found if v != 0 insert!(nzind, k, i) insert!(nzval, k, v) end end x end setindex!(x::SparseVector{Tv,Ti}, v, i::Integer) where {Tv,Ti<:Integer} = setindex!(x, convert(Tv, v), convert(Ti, i)) ### dropstored! """ dropstored!(x::SparseVector, i::Integer) Drop entry `x[i]` from `x` if `x[i]` is stored and otherwise do nothing. # Examples ```jldoctest julia> x = sparsevec([1, 3], [1.0, 2.0]) 3-element SparseVector{Float64,Int64} with 2 stored entries: [1] = 1.0 [3] = 2.0 julia> Base.SparseArrays.dropstored!(x, 3) 3-element SparseVector{Float64,Int64} with 1 stored entry: [1] = 1.0 julia> Base.SparseArrays.dropstored!(x, 2) 3-element SparseVector{Float64,Int64} with 1 stored entry: [1] = 1.0 ``` """ function dropstored!(x::SparseVector, i::Integer) if !(1 <= i <= x.n) throw(BoundsError(x, i)) end searchk = searchsortedfirst(x.nzind, i) if searchk <= length(x.nzind) && x.nzind[searchk] == i # Entry x[i] is stored. Drop and return. deleteat!(x.nzind, searchk) deleteat!(x.nzval, searchk) end return x end # TODO: Implement linear collection indexing methods for dropstored! ? # TODO: Implement logical indexing methods for dropstored! ? ### Conversion # convert SparseMatrixCSC to SparseVector function convert(::Type{SparseVector{Tv,Ti}}, s::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti<:Integer} size(s, 2) == 1 || throw(ArgumentError("The input argument must have a single-column.")) SparseVector(s.m, s.rowval, s.nzval) end convert(::Type{SparseVector{Tv}}, s::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = convert(SparseVector{Tv,Ti}, s) convert(::Type{SparseVector}, s::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = convert(SparseVector{Tv,Ti}, s) # convert Vector to SparseVector """ sparsevec(A) Convert a vector `A` into a sparse vector of length `m`. # Example ```jldoctest julia> sparsevec([1.0, 2.0, 0.0, 0.0, 3.0, 0.0]) 6-element SparseVector{Float64,Int64} with 3 stored entries: [1] = 1.0 [2] = 2.0 [5] = 3.0 ``` """ sparsevec(a::AbstractVector{T}) where {T} = convert(SparseVector{T, Int}, a) sparsevec(a::AbstractArray) = sparsevec(vec(a)) sparsevec(a::AbstractSparseArray) = vec(a) sparsevec(a::AbstractSparseVector) = vec(a) sparse(a::AbstractVector) = sparsevec(a) function _dense2sparsevec(s::AbstractArray{Tv}, initcap::Ti) where {Tv,Ti} # pre-condition: initcap > 0; the initcap determines the index type n = length(s) cap = initcap nzind = Vector{Ti}(cap) nzval = Vector{Tv}(cap) c = 0 @inbounds for i = 1:n v = s[i] if v != 0 if c >= cap cap *= 2 resize!(nzind, cap) resize!(nzval, cap) end c += 1 nzind[c] = i nzval[c] = v end end if c < cap resize!(nzind, c) resize!(nzval, c) end SparseVector(n, nzind, nzval) end convert(::Type{SparseVector{Tv,Ti}}, s::AbstractVector{Tv}) where {Tv,Ti} = _dense2sparsevec(s, convert(Ti, max(8, div(length(s), 8)))) convert(::Type{SparseVector{Tv}}, s::AbstractVector{Tv}) where {Tv} = convert(SparseVector{Tv,Int}, s) convert(::Type{SparseVector}, s::AbstractVector{Tv}) where {Tv} = convert(SparseVector{Tv,Int}, s) # convert between different types of SparseVector convert(::Type{SparseVector{Tv}}, s::SparseVector{Tv}) where {Tv} = s convert(::Type{SparseVector{Tv,Ti}}, s::SparseVector{Tv,Ti}) where {Tv,Ti} = s convert(::Type{SparseVector{Tv,Ti}}, s::SparseVector) where {Tv,Ti} = SparseVector{Tv,Ti}(s.n, convert(Vector{Ti}, s.nzind), convert(Vector{Tv}, s.nzval)) convert(::Type{SparseVector{Tv}}, s::SparseVector{<:Any,Ti}) where {Tv,Ti} = SparseVector{Tv,Ti}(s.n, s.nzind, convert(Vector{Tv}, s.nzval)) ### copying function prep_sparsevec_copy_dest!(A::SparseVector, lB, nnzB) lA = length(A) lA >= lB || throw(BoundsError()) # If the two vectors have the same length then all the elements in A will be overwritten. if length(A) == lB resize!(A.nzval, nnzB) resize!(A.nzind, nnzB) else nnzA = nnz(A) lastmodindA = searchsortedlast(A.nzind, lB) if lastmodindA >= nnzB # A will have fewer non-zero elements; unmodified elements are kept at the end. deleteat!(A.nzind, nnzB+1:lastmodindA) deleteat!(A.nzval, nnzB+1:lastmodindA) else # A will have more non-zero elements; unmodified elements are kept at the end. resize!(A.nzind, nnzB + nnzA - lastmodindA) resize!(A.nzval, nnzB + nnzA - lastmodindA) copy!(A.nzind, nnzB+1, A.nzind, lastmodindA+1, nnzA-lastmodindA) copy!(A.nzval, nnzB+1, A.nzval, lastmodindA+1, nnzA-lastmodindA) end end end function copy!(A::SparseVector, B::SparseVector) prep_sparsevec_copy_dest!(A, length(B), nnz(B)) copy!(A.nzind, B.nzind) copy!(A.nzval, B.nzval) return A end function copy!(A::SparseVector, B::SparseMatrixCSC) prep_sparsevec_copy_dest!(A, length(B), nnz(B)) ptr = 1 @assert length(A.nzind) >= length(B.rowval) maximum(B.colptr)-1 <= length(B.rowval) || throw(BoundsError()) @inbounds for col=1:length(B.colptr)-1 offsetA = (col - 1) * B.m while ptr <= B.colptr[col+1]-1 A.nzind[ptr] = B.rowval[ptr] + offsetA ptr += 1 end end copy!(A.nzval, B.nzval) return A end copy!(A::SparseMatrixCSC, B::SparseVector{TvB,TiB}) where {TvB,TiB} = copy!(A, SparseMatrixCSC{TvB,TiB}(B.n, 1, TiB[1, length(B.nzind)+1], B.nzind, B.nzval)) ### Rand Construction sprand(n::Integer, p::AbstractFloat, rfn::Function, ::Type{T}) where {T} = sprand(GLOBAL_RNG, n, p, rfn, T) function sprand(r::AbstractRNG, n::Integer, p::AbstractFloat, rfn::Function, ::Type{T}) where T I = randsubseq(r, 1:convert(Int, n), p) V = rfn(r, T, length(I)) SparseVector(n, I, V) end sprand(n::Integer, p::AbstractFloat, rfn::Function) = sprand(GLOBAL_RNG, n, p, rfn) function sprand(r::AbstractRNG, n::Integer, p::AbstractFloat, rfn::Function) I = randsubseq(r, 1:convert(Int, n), p) V = rfn(r, length(I)) SparseVector(n, I, V) end sprand(n::Integer, p::AbstractFloat) = sprand(GLOBAL_RNG, n, p, rand) sprand(r::AbstractRNG, n::Integer, p::AbstractFloat) = sprand(r, n, p, rand) sprand(r::AbstractRNG, ::Type{T}, n::Integer, p::AbstractFloat) where {T} = sprand(r, n, p, (r, i) -> rand(r, T, i)) sprand(r::AbstractRNG, ::Type{Bool}, n::Integer, p::AbstractFloat) = sprand(r, n, p, truebools) sprand(::Type{T}, n::Integer, p::AbstractFloat) where {T} = sprand(GLOBAL_RNG, T, n, p) sprandn(n::Integer, p::AbstractFloat) = sprand(GLOBAL_RNG, n, p, randn) sprandn(r::AbstractRNG, n::Integer, p::AbstractFloat) = sprand(r, n, p, randn) ## Indexing into Matrices can return SparseVectors # Column slices function getindex(x::SparseMatrixCSC, ::Colon, j::Integer) checkbounds(x, :, j) r1 = convert(Int, x.colptr[j]) r2 = convert(Int, x.colptr[j+1]) - 1 SparseVector(x.m, x.rowval[r1:r2], x.nzval[r1:r2]) end function getindex(x::SparseMatrixCSC, I::AbstractUnitRange, j::Integer) checkbounds(x, I, j) # Get the selected column c1 = convert(Int, x.colptr[j]) c2 = convert(Int, x.colptr[j+1]) - 1 # Restrict to the selected rows r1 = searchsortedfirst(x.rowval, first(I), c1, c2, Forward) r2 = searchsortedlast(x.rowval, last(I), c1, c2, Forward) SparseVector(length(I), [x.rowval[i] - first(I) + 1 for i = r1:r2], x.nzval[r1:r2]) end # In the general case, we piggy back upon SparseMatrixCSC's optimized solution @inline function getindex(A::SparseMatrixCSC, I::AbstractVector, J::Integer) M = A[I, [J]] SparseVector(M.m, M.rowval, M.nzval) end # Row slices getindex(A::SparseMatrixCSC, i::Integer, ::Colon) = A[i, 1:end] function Base.getindex(A::SparseMatrixCSC{Tv,Ti}, i::Integer, J::AbstractVector) where {Tv,Ti} checkbounds(A, i, J) nJ = length(J) colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval nzinds = Vector{Ti}(0) nzvals = Vector{Tv}(0) # adapted from SparseMatrixCSC's sorted_bsearch_A ptrI = 1 @inbounds for j = 1:nJ col = J[j] rowI = i ptrA = Int(colptrA[col]) stopA = Int(colptrA[col+1]-1) if ptrA <= stopA if rowvalA[ptrA] <= rowI ptrA = searchsortedfirst(rowvalA, rowI, ptrA, stopA, Base.Order.Forward) if ptrA <= stopA && rowvalA[ptrA] == rowI push!(nzinds, j) push!(nzvals, nzvalA[ptrA]) end end ptrI += 1 end end return SparseVector(nJ, nzinds, nzvals) end # Logical and linear indexing into SparseMatrices getindex(A::SparseMatrixCSC, I::AbstractVector{Bool}) = _logical_index(A, I) # Ambiguities getindex(A::SparseMatrixCSC, I::AbstractArray{Bool}) = _logical_index(A, I) function _logical_index(A::SparseMatrixCSC{Tv}, I::AbstractArray{Bool}) where Tv checkbounds(A, I) n = sum(I) nnzB = min(n, nnz(A)) colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval rowvalB = Vector{Int}(nnzB) nzvalB = Vector{Tv}(nnzB) c = 1 rowB = 1 @inbounds for col in 1:A.n r1 = colptrA[col] r2 = colptrA[col+1]-1 for row in 1:A.m if I[row, col] while (r1 <= r2) && (rowvalA[r1] < row) r1 += 1 end if (r1 <= r2) && (rowvalA[r1] == row) nzvalB[c] = nzvalA[r1] rowvalB[c] = rowB c += 1 end rowB += 1 (rowB > n) && break end end (rowB > n) && break end if nnzB > (c-1) deleteat!(nzvalB, c:nnzB) deleteat!(rowvalB, c:nnzB) end SparseVector(n, rowvalB, nzvalB) end # TODO: further optimizations are available for ::Colon and other types of Range getindex(A::SparseMatrixCSC, ::Colon) = A[1:end] function getindex(A::SparseMatrixCSC{Tv}, I::AbstractUnitRange) where Tv checkbounds(A, I) szA = size(A) nA = szA[1]*szA[2] colptrA = A.colptr rowvalA = A.rowval nzvalA = A.nzval n = length(I) nnzB = min(n, nnz(A)) rowvalB = Vector{Int}(nnzB) nzvalB = Vector{Tv}(nnzB) rowstart,colstart = ind2sub(szA, first(I)) rowend,colend = ind2sub(szA, last(I)) idxB = 1 @inbounds for col in colstart:colend minrow = (col == colstart ? rowstart : 1) maxrow = (col == colend ? rowend : szA[1]) for r in colptrA[col]:(colptrA[col+1]-1) rowA = rowvalA[r] if minrow <= rowA <= maxrow rowvalB[idxB] = sub2ind(szA, rowA, col) - first(I) + 1 nzvalB[idxB] = nzvalA[r] idxB += 1 end end end if nnzB > (idxB-1) deleteat!(nzvalB, idxB:nnzB) deleteat!(rowvalB, idxB:nnzB) end SparseVector(n, rowvalB, nzvalB) end function getindex(A::SparseMatrixCSC{Tv}, I::AbstractVector) where Tv szA = size(A) nA = szA[1]*szA[2] colptrA = A.colptr rowvalA = A.rowval nzvalA = A.nzval n = length(I) nnzB = min(n, nnz(A)) rowvalB = Vector{Int}(nnzB) nzvalB = Vector{Tv}(nnzB) idxB = 1 for i in 1:n ((I[i] < 1) | (I[i] > nA)) && throw(BoundsError(A, I)) row,col = ind2sub(szA, I[i]) for r in colptrA[col]:(colptrA[col+1]-1) @inbounds if rowvalA[r] == row if idxB <= nnzB rowvalB[idxB] = i nzvalB[idxB] = nzvalA[r] idxB += 1 else # this can happen if there are repeated indices in I push!(rowvalB, i) push!(nzvalB, nzvalA[r]) end break end end end if nnzB > (idxB-1) deleteat!(nzvalB, idxB:nnzB) deleteat!(rowvalB, idxB:nnzB) end SparseVector(n, rowvalB, nzvalB) end function find(x::SparseVector{<:Any,Ti}) where Ti numnz = nnz(x) I = Vector{Ti}(numnz) nzind = x.nzind nzval = x.nzval count = 1 @inbounds for i = 1 : numnz if nzval[i] != 0 I[count] = nzind[i] count += 1 end end count -= 1 if numnz != count deleteat!(I, (count+1):numnz) end return I end function findnz(x::SparseVector{Tv,Ti}) where {Tv,Ti} numnz = nnz(x) I = Vector{Ti}(numnz) V = Vector{Tv}(numnz) nzind = x.nzind nzval = x.nzval count = 1 @inbounds for i = 1 : numnz if nzval[i] != 0 I[count] = nzind[i] V[count] = nzval[i] count += 1 end end count -= 1 if numnz != count deleteat!(I, (count+1):numnz) deleteat!(V, (count+1):numnz) end return (I, V) end ### Generic functions operating on AbstractSparseVector ### getindex function _spgetindex(m::Int, nzind::AbstractVector{Ti}, nzval::AbstractVector{Tv}, i::Integer) where {Tv,Ti} ii = searchsortedfirst(nzind, convert(Ti, i)) (ii <= m && nzind[ii] == i) ? nzval[ii] : zero(Tv) end function getindex(x::AbstractSparseVector, i::Integer) checkbounds(x, i) _spgetindex(nnz(x), nonzeroinds(x), nonzeros(x), i) end function getindex(x::AbstractSparseVector{Tv,Ti}, I::AbstractUnitRange) where {Tv,Ti} checkbounds(x, I) xlen = length(x) i0 = first(I) i1 = last(I) xnzind = nonzeroinds(x) xnzval = nonzeros(x) m = length(xnzind) # locate the first j0, s.t. xnzind[j0] >= i0 j0 = searchsortedfirst(xnzind, i0) # locate the last j1, s.t. xnzind[j1] <= i1 j1 = searchsortedlast(xnzind, i1, j0, m, Forward) # compute the number of non-zeros jrgn = j0:j1 mr = length(jrgn) rind = Vector{Ti}(mr) rval = Vector{Tv}(mr) if mr > 0 c = 0 for j in jrgn c += 1 rind[c] = convert(Ti, xnzind[j] - i0 + 1) rval[c] = xnzval[j] end end SparseVector(length(I), rind, rval) end getindex(x::AbstractSparseVector, I::AbstractVector{Bool}) = x[find(I)] getindex(x::AbstractSparseVector, I::AbstractArray{Bool}) = x[find(I)] @inline function getindex(x::AbstractSparseVector, I::AbstractVector) # SparseMatrixCSC has a nicely optimized routine for this; punt S = SparseMatrixCSC(x.n, 1, [1,length(x.nzind)+1], x.nzind, x.nzval) S[I, 1] end function getindex(x::AbstractSparseVector, I::AbstractArray) # punt to SparseMatrixCSC S = SparseMatrixCSC(x.n, 1, [1,length(x.nzind)+1], x.nzind, x.nzval) S[I] end getindex(x::AbstractSparseVector, ::Colon) = copy(x) ### show and friends function show(io::IO, ::MIME"text/plain", x::AbstractSparseVector) xnnz = length(nonzeros(x)) print(io, length(x), "-element ", typeof(x), " with ", xnnz, " stored ", xnnz == 1 ? "entry" : "entries") if xnnz != 0 println(io, ":") show(io, x) end end show(io::IO, x::AbstractSparseVector) = show(convert(IOContext, io), x) function show(io::IOContext, x::AbstractSparseVector) # TODO: make this a one-line form n = length(x) nzind = nonzeroinds(x) nzval = nonzeros(x) xnnz = length(nzind) if length(nzind) == 0 return show(io, MIME("text/plain"), x) end limit::Bool = get(io, :limit, false) half_screen_rows = limit ? div(displaysize(io)[1] - 8, 2) : typemax(Int) pad = ndigits(n) if !haskey(io, :compact) io = IOContext(io, :compact => true) end for k = 1:length(nzind) if k < half_screen_rows || k > xnnz - half_screen_rows print(io, " ", '[', rpad(nzind[k], pad), "] = ") if isassigned(nzval, Int(k)) show(io, nzval[k]) else print(io, Base.undef_ref_str) end k != length(nzind) && println(io) elseif k == half_screen_rows println(io, " ", " "^pad, " \u22ee") end end end ### Conversion to matrix function convert(::Type{SparseMatrixCSC{Tv,Ti}}, x::AbstractSparseVector) where {Tv,Ti} n = length(x) xnzind = nonzeroinds(x) xnzval = nonzeros(x) m = length(xnzind) colptr = Ti[1, m+1] # Note that this *cannot* share data like normal array conversions, since # modifying one would put the other in an inconsistent state rowval = collect(Ti, xnzind) nzval = collect(Tv, xnzval) SparseMatrixCSC(n, 1, colptr, rowval, nzval) end convert(::Type{SparseMatrixCSC{Tv}}, x::AbstractSparseVector{<:Any,Ti}) where {Tv,Ti} = convert(SparseMatrixCSC{Tv,Ti}, x) convert(::Type{SparseMatrixCSC}, x::AbstractSparseVector{Tv,Ti}) where {Tv,Ti} = convert(SparseMatrixCSC{Tv,Ti}, x) function convert(::Type{Vector}, x::AbstractSparseVector{Tv}) where Tv n = length(x) n == 0 && return Vector{Tv}(0) nzind = nonzeroinds(x) nzval = nonzeros(x) r = zeros(Tv, n) for k in 1:nnz(x) i = nzind[k] v = nzval[k] r[i] = v end return r end convert(::Type{Array}, x::AbstractSparseVector) = convert(Vector, x) full(x::AbstractSparseVector) = convert(Array, x) ### Array manipulation vec(x::AbstractSparseVector) = x copy(x::AbstractSparseVector) = SparseVector(length(x), copy(nonzeroinds(x)), copy(nonzeros(x))) function reinterpret(::Type{T}, x::AbstractSparseVector{Tv}) where {T,Tv} sizeof(T) == sizeof(Tv) || throw(ArgumentError("reinterpret of sparse vectors only supports element types of the same size.")) SparseVector(length(x), copy(nonzeroinds(x)), reinterpret(T, nonzeros(x))) end float(x::AbstractSparseVector{<:AbstractFloat}) = x float(x::AbstractSparseVector) = SparseVector(length(x), copy(nonzeroinds(x)), float(nonzeros(x))) complex(x::AbstractSparseVector{<:Complex}) = x complex(x::AbstractSparseVector) = SparseVector(length(x), copy(nonzeroinds(x)), complex(nonzeros(x))) ### Concatenation # Without the first of these methods, horizontal concatenations of SparseVectors fall # back to the horizontal concatenation method that ensures that combinations of # sparse/special/dense matrix/vector types concatenate to SparseMatrixCSCs, instead # of _absspvec_hcat below. The <:Integer qualifications are necessary for correct dispatch. hcat(X::SparseVector{Tv,Ti}...) where {Tv,Ti<:Integer} = _absspvec_hcat(X...) hcat(X::AbstractSparseVector{Tv,Ti}...) where {Tv,Ti<:Integer} = _absspvec_hcat(X...) function _absspvec_hcat(X::AbstractSparseVector{Tv,Ti}...) where {Tv,Ti} # check sizes n = length(X) m = length(X[1]) tnnz = nnz(X[1]) for j = 2:n length(X[j]) == m || throw(DimensionMismatch("Inconsistent column lengths.")) tnnz += nnz(X[j]) end # construction colptr = Vector{Ti}(n+1) nzrow = Vector{Ti}(tnnz) nzval = Vector{Tv}(tnnz) roff = 1 @inbounds for j = 1:n xj = X[j] xnzind = nonzeroinds(xj) xnzval = nonzeros(xj) colptr[j] = roff copy!(nzrow, roff, xnzind) copy!(nzval, roff, xnzval) roff += length(xnzind) end colptr[n+1] = roff SparseMatrixCSC{Tv,Ti}(m, n, colptr, nzrow, nzval) end # Without the first of these methods, vertical concatenations of SparseVectors fall # back to the vertical concatenation method that ensures that combinations of # sparse/special/dense matrix/vector types concatenate to SparseMatrixCSCs, instead # of _absspvec_vcat below. The <:Integer qualifications are necessary for correct dispatch. vcat(X::SparseVector{Tv,Ti}...) where {Tv,Ti<:Integer} = _absspvec_vcat(X...) vcat(X::AbstractSparseVector{Tv,Ti}...) where {Tv,Ti<:Integer} = _absspvec_vcat(X...) function vcat(X::SparseVector...) commeltype = promote_type(map(eltype, X)...) commindtype = promote_type(map(indtype, X)...) vcat(map(x -> SparseVector{commeltype,commindtype}(x), X)...) end function _absspvec_vcat(X::AbstractSparseVector{Tv,Ti}...) where {Tv,Ti} # check sizes n = length(X) tnnz = 0 for j = 1:n tnnz += nnz(X[j]) end # construction rnzind = Vector{Ti}(tnnz) rnzval = Vector{Tv}(tnnz) ir = 0 len = 0 @inbounds for j = 1:n xj = X[j] xnzind = nonzeroinds(xj) xnzval = nonzeros(xj) xnnz = length(xnzind) for i = 1:xnnz rnzind[ir + i] = xnzind[i] + len end copy!(rnzval, ir+1, xnzval) ir += xnnz len += length(xj) end SparseVector(len, rnzind, rnzval) end hcat(Xin::Union{Vector, AbstractSparseVector}...) = hcat(map(sparse, Xin)...) vcat(Xin::Union{Vector, AbstractSparseVector}...) = vcat(map(sparse, Xin)...) # Without the following method, vertical concatenations of SparseVectors with Vectors # fall back to the vertical concatenation method that ensures that combinations of # sparse/special/dense matrix/vector types concatenate to SparseMatrixCSCs (because # the vcat method immediately above is less specific, being defined in AbstractSparseVector # rather than SparseVector). vcat(X::Union{Vector,SparseVector}...) = vcat(map(sparse, X)...) ### Concatenation of un/annotated sparse/special/dense vectors/matrices # TODO: These methods and definitions should be moved to a more appropriate location, # particularly some future equivalent of base/linalg/special.jl dedicated to interactions # between a broader set of matrix types. # TODO: A definition similar to the third exists in base/linalg/bidiag.jl. These definitions # should be consolidated in a more appropriate location, e.g. base/linalg/special.jl. const _SparseArrays = Union{SparseVector, SparseMatrixCSC, RowVector{<:Any, <:SparseVector}} const _SpecialArrays = Union{Diagonal, Bidiagonal, Tridiagonal, SymTridiagonal} const _SparseConcatArrays = Union{_SpecialArrays, _SparseArrays} const _Symmetric_SparseConcatArrays{T,A<:_SparseConcatArrays} = Symmetric{T,A} const _Hermitian_SparseConcatArrays{T,A<:_SparseConcatArrays} = Hermitian{T,A} const _Triangular_SparseConcatArrays{T,A<:_SparseConcatArrays} = Base.LinAlg.AbstractTriangular{T,A} const _Annotated_SparseConcatArrays = Union{_Triangular_SparseConcatArrays, _Symmetric_SparseConcatArrays, _Hermitian_SparseConcatArrays} const _Symmetric_DenseArrays{T,A<:Matrix} = Symmetric{T,A} const _Hermitian_DenseArrays{T,A<:Matrix} = Hermitian{T,A} const _Triangular_DenseArrays{T,A<:Matrix} = Base.LinAlg.AbstractTriangular{T,A} const _Annotated_DenseArrays = Union{_Triangular_DenseArrays, _Symmetric_DenseArrays, _Hermitian_DenseArrays} const _Annotated_Typed_DenseArrays{T} = Union{_Triangular_DenseArrays{T}, _Symmetric_DenseArrays{T}, _Hermitian_DenseArrays{T}} const _SparseConcatGroup = Union{Vector, RowVector{<:Any, <:Vector}, Matrix, _SparseConcatArrays, _Annotated_SparseConcatArrays, _Annotated_DenseArrays} const _DenseConcatGroup = Union{Vector, RowVector{<:Any, <:Vector}, Matrix, _Annotated_DenseArrays} const _TypedDenseConcatGroup{T} = Union{Vector{T}, RowVector{T,Vector{T}}, Matrix{T}, _Annotated_Typed_DenseArrays{T}} # Concatenations involving un/annotated sparse/special matrices/vectors should yield sparse arrays function cat(catdims, Xin::_SparseConcatGroup...) X = map(x -> SparseMatrixCSC(issparse(x) ? x : sparse(x)), Xin) T = promote_eltype(Xin...) Base.cat_t(catdims, T, X...) end function hcat(Xin::_SparseConcatGroup...) X = map(x -> SparseMatrixCSC(issparse(x) ? x : sparse(x)), Xin) hcat(X...) end function vcat(Xin::_SparseConcatGroup...) X = map(x -> SparseMatrixCSC(issparse(x) ? x : sparse(x)), Xin) vcat(X...) end function hvcat(rows::Tuple{Vararg{Int}}, X::_SparseConcatGroup...) nbr = length(rows) # number of block rows tmp_rows = Vector{SparseMatrixCSC}(nbr) k = 0 @inbounds for i = 1 : nbr tmp_rows[i] = hcat(X[(1 : rows[i]) + k]...) k += rows[i] end vcat(tmp_rows...) end # make sure UniformScaling objects are converted to sparse matrices for concatenation promote_to_array_type(A::Tuple{Vararg{Union{_SparseConcatGroup,UniformScaling}}}) = SparseMatrixCSC promote_to_array_type(A::Tuple{Vararg{Union{_DenseConcatGroup,UniformScaling}}}) = Matrix promote_to_arrays_(n::Int, ::Type{SparseMatrixCSC}, J::UniformScaling) = sparse(J, n, n) # Concatenations strictly involving un/annotated dense matrices/vectors should yield dense arrays cat(catdims, xs::_DenseConcatGroup...) = Base.cat_t(catdims, promote_eltype(xs...), xs...) vcat(A::Vector...) = Base.typed_vcat(promote_eltype(A...), A...) vcat(A::_DenseConcatGroup...) = Base.typed_vcat(promote_eltype(A...), A...) hcat(A::Vector...) = Base.typed_hcat(promote_eltype(A...), A...) hcat(A::_DenseConcatGroup...) = Base.typed_hcat(promote_eltype(A...), A...) hvcat(rows::Tuple{Vararg{Int}}, xs::_DenseConcatGroup...) = Base.typed_hvcat(promote_eltype(xs...), rows, xs...) # For performance, specially handle the case where the matrices/vectors have homogeneous eltype cat(catdims, xs::_TypedDenseConcatGroup{T}...) where {T} = Base.cat_t(catdims, T, xs...) vcat(A::_TypedDenseConcatGroup{T}...) where {T} = Base.typed_vcat(T, A...) hcat(A::_TypedDenseConcatGroup{T}...) where {T} = Base.typed_hcat(T, A...) hvcat(rows::Tuple{Vararg{Int}}, xs::_TypedDenseConcatGroup{T}...) where {T} = Base.typed_hvcat(T, rows, xs...) ### math functions ### Unary Map # zero-preserving functions (z->z, nz->nz) -(x::SparseVector) = SparseVector(length(x), copy(nonzeroinds(x)), -(nonzeros(x))) # functions f, such that # f(x) can be zero or non-zero when x != 0 # f(x) = 0 when x == 0 # macro unarymap_nz2z_z2z(op, TF) esc(quote function $(op)(x::AbstractSparseVector{Tv,Ti}) where Tv<:$(TF) where Ti<:Integer R = typeof($(op)(zero(Tv))) xnzind = nonzeroinds(x) xnzval = nonzeros(x) m = length(xnzind) ynzind = Vector{Ti}(m) ynzval = Vector{R}(m) ir = 0 @inbounds for j = 1:m i = xnzind[j] v = $(op)(xnzval[j]) if v != zero(v) ir += 1 ynzind[ir] = i ynzval[ir] = v end end resize!(ynzind, ir) resize!(ynzval, ir) SparseVector(length(x), ynzind, ynzval) end end) end # the rest of real, conj, imag are handled correctly via AbstractArray methods @unarymap_nz2z_z2z real Complex conj(x::SparseVector{<:Complex}) = SparseVector(length(x), copy(nonzeroinds(x)), conj(nonzeros(x))) imag(x::AbstractSparseVector{Tv,Ti}) where {Tv<:Real,Ti<:Integer} = SparseVector(length(x), Ti[], Tv[]) @unarymap_nz2z_z2z imag Complex for op in [:floor, :ceil, :trunc, :round] @eval @unarymap_nz2z_z2z $(op) Real end for op in [:log1p, :expm1, :sin, :tan, :sinpi, :sind, :tand, :asin, :atan, :asind, :atand, :sinh, :tanh, :asinh, :atanh] @eval @unarymap_nz2z_z2z $(op) Number end # function that does not preserve zeros macro unarymap_z2nz(op, TF) esc(quote function $(op)(x::AbstractSparseVector{Tv,<:Integer}) where Tv<:$(TF) v0 = $(op)(zero(Tv)) R = typeof(v0) xnzind = nonzeroinds(x) xnzval = nonzeros(x) n = length(x) m = length(xnzind) y = fill(v0, n) @inbounds for j = 1:m y[xnzind[j]] = $(op)(xnzval[j]) end y end end) end for op in [:exp, :exp2, :exp10, :log, :log2, :log10, :cos, :csc, :cot, :sec, :cospi, :cosd, :cscd, :cotd, :secd, :acos, :acot, :acosd, :acotd, :cosh, :csch, :coth, :sech, :acsch, :asech] @eval @unarymap_z2nz $(op) Number end ### Binary Map # mode: # 0: f(nz, nz) -> nz, f(z, nz) -> z, f(nz, z) -> z # 1: f(nz, nz) -> z/nz, f(z, nz) -> nz, f(nz, z) -> nz # 2: f(nz, nz) -> z/nz, f(z, nz) -> z/nz, f(nz, z) -> z/nz function _binarymap(f::Function, x::AbstractSparseVector{Tx}, y::AbstractSparseVector{Ty}, mode::Int) where {Tx,Ty} 0 <= mode <= 2 || throw(ArgumentError("Incorrect mode $mode.")) R = typeof(f(zero(Tx), zero(Ty))) n = length(x) length(y) == n || throw(DimensionMismatch()) xnzind = nonzeroinds(x) xnzval = nonzeros(x) ynzind = nonzeroinds(y) ynzval = nonzeros(y) mx = length(xnzind) my = length(ynzind) cap = (mode == 0 ? min(mx, my) : mx + my)::Int rind = Vector{Int}(cap) rval = Vector{R}(cap) ir = 0 ix = 1 iy = 1 ir = ( mode == 0 ? _binarymap_mode_0!(f, mx, my, xnzind, xnzval, ynzind, ynzval, rind, rval) : mode == 1 ? _binarymap_mode_1!(f, mx, my, xnzind, xnzval, ynzind, ynzval, rind, rval) : _binarymap_mode_2!(f, mx, my, xnzind, xnzval, ynzind, ynzval, rind, rval) )::Int resize!(rind, ir) resize!(rval, ir) return SparseVector(n, rind, rval) end function _binarymap_mode_0!(f::Function, mx::Int, my::Int, xnzind, xnzval, ynzind, ynzval, rind, rval) # f(nz, nz) -> nz, f(z, nz) -> z, f(nz, z) -> z ir = 0; ix = 1; iy = 1 @inbounds while ix <= mx && iy <= my jx = xnzind[ix] jy = ynzind[iy] if jx == jy v = f(xnzval[ix], ynzval[iy]) ir += 1; rind[ir] = jx; rval[ir] = v ix += 1; iy += 1 elseif jx < jy ix += 1 else iy += 1 end end return ir end function _binarymap_mode_1!(f::Function, mx::Int, my::Int, xnzind, xnzval::AbstractVector{Tx}, ynzind, ynzval::AbstractVector{Ty}, rind, rval) where {Tx,Ty} # f(nz, nz) -> z/nz, f(z, nz) -> nz, f(nz, z) -> nz ir = 0; ix = 1; iy = 1 @inbounds while ix <= mx && iy <= my jx = xnzind[ix] jy = ynzind[iy] if jx == jy v = f(xnzval[ix], ynzval[iy]) if v != zero(v) ir += 1; rind[ir] = jx; rval[ir] = v end ix += 1; iy += 1 elseif jx < jy v = f(xnzval[ix], zero(Ty)) ir += 1; rind[ir] = jx; rval[ir] = v ix += 1 else v = f(zero(Tx), ynzval[iy]) ir += 1; rind[ir] = jy; rval[ir] = v iy += 1 end end @inbounds while ix <= mx v = f(xnzval[ix], zero(Ty)) ir += 1; rind[ir] = xnzind[ix]; rval[ir] = v ix += 1 end @inbounds while iy <= my v = f(zero(Tx), ynzval[iy]) ir += 1; rind[ir] = ynzind[iy]; rval[ir] = v iy += 1 end return ir end function _binarymap_mode_2!(f::Function, mx::Int, my::Int, xnzind, xnzval::AbstractVector{Tx}, ynzind, ynzval::AbstractVector{Ty}, rind, rval) where {Tx,Ty} # f(nz, nz) -> z/nz, f(z, nz) -> z/nz, f(nz, z) -> z/nz ir = 0; ix = 1; iy = 1 @inbounds while ix <= mx && iy <= my jx = xnzind[ix] jy = ynzind[iy] if jx == jy v = f(xnzval[ix], ynzval[iy]) if v != zero(v) ir += 1; rind[ir] = jx; rval[ir] = v end ix += 1; iy += 1 elseif jx < jy v = f(xnzval[ix], zero(Ty)) if v != zero(v) ir += 1; rind[ir] = jx; rval[ir] = v end ix += 1 else v = f(zero(Tx), ynzval[iy]) if v != zero(v) ir += 1; rind[ir] = jy; rval[ir] = v end iy += 1 end end @inbounds while ix <= mx v = f(xnzval[ix], zero(Ty)) if v != zero(v) ir += 1; rind[ir] = xnzind[ix]; rval[ir] = v end ix += 1 end @inbounds while iy <= my v = f(zero(Tx), ynzval[iy]) if v != zero(v) ir += 1; rind[ir] = ynzind[iy]; rval[ir] = v end iy += 1 end return ir end function _binarymap(f::Function, x::AbstractVector{Tx}, y::AbstractSparseVector{Ty}, mode::Int) where {Tx,Ty} 0 <= mode <= 2 || throw(ArgumentError("Incorrect mode $mode.")) R = typeof(f(zero(Tx), zero(Ty))) n = length(x) length(y) == n || throw(DimensionMismatch()) ynzind = nonzeroinds(y) ynzval = nonzeros(y) m = length(ynzind) dst = Vector{R}(n) if mode == 0 ii = 1 @inbounds for i = 1:m j = ynzind[i] while ii < j dst[ii] = zero(R); ii += 1 end dst[j] = f(x[j], ynzval[i]); ii += 1 end @inbounds while ii <= n dst[ii] = zero(R); ii += 1 end else # mode >= 1 ii = 1 @inbounds for i = 1:m j = ynzind[i] while ii < j dst[ii] = f(x[ii], zero(Ty)); ii += 1 end dst[j] = f(x[j], ynzval[i]); ii += 1 end @inbounds while ii <= n dst[ii] = f(x[ii], zero(Ty)); ii += 1 end end return dst end function _binarymap(f::Function, x::AbstractSparseVector{Tx}, y::AbstractVector{Ty}, mode::Int) where {Tx,Ty} 0 <= mode <= 2 || throw(ArgumentError("Incorrect mode $mode.")) R = typeof(f(zero(Tx), zero(Ty))) n = length(x) length(y) == n || throw(DimensionMismatch()) xnzind = nonzeroinds(x) xnzval = nonzeros(x) m = length(xnzind) dst = Vector{R}(n) if mode == 0 ii = 1 @inbounds for i = 1:m j = xnzind[i] while ii < j dst[ii] = zero(R); ii += 1 end dst[j] = f(xnzval[i], y[j]); ii += 1 end @inbounds while ii <= n dst[ii] = zero(R); ii += 1 end else # mode >= 1 ii = 1 @inbounds for i = 1:m j = xnzind[i] while ii < j dst[ii] = f(zero(Tx), y[ii]); ii += 1 end dst[j] = f(xnzval[i], y[j]); ii += 1 end @inbounds while ii <= n dst[ii] = f(zero(Tx), y[ii]); ii += 1 end end return dst end ### Binary arithmetics: +, -, * for (vop, fun, mode) in [(:_vadd, :+, 1), (:_vsub, :-, 1), (:_vmul, :*, 0)] @eval begin $(vop)(x::AbstractSparseVector, y::AbstractSparseVector) = _binarymap($(fun), x, y, $mode) $(vop)(x::StridedVector, y::AbstractSparseVector) = _binarymap($(fun), x, y, $mode) $(vop)(x::AbstractSparseVector, y::StridedVector) = _binarymap($(fun), x, y, $mode) end end # to workaround the ambiguities with BitVector broadcast(::typeof(*), x::BitVector, y::AbstractSparseVector{Bool}) = _vmul(x, y) broadcast(::typeof(*), x::AbstractSparseVector{Bool}, y::BitVector) = _vmul(x, y) # definition of operators for (op, vop) in [(:+, :_vadd), (:-, :_vsub), (:*, :_vmul)] op != :* && @eval begin $(op)(x::AbstractSparseVector, y::AbstractSparseVector) = $(vop)(x, y) $(op)(x::StridedVector, y::AbstractSparseVector) = $(vop)(x, y) $(op)(x::AbstractSparseVector, y::StridedVector) = $(vop)(x, y) end @eval begin broadcast(::typeof($op), x::AbstractSparseVector, y::AbstractSparseVector) = $(vop)(x, y) broadcast(::typeof($op), x::StridedVector, y::AbstractSparseVector) = $(vop)(x, y) broadcast(::typeof($op), x::AbstractSparseVector, y::StridedVector) = $(vop)(x, y) end end # definition of other binary functions broadcast(::typeof(min), x::SparseVector{<:Real}, y::SparseVector{<:Real}) = _binarymap(min, x, y, 2) broadcast(::typeof(min), x::AbstractSparseVector{<:Real}, y::AbstractSparseVector{<:Real}) = _binarymap(min, x, y, 2) broadcast(::typeof(min), x::StridedVector{<:Real}, y::AbstractSparseVector{<:Real}) = _binarymap(min, x, y, 2) broadcast(::typeof(min), x::AbstractSparseVector{<:Real}, y::StridedVector{<:Real}) = _binarymap(min, x, y, 2) broadcast(::typeof(max), x::SparseVector{<:Real}, y::SparseVector{<:Real}) = _binarymap(max, x, y, 2) broadcast(::typeof(max), x::AbstractSparseVector{<:Real}, y::AbstractSparseVector{<:Real}) = _binarymap(max, x, y, 2) broadcast(::typeof(max), x::StridedVector{<:Real}, y::AbstractSparseVector{<:Real}) = _binarymap(max, x, y, 2) broadcast(::typeof(max), x::AbstractSparseVector{<:Real}, y::StridedVector{<:Real}) = _binarymap(max, x, y, 2) complex(x::AbstractSparseVector{<:Real}, y::AbstractSparseVector{<:Real}) = _binarymap(complex, x, y, 1) complex(x::StridedVector{<:Real}, y::AbstractSparseVector{<:Real}) = _binarymap(complex, x, y, 1) complex(x::AbstractSparseVector{<:Real}, y::StridedVector{<:Real}) = _binarymap(complex, x, y, 1) ### Reduction sum(x::AbstractSparseVector) = sum(nonzeros(x)) function maximum(x::AbstractSparseVector{T}) where T<:Real n = length(x) n > 0 || throw(ArgumentError("maximum over empty array is not allowed.")) m = nnz(x) (m == 0 ? zero(T) : m == n ? maximum(nonzeros(x)) : max(zero(T), maximum(nonzeros(x))))::T end function minimum(x::AbstractSparseVector{T}) where T<:Real n = length(x) n > 0 || throw(ArgumentError("minimum over empty array is not allowed.")) m = nnz(x) (m == 0 ? zero(T) : m == n ? minimum(nonzeros(x)) : min(zero(T), minimum(nonzeros(x))))::T end for f in [:sum, :maximum, :minimum], op in [:abs, :abs2] SV = :AbstractSparseVector if f == :minimum @eval ($f)(::typeof($op), x::$SV{T}) where {T<:Number} = nnz(x) < length(x) ? ($op)(zero(T)) : ($f)($op, nonzeros(x)) else @eval ($f)(::typeof($op), x::$SV) = ($f)($op, nonzeros(x)) end end vecnorm(x::AbstractSparseVector, p::Real=2) = vecnorm(nonzeros(x), p) ### linalg.jl # Transpose # (The only sparse matrix structure in base is CSC, so a one-row sparse matrix is worse than dense) @inline transpose(sv::SparseVector) = RowVector(sv) @inline ctranspose(sv::SparseVector) = RowVector(conj(sv)) ### BLAS Level-1 # axpy function LinAlg.axpy!(a::Number, x::AbstractSparseVector, y::StridedVector) length(x) == length(y) || throw(DimensionMismatch()) nzind = nonzeroinds(x) nzval = nonzeros(x) m = length(nzind) if a == oneunit(a) for i = 1:m @inbounds ii = nzind[i] @inbounds v = nzval[i] y[ii] += v end elseif a == -oneunit(a) for i = 1:m @inbounds ii = nzind[i] @inbounds v = nzval[i] y[ii] -= v end else for i = 1:m @inbounds ii = nzind[i] @inbounds v = nzval[i] y[ii] += a * v end end return y end # scale scale!(x::AbstractSparseVector, a::Real) = (scale!(nonzeros(x), a); x) scale!(x::AbstractSparseVector, a::Complex) = (scale!(nonzeros(x), a); x) scale!(a::Real, x::AbstractSparseVector) = (scale!(nonzeros(x), a); x) scale!(a::Complex, x::AbstractSparseVector) = (scale!(nonzeros(x), a); x) (*)(x::AbstractSparseVector, a::Number) = SparseVector(length(x), copy(nonzeroinds(x)), nonzeros(x) * a) (*)(a::Number, x::AbstractSparseVector) = SparseVector(length(x), copy(nonzeroinds(x)), a * nonzeros(x)) (/)(x::AbstractSparseVector, a::Number) = SparseVector(length(x), copy(nonzeroinds(x)), nonzeros(x) / a) broadcast(::typeof(*), x::AbstractSparseVector, a::Number) = x * a broadcast(::typeof(*), a::Number, x::AbstractSparseVector) = a * x broadcast(::typeof(/), x::AbstractSparseVector, a::Number) = x / a # dot function dot(x::StridedVector{Tx}, y::AbstractSparseVector{Ty}) where {Tx<:Number,Ty<:Number} n = length(x) length(y) == n || throw(DimensionMismatch()) nzind = nonzeroinds(y) nzval = nonzeros(y) s = zero(Tx) * zero(Ty) for i = 1:length(nzind) s += conj(x[nzind[i]]) * nzval[i] end return s end function dot(x::AbstractSparseVector{Tx}, y::AbstractVector{Ty}) where {Tx<:Number,Ty<:Number} n = length(y) length(x) == n || throw(DimensionMismatch()) nzind = nonzeroinds(x) nzval = nonzeros(x) s = zero(Tx) * zero(Ty) for i = 1:length(nzind) s += conj(nzval[i]) * y[nzind[i]] end return s end function _spdot(f::Function, xj::Int, xj_last::Int, xnzind, xnzval, yj::Int, yj_last::Int, ynzind, ynzval) # dot product between ranges of non-zeros, s = zero(eltype(xnzval)) * zero(eltype(ynzval)) @inbounds while xj <= xj_last && yj <= yj_last ix = xnzind[xj] iy = ynzind[yj] if ix == iy s += f(xnzval[xj], ynzval[yj]) xj += 1 yj += 1 elseif ix < iy xj += 1 else yj += 1 end end s end function dot(x::AbstractSparseVector{<:Number}, y::AbstractSparseVector{<:Number}) x === y && return sum(abs2, x) n = length(x) length(y) == n || throw(DimensionMismatch()) xnzind = nonzeroinds(x) ynzind = nonzeroinds(y) xnzval = nonzeros(x) ynzval = nonzeros(y) _spdot(dot, 1, length(xnzind), xnzind, xnzval, 1, length(ynzind), ynzind, ynzval) end ### BLAS-2 / dense A * sparse x -> dense y # A_mul_B function (*)(A::StridedMatrix{Ta}, x::AbstractSparseVector{Tx}) where {Ta,Tx} m, n = size(A) length(x) == n || throw(DimensionMismatch()) Ty = promote_type(Ta, Tx) y = Vector{Ty}(m) A_mul_B!(y, A, x) end A_mul_B!(y::StridedVector{Ty}, A::StridedMatrix, x::AbstractSparseVector{Tx}) where {Tx,Ty} = A_mul_B!(one(Tx), A, x, zero(Ty), y) function A_mul_B!(α::Number, A::StridedMatrix, x::AbstractSparseVector, β::Number, y::StridedVector) m, n = size(A) length(x) == n && length(y) == m || throw(DimensionMismatch()) m == 0 && return y if β != one(β) β == zero(β) ? fill!(y, zero(eltype(y))) : scale!(y, β) end α == zero(α) && return y xnzind = nonzeroinds(x) xnzval = nonzeros(x) @inbounds for i = 1:length(xnzind) v = xnzval[i] if v != zero(v) j = xnzind[i] αv = v * α for r = 1:m y[r] += A[r,j] * αv end end end return y end # At_mul_B function At_mul_B(A::StridedMatrix{Ta}, x::AbstractSparseVector{Tx}) where {Ta,Tx} m, n = size(A) length(x) == m || throw(DimensionMismatch()) Ty = promote_type(Ta, Tx) y = Vector{Ty}(n) At_mul_B!(y, A, x) end At_mul_B!(y::StridedVector{Ty}, A::StridedMatrix, x::AbstractSparseVector{Tx}) where {Tx,Ty} = At_mul_B!(one(Tx), A, x, zero(Ty), y) function At_mul_B!(α::Number, A::StridedMatrix, x::AbstractSparseVector, β::Number, y::StridedVector) m, n = size(A) length(x) == m && length(y) == n || throw(DimensionMismatch()) n == 0 && return y if β != one(β) β == zero(β) ? fill!(y, zero(eltype(y))) : scale!(y, β) end α == zero(α) && return y xnzind = nonzeroinds(x) xnzval = nonzeros(x) _nnz = length(xnzind) _nnz == 0 && return y s0 = zero(eltype(A)) * zero(eltype(x)) @inbounds for j = 1:n s = zero(s0) for i = 1:_nnz s += A[xnzind[i], j] * xnzval[i] end y[j] += s * α end return y end ### BLAS-2 / sparse A * sparse x -> dense y function densemv(A::SparseMatrixCSC, x::AbstractSparseVector; trans::Char='N') local xlen::Int, ylen::Int m, n = size(A) if trans == 'N' || trans == 'n' xlen = n; ylen = m elseif trans == 'T' || trans == 't' || trans == 'C' || trans == 'c' xlen = m; ylen = n else throw(ArgumentError("Invalid trans character $trans")) end xlen == length(x) || throw(DimensionMismatch()) T = promote_type(eltype(A), eltype(x)) y = Vector{T}(ylen) if trans == 'N' || trans == 'N' A_mul_B!(y, A, x) elseif trans == 'T' || trans == 't' At_mul_B!(y, A, x) elseif trans == 'C' || trans == 'c' Ac_mul_B!(y, A, x) else throw(ArgumentError("Invalid trans character $trans")) end y end # A_mul_B A_mul_B!(y::StridedVector{Ty}, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}) where {Tx,Ty} = A_mul_B!(one(Tx), A, x, zero(Ty), y) function A_mul_B!(α::Number, A::SparseMatrixCSC, x::AbstractSparseVector, β::Number, y::StridedVector) m, n = size(A) length(x) == n && length(y) == m || throw(DimensionMismatch()) m == 0 && return y if β != one(β) β == zero(β) ? fill!(y, zero(eltype(y))) : scale!(y, β) end α == zero(α) && return y xnzind = nonzeroinds(x) xnzval = nonzeros(x) Acolptr = A.colptr Arowval = A.rowval Anzval = A.nzval @inbounds for i = 1:length(xnzind) v = xnzval[i] if v != zero(v) αv = v * α j = xnzind[i] for r = A.colptr[j]:(Acolptr[j+1]-1) y[Arowval[r]] += Anzval[r] * αv end end end return y end # At_mul_B At_mul_B!(y::StridedVector{Ty}, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}) where {Tx,Ty} = At_mul_B!(one(Tx), A, x, zero(Ty), y) At_mul_B!(α::Number, A::SparseMatrixCSC, x::AbstractSparseVector, β::Number, y::StridedVector) = _At_or_Ac_mul_B!(*, α, A, x, β, y) Ac_mul_B!(y::StridedVector{Ty}, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}) where {Tx,Ty} = Ac_mul_B!(one(Tx), A, x, zero(Ty), y) Ac_mul_B!(α::Number, A::SparseMatrixCSC, x::AbstractSparseVector, β::Number, y::StridedVector) = _At_or_Ac_mul_B!(dot, α, A, x, β, y) function _At_or_Ac_mul_B!(tfun::Function, α::Number, A::SparseMatrixCSC, x::AbstractSparseVector, β::Number, y::StridedVector) m, n = size(A) length(x) == m && length(y) == n || throw(DimensionMismatch()) n == 0 && return y if β != one(β) β == zero(β) ? fill!(y, zero(eltype(y))) : scale!(y, β) end α == zero(α) && return y xnzind = nonzeroinds(x) xnzval = nonzeros(x) Acolptr = A.colptr Arowval = A.rowval Anzval = A.nzval mx = length(xnzind) for j = 1:n # s <- dot(A[:,j], x) s = _spdot(tfun, Acolptr[j], Acolptr[j+1]-1, Arowval, Anzval, 1, mx, xnzind, xnzval) @inbounds y[j] += s * α end return y end ### BLAS-2 / sparse A * sparse x -> dense y function *(A::SparseMatrixCSC, x::AbstractSparseVector) y = densemv(A, x) initcap = min(nnz(A), size(A,1)) _dense2sparsevec(y, initcap) end At_mul_B(A::SparseMatrixCSC, x::AbstractSparseVector) = _At_or_Ac_mul_B(*, A, x) Ac_mul_B(A::SparseMatrixCSC, x::AbstractSparseVector) = _At_or_Ac_mul_B(dot, A, x) function _At_or_Ac_mul_B(tfun::Function, A::SparseMatrixCSC{TvA,TiA}, x::AbstractSparseVector{TvX,TiX}) where {TvA,TiA,TvX,TiX} m, n = size(A) length(x) == m || throw(DimensionMismatch()) Tv = promote_type(TvA, TvX) Ti = promote_type(TiA, TiX) xnzind = nonzeroinds(x) xnzval = nonzeros(x) Acolptr = A.colptr Arowval = A.rowval Anzval = A.nzval mx = length(xnzind) ynzind = Vector{Ti}(n) ynzval = Vector{Tv}(n) jr = 0 for j = 1:n s = _spdot(tfun, Acolptr[j], Acolptr[j+1]-1, Arowval, Anzval, 1, mx, xnzind, xnzval) if s != zero(s) jr += 1 ynzind[jr] = j ynzval[jr] = s end end if jr < n resize!(ynzind, jr) resize!(ynzval, jr) end SparseVector(n, ynzind, ynzval) end # define matrix division operations involving triangular matrices and sparse vectors # the valid left-division operations are A[t|c]_ldiv_B[!] and \ # the valid right-division operations are A(t|c)_rdiv_B[t|c][!] # see issue #14005 for discussion of these methods for isunittri in (true, false), islowertri in (true, false) unitstr = isunittri ? "Unit" : "" halfstr = islowertri ? "Lower" : "Upper" tritype = :(Base.LinAlg.$(Symbol(unitstr, halfstr, "Triangular"))) # build out-of-place left-division operations for (istrans, func, ipfunc) in ( (false, :(\), :(A_ldiv_B!)), (true, :(At_ldiv_B), :(At_ldiv_B!)), (true, :(Ac_ldiv_B), :(Ac_ldiv_B!)) ) # broad method where elements are Numbers @eval function ($func)(A::$tritype{TA,<:AbstractMatrix}, b::SparseVector{Tb}) where {TA<:Number,Tb<:Number} TAb = $(isunittri ? :(typeof(zero(TA)*zero(Tb) + zero(TA)*zero(Tb))) : :(typeof((zero(TA)*zero(Tb) + zero(TA)*zero(Tb))/one(TA))) ) ($ipfunc)(convert(AbstractArray{TAb}, A), convert(Array{TAb}, b)) end # faster method requiring good view support of the # triangular matrix type. hence the StridedMatrix restriction. @eval function ($func)(A::$tritype{TA,<:StridedMatrix}, b::SparseVector{Tb}) where {TA<:Number,Tb<:Number} TAb = $(isunittri ? :(typeof(zero(TA)*zero(Tb) + zero(TA)*zero(Tb))) : :(typeof((zero(TA)*zero(Tb) + zero(TA)*zero(Tb))/one(TA))) ) r = convert(Array{TAb}, b) # If b has no nonzero entries, then r is necessarily zero. If b has nonzero # entries, then the operation involves only b[nzrange], so we extract and # operate on solely b[nzrange] for efficiency. if nnz(b) != 0 nzrange = $( (islowertri && !istrans) || (!islowertri && istrans) ? :(b.nzind[1]:b.n) : :(1:b.nzind[end]) ) nzrangeviewr = view(r, nzrange) nzrangeviewA = $tritype(view(A.data, nzrange, nzrange)) ($ipfunc)(convert(AbstractArray{TAb}, nzrangeviewA), nzrangeviewr) end r end # fallback where elements are not Numbers @eval ($func)(A::$tritype, b::SparseVector) = ($ipfunc)(A, copy(b)) end # build in-place left-division operations for (istrans, func) in ( (false, :(A_ldiv_B!)), (true, :(At_ldiv_B!)), (true, :(Ac_ldiv_B!)) ) # the generic in-place left-division methods handle these cases, but # we can achieve greater efficiency where the triangular matrix provides # good view support. hence the StridedMatrix restriction. @eval function ($func)(A::$tritype{<:Any,<:StridedMatrix}, b::SparseVector) # If b has no nonzero entries, the result is necessarily zero and this call # reduces to a no-op. If b has nonzero entries, then... if nnz(b) != 0 # densify the relevant part of b in one shot rather # than potentially repeatedly reallocating during the solve $( (islowertri && !istrans) || (!islowertri && istrans) ? :(_densifyfirstnztoend!(b)) : :(_densifystarttolastnz!(b)) ) # this operation involves only the densified section, so # for efficiency we extract and operate on solely that section # furthermore we operate on that section as a dense vector # such that dispatch has a chance to exploit, e.g., tuned BLAS nzrange = $( (islowertri && !istrans) || (!islowertri && istrans) ? :(b.nzind[1]:b.n) : :(1:b.nzind[end]) ) nzrangeviewbnz = view(b.nzval, nzrange - b.nzind[1] + 1) nzrangeviewA = $tritype(view(A.data, nzrange, nzrange)) ($func)(nzrangeviewA, nzrangeviewbnz) # could strip any miraculous zeros here perhaps end b end end end # helper functions for in-place matrix division operations defined above "Densifies `x::SparseVector` from its first nonzero (`x[x.nzind[1]]`) through its end (`x[x.n]`)." function _densifyfirstnztoend!(x::SparseVector) # lengthen containers oldnnz = nnz(x) newnnz = x.n - x.nzind[1] + 1 resize!(x.nzval, newnnz) resize!(x.nzind, newnnz) # redistribute nonzero values over lengthened container # initialize now-allocated zero values simultaneously nextpos = newnnz @inbounds for oldpos in oldnnz:-1:1 nzi = x.nzind[oldpos] nzv = x.nzval[oldpos] newpos = nzi - x.nzind[1] + 1 newpos < nextpos && (x.nzval[newpos+1:nextpos] = 0) newpos == oldpos && break x.nzval[newpos] = nzv nextpos = newpos - 1 end # finally update lengthened nzinds x.nzind[2:end] = (x.nzind[1]+1):x.n x end "Densifies `x::SparseVector` from its beginning (`x[1]`) through its last nonzero (`x[x.nzind[end]]`)." function _densifystarttolastnz!(x::SparseVector) # lengthen containers oldnnz = nnz(x) newnnz = x.nzind[end] resize!(x.nzval, newnnz) resize!(x.nzind, newnnz) # redistribute nonzero values over lengthened container # initialize now-allocated zero values simultaneously nextpos = newnnz @inbounds for oldpos in oldnnz:-1:1 nzi = x.nzind[oldpos] nzv = x.nzval[oldpos] nzi < nextpos && (x.nzval[nzi+1:nextpos] = 0) nzi == oldpos && (nextpos = 0; break) x.nzval[nzi] = nzv nextpos = nzi - 1 end nextpos > 0 && (x.nzval[1:nextpos] = 0) # finally update lengthened nzinds x.nzind[1:newnnz] = 1:newnnz x end #sorting function sort{Tv,Ti}(x::SparseVector{Tv,Ti}; kws...) allvals = push!(copy(nonzeros(x)),zero(Tv)) sinds = sortperm(allvals;kws...) n,k = length(x),length(allvals) z = findfirst(sinds,k) newnzind = collect(Ti,1:k-1) newnzind[z:end]+= n-k+1 newnzvals = allvals[deleteat!(sinds[1:k],z)] SparseVector(n,newnzind,newnzvals) end function fkeep!(x::SparseVector, f, trim::Bool = true) n = x.n nzind = x.nzind nzval = x.nzval x_writepos = 1 @inbounds for xk in 1:nnz(x) xi = nzind[xk] xv = nzval[xk] # If this element should be kept, rewrite in new position if f(xi, xv) if x_writepos != xk nzind[x_writepos] = xi nzval[x_writepos] = xv end x_writepos += 1 end end # Trim x's storage if necessary and desired if trim x_nnz = x_writepos - 1 if length(nzind) != x_nnz resize!(nzval, x_nnz) resize!(nzind, x_nnz) end end x end droptol!(x::SparseVector, tol, trim::Bool = true) = fkeep!(x, (i, x) -> abs(x) > tol, trim) """ dropzeros!(x::SparseVector, trim::Bool = true) Removes stored numerical zeros from `x`, optionally trimming resulting excess space from `x.nzind` and `x.nzval` when `trim` is `true`. For an out-of-place version, see [`dropzeros`](@ref). For algorithmic information, see `fkeep!`. """ dropzeros!(x::SparseVector, trim::Bool = true) = fkeep!(x, (i, x) -> x != 0, trim) """ dropzeros(x::SparseVector, trim::Bool = true) Generates a copy of `x` and removes numerical zeros from that copy, optionally trimming excess space from the result's `nzind` and `nzval` arrays when `trim` is `true`. For an in-place version and algorithmic information, see [`dropzeros!`](@ref). # Example ```jldoctest julia> A = sparsevec([1, 2, 3], [1.0, 0.0, 1.0]) 3-element SparseVector{Float64,Int64} with 3 stored entries: [1] = 1.0 [2] = 0.0 [3] = 1.0 julia> dropzeros(A) 3-element SparseVector{Float64,Int64} with 2 stored entries: [1] = 1.0 [3] = 1.0 ``` """ dropzeros(x::SparseVector, trim::Bool = true) = dropzeros!(copy(x), trim) function _fillnonzero!(arr::SparseMatrixCSC{Tv, Ti}, val) where {Tv,Ti} m, n = size(arr) resize!(arr.colptr, n+1) resize!(arr.rowval, m*n) resize!(arr.nzval, m*n) copy!(arr.colptr, 1:m:n*m+1) fill!(arr.nzval, val) index = 1 @inbounds for _ in 1:n for i in 1:m arr.rowval[index] = Ti(i) index += 1 end end arr end function _fillnonzero!(arr::SparseVector{Tv,Ti}, val) where {Tv,Ti} n = arr.n resize!(arr.nzind, n) resize!(arr.nzval, n) @inbounds for i in 1:n arr.nzind[i] = Ti(i) end fill!(arr.nzval, val) arr end import Base.fill! function fill!(A::Union{SparseVector, SparseMatrixCSC}, x) T = eltype(A) xT = convert(T, x) if xT == zero(T) fill!(A.nzval, xT) else _fillnonzero!(A, xT) end return A end