Skip to content

Commit 2632257

Browse files
authored
Add SparseArrays.spzeros! for pattern creation with allocated buffers (#315)
Add `SparseArrays.spzeros!` for pattern creation with allocated buffers This patch adds `SparseArrays.spzeros!`, which is to `spzeros` what `SparseArrays.sparse!` is to `sparse`. This reverts the changes in `sparse` from #284 and instead hook up `spzeros`, and the new `SparseArrays.spzeros!` to `SparseArrays.sparse!` directly.
1 parent 85a381b commit 2632257

File tree

2 files changed

+105
-19
lines changed

2 files changed

+105
-19
lines changed

src/sparsematrix.jl

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -997,16 +997,14 @@ julia> sparse(Is, Js, Vs)
997997
⋅ ⋅ 3
998998
```
999999
"""
1000-
sparse(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::AbstractVector{Tv}, m::Integer, n::Integer, combine) where {Tv,Ti<:Integer} =
1001-
_sparse(I, J, V, m, n, combine)
1002-
1003-
function _sparse(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::Union{Tv,AbstractVector{Tv}}, m::Integer, n::Integer, combine) where {Tv,Ti<:Integer}
1000+
function sparse(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::AbstractVector{Tv}, m::Integer, n::Integer, combine) where {Tv,Ti<:Integer}
10041001
require_one_based_indexing(I, J, V)
10051002
coolen = length(I)
1006-
length(J) == coolen || throw(ArgumentError("J (= $(length(J))) need length == length(I) = $coolen"))
1007-
only_sparsity_pattern = (V isa Tv && iszero(V)) # We can use an optimised version if we only care about the sparsity pattern (and not the values)
1008-
only_sparsity_pattern || length(V) == coolen || throw(ArgumentError("V (= $(length(V))) need length == length(I) = $coolen"))
1009-
1003+
if length(J) != coolen || length(V) != coolen
1004+
throw(ArgumentError(string("the first three arguments' lengths must match, ",
1005+
"length(I) (=$(length(I))) == length(J) (= $(length(J))) == length(V) (= ",
1006+
"$(length(V)))")))
1007+
end
10101008
if Base.hastypemax(Ti) && coolen >= typemax(Ti)
10111009
throw(ArgumentError("the index type $Ti cannot hold $coolen elements; use a larger index type"))
10121010
end
@@ -1023,7 +1021,7 @@ function _sparse(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::Union{Tv,Abstr
10231021
# Allocate storage for CSR form
10241022
csrrowptr = Vector{Ti}(undef, m+1)
10251023
csrcolval = Vector{Ti}(undef, coolen)
1026-
csrnzval = Vector{Tv}(undef, only_sparsity_pattern ? 0 : coolen)
1024+
csrnzval = Vector{Tv}(undef, coolen)
10271025

10281026
# Allocate storage for the CSC form's column pointers and a necessary workspace
10291027
csccolptr = Vector{Ti}(undef, n+1)
@@ -1091,15 +1089,21 @@ F. Gustavson, "Two fast algorithms for sparse matrices: multiplication and permu
10911089
transposition," ACM TOMS 4(3), 250-269 (1978) inspired this method's use of a pair of
10921090
counting sorts.
10931091
"""
1094-
function sparse!(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::Union{Tv,AbstractVector{Tv}},
1092+
function sparse!(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::AbstractVector{Tv},
10951093
m::Integer, n::Integer, combine, klasttouch::Vector{Tj},
10961094
csrrowptr::Vector{Tj}, csrcolval::Vector{Ti}, csrnzval::Vector{Tv},
10971095
csccolptr::Vector{Ti}, cscrowval::Vector{Ti}, cscnzval::Vector{Tv}) where {Tv,Ti<:Integer,Tj<:Integer}
10981096

10991097
require_one_based_indexing(I, J, V)
11001098
sparse_check_Ti(m, n, Ti)
11011099
sparse_check_length("I", I, 0, Tj)
1102-
only_sparsity_pattern = (V isa Tv && iszero(V)) # We can use an optimised version if we only care about the sparsity pattern (and not the values)
1100+
1101+
# This method is also used internally by spzeros! to build the sparsity pattern without
1102+
# caring about the values. This is communicated by passing combine=nothing and in this
1103+
# case V and csrnzval should *not* be accessed. When called from spzeros! they will both
1104+
# alias cscnzval, which will be resized and filled with zero(Tv).
1105+
only_sparsity_pattern = combine === nothing
1106+
11031107
# Compute the CSR form's row counts and store them shifted forward by one in csrrowptr
11041108
fill!(csrrowptr, Tj(0))
11051109
coolen = length(I)
@@ -1168,11 +1172,9 @@ function sparse!(I::AbstractVector{Ti}, J::AbstractVector{Ti}, V::Union{Tv,Abstr
11681172
end
11691173
writek += Tj(1)
11701174
csccolptr[j+1] += Ti(1)
1171-
else
1175+
elseif !only_sparsity_pattern
11721176
klt = klasttouch[j]
1173-
if !only_sparsity_pattern
1174-
csrnzval[klt] = combine(csrnzval[klt], csrnzval[readk])
1175-
end
1177+
csrnzval[klt] = combine(csrnzval[klt], csrnzval[readk])
11761178
end
11771179
end
11781180
newcsrrowptri = writek
@@ -2031,14 +2033,49 @@ Create a sparse matrix `S` of dimensions `m x n` with structural zeros at `S[I[k
20312033
This method can be used to construct the sparsity pattern of the matrix, and is more
20322034
efficient than using e.g. `sparse(I, J, zeros(length(I)))`.
20332035
2036+
For additional documentation and an expert driver, see `SparseArrays.spzeros!`.
2037+
20342038
!!! compat "Julia 1.10"
20352039
This methods requires Julia version 1.10 or later.
20362040
"""
20372041
spzeros(I::AbstractVector, J::AbstractVector) = spzeros(Float64, I, J)
20382042
spzeros(I::AbstractVector, J::AbstractVector, m::Integer, n::Integer) = spzeros(Float64, I, J, m, n)
20392043
spzeros(::Type{Tv}, I::AbstractVector, J::AbstractVector) where {Tv} = spzeros(Tv, I, J, dimlub(I), dimlub(J))
2040-
spzeros(::Type{Tv}, I::AbstractVector, J::AbstractVector, m::Integer, n::Integer) where {Tv} =
2041-
_sparse(I, J, zero(Tv), Int(m), Int(n), +)
2044+
function spzeros(::Type{Tv}, I::AbstractVector, J::AbstractVector, m::Integer, n::Integer) where {Tv}
2045+
return spzeros(Tv, AbstractVector{Int}(I), AbstractVector{Int}(J), m, n)
2046+
end
2047+
function spzeros(::Type{Tv}, I::AbstractVector{Ti}, J::AbstractVector{Ti}, m::Integer, n::Integer) where {Tv, Ti<:Integer}
2048+
if length(I) != length(J)
2049+
throw(ArgumentError("length(I) = $(length(I)) does not match length(J) = $(length(J))"))
2050+
end
2051+
klasttouch = Vector{Ti}(undef, n)
2052+
csrrowptr = Vector{Ti}(undef, m+1)
2053+
csrcolval = Vector{Ti}(undef, length(I))
2054+
return spzeros!(Tv, I, J, m, n, klasttouch, csrrowptr, csrcolval)
2055+
end
2056+
2057+
"""
2058+
spzeros!(::Type{Tv}, I::AbstractVector{Ti}, J::AbstractVector{Ti}, m::Integer, n::Integer,
2059+
klasttouch::Vector{Ti}, csrrowptr::Vector{Ti}, csrcolval::Vector{Ti},
2060+
[csccolptr::Vector{Ti}], [cscrowval::Vector{Ti}, cscnzval::Vector{Tv}]) where {Tv,Ti<:Integer}
2061+
2062+
Parent of and expert driver for `spzeros(I, J)` allowing user to provide preallocated
2063+
storage for intermediate objects. This method is to `spzeros` what `SparseArrays.sparse!` is
2064+
to `sparse`. See documentation for `SparseArrays.sparse!` for details and required buffer
2065+
lengths.
2066+
2067+
!!! compat "Julia 1.10"
2068+
This methods requires Julia version 1.10 or later.
2069+
"""
2070+
function spzeros!(::Type{Tv}, I::AbstractVector{Ti}, J::AbstractVector{Ti}, m::Integer, n::Integer,
2071+
klasttouch::Vector{Ti}, csrrowptr::Vector{Ti}, csrcolval::Vector{Ti},
2072+
csccolptr::Vector{Ti}=Ti[], cscrowval::Vector{Ti}=Ti[], cscnzval::Vector{Tv}=Tv[]
2073+
) where {Tv, Ti<:Integer}
2074+
# We can pass V = csrnzval = cscnzval since V and csrnzval are unused in sparse! if used
2075+
# to only build the sparsity pattern (which is indicated by passing combine=nothing).
2076+
return sparse!(I, J, cscnzval, m, n, nothing, klasttouch,
2077+
csrrowptr, csrcolval, cscnzval, csccolptr, cscrowval, cscnzval)
2078+
end
20422079

20432080
import Base._one
20442081
function Base._one(unit::T, S::AbstractSparseMatrixCSC) where T

test/sparsematrix_constructors_indexing.jl

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,8 +1626,8 @@ end
16261626
@test_throws ArgumentError SparseArrays.expandptr([2; 3])
16271627
end
16281628

1629-
@testset "sparse!" begin
1630-
using SparseArrays: sparse!, getcolptr, getrowval, nonzeros
1629+
@testset "sparse! and spzeros!" begin
1630+
using SparseArrays: sparse!, spzeros!, getcolptr, getrowval, nonzeros
16311631

16321632
function allocate_arrays(m, n)
16331633
N = round(Int, 0.5 * m * n)
@@ -1653,6 +1653,13 @@ end
16531653
@test S == S!
16541654
@test same_structure(S, S!)
16551655

1656+
I, J, _, klasttouch, csrrowptr, csrcolval = allocate_arrays(m, n)
1657+
S = spzeros(I, J, m, n)
1658+
S! = spzeros!(Float64, I, J, m, n, klasttouch, csrrowptr, csrcolval)
1659+
@test S == S!
1660+
@test iszero(S!)
1661+
@test same_structure(S, S!)
1662+
16561663
# Passing csr vectors + csccolptr
16571664
I, J, V, klasttouch, csrrowptr, csrcolval, csrnzval, csccolptr = allocate_arrays(m, n)
16581665
S = sparse(I, J, V, m, n)
@@ -1661,6 +1668,14 @@ end
16611668
@test same_structure(S, S!)
16621669
@test getcolptr(S!) === csccolptr
16631670

1671+
I, J, _, klasttouch, csrrowptr, csrcolval, _, csccolptr = allocate_arrays(m, n)
1672+
S = spzeros(I, J, m, n)
1673+
S! = spzeros!(Float64, I, J, m, n, klasttouch, csrrowptr, csrcolval, csccolptr)
1674+
@test S == S!
1675+
@test iszero(S!)
1676+
@test same_structure(S, S!)
1677+
@test getcolptr(S!) === csccolptr
1678+
16641679
# Passing csr vectors, and csc vectors
16651680
I, J, V, klasttouch, csrrowptr, csrcolval, csrnzval, csccolptr, cscrowval, cscnzval =
16661681
allocate_arrays(m, n)
@@ -1673,6 +1688,18 @@ end
16731688
@test getrowval(S!) === cscrowval
16741689
@test nonzeros(S!) === cscnzval
16751690

1691+
I, J, _, klasttouch, csrrowptr, csrcolval, _, csccolptr, cscrowval, cscnzval =
1692+
allocate_arrays(m, n)
1693+
S = spzeros(I, J, m, n)
1694+
S! = spzeros!(Float64, I, J, m, n, klasttouch, csrrowptr, csrcolval,
1695+
csccolptr, cscrowval, cscnzval)
1696+
@test S == S!
1697+
@test iszero(S!)
1698+
@test same_structure(S, S!)
1699+
@test getcolptr(S!) === csccolptr
1700+
@test getrowval(S!) === cscrowval
1701+
@test nonzeros(S!) === cscnzval
1702+
16761703
# Passing csr vectors, and csc vectors of insufficient lengths
16771704
I, J, V, klasttouch, csrrowptr, csrcolval, csrnzval, csccolptr, cscrowval, cscnzval =
16781705
allocate_arrays(m, n)
@@ -1685,6 +1712,18 @@ end
16851712
@test getrowval(S!) === cscrowval
16861713
@test nonzeros(S!) === cscnzval
16871714

1715+
I, J, _, klasttouch, csrrowptr, csrcolval, _, csccolptr, cscrowval, cscnzval =
1716+
allocate_arrays(m, n)
1717+
S = spzeros(I, J, m, n)
1718+
S! = spzeros!(Float64, I, J, m, n, klasttouch, csrrowptr, csrcolval,
1719+
resize!(csccolptr, 0), resize!(cscrowval, 0), resize!(cscnzval, 0))
1720+
@test S == S!
1721+
@test iszero(S!)
1722+
@test same_structure(S, S!)
1723+
@test getcolptr(S!) === csccolptr
1724+
@test getrowval(S!) === cscrowval
1725+
@test nonzeros(S!) === cscnzval
1726+
16881727
# Passing csr vectors, and csc vectors aliased with I, J, V
16891728
I, J, V, klasttouch, csrrowptr, csrcolval, csrnzval = allocate_arrays(m, n)
16901729
S = sparse(I, J, V, m, n)
@@ -1694,6 +1733,16 @@ end
16941733
@test getcolptr(S!) === I
16951734
@test getrowval(S!) === J
16961735
@test nonzeros(S!) === V
1736+
1737+
I, J, V, klasttouch, csrrowptr, csrcolval = allocate_arrays(m, n)
1738+
S = spzeros(I, J, m, n)
1739+
S! = spzeros!(Float64, I, J, m, n, klasttouch, csrrowptr, csrcolval, I, J, V)
1740+
@test S == S!
1741+
@test iszero(S!)
1742+
@test same_structure(S, S!)
1743+
@test getcolptr(S!) === I
1744+
@test getrowval(S!) === J
1745+
@test nonzeros(S!) === V
16971746
end
16981747
end
16991748

0 commit comments

Comments
 (0)