3611 lines
120 KiB
Julia
3611 lines
120 KiB
Julia
# This file is a part of Julia. License is MIT: https://julialang.org/license
|
||
|
||
# Compressed sparse columns data structure
|
||
# Assumes that no zeros are stored in the data structure
|
||
# Assumes that row values in rowval for each column are sorted
|
||
# issorted(rowval[colptr[i]:(colptr[i+1]-1)]) == true
|
||
|
||
"""
|
||
SparseMatrixCSC{Tv,Ti<:Integer} <: AbstractSparseMatrix{Tv,Ti}
|
||
|
||
Matrix type for storing sparse matrices in the
|
||
[Compressed Sparse Column](@ref man-csc) format.
|
||
"""
|
||
struct SparseMatrixCSC{Tv,Ti<:Integer} <: AbstractSparseMatrix{Tv,Ti}
|
||
m::Int # Number of rows
|
||
n::Int # Number of columns
|
||
colptr::Vector{Ti} # Column i is in colptr[i]:(colptr[i+1]-1)
|
||
rowval::Vector{Ti} # Row indices of stored values
|
||
nzval::Vector{Tv} # Stored values, typically nonzeros
|
||
|
||
function SparseMatrixCSC{Tv,Ti}(m::Integer, n::Integer, colptr::Vector{Ti}, rowval::Vector{Ti},
|
||
nzval::Vector{Tv}) where {Tv,Ti<:Integer}
|
||
m < 0 && throw(ArgumentError("number of rows (m) must be ≥ 0, got $m"))
|
||
n < 0 && throw(ArgumentError("number of columns (n) must be ≥ 0, got $n"))
|
||
new(Int(m), Int(n), colptr, rowval, nzval)
|
||
end
|
||
end
|
||
function SparseMatrixCSC(m::Integer, n::Integer, colptr::Vector, rowval::Vector, nzval::Vector)
|
||
Tv = eltype(nzval)
|
||
Ti = promote_type(eltype(colptr), eltype(rowval))
|
||
SparseMatrixCSC{Tv,Ti}(m, n, colptr, rowval, nzval)
|
||
end
|
||
|
||
size(S::SparseMatrixCSC) = (S.m, S.n)
|
||
|
||
"""
|
||
nnz(A)
|
||
|
||
Returns the number of stored (filled) elements in a sparse array.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = speye(3)
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
|
||
julia> nnz(A)
|
||
3
|
||
```
|
||
"""
|
||
nnz(S::SparseMatrixCSC) = Int(S.colptr[S.n + 1]-1)
|
||
countnz(S::SparseMatrixCSC) = countnz(S.nzval)
|
||
count(S::SparseMatrixCSC) = count(S.nzval)
|
||
|
||
"""
|
||
nonzeros(A)
|
||
|
||
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`](@ref) and [`nzrange`](@ref).
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = speye(3)
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
|
||
julia> nonzeros(A)
|
||
3-element Array{Float64,1}:
|
||
1.0
|
||
1.0
|
||
1.0
|
||
```
|
||
"""
|
||
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 conjunction with iterating over structural
|
||
nonzero values. See also [`nonzeros`](@ref) and [`nzrange`](@ref).
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = speye(3)
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
|
||
julia> rowvals(A)
|
||
3-element Array{Int64,1}:
|
||
1
|
||
2
|
||
3
|
||
```
|
||
"""
|
||
rowvals(S::SparseMatrixCSC) = S.rowval
|
||
|
||
"""
|
||
nzrange(A::SparseMatrixCSC, col::Integer)
|
||
|
||
Return the range of indices to the structural nonzero values of a sparse matrix
|
||
column. In conjunction with [`nonzeros`](@ref) and
|
||
[`rowvals`](@ref), this allows for convenient iterating over a sparse matrix :
|
||
|
||
A = sparse(I,J,V)
|
||
rows = rowvals(A)
|
||
vals = nonzeros(A)
|
||
m, n = size(A)
|
||
for i = 1:n
|
||
for j in nzrange(A, i)
|
||
row = rows[j]
|
||
val = vals[j]
|
||
# perform sparse wizardry...
|
||
end
|
||
end
|
||
"""
|
||
nzrange(S::SparseMatrixCSC, col::Integer) = S.colptr[col]:(S.colptr[col+1]-1)
|
||
|
||
function Base.show(io::IO, ::MIME"text/plain", S::SparseMatrixCSC)
|
||
xnnz = nnz(S)
|
||
print(io, S.m, "×", S.n, " ", typeof(S), " with ", xnnz, " stored ",
|
||
xnnz == 1 ? "entry" : "entries")
|
||
if xnnz != 0
|
||
print(io, ":")
|
||
show(io, S)
|
||
end
|
||
end
|
||
|
||
Base.show(io::IO, S::SparseMatrixCSC) = Base.show(convert(IOContext, io), S::SparseMatrixCSC)
|
||
function Base.show(io::IOContext, S::SparseMatrixCSC)
|
||
if nnz(S) == 0
|
||
return show(io, MIME("text/plain"), S)
|
||
end
|
||
|
||
limit::Bool = get(io, :limit, false)
|
||
if limit
|
||
rows = displaysize(io)[1]
|
||
half_screen_rows = div(rows - 8, 2)
|
||
else
|
||
half_screen_rows = typemax(Int)
|
||
end
|
||
pad = ndigits(max(S.m,S.n))
|
||
k = 0
|
||
sep = "\n "
|
||
if !haskey(io, :compact)
|
||
io = IOContext(io, :compact => true)
|
||
end
|
||
for col = 1:S.n, k = S.colptr[col] : (S.colptr[col+1]-1)
|
||
if k < half_screen_rows || k > nnz(S)-half_screen_rows
|
||
print(io, sep, '[', rpad(S.rowval[k], pad), ", ", lpad(col, pad), "] = ")
|
||
if isassigned(S.nzval, Int(k))
|
||
show(io, S.nzval[k])
|
||
else
|
||
print(io, Base.undef_ref_str)
|
||
end
|
||
elseif k == half_screen_rows
|
||
print(io, sep, '\u22ee')
|
||
end
|
||
k += 1
|
||
end
|
||
end
|
||
|
||
## Reinterpret and Reshape
|
||
|
||
function reinterpret(::Type{T}, a::SparseMatrixCSC{Tv}) where {T,Tv}
|
||
if sizeof(T) != sizeof(Tv)
|
||
throw(ArgumentError("SparseMatrixCSC reinterpret is only supported for element types of the same size"))
|
||
end
|
||
mA, nA = size(a)
|
||
colptr = copy(a.colptr)
|
||
rowval = copy(a.rowval)
|
||
nzval = reinterpret(T, a.nzval)
|
||
return SparseMatrixCSC(mA, nA, colptr, rowval, nzval)
|
||
end
|
||
|
||
function sparse_compute_reshaped_colptr_and_rowval{Ti}(colptrS::Vector{Ti}, rowvalS::Vector{Ti}, mS::Int, nS::Int, colptrA::Vector{Ti}, rowvalA::Vector{Ti}, mA::Int, nA::Int)
|
||
lrowvalA = length(rowvalA)
|
||
maxrowvalA = (lrowvalA > 0) ? maximum(rowvalA) : zero(Ti)
|
||
((length(colptrA) == (nA+1)) && (maximum(colptrA) <= (lrowvalA+1)) && (maxrowvalA <= mA)) || throw(BoundsError())
|
||
|
||
colptrS[1] = 1
|
||
colA = 1
|
||
colS = 1
|
||
ptr = 1
|
||
|
||
@inbounds while colA <= nA
|
||
offsetA = (colA - 1) * mA
|
||
while ptr <= colptrA[colA+1]-1
|
||
rowA = rowvalA[ptr]
|
||
i = offsetA + rowA - 1
|
||
colSn = div(i, mS) + 1
|
||
rowS = mod(i, mS) + 1
|
||
while colS < colSn
|
||
colptrS[colS+1] = ptr
|
||
colS += 1
|
||
end
|
||
rowvalS[ptr] = rowS
|
||
ptr += 1
|
||
end
|
||
colA += 1
|
||
end
|
||
@inbounds while colS <= nS
|
||
colptrS[colS+1] = ptr
|
||
colS += 1
|
||
end
|
||
end
|
||
|
||
function reinterpret(::Type{T}, a::SparseMatrixCSC{Tv,Ti}, dims::NTuple{N,Int}) where {T,Tv,Ti,N}
|
||
if sizeof(T) != sizeof(Tv)
|
||
throw(ArgumentError("SparseMatrixCSC reinterpret is only supported for element types of the same size"))
|
||
end
|
||
if prod(dims) != length(a)
|
||
throw(DimensionMismatch("new dimensions $(dims) must be consistent with array size $(length(a))"))
|
||
end
|
||
mS,nS = dims
|
||
mA,nA = size(a)
|
||
numnz = nnz(a)
|
||
colptr = Vector{Ti}(nS+1)
|
||
rowval = similar(a.rowval)
|
||
nzval = reinterpret(T, a.nzval)
|
||
|
||
sparse_compute_reshaped_colptr_and_rowval(colptr, rowval, mS, nS, a.colptr, a.rowval, mA, nA)
|
||
|
||
return SparseMatrixCSC(mS, nS, colptr, rowval, nzval)
|
||
end
|
||
|
||
function copy(ra::ReshapedArray{<:Any,2,<:SparseMatrixCSC})
|
||
mS,nS = size(ra)
|
||
a = parent(ra)
|
||
mA,nA = size(a)
|
||
numnz = nnz(a)
|
||
colptr = similar(a.colptr, nS+1)
|
||
rowval = similar(a.rowval)
|
||
nzval = copy(a.nzval)
|
||
|
||
sparse_compute_reshaped_colptr_and_rowval(colptr, rowval, mS, nS, a.colptr, a.rowval, mA, nA)
|
||
|
||
return SparseMatrixCSC(mS, nS, colptr, rowval, nzval)
|
||
end
|
||
|
||
## Constructors
|
||
|
||
copy(S::SparseMatrixCSC) =
|
||
SparseMatrixCSC(S.m, S.n, copy(S.colptr), copy(S.rowval), copy(S.nzval))
|
||
|
||
function copy!(A::SparseMatrixCSC, B::SparseMatrixCSC)
|
||
# If the two matrices have the same length then all the
|
||
# elements in A will be overwritten.
|
||
if length(A) == length(B)
|
||
resize!(A.nzval, length(B.nzval))
|
||
resize!(A.rowval, length(B.rowval))
|
||
if size(A) == size(B)
|
||
# Simple case: we can simply copy the internal fields of B to A.
|
||
copy!(A.colptr, B.colptr)
|
||
copy!(A.rowval, B.rowval)
|
||
else
|
||
# This is like a "reshape B into A".
|
||
sparse_compute_reshaped_colptr_and_rowval(A.colptr, A.rowval, A.m, A.n, B.colptr, B.rowval, B.m, B.n)
|
||
end
|
||
else
|
||
length(A) >= length(B) || throw(BoundsError())
|
||
lB = length(B)
|
||
nnzA = nnz(A)
|
||
nnzB = nnz(B)
|
||
# Up to which col, row, and ptr in rowval/nzval will A be overwritten?
|
||
lastmodcolA = div(lB - 1, A.m) + 1
|
||
lastmodrowA = mod(lB - 1, A.m) + 1
|
||
lastmodptrA = A.colptr[lastmodcolA]
|
||
while lastmodptrA < A.colptr[lastmodcolA+1] && A.rowval[lastmodptrA] <= lastmodrowA
|
||
lastmodptrA += 1
|
||
end
|
||
lastmodptrA -= 1
|
||
if lastmodptrA >= nnzB
|
||
# A will have fewer non-zero elements; unmodified elements are kept at the end.
|
||
deleteat!(A.rowval, nnzB+1:lastmodptrA)
|
||
deleteat!(A.nzval, nnzB+1:lastmodptrA)
|
||
else
|
||
# A will have more non-zero elements; unmodified elements are kept at the end.
|
||
resize!(A.rowval, nnzB + nnzA - lastmodptrA)
|
||
resize!(A.nzval, nnzB + nnzA - lastmodptrA)
|
||
copy!(A.rowval, nnzB+1, A.rowval, lastmodptrA+1, nnzA-lastmodptrA)
|
||
copy!(A.nzval, nnzB+1, A.nzval, lastmodptrA+1, nnzA-lastmodptrA)
|
||
end
|
||
# Adjust colptr accordingly.
|
||
@inbounds for i in 2:length(A.colptr)
|
||
A.colptr[i] += nnzB - lastmodptrA
|
||
end
|
||
sparse_compute_reshaped_colptr_and_rowval(A.colptr, A.rowval, A.m, lastmodcolA-1, B.colptr, B.rowval, B.m, B.n)
|
||
end
|
||
copy!(A.nzval, B.nzval)
|
||
return A
|
||
end
|
||
|
||
function similar(S::SparseMatrixCSC, ::Type{Tv} = eltype(S)) where Tv
|
||
SparseMatrixCSC(S.m, S.n, copy(S.colptr), copy(S.rowval), Vector{Tv}(length(S.nzval)))
|
||
end
|
||
|
||
function similar(S::SparseMatrixCSC, ::Type{Tv}, ::Type{Ti}) where {Tv,Ti}
|
||
new_colptr = copy!(similar(S.colptr, Ti), S.colptr)
|
||
new_rowval = copy!(similar(S.rowval, Ti), S.rowval)
|
||
new_nzval = copy!(similar(S.nzval, Tv), S.nzval)
|
||
SparseMatrixCSC(S.m, S.n, new_colptr, new_rowval, new_nzval)
|
||
end
|
||
@inline similar(S::SparseMatrixCSC, ::Type{Tv}, d::Dims) where {Tv} = spzeros(Tv, d...)
|
||
|
||
# convert'ing between SparseMatrixCSC types
|
||
convert(::Type{AbstractMatrix{Tv}}, A::SparseMatrixCSC{Tv}) where {Tv} = A
|
||
convert(::Type{AbstractMatrix{Tv}}, A::SparseMatrixCSC) where {Tv} = convert(SparseMatrixCSC{Tv}, A)
|
||
convert(::Type{SparseMatrixCSC{Tv}}, S::SparseMatrixCSC{Tv}) where {Tv} = S
|
||
convert(::Type{SparseMatrixCSC{Tv}}, S::SparseMatrixCSC) where {Tv} = convert(SparseMatrixCSC{Tv,eltype(S.colptr)}, S)
|
||
convert(::Type{SparseMatrixCSC{Tv,Ti}}, S::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = S
|
||
function convert(::Type{SparseMatrixCSC{Tv,Ti}}, S::SparseMatrixCSC) where {Tv,Ti}
|
||
eltypeTicolptr = convert(Vector{Ti}, S.colptr)
|
||
eltypeTirowval = convert(Vector{Ti}, S.rowval)
|
||
eltypeTvnzval = convert(Vector{Tv}, S.nzval)
|
||
return SparseMatrixCSC(S.m, S.n, eltypeTicolptr, eltypeTirowval, eltypeTvnzval)
|
||
end
|
||
# convert'ing from other matrix types to SparseMatrixCSC (also see sparse())
|
||
convert(::Type{SparseMatrixCSC}, M::Matrix) = sparse(M)
|
||
convert(::Type{SparseMatrixCSC}, M::AbstractMatrix{Tv}) where {Tv} = convert(SparseMatrixCSC{Tv,Int}, M)
|
||
convert(::Type{SparseMatrixCSC{Tv}}, M::AbstractMatrix{Tv}) where {Tv} = convert(SparseMatrixCSC{Tv,Int}, M)
|
||
function convert(::Type{SparseMatrixCSC{Tv,Ti}}, M::AbstractMatrix) where {Tv,Ti}
|
||
(I, J, V) = findnz(M)
|
||
eltypeTiI = convert(Vector{Ti}, I)
|
||
eltypeTiJ = convert(Vector{Ti}, J)
|
||
eltypeTvV = convert(Vector{Tv}, V)
|
||
return sparse_IJ_sorted!(eltypeTiI, eltypeTiJ, eltypeTvV, size(M)...)
|
||
end
|
||
# convert'ing from SparseMatrixCSC to other matrix types
|
||
function convert(::Type{Matrix}, S::SparseMatrixCSC{Tv}) where Tv
|
||
# Handle cases where zero(Tv) is not defined but the array is dense.
|
||
A = length(S) == nnz(S) ? Matrix{Tv}(S.m, S.n) : zeros(Tv, S.m, S.n)
|
||
for Sj in 1:S.n
|
||
for Sk in nzrange(S, Sj)
|
||
Si = S.rowval[Sk]
|
||
Sv = S.nzval[Sk]
|
||
A[Si, Sj] = Sv
|
||
end
|
||
end
|
||
return A
|
||
end
|
||
convert(::Type{Array}, S::SparseMatrixCSC) = convert(Matrix, S)
|
||
full(S::SparseMatrixCSC) = convert(Array, S)
|
||
|
||
"""
|
||
full(S)
|
||
|
||
Convert a sparse matrix or vector `S` into a dense matrix or vector.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = speye(3)
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
|
||
julia> full(A)
|
||
3×3 Array{Float64,2}:
|
||
1.0 0.0 0.0
|
||
0.0 1.0 0.0
|
||
0.0 0.0 1.0
|
||
```
|
||
"""
|
||
full
|
||
|
||
float(S::SparseMatrixCSC) = SparseMatrixCSC(S.m, S.n, copy(S.colptr), copy(S.rowval), float.(S.nzval))
|
||
|
||
complex(S::SparseMatrixCSC) = SparseMatrixCSC(S.m, S.n, copy(S.colptr), copy(S.rowval), complex(copy(S.nzval)))
|
||
|
||
# Construct a sparse vector
|
||
|
||
# Note that unlike `vec` for arrays, this does not share data
|
||
vec(S::SparseMatrixCSC) = S[:]
|
||
|
||
"""
|
||
sparse(A)
|
||
|
||
Convert an AbstractMatrix `A` into a sparse matrix.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = eye(3)
|
||
3×3 Array{Float64,2}:
|
||
1.0 0.0 0.0
|
||
0.0 1.0 0.0
|
||
0.0 0.0 1.0
|
||
|
||
julia> sparse(A)
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
```
|
||
"""
|
||
sparse(A::AbstractMatrix{Tv}) where {Tv} = convert(SparseMatrixCSC{Tv,Int}, A)
|
||
|
||
sparse(S::SparseMatrixCSC) = copy(S)
|
||
|
||
sparse_IJ_sorted!(I,J,V,m,n) = sparse_IJ_sorted!(I,J,V,m,n,+)
|
||
|
||
sparse_IJ_sorted!(I,J,V::AbstractVector{Bool},m,n) = sparse_IJ_sorted!(I,J,V,m,n,|)
|
||
|
||
function sparse_IJ_sorted!(I::AbstractVector{Ti}, J::AbstractVector{Ti},
|
||
V::AbstractVector,
|
||
m::Integer, n::Integer, combine::Function) where Ti<:Integer
|
||
m = m < 0 ? 0 : m
|
||
n = n < 0 ? 0 : n
|
||
if isempty(V); return spzeros(eltype(V),Ti,m,n); end
|
||
|
||
cols = zeros(Ti, n+1)
|
||
cols[1] = 1 # For cumsum purposes
|
||
cols[J[1] + 1] = 1
|
||
|
||
lastdup = 1
|
||
ndups = 0
|
||
I_lastdup = I[1]
|
||
J_lastdup = J[1]
|
||
L = length(I)
|
||
|
||
@inbounds for k=2:L
|
||
if I[k] == I_lastdup && J[k] == J_lastdup
|
||
V[lastdup] = combine(V[lastdup], V[k])
|
||
ndups += 1
|
||
else
|
||
cols[J[k] + 1] += 1
|
||
lastdup = k-ndups
|
||
I_lastdup = I[k]
|
||
J_lastdup = J[k]
|
||
if ndups != 0
|
||
I[lastdup] = I_lastdup
|
||
V[lastdup] = V[k]
|
||
end
|
||
end
|
||
end
|
||
|
||
colptr = cumsum!(similar(cols), cols)
|
||
|
||
# Allow up to 20% slack
|
||
if ndups > 0.2*L
|
||
numnz = L-ndups
|
||
deleteat!(I, (numnz+1):L)
|
||
deleteat!(V, (numnz+1):length(V))
|
||
end
|
||
|
||
return SparseMatrixCSC(m, n, colptr, I, V)
|
||
end
|
||
|
||
"""
|
||
sparse(I, J, V,[ m, n, combine])
|
||
|
||
Create a sparse matrix `S` of dimensions `m x n` such that `S[I[k], J[k]] = V[k]`. The
|
||
`combine` function is used to combine duplicates. If `m` and `n` are not specified, they
|
||
are set to `maximum(I)` and `maximum(J)` respectively. If the `combine` function is not
|
||
supplied, `combine` defaults to `+` unless the elements of `V` are Booleans in which case
|
||
`combine` defaults to `|`. All elements of `I` must satisfy `1 <= I[k] <= m`, and all
|
||
elements of `J` must satisfy `1 <= J[k] <= n`. Numerical zeros in (`I`, `J`, `V`) are
|
||
retained as structural nonzeros; to drop numerical zeros, use [`dropzeros!`](@ref).
|
||
|
||
For additional documentation and an expert driver, see `Base.SparseArrays.sparse!`.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> Is = [1; 2; 3];
|
||
|
||
julia> Js = [1; 2; 3];
|
||
|
||
julia> Vs = [1; 2; 3];
|
||
|
||
julia> sparse(Is, Js, Vs)
|
||
3×3 SparseMatrixCSC{Int64,Int64} with 3 stored entries:
|
||
[1, 1] = 1
|
||
[2, 2] = 2
|
||
[3, 3] = 3
|
||
```
|
||
"""
|
||
function sparse(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::AbstractVector{Tv}, m::Integer, n::Integer, combine) where {Tv,Ti<:Integer}
|
||
coolen = length(I)
|
||
if length(J) != coolen || length(V) != coolen
|
||
throw(ArgumentError(string("the first three arguments' lengths must match, ",
|
||
"length(I) (=$(length(I))) == length(J) (= $(length(J))) == length(V) (= ",
|
||
"$(length(V)))")))
|
||
end
|
||
|
||
if m == 0 || n == 0 || coolen == 0
|
||
if coolen != 0
|
||
if n == 0
|
||
throw(ArgumentError("column indices J[k] must satisfy 1 <= J[k] <= n"))
|
||
elseif m == 0
|
||
throw(ArgumentError("row indices I[k] must satisfy 1 <= I[k] <= m"))
|
||
end
|
||
end
|
||
SparseMatrixCSC(m, n, ones(Ti, n+1), Vector{Ti}(), Vector{Tv}())
|
||
else
|
||
# Allocate storage for CSR form
|
||
csrrowptr = Vector{Ti}(m+1)
|
||
csrcolval = Vector{Ti}(coolen)
|
||
csrnzval = Vector{Tv}(coolen)
|
||
|
||
# Allocate storage for the CSC form's column pointers and a necessary workspace
|
||
csccolptr = Vector{Ti}(n+1)
|
||
klasttouch = Vector{Ti}(n)
|
||
|
||
# Allocate empty arrays for the CSC form's row and nonzero value arrays
|
||
# The parent method called below automagically resizes these arrays
|
||
cscrowval = Vector{Ti}()
|
||
cscnzval = Vector{Tv}()
|
||
|
||
sparse!(I, J, V, m, n, combine, klasttouch,
|
||
csrrowptr, csrcolval, csrnzval,
|
||
csccolptr, cscrowval, cscnzval)
|
||
end
|
||
end
|
||
|
||
sparse(I::AbstractVector, J::AbstractVector, V::AbstractVector, m::Integer, n::Integer, combine) =
|
||
sparse(AbstractVector{Int}(I), AbstractVector{Int}(J), V, m, n, combine)
|
||
|
||
"""
|
||
sparse!{Tv,Ti<:Integer}(
|
||
I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::AbstractVector{Tv},
|
||
m::Integer, n::Integer, combine, klasttouch::Vector{Ti},
|
||
csrrowptr::Vector{Ti}, csrcolval::Vector{Ti}, csrnzval::Vector{Tv},
|
||
[csccolptr::Vector{Ti}], [cscrowval::Vector{Ti}, cscnzval::Vector{Tv}] )
|
||
|
||
Parent of and expert driver for [`sparse`](@ref);
|
||
see [`sparse`](@ref) for basic usage. This method
|
||
allows the user to provide preallocated storage for `sparse`'s intermediate objects and
|
||
result as described below. This capability enables more efficient successive construction
|
||
of [`SparseMatrixCSC`](@ref)s from coordinate representations, and also enables extraction
|
||
of an unsorted-column representation of the result's transpose at no additional cost.
|
||
|
||
This method consists of three major steps: (1) Counting-sort the provided coordinate
|
||
representation into an unsorted-row CSR form including repeated entries. (2) Sweep through
|
||
the CSR form, simultaneously calculating the desired CSC form's column-pointer array,
|
||
detecting repeated entries, and repacking the CSR form with repeated entries combined;
|
||
this stage yields an unsorted-row CSR form with no repeated entries. (3) Counting-sort the
|
||
preceding CSR form into a fully-sorted CSC form with no repeated entries.
|
||
|
||
Input arrays `csrrowptr`, `csrcolval`, and `csrnzval` constitute storage for the
|
||
intermediate CSR forms and require `length(csrrowptr) >= m + 1`,
|
||
`length(csrcolval) >= length(I)`, and `length(csrnzval >= length(I))`. Input
|
||
array `klasttouch`, workspace for the second stage, requires `length(klasttouch) >= n`.
|
||
Optional input arrays `csccolptr`, `cscrowval`, and `cscnzval` constitute storage for the
|
||
returned CSC form `S`. `csccolptr` requires `length(csccolptr) >= n + 1`. If necessary,
|
||
`cscrowval` and `cscnzval` are automatically resized to satisfy
|
||
`length(cscrowval) >= nnz(S)` and `length(cscnzval) >= nnz(S)`; hence, if `nnz(S)` is
|
||
unknown at the outset, passing in empty vectors of the appropriate type (`Vector{Ti}()`
|
||
and `Vector{Tv}()` respectively) suffices, or calling the `sparse!` method
|
||
neglecting `cscrowval` and `cscnzval`.
|
||
|
||
On return, `csrrowptr`, `csrcolval`, and `csrnzval` contain an unsorted-column
|
||
representation of the result's transpose.
|
||
|
||
You may reuse the input arrays' storage (`I`, `J`, `V`) for the output arrays
|
||
(`csccolptr`, `cscrowval`, `cscnzval`). For example, you may call
|
||
`sparse!(I, J, V, csrrowptr, csrcolval, csrnzval, I, J, V)`.
|
||
|
||
For the sake of efficiency, this method performs no argument checking beyond
|
||
`1 <= I[k] <= m` and `1 <= J[k] <= n`. Use with care. Testing with `--check-bounds=yes`
|
||
is wise.
|
||
|
||
This method runs in `O(m, n, length(I))` time. The HALFPERM algorithm described in
|
||
F. Gustavson, "Two fast algorithms for sparse matrices: multiplication and permuted
|
||
transposition," ACM TOMS 4(3), 250-269 (1978) inspired this method's use of a pair of
|
||
counting sorts.
|
||
"""
|
||
function sparse!(I::AbstractVector{Ti}, J::AbstractVector{Ti},
|
||
V::AbstractVector{Tv}, m::Integer, n::Integer, combine, klasttouch::Vector{Ti},
|
||
csrrowptr::Vector{Ti}, csrcolval::Vector{Ti}, csrnzval::Vector{Tv},
|
||
csccolptr::Vector{Ti}, cscrowval::Vector{Ti}, cscnzval::Vector{Tv}) where {Tv,Ti<:Integer}
|
||
|
||
# Compute the CSR form's row counts and store them shifted forward by one in csrrowptr
|
||
fill!(csrrowptr, 0)
|
||
coolen = length(I)
|
||
@inbounds for k in 1:coolen
|
||
Ik = I[k]
|
||
if 1 > Ik || m < Ik
|
||
throw(ArgumentError("row indices I[k] must satisfy 1 <= I[k] <= m"))
|
||
end
|
||
csrrowptr[Ik+1] += 1
|
||
end
|
||
|
||
# Compute the CSR form's rowptrs and store them shifted forward by one in csrrowptr
|
||
countsum = 1
|
||
csrrowptr[1] = 1
|
||
@inbounds for i in 2:(m+1)
|
||
overwritten = csrrowptr[i]
|
||
csrrowptr[i] = countsum
|
||
countsum += overwritten
|
||
end
|
||
|
||
# Counting-sort the column and nonzero values from J and V into csrcolval and csrnzval
|
||
# Tracking write positions in csrrowptr corrects the row pointers
|
||
@inbounds for k in 1:coolen
|
||
Ik, Jk = I[k], J[k]
|
||
if 1 > Jk || n < Jk
|
||
throw(ArgumentError("column indices J[k] must satisfy 1 <= J[k] <= n"))
|
||
end
|
||
csrk = csrrowptr[Ik+1]
|
||
csrrowptr[Ik+1] = csrk+1
|
||
csrcolval[csrk] = Jk
|
||
csrnzval[csrk] = V[k]
|
||
end
|
||
# This completes the unsorted-row, has-repeats CSR form's construction
|
||
|
||
# Sweep through the CSR form, simultaneously (1) caculating the CSC form's column
|
||
# counts and storing them shifted forward by one in csccolptr; (2) detecting repeated
|
||
# entries; and (3) repacking the CSR form with the repeated entries combined.
|
||
#
|
||
# Minimizing extraneous communication and nonlocality of reference, primarily by using
|
||
# only a single auxiliary array in this step, is the key to this method's performance.
|
||
fill!(csccolptr, 0)
|
||
fill!(klasttouch, 0)
|
||
writek = 1
|
||
newcsrrowptri = 1
|
||
origcsrrowptri = 1
|
||
origcsrrowptrip1 = csrrowptr[2]
|
||
@inbounds for i in 1:m
|
||
for readk in origcsrrowptri:(origcsrrowptrip1-1)
|
||
j = csrcolval[readk]
|
||
if klasttouch[j] < newcsrrowptri
|
||
klasttouch[j] = writek
|
||
if writek != readk
|
||
csrcolval[writek] = j
|
||
csrnzval[writek] = csrnzval[readk]
|
||
end
|
||
writek += 1
|
||
csccolptr[j+1] += 1
|
||
else
|
||
klt = klasttouch[j]
|
||
csrnzval[klt] = combine(csrnzval[klt], csrnzval[readk])
|
||
end
|
||
end
|
||
newcsrrowptri = writek
|
||
origcsrrowptri = origcsrrowptrip1
|
||
origcsrrowptrip1 != writek && (csrrowptr[i+1] = writek)
|
||
i < m && (origcsrrowptrip1 = csrrowptr[i+2])
|
||
end
|
||
|
||
# Compute the CSC form's colptrs and store them shifted forward by one in csccolptr
|
||
countsum = 1
|
||
csccolptr[1] = 1
|
||
@inbounds for j in 2:(n+1)
|
||
overwritten = csccolptr[j]
|
||
csccolptr[j] = countsum
|
||
countsum += overwritten
|
||
end
|
||
|
||
# Now knowing the CSC form's entry count, resize cscrowval and cscnzval if necessary
|
||
cscnnz = countsum - 1
|
||
length(cscrowval) < cscnnz && resize!(cscrowval, cscnnz)
|
||
length(cscnzval) < cscnnz && resize!(cscnzval, cscnnz)
|
||
|
||
# Finally counting-sort the row and nonzero values from the CSR form into cscrowval and
|
||
# cscnzval. Tracking write positions in csccolptr corrects the column pointers.
|
||
@inbounds for i in 1:m
|
||
for csrk in csrrowptr[i]:(csrrowptr[i+1]-1)
|
||
j = csrcolval[csrk]
|
||
x = csrnzval[csrk]
|
||
csck = csccolptr[j+1]
|
||
csccolptr[j+1] = csck+1
|
||
cscrowval[csck] = i
|
||
cscnzval[csck] = x
|
||
end
|
||
end
|
||
|
||
SparseMatrixCSC(m, n, csccolptr, cscrowval, cscnzval)
|
||
end
|
||
function sparse!(I::AbstractVector{Ti}, J::AbstractVector{Ti},
|
||
V::AbstractVector{Tv}, m::Integer, n::Integer, combine, klasttouch::Vector{Ti},
|
||
csrrowptr::Vector{Ti}, csrcolval::Vector{Ti}, csrnzval::Vector{Tv},
|
||
csccolptr::Vector{Ti}) where {Tv,Ti<:Integer}
|
||
sparse!(I, J, V, m, n, combine, klasttouch,
|
||
csrrowptr, csrcolval, csrnzval,
|
||
csccolptr, Vector{Ti}(), Vector{Tv}())
|
||
end
|
||
function sparse!(I::AbstractVector{Ti}, J::AbstractVector{Ti},
|
||
V::AbstractVector{Tv}, m::Integer, n::Integer, combine, klasttouch::Vector{Ti},
|
||
csrrowptr::Vector{Ti}, csrcolval::Vector{Ti}, csrnzval::Vector{Tv}) where {Tv,Ti<:Integer}
|
||
sparse!(I, J, V, m, n, combine, klasttouch,
|
||
csrrowptr, csrcolval, csrnzval,
|
||
Vector{Ti}(n+1), Vector{Ti}(), Vector{Tv}())
|
||
end
|
||
|
||
dimlub(I) = isempty(I) ? 0 : Int(maximum(I)) #least upper bound on required sparse matrix dimension
|
||
|
||
sparse(I,J,v::Number) = sparse(I, J, fill(v,length(I)))
|
||
|
||
sparse(I,J,V::AbstractVector) = sparse(I, J, V, dimlub(I), dimlub(J))
|
||
|
||
sparse(I,J,v::Number,m,n) = sparse(I, J, fill(v,length(I)), Int(m), Int(n))
|
||
|
||
sparse(I,J,V::AbstractVector,m,n) = sparse(I, J, V, Int(m), Int(n), +)
|
||
|
||
sparse(I,J,V::AbstractVector{Bool},m,n) = sparse(I, J, V, Int(m), Int(n), |)
|
||
|
||
sparse(I,J,v::Number,m,n,combine::Function) = sparse(I, J, fill(v,length(I)), Int(m), Int(n), combine)
|
||
|
||
function sparse(T::SymTridiagonal)
|
||
m = length(T.dv)
|
||
return sparse([1:m;2:m;1:m-1],[1:m;1:m-1;2:m],[T.dv;T.ev;T.ev], Int(m), Int(m))
|
||
end
|
||
|
||
function sparse(T::Tridiagonal)
|
||
m = length(T.d)
|
||
return sparse([1:m;2:m;1:m-1],[1:m;1:m-1;2:m],[T.d;T.dl;T.du], Int(m), Int(m))
|
||
end
|
||
|
||
function sparse(B::Bidiagonal)
|
||
m = length(B.dv)
|
||
B.isupper || return sparse([1:m;2:m],[1:m;1:m-1],[B.dv;B.ev], Int(m), Int(m)) # lower bidiagonal
|
||
return sparse([1:m;1:m-1],[1:m;2:m],[B.dv;B.ev], Int(m), Int(m)) # upper bidiagonal
|
||
end
|
||
|
||
## Transposition and permutation methods
|
||
|
||
"""
|
||
halfperm!{Tv,Ti}(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti},
|
||
q::AbstractVector{<:Integer}, f::Function = identity)
|
||
|
||
Column-permute and transpose `A`, simultaneously applying `f` to each entry of `A`, storing
|
||
the result `(f(A)Q)^T` (`map(f, transpose(A[:,q]))`) in `X`.
|
||
|
||
`X`'s dimensions must match those of `transpose(A)` (`X.m == A.n` and `X.n == A.m`), and `X`
|
||
must have enough storage to accommodate all allocated entries in `A` (`length(X.rowval) >= nnz(A)`
|
||
and `length(X.nzval) >= nnz(A)`). Column-permutation `q`'s length must match `A`'s column
|
||
count (`length(q) == A.n`).
|
||
|
||
This method is the parent of several methods performing transposition and permutation
|
||
operations on [`SparseMatrixCSC`](@ref)s. As this method performs no argument checking,
|
||
prefer the safer child methods (`[c]transpose[!]`, `permute[!]`) to direct use.
|
||
|
||
This method implements the `HALFPERM` algorithm described in F. Gustavson, "Two fast
|
||
algorithms for sparse matrices: multiplication and permuted transposition," ACM TOMS 4(3),
|
||
250-269 (1978). The algorithm runs in `O(A.m, A.n, nnz(A))` time and requires no space
|
||
beyond that passed in.
|
||
"""
|
||
function halfperm!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti},
|
||
q::AbstractVector{<:Integer}, f::Function = identity) where {Tv,Ti}
|
||
_computecolptrs_halfperm!(X, A)
|
||
_distributevals_halfperm!(X, A, q, f)
|
||
return X
|
||
end
|
||
"""
|
||
Helper method for `halfperm!`. Computes `transpose(A[:,q])`'s column pointers, storing them
|
||
shifted one position forward in `X.colptr`; `_distributevals_halfperm!` fixes this shift.
|
||
"""
|
||
function _computecolptrs_halfperm!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti}
|
||
# Compute `transpose(A[:,q])`'s column counts. Store shifted forward one position in X.colptr.
|
||
fill!(X.colptr, 0)
|
||
@inbounds for k in 1:nnz(A)
|
||
X.colptr[A.rowval[k] + 1] += 1
|
||
end
|
||
# Compute `transpose(A[:,q])`'s column pointers. Store shifted forward one position in X.colptr.
|
||
X.colptr[1] = 1
|
||
countsum = 1
|
||
@inbounds for k in 2:(A.m + 1)
|
||
overwritten = X.colptr[k]
|
||
X.colptr[k] = countsum
|
||
countsum += overwritten
|
||
end
|
||
end
|
||
"""
|
||
Helper method for `halfperm!`. With `transpose(A[:,q])`'s column pointers shifted one
|
||
position forward in `X.colptr`, computes `map(f, transpose(A[:,q]))` by appropriately
|
||
distributing `A.rowval` and `f`-transformed `A.nzval` into `X.rowval` and `X.nzval`
|
||
respectively. Simultaneously fixes the one-position-forward shift in `X.colptr`.
|
||
"""
|
||
function _distributevals_halfperm!(X::SparseMatrixCSC{Tv,Ti},
|
||
A::SparseMatrixCSC{Tv,Ti}, q::AbstractVector{<:Integer}, f::Function) where {Tv,Ti}
|
||
@inbounds for Xi in 1:A.n
|
||
Aj = q[Xi]
|
||
for Ak in nzrange(A, Aj)
|
||
Ai = A.rowval[Ak]
|
||
Xk = X.colptr[Ai + 1]
|
||
X.rowval[Xk] = Xi
|
||
X.nzval[Xk] = f(A.nzval[Ak])
|
||
X.colptr[Ai + 1] += 1
|
||
end
|
||
end
|
||
return # kill potential type instability
|
||
end
|
||
|
||
function ftranspose!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}, f::Function) where {Tv,Ti}
|
||
# Check compatibility of source argument A and destination argument X
|
||
if X.n != A.m
|
||
throw(DimensionMismatch(string("destination argument `X`'s column count, ",
|
||
"`X.n (= $(X.n))`, must match source argument `A`'s row count, `A.m (= $(A.m))`")))
|
||
elseif X.m != A.n
|
||
throw(DimensionMismatch(string("destination argument `X`'s row count,
|
||
`X.m (= $(X.m))`, must match source argument `A`'s column count, `A.n (= $(A.n))`")))
|
||
elseif length(X.rowval) < nnz(A)
|
||
throw(ArgumentError(string("the length of destination argument `X`'s `rowval` ",
|
||
"array, `length(X.rowval) (= $(length(X.rowval)))`, must be greater than or ",
|
||
"equal to source argument `A`'s allocated entry count, `nnz(A) (= $(nnz(A)))`")))
|
||
elseif length(X.nzval) < nnz(A)
|
||
throw(ArgumentError(string("the length of destination argument `X`'s `nzval` ",
|
||
"array, `length(X.nzval) (= $(length(X.nzval)))`, must be greater than or ",
|
||
"equal to source argument `A`'s allocated entry count, `nnz(A) (= $(nnz(A)))`")))
|
||
end
|
||
halfperm!(X, A, 1:A.n, f)
|
||
end
|
||
transpose!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, identity)
|
||
ctranspose!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, conj)
|
||
|
||
function ftranspose(A::SparseMatrixCSC{Tv,Ti}, f::Function) where {Tv,Ti}
|
||
X = SparseMatrixCSC(A.n, A.m, Vector{Ti}(A.m+1), Vector{Ti}(nnz(A)), Vector{Tv}(nnz(A)))
|
||
halfperm!(X, A, 1:A.n, f)
|
||
end
|
||
transpose(A::SparseMatrixCSC) = ftranspose(A, identity)
|
||
ctranspose(A::SparseMatrixCSC) = ftranspose(A, conj)
|
||
|
||
"""
|
||
unchecked_noalias_permute!{Tv,Ti}(X::SparseMatrixCSC{Tv,Ti},
|
||
A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer}, C::SparseMatrixCSC{Tv,Ti})
|
||
|
||
See [`permute!`](@ref) for basic usage. Parent of `permute[!]`
|
||
methods operating on `SparseMatrixCSC`s that assume none of `X`, `A`, and `C` alias each
|
||
other. As this method performs no argument checking, prefer the safer child methods
|
||
(`permute[!]`) to direct use.
|
||
|
||
This method consists of two major steps: (1) Column-permute (`Q`,`I[:,q]`) and transpose `A`
|
||
to generate intermediate result `(AQ)^T` (`transpose(A[:,q])`) in `C`. (2) Column-permute
|
||
(`P^T`, I[:,p]) and transpose intermediate result `(AQ)^T` to generate result
|
||
`((AQ)^T P^T)^T = PAQ` (`A[p,q]`) in `X`.
|
||
|
||
The first step is a call to `halfperm!`, and the second is a variant on `halfperm!` that
|
||
avoids an unnecessary length-`nnz(A)` array-sweep and associated recomputation of column
|
||
pointers. See [`halfperm!`](:func:Base.SparseArrays.halfperm!) for additional algorithmic
|
||
information.
|
||
|
||
See also: `unchecked_aliasing_permute!`
|
||
"""
|
||
function unchecked_noalias_permute!{Tv,Ti}(X::SparseMatrixCSC{Tv,Ti},
|
||
A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer}, C::SparseMatrixCSC{Tv,Ti})
|
||
halfperm!(C, A, q)
|
||
_computecolptrs_permute!(X, A, q, X.colptr)
|
||
_distributevals_halfperm!(X, C, p, identity)
|
||
return X
|
||
end
|
||
"""
|
||
unchecked_aliasing_permute!{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti},
|
||
p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer},
|
||
C::SparseMatrixCSC{Tv,Ti}, workcolptr::Vector{Ti})
|
||
|
||
See [`permute!`](@ref) for basic usage. Parent of `permute!`
|
||
methods operating on [`SparseMatrixCSC`](@ref)s where the source and destination matrices
|
||
are the same. See `unchecked_noalias_permute!`
|
||
for additional information; these methods are identical but for this method's requirement of
|
||
the additional `workcolptr`, `length(workcolptr) >= A.n + 1`, which enables efficient
|
||
handling of the source-destination aliasing.
|
||
"""
|
||
function unchecked_aliasing_permute!{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti},
|
||
p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer},
|
||
C::SparseMatrixCSC{Tv,Ti}, workcolptr::Vector{Ti})
|
||
halfperm!(C, A, q)
|
||
_computecolptrs_permute!(A, A, q, workcolptr)
|
||
_distributevals_halfperm!(A, C, p, identity)
|
||
return A
|
||
end
|
||
"""
|
||
Helper method for `unchecked_noalias_permute!` and `unchecked_aliasing_permute!`.
|
||
Computes `PAQ`'s column pointers, storing them shifted one position forward in `X.colptr`;
|
||
`_distributevals_halfperm!` fixes this shift. Saves some work relative to
|
||
`_computecolptrs_halfperm!` as described in `uncheckednoalias_permute!`'s documentation.
|
||
"""
|
||
function _computecolptrs_permute!{Tv,Ti}(X::SparseMatrixCSC{Tv,Ti},
|
||
A::SparseMatrixCSC{Tv,Ti}, q::AbstractVector{<:Integer}, workcolptr::Vector{Ti})
|
||
# Compute `A[p,q]`'s column counts. Store shifted forward one position in workcolptr.
|
||
@inbounds for k in 1:A.n
|
||
workcolptr[k+1] = A.colptr[q[k] + 1] - A.colptr[q[k]]
|
||
end
|
||
# Compute `A[p,q]`'s column pointers. Store shifted forward one position in X.colptr.
|
||
X.colptr[1] = 1
|
||
countsum = 1
|
||
@inbounds for k in 2:(X.n + 1)
|
||
overwritten = workcolptr[k]
|
||
X.colptr[k] = countsum
|
||
countsum += overwritten
|
||
end
|
||
end
|
||
|
||
"""
|
||
Helper method for `permute` and `permute!` methods operating on `SparseMatrixCSC`s.
|
||
Checks compatibility of source argument `A`, row-permutation argument `p`, and
|
||
column-permutation argument `q`.
|
||
"""
|
||
function _checkargs_sourcecompatperms_permute!(A::SparseMatrixCSC,
|
||
p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer})
|
||
if length(q) != A.n
|
||
throw(DimensionMismatch(string("the length of column-permutation argument `q`, ",
|
||
"`length(q) (= $(length(q)))`, must match source argument `A`'s column ",
|
||
"count, `A.n (= $(A.n))`")))
|
||
elseif length(p) != A.m
|
||
throw(DimensionMismatch(string("the length of row-permutation argument `p`, ",
|
||
"`length(p) (= $(length(p)))`, must match source argument `A`'s row count, ",
|
||
"`A.m (= $(A.m))`")))
|
||
end
|
||
end
|
||
"""
|
||
Helper method for `permute` and `permute!` methods operating on `SparseMatrixCSC`s.
|
||
Checks whether row- and column- permutation arguments `p` and `q` are valid permutations.
|
||
"""
|
||
function _checkargs_permutationsvalid_permute!{Ti<:Integer}(
|
||
p::AbstractVector{<:Integer}, pcheckspace::Vector{Ti},
|
||
q::AbstractVector{<:Integer}, qcheckspace::Vector{Ti})
|
||
if !_ispermutationvalid_permute!(p, pcheckspace)
|
||
throw(ArgumentError("row-permutation argument `p` must be a valid permutation"))
|
||
elseif !_ispermutationvalid_permute!(q, qcheckspace)
|
||
throw(ArgumentError("column-permutation argument `q` must be a valid permutation"))
|
||
end
|
||
end
|
||
function _ispermutationvalid_permute!(perm::AbstractVector{<:Integer},
|
||
checkspace::Vector{<:Integer})
|
||
n = length(perm)
|
||
checkspace[1:n] = 0
|
||
for k in perm
|
||
(0 < k ≤ n) && ((checkspace[k] ⊻= 1) == 1) || return false
|
||
end
|
||
return true
|
||
end
|
||
"""
|
||
Helper method for `permute` and `permute!` methods operating on `SparseMatrixCSC`s.
|
||
Checks compatibility of source argument `A` and destination argument `X`.
|
||
"""
|
||
function _checkargs_sourcecompatdest_permute!(A::SparseMatrixCSC{Tv,Ti},
|
||
X::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti}
|
||
if X.m != A.m
|
||
throw(DimensionMismatch(string("destination argument `X`'s row count, ",
|
||
"`X.m (= $(X.m))`, must match source argument `A`'s row count, `A.m (= $(A.m))`")))
|
||
elseif X.n != A.n
|
||
throw(DimensionMismatch(string("destination argument `X`'s column count, ",
|
||
"`X.n (= $(X.n))`, must match source argument `A`'s column count, `A.n (= $(A.n))`")))
|
||
elseif length(X.rowval) < nnz(A)
|
||
throw(ArgumentError(string("the length of destination argument `X`'s `rowval` ",
|
||
"array, `length(X.rowval) (= $(length(X.rowval)))`, must be greater than or ",
|
||
"equal to source argument `A`'s allocated entry count, `nnz(A) (= $(nnz(A)))`")))
|
||
elseif length(X.nzval) < nnz(A)
|
||
throw(ArgumentError(string("the length of destination argument `X`'s `nzval` ",
|
||
"array, `length(X.nzval) (= $(length(X.nzval)))`, must be greater than or ",
|
||
"equal to source argument `A`'s allocated entry count, `nnz(A) (= $(nnz(A)))`")))
|
||
end
|
||
end
|
||
"""
|
||
Helper method for `permute` and `permute!` methods operating on `SparseMatrixCSC`s.
|
||
Checks compatibility of source argument `A` and intermediate result argument `C`.
|
||
"""
|
||
function _checkargs_sourcecompatworkmat_permute!(A::SparseMatrixCSC{Tv,Ti},
|
||
C::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti}
|
||
if C.n != A.m
|
||
throw(DimensionMismatch(string("intermediate result argument `C`'s column count, ",
|
||
"`C.n (= $(C.n))`, must match source argument `A`'s row count, `A.m (= $(A.m))`")))
|
||
elseif C.m != A.n
|
||
throw(DimensionMismatch(string("intermediate result argument `C`'s row count, ",
|
||
"`C.m (= $(C.m))`, must match source argument `A`'s column count, `A.n (= $(A.n))`")))
|
||
elseif length(C.rowval) < nnz(A)
|
||
throw(ArgumentError(string("the length of intermediate result argument `C`'s ",
|
||
"`rowval` array, `length(C.rowval) (= $(length(C.rowval)))`, must be greater than ",
|
||
"or equal to source argument `A`'s allocated entry count, `nnz(A) (= $(nnz(A)))`")))
|
||
elseif length(C.nzval) < nnz(A)
|
||
throw(ArgumentError(string("the length of intermediate result argument `C`'s ",
|
||
"`rowval` array, `length(C.nzval) (= $(length(C.nzval)))`, must be greater than ",
|
||
"or equal to source argument `A`'s allocated entry count, `nnz(A)` (= $(nnz(A)))")))
|
||
end
|
||
end
|
||
"""
|
||
Helper method for `permute` and `permute!` methods operating on `SparseMatrixCSC`s.
|
||
Checks compatibility of source argument `A` and workspace argument `workcolptr`.
|
||
"""
|
||
function _checkargs_sourcecompatworkcolptr_permute!(A::SparseMatrixCSC{Tv,Ti},
|
||
workcolptr::Vector{Ti}) where {Tv,Ti}
|
||
if length(workcolptr) <= A.n
|
||
throw(DimensionMismatch(string("argument `workcolptr`'s length, ",
|
||
"`length(workcolptr) (= $(length(workcolptr)))`, must exceed source argument ",
|
||
"`A`'s column count, `A.n (= $(A.n))`")))
|
||
end
|
||
end
|
||
"""
|
||
permute!{Tv,Ti}(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti},
|
||
p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer}[, C::SparseMatrixCSC{Tv,Ti}])
|
||
|
||
Bilaterally permute `A`, storing result `PAQ` (`A[p,q]`) in `X`. Stores intermediate result
|
||
`(AQ)^T` (`transpose(A[:,q])`) in optional argument `C` if present. Requires that none of
|
||
`X`, `A`, and, if present, `C` alias each other; to store result `PAQ` back into `A`, use
|
||
the following method lacking `X`:
|
||
|
||
permute!{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer}[, C::SparseMatrixCSC{Tv,Ti}[, workcolptr::Vector{Ti}]])
|
||
|
||
`X`'s dimensions must match those of `A` (`X.m == A.m` and `X.n == A.n`), and `X` must
|
||
have enough storage to accommodate all allocated entries in `A` (`length(X.rowval) >= nnz(A)`
|
||
and `length(X.nzval) >= nnz(A)`). Column-permutation `q`'s length must match `A`'s column
|
||
count (`length(q) == A.n`). Row-permutation `p`'s length must match `A`'s row count
|
||
(`length(p) == A.m`).
|
||
|
||
`C`'s dimensions must match those of `transpose(A)` (`C.m == A.n` and `C.n == A.m`), and `C`
|
||
must have enough storage to accommodate all allocated entries in `A` (`length(C.rowval) >= nnz(A)`
|
||
and `length(C.nzval) >= nnz(A)`).
|
||
|
||
For additional (algorithmic) information, and for versions of these methods that forgo
|
||
argument checking, see (unexported) parent methods `unchecked_noalias_permute!`
|
||
and `unchecked_aliasing_permute!`.
|
||
|
||
See also: [`permute`](@ref).
|
||
"""
|
||
function permute!{Tv,Ti}(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti},
|
||
p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer})
|
||
_checkargs_sourcecompatdest_permute!(A, X)
|
||
_checkargs_sourcecompatperms_permute!(A, p, q)
|
||
C = SparseMatrixCSC(A.n, A.m, Vector{Ti}(A.m + 1), Vector{Ti}(nnz(A)), Vector{Tv}(nnz(A)))
|
||
_checkargs_permutationsvalid_permute!(p, C.colptr, q, X.colptr)
|
||
unchecked_noalias_permute!(X, A, p, q, C)
|
||
end
|
||
function permute!{Tv,Ti}(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti},
|
||
p::AbstractVector{<:Integer}, q::AbstractVector{<:Integer},
|
||
C::SparseMatrixCSC{Tv,Ti})
|
||
_checkargs_sourcecompatdest_permute!(A, X)
|
||
_checkargs_sourcecompatperms_permute!(A, p, q)
|
||
_checkargs_sourcecompatworkmat_permute!(A, C)
|
||
_checkargs_permutationsvalid_permute!(p, C.colptr, q, X.colptr)
|
||
unchecked_noalias_permute!(X, A, p, q, C)
|
||
end
|
||
function permute!{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer})
|
||
_checkargs_sourcecompatperms_permute!(A, p, q)
|
||
C = SparseMatrixCSC(A.n, A.m, Vector{Ti}(A.m + 1), Vector{Ti}(nnz(A)), Vector{Tv}(nnz(A)))
|
||
workcolptr = Vector{Ti}(A.n + 1)
|
||
_checkargs_permutationsvalid_permute!(p, C.colptr, q, workcolptr)
|
||
unchecked_aliasing_permute!(A, p, q, C, workcolptr)
|
||
end
|
||
function permute!{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer}, C::SparseMatrixCSC{Tv,Ti})
|
||
_checkargs_sourcecompatperms_permute!(A, p, q)
|
||
_checkargs_sourcecompatworkmat_permute!(A, C)
|
||
workcolptr = Vector{Ti}(A.n + 1)
|
||
_checkargs_permutationsvalid_permute!(p, C.colptr, q, workcolptr)
|
||
unchecked_aliasing_permute!(A, p, q, C, workcolptr)
|
||
end
|
||
function permute!{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer}, C::SparseMatrixCSC{Tv,Ti},
|
||
workcolptr::Vector{Ti})
|
||
_checkargs_sourcecompatperms_permute!(A, p, q)
|
||
_checkargs_sourcecompatworkmat_permute!(A, C)
|
||
_checkargs_sourcecompatworkcolptr_permute!(A, workcolptr)
|
||
_checkargs_permutationsvalid_permute!(p, C.colptr, q, workcolptr)
|
||
unchecked_aliasing_permute!(A, p, q, C, workcolptr)
|
||
end
|
||
"""
|
||
permute{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer})
|
||
|
||
Bilaterally permute `A`, returning `PAQ` (`A[p,q]`). Column-permutation `q`'s length must
|
||
match `A`'s column count (`length(q) == A.n`). Row-permutation `p`'s length must match `A`'s
|
||
row count (`length(p) == A.m`).
|
||
|
||
For expert drivers and additional information, see [`permute!`](@ref).
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = spdiagm([1, 2, 3, 4], 0, 4, 4) + spdiagm([5, 6, 7], 1, 4, 4)
|
||
4×4 SparseMatrixCSC{Int64,Int64} with 7 stored entries:
|
||
[1, 1] = 1
|
||
[1, 2] = 5
|
||
[2, 2] = 2
|
||
[2, 3] = 6
|
||
[3, 3] = 3
|
||
[3, 4] = 7
|
||
[4, 4] = 4
|
||
|
||
julia> permute(A, [4, 3, 2, 1], [1, 2, 3, 4])
|
||
4×4 SparseMatrixCSC{Int64,Int64} with 7 stored entries:
|
||
[4, 1] = 1
|
||
[3, 2] = 2
|
||
[4, 2] = 5
|
||
[2, 3] = 3
|
||
[3, 3] = 6
|
||
[1, 4] = 4
|
||
[2, 4] = 7
|
||
|
||
julia> permute(A, [1, 2, 3, 4], [4, 3, 2, 1])
|
||
4×4 SparseMatrixCSC{Int64,Int64} with 7 stored entries:
|
||
[3, 1] = 7
|
||
[4, 1] = 4
|
||
[2, 2] = 6
|
||
[3, 2] = 3
|
||
[1, 3] = 5
|
||
[2, 3] = 2
|
||
[1, 4] = 1
|
||
```
|
||
"""
|
||
function permute{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, p::AbstractVector{<:Integer},
|
||
q::AbstractVector{<:Integer})
|
||
_checkargs_sourcecompatperms_permute!(A, p, q)
|
||
X = SparseMatrixCSC(A.m, A.n, Vector{Ti}(A.n + 1), Vector{Ti}(nnz(A)), Vector{Tv}(nnz(A)))
|
||
C = SparseMatrixCSC(A.n, A.m, Vector{Ti}(A.m + 1), Vector{Ti}(nnz(A)), Vector{Tv}(nnz(A)))
|
||
_checkargs_permutationsvalid_permute!(p, C.colptr, q, X.colptr)
|
||
unchecked_noalias_permute!(X, A, p, q, C)
|
||
end
|
||
|
||
## fkeep! and children tril!, triu!, droptol!, dropzeros[!]
|
||
|
||
"""
|
||
fkeep!(A::AbstractSparseArray, f, trim::Bool = true)
|
||
|
||
Keep elements of `A` for which test `f` returns `true`. `f`'s signature should be
|
||
|
||
f(i::Integer, [j::Integer,] x) -> Bool
|
||
|
||
where `i` and `j` are an element's row and column indices and `x` is the element's
|
||
value. This method makes a single sweep
|
||
through `A`, requiring `O(A.n, nnz(A))`-time for matrices and `O(nnz(A))`-time for vectors
|
||
and no space beyond that passed in. If `trim` is `true`, this method trims `A.rowval` or `A.nzind` and
|
||
`A.nzval` to length `nnz(A)` after dropping elements.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = spdiagm([1, 2, 3, 4])
|
||
4×4 SparseMatrixCSC{Int64,Int64} with 4 stored entries:
|
||
[1, 1] = 1
|
||
[2, 2] = 2
|
||
[3, 3] = 3
|
||
[4, 4] = 4
|
||
|
||
julia> Base.SparseArrays.fkeep!(A, (i, j, v) -> isodd(v))
|
||
4×4 SparseMatrixCSC{Int64,Int64} with 2 stored entries:
|
||
[1, 1] = 1
|
||
[3, 3] = 3
|
||
```
|
||
"""
|
||
function fkeep!(A::SparseMatrixCSC, f, trim::Bool = true)
|
||
An = A.n
|
||
Acolptr = A.colptr
|
||
Arowval = A.rowval
|
||
Anzval = A.nzval
|
||
|
||
# Sweep through columns, rewriting kept elements in their new positions
|
||
# and updating the column pointers accordingly as we go.
|
||
Awritepos = 1
|
||
oldAcolptrAj = 1
|
||
@inbounds for Aj in 1:An
|
||
for Ak in oldAcolptrAj:(Acolptr[Aj+1]-1)
|
||
Ai = Arowval[Ak]
|
||
Ax = Anzval[Ak]
|
||
# If this element should be kept, rewrite in new position
|
||
if f(Ai, Aj, Ax)
|
||
if Awritepos != Ak
|
||
Arowval[Awritepos] = Ai
|
||
Anzval[Awritepos] = Ax
|
||
end
|
||
Awritepos += 1
|
||
end
|
||
end
|
||
oldAcolptrAj = Acolptr[Aj+1]
|
||
Acolptr[Aj+1] = Awritepos
|
||
end
|
||
|
||
# Trim A's storage if necessary and desired
|
||
if trim
|
||
Annz = Acolptr[end] - 1
|
||
if length(Arowval) != Annz
|
||
resize!(Arowval, Annz)
|
||
end
|
||
if length(Anzval) != Annz
|
||
resize!(Anzval, Annz)
|
||
end
|
||
end
|
||
|
||
A
|
||
end
|
||
|
||
function tril!(A::SparseMatrixCSC, k::Integer = 0, trim::Bool = true)
|
||
if k > A.n-1 || k < 1-A.m
|
||
throw(ArgumentError("requested diagonal, $k, out of bounds in matrix of size ($(A.m),$(A.n))"))
|
||
end
|
||
fkeep!(A, (i, j, x) -> i + k >= j, trim)
|
||
end
|
||
function triu!(A::SparseMatrixCSC, k::Integer = 0, trim::Bool = true)
|
||
if k > A.n-1 || k < 1-A.m
|
||
throw(ArgumentError("requested diagonal, $k, out of bounds in matrix of size ($(A.m),$(A.n))"))
|
||
end
|
||
fkeep!(A, (i, j, x) -> j >= i + k, trim)
|
||
end
|
||
|
||
droptol!(A::SparseMatrixCSC, tol, trim::Bool = true) =
|
||
fkeep!(A, (i, j, x) -> abs(x) > tol, trim)
|
||
|
||
"""
|
||
dropzeros!(A::SparseMatrixCSC, trim::Bool = true)
|
||
|
||
Removes stored numerical zeros from `A`, optionally trimming resulting excess space from
|
||
`A.rowval` and `A.nzval` when `trim` is `true`.
|
||
|
||
For an out-of-place version, see [`dropzeros`](@ref). For
|
||
algorithmic information, see `fkeep!`.
|
||
"""
|
||
dropzeros!(A::SparseMatrixCSC, trim::Bool = true) = fkeep!(A, (i, j, x) -> x != 0, trim)
|
||
"""
|
||
dropzeros(A::SparseMatrixCSC, trim::Bool = true)
|
||
|
||
Generates a copy of `A` and removes stored numerical zeros from that copy, optionally
|
||
trimming excess space from the result's `rowval` and `nzval` arrays when `trim` is `true`.
|
||
|
||
For an in-place version and algorithmic information, see [`dropzeros!`](@ref).
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = sparse([1, 2, 3], [1, 2, 3], [1.0, 0.0, 1.0])
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 0.0
|
||
[3, 3] = 1.0
|
||
|
||
julia> dropzeros(A)
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 2 stored entries:
|
||
[1, 1] = 1.0
|
||
[3, 3] = 1.0
|
||
```
|
||
"""
|
||
dropzeros(A::SparseMatrixCSC, trim::Bool = true) = dropzeros!(copy(A), trim)
|
||
|
||
|
||
## Find methods
|
||
|
||
function find(S::SparseMatrixCSC)
|
||
sz = size(S)
|
||
I, J = findn(S)
|
||
return sub2ind(sz, I, J)
|
||
end
|
||
|
||
function findn(S::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti}
|
||
numnz = nnz(S)
|
||
I = Vector{Ti}(numnz)
|
||
J = Vector{Ti}(numnz)
|
||
|
||
count = 1
|
||
@inbounds for col = 1 : S.n, k = S.colptr[col] : (S.colptr[col+1]-1)
|
||
if S.nzval[k] != 0
|
||
I[count] = S.rowval[k]
|
||
J[count] = col
|
||
count += 1
|
||
end
|
||
end
|
||
|
||
count -= 1
|
||
if numnz != count
|
||
deleteat!(I, (count+1):numnz)
|
||
deleteat!(J, (count+1):numnz)
|
||
end
|
||
|
||
return (I, J)
|
||
end
|
||
|
||
function findnz(S::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti}
|
||
numnz = nnz(S)
|
||
I = Vector{Ti}(numnz)
|
||
J = Vector{Ti}(numnz)
|
||
V = Vector{Tv}(numnz)
|
||
|
||
count = 1
|
||
@inbounds for col = 1 : S.n, k = S.colptr[col] : (S.colptr[col+1]-1)
|
||
if S.nzval[k] != 0
|
||
I[count] = S.rowval[k]
|
||
J[count] = col
|
||
V[count] = S.nzval[k]
|
||
count += 1
|
||
end
|
||
end
|
||
|
||
count -= 1
|
||
if numnz != count
|
||
deleteat!(I, (count+1):numnz)
|
||
deleteat!(J, (count+1):numnz)
|
||
deleteat!(V, (count+1):numnz)
|
||
end
|
||
|
||
return (I, J, V)
|
||
end
|
||
|
||
|
||
import Base.Random.GLOBAL_RNG
|
||
function sprand_IJ(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat)
|
||
((m < 0) || (n < 0)) && throw(ArgumentError("invalid Array dimensions"))
|
||
0 <= density <= 1 || throw(ArgumentError("$density not in [0,1]"))
|
||
N = n*m
|
||
|
||
I, J = Vector{Int}(0), Vector{Int}(0) # indices of nonzero elements
|
||
sizehint!(I, round(Int,N*density))
|
||
sizehint!(J, round(Int,N*density))
|
||
|
||
# density of nonzero columns:
|
||
L = log1p(-density)
|
||
coldensity = -expm1(m*L) # = 1 - (1-density)^m
|
||
colsparsity = exp(m*L) # = 1 - coldensity
|
||
iL = 1/L
|
||
|
||
rows = Vector{Int}(0)
|
||
for j in randsubseq(r, 1:n, coldensity)
|
||
# To get the right statistics, we *must* have a nonempty column j
|
||
# even if p*m << 1. To do this, we use an approach similar to
|
||
# the one in randsubseq to compute the expected first nonzero row k,
|
||
# except given that at least one is nonzero (via Bayes' rule);
|
||
# carefully rearranged to avoid excessive roundoff errors.
|
||
k = ceil(log(colsparsity + rand(r)*coldensity) * iL)
|
||
ik = k < 1 ? 1 : k > m ? m : Int(k) # roundoff-error/underflow paranoia
|
||
randsubseq!(r, rows, 1:m-ik, density)
|
||
push!(rows, m-ik+1)
|
||
append!(I, rows)
|
||
nrows = length(rows)
|
||
Jlen = length(J)
|
||
resize!(J, Jlen+nrows)
|
||
@inbounds for i = Jlen+1:length(J)
|
||
J[i] = j
|
||
end
|
||
end
|
||
I, J
|
||
end
|
||
|
||
"""
|
||
sprand([rng],[type],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` and have the type `type`. The uniform
|
||
distribution is used in case `rfn` is not specified. The optional `rng`
|
||
argument specifies a random number generator, see [Random Numbers](@ref).
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> rng = MersenneTwister(1234);
|
||
|
||
julia> sprand(rng, Bool, 2, 2, 0.5)
|
||
2×2 SparseMatrixCSC{Bool,Int64} with 2 stored entries:
|
||
[1, 1] = true
|
||
[2, 1] = true
|
||
|
||
julia> sprand(rng, Float64, 3, 0.75)
|
||
3-element SparseVector{Float64,Int64} with 1 stored entry:
|
||
[3] = 0.298614
|
||
```
|
||
"""
|
||
function sprand{T}(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat,
|
||
rfn::Function, ::Type{T}=eltype(rfn(r,1)))
|
||
N = m*n
|
||
N == 0 && return spzeros(T,m,n)
|
||
N == 1 && return rand(r) <= density ? sparse([1], [1], rfn(r,1)) : spzeros(T,1,1)
|
||
|
||
I,J = sprand_IJ(r, m, n, density)
|
||
sparse_IJ_sorted!(I, J, rfn(r,length(I)), m, n, +) # it will never need to combine
|
||
end
|
||
|
||
function sprand{T}(m::Integer, n::Integer, density::AbstractFloat,
|
||
rfn::Function, ::Type{T}=eltype(rfn(1)))
|
||
N = m*n
|
||
N == 0 && return spzeros(T,m,n)
|
||
N == 1 && return rand() <= density ? sparse([1], [1], rfn(1)) : spzeros(T,1,1)
|
||
|
||
I,J = sprand_IJ(GLOBAL_RNG, m, n, density)
|
||
sparse_IJ_sorted!(I, J, rfn(length(I)), m, n, +) # it will never need to combine
|
||
end
|
||
|
||
truebools(r::AbstractRNG, n::Integer) = ones(Bool, n)
|
||
|
||
sprand(m::Integer, n::Integer, density::AbstractFloat) = sprand(GLOBAL_RNG,m,n,density)
|
||
|
||
sprand(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat) = sprand(r,m,n,density,rand,Float64)
|
||
sprand(r::AbstractRNG, ::Type{T}, m::Integer, n::Integer, density::AbstractFloat) where {T} = sprand(r,m,n,density,(r, i) -> rand(r, T, i), T)
|
||
sprand(r::AbstractRNG, ::Type{Bool}, m::Integer, n::Integer, density::AbstractFloat) = sprand(r,m,n,density, truebools, Bool)
|
||
sprand(::Type{T}, m::Integer, n::Integer, density::AbstractFloat) where {T} = sprand(GLOBAL_RNG, T, m, n, density)
|
||
|
||
|
||
"""
|
||
sprandn([rng], m[,n],p::AbstractFloat)
|
||
|
||
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. The optional `rng`
|
||
argument specifies a random number generator, see [Random Numbers](@ref).
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> rng = MersenneTwister(1234);
|
||
|
||
julia> sprandn(rng, 2, 2, 0.75)
|
||
2×2 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
|
||
[1, 1] = 0.532813
|
||
[2, 1] = -0.271735
|
||
[2, 2] = 0.502334
|
||
```
|
||
"""
|
||
sprandn(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat) = sprand(r,m,n,density,randn,Float64)
|
||
sprandn(m::Integer, n::Integer, density::AbstractFloat) = sprandn(GLOBAL_RNG,m,n,density)
|
||
|
||
|
||
"""
|
||
spones(S)
|
||
|
||
Create a sparse array with the same structure as that of `S`, but with every nonzero
|
||
element having the value `1.0`.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = sparse([1,2,3,4],[2,4,3,1],[5.,4.,3.,2.])
|
||
4×4 SparseMatrixCSC{Float64,Int64} with 4 stored entries:
|
||
[4, 1] = 2.0
|
||
[1, 2] = 5.0
|
||
[3, 3] = 3.0
|
||
[2, 4] = 4.0
|
||
|
||
julia> spones(A)
|
||
4×4 SparseMatrixCSC{Float64,Int64} with 4 stored entries:
|
||
[4, 1] = 1.0
|
||
[1, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
[2, 4] = 1.0
|
||
```
|
||
|
||
Note the difference from [`speye`](@ref).
|
||
"""
|
||
spones(S::SparseMatrixCSC{T}) where {T} =
|
||
SparseMatrixCSC(S.m, S.n, copy(S.colptr), copy(S.rowval), ones(T, S.colptr[end]-1))
|
||
|
||
"""
|
||
spzeros([type,]m[,n])
|
||
|
||
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. The type defaults to [`Float64`](@ref) if not
|
||
specified.
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> spzeros(3, 3)
|
||
3×3 SparseMatrixCSC{Float64,Int64} with 0 stored entries
|
||
|
||
julia> spzeros(Float32, 4)
|
||
4-element SparseVector{Float32,Int64} with 0 stored entries
|
||
```
|
||
"""
|
||
spzeros(m::Integer, n::Integer) = spzeros(Float64, m, n)
|
||
spzeros(::Type{Tv}, m::Integer, n::Integer) where {Tv} = spzeros(Tv, Int, m, n)
|
||
function spzeros(::Type{Tv}, ::Type{Ti}, m::Integer, n::Integer) where {Tv, Ti}
|
||
((m < 0) || (n < 0)) && throw(ArgumentError("invalid Array dimensions"))
|
||
SparseMatrixCSC(m, n, ones(Ti, n+1), Vector{Ti}(0), Vector{Tv}(0))
|
||
end
|
||
# de-splatting variant
|
||
function spzeros(::Type{Tv}, ::Type{Ti}, sz::Tuple{Integer,Integer}) where {Tv, Ti}
|
||
spzeros(Tv, Ti, sz[1], sz[2])
|
||
end
|
||
|
||
speye(n::Integer) = speye(Float64, n)
|
||
speye(::Type{T}, n::Integer) where {T} = speye(T, n, n)
|
||
speye(m::Integer, n::Integer) = speye(Float64, m, n)
|
||
|
||
"""
|
||
speye(S)
|
||
|
||
Create a sparse identity matrix with the same size as `S`.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = sparse([1,2,3,4],[2,4,3,1],[5.,4.,3.,2.])
|
||
4×4 SparseMatrixCSC{Float64,Int64} with 4 stored entries:
|
||
[4, 1] = 2.0
|
||
[1, 2] = 5.0
|
||
[3, 3] = 3.0
|
||
[2, 4] = 4.0
|
||
|
||
julia> speye(A)
|
||
4×4 SparseMatrixCSC{Float64,Int64} with 4 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
[4, 4] = 1.0
|
||
```
|
||
|
||
Note the difference from [`spones`](@ref).
|
||
"""
|
||
speye(S::SparseMatrixCSC{T}) where {T} = speye(T, size(S, 1), size(S, 2))
|
||
eye(S::SparseMatrixCSC) = speye(S)
|
||
|
||
"""
|
||
speye([type,]m[,n])
|
||
|
||
Create a sparse identity matrix of size `m x m`. When `n` is supplied,
|
||
create a sparse identity matrix of size `m x n`. The type defaults to [`Float64`](@ref)
|
||
if not specified.
|
||
|
||
`sparse(I, m, n)` is equivalent to `speye(Int, m, n)`, and
|
||
`sparse(α*I, m, n)` can be used to efficiently create a sparse
|
||
multiple `α` of the identity matrix.
|
||
"""
|
||
speye(::Type{T}, m::Integer, n::Integer) where {T} = speye_scaled(T, oneunit(T), m, n)
|
||
|
||
function one(S::SparseMatrixCSC{T}) where T
|
||
m,n = size(S)
|
||
if m != n; throw(DimensionMismatch("multiplicative identity only defined for square matrices")); end
|
||
speye(T, m)
|
||
end
|
||
|
||
speye_scaled(diag, m::Integer, n::Integer) = speye_scaled(typeof(diag), diag, m, n)
|
||
|
||
function speye_scaled(::Type{T}, diag, m::Integer, n::Integer) where T
|
||
((m < 0) || (n < 0)) && throw(ArgumentError("invalid array dimensions"))
|
||
nnz = min(m,n)
|
||
colptr = Vector{Int}(1+n)
|
||
colptr[1:nnz+1] = 1:nnz+1
|
||
colptr[nnz+2:end] = nnz+1
|
||
SparseMatrixCSC(Int(m), Int(n), colptr, Vector{Int}(1:nnz), fill!(Vector{T}(nnz), diag))
|
||
end
|
||
|
||
sparse(S::UniformScaling, m::Integer, n::Integer=m) = speye_scaled(S.λ, m, n)
|
||
|
||
|
||
# TODO: More appropriate location?
|
||
conj!(A::SparseMatrixCSC) = (@inbounds broadcast!(conj, A.nzval, A.nzval); A)
|
||
(-)(A::SparseMatrixCSC) = SparseMatrixCSC(A.m, A.n, copy(A.colptr), copy(A.rowval), map(-, A.nzval))
|
||
|
||
# the rest of real, conj, imag are handled correctly via AbstractArray methods
|
||
conj(A::SparseMatrixCSC{<:Complex}) =
|
||
SparseMatrixCSC(A.m, A.n, copy(A.colptr), copy(A.rowval), conj(A.nzval))
|
||
imag(A::SparseMatrixCSC{Tv,Ti}) where {Tv<:Real,Ti} = spzeros(Tv, Ti, A.m, A.n)
|
||
|
||
## Binary arithmetic and boolean operators
|
||
(+)(A::SparseMatrixCSC, B::SparseMatrixCSC) = map(+, A, B)
|
||
(-)(A::SparseMatrixCSC, B::SparseMatrixCSC) = map(-, A, B)
|
||
|
||
(+)(A::SparseMatrixCSC, B::Array) = Array(A) + B
|
||
(+)(A::Array, B::SparseMatrixCSC) = A + Array(B)
|
||
(-)(A::SparseMatrixCSC, B::Array) = Array(A) - B
|
||
(-)(A::Array, B::SparseMatrixCSC) = A - Array(B)
|
||
|
||
## full equality
|
||
function ==(A1::SparseMatrixCSC, A2::SparseMatrixCSC)
|
||
size(A1)!=size(A2) && return false
|
||
vals1, vals2 = nonzeros(A1), nonzeros(A2)
|
||
rows1, rows2 = rowvals(A1), rowvals(A2)
|
||
m, n = size(A1)
|
||
@inbounds for i = 1:n
|
||
nz1,nz2 = nzrange(A1,i), nzrange(A2,i)
|
||
j1,j2 = first(nz1), first(nz2)
|
||
# step through the rows of both matrices at once:
|
||
while j1<=last(nz1) && j2<=last(nz2)
|
||
r1,r2 = rows1[j1], rows2[j2]
|
||
if r1==r2
|
||
vals1[j1]!=vals2[j2] && return false
|
||
j1+=1
|
||
j2+=1
|
||
else
|
||
if r1<r2
|
||
vals1[j1]!=0 && return false
|
||
j1+=1
|
||
else
|
||
vals2[j2]!=0 && return false
|
||
j2+=1
|
||
end
|
||
end
|
||
end
|
||
# finish off any left-overs:
|
||
for j = j1:last(nz1)
|
||
vals1[j]!=0 && return false
|
||
end
|
||
for j = j2:last(nz2)
|
||
vals2[j]!=0 && return false
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
## Reductions
|
||
|
||
# In general, output of sparse matrix reductions will not be sparse,
|
||
# and computing reductions along columns into SparseMatrixCSC is
|
||
# non-trivial, so use Arrays for output
|
||
Base.reducedim_initarray{R}(A::SparseMatrixCSC, region, v0, ::Type{R}) =
|
||
fill!(similar(dims->Array{R}(dims), Base.reduced_indices(A,region)), v0)
|
||
Base.reducedim_initarray0{R}(A::SparseMatrixCSC, region, v0, ::Type{R}) =
|
||
fill!(similar(dims->Array{R}(dims), Base.reduced_indices0(A,region)), v0)
|
||
|
||
# General mapreduce
|
||
function _mapreducezeros(f, op, ::Type{T}, nzeros::Int, v0) where T
|
||
nzeros == 0 && return v0
|
||
|
||
# Reduce over first zero
|
||
zeroval = f(zero(T))
|
||
v = op(v0, zeroval)
|
||
isequal(v, v0) && return v
|
||
|
||
# Reduce over remaining zeros
|
||
for i = 2:nzeros
|
||
lastv = v
|
||
v = op(v, zeroval)
|
||
# Bail out early if we reach a fixed point
|
||
isequal(v, lastv) && break
|
||
end
|
||
|
||
v
|
||
end
|
||
|
||
function Base._mapreduce{T}(f, op, ::Base.IndexCartesian, A::SparseMatrixCSC{T})
|
||
z = nnz(A)
|
||
n = length(A)
|
||
if z == 0
|
||
if n == 0
|
||
Base.mr_empty(f, op, T)
|
||
else
|
||
_mapreducezeros(f, op, T, n-z-1, f(zero(T)))
|
||
end
|
||
else
|
||
_mapreducezeros(f, op, T, n-z, Base._mapreduce(f, op, A.nzval))
|
||
end
|
||
end
|
||
|
||
# Specialized mapreduce for +/*
|
||
_mapreducezeros(f, ::typeof(+), ::Type{T}, nzeros::Int, v0) where {T} =
|
||
nzeros == 0 ? v0 : f(zero(T))*nzeros + v0
|
||
_mapreducezeros(f, ::typeof(*), ::Type{T}, nzeros::Int, v0) where {T} =
|
||
nzeros == 0 ? v0 : f(zero(T))^nzeros * v0
|
||
|
||
function Base._mapreduce{T}(f, op::typeof(*), A::SparseMatrixCSC{T})
|
||
nzeros = length(A)-nnz(A)
|
||
if nzeros == 0
|
||
# No zeros, so don't compute f(0) since it might throw
|
||
Base._mapreduce(f, op, A.nzval)
|
||
else
|
||
v = f(zero(T))^(nzeros)
|
||
# Bail out early if initial reduction value is zero
|
||
v == zero(T) ? v : v*Base._mapreduce(f, op, A.nzval)
|
||
end
|
||
end
|
||
|
||
# General mapreducedim
|
||
function _mapreducerows!{T}(f, op, R::AbstractArray, A::SparseMatrixCSC{T})
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
nzval = A.nzval
|
||
m, n = size(A)
|
||
@inbounds for col = 1:n
|
||
r = R[1, col]
|
||
@simd for j = colptr[col]:colptr[col+1]-1
|
||
r = op(r, f(nzval[j]))
|
||
end
|
||
R[1, col] = _mapreducezeros(f, op, T, m-(colptr[col+1]-colptr[col]), r)
|
||
end
|
||
R
|
||
end
|
||
|
||
function _mapreducecols!{Tv,Ti}(f, op, R::AbstractArray, A::SparseMatrixCSC{Tv,Ti})
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
nzval = A.nzval
|
||
m, n = size(A)
|
||
rownz = fill(convert(Ti, n), m)
|
||
@inbounds for col = 1:n
|
||
@simd for j = colptr[col]:colptr[col+1]-1
|
||
row = rowval[j]
|
||
R[row, 1] = op(R[row, 1], f(nzval[j]))
|
||
rownz[row] -= 1
|
||
end
|
||
end
|
||
@inbounds for i = 1:m
|
||
R[i, 1] = _mapreducezeros(f, op, Tv, rownz[i], R[i, 1])
|
||
end
|
||
R
|
||
end
|
||
|
||
function Base._mapreducedim!{T}(f, op, R::AbstractArray, A::SparseMatrixCSC{T})
|
||
lsiz = Base.check_reducedims(R,A)
|
||
isempty(A) && return R
|
||
|
||
if size(R, 1) == size(R, 2) == 1
|
||
# Reduction along both columns and rows
|
||
R[1, 1] = mapreduce(f, op, A)
|
||
elseif size(R, 1) == 1
|
||
# Reduction along rows
|
||
_mapreducerows!(f, op, R, A)
|
||
elseif size(R, 2) == 1
|
||
# Reduction along columns
|
||
_mapreducecols!(f, op, R, A)
|
||
else
|
||
# Reduction along a dimension > 2
|
||
# Compute op(R, f(A))
|
||
m, n = size(A)
|
||
nzval = A.nzval
|
||
if length(nzval) == m*n
|
||
# No zeros, so don't compute f(0) since it might throw
|
||
for col = 1:n
|
||
@simd for row = 1:size(A, 1)
|
||
@inbounds R[row, col] = op(R[row, col], f(nzval[(col-1)*m+row]))
|
||
end
|
||
end
|
||
else
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
zeroval = f(zero(T))
|
||
@inbounds for col = 1:n
|
||
lastrow = 0
|
||
for j = colptr[col]:colptr[col+1]-1
|
||
row = rowval[j]
|
||
@simd for i = lastrow+1:row-1 # Zeros before this nonzero
|
||
R[i, col] = op(R[i, col], zeroval)
|
||
end
|
||
R[row, col] = op(R[row, col], f(nzval[j]))
|
||
lastrow = row
|
||
end
|
||
@simd for i = lastrow+1:m # Zeros at end
|
||
R[i, col] = op(R[i, col], zeroval)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
R
|
||
end
|
||
|
||
# Specialized mapreducedim for + cols to avoid allocating a
|
||
# temporary array when f(0) == 0
|
||
function _mapreducecols!{Tv,Ti}(f, op::typeof(+), R::AbstractArray, A::SparseMatrixCSC{Tv,Ti})
|
||
nzval = A.nzval
|
||
m, n = size(A)
|
||
if length(nzval) == m*n
|
||
# No zeros, so don't compute f(0) since it might throw
|
||
for col = 1:n
|
||
@simd for row = 1:size(A, 1)
|
||
@inbounds R[row, 1] = op(R[row, 1], f(nzval[(col-1)*m+row]))
|
||
end
|
||
end
|
||
else
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
zeroval = f(zero(Tv))
|
||
if isequal(zeroval, zero(Tv))
|
||
# Case where f(0) == 0
|
||
@inbounds for col = 1:size(A, 2)
|
||
@simd for j = colptr[col]:colptr[col+1]-1
|
||
R[rowval[j], 1] += f(nzval[j])
|
||
end
|
||
end
|
||
else
|
||
# Case where f(0) != 0
|
||
rownz = fill(convert(Ti, n), m)
|
||
@inbounds for col = 1:size(A, 2)
|
||
@simd for j = colptr[col]:colptr[col+1]-1
|
||
row = rowval[j]
|
||
R[row, 1] += f(nzval[j])
|
||
rownz[row] -= 1
|
||
end
|
||
end
|
||
for i = 1:m
|
||
R[i, 1] += rownz[i]*zeroval
|
||
end
|
||
end
|
||
end
|
||
R
|
||
end
|
||
|
||
# findmax/min and indmax/min methods
|
||
function _findz{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, rows=1:A.m, cols=1:A.n)
|
||
colptr = A.colptr; rowval = A.rowval; nzval = A.nzval
|
||
zval = zero(Tv)
|
||
col = cols[1]; row = 0
|
||
rowmin = rows[1]; rowmax = rows[end]
|
||
allrows = (rows == 1:A.m)
|
||
@inbounds while col <= cols[end]
|
||
r1::Int = colptr[col]
|
||
r2::Int = colptr[col+1] - 1
|
||
if !allrows && (r1 <= r2)
|
||
r1 = searchsortedfirst(rowval, rowmin, r1, r2, Forward)
|
||
(r1 <= r2 ) && (r2 = searchsortedlast(rowval, rowmax, r1, r2, Forward) + 1)
|
||
end
|
||
row = rowmin
|
||
(r1 > r2) && (return sub2ind(size(A),row,col))
|
||
while (r1 <= r2) && (row == rowval[r1]) && (nzval[r1] != zval)
|
||
r1 += 1
|
||
row += 1
|
||
end
|
||
(row <= rowmax) && (return sub2ind(size(A),row,col))
|
||
col += 1
|
||
end
|
||
return 0
|
||
end
|
||
|
||
macro _findr(op, A, region, Tv, Ti)
|
||
esc(quote
|
||
N = nnz($A)
|
||
L = length($A)
|
||
(L == 0) && error("array must be non-empty")
|
||
|
||
colptr = $A.colptr; rowval = $A.rowval; nzval = $A.nzval; m = $A.m; n = $A.n
|
||
zval = zero($Tv)
|
||
szA = size($A)
|
||
|
||
if $region == 1 || $region == (1,)
|
||
(N == 0) && (return (fill(zval,1,n), fill(convert($Ti,1),1,n)))
|
||
S = Vector{$Tv}(n); I = Vector{$Ti}(n)
|
||
@inbounds for i = 1 : n
|
||
Sc = zval; Ic = _findz($A, 1:m, i:i)
|
||
if Ic == 0
|
||
j = colptr[i]
|
||
Ic = sub2ind(szA, rowval[j], i)
|
||
Sc = nzval[j]
|
||
end
|
||
for j = colptr[i] : colptr[i+1]-1
|
||
if ($op)(nzval[j], Sc)
|
||
Sc = nzval[j]
|
||
Ic = sub2ind(szA, rowval[j], i)
|
||
end
|
||
end
|
||
S[i] = Sc; I[i] = Ic
|
||
end
|
||
return(reshape(S,1,n), reshape(I,1,n))
|
||
elseif $region == 2 || $region == (2,)
|
||
(N == 0) && (return (fill(zval,m,1), fill(convert($Ti,1),m,1)))
|
||
S = Vector{$Tv}(m); I = Vector{$Ti}(m)
|
||
@inbounds for row in 1:m
|
||
S[row] = zval; I[row] = _findz($A, row:row, 1:n)
|
||
if I[row] == 0
|
||
I[row] = sub2ind(szA, row, 1)
|
||
S[row] = A[row,1]
|
||
end
|
||
end
|
||
@inbounds for i = 1 : n, j = colptr[i] : colptr[i+1]-1
|
||
row = rowval[j]
|
||
if ($op)(nzval[j], S[row])
|
||
S[row] = nzval[j]
|
||
I[row] = sub2ind(szA, row, i)
|
||
end
|
||
end
|
||
return (reshape(S,m,1), reshape(I,m,1))
|
||
elseif $region == (1,2)
|
||
(N == 0) && (return (fill(zval,1,1), fill(convert($Ti,1),1,1)))
|
||
hasz = nnz($A) != length($A)
|
||
Sv = hasz ? zval : nzval[1]
|
||
Iv::($Ti) = hasz ? _findz($A) : 1
|
||
@inbounds for i = 1 : $A.n, j = colptr[i] : (colptr[i+1]-1)
|
||
if ($op)(nzval[j], Sv)
|
||
Sv = nzval[j]
|
||
Iv = sub2ind(szA, rowval[j], i)
|
||
end
|
||
end
|
||
return (fill(Sv,1,1), fill(Iv,1,1))
|
||
else
|
||
throw(ArgumentError("invalid value for region; must be 1, 2, or (1,2)"))
|
||
end
|
||
end) #quote
|
||
end
|
||
|
||
findmin(A::SparseMatrixCSC{Tv,Ti}, region) where {Tv,Ti} = @_findr(<, A, region, Tv, Ti)
|
||
findmax(A::SparseMatrixCSC{Tv,Ti}, region) where {Tv,Ti} = @_findr(>, A, region, Tv, Ti)
|
||
findmin(A::SparseMatrixCSC) = (r=findmin(A,(1,2)); (r[1][1], r[2][1]))
|
||
findmax(A::SparseMatrixCSC) = (r=findmax(A,(1,2)); (r[1][1], r[2][1]))
|
||
|
||
indmin(A::SparseMatrixCSC) = findmin(A)[2]
|
||
indmax(A::SparseMatrixCSC) = findmax(A)[2]
|
||
|
||
#all(A::SparseMatrixCSC{Bool}, region) = reducedim(all,A,region,true)
|
||
#any(A::SparseMatrixCSC{Bool}, region) = reducedim(any,A,region,false)
|
||
#sum(A::SparseMatrixCSC{Bool}, region) = reducedim(+,A,region,0,Int)
|
||
#sum(A::SparseMatrixCSC{Bool}) = countnz(A)
|
||
|
||
## getindex
|
||
function rangesearch(haystack::Range, needle)
|
||
(i,rem) = divrem(needle - first(haystack), step(haystack))
|
||
(rem==0 && 1<=i+1<=length(haystack)) ? i+1 : 0
|
||
end
|
||
|
||
getindex(A::SparseMatrixCSC, I::Tuple{Integer,Integer}) = getindex(A, I[1], I[2])
|
||
|
||
function getindex(A::SparseMatrixCSC{T}, i0::Integer, i1::Integer) where T
|
||
if !(1 <= i0 <= A.m && 1 <= i1 <= A.n); throw(BoundsError()); end
|
||
r1 = Int(A.colptr[i1])
|
||
r2 = Int(A.colptr[i1+1]-1)
|
||
(r1 > r2) && return zero(T)
|
||
r1 = searchsortedfirst(A.rowval, i0, r1, r2, Forward)
|
||
((r1 > r2) || (A.rowval[r1] != i0)) ? zero(T) : A.nzval[r1]
|
||
end
|
||
|
||
# Colon translation
|
||
getindex(A::SparseMatrixCSC, ::Colon, ::Colon) = copy(A)
|
||
getindex(A::SparseMatrixCSC, i, ::Colon) = getindex(A, i, 1:size(A, 2))
|
||
getindex(A::SparseMatrixCSC, ::Colon, i) = getindex(A, 1:size(A, 1), i)
|
||
|
||
function getindex_cols(A::SparseMatrixCSC{Tv,Ti}, J::AbstractVector) where {Tv,Ti}
|
||
# for indexing whole columns
|
||
(m, n) = size(A)
|
||
nJ = length(J)
|
||
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval
|
||
|
||
colptrS = Vector{Ti}(nJ+1)
|
||
colptrS[1] = 1
|
||
nnzS = 0
|
||
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
1 <= col <= n || throw(BoundsError())
|
||
nnzS += colptrA[col+1] - colptrA[col]
|
||
colptrS[j+1] = nnzS + 1
|
||
end
|
||
|
||
rowvalS = Vector{Ti}(nnzS)
|
||
nzvalS = Vector{Tv}(nnzS)
|
||
ptrS = 0
|
||
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
for k = colptrA[col]:colptrA[col+1]-1
|
||
ptrS += 1
|
||
rowvalS[ptrS] = rowvalA[k]
|
||
nzvalS[ptrS] = nzvalA[k]
|
||
end
|
||
end
|
||
return SparseMatrixCSC(m, nJ, colptrS, rowvalS, nzvalS)
|
||
end
|
||
|
||
function getindex(A::SparseMatrixCSC{Tv,Ti}, I::Range, J::AbstractVector) where {Tv,Ti<:Integer}
|
||
# Ranges for indexing rows
|
||
(m, n) = size(A)
|
||
# whole columns:
|
||
if I == 1:m
|
||
return getindex_cols(A, J)
|
||
end
|
||
|
||
nI = length(I)
|
||
nI == 0 || (minimum(I) >= 1 && maximum(I) <= m) || throw(BoundsError())
|
||
nJ = length(J)
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval
|
||
colptrS = Vector{Ti}(nJ+1)
|
||
colptrS[1] = 1
|
||
nnzS = 0
|
||
|
||
# Form the structure of the result and compute space
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
1 <= col <= n || throw(BoundsError())
|
||
@simd for k in colptrA[col]:colptrA[col+1]-1
|
||
nnzS += rowvalA[k] in I # `in` is fast for ranges
|
||
end
|
||
colptrS[j+1] = nnzS+1
|
||
end
|
||
|
||
# Populate the values in the result
|
||
rowvalS = Vector{Ti}(nnzS)
|
||
nzvalS = Vector{Tv}(nnzS)
|
||
ptrS = 1
|
||
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
for k = colptrA[col]:colptrA[col+1]-1
|
||
rowA = rowvalA[k]
|
||
i = rangesearch(I, rowA)
|
||
if i > 0
|
||
rowvalS[ptrS] = i
|
||
nzvalS[ptrS] = nzvalA[k]
|
||
ptrS += 1
|
||
end
|
||
end
|
||
end
|
||
|
||
return SparseMatrixCSC(nI, nJ, colptrS, rowvalS, nzvalS)
|
||
end
|
||
|
||
function getindex_I_sorted{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, J::AbstractVector)
|
||
# Sorted vectors for indexing rows.
|
||
# Similar to getindex_general but without the transpose trick.
|
||
(m, n) = size(A)
|
||
|
||
nI = length(I)
|
||
nzA = nnz(A)
|
||
avgM = div(nzA,n)
|
||
# Heuristics based on experiments discussed in:
|
||
# https://github.com/JuliaLang/julia/issues/12860
|
||
# https://github.com/JuliaLang/julia/pull/12934
|
||
alg = ((m > nzA) && (m > nI)) ? 0 :
|
||
((nI - avgM) > 2^8) ? 1 :
|
||
((avgM - nI) > 2^10) ? 0 : 2
|
||
|
||
(alg == 0) ? getindex_I_sorted_bsearch_A(A, I, J) :
|
||
(alg == 1) ? getindex_I_sorted_bsearch_I(A, I, J) :
|
||
getindex_I_sorted_linear(A, I, J)
|
||
end
|
||
|
||
function getindex_I_sorted_bsearch_A{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, J::AbstractVector)
|
||
const nI = length(I)
|
||
const nJ = length(J)
|
||
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval
|
||
colptrS = Vector{Ti}(nJ+1)
|
||
colptrS[1] = 1
|
||
|
||
ptrS = 1
|
||
# determine result size
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
ptrI::Int = 1 # runs through I
|
||
ptrA::Int = colptrA[col]
|
||
stopA::Int = colptrA[col+1]-1
|
||
if ptrA <= stopA
|
||
while ptrI <= nI
|
||
rowI = I[ptrI]
|
||
ptrI += 1
|
||
(rowvalA[ptrA] > rowI) && continue
|
||
ptrA = searchsortedfirst(rowvalA, rowI, ptrA, stopA, Base.Order.Forward)
|
||
(ptrA <= stopA) || break
|
||
if rowvalA[ptrA] == rowI
|
||
ptrS += 1
|
||
end
|
||
end
|
||
end
|
||
colptrS[j+1] = ptrS
|
||
end
|
||
|
||
rowvalS = Vector{Ti}(ptrS-1)
|
||
nzvalS = Vector{Tv}(ptrS-1)
|
||
|
||
# fill the values
|
||
ptrS = 1
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
ptrI::Int = 1 # runs through I
|
||
ptrA::Int = colptrA[col]
|
||
stopA::Int = colptrA[col+1]-1
|
||
if ptrA <= stopA
|
||
while ptrI <= nI
|
||
rowI = I[ptrI]
|
||
if rowvalA[ptrA] <= rowI
|
||
ptrA = searchsortedfirst(rowvalA, rowI, ptrA, stopA, Base.Order.Forward)
|
||
(ptrA <= stopA) || break
|
||
if rowvalA[ptrA] == rowI
|
||
rowvalS[ptrS] = ptrI
|
||
nzvalS[ptrS] = nzvalA[ptrA]
|
||
ptrS += 1
|
||
end
|
||
end
|
||
ptrI += 1
|
||
end
|
||
end
|
||
end
|
||
return SparseMatrixCSC(nI, nJ, colptrS, rowvalS, nzvalS)
|
||
end
|
||
|
||
function getindex_I_sorted_linear(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, J::AbstractVector) where {Tv,Ti}
|
||
const nI = length(I)
|
||
const nJ = length(J)
|
||
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval
|
||
colptrS = Vector{Ti}(nJ+1)
|
||
colptrS[1] = 1
|
||
cacheI = zeros(Int, A.m)
|
||
|
||
ptrS = 1
|
||
# build the cache and determine result size
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
ptrI::Int = 1 # runs through I
|
||
ptrA::Int = colptrA[col]
|
||
stopA::Int = colptrA[col+1]
|
||
while ptrI <= nI && ptrA < stopA
|
||
rowA = rowvalA[ptrA]
|
||
rowI = I[ptrI]
|
||
|
||
if rowI > rowA
|
||
ptrA += 1
|
||
elseif rowI < rowA
|
||
ptrI += 1
|
||
else
|
||
(cacheI[rowA] == 0) && (cacheI[rowA] = ptrI)
|
||
ptrS += 1
|
||
ptrI += 1
|
||
end
|
||
end
|
||
colptrS[j+1] = ptrS
|
||
end
|
||
|
||
rowvalS = Vector{Ti}(ptrS-1)
|
||
nzvalS = Vector{Tv}(ptrS-1)
|
||
|
||
# fill the values
|
||
ptrS = 1
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
ptrA::Int = colptrA[col]
|
||
stopA::Int = colptrA[col+1]
|
||
while ptrA < stopA
|
||
rowA = rowvalA[ptrA]
|
||
ptrI = cacheI[rowA]
|
||
if ptrI > 0
|
||
while ptrI <= nI && I[ptrI] == rowA
|
||
rowvalS[ptrS] = ptrI
|
||
nzvalS[ptrS] = nzvalA[ptrA]
|
||
ptrS += 1
|
||
ptrI += 1
|
||
end
|
||
end
|
||
ptrA += 1
|
||
end
|
||
end
|
||
return SparseMatrixCSC(nI, nJ, colptrS, rowvalS, nzvalS)
|
||
end
|
||
|
||
function getindex_I_sorted_bsearch_I(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, J::AbstractVector) where {Tv,Ti}
|
||
const nI = length(I)
|
||
const nJ = length(J)
|
||
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval
|
||
colptrS = Vector{Ti}(nJ+1)
|
||
colptrS[1] = 1
|
||
|
||
m = A.m
|
||
|
||
# cacheI is used first to store num occurrences of each row in columns of interest
|
||
# and later to store position of first occurrence of each row in I
|
||
cacheI = zeros(Int, m)
|
||
|
||
# count rows
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
for ptrA in colptrA[col]:(colptrA[col+1]-1)
|
||
cacheI[rowvalA[ptrA]] += 1
|
||
end
|
||
end
|
||
|
||
# fill cache and count nnz
|
||
ptrS::Int = 0
|
||
ptrI::Int = 1
|
||
@inbounds for j = 1:m
|
||
cval = cacheI[j]
|
||
(cval == 0) && continue
|
||
ptrI = searchsortedfirst(I, j, ptrI, nI, Base.Order.Forward)
|
||
cacheI[j] = ptrI
|
||
while ptrI <= nI && I[ptrI] == j
|
||
ptrS += cval
|
||
ptrI += 1
|
||
end
|
||
if ptrI > nI
|
||
@simd for i=(j+1):m; @inbounds cacheI[i]=ptrI; end
|
||
break
|
||
end
|
||
end
|
||
rowvalS = Vector{Ti}(ptrS)
|
||
nzvalS = Vector{Tv}(ptrS)
|
||
colptrS[nJ+1] = ptrS+1
|
||
|
||
# fill the values
|
||
ptrS = 1
|
||
@inbounds for j = 1:nJ
|
||
col = J[j]
|
||
ptrA::Int = colptrA[col]
|
||
stopA::Int = colptrA[col+1]
|
||
while ptrA < stopA
|
||
rowA = rowvalA[ptrA]
|
||
ptrI = cacheI[rowA]
|
||
(ptrI > nI) && break
|
||
if ptrI > 0
|
||
while I[ptrI] == rowA
|
||
rowvalS[ptrS] = ptrI
|
||
nzvalS[ptrS] = nzvalA[ptrA]
|
||
ptrS += 1
|
||
ptrI += 1
|
||
(ptrI > nI) && break
|
||
end
|
||
end
|
||
ptrA += 1
|
||
end
|
||
colptrS[j+1] = ptrS
|
||
end
|
||
return SparseMatrixCSC(nI, nJ, colptrS, rowvalS, nzvalS)
|
||
end
|
||
|
||
function permute_rows!(S::SparseMatrixCSC{Tv,Ti}, pI::Vector{Int}) where {Tv,Ti}
|
||
(m, n) = size(S)
|
||
colptrS = S.colptr; rowvalS = S.rowval; nzvalS = S.nzval
|
||
# preallocate temporary sort space
|
||
nr = min(nnz(S), m)
|
||
rowperm = Vector{Int}(nr)
|
||
rowvalTemp = Vector{Ti}(nr)
|
||
nzvalTemp = Vector{Tv}(nr)
|
||
|
||
@inbounds for j in 1:n
|
||
rowrange = colptrS[j]:(colptrS[j+1]-1)
|
||
nr = length(rowrange)
|
||
(nr > 0) || continue
|
||
k = 1
|
||
for i in rowrange
|
||
rowA = rowvalS[i]
|
||
rowvalTemp[k] = pI[rowA]
|
||
nzvalTemp[k] = nzvalS[i]
|
||
k += 1
|
||
end
|
||
sortperm!(unsafe_wrap(Vector{Int}, pointer(rowperm), nr), unsafe_wrap(Vector{Ti}, pointer(rowvalTemp), nr))
|
||
k = 1
|
||
for i in rowrange
|
||
kperm = rowperm[k]
|
||
rowvalS[i] = rowvalTemp[kperm]
|
||
nzvalS[i] = nzvalTemp[kperm]
|
||
k += 1
|
||
end
|
||
end
|
||
S
|
||
end
|
||
|
||
function getindex_general(A::SparseMatrixCSC, I::AbstractVector, J::AbstractVector)
|
||
pI = sortperm(I)
|
||
@inbounds Is = I[pI]
|
||
permute_rows!(getindex_I_sorted(A, Is, J), pI)
|
||
end
|
||
|
||
# the general case:
|
||
function getindex(A::SparseMatrixCSC{Tv,Ti}, I::AbstractVector, J::AbstractVector) where {Tv,Ti}
|
||
(m, n) = size(A)
|
||
|
||
if !isempty(J)
|
||
minj, maxj = extrema(J)
|
||
((minj < 1) || (maxj > n)) && throw(BoundsError())
|
||
end
|
||
|
||
if !isempty(I)
|
||
mini, maxi = extrema(I)
|
||
((mini < 1) || (maxi > m)) && throw(BoundsError())
|
||
end
|
||
|
||
if isempty(I) || isempty(J) || (0 == nnz(A))
|
||
return spzeros(Tv, Ti, length(I), length(J))
|
||
end
|
||
|
||
if issorted(I)
|
||
return getindex_I_sorted(A, I, J)
|
||
else
|
||
return getindex_general(A, I, J)
|
||
end
|
||
end
|
||
|
||
function getindex(A::SparseMatrixCSC{Tv}, I::AbstractArray) where Tv
|
||
szA = size(A)
|
||
nA = szA[1]*szA[2]
|
||
colptrA = A.colptr
|
||
rowvalA = A.rowval
|
||
nzvalA = A.nzval
|
||
|
||
n = length(I)
|
||
outm = size(I,1)
|
||
outn = size(I,2)
|
||
szB = (outm, outn)
|
||
colptrB = zeros(Int, outn+1)
|
||
rowvalB = Vector{Int}(n)
|
||
nzvalB = Vector{Tv}(n)
|
||
|
||
colB = 1
|
||
rowB = 1
|
||
colptrB[colB] = 1
|
||
idxB = 1
|
||
|
||
for i in 1:n
|
||
((I[i] < 1) | (I[i] > nA)) && throw(BoundsError())
|
||
row,col = ind2sub(szA, I[i])
|
||
for r in colptrA[col]:(colptrA[col+1]-1)
|
||
@inbounds if rowvalA[r] == row
|
||
rowB,colB = ind2sub(szB, i)
|
||
colptrB[colB+1] += 1
|
||
rowvalB[idxB] = rowB
|
||
nzvalB[idxB] = nzvalA[r]
|
||
idxB += 1
|
||
break
|
||
end
|
||
end
|
||
end
|
||
colptrB = cumsum(colptrB)
|
||
if n > (idxB-1)
|
||
deleteat!(nzvalB, idxB:n)
|
||
deleteat!(rowvalB, idxB:n)
|
||
end
|
||
SparseMatrixCSC(outm, outn, colptrB, rowvalB, nzvalB)
|
||
end
|
||
|
||
# logical getindex
|
||
getindex(A::SparseMatrixCSC{<:Any,<:Integer}, I::Range{Bool}, J::AbstractVector{Bool}) = error("Cannot index with Range{Bool}")
|
||
getindex(A::SparseMatrixCSC{<:Any,<:Integer}, I::Range{Bool}, J::AbstractVector{<:Integer}) = error("Cannot index with Range{Bool}")
|
||
|
||
getindex(A::SparseMatrixCSC, I::Range{<:Integer}, J::AbstractVector{Bool}) = A[I,find(J)]
|
||
getindex(A::SparseMatrixCSC, I::Integer, J::AbstractVector{Bool}) = A[I,find(J)]
|
||
getindex(A::SparseMatrixCSC, I::AbstractVector{Bool}, J::Integer) = A[find(I),J]
|
||
getindex(A::SparseMatrixCSC, I::AbstractVector{Bool}, J::AbstractVector{Bool}) = A[find(I),find(J)]
|
||
getindex(A::SparseMatrixCSC, I::AbstractVector{<:Integer}, J::AbstractVector{Bool}) = A[I,find(J)]
|
||
getindex(A::SparseMatrixCSC, I::AbstractVector{Bool}, J::AbstractVector{<:Integer}) = A[find(I),J]
|
||
|
||
## setindex!
|
||
function setindex!(A::SparseMatrixCSC{Tv,Ti}, v, i::Integer, j::Integer) where Tv where Ti
|
||
setindex!(A, convert(Tv, v), convert(Ti, i), convert(Ti, j))
|
||
end
|
||
function setindex!(A::SparseMatrixCSC{Tv,Ti}, v::Tv, i::Ti, j::Ti) where Tv where Ti<:Integer
|
||
if !((1 <= i <= A.m) & (1 <= j <= A.n))
|
||
throw(BoundsError(A, (i,j)))
|
||
end
|
||
coljfirstk = Int(A.colptr[j])
|
||
coljlastk = Int(A.colptr[j+1] - 1)
|
||
searchk = searchsortedfirst(A.rowval, i, coljfirstk, coljlastk, Base.Order.Forward)
|
||
if searchk <= coljlastk && A.rowval[searchk] == i
|
||
# Column j contains entry A[i,j]. Update and return
|
||
A.nzval[searchk] = v
|
||
return A
|
||
end
|
||
# Column j does not contain entry A[i,j]. If v is nonzero, insert entry A[i,j] = v
|
||
# and return. If to the contrary v is zero, then simply return.
|
||
if v != 0
|
||
insert!(A.rowval, searchk, i)
|
||
insert!(A.nzval, searchk, v)
|
||
@simd for m in (j + 1):(A.n + 1)
|
||
@inbounds A.colptr[m] += 1
|
||
end
|
||
end
|
||
return A
|
||
end
|
||
|
||
setindex!(A::SparseMatrixCSC, v::AbstractMatrix, i::Integer, J::AbstractVector{<:Integer}) = setindex!(A, v, [i], J)
|
||
setindex!(A::SparseMatrixCSC, v::AbstractMatrix, I::AbstractVector{<:Integer}, j::Integer) = setindex!(A, v, I, [j])
|
||
|
||
setindex!(A::SparseMatrixCSC, x::Number, i::Integer, J::AbstractVector{<:Integer}) = setindex!(A, x, [i], J)
|
||
setindex!(A::SparseMatrixCSC, x::Number, I::AbstractVector{<:Integer}, j::Integer) = setindex!(A, x, I, [j])
|
||
|
||
# Colon translation
|
||
setindex!(A::SparseMatrixCSC, x, ::Colon) = setindex!(A, x, 1:length(A))
|
||
setindex!(A::SparseMatrixCSC, x, ::Colon, ::Colon) = setindex!(A, x, 1:size(A, 1), 1:size(A,2))
|
||
setindex!(A::SparseMatrixCSC, x, ::Colon, j::Union{Integer, AbstractVector}) = setindex!(A, x, 1:size(A, 1), j)
|
||
setindex!(A::SparseMatrixCSC, x, i::Union{Integer, AbstractVector}, ::Colon) = setindex!(A, x, i, 1:size(A, 2))
|
||
|
||
function setindex!{Tv}(A::SparseMatrixCSC{Tv}, x::Number,
|
||
I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer})
|
||
if isempty(I) || isempty(J); return A; end
|
||
# lt=≤ to check for strict sorting
|
||
if !issorted(I, lt=≤); I = sort!(unique(I)); end
|
||
if !issorted(J, lt=≤); J = sort!(unique(J)); end
|
||
if (I[1] < 1 || I[end] > A.m) || (J[1] < 1 || J[end] > A.n)
|
||
throw(BoundsError(A, (I, J)))
|
||
end
|
||
if x == 0
|
||
_spsetz_setindex!(A, I, J)
|
||
else
|
||
_spsetnz_setindex!(A, convert(Tv, x), I, J)
|
||
end
|
||
end
|
||
"""
|
||
Helper method for immediately preceding setindex! method. For all (i,j) such that i in I and
|
||
j in J, assigns zero to A[i,j] if A[i,j] is a presently-stored entry, and otherwise does nothing.
|
||
"""
|
||
function _spsetz_setindex!(A::SparseMatrixCSC,
|
||
I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer})
|
||
lengthI = length(I)
|
||
for j in J
|
||
coljAfirstk = A.colptr[j]
|
||
coljAlastk = A.colptr[j+1] - 1
|
||
coljAfirstk > coljAlastk && continue
|
||
kA = coljAfirstk
|
||
kI = 1
|
||
entrykArow = A.rowval[kA]
|
||
entrykIrow = I[kI]
|
||
while true
|
||
if entrykArow < entrykIrow
|
||
kA += 1
|
||
kA > coljAlastk && break
|
||
entrykArow = A.rowval[kA]
|
||
elseif entrykArow > entrykIrow
|
||
kI += 1
|
||
kI > lengthI && break
|
||
entrykIrow = I[kI]
|
||
else # entrykArow == entrykIrow
|
||
A.nzval[kA] = 0
|
||
kA += 1
|
||
kI += 1
|
||
(kA > coljAlastk || kI > lengthI) && break
|
||
entrykArow = A.rowval[kA]
|
||
entrykIrow = I[kI]
|
||
end
|
||
end
|
||
end
|
||
end
|
||
"""
|
||
Helper method for immediately preceding setindex! method. For all (i,j) such that i in I
|
||
and j in J, assigns x to A[i,j] if A[i,j] is a presently-stored entry, and allocates and
|
||
assigns x to A[i,j] if A[i,j] is not presently stored.
|
||
"""
|
||
function _spsetnz_setindex!(A::SparseMatrixCSC{Tv}, x::Tv,
|
||
I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer}) where Tv
|
||
m, n = size(A)
|
||
lenI = length(I)
|
||
|
||
nnzA = nnz(A) + lenI * length(J)
|
||
|
||
rowvalA = rowval = A.rowval
|
||
nzvalA = nzval = A.nzval
|
||
|
||
rowidx = 1
|
||
nadd = 0
|
||
@inbounds for col in 1:n
|
||
rrange = nzrange(A, col)
|
||
if nadd > 0
|
||
A.colptr[col] = A.colptr[col] + nadd
|
||
end
|
||
|
||
if col in J
|
||
if isempty(rrange) # set new vals only
|
||
nincl = lenI
|
||
if nadd == 0
|
||
rowval = copy(rowvalA)
|
||
nzval = copy(nzvalA)
|
||
resize!(rowvalA, nnzA)
|
||
resize!(nzvalA, nnzA)
|
||
end
|
||
r = rowidx:(rowidx+nincl-1)
|
||
rowvalA[r] = I
|
||
nzvalA[r] = x
|
||
rowidx += nincl
|
||
nadd += nincl
|
||
else # set old + new vals
|
||
old_ptr = rrange[1]
|
||
old_stop = rrange[end]
|
||
new_ptr = 1
|
||
new_stop = lenI
|
||
|
||
while true
|
||
old_row = rowval[old_ptr]
|
||
new_row = I[new_ptr]
|
||
if old_row < new_row
|
||
rowvalA[rowidx] = old_row
|
||
nzvalA[rowidx] = nzval[old_ptr]
|
||
rowidx += 1
|
||
old_ptr += 1
|
||
else
|
||
if old_row == new_row
|
||
old_ptr += 1
|
||
else
|
||
if nadd == 0
|
||
rowval = copy(rowvalA)
|
||
nzval = copy(nzvalA)
|
||
resize!(rowvalA, nnzA)
|
||
resize!(nzvalA, nnzA)
|
||
end
|
||
nadd += 1
|
||
end
|
||
rowvalA[rowidx] = new_row
|
||
nzvalA[rowidx] = x
|
||
rowidx += 1
|
||
new_ptr += 1
|
||
end
|
||
|
||
if old_ptr > old_stop
|
||
if new_ptr <= new_stop
|
||
if nadd == 0
|
||
rowval = copy(rowvalA)
|
||
nzval = copy(nzvalA)
|
||
resize!(rowvalA, nnzA)
|
||
resize!(nzvalA, nnzA)
|
||
end
|
||
r = rowidx:(rowidx+(new_stop-new_ptr))
|
||
rowvalA[r] = I[new_ptr:new_stop]
|
||
nzvalA[r] = x
|
||
rowidx += length(r)
|
||
nadd += length(r)
|
||
end
|
||
break
|
||
end
|
||
|
||
if new_ptr > new_stop
|
||
nincl = old_stop-old_ptr+1
|
||
copy!(rowvalA, rowidx, rowval, old_ptr, nincl)
|
||
copy!(nzvalA, rowidx, nzval, old_ptr, nincl)
|
||
rowidx += nincl
|
||
break
|
||
end
|
||
end
|
||
end
|
||
elseif !isempty(rrange) # set old vals only
|
||
nincl = length(rrange)
|
||
copy!(rowvalA, rowidx, rowval, rrange[1], nincl)
|
||
copy!(nzvalA, rowidx, nzval, rrange[1], nincl)
|
||
rowidx += nincl
|
||
end
|
||
end
|
||
|
||
if nadd > 0
|
||
A.colptr[n+1] = rowidx
|
||
deleteat!(rowvalA, rowidx:nnzA)
|
||
deleteat!(nzvalA, rowidx:nnzA)
|
||
end
|
||
return A
|
||
end
|
||
|
||
setindex!(A::SparseMatrixCSC{Tv,Ti}, S::Matrix, I::AbstractVector{T}, J::AbstractVector{T}) where {Tv,Ti,T<:Integer} =
|
||
setindex!(A, convert(SparseMatrixCSC{Tv,Ti}, S), I, J)
|
||
|
||
setindex!(A::SparseMatrixCSC, v::AbstractVector, I::AbstractVector{<:Integer}, j::Integer) = setindex!(A, v, I, [j])
|
||
setindex!(A::SparseMatrixCSC, v::AbstractVector, i::Integer, J::AbstractVector{<:Integer}) = setindex!(A, v, [i], J)
|
||
setindex!(A::SparseMatrixCSC, v::AbstractVector, I::AbstractVector{T}, J::AbstractVector{T}) where {T<:Integer} =
|
||
setindex!(A, reshape(v, length(I), length(J)), I, J)
|
||
|
||
|
||
# A[I,J] = B
|
||
function setindex!(A::SparseMatrixCSC{Tv,Ti}, B::SparseMatrixCSC{Tv,Ti}, I::AbstractVector{T}, J::AbstractVector{T}) where {Tv,Ti,T<:Integer}
|
||
if size(B,1) != length(I) || size(B,2) != length(J)
|
||
throw(DimensionMismatch(""))
|
||
end
|
||
|
||
issortedI = issorted(I)
|
||
issortedJ = issorted(J)
|
||
|
||
if !issortedI && !issortedJ
|
||
pI = sortperm(I); @inbounds I = I[pI]
|
||
pJ = sortperm(J); @inbounds J = J[pJ]
|
||
B = B[pI, pJ]
|
||
elseif !issortedI
|
||
pI = sortperm(I); @inbounds I = I[pI]
|
||
B = B[pI,:]
|
||
else !issortedJ
|
||
pJ = sortperm(J); @inbounds J = J[pJ]
|
||
B = B[:, pJ]
|
||
end
|
||
|
||
m, n = size(A)
|
||
mB, nB = size(B)
|
||
|
||
if (!isempty(I) && (I[1] < 1 || I[end] > m)) || (!isempty(J) && (J[1] < 1 || J[end] > n))
|
||
throw(BoundsError(A, (I, J)))
|
||
end
|
||
|
||
if isempty(I) || isempty(J)
|
||
return A
|
||
end
|
||
|
||
nI = length(I)
|
||
nJ = length(J)
|
||
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval
|
||
colptrB = B.colptr; rowvalB = B.rowval; nzvalB = B.nzval
|
||
|
||
nnzS = nnz(A) + nnz(B)
|
||
|
||
colptrS = copy(A.colptr)
|
||
rowvalS = copy(A.rowval)
|
||
nzvalS = copy(A.nzval)
|
||
|
||
resize!(rowvalA, nnzS)
|
||
resize!(nzvalA, nnzS)
|
||
|
||
colB = 1
|
||
asgn_col = J[colB]
|
||
|
||
I_asgn = falses(m)
|
||
I_asgn[I] = true
|
||
|
||
ptrS = 1
|
||
|
||
@inbounds for col = 1:n
|
||
|
||
# Copy column of A if it is not being assigned into
|
||
if colB > nJ || col != J[colB]
|
||
colptrA[col+1] = colptrA[col] + (colptrS[col+1]-colptrS[col])
|
||
|
||
for k = colptrS[col]:colptrS[col+1]-1
|
||
rowvalA[ptrS] = rowvalS[k]
|
||
nzvalA[ptrS] = nzvalS[k]
|
||
ptrS += 1
|
||
end
|
||
continue
|
||
end
|
||
|
||
ptrA::Int = colptrS[col]
|
||
stopA::Int = colptrS[col+1]
|
||
ptrB::Int = colptrB[colB]
|
||
stopB::Int = colptrB[colB+1]
|
||
|
||
while ptrA < stopA && ptrB < stopB
|
||
rowA = rowvalS[ptrA]
|
||
rowB = I[rowvalB[ptrB]]
|
||
if rowA < rowB
|
||
rowvalA[ptrS] = rowA
|
||
nzvalA[ptrS] = I_asgn[rowA] ? zero(Tv) : nzvalS[ptrA]
|
||
ptrS += 1
|
||
ptrA += 1
|
||
elseif rowB < rowA
|
||
if nzvalB[ptrB] != zero(Tv)
|
||
rowvalA[ptrS] = rowB
|
||
nzvalA[ptrS] = nzvalB[ptrB]
|
||
ptrS += 1
|
||
end
|
||
ptrB += 1
|
||
else
|
||
rowvalA[ptrS] = rowB
|
||
nzvalA[ptrS] = nzvalB[ptrB]
|
||
ptrS += 1
|
||
ptrB += 1
|
||
ptrA += 1
|
||
end
|
||
end
|
||
|
||
while ptrA < stopA
|
||
rowA = rowvalS[ptrA]
|
||
rowvalA[ptrS] = rowA
|
||
nzvalA[ptrS] = I_asgn[rowA] ? zero(Tv) : nzvalS[ptrA]
|
||
ptrS += 1
|
||
ptrA += 1
|
||
end
|
||
|
||
while ptrB < stopB
|
||
rowB = I[rowvalB[ptrB]]
|
||
if nzvalB[ptrB] != zero(Tv)
|
||
rowvalA[ptrS] = rowB
|
||
nzvalA[ptrS] = nzvalB[ptrB]
|
||
ptrS += 1
|
||
end
|
||
ptrB += 1
|
||
end
|
||
|
||
colptrA[col+1] = ptrS
|
||
colB += 1
|
||
end
|
||
|
||
deleteat!(rowvalA, colptrA[end]:length(rowvalA))
|
||
deleteat!(nzvalA, colptrA[end]:length(nzvalA))
|
||
|
||
return A
|
||
end
|
||
|
||
# Logical setindex!
|
||
|
||
setindex!(A::SparseMatrixCSC, x::Matrix, I::Integer, J::AbstractVector{Bool}) = setindex!(A, sparse(x), I, find(J))
|
||
setindex!(A::SparseMatrixCSC, x::Matrix, I::AbstractVector{Bool}, J::Integer) = setindex!(A, sparse(x), find(I), J)
|
||
setindex!(A::SparseMatrixCSC, x::Matrix, I::AbstractVector{Bool}, J::AbstractVector{Bool}) = setindex!(A, sparse(x), find(I), find(J))
|
||
setindex!(A::SparseMatrixCSC, x::Matrix, I::AbstractVector{<:Integer}, J::AbstractVector{Bool}) = setindex!(A, sparse(x), I, find(J))
|
||
setindex!(A::SparseMatrixCSC, x::Matrix, I::AbstractVector{Bool}, J::AbstractVector{<:Integer}) = setindex!(A, sparse(x), find(I),J)
|
||
|
||
setindex!(A::Matrix, x::SparseMatrixCSC, I::Integer, J::AbstractVector{Bool}) = setindex!(A, Array(x), I, find(J))
|
||
setindex!(A::Matrix, x::SparseMatrixCSC, I::AbstractVector{Bool}, J::Integer) = setindex!(A, Array(x), find(I), J)
|
||
setindex!(A::Matrix, x::SparseMatrixCSC, I::AbstractVector{Bool}, J::AbstractVector{Bool}) = setindex!(A, Array(x), find(I), find(J))
|
||
setindex!(A::Matrix, x::SparseMatrixCSC, I::AbstractVector{<:Integer}, J::AbstractVector{Bool}) = setindex!(A, Array(x), I, find(J))
|
||
setindex!(A::Matrix, x::SparseMatrixCSC, I::AbstractVector{Bool}, J::AbstractVector{<:Integer}) = setindex!(A, Array(x), find(I), J)
|
||
|
||
setindex!(A::SparseMatrixCSC, x, I::AbstractVector{Bool}) = throw(BoundsError())
|
||
function setindex!(A::SparseMatrixCSC, x, I::AbstractMatrix{Bool})
|
||
checkbounds(A, I)
|
||
n = sum(I)
|
||
(n == 0) && (return A)
|
||
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval
|
||
colptrB = colptrA; rowvalB = rowvalA; nzvalB = nzvalA
|
||
nadd = 0
|
||
bidx = xidx = 1
|
||
r1 = r2 = 0
|
||
|
||
@inbounds for col in 1:A.n
|
||
r1 = Int(colptrA[col])
|
||
r2 = Int(colptrA[col+1]-1)
|
||
|
||
for row in 1:A.m
|
||
if I[row, col]
|
||
v = isa(x, AbstractArray) ? x[xidx] : x
|
||
xidx += 1
|
||
|
||
if r1 <= r2
|
||
copylen = searchsortedfirst(rowvalA, row, r1, r2, Forward) - r1
|
||
if (copylen > 0)
|
||
if (nadd > 0)
|
||
copy!(rowvalB, bidx, rowvalA, r1, copylen)
|
||
copy!(nzvalB, bidx, nzvalA, r1, copylen)
|
||
end
|
||
bidx += copylen
|
||
r1 += copylen
|
||
end
|
||
end
|
||
|
||
# 0: no change, 1: update, 2: add new
|
||
mode = ((r1 <= r2) && (rowvalA[r1] == row)) ? 1 : ((v == 0) ? 0 : 2)
|
||
|
||
if (mode > 1) && (nadd == 0)
|
||
# copy storage to take changes
|
||
colptrA = copy(colptrB)
|
||
memreq = (x == 0) ? 0 : n
|
||
# this x == 0 check and approach doesn't jive with use of v above
|
||
# and may not make sense generally, as scalar x == 0 probably
|
||
# means this section should never be called. also may not be generic.
|
||
# TODO: clean this up, maybe separate scalar and array X cases
|
||
rowvalA = copy(rowvalB)
|
||
nzvalA = copy(nzvalB)
|
||
resize!(rowvalB, length(rowvalA)+memreq)
|
||
resize!(nzvalB, length(rowvalA)+memreq)
|
||
end
|
||
if mode == 1
|
||
rowvalB[bidx] = row
|
||
nzvalB[bidx] = v
|
||
bidx += 1
|
||
r1 += 1
|
||
elseif mode == 2
|
||
rowvalB[bidx] = row
|
||
nzvalB[bidx] = v
|
||
bidx += 1
|
||
nadd += 1
|
||
end
|
||
(xidx > n) && break
|
||
end # if I[row, col]
|
||
end # for row in 1:A.m
|
||
|
||
if (nadd != 0)
|
||
l = r2-r1+1
|
||
if l > 0
|
||
copy!(rowvalB, bidx, rowvalA, r1, l)
|
||
copy!(nzvalB, bidx, nzvalA, r1, l)
|
||
bidx += l
|
||
end
|
||
colptrB[col+1] = bidx
|
||
|
||
if (xidx > n) && (length(colptrB) > (col+1))
|
||
diff = nadd
|
||
colptrB[(col+2):end] = colptrA[(col+2):end] .+ diff
|
||
r1 = colptrA[col+1]
|
||
r2 = colptrA[end]-1
|
||
l = r2-r1+1
|
||
if l > 0
|
||
copy!(rowvalB, bidx, rowvalA, r1, l)
|
||
copy!(nzvalB, bidx, nzvalA, r1, l)
|
||
bidx += l
|
||
end
|
||
end
|
||
else
|
||
bidx = colptrA[col+1]
|
||
end
|
||
(xidx > n) && break
|
||
end # for col in 1:A.n
|
||
|
||
if (nadd != 0)
|
||
n = length(nzvalB)
|
||
if n > (bidx-1)
|
||
deleteat!(nzvalB, bidx:n)
|
||
deleteat!(rowvalB, bidx:n)
|
||
end
|
||
end
|
||
A
|
||
end
|
||
|
||
|
||
function setindex!(A::SparseMatrixCSC, x, I::AbstractVector{<:Real})
|
||
n = length(I)
|
||
(n == 0) && (return A)
|
||
|
||
colptrA = A.colptr; rowvalA = A.rowval; nzvalA = A.nzval; szA = size(A)
|
||
colptrB = colptrA; rowvalB = rowvalA; nzvalB = nzvalA
|
||
nadd = 0
|
||
bidx = aidx = 1
|
||
|
||
S = issorted(I) ? (1:n) : sortperm(I)
|
||
sxidx = r1 = r2 = 0
|
||
|
||
if (!isempty(I) && (I[S[1]] < 1 || I[S[end]] > length(A)))
|
||
throw(BoundsError(A, I))
|
||
end
|
||
|
||
isa(x, AbstractArray) && setindex_shape_check(x, length(I))
|
||
|
||
lastcol = 0
|
||
(nrowA, ncolA) = szA
|
||
@inbounds for xidx in 1:n
|
||
sxidx = S[xidx]
|
||
(sxidx < n) && (I[sxidx] == I[sxidx+1]) && continue
|
||
|
||
row,col = ind2sub(szA, I[sxidx])
|
||
v = isa(x, AbstractArray) ? x[sxidx] : x
|
||
|
||
if col > lastcol
|
||
r1 = Int(colptrA[col])
|
||
r2 = Int(colptrA[col+1] - 1)
|
||
|
||
# copy from last position till current column
|
||
if (nadd > 0)
|
||
colptrB[(lastcol+1):col] = colptrA[(lastcol+1):col] .+ nadd
|
||
copylen = r1 - aidx
|
||
if copylen > 0
|
||
copy!(rowvalB, bidx, rowvalA, aidx, copylen)
|
||
copy!(nzvalB, bidx, nzvalA, aidx, copylen)
|
||
aidx += copylen
|
||
bidx += copylen
|
||
end
|
||
else
|
||
aidx = bidx = r1
|
||
end
|
||
lastcol = col
|
||
end
|
||
|
||
if r1 <= r2
|
||
copylen = searchsortedfirst(rowvalA, row, r1, r2, Forward) - r1
|
||
if (copylen > 0)
|
||
if (nadd > 0)
|
||
copy!(rowvalB, bidx, rowvalA, r1, copylen)
|
||
copy!(nzvalB, bidx, nzvalA, r1, copylen)
|
||
end
|
||
bidx += copylen
|
||
r1 += copylen
|
||
aidx += copylen
|
||
end
|
||
end
|
||
|
||
# 0: no change, 1: update, 2: add new
|
||
mode = ((r1 <= r2) && (rowvalA[r1] == row)) ? 1 : ((v == 0) ? 0 : 2)
|
||
|
||
if (mode > 1) && (nadd == 0)
|
||
# copy storage to take changes
|
||
colptrA = copy(colptrB)
|
||
memreq = (x == 0) ? 0 : n
|
||
# see comment/TODO for same statement in preceding logical setindex! method
|
||
rowvalA = copy(rowvalB)
|
||
nzvalA = copy(nzvalB)
|
||
resize!(rowvalB, length(rowvalA)+memreq)
|
||
resize!(nzvalB, length(rowvalA)+memreq)
|
||
end
|
||
if mode == 1
|
||
rowvalB[bidx] = row
|
||
nzvalB[bidx] = v
|
||
bidx += 1
|
||
aidx += 1
|
||
r1 += 1
|
||
elseif mode == 2
|
||
rowvalB[bidx] = row
|
||
nzvalB[bidx] = v
|
||
bidx += 1
|
||
nadd += 1
|
||
end
|
||
end
|
||
|
||
# copy the rest
|
||
@inbounds if (nadd > 0)
|
||
colptrB[(lastcol+1):end] = colptrA[(lastcol+1):end] .+ nadd
|
||
r1 = colptrA[end]-1
|
||
copylen = r1 - aidx + 1
|
||
if copylen > 0
|
||
copy!(rowvalB, bidx, rowvalA, aidx, copylen)
|
||
copy!(nzvalB, bidx, nzvalA, aidx, copylen)
|
||
aidx += copylen
|
||
bidx += copylen
|
||
end
|
||
|
||
n = length(nzvalB)
|
||
if n > (bidx-1)
|
||
deleteat!(nzvalB, bidx:n)
|
||
deleteat!(rowvalB, bidx:n)
|
||
end
|
||
end
|
||
A
|
||
end
|
||
|
||
## dropstored! methods
|
||
"""
|
||
dropstored!(A::SparseMatrixCSC, i::Integer, j::Integer)
|
||
|
||
Drop entry `A[i,j]` from `A` if `A[i,j]` is stored, and otherwise do nothing.
|
||
|
||
```jldoctest
|
||
julia> A = sparse([1 2; 0 0])
|
||
2×2 SparseMatrixCSC{Int64,Int64} with 2 stored entries:
|
||
[1, 1] = 1
|
||
[1, 2] = 2
|
||
|
||
julia> Base.SparseArrays.dropstored!(A, 1, 2); A
|
||
2×2 SparseMatrixCSC{Int64,Int64} with 1 stored entry:
|
||
[1, 1] = 1
|
||
```
|
||
"""
|
||
function dropstored!(A::SparseMatrixCSC, i::Integer, j::Integer)
|
||
if !((1 <= i <= A.m) & (1 <= j <= A.n))
|
||
throw(BoundsError(A, (i,j)))
|
||
end
|
||
coljfirstk = Int(A.colptr[j])
|
||
coljlastk = Int(A.colptr[j+1] - 1)
|
||
searchk = searchsortedfirst(A.rowval, i, coljfirstk, coljlastk, Base.Order.Forward)
|
||
if searchk <= coljlastk && A.rowval[searchk] == i
|
||
# Entry A[i,j] is stored. Drop and return.
|
||
deleteat!(A.rowval, searchk)
|
||
deleteat!(A.nzval, searchk)
|
||
@simd for m in (j+1):(A.n + 1)
|
||
@inbounds A.colptr[m] -= 1
|
||
end
|
||
end
|
||
return A
|
||
end
|
||
"""
|
||
dropstored!(A::SparseMatrixCSC, I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer})
|
||
|
||
For each `(i,j)` where `i in I` and `j in J`, drop entry `A[i,j]` from `A` if `A[i,j]` is
|
||
stored and otherwise do nothing. Derivative forms:
|
||
|
||
dropstored!(A::SparseMatrixCSC, i::Integer, J::AbstractVector{<:Integer})
|
||
dropstored!(A::SparseMatrixCSC, I::AbstractVector{<:Integer}, j::Integer)
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> A = spdiagm([1, 2, 3, 4])
|
||
4×4 SparseMatrixCSC{Int64,Int64} with 4 stored entries:
|
||
[1, 1] = 1
|
||
[2, 2] = 2
|
||
[3, 3] = 3
|
||
[4, 4] = 4
|
||
|
||
julia> Base.SparseArrays.dropstored!(A, [1, 2], [1, 1])
|
||
4×4 SparseMatrixCSC{Int64,Int64} with 3 stored entries:
|
||
[2, 2] = 2
|
||
[3, 3] = 3
|
||
[4, 4] = 4
|
||
```
|
||
"""
|
||
function dropstored!(A::SparseMatrixCSC,
|
||
I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer})
|
||
m, n = size(A)
|
||
nnzA = nnz(A)
|
||
(nnzA == 0) && (return A)
|
||
|
||
!issorted(I) && (I = sort(I))
|
||
!issorted(J) && (J = sort(J))
|
||
|
||
if (!isempty(I) && (I[1] < 1 || I[end] > m)) || (!isempty(J) && (J[1] < 1 || J[end] > n))
|
||
throw(BoundsError(A, (I, J)))
|
||
end
|
||
|
||
if isempty(I) || isempty(J)
|
||
return A
|
||
end
|
||
|
||
rowval = rowvalA = A.rowval
|
||
nzval = nzvalA = A.nzval
|
||
rowidx = 1
|
||
ndel = 0
|
||
@inbounds for col in 1:n
|
||
rrange = nzrange(A, col)
|
||
if ndel > 0
|
||
A.colptr[col] = A.colptr[col] - ndel
|
||
end
|
||
|
||
if isempty(rrange) || !(col in J)
|
||
nincl = length(rrange)
|
||
if(ndel > 0) && !isempty(rrange)
|
||
copy!(rowvalA, rowidx, rowval, rrange[1], nincl)
|
||
copy!(nzvalA, rowidx, nzval, rrange[1], nincl)
|
||
end
|
||
rowidx += nincl
|
||
else
|
||
for ridx in rrange
|
||
if rowval[ridx] in I
|
||
if ndel == 0
|
||
rowval = copy(rowvalA)
|
||
nzval = copy(nzvalA)
|
||
end
|
||
ndel += 1
|
||
else
|
||
if ndel > 0
|
||
rowvalA[rowidx] = rowval[ridx]
|
||
nzvalA[rowidx] = nzval[ridx]
|
||
end
|
||
rowidx += 1
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if ndel > 0
|
||
A.colptr[n+1] = rowidx
|
||
deleteat!(rowvalA, rowidx:nnzA)
|
||
deleteat!(nzvalA, rowidx:nnzA)
|
||
end
|
||
return A
|
||
end
|
||
dropstored!(A::SparseMatrixCSC, i::Integer, J::AbstractVector{<:Integer}) = dropstored!(A, [i], J)
|
||
dropstored!(A::SparseMatrixCSC, I::AbstractVector{<:Integer}, j::Integer) = dropstored!(A, I, [j])
|
||
dropstored!(A::SparseMatrixCSC, ::Colon, j::Union{Integer,AbstractVector}) = dropstored!(A, 1:size(A,1), j)
|
||
dropstored!(A::SparseMatrixCSC, i::Union{Integer,AbstractVector}, ::Colon) = dropstored!(A, i, 1:size(A,2))
|
||
dropstored!(A::SparseMatrixCSC, ::Colon, ::Colon) = dropstored!(A, 1:size(A,1), 1:size(A,2))
|
||
dropstored!(A::SparseMatrixCSC, ::Colon) = dropstored!(A, :, :)
|
||
# TODO: Several of the preceding methods are optimization candidates.
|
||
# TODO: Implement linear indexing methods for dropstored! ?
|
||
# TODO: Implement logical indexing methods for dropstored! ?
|
||
|
||
|
||
# Sparse concatenation
|
||
|
||
function vcat(X::SparseMatrixCSC...)
|
||
num = length(X)
|
||
mX = Int[ size(x, 1) for x in X ]
|
||
nX = Int[ size(x, 2) for x in X ]
|
||
m = sum(mX)
|
||
n = nX[1]
|
||
|
||
for i = 2 : num
|
||
if nX[i] != n
|
||
throw(DimensionMismatch("All inputs to vcat should have the same number of columns"))
|
||
end
|
||
end
|
||
|
||
Tv = promote_eltype(X...)
|
||
Ti = promote_eltype(map(x->x.rowval, X)...)
|
||
|
||
nnzX = Int[ nnz(x) for x in X ]
|
||
nnz_res = sum(nnzX)
|
||
colptr = Vector{Ti}(n+1)
|
||
rowval = Vector{Ti}(nnz_res)
|
||
nzval = Vector{Tv}(nnz_res)
|
||
|
||
colptr[1] = 1
|
||
for c = 1:n
|
||
mX_sofar = 0
|
||
ptr_res = colptr[c]
|
||
for i = 1 : num
|
||
colptrXi = X[i].colptr
|
||
col_length = (colptrXi[c + 1] - 1) - colptrXi[c]
|
||
ptr_Xi = colptrXi[c]
|
||
|
||
stuffcol!(X[i], colptr, rowval, nzval,
|
||
ptr_res, ptr_Xi, col_length, mX_sofar)
|
||
|
||
ptr_res += col_length + 1
|
||
mX_sofar += mX[i]
|
||
end
|
||
colptr[c + 1] = ptr_res
|
||
end
|
||
SparseMatrixCSC(m, n, colptr, rowval, nzval)
|
||
end
|
||
|
||
@inline function stuffcol!(Xi::SparseMatrixCSC, colptr, rowval, nzval,
|
||
ptr_res, ptr_Xi, col_length, mX_sofar)
|
||
colptrXi = Xi.colptr
|
||
rowvalXi = Xi.rowval
|
||
nzvalXi = Xi.nzval
|
||
|
||
for k=ptr_res:(ptr_res + col_length)
|
||
@inbounds rowval[k] = rowvalXi[ptr_Xi] + mX_sofar
|
||
@inbounds nzval[k] = nzvalXi[ptr_Xi]
|
||
ptr_Xi += 1
|
||
end
|
||
end
|
||
|
||
|
||
function hcat(X::SparseMatrixCSC...)
|
||
num = length(X)
|
||
mX = Int[ size(x, 1) for x in X ]
|
||
nX = Int[ size(x, 2) for x in X ]
|
||
m = mX[1]
|
||
for i = 2 : num
|
||
if mX[i] != m; throw(DimensionMismatch("")); end
|
||
end
|
||
n = sum(nX)
|
||
|
||
Tv = promote_eltype(X...)
|
||
Ti = promote_eltype(map(x->x.rowval, X)...)
|
||
|
||
colptr = Vector{Ti}(n+1)
|
||
nnzX = Int[ nnz(x) for x in X ]
|
||
nnz_res = sum(nnzX)
|
||
rowval = Vector{Ti}(nnz_res)
|
||
nzval = Vector{Tv}(nnz_res)
|
||
|
||
nnz_sofar = 0
|
||
nX_sofar = 0
|
||
@inbounds for i = 1 : num
|
||
XI = X[i]
|
||
colptr[(1 : nX[i] + 1) + nX_sofar] = XI.colptr .+ nnz_sofar
|
||
if nnzX[i] == length(XI.rowval)
|
||
rowval[(1 : nnzX[i]) + nnz_sofar] = XI.rowval
|
||
nzval[(1 : nnzX[i]) + nnz_sofar] = XI.nzval
|
||
else
|
||
rowval[(1 : nnzX[i]) + nnz_sofar] = XI.rowval[1:nnzX[i]]
|
||
nzval[(1 : nnzX[i]) + nnz_sofar] = XI.nzval[1:nnzX[i]]
|
||
end
|
||
nnz_sofar += nnzX[i]
|
||
nX_sofar += nX[i]
|
||
end
|
||
|
||
SparseMatrixCSC(m, n, colptr, rowval, nzval)
|
||
end
|
||
|
||
"""
|
||
blkdiag(A...)
|
||
|
||
Concatenate matrices block-diagonally. Currently only implemented for sparse matrices.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> blkdiag(speye(3), 2*speye(2))
|
||
5×5 SparseMatrixCSC{Float64,Int64} with 5 stored entries:
|
||
[1, 1] = 1.0
|
||
[2, 2] = 1.0
|
||
[3, 3] = 1.0
|
||
[4, 4] = 2.0
|
||
[5, 5] = 2.0
|
||
```
|
||
"""
|
||
function blkdiag(X::SparseMatrixCSC...)
|
||
num = length(X)
|
||
mX = Int[ size(x, 1) for x in X ]
|
||
nX = Int[ size(x, 2) for x in X ]
|
||
m = sum(mX)
|
||
n = sum(nX)
|
||
|
||
Tv = promote_type(map(x->eltype(x.nzval), X)...)
|
||
Ti = promote_type(map(x->eltype(x.rowval), X)...)
|
||
|
||
colptr = Vector{Ti}(n+1)
|
||
nnzX = Int[ nnz(x) for x in X ]
|
||
nnz_res = sum(nnzX)
|
||
rowval = Vector{Ti}(nnz_res)
|
||
nzval = Vector{Tv}(nnz_res)
|
||
|
||
nnz_sofar = 0
|
||
nX_sofar = 0
|
||
mX_sofar = 0
|
||
for i = 1 : num
|
||
colptr[(1 : nX[i] + 1) + nX_sofar] = X[i].colptr .+ nnz_sofar
|
||
rowval[(1 : nnzX[i]) + nnz_sofar] = X[i].rowval .+ mX_sofar
|
||
nzval[(1 : nnzX[i]) + nnz_sofar] = X[i].nzval
|
||
nnz_sofar += nnzX[i]
|
||
nX_sofar += nX[i]
|
||
mX_sofar += mX[i]
|
||
end
|
||
|
||
SparseMatrixCSC(m, n, colptr, rowval, nzval)
|
||
end
|
||
|
||
## Structure query functions
|
||
issymmetric(A::SparseMatrixCSC) = is_hermsym(A, identity)
|
||
|
||
ishermitian(A::SparseMatrixCSC) = is_hermsym(A, conj)
|
||
|
||
function is_hermsym(A::SparseMatrixCSC, check::Function)
|
||
m, n = size(A)
|
||
if m != n; return false; end
|
||
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
nzval = A.nzval
|
||
tracker = copy(A.colptr)
|
||
for col = 1:A.n
|
||
# `tracker` is updated such that, for symmetric matrices,
|
||
# the loop below starts from an element at or below the
|
||
# diagonal element of column `col`"
|
||
for p = tracker[col]:colptr[col+1]-1
|
||
val = nzval[p]
|
||
row = rowval[p]
|
||
|
||
# Ignore stored zeros
|
||
if val == 0
|
||
continue
|
||
end
|
||
|
||
# If the matrix was symmetric we should have updated
|
||
# the tracker to start at the diagonal or below. Here
|
||
# we are above the diagonal so the matrix can't be symmetric.
|
||
if row < col
|
||
return false
|
||
end
|
||
|
||
# Diagonal element
|
||
if row == col
|
||
if val != check(val)
|
||
return false
|
||
end
|
||
else
|
||
offset = tracker[row]
|
||
|
||
# If the matrix is unsymmetric, there might not exist
|
||
# a rowval[offset]
|
||
if offset > length(rowval)
|
||
return false
|
||
end
|
||
|
||
row2 = rowval[offset]
|
||
|
||
# row2 can be less than col if the tracker didn't
|
||
# get updated due to stored zeros in previous elements.
|
||
# We therefore "catch up" here while making sure that
|
||
# the elements are actually zero.
|
||
while row2 < col
|
||
if nzval[offset] != 0
|
||
return false
|
||
end
|
||
offset += 1
|
||
row2 = rowval[offset]
|
||
tracker[row] += 1
|
||
end
|
||
|
||
# Non zero A[i,j] exists but A[j,i] does not exist
|
||
if row2 > col
|
||
return false
|
||
end
|
||
|
||
# A[i,j] and A[j,i] exists
|
||
if row2 == col
|
||
if val != check(nzval[offset])
|
||
return false
|
||
end
|
||
tracker[row] += 1
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
function istriu(A::SparseMatrixCSC)
|
||
m, n = size(A)
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
nzval = A.nzval
|
||
|
||
for col = 1:min(n, m-1)
|
||
l1 = colptr[col+1]-1
|
||
for i = 0 : (l1 - colptr[col])
|
||
if rowval[l1-i] <= col
|
||
break
|
||
end
|
||
if nzval[l1-i] != 0
|
||
return false
|
||
end
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
function istril(A::SparseMatrixCSC)
|
||
m, n = size(A)
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
nzval = A.nzval
|
||
|
||
for col = 2:n
|
||
for i = colptr[col] : (colptr[col+1]-1)
|
||
if rowval[i] >= col
|
||
break
|
||
end
|
||
if nzval[i] != 0
|
||
return false
|
||
end
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
# Create a sparse diagonal matrix by specifying multiple diagonals
|
||
# packed into a tuple, alongside their diagonal offsets and matrix shape
|
||
|
||
function spdiagm_internal(B, d)
|
||
ndiags = length(d)
|
||
if length(B) != ndiags; throw(ArgumentError("first argument should be a tuple of length(d)=$ndiags arrays of diagonals")); end
|
||
ncoeffs = 0
|
||
for vec in B
|
||
ncoeffs += length(vec)
|
||
end
|
||
I = Vector{Int}(ncoeffs)
|
||
J = Vector{Int}(ncoeffs)
|
||
V = Vector{promote_type(map(eltype, B)...)}(ncoeffs)
|
||
id = 0
|
||
i = 0
|
||
for vec in B
|
||
id += 1
|
||
diag = d[id]
|
||
numel = length(vec)
|
||
if diag < 0
|
||
row = -diag
|
||
col = 0
|
||
elseif diag > 0
|
||
row = 0
|
||
col = diag
|
||
else
|
||
row = 0
|
||
col = 0
|
||
end
|
||
range = 1+i:numel+i
|
||
I[range] = row+1:row+numel
|
||
J[range] = col+1:col+numel
|
||
copy!(view(V, range), vec)
|
||
i += numel
|
||
end
|
||
|
||
return (I,J,V)
|
||
end
|
||
|
||
"""
|
||
spdiagm(B, d[, m, n])
|
||
|
||
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.
|
||
|
||
# Example
|
||
```jldoctest
|
||
julia> spdiagm(([1,2,3,4],[4,3,2,1]),(-1,1))
|
||
5×5 SparseMatrixCSC{Int64,Int64} with 8 stored entries:
|
||
[2, 1] = 1
|
||
[1, 2] = 4
|
||
[3, 2] = 2
|
||
[2, 3] = 3
|
||
[4, 3] = 3
|
||
[3, 4] = 2
|
||
[5, 4] = 4
|
||
[4, 5] = 1
|
||
```
|
||
"""
|
||
function spdiagm(B, d, m::Integer, n::Integer)
|
||
(I,J,V) = spdiagm_internal(B, d)
|
||
return sparse(I,J,V,m,n)
|
||
end
|
||
|
||
function spdiagm(B, d)
|
||
(I,J,V) = spdiagm_internal(B, d)
|
||
return sparse(I,J,V)
|
||
end
|
||
|
||
spdiagm(B::AbstractVector, d::Number, m::Integer, n::Integer) = spdiagm((B,), (d,), m, n)
|
||
|
||
spdiagm(B::AbstractVector, d::Number=0) = spdiagm((B,), (d,))
|
||
|
||
## expand a colptr or rowptr into a dense index vector
|
||
function expandptr(V::Vector{<:Integer})
|
||
if V[1] != 1 throw(ArgumentError("first index must be one")) end
|
||
res = similar(V, (Int64(V[end]-1),))
|
||
for i in 1:(length(V)-1), j in V[i]:(V[i+1] - 1); res[j] = i end
|
||
res
|
||
end
|
||
|
||
## diag and related using an iterator
|
||
|
||
mutable struct SpDiagIterator{Tv,Ti}
|
||
A::SparseMatrixCSC{Tv,Ti}
|
||
n::Int
|
||
end
|
||
SpDiagIterator(A::SparseMatrixCSC) = SpDiagIterator(A,minimum(size(A)))
|
||
|
||
length(d::SpDiagIterator) = d.n
|
||
start(d::SpDiagIterator) = 1
|
||
done(d::SpDiagIterator, j) = j > d.n
|
||
|
||
function next(d::SpDiagIterator{Tv}, j) where Tv
|
||
A = d.A
|
||
r1 = Int(A.colptr[j])
|
||
r2 = Int(A.colptr[j+1]-1)
|
||
(r1 > r2) && (return (zero(Tv), j+1))
|
||
r1 = searchsortedfirst(A.rowval, j, r1, r2, Forward)
|
||
(((r1 > r2) || (A.rowval[r1] != j)) ? zero(Tv) : A.nzval[r1], j+1)
|
||
end
|
||
|
||
function trace{Tv}(A::SparseMatrixCSC{Tv})
|
||
if size(A,1) != size(A,2)
|
||
throw(DimensionMismatch("expected square matrix"))
|
||
end
|
||
s = zero(Tv)
|
||
for d in SpDiagIterator(A)
|
||
s += d
|
||
end
|
||
s
|
||
end
|
||
|
||
diag{Tv}(A::SparseMatrixCSC{Tv}) = Tv[d for d in SpDiagIterator(A)]
|
||
|
||
function diagm{Tv,Ti}(v::SparseMatrixCSC{Tv,Ti})
|
||
if (size(v,1) != 1 && size(v,2) != 1)
|
||
throw(DimensionMismatch("input should be nx1 or 1xn"))
|
||
end
|
||
|
||
n = length(v)
|
||
numnz = nnz(v)
|
||
colptr = Vector{Ti}(n+1)
|
||
rowval = Vector{Ti}(numnz)
|
||
nzval = Vector{Tv}(numnz)
|
||
|
||
if size(v,1) == 1
|
||
copy!(colptr, 1, v.colptr, 1, n+1)
|
||
ptr = 1
|
||
for col = 1:n
|
||
if colptr[col] != colptr[col+1]
|
||
rowval[ptr] = col
|
||
nzval[ptr] = v.nzval[ptr]
|
||
ptr += 1
|
||
end
|
||
end
|
||
else
|
||
copy!(rowval, 1, v.rowval, 1, numnz)
|
||
copy!(nzval, 1, v.nzval, 1, numnz)
|
||
colptr[1] = 1
|
||
ptr = 1
|
||
col = 1
|
||
while col <= n && ptr <= numnz
|
||
while rowval[ptr] > col
|
||
colptr[col+1] = colptr[col]
|
||
col += 1
|
||
end
|
||
colptr[col+1] = colptr[col] + 1
|
||
ptr += 1
|
||
col += 1
|
||
end
|
||
if col <= n
|
||
colptr[(col+1):(n+1)] = colptr[col]
|
||
end
|
||
end
|
||
|
||
return SparseMatrixCSC(n, n, colptr, rowval, nzval)
|
||
end
|
||
|
||
# Sort all the indices in each column of a CSC sparse matrix
|
||
# sortSparseMatrixCSC!(A, sortindices = :sortcols) # Sort each column with sort()
|
||
# sortSparseMatrixCSC!(A, sortindices = :doubletranspose) # Sort with a double transpose
|
||
function sortSparseMatrixCSC!(A::SparseMatrixCSC{Tv,Ti}; sortindices::Symbol = :sortcols) where {Tv,Ti}
|
||
if sortindices == :doubletranspose
|
||
nB, mB = size(A)
|
||
B = SparseMatrixCSC(mB, nB, Vector{Ti}(nB+1), similar(A.rowval), similar(A.nzval))
|
||
transpose!(B, A)
|
||
transpose!(A, B)
|
||
return A
|
||
end
|
||
|
||
m, n = size(A)
|
||
colptr = A.colptr; rowval = A.rowval; nzval = A.nzval
|
||
|
||
index = zeros(Ti, m)
|
||
row = zeros(Ti, m)
|
||
val = zeros(Tv, m)
|
||
|
||
for i = 1:n
|
||
@inbounds col_start = colptr[i]
|
||
@inbounds col_end = (colptr[i+1] - 1)
|
||
|
||
numrows = col_end - col_start + 1
|
||
if numrows <= 1
|
||
continue
|
||
elseif numrows == 2
|
||
f = col_start
|
||
s = f+1
|
||
if rowval[f] > rowval[s]
|
||
@inbounds rowval[f], rowval[s] = rowval[s], rowval[f]
|
||
@inbounds nzval[f], nzval[s] = nzval[s], nzval[f]
|
||
end
|
||
continue
|
||
end
|
||
|
||
jj = 1
|
||
@simd for j = col_start:col_end
|
||
@inbounds row[jj] = rowval[j]
|
||
@inbounds val[jj] = nzval[j]
|
||
jj += 1
|
||
end
|
||
|
||
sortperm!(unsafe_wrap(Vector{Ti}, pointer(index), numrows),
|
||
unsafe_wrap(Vector{Ti}, pointer(row), numrows))
|
||
|
||
jj = 1
|
||
@simd for j = col_start:col_end
|
||
@inbounds rowval[j] = row[index[jj]]
|
||
@inbounds nzval[j] = val[index[jj]]
|
||
jj += 1
|
||
end
|
||
end
|
||
|
||
return A
|
||
end
|
||
|
||
## rotations
|
||
|
||
function rot180(A::SparseMatrixCSC)
|
||
I,J,V = findnz(A)
|
||
m,n = size(A)
|
||
for i=1:length(I)
|
||
I[i] = m - I[i] + 1
|
||
J[i] = n - J[i] + 1
|
||
end
|
||
return sparse(I,J,V,m,n)
|
||
end
|
||
|
||
function rotr90(A::SparseMatrixCSC)
|
||
I,J,V = findnz(A)
|
||
m,n = size(A)
|
||
#old col inds are new row inds
|
||
for i=1:length(I)
|
||
I[i] = m - I[i] + 1
|
||
end
|
||
return sparse(J, I, V, n, m)
|
||
end
|
||
|
||
function rotl90(A::SparseMatrixCSC)
|
||
I,J,V = findnz(A)
|
||
m,n = size(A)
|
||
#old row inds are new col inds
|
||
for i=1:length(J)
|
||
J[i] = n - J[i] + 1
|
||
end
|
||
return sparse(J, I, V, n, m)
|
||
end
|
||
|
||
## hashing
|
||
|
||
# End the run and return the current hash
|
||
@inline function hashrun(val, runlength::Int, h::UInt)
|
||
if runlength == 0
|
||
return h
|
||
elseif runlength > 1
|
||
h += Base.hashrle_seed
|
||
h = hash(runlength, h)
|
||
end
|
||
hash(val, h)
|
||
end
|
||
|
||
function hash(A::SparseMatrixCSC{T}, h::UInt) where T
|
||
h += Base.hashaa_seed
|
||
sz = size(A)
|
||
h += hash(sz)
|
||
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
nzval = A.nzval
|
||
lastidx = 0
|
||
runlength = 0
|
||
lastnz = zero(T)
|
||
@inbounds for col = 1:size(A, 2)
|
||
for j = colptr[col]:colptr[col+1]-1
|
||
nz = nzval[j]
|
||
isequal(nz, zero(T)) && continue
|
||
idx = sub2ind(sz, rowval[j], col)
|
||
if idx != lastidx+1 || !isequal(nz, lastnz) # Run is over
|
||
h = hashrun(lastnz, runlength, h) # Hash previous run
|
||
h = hashrun(0, idx-lastidx-1, h) # Hash intervening zeros
|
||
|
||
runlength = 1
|
||
lastnz = nz
|
||
else
|
||
runlength += 1
|
||
end
|
||
lastidx = idx
|
||
end
|
||
end
|
||
h = hashrun(lastnz, runlength, h) # Hash previous run
|
||
hashrun(0, length(A)-lastidx, h) # Hash zeros at end
|
||
end
|
||
|
||
## Statistics
|
||
|
||
# This is the function that does the reduction underlying var/std
|
||
function Base.centralize_sumabs2!(R::AbstractArray{S}, A::SparseMatrixCSC{Tv,Ti}, means::AbstractArray) where {S,Tv,Ti}
|
||
lsiz = Base.check_reducedims(R,A)
|
||
size(means) == size(R) || error("size of means must match size of R")
|
||
isempty(R) || fill!(R, zero(S))
|
||
isempty(A) && return R
|
||
|
||
colptr = A.colptr
|
||
rowval = A.rowval
|
||
nzval = A.nzval
|
||
m = size(A, 1)
|
||
n = size(A, 2)
|
||
|
||
if size(R, 1) == size(R, 2) == 1
|
||
# Reduction along both columns and rows
|
||
R[1, 1] = Base.centralize_sumabs2(A, means[1])
|
||
elseif size(R, 1) == 1
|
||
# Reduction along rows
|
||
@inbounds for col = 1:n
|
||
mu = means[col]
|
||
r = convert(S, (m-colptr[col+1]+colptr[col])*abs2(mu))
|
||
@simd for j = colptr[col]:colptr[col+1]-1
|
||
r += abs2(nzval[j] - mu)
|
||
end
|
||
R[1, col] = r
|
||
end
|
||
elseif size(R, 2) == 1
|
||
# Reduction along columns
|
||
rownz = fill(convert(Ti, n), m)
|
||
@inbounds for col = 1:n
|
||
@simd for j = colptr[col]:colptr[col+1]-1
|
||
row = rowval[j]
|
||
R[row, 1] += abs2(nzval[j] - means[row])
|
||
rownz[row] -= 1
|
||
end
|
||
end
|
||
for i = 1:m
|
||
R[i, 1] += rownz[i]*abs2(means[i])
|
||
end
|
||
else
|
||
# Reduction along a dimension > 2
|
||
@inbounds for col = 1:n
|
||
lastrow = 0
|
||
@simd for j = colptr[col]:colptr[col+1]-1
|
||
row = rowval[j]
|
||
for i = lastrow+1:row-1
|
||
R[i, col] = abs2(means[i, col])
|
||
end
|
||
R[row, col] = abs2(nzval[j] - means[row, col])
|
||
lastrow = row
|
||
end
|
||
for i = lastrow+1:m
|
||
R[i, col] = abs2(means[i, col])
|
||
end
|
||
end
|
||
end
|
||
return R
|
||
end
|
||
|
||
## Uniform matrix arithmetic
|
||
|
||
(+)(A::SparseMatrixCSC, J::UniformScaling) = A + J.λ * speye(A)
|
||
(-)(A::SparseMatrixCSC, J::UniformScaling) = A - J.λ * speye(A)
|
||
(-)(J::UniformScaling, A::SparseMatrixCSC) = J.λ * speye(A) - A
|