diff --git a/NEWS.md b/NEWS.md index 98b77f328924d..75a5cc9246855 100644 --- a/NEWS.md +++ b/NEWS.md @@ -37,6 +37,11 @@ Library improvements * New method for generic QR with column pivoting ([#13480]). + * A new `SparseVector` type allows for one-dimensional sparse arrays. Slicing + and reshaping sparse matrices now return vectors when appropriate. The + `sparsevec` function returns a one-dimensional sparse vector instead of a + one-column sparse matrix. + Deprecated or removed --------------------- diff --git a/base/deprecated.jl b/base/deprecated.jl index 1219c1107bffe..687ff736c9316 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -852,3 +852,5 @@ end @deprecate cor(X::AbstractMatrix; vardim=1, mean=Base.mean(X, vardim)) corm(X, mean, vardim) @deprecate cor(x::AbstractVector, y::AbstractVector; mean=(Base.mean(x), Base.mean(y))) corm(x, mean[1], y, mean[2]) @deprecate cor(X::AbstractVecOrMat, Y::AbstractVecOrMat; vardim=1, mean=(Base.mean(X, vardim), Base.mean(Y, vardim))) corm(X, mean[1], Y, mean[2], vardim) + +@deprecate_binding SparseMatrix SparseArrays diff --git a/base/docs/helpdb.jl b/base/docs/helpdb.jl index 0940f67a6d881..bac08307aab65 100644 --- a/base/docs/helpdb.jl +++ b/base/docs/helpdb.jl @@ -2387,17 +2387,6 @@ Right division operator: multiplication of `x` by the inverse of `y` on the righ """ Base.(:(/)) -doc""" - ldltfact(::Union{SparseMatrixCSC,Symmetric{Float64,SparseMatrixCSC{Flaot64,SuiteSparse_long}},Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},SuiteSparse_long}}}; shift=0, perm=Int[]) -> CHOLMOD.Factor - -Compute the `LDLt` factorization of a sparse symmetric or Hermitian matrix. A fill-reducing permutation is used. `F = ldltfact(A)` is most frequently used to solve systems of equations `A*x = b` with `F\b`, but also the methods `diag`, `det`, `logdet` are defined for `F`. You can also extract individual factors from `F`, using `F[:L]`. However, since pivoting is on by default, the factorization is internally represented as `A == P'*L*D*L'*P` with a permutation matrix `P`; using just `L` without accounting for `P` will give incorrect answers. To include the effects of permutation, it's typically preferable to extact "combined" factors like `PtL = F[:PtL]` (the equivalent of `P'*L`) and `LtP = F[:UP]` (the equivalent of `L'*P`). The complete list of supported factors is `:L, :PtL, :D, :UP, :U, :LD, :DU, :PtLD, :DUP`. - -Setting optional `shift` keyword argument computes the factorization of `A+shift*I` instead of `A`. If the `perm` argument is nonempty, it should be a permutation of `1:size(A,1)` giving the ordering to use (instead of CHOLMOD's default AMD ordering). - -The function calls the C library CHOLMOD and many other functions from the library are wrapped but not exported. -""" -ldltfact(A::SparseMatrixCSC; shift=0, perm=Int[]) - doc""" connect([host],port) -> TcpSocket @@ -7256,13 +7245,6 @@ Tests whether `A` or its elements are of type `T`. """ iseltype -doc""" - symperm(A, p) - -Return the symmetric permutation of `A`, which is `A[p,p]`. `A` should be symmetric and sparse, where only the upper triangular part of the matrix is stored. This algorithm ignores the lower triangular part of the matrix. Only the upper triangular part of the result is returned as well. -""" -symperm - doc""" min(x, y, ...) @@ -9993,15 +9975,6 @@ Like permute!, but the inverse of the given permutation is applied. """ ipermute! -doc""" -```rst -.. full(S) - -Convert a sparse matrix ``S`` into a dense matrix. -``` -""" -full(::AbstractSparseMatrix) - doc""" ```rst .. full(F) @@ -10487,13 +10460,6 @@ k]``.) """ eigfact(A,B) -doc""" - rowvals(A) - -Return a vector of the row indices of `A`, and any modifications to the returned vector will mutate `A` as well. Given the internal storage format of sparse matrices, providing access to how the row indices are stored internally can be useful in conjuction with iterating over structural nonzero values. See `nonzeros(A)` and `nzrange(A, col)`. -""" -rowvals - doc""" mkdir(path, [mode]) diff --git a/base/exports.jl b/base/exports.jl index c19f89a6c1604..e32a5ae247046 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -20,16 +20,12 @@ export BLAS, LAPACK, Serializer, - SparseMatrix, Docs, Markdown, # Types AbstractChannel, AbstractMatrix, - AbstractSparseArray, - AbstractSparseMatrix, - AbstractSparseVector, AbstractVector, AbstractVecOrMat, Array, @@ -107,7 +103,6 @@ export SharedArray, SharedMatrix, SharedVector, - SparseMatrixCSC, StatStruct, StepRange, StridedArray, @@ -562,7 +557,6 @@ export minimum, minmax, ndims, - nnz, nonzeros, nthperm!, nthperm, @@ -715,21 +709,7 @@ export ×, # sparse - etree, full, - issparse, - sparse, - sparsevec, - spdiagm, - speye, - spones, - sprand, - sprandbool, - sprandn, - spzeros, - symperm, - rowvals, - nzrange, # bitarrays bitpack, @@ -1434,4 +1414,27 @@ export @assert, @enum, @label, - @goto + @goto, + +# SparseArrays module re-exports + SparseArrays, + AbstractSparseArray, + AbstractSparseMatrix, + AbstractSparseVector, + SparseMatrixCSC, + SparseVector, + etree, + issparse, + sparse, + sparsevec, + spdiagm, + speye, + spones, + sprand, + sprandbool, + sprandn, + spzeros, + symperm, + rowvals, + nzrange, + nnz diff --git a/base/irrationals.jl b/base/irrationals.jl index 7e7d1342f4a1f..24346506fead6 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -122,10 +122,9 @@ const golden = φ for T in (Irrational, Rational, Integer, Number) ^(::Irrational{:e}, x::T) = exp(x) end -for T in (Range, BitArray, SparseMatrixCSC, StridedArray, AbstractArray) +for T in (Range, BitArray, StridedArray, AbstractArray) .^(::Irrational{:e}, x::T) = exp(x) end -^(::Irrational{:e}, x::AbstractMatrix) = expm(x) log(::Irrational{:e}) = 1 # use 1 to correctly promote expressions like log(x)/log(e) log(::Irrational{:e}, x) = log(x) diff --git a/base/precompile.jl b/base/precompile.jl index a418295527902..42c326b0a7a38 100644 --- a/base/precompile.jl +++ b/base/precompile.jl @@ -294,7 +294,6 @@ precompile(Base.next, (Dict{Symbol,Any},Int)) precompile(Base.next, (IntSet, Int)) precompile(Base.next, (UnitRange{Int},Int)) precompile(Base.nextind, (ASCIIString, Int)) -precompile(Base.nnz, (BitArray{1},)) precompile(Base.normpath, (ASCIIString, ASCIIString)) precompile(Base.normpath, (ASCIIString,)) precompile(Base.normpath, (UTF8String, UTF8String)) diff --git a/base/sparse.jl b/base/sparse.jl index 92573e83a184d..3a5485ebe1821 100644 --- a/base/sparse.jl +++ b/base/sparse.jl @@ -1,28 +1,38 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license -module SparseMatrix +module SparseArrays using Base: Func, AddFun, OrFun, ConjFun, IdFun using Base.Sort: Forward using Base.LinAlg: AbstractTriangular, PosDefException import Base: +, -, *, &, |, $, .+, .-, .*, ./, .\, .^, .<, .!=, == -import Base: A_mul_B!, Ac_mul_B, Ac_mul_B!, At_mul_B!, A_ldiv_B! -import Base: @get!, abs, abs2, broadcast, ceil, complex, cond, conj, convert, copy, - ctranspose, diagm, exp, expm1, factorize, find, findmax, findmin, findnz, float, - full, getindex, hcat, hvcat, imag, indmax, ishermitian, kron, length, log, log1p, - max, min, norm, one, promote_eltype, real, reinterpret, reshape, rot180, rotl90, - rotr90, round, scale, scale!, setindex!, similar, size, transpose, tril, triu, vcat, - vec +import Base: A_mul_B!, Ac_mul_B, Ac_mul_B!, At_mul_B, At_mul_B!, A_ldiv_B! + +import Base: @get!, acos, acosd, acot, acotd, acsch, asech, asin, asind, asinh, + atan, atand, atanh, broadcast!, chol, conj!, cos, cosc, cosd, cosh, cospi, cot, + cotd, coth, countnz, csc, cscd, csch, ctranspose!, diag, diff, done, dot, eig, + exp10, exp2, eye, findn, floor, hash, indmin, inv, issym, istril, istriu, log10, + log2, lu, maxabs, minabs, next, sec, secd, sech, show, showarray, sin, sinc, + sind, sinh, sinpi, squeeze, start, sum, sumabs, sumabs2, summary, tan, tand, + tanh, trace, transpose!, tril!, triu!, trunc, vecnorm, writemime, abs, abs2, + broadcast, call, ceil, complex, cond, conj, convert, copy, ctranspose, diagm, + exp, expm1, factorize, find, findmax, findmin, findnz, float, full, getindex, + hcat, hvcat, imag, indmax, ishermitian, kron, length, log, log1p, max, min, + maximum, minimum, norm, one, promote_eltype, real, reinterpret, reshape, rot180, + rotl90, rotr90, round, scale, scale!, setindex!, similar, size, transpose, tril, + triu, vcat, vec + import Base.Broadcast: eltype_plus, broadcast_shape -export AbstractSparseArray, AbstractSparseMatrix, AbstractSparseVector, SparseMatrixCSC, - blkdiag, dense, droptol!, dropzeros!, etree, issparse, nnz, nonzeros, nzrange, - rowvals, sparse, sparsevec, spdiagm, speye, spones, sprand, sprandbool, sprandn, - spzeros, symperm +export AbstractSparseArray, AbstractSparseMatrix, AbstractSparseVector, + SparseMatrixCSC, SparseVector, blkdiag, dense, droptol!, dropzeros!, etree, + issparse, nonzeros, nzrange, rowvals, sparse, sparsevec, spdiagm, speye, spones, + sprand, sprandbool, sprandn, spzeros, symperm, nnz include("sparse/abstractsparse.jl") include("sparse/sparsematrix.jl") +include("sparse/sparsevector.jl") include("sparse/csparse.jl") include("sparse/linalg.jl") @@ -32,4 +42,4 @@ if Base.USE_GPL_LIBS include("sparse/spqr.jl") end -end # module SparseMatrix +end diff --git a/base/sparse/cholmod.jl b/base/sparse/cholmod.jl index ec7e980a7ed49..cb084c4910d5d 100644 --- a/base/sparse/cholmod.jl +++ b/base/sparse/cholmod.jl @@ -9,14 +9,14 @@ import Base.LinAlg: (\), A_mul_Bc, A_mul_Bt, Ac_ldiv_B, Ac_mul_B, At_ldiv_B, At_ cholfact, det, diag, ishermitian, isposdef, issym, ldltfact, logdet -import Base.SparseMatrix: sparse, nnz +importall ..SparseArrays export Dense, Factor, Sparse -using Base.SparseMatrix: AbstractSparseMatrix, SparseMatrixCSC, increment, indtype +import ..SparseArrays: AbstractSparseMatrix, SparseMatrixCSC, increment, indtype ######### # Setup # @@ -853,6 +853,9 @@ function convert{Tv<:VTypes}(::Type{Sparse}, A::SparseMatrixCSC{Tv,SuiteSparse_l return o end + +# convert SparseVectors into CHOLMOD Sparse types through a mx1 CSC matrix +convert{Tv<:VTypes}(::Type{Sparse}, A::SparseVector{Tv,SuiteSparse_long}) = convert(Sparse, convert(SparseMatrixCSC, A)) function convert{Tv<:VTypes}(::Type{Sparse}, A::SparseMatrixCSC{Tv,SuiteSparse_long}) o = Sparse(A, 0) # check if array is symmetric and change stype if it is @@ -994,7 +997,7 @@ function sparse(F::Factor) L, d = getLd!(LD) A = scale(L, d)*L' end - SparseMatrix.sortSparseMatrixCSC!(A) + SparseArrays.sortSparseMatrixCSC!(A) p = get_perm(F) if p != [1:s.n;] pinv = Array(Int, length(p)) @@ -1216,6 +1219,32 @@ function cholfact(A::Sparse; kws...) return F end +doc""" + ldltfact(::Union{SparseMatrixCSC,Symmetric{Float64,SparseMatrixCSC{Flaot64,SuiteSparse_long}},Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},SuiteSparse_long}}}; shift=0, perm=Int[]) -> CHOLMOD.Factor + +Compute the `LDLt` factorization of a sparse symmetric or Hermitian matrix. A +fill-reducing permutation is used. `F = ldltfact(A)` is most frequently used to +solve systems of equations `A*x = b` with `F\b`. The returned factorization +object `F` also supports the methods `diag`, `det`, and `logdet`. You can +extract individual factors from `F` using `F[:L]`. However, since pivoting is +on by default, the factorization is internally represented as `A == P'*L*D*L'*P` +with a permutation matrix `P`; using just `L` without accounting for `P` will +give incorrect answers. To include the effects of permutation, it's typically +preferable to extact "combined" factors like `PtL = F[:PtL]` (the equivalent of +`P'*L`) and `LtP = F[:UP]` (the equivalent of `L'*P`). The complete list of +supported factors is `:L, :PtL, :D, :UP, :U, :LD, :DU, :PtLD, :DUP`. + +Setting optional `shift` keyword argument computes the factorization of +`A+shift*I` instead of `A`. If the `perm` argument is nonempty, it should be a +permutation of `1:size(A,1)` giving the ordering to use (instead of CHOLMOD's +default AMD ordering). + +The function calls the C library CHOLMOD and many other functions from the +library are wrapped but not exported. + +""" +ldltfact(A::SparseMatrixCSC; shift=0, perm=Int[]) + function ldltfact(A::Sparse; kws...) cm = defaults(common()) # setting the common struct to default values. Should only be done when creating new factorization. set_print_level(cm, 0) # no printing from CHOLMOD by default @@ -1294,13 +1323,15 @@ for (T, f) in ((:Dense, :solve), (:Sparse, :spsolve)) end end +typealias SparseVecOrMat{Tv,Ti} Union{SparseVector{Tv,Ti}, SparseMatrixCSC{Tv,Ti}} + function (\)(L::FactorComponent, b::Vector) reshape(convert(Matrix, L\Dense(b)), length(b)) end function (\)(L::FactorComponent, B::Matrix) convert(Matrix, L\Dense(B)) end -function (\)(L::FactorComponent, B::SparseMatrixCSC) +function (\)(L::FactorComponent, B::SparseVecOrMat) sparse(L\Sparse(B,0)) end @@ -1311,12 +1342,12 @@ Ac_ldiv_B(L::FactorComponent, B) = ctranspose(L)\B (\)(L::Factor, B::Matrix) = convert(Matrix, solve(CHOLMOD_A, L, Dense(B))) (\)(L::Factor, B::Sparse) = spsolve(CHOLMOD_A, L, B) # When right hand side is sparse, we have to ensure that the rhs is not marked as symmetric. -(\)(L::Factor, B::SparseMatrixCSC) = sparse(spsolve(CHOLMOD_A, L, Sparse(B, 0))) +(\)(L::Factor, B::SparseVecOrMat) = sparse(spsolve(CHOLMOD_A, L, Sparse(B, 0))) Ac_ldiv_B(L::Factor, B::Dense) = solve(CHOLMOD_A, L, B) Ac_ldiv_B(L::Factor, B::VecOrMat) = convert(Matrix, solve(CHOLMOD_A, L, Dense(B))) Ac_ldiv_B(L::Factor, B::Sparse) = spsolve(CHOLMOD_A, L, B) -Ac_ldiv_B(L::Factor, B::SparseMatrixCSC) = Ac_ldiv_B(L, Sparse(B)) +Ac_ldiv_B(L::Factor, B::SparseVecOrMat) = Ac_ldiv_B(L, Sparse(B)) ## Other convenience methods function diag{Tv}(F::Factor{Tv}) @@ -1398,7 +1429,7 @@ function ishermitian(A::Sparse{Complex{Float64}}) end end -(*){Ti}(A::Symmetric{Float64,SparseMatrixCSC{Float64,Ti}}, B::SparseMatrixCSC{Float64,Ti}) = sparse(Sparse(A)*Sparse(B)) -(*){Ti}(A::Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},Ti}}, B::SparseMatrixCSC{Complex{Float64},Ti}) = sparse(Sparse(A)*Sparse(B)) +(*){Ti}(A::Symmetric{Float64,SparseMatrixCSC{Float64,Ti}}, B::SparseVecOrMat{Float64,Ti}) = sparse(Sparse(A)*Sparse(B)) +(*){Ti}(A::Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},Ti}}, B::SparseVecOrMat{Complex{Float64},Ti}) = sparse(Sparse(A)*Sparse(B)) end #module diff --git a/base/sparse/csparse.jl b/base/sparse/csparse.jl index 695ad08236213..16977b6d19056 100644 --- a/base/sparse/csparse.jl +++ b/base/sparse/csparse.jl @@ -313,8 +313,17 @@ function csc_permute{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, pinv::Vector{Ti}, q::Vect (C.').' # double transpose to order the columns end + # based on cs_symperm p. 21, "Direct Methods for Sparse Linear Systems" # form A[p,p] for a symmetric A stored in the upper triangle +doc""" + symperm(A, p) + +Return the symmetric permutation of `A`, which is `A[p,p]`. `A` should be +symmetric, sparse, and only contain nonzeros in the upper triangular part of the +matrix is stored. This algorithm ignores the lower triangular part of the +matrix. Only the upper triangular part of the result is returned. +""" function symperm{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, pinv::Vector{Ti}) m, n = size(A) if m != n diff --git a/base/sparse/linalg.jl b/base/sparse/linalg.jl index ffad8c5f4b51a..9475575073df7 100644 --- a/base/sparse/linalg.jl +++ b/base/sparse/linalg.jl @@ -160,7 +160,7 @@ function spmatmul{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, B::SparseMatrixCSC{Tv,Ti}; # The Gustavson algorithm does not guarantee the product to have sorted row indices. Cunsorted = SparseMatrixCSC(mA, nB, colptrC, rowvalC, nzvalC) - C = Base.SparseMatrix.sortSparseMatrixCSC!(Cunsorted, sortindices=sortindices) + C = SparseArrays.sortSparseMatrixCSC!(Cunsorted, sortindices=sortindices) return C end @@ -752,7 +752,7 @@ inv(A::SparseMatrixCSC) = error("The inverse of a sparse matrix can often be den ## scale methods -# Copy colptr and rowval from one SparseMatrix to another +# Copy colptr and rowval from one sparse matrix to another function copyinds!(C::SparseMatrixCSC, A::SparseMatrixCSC) if C.colptr !== A.colptr resize!(C.colptr, length(A.colptr)) diff --git a/base/sparse/sparsematrix.jl b/base/sparse/sparsematrix.jl index c29100678abd3..027012dea94e2 100644 --- a/base/sparse/sparsematrix.jl +++ b/base/sparse/sparsematrix.jl @@ -29,7 +29,7 @@ size(S::SparseMatrixCSC) = (S.m, S.n) """ nnz(A) -Returns the number of stored (filled) elements in a sparse matrix. +Returns the number of stored (filled) elements in a sparse array. """ nnz(S::SparseMatrixCSC) = Int(S.colptr[end]-1) countnz(S::SparseMatrixCSC) = countnz(S.nzval) @@ -37,17 +37,26 @@ countnz(S::SparseMatrixCSC) = countnz(S.nzval) """ nonzeros(A) -Return a vector of the structural nonzero values in sparse matrix `A`. This -includes zeros that are explicitly stored in the sparse matrix. The returned +Return a vector of the structural nonzero values in sparse array `A`. This +includes zeros that are explicitly stored in the sparse array. The returned vector points directly to the internal nonzero storage of `A`, and any modifications to the returned vector will mutate `A` as well. See `rowvals(A)` and `nzrange(A, col)`. """ nonzeros(S::SparseMatrixCSC) = S.nzval + +""" + rowvals(A::SparseMatrixCSC) + +Return a vector of the row indices of `A`. Any modifications to the returned +vector will mutate `A` as well. Providing access to how the row indices are +stored internally can be useful in conjuction with iterating over structural +nonzero values. See also `nonzeros(A)` and `nzrange(A, col)`. +""" rowvals(S::SparseMatrixCSC) = S.rowval """ - nzrange(A, col) + nzrange(A::SparseMatrixCSC, col) Return the range of indices to the structural nonzero values of a sparse matrix column. In conjunction with `nonzeros(A)` and `rowvals(A)`, this allows for @@ -221,6 +230,14 @@ end convert{T}(::Type{AbstractMatrix{T}}, A::SparseMatrixCSC) = convert(SparseMatrixCSC{T}, A) convert(::Type{Matrix}, S::SparseMatrixCSC) = full(S) + +""" + full(S) + +Convert a sparse matrix or vector `S` into a dense matrix or vector. +""" +full + function full{Tv}(S::SparseMatrixCSC{Tv}) # Handle cases where zero(Tv) is not defined but the array is dense. # (Should we really worry about this?) @@ -239,78 +256,8 @@ complex(A::SparseMatrixCSC, B::SparseMatrixCSC) = A + im*B # Construct a sparse vector -function vec{Tv,Ti}(S::SparseMatrixCSC{Tv,Ti}) - colptr = Array(Ti,2) - rowval = similar(S.rowval) - lS = length(S) - sparse_compute_reshaped_colptr_and_rowval(colptr, rowval, lS, 1, S.colptr, S.rowval, S.m, S.n) - SparseMatrixCSC(lS, 1, colptr, rowval, copy(S.nzval)) -end - -sparsevec(A::AbstractMatrix) = reshape(sparse(A), (length(A),1)) -sparsevec(S::SparseMatrixCSC) = vec(S) - -""" - sparsevec(D::Dict, [m]) - -Create a sparse matrix of size `m x 1` where the row values are keys from -the dictionary, and the nonzero values are the values from the dictionary. -""" -sparsevec{K<:Integer,V}(d::Dict{K,V}, len::Int) = sparsevec(collect(keys(d)), collect(values(d)), len) - -sparsevec{K<:Integer,V}(d::Dict{K,V}) = sparsevec(collect(keys(d)), collect(values(d))) - -sparsevec(I::AbstractVector, V, m::Integer) = sparsevec(I, V, m, AddFun()) - -sparsevec(I::AbstractVector, V) = sparsevec(I, V, maximum(I), AddFun()) - -""" - sparsevec(I, V, [m, combine]) - -Create a sparse matrix `S` of size `m x 1` such that `S[I[k]] = V[k]`. -Duplicates are combined using the `combine` function, which defaults to -`+` if it is not provided. In julia, sparse vectors are really just sparse -matrices with one column. Given Julia's Compressed Sparse Columns (CSC) -storage format, a sparse column matrix with one column is sparse, whereas -a sparse row matrix with one row ends up being dense. -""" -function sparsevec(I::AbstractVector, V, m::Integer, combine::Union{Function,Base.Func}) - nI = length(I) - if isa(V, Number) - V = fill(V, nI) - end - if nI != length(V) - throw(ArgumentError("index and value vectors must be the same length")) - end - p = sortperm(I) - @inbounds I = I[p] - if nI > 0 - if I[1] <= 0 - throw(ArgumentError("I index values must be ≥ 0")) - end - if I[end] > m - throw(ArgumentError("all I index values must be ≤ length(sparsevec)")) - end - end - V = V[p] - sparse_IJ_sorted!(I, ones(eltype(I), nI), V, m, 1, combine) -end - -""" - sparsevec(A) - -Convert a dense vector `A` into a sparse matrix of size `m x 1`. In julia, -sparse vectors are really just sparse matrices with one column. -""" -function sparsevec(a::Vector) - n = length(a) - I = find(a) - J = ones(Int, n) - V = a[I] - return sparse_IJ_sorted!(I,J,V,n,1,AddFun()) -end - -sparse(a::Vector) = sparsevec(a) +# Note that unlike `vec` for arrays, this does not share data +vec(S::SparseMatrixCSC) = S[:] """ sparse(A) @@ -500,14 +447,15 @@ end """ ```rst -.. sprand([rng,] m,n,p [,rfn]) - -Create a random ``m`` by ``n`` sparse matrix, in which the probability of any -element being nonzero is independently given by ``p`` (and hence the mean -density of nonzeros is also exactly ``p``). Nonzero values are sampled from -the distribution specified by ``rfn``. The uniform distribution is used in -case ``rfn`` is not specified. The optional ``rng`` argument specifies a -random number generator, see :ref:`Random Numbers `. +.. sprand([rng],m,[n],p::AbstractFloat,[rfn]) + +Create a random length ``m`` sparse vector or ``m`` by ``n`` sparse matrix, in +which the probability of any element being nonzero is independently given by +``p`` (and hence the mean density of nonzeros is also exactly ``p``). Nonzero +values are sampled from the distribution specified by ``rfn``. The uniform +distribution is used in case ``rfn`` is not specified. The optional ``rng`` +argument specifies a random number generator, see :ref:`Random Numbers +`. ``` """ function sprand{T}(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, @@ -535,11 +483,11 @@ sprand(m::Integer, n::Integer, density::AbstractFloat) = sprand(GLOBAL_RNG,m,n,d sprandn(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat) = sprand(r,m,n,density,randn,Float64) """ - sprandn(m,n,p) + sprandn(m[,n],p::AbstractFloat) -Create a random `m` by `n` sparse matrix with the specified (independent) -probability `p` of any entry being nonzero, where nonzero values are -sampled from the normal distribution. +Create a random sparse vector of length `m` or sparse matrix of size `m` by `n` +with the specified (independent) probability `p` of any entry being nonzero, +where nonzero values are sampled from the normal distribution. """ sprandn( m::Integer, n::Integer, density::AbstractFloat) = sprandn(GLOBAL_RNG,m,n,density) @@ -547,27 +495,29 @@ truebools(r::AbstractRNG, n::Integer) = ones(Bool, n) sprandbool(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat) = sprand(r,m,n,density,truebools,Bool) """ - sprandbool(m,n,p) + sprandbool(m[,n],p) -Create a random `m` by `n` sparse boolean matrix with the specified -(independent) probability `p` of any entry being `true`. +Create a random `m` by `n` sparse boolean matrix or length `m` sparse boolean +vector with the specified (independent) probability `p` of any entry being +`true`. """ sprandbool(m::Integer, n::Integer, density::AbstractFloat) = sprandbool(GLOBAL_RNG,m,n,density) """ spones(S) -Create a sparse matrix with the same structure as that of `S`, but with every nonzero +Create a sparse array with the same structure as that of `S`, but with every nonzero element having the value `1.0`. """ spones{T}(S::SparseMatrixCSC{T}) = SparseMatrixCSC(S.m, S.n, copy(S.colptr), copy(S.rowval), ones(T, S.colptr[end]-1)) """ - spzeros(m,n) + spzeros(m[,n]) -Create a sparse matrix of size `m x n`. This sparse matrix will not contain any -nonzero values. No storage will be allocated for nonzero values during construction. +Create a sparse vector of length `m` or sparse matrix of size `m x n`. This +sparse array will not contain any nonzero values. No storage will be allocated +for nonzero values during construction. """ spzeros(m::Integer, n::Integer) = spzeros(Float64, m, n) spzeros(Tv::Type, m::Integer, n::Integer) = spzeros(Tv, Int, m, n) @@ -1041,6 +991,7 @@ end # macro (.^)(A::SparseMatrixCSC, B::Number) = B==0 ? sparse(ones(typeof(one(eltype(A)).^B), A.m, A.n)) : SparseMatrixCSC(A.m, A.n, copy(A.colptr), copy(A.rowval), A.nzval .^ B) +(.^)(::Irrational{:e}, B::SparseMatrixCSC) = exp(B) (.^)(A::Number, B::SparseMatrixCSC) = (.^)(A, full(B)) (.^)(A::SparseMatrixCSC, B::Array) = (.^)(full(A), B) (.^)(A::Array, B::SparseMatrixCSC) = (.^)(A, full(B)) @@ -1400,13 +1351,10 @@ function getindex{T}(A::SparseMatrixCSC{T}, i0::Integer, i1::Integer) ((r1 > r2) || (A.rowval[r1] != i0)) ? zero(T) : A.nzval[r1] end -getindex{T<:Integer}(A::SparseMatrixCSC, I::AbstractVector{T}, j::Integer) = getindex(A,I,[j]) getindex{T<:Integer}(A::SparseMatrixCSC, i::Integer, J::AbstractVector{T}) = getindex(A,[i],J) -# Colon translation (this could be done more efficiently) -getindex(A::SparseMatrixCSC, ::Colon) = getindex(A, 1:length(A)) -getindex(A::SparseMatrixCSC, ::Colon, ::Colon) = getindex(A, 1:size(A, 1), 1:size(A, 2)) -getindex(A::SparseMatrixCSC, ::Colon, j) = getindex(A, 1:size(A, 1), j) +# Colon translation +getindex(A::SparseMatrixCSC, ::Colon, ::Colon) = copy(A) getindex(A::SparseMatrixCSC, i, ::Colon) = getindex(A, i, 1:size(A, 2)) function getindex_cols{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, J::AbstractVector) @@ -1730,8 +1678,8 @@ end function getindex_general{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, J::AbstractVector) pI = sortperm(I) - @inbounds I = I[pI] - permute_rows!(getindex_I_sorted(A, I, J), pI) + @inbounds Is = I[pI] + permute_rows!(getindex_I_sorted(A, Is, J), pI) end # the general case: @@ -1812,46 +1760,6 @@ getindex(A::SparseMatrixCSC, I::AbstractVector{Bool}, J::AbstractVector{Bool}) = getindex{T<:Integer}(A::SparseMatrixCSC, I::AbstractVector{T}, J::AbstractVector{Bool}) = A[I,find(J)] getindex{T<:Integer}(A::SparseMatrixCSC, I::AbstractVector{Bool}, J::AbstractVector{T}) = A[find(I),J] -function getindex{Tv}(A::SparseMatrixCSC{Tv}, I::AbstractArray{Bool}) - checkbounds(A, I) - n = sum(I) - - colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval - colptrB = Int[1,n+1] - rowvalB = Array(Int, n) - nzvalB = Array(Tv, n) - 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 - colptrB[end] = c - n = length(nzvalB) - if n > (c-1) - deleteat!(nzvalB, c:n) - deleteat!(rowvalB, c:n) - end - SparseMatrixCSC(n, 1, colptrB, rowvalB, nzvalB) -end - ## setindex! function setindex!{T,Ti}(A::SparseMatrixCSC{T,Ti}, v, i0::Integer, i1::Integer) i0 = convert(Ti, i0) diff --git a/base/sparse/sparsevector.jl b/base/sparse/sparsevector.jl new file mode 100644 index 0000000000000..43ea1dbf5cbc5 --- /dev/null +++ b/base/sparse/sparsevector.jl @@ -0,0 +1,1420 @@ +# This file is a part of Julia. License is MIT: http://julialang.org/license + +### Common definitions + +import Base: Func, AddFun, MulFun, MaxFun, MinFun, SubFun + +immutable ComplexFun <: Func{2} end +call(::ComplexFun, x::Real, y::Real) = complex(x, y) + +immutable DotFun <: Func{2} end +call(::DotFun, x::Number, y::Number) = conj(x) * y + +typealias UnaryOp Union{Function, Func{1}} +typealias BinaryOp Union{Function, Func{2}} + +### The SparseVector + +### Types + +immutable SparseVector{Tv,Ti<:Integer} <: AbstractSparseVector{Tv,Ti} + n::Int # the number of elements + nzind::Vector{Ti} # the indices of nonzeros + nzval::Vector{Tv} # the values of nonzeros + + function SparseVector(n::Integer, nzind::Vector{Ti}, nzval::Vector{Tv}) + 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{Tv,Ti}(n::Integer, nzind::Vector{Ti}, nzval::Vector{Tv}) = + 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) +nonzeros(x::SparseVector) = x.nzval +nonzeroinds(x::SparseVector) = x.nzind + +similar{T}(x::SparseVector, ::Type{T}, D::Dims) = spzeros(T, D...) + +### Construct empty sparse vector + +spzeros(len::Integer) = spzeros(Float64, len) +spzeros{T}(::Type{T}, len::Integer) = SparseVector(len, Int[], T[]) + +# Construction of same structure, but with all ones +spones{T}(x::SparseVector{T}) = SparseVector(x.n, copy(x.nzind), ones(T, length(x.nzval))) + +### Construction from lists of indices and values + +function _sparsevector!{Ti<:Integer}(I::Vector{Ti}, 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!{Tv,Ti<:Integer}(I::Vector{Ti}, V::Vector{Tv}, len::Integer, combine::BinaryOp) + if !isempty(I) + p = sortperm(I) + permute!(I, p) + permute!(V, p) + m = length(I) + + # advances to the first non-zero element + r = 1 # index of last processed entry + while r <= m + if V[r] == 0 + r += 1 + else + break + end + end + r > m && return SparseVector(len, Ti[], Tv[]) + + # move r-th to l-th + l = 1 # length of processed part + i = I[r] # row-index of current element + if r > 1 + I[l] = i; V[l] = V[r] + end + + # 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] + if pv != 0 + l += 1 + end + i = i2 + if l < r + I[l] = i; V[l] = V[r] + end + end + end + if V[l] == 0 + l -= 1 + 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 it is not provided. +""" +function sparsevec{Tv,Ti<:Integer}(I::AbstractVector{Ti}, V::AbstractVector{Tv}, combine::BinaryOp) + 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(Ti, I), collect(Tv, V), len, combine) +end + +function sparsevec{Tv,Ti<:Integer}(I::AbstractVector{Ti}, V::AbstractVector{Tv}, len::Integer, combine::BinaryOp) + length(I) == length(V) || + throw(ArgumentError("index and value vectors must be the same length")) + maxi = convert(Ti, len) + for i in I + 1 <= i <= maxi || throw(ArgumentError("An index is out of bound.")) + end + _sparsevector!(collect(Ti, I), collect(Tv, V), len, combine) +end + +sparsevec{Ti<:Integer}(I::AbstractVector{Ti}, V::AbstractVector) = + sparsevec(I, V, AddFun()) + +sparsevec{Ti<:Integer}(I::AbstractVector{Ti}, V::AbstractVector, len::Integer) = + sparsevec(I, V, len, AddFun()) + +sparsevec{Ti<:Integer}(I::AbstractVector{Ti}, v::Number, combine::BinaryOp) = + sparsevec(I, fill(v, length(I)), combine) + +sparsevec{Ti<:Integer}(I::AbstractVector{Ti}, v::Number, len::Integer, combine::BinaryOp) = + sparsevec(I, fill(v, length(I)), len, combine) + +sparsevec{Ti<:Integer}(I::AbstractVector{Ti}, v::Number) = + sparsevec(I, v, AddFun()) + +sparsevec{Ti<:Integer}(I::AbstractVector{Ti}, v::Number, len::Integer) = + sparsevec(I, v, len, AddFun()) + + +### 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. +""" +function sparsevec{Tv,Ti<:Integer}(dict::Associative{Ti,Tv}) + m = length(dict) + nzind = Array(Ti, m) + nzval = Array(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 + if v != 0 + cnt += 1 + @inbounds nzind[cnt] = k + @inbounds nzval[cnt] = v + end + end + resize!(nzind, cnt) + resize!(nzval, cnt) + _sparsevector!(nzind, nzval, len) +end + +function sparsevec{Tv,Ti<:Integer}(dict::Associative{Ti,Tv}, len::Integer) + m = length(dict) + nzind = Array(Ti, m) + nzval = Array(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.")) + if v != 0 + cnt += 1 + @inbounds nzind[cnt] = k + @inbounds nzval[cnt] = v + end + end + resize!(nzind, cnt) + resize!(nzval, cnt) + _sparsevector!(nzind, nzval, len) +end + + +### Element access + +function setindex!{Tv,Ti<:Integer}(x::SparseVector{Tv,Ti}, v::Tv, i::Ti) + 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 + if v == 0 + deleteat!(nzind, k) + deleteat!(nzval, k) + else + nzval[k] = v + end + else # i not found + if v != 0 + insert!(nzind, k, i) + insert!(nzval, k, v) + end + end + x +end + +setindex!{Tv, Ti<:Integer}(x::SparseVector{Tv,Ti}, v, i::Integer) = + setindex!(x, convert(Tv, v), convert(Ti, i)) + + +### Conversion + +# convert SparseMatrixCSC to SparseVector +function convert{Tv,Ti<:Integer}(::Type{SparseVector{Tv,Ti}}, s::SparseMatrixCSC{Tv,Ti}) + size(s, 2) == 1 || throw(ArgumentError("The input argument must have a single-column.")) + SparseVector(s.m, s.rowval, s.nzval) +end + +convert{Tv,Ti}(::Type{SparseVector{Tv}}, s::SparseMatrixCSC{Tv,Ti}) = + convert(SparseVector{Tv,Ti}, s) + +convert{Tv,Ti}(::Type{SparseVector}, s::SparseMatrixCSC{Tv,Ti}) = + convert(SparseVector{Tv,Ti}, s) + +# convert Vector to SparseVector + +""" + sparsevec(A) + +Convert a vector `A` into a sparse vector of length `m`. +""" +sparsevec{T}(a::AbstractVector{T}) = convert(SparseVector{T, Int}, a) +sparsevec(a::AbstractArray) = sparsevec(vec(a)) +sparsevec(a::AbstractSparseArray) = vec(a) +sparse(a::AbstractVector) = sparsevec(a) + +function _dense2sparsevec{Tv,Ti}(s::AbstractArray{Tv}, initcap::Ti) + # pre-condition: initcap > 0; the initcap determines the index type + n = length(s) + cap = initcap + nzind = Array(Ti, cap) + nzval = Array(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{Tv,Ti}(::Type{SparseVector{Tv,Ti}}, s::AbstractVector{Tv}) = + _dense2sparsevec(s, convert(Ti, max(8, div(length(s), 8)))) + +convert{Tv}(::Type{SparseVector{Tv}}, s::AbstractVector{Tv}) = + convert(SparseVector{Tv,Int}, s) + +convert{Tv}(::Type{SparseVector}, s::AbstractVector{Tv}) = + convert(SparseVector{Tv,Int}, s) + + +# convert between different types of SparseVector +convert{Tv,Ti,TvS,TiS}(::Type{SparseVector{Tv,Ti}}, s::SparseVector{TvS,TiS}) = + SparseVector{Tv,Ti}(s.n, convert(Vector{Ti}, s.nzind), convert(Vector{Tv}, s.nzval)) + +convert{Tv,TvS,TiS}(::Type{SparseVector{Tv}}, s::SparseVector{TvS,TiS}) = + SparseVector{Tv,TiS}(s.n, s.nzind, convert(Vector{Tv}, s.nzval)) + + +### Rand Construction +sprand{T}(n::Integer, p::AbstractFloat, rfn::Function, ::Type{T}) = sprand(GLOBAL_RNG, n, p, rfn, T) +function sprand{T}(r::AbstractRNG, n::Integer, p::AbstractFloat, rfn::Function, ::Type{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{T}(n::Integer, p::AbstractFloat, ::Type{T}) = sprand(GLOBAL_RNG, n, p, rand, T) +sprand(n::Integer, p::AbstractFloat) = sprand(GLOBAL_RNG, n, p, rand) +sprand{T}(r::AbstractRNG, n::Integer, p::AbstractFloat, ::Type{T}) = sprand(r, n, p, rand, T) +sprand(r::AbstractRNG, n::Integer, p::AbstractFloat) = sprand(r, n, p, rand) + +sprandn(n::Integer, p::AbstractFloat) = sprand(GLOBAL_RNG, n, p, randn) +sprandn(r::AbstractRNG, n::Integer, p::AbstractFloat) = sprand(r, n, p, randn) + +sprandbool(n::Integer, p::AbstractFloat) = sprand(GLOBAL_RNG, n, p, truebools) +sprandbool(r::AbstractRNG, n::Integer, p::AbstractFloat) = sprand(r, n, p, truebools) + +## Indexing into Matrices can return SparseVectors + +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::UnitRange, 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[r1:r2] - first(I) + 1, x.nzval[r1:r2]) +end + +# In the general case, we piggy back upon SparseMatrixCSC's optimized solution +@inline function getindex{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, J::Integer) + M = A[I, [J]] + SparseVector(M.m, M.rowval, M.nzval) +end + + +# Logical and linear indexing into SparseMatrices +getindex{Tv}(A::SparseMatrixCSC{Tv}, I::AbstractVector{Bool}) = _logical_index(A, I) # Ambiguities +getindex{Tv}(A::SparseMatrixCSC{Tv}, I::AbstractArray{Bool}) = _logical_index(A, I) +function _logical_index{Tv}(A::SparseMatrixCSC{Tv}, I::AbstractArray{Bool}) + checkbounds(A, I) + n = sum(I) + + colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval + rowvalB = Array(Int, n) + nzvalB = Array(Tv, n) + 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 + n = length(nzvalB) + if n > (c-1) + deleteat!(nzvalB, c:n) + deleteat!(rowvalB, c:n) + end + SparseVector(n, rowvalB, nzvalB) +end + +# TODO: huge optimizations are available for I::Range and ::Colon +getindex(A::SparseMatrixCSC, ::Colon) = A[1:end] +function getindex{Tv}(A::SparseMatrixCSC{Tv}, I::AbstractVector) + szA = size(A) + nA = szA[1]*szA[2] + colptrA = A.colptr + rowvalA = A.rowval + nzvalA = A.nzval + + n = length(I) + rowvalB = Array(Int, n) + nzvalB = Array(Tv, n) + + rowB = 1 + 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 + rowvalB[idxB] = i + nzvalB[idxB] = nzvalA[r] + idxB += 1 + break + end + end + end + if n > (idxB-1) + deleteat!(nzvalB, idxB:n) + deleteat!(rowvalB, idxB:n) + end + SparseVector(n, rowvalB, nzvalB) +end + + +### Generic functions operating on AbstractSparseVector + +### getindex + +function _spgetindex{Tv,Ti}(m::Int, nzind::AbstractVector{Ti}, nzval::AbstractVector{Tv}, i::Integer) + ii = searchsortedfirst(nzind, convert(Ti, i)) + (ii <= m && nzind[ii] == i) ? nzval[ii] : zero(Tv) +end + +function getindex{Tv}(x::AbstractSparseVector{Tv}, i::Integer) + checkbounds(x, i) + _spgetindex(nnz(x), nonzeroinds(x), nonzeros(x), i) +end + +function getindex{Tv,Ti}(x::AbstractSparseVector{Tv,Ti}, I::UnitRange) + 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 = Array(Ti, mr) + rval = Array(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{Tv,Ti}(x::AbstractSparseVector{Tv,Ti}, I::AbstractVector{Bool}) = x[find(I)] +getindex{Tv,Ti}(x::AbstractSparseVector{Tv,Ti}, I::AbstractArray{Bool}) = x[find(I)] +@inline function getindex{Tv,Ti}(x::AbstractSparseVector{Tv,Ti}, 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{Tv,Ti}(x::AbstractSparseVector{Tv,Ti}, 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 showarray(io::IO, x::AbstractSparseVector; + header::Bool=true, limit::Bool=Base._limit_output, + rows = Base.tty_size()[1], repr=false) + + n = length(x) + nzind = nonzeroinds(x) + nzval = nonzeros(x) + xnnz = length(nzind) + + if header + println(io, summary(x)) + end + half_screen_rows = limit ? div(rows - 8, 2) : typemax(Int) + pad = ndigits(n) + sep = "\n\t" + for k = 1:length(nzind) + if k < half_screen_rows || k > xnnz - half_screen_rows + print(io, " ", '[', rpad(nzind[k], pad), "] = ") + showcompact(io, nzval[k]) + println(io) + elseif k == half_screen_rows + println(io, " ", " "^pad, " \u22ee") + end + end +end + +function summary(x::AbstractSparseVector) + string("Sparse vector of length ", length(x), ", with ", length(nonzeros(x)), + " ", eltype(x), " nonzero entries:") +end + +show(io::IO, x::AbstractSparseVector) = showarray(io, x) +writemime(io::IO, ::MIME"text/plain", x::AbstractSparseVector) = Base.with_output_limit(()->show(io, x)) + +### Conversion to matrix + +function convert{TvD,TiD,Tv,Ti}(::Type{SparseMatrixCSC{TvD,TiD}}, x::AbstractSparseVector{Tv,Ti}) + n = length(x) + xnzind = nonzeroinds(x) + xnzval = nonzeros(x) + m = length(xnzind) + colptr = TiD[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(TiD, xnzind) + nzval = collect(TvD, xnzval) + SparseMatrixCSC(n, 1, colptr, rowval, nzval) +end + +convert{TvD,Tv,Ti}(::Type{SparseMatrixCSC{TvD}}, x::AbstractSparseVector{Tv,Ti}) = + convert(SparseMatrixCSC{TvD,Ti}, x) + +convert{Tv,Ti}(::Type{SparseMatrixCSC}, x::AbstractSparseVector{Tv,Ti}) = + convert(SparseMatrixCSC{Tv,Ti}, x) + + +### Array manipulation + +function full{Tv}(x::AbstractSparseVector{Tv}) + n = length(x) + n == 0 && return Array(Tv, 0) + nzind = nonzeroinds(x) + nzval = nonzeros(x) + r = zeros(Tv, n) + for i = 1:length(nzind) + r[nzind[i]] = nzval[i] + end + return r +end + +vec(x::AbstractSparseVector) = x +copy(x::AbstractSparseVector) = + SparseVector(length(x), copy(nonzeroinds(x)), copy(nonzeros(x))) + +function reinterpret{T,Tv}(::Type{T}, x::AbstractSparseVector{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{Tv<:AbstractFloat}(x::AbstractSparseVector{Tv}) = x +float(x::AbstractSparseVector) = + SparseVector(length(x), copy(nonzeroinds(x)), float(nonzeros(x))) + +complex{Tv<:Complex}(x::AbstractSparseVector{Tv}) = x +complex(x::AbstractSparseVector) = + SparseVector(length(x), copy(nonzeroinds(x)), complex(nonzeros(x))) + + +### Concatenation + +function hcat{Tv,Ti}(X::AbstractSparseVector{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 = Array(Ti, n+1) + nzrow = Array(Ti, tnnz) + nzval = Array(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 + +function vcat{Tv,Ti}(X::AbstractSparseVector{Tv,Ti}...) + # check sizes + n = length(X) + tnnz = 0 + for j = 1:n + tnnz += nnz(X[j]) + end + + # construction + rnzind = Array(Ti, tnnz) + rnzval = Array(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 + +### math functions + +### Unary Map + +# zero-preserving functions (z->z, nz->nz) +for op in [:-, :abs, :abs2, :conj] + @eval begin + $(op)(x::AbstractSparseVector) = + SparseVector(length(x), copy(nonzeroinds(x)), $(op)(nonzeros(x))) + end +end + +# 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){Tv<:$(TF),Ti<:Integer}(x::AbstractSparseVector{Tv,Ti}) + R = typeof($(op)(zero(Tv))) + xnzind = nonzeroinds(x) + xnzval = nonzeros(x) + m = length(xnzind) + + ynzind = Array(Ti, m) + ynzval = Array(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 + +real{T<:Real}(x::AbstractSparseVector{T}) = x +@unarymap_nz2z_z2z real Complex + +imag{Tv<:Real,Ti<:Integer}(x::AbstractSparseVector{Tv,Ti}) = 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){Tv<:$(TF),Ti<:Integer}(x::AbstractSparseVector{Tv,Ti}) + 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{Tx,Ty}(f::BinaryOp, + x::AbstractSparseVector{Tx}, + y::AbstractSparseVector{Ty}, + mode::Int) + + 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) + (mx == 0 || my == 0) && return SparseVector(R, 0) + cap = (mode == 0 ? min(mx, my) : mx + my)::Int + + rind = Array(Int, cap) + rval = Array(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::BinaryOp, 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!{Tx,Ty}(f::BinaryOp, mx::Int, my::Int, + xnzind, xnzval::AbstractVector{Tx}, + ynzind, ynzval::AbstractVector{Ty}, + rind, rval) + # 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!{Tx,Ty}(f::BinaryOp, mx::Int, my::Int, + xnzind, xnzval::AbstractVector{Tx}, + ynzind, ynzval::AbstractVector{Ty}, + rind, rval) + # 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{Tx,Ty}(f::BinaryOp, + x::AbstractVector{Tx}, + y::AbstractSparseVector{Ty}, + mode::Int) + + 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 = Array(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{Tx,Ty}(f::BinaryOp, + x::AbstractSparseVector{Tx}, + y::AbstractVector{Ty}, + mode::Int) + + 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 = Array(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, :AddFun, 1), + (:_vsub, :SubFun, 1), + (:_vmul, :MulFun, 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 +.*(x::BitVector, y::AbstractSparseVector{Bool}) = _vmul(x, y) +.*(x::AbstractSparseVector{Bool}, y::BitVector) = _vmul(x, y) + +# definition of operators + +for (op, vop) in [(:+, :_vadd), (:(.+), :_vadd), + (:-, :_vsub), (:(.-), :_vsub), + (:.*, :_vmul)] + @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 +end + +# definition of other binary functions + +for (op, fun, TF, mode) in [(:max, :MaxFun, :Real, 2), + (:min, :MinFun, :Real, 2), + (:complex, :ComplexFun, :Real, 1)] + @eval begin + $(op){Tx<:$(TF),Ty<:$(TF)}(x::AbstractSparseVector{Tx}, y::AbstractSparseVector{Ty}) = + _binarymap($(fun)(), x, y, $mode) + $(op){Tx<:$(TF),Ty<:$(TF)}(x::StridedVector{Tx}, y::AbstractSparseVector{Ty}) = + _binarymap($(fun)(), x, y, $mode) + $(op){Tx<:$(TF),Ty<:$(TF)}(x::AbstractSparseVector{Tx}, y::StridedVector{Ty}) = + _binarymap($(fun)(), x, y, $mode) + end +end + +### Reduction + +sum(x::AbstractSparseVector) = sum(nonzeros(x)) +sumabs(x::AbstractSparseVector) = sumabs(nonzeros(x)) +sumabs2(x::AbstractSparseVector) = sumabs2(nonzeros(x)) + +function maximum{T<:Real}(x::AbstractSparseVector{T}) + 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{T<:Real}(x::AbstractSparseVector{T}) + 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 + +maxabs{T<:Number}(x::AbstractSparseVector{T}) = maxabs(nonzeros(x)) +minabs{T<:Number}(x::AbstractSparseVector{T}) = nnz(x) < length(x) ? abs(zero(T)) : minabs(nonzeros(x)) + +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) +transpose(x::SparseVector) = _ct(IdFun(), x) +ctranspose(x::SparseVector) = _ct(ConjFun(), x) +function _ct{T}(f, x::SparseVector{T}) + isempty(x) && return Array(T, 1, 0) + A = zeros(T, 1, length(x)) + xnzind = nonzeroinds(x) + xnzval = nonzeros(x) + for (i,v) in zip(xnzind, xnzval) + @inbounds A[i] = f(v) + end + A +end + +### 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 == one(a) + for i = 1:m + @inbounds ii = nzind[i] + @inbounds v = nzval[i] + y[ii] += v + end + elseif a == -one(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) +scale!(a::Complex, x::AbstractSparseVector) = scale!(nonzeros(x), a) + +scale(x::AbstractSparseVector, a::Real) = + SparseVector(length(x), copy(nonzeroinds(x)), scale(nonzeros(x), a)) +scale(x::AbstractSparseVector, a::Complex) = + SparseVector(length(x), copy(nonzeroinds(x)), scale(nonzeros(x), a)) + +scale(a::Real, x::AbstractSparseVector) = scale(x, a) +scale(a::Complex, x::AbstractSparseVector) = scale(x, a) + +*(x::AbstractSparseVector, a::Number) = scale(x, a) +*(a::Number, x::AbstractSparseVector) = scale(x, a) +.*(x::AbstractSparseVector, a::Number) = scale(x, a) +.*(a::Number, x::AbstractSparseVector) = scale(x, a) + + +# dot + +function dot{Tx<:Number,Ty<:Number}(x::StridedVector{Tx}, y::AbstractSparseVector{Ty}) + 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{Tx<:Number,Ty<:Number}(x::AbstractSparseVector{Tx}, y::AbstractVector{Ty}) + 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::BinaryOp, + 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{Tx<:Number,Ty<:Number}(x::AbstractSparseVector{Tx}, y::AbstractSparseVector{Ty}) + is(x, y) && return sumabs2(x) + n = length(x) + length(y) == n || throw(DimensionMismatch()) + + xnzind = nonzeroinds(x) + ynzind = nonzeroinds(y) + xnzval = nonzeros(x) + ynzval = nonzeros(y) + + _spdot(DotFun(), + 1, length(xnzind), xnzind, xnzval, + 1, length(ynzind), ynzind, ynzval) +end + + +### BLAS-2 / dense A * sparse x -> dense y + +# A_mul_B + +function *{Ta,Tx}(A::StridedMatrix{Ta}, x::AbstractSparseVector{Tx}) + m, n = size(A) + length(x) == n || throw(DimensionMismatch()) + Ty = promote_type(Ta, Tx) + y = Array(Ty, m) + A_mul_B!(y, A, x) +end + +A_mul_B!{Tx,Ty}(y::StridedVector{Ty}, A::StridedMatrix, x::AbstractSparseVector{Tx}) = + 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{Ta,Tx}(A::StridedMatrix{Ta}, x::AbstractSparseVector{Tx}) + m, n = size(A) + length(x) == m || throw(DimensionMismatch()) + Ty = promote_type(Ta, Tx) + y = Array(Ty, n) + At_mul_B!(y, A, x) +end + +At_mul_B!{Tx,Ty}(y::StridedVector{Ty}, A::StridedMatrix, x::AbstractSparseVector{Tx}) = + 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') + 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 = Array(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!{Tx,Ty}(y::StridedVector{Ty}, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}) = + 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!{Tx,Ty}(y::StridedVector{Ty}, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}) = + At_mul_B!(one(Tx), A, x, zero(Ty), y) + +At_mul_B!{Tx,Ty}(α::Number, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}, β::Number, y::StridedVector{Ty}) = + _At_or_Ac_mul_B!(MulFun(), α, A, x, β, y) + +Ac_mul_B!{Tx,Ty}(y::StridedVector{Ty}, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}) = + Ac_mul_B!(one(Tx), A, x, zero(Ty), y) + +Ac_mul_B!{Tx,Ty}(α::Number, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}, β::Number, y::StridedVector{Ty}) = + _At_or_Ac_mul_B!(DotFun(), α, A, x, β, y) + +function _At_or_Ac_mul_B!{Tx,Ty}(tfun::BinaryOp, + α::Number, A::SparseMatrixCSC, x::AbstractSparseVector{Tx}, + β::Number, y::StridedVector{Ty}) + 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(MulFun(), A, x) + +Ac_mul_B(A::SparseMatrixCSC, x::AbstractSparseVector) = + _At_or_Ac_mul_B(DotFun(), A, x) + +function _At_or_Ac_mul_B{TvA,TiA,TvX,TiX}(tfun::BinaryOp, A::SparseMatrixCSC{TvA,TiA}, x::AbstractSparseVector{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 = Array(Ti, n) + ynzval = Array(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 diff --git a/base/sparse/spqr.jl b/base/sparse/spqr.jl index aa32492695213..aed43b4dbf08e 100644 --- a/base/sparse/spqr.jl +++ b/base/sparse/spqr.jl @@ -38,12 +38,12 @@ const RTX_EQUALS_B = Int32(2) # solve R'*X=B or X = R'\B const RTX_EQUALS_ETB = Int32(3) # solve R'*X=E'*B or X = R'\(E'*B) -using Base.SparseMatrix: SparseMatrixCSC -using Base.SparseMatrix.CHOLMOD: C_Dense, C_Sparse, Dense, ITypes, Sparse, VTypes, common +using ..SparseArrays: SparseMatrixCSC +using ..SparseArrays.CHOLMOD: C_Dense, C_Sparse, Dense, ITypes, Sparse, VTypes, common import Base: size import Base.LinAlg: qrfact -import Base.SparseMatrix.CHOLMOD: convert, free! +import ..SparseArrays.CHOLMOD: convert, free! diff --git a/base/sparse/umfpack.jl b/base/sparse/umfpack.jl index 6310fb8f1d0a5..9ed382a8a282e 100644 --- a/base/sparse/umfpack.jl +++ b/base/sparse/umfpack.jl @@ -7,8 +7,8 @@ export UmfpackLU import Base: (\), Ac_ldiv_B, At_ldiv_B, findnz, getindex, show, size import Base.LinAlg: A_ldiv_B!, Ac_ldiv_B!, At_ldiv_B!, Factorization, det, lufact -importall Base.SparseMatrix -import Base.SparseMatrix: increment, increment!, decrement, decrement!, nnz +importall ..SparseArrays +import ..SparseArrays: increment, increment!, decrement, decrement!, nnz include("umfpack_h.jl") type MatrixIllConditionedException <: Exception diff --git a/base/sysimg.jl b/base/sysimg.jl index c555a41cf6d6c..5b075ac237067 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -248,10 +248,6 @@ importall .Broadcast # statistics include("statistics.jl") -# sparse matrices and sparse linear algebra -include("sparse.jl") -importall .SparseMatrix - # irrational mathematical constants include("irrationals.jl") @@ -288,6 +284,10 @@ importall .Profile include("Dates.jl") import .Dates: Date, DateTime, now +# sparse matrices, vectors, and sparse linear algebra +include("sparse.jl") +importall .SparseArrays + # Documentation include("markdown/Markdown.jl") diff --git a/doc/stdlib/arrays.rst b/doc/stdlib/arrays.rst index af88e9342a287..058a189a2a585 100644 --- a/doc/stdlib/arrays.rst +++ b/doc/stdlib/arrays.rst @@ -909,10 +909,11 @@ BitArrays .. _stdlib-sparse: -Sparse Matrices ---------------- +Sparse Vectors and Matrices +--------------------------- -Sparse matrices support much of the same set of operations as dense matrices. The following functions are specific to sparse matrices. +Sparse vectors and matrices largely support the same set of operations as their +dense counterparts. The following functions are specific to sparse arrays. .. function:: sparse(I,J,V,[m,n,combine]) @@ -924,13 +925,13 @@ Sparse matrices support much of the same set of operations as dense matrices. Th .. Docstring generated from Julia source - Create a sparse matrix ``S`` of size ``m x 1`` such that ``S[I[k]] = V[k]``\ . Duplicates are combined using the ``combine`` function, which defaults to ``+`` if it is not provided. In julia, sparse vectors are really just sparse matrices with one column. Given Julia's Compressed Sparse Columns (CSC) storage format, a sparse column matrix with one column is sparse, whereas a sparse row matrix with one row ends up being dense. + 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 it is not provided. .. function:: sparsevec(D::Dict, [m]) .. Docstring generated from Julia source - Create a sparse matrix of size ``m x 1`` where the row values are keys from the dictionary, and the nonzero values are the values from the dictionary. + 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. .. function:: issparse(S) @@ -948,31 +949,31 @@ Sparse matrices support much of the same set of operations as dense matrices. Th .. Docstring generated from Julia source - Convert a dense vector ``A`` into a sparse matrix of size ``m x 1``\ . In julia, sparse vectors are really just sparse matrices with one column. + Convert a vector ``A`` into a sparse vector of length ``m``\ . .. function:: full(S) .. Docstring generated from Julia source - Convert a sparse matrix ``S`` into a dense matrix. + Convert a sparse matrix or vector ``S`` into a dense matrix or vector. .. function:: nnz(A) .. Docstring generated from Julia source - Returns the number of stored (filled) elements in a sparse matrix. + Returns the number of stored (filled) elements in a sparse array. -.. function:: spzeros(m,n) +.. function:: spzeros(m[,n]) .. Docstring generated from Julia source - Create a sparse matrix of size ``m x n``\ . This sparse matrix will not contain any nonzero values. No storage will be allocated for nonzero values during construction. + Create a sparse vector of length ``m`` or sparse matrix of size ``m x n``\ . This sparse array will not contain any nonzero values. No storage will be allocated for nonzero values during construction. .. function:: spones(S) .. Docstring generated from Julia source - Create a sparse matrix with the same structure as that of ``S``\ , but with every nonzero element having the value ``1.0``\ . + Create a sparse array with the same structure as that of ``S``\ , but with every nonzero element having the value ``1.0``\ . .. function:: speye(type,m[,n]) @@ -986,28 +987,29 @@ Sparse matrices support much of the same set of operations as dense matrices. Th Construct a sparse diagonal matrix. ``B`` is a tuple of vectors containing the diagonals and ``d`` is a tuple containing the positions of the diagonals. In the case the input contains only one diagonal, ``B`` can be a vector (instead of a tuple) and ``d`` can be the diagonal position (instead of a tuple), defaulting to 0 (diagonal). Optionally, ``m`` and ``n`` specify the size of the resulting sparse matrix. -.. function:: sprand([rng,] m,n,p [,rfn]) +.. function:: sprand([rng],m,[n],p::AbstractFloat,[rfn]) .. Docstring generated from Julia source - Create a random ``m`` by ``n`` sparse matrix, in which the probability of any - element being nonzero is independently given by ``p`` (and hence the mean - density of nonzeros is also exactly ``p``). Nonzero values are sampled from - the distribution specified by ``rfn``. The uniform distribution is used in - case ``rfn`` is not specified. The optional ``rng`` argument specifies a - random number generator, see :ref:`Random Numbers `. + Create a random length ``m`` sparse vector or ``m`` by ``n`` sparse matrix, in + which the probability of any element being nonzero is independently given by + ``p`` (and hence the mean density of nonzeros is also exactly ``p``). Nonzero + values are sampled from the distribution specified by ``rfn``. The uniform + distribution is used in case ``rfn`` is not specified. The optional ``rng`` + argument specifies a random number generator, see :ref:`Random Numbers + `. -.. function:: sprandn(m,n,p) +.. function:: sprandn(m[,n],p::AbstractFloat) .. Docstring generated from Julia source - Create a random ``m`` by ``n`` sparse matrix with the specified (independent) probability ``p`` of any entry being nonzero, where nonzero values are sampled from the normal distribution. + Create a random sparse vector of length ``m`` or sparse matrix of size ``m`` by ``n`` with the specified (independent) probability ``p`` of any entry being nonzero, where nonzero values are sampled from the normal distribution. -.. function:: sprandbool(m,n,p) +.. function:: sprandbool(m[,n],p) .. Docstring generated from Julia source - Create a random ``m`` by ``n`` sparse boolean matrix with the specified (independent) probability ``p`` of any entry being ``true``\ . + Create a random ``m`` by ``n`` sparse boolean matrix or length ``m`` sparse boolean vector with the specified (independent) probability ``p`` of any entry being ``true``\ . .. function:: etree(A[, post]) @@ -1019,21 +1021,21 @@ Sparse matrices support much of the same set of operations as dense matrices. Th .. Docstring generated from Julia source - Return the symmetric permutation of ``A``\ , which is ``A[p,p]``\ . ``A`` should be symmetric and sparse, where only the upper triangular part of the matrix is stored. This algorithm ignores the lower triangular part of the matrix. Only the upper triangular part of the result is returned as well. + Return the symmetric permutation of ``A``\ , which is ``A[p,p]``\ . ``A`` should be symmetric, sparse, and only contain nonzeros in the upper triangular part of the matrix is stored. This algorithm ignores the lower triangular part of the matrix. Only the upper triangular part of the result is returned. .. function:: nonzeros(A) .. Docstring generated from Julia source - Return a vector of the structural nonzero values in sparse matrix ``A``\ . This includes zeros that are explicitly stored in the sparse matrix. The returned vector points directly to the internal nonzero storage of ``A``\ , and any modifications to the returned vector will mutate ``A`` as well. See ``rowvals(A)`` and ``nzrange(A, col)``\ . + Return a vector of the structural nonzero values in sparse array ``A``\ . This includes zeros that are explicitly stored in the sparse array. The returned vector points directly to the internal nonzero storage of ``A``\ , and any modifications to the returned vector will mutate ``A`` as well. See ``rowvals(A)`` and ``nzrange(A, col)``\ . -.. function:: rowvals(A) +.. function:: rowvals(A::SparseMatrixCSC) .. Docstring generated from Julia source - Return a vector of the row indices of ``A``\ , and any modifications to the returned vector will mutate ``A`` as well. Given the internal storage format of sparse matrices, providing access to how the row indices are stored internally can be useful in conjuction with iterating over structural nonzero values. See ``nonzeros(A)`` and ``nzrange(A, col)``\ . + Return a vector of the row indices of ``A``\ . Any modifications to the returned vector will mutate ``A`` as well. Providing access to how the row indices are stored internally can be useful in conjuction with iterating over structural nonzero values. See also ``nonzeros(A)`` and ``nzrange(A, col)``\ . -.. function:: nzrange(A, col) +.. function:: nzrange(A::SparseMatrixCSC, col) .. Docstring generated from Julia source diff --git a/doc/stdlib/linalg.rst b/doc/stdlib/linalg.rst index 591222d739e54..76135a1999b08 100644 --- a/doc/stdlib/linalg.rst +++ b/doc/stdlib/linalg.rst @@ -149,7 +149,7 @@ Linear algebra functions in Julia are largely implemented by calling functions f .. Docstring generated from Julia source - Compute the ``LDLt`` factorization of a sparse symmetric or Hermitian matrix. A fill-reducing permutation is used. ``F = ldltfact(A)`` is most frequently used to solve systems of equations ``A*x = b`` with ``F\b``\ , but also the methods ``diag``\ , ``det``\ , ``logdet`` are defined for ``F``\ . You can also extract individual factors from ``F``\ , using ``F[:L]``\ . However, since pivoting is on by default, the factorization is internally represented as ``A == P'*L*D*L'*P`` with a permutation matrix ``P``\ ; using just ``L`` without accounting for ``P`` will give incorrect answers. To include the effects of permutation, it's typically preferable to extact "combined" factors like ``PtL = F[:PtL]`` (the equivalent of ``P'*L``\ ) and ``LtP = F[:UP]`` (the equivalent of ``L'*P``\ ). The complete list of supported factors is ``:L, :PtL, :D, :UP, :U, :LD, :DU, :PtLD, :DUP``\ . + Compute the ``LDLt`` factorization of a sparse symmetric or Hermitian matrix. A fill-reducing permutation is used. ``F = ldltfact(A)`` is most frequently used to solve systems of equations ``A*x = b`` with ``F\b``\ . The returned factorization object ``F`` also supports the methods ``diag``\ , ``det``\ , and ``logdet``\ . You can extract individual factors from ``F`` using ``F[:L]``\ . However, since pivoting is on by default, the factorization is internally represented as ``A == P'*L*D*L'*P`` with a permutation matrix ``P``\ ; using just ``L`` without accounting for ``P`` will give incorrect answers. To include the effects of permutation, it's typically preferable to extact "combined" factors like ``PtL = F[:PtL]`` (the equivalent of ``P'*L``\ ) and ``LtP = F[:UP]`` (the equivalent of ``L'*P``\ ). The complete list of supported factors is ``:L, :PtL, :D, :UP, :U, :LD, :DU, :PtLD, :DUP``\ . Setting optional ``shift`` keyword argument computes the factorization of ``A+shift*I`` instead of ``A``\ . If the ``perm`` argument is nonempty, it should be a permutation of ``1:size(A,1)`` giving the ordering to use (instead of CHOLMOD's default AMD ordering). diff --git a/test/sparse.jl b/test/sparse.jl index 79dcd9c27f413..c57b889fa11f8 100644 --- a/test/sparse.jl +++ b/test/sparse.jl @@ -1,6 +1,7 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license include("sparsedir/sparse.jl") +include("sparsedir/sparsevector.jl") if Base.USE_GPL_LIBS include("sparsedir/umfpack.jl") include("sparsedir/cholmod.jl") diff --git a/test/sparsedir/cholmod.jl b/test/sparsedir/cholmod.jl index 8026c0039bf9e..37363dafcdf1f 100644 --- a/test/sparsedir/cholmod.jl +++ b/test/sparsedir/cholmod.jl @@ -3,7 +3,7 @@ srand(123) using Base.Test -using Base.SparseMatrix.CHOLMOD +using Base.SparseArrays.CHOLMOD # based on deps/SuiteSparse-4.0.2/CHOLMOD/Demo/ @@ -318,8 +318,8 @@ for elty in (Float64, Complex{Float64}) A1pdSparse = CHOLMOD.Sparse( A1pd.m, A1pd.n, - Base.SparseMatrix.decrement(A1pd.colptr), - Base.SparseMatrix.decrement(A1pd.rowval), + Base.SparseArrays.decrement(A1pd.colptr), + Base.SparseArrays.decrement(A1pd.rowval), A1pd.nzval) ## High level interface @@ -564,7 +564,7 @@ b = rand(3) Asp = As[p,p] LDp = sparse(ldltfact(Asp, perm=[1,2,3])[:LD]) # LDp = sparse(Fs[:LD]) -Lp, dp = Base.SparseMatrix.CHOLMOD.getLd!(copy(LDp)) +Lp, dp = Base.SparseArrays.CHOLMOD.getLd!(copy(LDp)) Dp = spdiagm(dp) @test_approx_eq Fs\b Af\b @test_approx_eq Fs[:UP]\(Fs[:PtLD]\b) Af\b diff --git a/test/sparsedir/sparse.jl b/test/sparsedir/sparse.jl index 1504d6d3e6c79..27e363354888b 100644 --- a/test/sparsedir/sparse.jl +++ b/test/sparsedir/sparse.jl @@ -190,8 +190,8 @@ for i = 1:5 a = sprand(10, 5, 0.7) b = sprand(5, 15, 0.3) @test maximum(abs(a*b - full(a)*full(b))) < 100*eps() - @test maximum(abs(Base.SparseMatrix.spmatmul(a,b,sortindices=:sortcols) - full(a)*full(b))) < 100*eps() - @test maximum(abs(Base.SparseMatrix.spmatmul(a,b,sortindices=:doubletranspose) - full(a)*full(b))) < 100*eps() + @test maximum(abs(Base.SparseArrays.spmatmul(a,b,sortindices=:sortcols) - full(a)*full(b))) < 100*eps() + @test maximum(abs(Base.SparseArrays.spmatmul(a,b,sortindices=:doubletranspose) - full(a)*full(b))) < 100*eps() @test full(kron(a,b)) == kron(full(a), full(b)) @test full(kron(full(a),b)) == kron(full(a), full(b)) @test full(kron(a,full(b))) == kron(full(a), full(b)) @@ -381,18 +381,18 @@ for (aa116, ss116) in [(a116, s116), (ad116, sd116)] # range indexing @test full(ss116[i,:]) == aa116[i,:] - @test full(ss116[:,j]) == aa116[:,j]'' # sparse matrices/vectors always have ndims==2: + @test full(ss116[:,j]) == aa116[:,j] @test full(ss116[i,1:2:end]) == aa116[i,1:2:end] - @test full(ss116[1:2:end,j]) == aa116[1:2:end,j]'' + @test full(ss116[1:2:end,j]) == aa116[1:2:end,j] @test full(ss116[i,end:-2:1]) == aa116[i,end:-2:1] - @test full(ss116[end:-2:1,j]) == aa116[end:-2:1,j]'' + @test full(ss116[end:-2:1,j]) == aa116[end:-2:1,j] # float-range indexing is not supported # sorted vector indexing @test full(ss116[i,[3:2:end-3;]]) == aa116[i,[3:2:end-3;]] - @test full(ss116[[3:2:end-3;],j]) == aa116[[3:2:end-3;],j]'' + @test full(ss116[[3:2:end-3;],j]) == aa116[[3:2:end-3;],j] @test full(ss116[i,[end-3:-2:1;]]) == aa116[i,[end-3:-2:1;]] - @test full(ss116[[end-3:-2:1;],j]) == aa116[[end-3:-2:1;],j]'' + @test full(ss116[[end-3:-2:1;],j]) == aa116[[end-3:-2:1;],j] # unsorted vector indexing with repetition p = [4, 1, 2, 3, 2, 6] @@ -403,7 +403,7 @@ for (aa116, ss116) in [(a116, s116), (ad116, sd116)] # bool indexing li = bitrand(size(aa116,1)) lj = bitrand(size(aa116,2)) - @test full(ss116[li,j]) == aa116[li,j]'' + @test full(ss116[li,j]) == aa116[li,j] @test full(ss116[li,:]) == aa116[li,:] @test full(ss116[i,lj]) == aa116[i,lj] @test full(ss116[:,lj]) == aa116[:,lj] @@ -412,7 +412,7 @@ for (aa116, ss116) in [(a116, s116), (ad116, sd116)] # empty indices for empty in (1:0, Int[]) @test full(ss116[empty,:]) == aa116[empty,:] - @test full(ss116[:,empty]) == aa116[:,empty]'' + @test full(ss116[:,empty]) == aa116[:,empty] @test full(ss116[empty,lj]) == aa116[empty,lj] @test full(ss116[li,empty]) == aa116[li,empty] @test full(ss116[empty,empty]) == aa116[empty,empty] @@ -445,7 +445,7 @@ S1290 = SparseMatrixCSC(3, 3, UInt8[1,1,1,1], UInt8[], Int64[]) S1290[end] = 3 @test S1290[end] == (S1290[1] + S1290[2,2]) @test 6 == sum(diag(S1290)) - @test (full(S1290)[[3,1],1])'' == full(S1290[[3,1],1]) + @test full(S1290)[[3,1],1] == full(S1290[[3,1],1]) # end @@ -457,7 +457,7 @@ let a = spzeros(Int, 10, 10) @test a[1,:] == sparse(ones(Int,1,10)) a[:,2] = 2 @test countnz(a) == 19 - @test a[:,2] == 2*sparse(ones(Int,10,1)) + @test a[:,2] == 2*sparse(ones(Int,10)) a[1,:] = 1:10 @test a[1,:] == sparse([1:10;]') @@ -500,9 +500,9 @@ let ASZ = 1000, TSZ = 800 end let A = speye(Int, 5), I=1:10, X=reshape([trues(10); falses(15)],5,5) - @test A[I] == A[X] == reshape([1,0,0,0,0,0,1,0,0,0], 10, 1) + @test A[I] == A[X] == [1,0,0,0,0,0,1,0,0,0] A[I] = [1:10;] - @test A[I] == A[X] == reshape(1:10, 10, 1) + @test A[I] == A[X] == collect(1:10) end let S = sprand(50, 30, 0.5, x->round(Int,rand(x)*100)), I = sprandbool(50, 30, 0.2) @@ -540,7 +540,7 @@ let S = sprand(50, 30, 0.5, x->round(Int,rand(x)*100)) end #Issue 7507 -@test (i7507=sparsevec(Dict{Int64, Float64}(), 10))==spzeros(10,1) +@test (i7507=sparsevec(Dict{Int64, Float64}(), 10))==spzeros(10) #Issue 7650 let S = spzeros(3, 3) @@ -552,9 +552,10 @@ let X = eye(5), M = rand(5,4), C = spzeros(3,3) VX = vec(X); VSX = vec(SX) VM = vec(M); VSM1 = vec(SM); VSM2 = sparsevec(M) VC = vec(C) - @test reshape(VX, (25,1)) == VSX - @test reshape(VM, (20,1)) == VSM1 == VSM2 - @test size(VC) == (9,1) + @test VX == VSX + @test VM == VSM1 + @test VM == VSM2 + @test size(VC) == (9,) @test nnz(VC) == 0 @test nnz(VSX) == 5 end @@ -641,9 +642,9 @@ function test_getindex_algs{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, ((minj < 1) || (maxj > n)) && BoundsError() end - (alg == 0) ? Base.SparseMatrix.getindex_I_sorted_bsearch_A(A, I, J) : - (alg == 1) ? Base.SparseMatrix.getindex_I_sorted_bsearch_I(A, I, J) : - Base.SparseMatrix.getindex_I_sorted_linear(A, I, J) + (alg == 0) ? Base.SparseArrays.getindex_I_sorted_bsearch_A(A, I, J) : + (alg == 1) ? Base.SparseArrays.getindex_I_sorted_bsearch_I(A, I, J) : + Base.SparseArrays.getindex_I_sorted_linear(A, I, J) end let M=2^14, N=2^4 @@ -761,7 +762,7 @@ end # issue #9917 @test sparse([]') == reshape(sparse([]), 1, 0) -@test full(sparse([])) == zeros(0, 1) +@test full(sparse([])) == zeros(0) @test_throws BoundsError sparse([])[1] @test_throws BoundsError sparse([])[1] = 1 x = speye(100) @@ -1071,7 +1072,7 @@ Ai = ceil(Int,Ar*100) @test_approx_eq vecnorm(Ai) vecnorm(full(Ai)) # test sparse matrix cond -A = sparse([1.0]) +A = sparse(reshape([1.0],1,1)) Ac = sprandn(20,20,.5) + im* sprandn(20,20,.5) Ar = sprandn(20,20,.5) @test cond(A,1) == 1.0 @@ -1092,12 +1093,12 @@ Ac = sprandn(20,20,.5) + im* sprandn(20,20,.5) Aci = ceil(Int64,100*sprand(20,20,.5))+ im*ceil(Int64,sprand(20,20,.5)) Ar = sprandn(20,20,.5) Ari = ceil(Int64,100*Ar) -@test_approx_eq_eps Base.SparseMatrix.normestinv(Ac,3) norm(inv(full(Ac)),1) 1e-4 -@test_approx_eq_eps Base.SparseMatrix.normestinv(Aci,3) norm(inv(full(Aci)),1) 1e-4 -@test_approx_eq_eps Base.SparseMatrix.normestinv(Ar) norm(inv(full(Ar)),1) 1e-4 -@test_throws ArgumentError Base.SparseMatrix.normestinv(Ac,0) -@test_throws ArgumentError Base.SparseMatrix.normestinv(Ac,21) -@test_throws DimensionMismatch Base.SparseMatrix.normestinv(sprand(3,5,.9)) +@test_approx_eq_eps Base.SparseArrays.normestinv(Ac,3) norm(inv(full(Ac)),1) 1e-4 +@test_approx_eq_eps Base.SparseArrays.normestinv(Aci,3) norm(inv(full(Aci)),1) 1e-4 +@test_approx_eq_eps Base.SparseArrays.normestinv(Ar) norm(inv(full(Ar)),1) 1e-4 +@test_throws ArgumentError Base.SparseArrays.normestinv(Ac,0) +@test_throws ArgumentError Base.SparseArrays.normestinv(Ac,21) +@test_throws DimensionMismatch Base.SparseArrays.normestinv(sprand(3,5,.9)) @test_throws ErrorException transpose(sub(sprandn(10, 10, 0.3), 1:4, 1:4)) @test_throws ErrorException ctranspose(sub(sprandn(10, 10, 0.3), 1:4, 1:4)) @@ -1106,7 +1107,7 @@ Ari = ceil(Int64,100*Ar) A = sprand(10,10,0.2) p = randperm(10) q = randperm(10) -@test Base.SparseMatrix.csc_permute(A, invperm(p), q) == full(A)[p, q] +@test Base.SparseArrays.csc_permute(A, invperm(p), q) == full(A)[p, q] # issue #13008 @test_throws ArgumentError sparse(collect(1:100), collect(1:100), fill(5,100), 5, 5) diff --git a/test/sparsedir/sparsevector.jl b/test/sparsedir/sparsevector.jl new file mode 100644 index 0000000000000..d7c1a1eba6177 --- /dev/null +++ b/test/sparsedir/sparsevector.jl @@ -0,0 +1,685 @@ +# This file is a part of Julia. License is MIT: http://julialang.org/license + +### Data + +spv_x1 = SparseVector(8, [2, 5, 6], [1.25, -0.75, 3.5]) + +@test isa(spv_x1, SparseVector{Float64,Int}) + +x1_full = zeros(length(spv_x1)) +x1_full[SparseArrays.nonzeroinds(spv_x1)] = nonzeros(spv_x1) + +### Basic Properties + +let x = spv_x1 + @test eltype(x) == Float64 + @test ndims(x) == 1 + @test length(x) == 8 + @test size(x) == (8,) + @test size(x,1) == 8 + @test size(x,2) == 1 + @test !isempty(x) + + @test countnz(x) == 3 + @test nnz(x) == 3 + @test SparseArrays.nonzeroinds(x) == [2, 5, 6] + @test nonzeros(x) == [1.25, -0.75, 3.5] +end + +# full + +for (x, xf) in [(spv_x1, x1_full)] + @test isa(full(x), Vector{Float64}) + @test full(x) == xf +end + +### Show + +@test contains(string(spv_x1), "1.25") +@test contains(string(spv_x1), "-0.75") +@test contains(string(spv_x1), "3.5") + +### Other Constructors + +### Comparison helper to ensure exact equality with internal structure +function exact_equal(x::AbstractSparseVector, y::AbstractSparseVector) + eltype(x) == eltype(y) && + eltype(SparseArrays.nonzeroinds(x)) == eltype(SparseArrays.nonzeroinds(y)) && + length(x) == length(y) && + SparseArrays.nonzeroinds(x) == SparseArrays.nonzeroinds(y) && + nonzeros(x) == nonzeros(y) +end + +# construct empty sparse vector + +@test exact_equal(spzeros(Float64, 8), SparseVector(8, Int[], Float64[])) + +# from list of indices and values + +@test exact_equal( + sparsevec(Int[], Float64[], 8), + SparseVector(8, Int[], Float64[])) + +@test exact_equal( + sparsevec(Int[], Float64[]), + SparseVector(0, Int[], Float64[])) + +@test exact_equal( + sparsevec([3, 3], [5.0, -5.0], 8), + spzeros(Float64, 8)) + +@test exact_equal( + sparsevec([2, 3, 6], [12.0, 18.0, 25.0]), + SparseVector(6, [2, 3, 6], [12.0, 18.0, 25.0])) + +let x0 = SparseVector(8, [2, 3, 6], [12.0, 18.0, 25.0]) + @test exact_equal( + sparsevec([2, 3, 6], [12.0, 18.0, 25.0], 8), x0) + + @test exact_equal( + sparsevec([3, 6, 2], [18.0, 25.0, 12.0], 8), x0) + + @test exact_equal( + sparsevec([2, 3, 4, 4, 6], [12.0, 18.0, 5.0, -5.0, 25.0], 8), + x0) + + @test exact_equal( + sparsevec([1, 1, 1, 2, 3, 3, 6], [2.0, 3.0, -5.0, 12.0, 10.0, 8.0, 25.0], 8), + x0) + + @test exact_equal( + sparsevec([2, 3, 6, 7, 7], [12.0, 18.0, 25.0, 5.0, -5.0], 8), x0) +end + +# from dictionary + +function my_intmap(x) + a = Dict{Int,eltype(x)}() + for i in SparseArrays.nonzeroinds(x) + a[i] = x[i] + end + return a +end + +let x = spv_x1 + a = my_intmap(x) + xc = sparsevec(a, 8) + @test exact_equal(x, xc) + + xc = sparsevec(a) + @test exact_equal(xc, SparseVector(6, [2, 5, 6], [1.25, -0.75, 3.5])) +end + +# spones - copies structure, but replaces nzvals with ones +let x = SparseVector(8, [2, 3, 6], [12.0, 18.0, 25.0]) + y = spones(x) + @test (x .!= 0) == (y .!= 0) + @test y == SparseVector(8, [2, 3, 6], [1.0, 1.0, 1.0]) +end + +# sprand & sprandn + +let xr = sprand(1000, 0.9) + @test isa(xr, SparseVector{Float64,Int}) + @test length(xr) == 1000 + @test all(nonzeros(xr) .>= 0.0) +end + +let xr = sprand(1000, 0.9, Float32) + @test isa(xr, SparseVector{Float32,Int}) + @test length(xr) == 1000 + @test all(nonzeros(xr) .>= 0.0) +end + +let xr = sprandn(1000, 0.9) + @test isa(xr, SparseVector{Float64,Int}) + @test length(xr) == 1000 + if !isempty(nonzeros(xr)) + @test any(nonzeros(xr) .> 0.0) && any(nonzeros(xr) .< 0.0) + end +end + +let xr = sprandbool(1000, 0.9) + @test isa(xr, SparseVector{Bool,Int}) + @test length(xr) == 1000 + @test all(nonzeros(xr)) +end + +let r1 = MersenneTwister(), r2 = MersenneTwister() + @test sprand(r1, 100, .9) == sprand(r2, 100, .9) + @test sprandn(r1, 100, .9) == sprandn(r2, 100, .9) + @test sprandbool(r1, 100, .9) == sprandbool(r2, 100, .9) +end + +### Element access + +# getindex + +# single integer index +for (x, xf) in [(spv_x1, x1_full)] + for i = 1:length(x) + @test x[i] == xf[i] + end +end + +# generic array index +let x = sprand(100, 0.5) + I = rand(1:length(x), 20) + @which x[I] + r = x[I] + @test isa(r, SparseVector{Float64,Int}) + @test all(nonzeros(r) .!= 0.0) + @test full(r) == full(x)[I] +end + +# setindex + +let xc = spzeros(Float64, 8) + xc[3] = 2.0 + @test exact_equal(xc, SparseVector(8, [3], [2.0])) +end + +let xc = copy(spv_x1) + xc[5] = 2.0 + @test exact_equal(xc, SparseVector(8, [2, 5, 6], [1.25, 2.0, 3.5])) +end + +let xc = copy(spv_x1) + xc[3] = 4.0 + @test exact_equal(xc, SparseVector(8, [2, 3, 5, 6], [1.25, 4.0, -0.75, 3.5])) + + xc[1] = 6.0 + @test exact_equal(xc, SparseVector(8, [1, 2, 3, 5, 6], [6.0, 1.25, 4.0, -0.75, 3.5])) + + xc[8] = -1.5 + @test exact_equal(xc, SparseVector(8, [1, 2, 3, 5, 6, 8], [6.0, 1.25, 4.0, -0.75, 3.5, -1.5])) +end + +let xc = copy(spv_x1) + xc[5] = 0.0 + @test exact_equal(xc, SparseVector(8, [2, 6], [1.25, 3.5])) + + xc[6] = 0.0 + @test exact_equal(xc, SparseVector(8, [2], [1.25])) + + xc[2] = 0.0 + @test exact_equal(xc, SparseVector(8, Int[], Float64[])) +end + + +### Array manipulation + +# copy + +let x = spv_x1 + xc = copy(x) + @test isa(xc, SparseVector{Float64,Int}) + @test !is(x.nzind, xc.nzval) + @test !is(x.nzval, xc.nzval) + @test exact_equal(x, xc) +end + +let a = SparseVector(8, [2, 5, 6], Int32[12, 35, 72]) + # reinterpret + au = reinterpret(UInt32, a) + @test isa(au, SparseVector{UInt32,Int}) + @test exact_equal(au, SparseVector(8, [2, 5, 6], UInt32[12, 35, 72])) + + # float + af = float(a) + @test isa(af, SparseVector{Float64,Int}) + @test exact_equal(af, SparseVector(8, [2, 5, 6], [12., 35., 72.])) + + # complex + acp = complex(af) + @test isa(acp, SparseVector{Complex128,Int}) + @test exact_equal(acp, SparseVector(8, [2, 5, 6], complex([12., 35., 72.]))) +end + +### Type conversion + +let x = convert(SparseVector, sparse([2, 5, 6], [1, 1, 1], [1.25, -0.75, 3.5], 8, 1)) + @test isa(x, SparseVector{Float64,Int}) + @test exact_equal(x, spv_x1) +end + +let x = spv_x1, xf = x1_full + xc = convert(SparseVector, xf) + @test isa(xc, SparseVector{Float64,Int}) + @test exact_equal(xc, x) + + xc = convert(SparseVector{Float32,Int}, x) + xf32 = SparseVector(8, [2, 5, 6], [1.25f0, -0.75f0, 3.5f0]) + @test isa(xc, SparseVector{Float32,Int}) + @test exact_equal(xc, xf32) + + xc = convert(SparseVector{Float32}, x) + @test isa(xc, SparseVector{Float32,Int}) + @test exact_equal(xc, xf32) + + xm = convert(SparseMatrixCSC, x) + @test isa(xm, SparseMatrixCSC{Float64,Int}) + @test full(xm) == reshape(xf, 8, 1) + + xm = convert(SparseMatrixCSC{Float32}, x) + @test isa(xm, SparseMatrixCSC{Float32,Int}) + @test full(xm) == reshape(convert(Vector{Float32}, xf), 8, 1) +end + + +### Concatenation + +let m = 80, n = 100 + A = Array(SparseVector{Float64,Int}, n) + tnnz = 0 + for i = 1:length(A) + A[i] = sprand(m, 0.3) + tnnz += nnz(A[i]) + end + + H = hcat(A...) + @test isa(H, SparseMatrixCSC{Float64,Int}) + @test size(H) == (m, n) + @test nnz(H) == tnnz + Hr = zeros(m, n) + for j = 1:n + Hr[:,j] = full(A[j]) + end + @test full(H) == Hr + + V = vcat(A...) + @test isa(V, SparseVector{Float64,Int}) + @test length(V) == m * n + Vr = vec(Hr) + @test full(V) == Vr +end + + +## sparsemat: combinations with sparse matrix + +let S = sprand(4, 8, 0.5) + Sf = full(S) + @assert isa(Sf, Matrix{Float64}) + + # get a single column + for j = 1:size(S,2) + col = S[:, j] + @test isa(col, SparseVector{Float64,Int}) + @test length(col) == size(S,1) + @test full(col) == Sf[:,j] + end + + # Get a reshaped vector + v = S[:] + @test isa(v, SparseVector{Float64,Int}) + @test length(v) == length(S) + @test full(v) == Sf[:] + + # Get a linear subset + for i=0:length(S) + v = S[1:i] + @test isa(v, SparseVector{Float64,Int}) + @test length(v) == i + @test full(v) == Sf[1:i] + end + for i=1:length(S)+1 + v = S[i:end] + @test isa(v, SparseVector{Float64,Int}) + @test length(v) == length(S) - i + 1 + @test full(v) == Sf[i:end] + end +end + +## math + +### Data + +rnd_x0 = sprand(50, 0.6) +rnd_x0f = full(rnd_x0) + +rnd_x1 = sprand(50, 0.7) * 4.0 +rnd_x1f = full(rnd_x1) + +spv_x1 = SparseVector(8, [2, 5, 6], [1.25, -0.75, 3.5]) +spv_x2 = SparseVector(8, [1, 2, 6, 7], [3.25, 4.0, -5.5, -6.0]) + +### Arithmetic operations + +let x = spv_x1, x2 = x2 = spv_x2 + # negate + @test exact_equal(-x, SparseVector(8, [2, 5, 6], [-1.25, 0.75, -3.5])) + + # abs and abs2 + @test exact_equal(abs(x), SparseVector(8, [2, 5, 6], abs([1.25, -0.75, 3.5]))) + @test exact_equal(abs2(x), SparseVector(8, [2, 5, 6], abs2([1.25, -0.75, 3.5]))) + + # plus and minus + xa = SparseVector(8, [1,2,5,6,7], [3.25,5.25,-0.75,-2.0,-6.0]) + + @test exact_equal(x + x, x * 2) + @test exact_equal(x + x2, xa) + @test exact_equal(x2 + x, xa) + + xb = SparseVector(8, [1,2,5,6,7], [-3.25,-2.75,-0.75,9.0,6.0]) + @test exact_equal(x - x, SparseVector(8, Int[], Float64[])) + @test exact_equal(x - x2, xb) + @test exact_equal(x2 - x, -xb) + + @test full(x) + x2 == full(xa) + @test full(x) - x2 == full(xb) + @test x + full(x2) == full(xa) + @test x - full(x2) == full(xb) + + # multiplies + xm = SparseVector(8, [2, 6], [5.0, -19.25]) + @test exact_equal(x .* x, abs2(x)) + @test exact_equal(x .* x2, xm) + @test exact_equal(x2 .* x, xm) + + @test full(x) .* x2 == full(xm) + @test x .* full(x2) == full(xm) + + # max & min + @test exact_equal(max(x, x), x) + @test exact_equal(min(x, x), x) + @test exact_equal(max(x, x2), + SparseVector(8, Int[1, 2, 6], Float64[3.25, 4.0, 3.5])) + @test exact_equal(min(x, x2), + SparseVector(8, Int[2, 5, 6, 7], Float64[1.25, -0.75, -5.5, -6.0])) +end + +### Complex + +let x = spv_x1, x2 = spv_x2 + # complex + @test exact_equal(complex(x, x), + SparseVector(8, [2,5,6], [1.25+1.25im, -0.75-0.75im, 3.5+3.5im])) + @test exact_equal(complex(x, x2), + SparseVector(8, [1,2,5,6,7], [3.25im, 1.25+4.0im, -0.75+0.im, 3.5-5.5im, -6.0im])) + @test exact_equal(complex(x2, x), + SparseVector(8, [1,2,5,6,7], [3.25+0.im, 4.0+1.25im, -0.75im, -5.5+3.5im, -6.0+0.im])) + + # real & imag + + @test is(real(x), x) + @test exact_equal(imag(x), spzeros(Float64, length(x))) + + xcp = complex(x, x2) + @test exact_equal(real(xcp), x) + @test exact_equal(imag(xcp), x2) +end + +### Zero-preserving math functions: sparse -> sparse + +function check_nz2z_z2z{T}(f::Function, x::SparseVector{T}, xf::Vector{T}) + R = typeof(f(zero(T))) + r = f(x) + isa(r, AbstractSparseVector) || error("$f(x) is not a sparse vector.") + eltype(r) == R || error("$f(x) results in eltype = $(eltype(r)), expect $R") + all(r.nzval .!= 0) || error("$f(x) contains zeros in nzval.") + full(r) == f(xf) || error("Incorrect results found in $f(x).") +end + +for f in [floor, ceil, trunc, round] + check_nz2z_z2z(f, rnd_x1, rnd_x1f) +end + +for f in [log1p, expm1, + sin, tan, sinpi, sind, tand, + asin, atan, asind, atand, + sinh, tanh, asinh, atanh] + check_nz2z_z2z(f, rnd_x0, rnd_x0f) +end + +### Non-zero-preserving math functions: sparse -> dense + +function check_z2nz{T}(f::Function, x::SparseVector{T}, xf::Vector{T}) + R = typeof(f(zero(T))) + r = f(x) + isa(r, Vector) || error("$f(x) is not a dense vector.") + eltype(r) == R || error("$f(x) results in eltype = $(eltype(r)), expect $R") + r == f(xf) || error("Incorrect results found in $f(x).") +end + +for f 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] + check_z2nz(f, rnd_x0, rnd_x0f) +end + + +### Reduction + +# sum, sumabs, sumabs2, vecnorm + +let x = spv_x1 + @test sum(x) == 4.0 + @test sumabs(x) == 5.5 + @test sumabs2(x) == 14.375 + + @test vecnorm(x) == sqrt(14.375) + @test vecnorm(x, 1) == 5.5 + @test vecnorm(x, 2) == sqrt(14.375) + @test vecnorm(x, Inf) == 3.5 +end + +# maximum, minimum, maxabs, minabs + +let x = spv_x1 + @test maximum(x) == 3.5 + @test minimum(x) == -0.75 + @test maxabs(x) == 3.5 + @test minabs(x) == 0.0 +end + +let x = abs(spv_x1) + @test maximum(x) == 3.5 + @test minimum(x) == 0.0 +end + +let x = -abs(spv_x1) + @test maximum(x) == 0.0 + @test minimum(x) == -3.5 +end + +let x = SparseVector(3, [1, 2, 3], [-4.5, 2.5, 3.5]) + @test maximum(x) == 3.5 + @test minimum(x) == -4.5 + @test maxabs(x) == 4.5 + @test minabs(x) == 2.5 +end + +let x = spzeros(Float64, 8) + @test maximum(x) == 0.0 + @test minimum(x) == 0.0 + @test maxabs(x) == 0.0 + @test minabs(x) == 0.0 +end + + +### linalg + +### BLAS Level-1 + +let x = sprand(16, 0.5), x2 = sprand(16, 0.4) + xf = full(x) + xf2 = full(x2) + + # axpy! + for c in [1.0, -1.0, 2.0, -2.0] + y = full(x) + @test is(Base.axpy!(c, x2, y), y) + @test y == full(x2 * c + x) + end + + # scale + let sx = SparseVector(x.n, x.nzind, x.nzval * 2.5) + @test exact_equal(scale(x, 2.5), sx) + @test exact_equal(scale(2.5, x), sx) + @test exact_equal(x * 2.5, sx) + @test exact_equal(2.5 * x, sx) + @test exact_equal(x .* 2.5, sx) + @test exact_equal(2.5 .* x, sx) + + xc = copy(x) + @test is(scale!(xc, 2.5), xc) + @test exact_equal(xc, sx) + end + + # dot + let dv = dot(xf, xf2) + @test dot(x, x) == sumabs2(x) + @test dot(x2, x2) == sumabs2(x2) + @test_approx_eq dot(x, x2) dv + @test_approx_eq dot(x2, x) dv + @test_approx_eq dot(full(x), x2) dv + @test_approx_eq dot(x, full(x2)) dv + end +end + +let x = complex(sprand(32, 0.6), sprand(32, 0.6)), + y = complex(sprand(32, 0.6), sprand(32, 0.6)) + xf = full(x)::Vector{Complex128} + yf = full(y)::Vector{Complex128} + @test_approx_eq dot(x, x) dot(xf, xf) + @test_approx_eq dot(x, y) dot(xf, yf) +end + + + +### BLAS Level-2: + +## dense A * sparse x -> dense y + +let A = randn(9, 16), x = sprand(16, 0.7) + xf = full(x) + for α in [0.0, 1.0, 2.0], β in [0.0, 0.5, 1.0] + y = rand(9) + rr = α * A * xf + β * y + @test is(A_mul_B!(α, A, x, β, y), y) + @test_approx_eq y rr + end + y = A * x + @test isa(y, Vector{Float64}) + @test_approx_eq A * x A * xf +end + +let A = randn(16, 9), x = sprand(16, 0.7) + xf = full(x) + for α in [0.0, 1.0, 2.0], β in [0.0, 0.5, 1.0] + y = rand(9) + rr = α * A'xf + β * y + @test is(At_mul_B!(α, A, x, β, y), y) + @test_approx_eq y rr + end + y = At_mul_B(A, x) + @test isa(y, Vector{Float64}) + @test_approx_eq y At_mul_B(A, xf) +end + +## sparse A * sparse x -> dense y + +let A = sprandn(9, 16, 0.5), x = sprand(16, 0.7) + Af = full(A) + xf = full(x) + for α in [0.0, 1.0, 2.0], β in [0.0, 0.5, 1.0] + y = rand(9) + rr = α * Af * xf + β * y + @test is(A_mul_B!(α, A, x, β, y), y) + @test_approx_eq y rr + end + y = SparseArrays.densemv(A, x) + @test isa(y, Vector{Float64}) + @test_approx_eq y Af * xf +end + +let A = sprandn(16, 9, 0.5), x = sprand(16, 0.7) + Af = full(A) + xf = full(x) + for α in [0.0, 1.0, 2.0], β in [0.0, 0.5, 1.0] + y = rand(9) + rr = α * Af'xf + β * y + @test is(At_mul_B!(α, A, x, β, y), y) + @test_approx_eq y rr + end + y = SparseArrays.densemv(A, x; trans='T') + @test isa(y, Vector{Float64}) + @test_approx_eq y At_mul_B(Af, xf) +end + +let A = complex(sprandn(7, 8, 0.5), sprandn(7, 8, 0.5)), + x = complex(sprandn(8, 0.6), sprandn(8, 0.6)), + x2 = complex(sprandn(7, 0.75), sprandn(7, 0.75)) + Af = full(A) + xf = full(x) + x2f = full(x2) + @test_approx_eq SparseArrays.densemv(A, x; trans='N') Af * xf + @test_approx_eq SparseArrays.densemv(A, x2; trans='T') Af.' * x2f + @test_approx_eq SparseArrays.densemv(A, x2; trans='C') Af'x2f +end + +## sparse A * sparse x -> sparse y + +let A = sprandn(9, 16, 0.5), x = sprand(16, 0.7), x2 = sprand(9, 0.7) + Af = full(A) + xf = full(x) + x2f = full(x2) + + y = A * x + @test isa(y, SparseVector{Float64,Int}) + @test all(nonzeros(y) .!= 0.0) + @test_approx_eq full(y) Af * xf + + y = At_mul_B(A, x2) + @test isa(y, SparseVector{Float64,Int}) + @test all(nonzeros(y) .!= 0.0) + @test_approx_eq full(y) Af'x2f +end + +let A = complex(sprandn(7, 8, 0.5), sprandn(7, 8, 0.5)), + x = complex(sprandn(8, 0.6), sprandn(8, 0.6)), + x2 = complex(sprandn(7, 0.75), sprandn(7, 0.75)) + Af = full(A) + xf = full(x) + x2f = full(x2) + + y = A * x + @test isa(y, SparseVector{Complex128,Int}) + @test_approx_eq full(y) Af * xf + + y = At_mul_B(A, x2) + @test isa(y, SparseVector{Complex128,Int}) + @test_approx_eq full(y) Af.' * x2f + + y = Ac_mul_B(A, x2) + @test isa(y, SparseVector{Complex128,Int}) + @test_approx_eq full(y) Af'x2f +end + +# It's tempting to share data between a SparseVector and a SparseArrays, +# but if that's done, then modifications to one or the other will cause +# an inconsistent state: +sv = sparse(1:10) +sm = convert(SparseMatrixCSC, sv) +sv[1] = 0 +@test full(sm)[2:end] == collect(2:10) + +# Ensure that sparsevec with all-zero values returns an array of zeros +@test sparsevec([1,2,3],[0,0,0]) == [0,0,0] + +# Compare stored zero semantics between SparseVector and SparseMatrixCSC +let S = SparseMatrixCSC(10,1,[1,6],[1,3,5,6,7],[0,1,2,0,3]), x = SparseVector(10,[1,3,5,6,7],[0,1,2,0,3]) + @test nnz(S) == nnz(x) == 5 + for I = (:, 1:10, collect(1:10)) + @test S[I,1] == S[I] == x[I] == x + @test nnz(S[I,1]) == nnz(S[I]) == nnz(x[I]) == nnz(x) + end + for I = (2:9, 1:2, 9:10, [3,6,1], [10,9,8], []) + @test S[I,1] == S[I] == x[I] + @test nnz(S[I,1]) == nnz(S[I]) == nnz(x[I]) + end + @test S[[1 3 5; 2 4 6]] == x[[1 3 5; 2 4 6]] + @test nnz(S[[1 3 5; 2 4 6]]) == nnz(x[[1 3 5; 2 4 6]]) +end diff --git a/test/sparsedir/spqr.jl b/test/sparsedir/spqr.jl index 3b9c0f9d1782a..1fee41328c1ad 100644 --- a/test/sparsedir/spqr.jl +++ b/test/sparsedir/spqr.jl @@ -2,8 +2,8 @@ using Base.Test -using Base.SparseMatrix.SPQR -using Base.SparseMatrix.CHOLMOD +using Base.SparseArrays.SPQR +using Base.SparseArrays.CHOLMOD m, n = 100, 10 nn = 100 diff --git a/test/sparsedir/umfpack.jl b/test/sparsedir/umfpack.jl index 2a7e708eca2e5..be6ea41cae4d6 100644 --- a/test/sparsedir/umfpack.jl +++ b/test/sparsedir/umfpack.jl @@ -6,14 +6,14 @@ do33 = ones(3) # based on deps/Suitesparse-4.0.2/UMFPACK/Demo/umfpack_di_demo.c -using Base.SparseMatrix.UMFPACK.increment! +using Base.SparseArrays.UMFPACK.increment! A0 = sparse(increment!([0,4,1,1,2,2,0,1,2,3,4,4]), increment!([0,4,0,2,1,2,1,4,3,2,1,2]), [2.,1.,3.,4.,-1.,-3.,3.,6.,2.,1.,4.,2.], 5, 5) for Tv in (Float64, Complex128) - for Ti in Base.SparseMatrix.UMFPACK.UMFITypes.types + for Ti in Base.SparseArrays.UMFPACK.UMFITypes.types A = convert(SparseMatrixCSC{Tv,Ti}, A0) lua = lufact(A) @test nnz(lua) == 18 @@ -40,7 +40,7 @@ for Tv in (Float64, Complex128) end Ac0 = complex(A0,A0) -for Ti in Base.SparseMatrix.UMFPACK.UMFITypes.types +for Ti in Base.SparseArrays.UMFPACK.UMFITypes.types Ac = convert(SparseMatrixCSC{Complex128,Ti}, Ac0) lua = lufact(Ac) L,U,p,q,Rs = lua[:(:)]