From d98b166a2f02ed374309de38a0a530ab09e3825e Mon Sep 17 00:00:00 2001 From: Sacha Verweij Date: Mon, 16 Oct 2017 12:45:01 -0700 Subject: [PATCH] Make similar for sparse matrices more consistent and comprehensive. Makes similar methods for SparseMatrixCSC more consistent both among themselves and with similar methods for other types. Also fleshes those methods out in full. --- base/sparse/sparsematrix.jl | 44 +++++++++++---- test/sparse/sparse.jl | 105 +++++++++++++++++++++++++++++------- 2 files changed, 120 insertions(+), 29 deletions(-) diff --git a/base/sparse/sparsematrix.jl b/base/sparse/sparsematrix.jl index b226c9754683d..be2845048e2ab 100644 --- a/base/sparse/sparsematrix.jl +++ b/base/sparse/sparsematrix.jl @@ -313,17 +313,41 @@ function copy!(A::SparseMatrixCSC, B::SparseMatrixCSC) 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 +## similar +# +# parent method for similar that preserves stored-entry structure (for when new and old dims match) +function _sparsesimilar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}) where {TvNew,TiNew} + newcolptr = copy!(similar(S.colptr, TiNew), S.colptr) + newrowval = copy!(similar(S.rowval, TiNew), S.rowval) + return SparseMatrixCSC(S.m, S.n, newcolptr, newrowval, similar(S.nzval, TvNew)) +end +# parent methods for similar that preserves only storage space (for when new and old dims differ) +_sparsesimilar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, dims::Dims{2}) where {TvNew,TiNew} = + SparseMatrixCSC(dims..., ones(TiNew, last(dims)+1), similar(S.rowval, TiNew), similar(S.nzval, TvNew)) +# parent method for similar that allocates an empty sparse vector (when new dims are single) +_sparsesimilar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, dims::Dims{1}) where {TvNew,TiNew} = + SparseVector(dims..., similar(S.rowval, TiNew, 0), similar(S.nzval, TvNew, 0)) +# +# The following methods hook into the AbstractArray similar hierarchy. The first method +# covers similar(A[, Tv]) calls, which preserve stored-entry structure, and the latter +# methods cover similar(A[, Tv], shape...) calls, which preserve storage space when the shape +# calls for a two-dimensional result. +similar(S::SparseMatrixCSC{<:Any,Ti}, ::Type{TvNew}) where {Ti,TvNew} = _sparsesimilar(S, TvNew, Ti) +similar(S::SparseMatrixCSC{<:Any,Ti}, ::Type{TvNew}, dims::Union{Dims{1},Dims{2}}) where {Ti,TvNew} = + _sparsesimilar(S, TvNew, Ti, dims) +# The following methods cover similar(A, Tv, Ti[, shape...]) calls, which specify the +# result's index type in addition to its entry type, and aren't covered by the hooks above. +# The calls without shape again preserve stored-entry structure, whereas those with shape +# preserve storage space when the shape calls for a two-dimensional result. +similar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}) where{TvNew,TiNew} = + _sparsesimilar(S, TvNew, TiNew) +similar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, dims::Union{Dims{1},Dims{2}}) where {TvNew,TiNew} = + _sparsesimilar(S, TvNew, TiNew, dims) +similar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, m::Integer) where {TvNew,TiNew} = + _sparsesimilar(S, TvNew, TiNew, (m,)) +similar(S::SparseMatrixCSC, ::Type{TvNew}, ::Type{TiNew}, m::Integer, n::Integer) where {TvNew,TiNew} = + _sparsesimilar(S, TvNew, TiNew, (m, n)) -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 diff --git a/test/sparse/sparse.jl b/test/sparse/sparse.jl index f5e815a22b370..175cb59beb48e 100644 --- a/test/sparse/sparse.jl +++ b/test/sparse/sparse.jl @@ -1302,16 +1302,6 @@ end @test_throws ArgumentError squeeze(A,(1, 1)) end -@testset "similar with type conversion" begin - local A = speye(5) - @test size(similar(A, Complex128, Int)) == (5, 5) - @test typeof(similar(A, Complex128, Int)) == SparseMatrixCSC{Complex128, Int} - @test size(similar(A, Complex128, Int8)) == (5, 5) - @test typeof(similar(A, Complex128, Int8)) == SparseMatrixCSC{Complex128, Int8} - @test similar(A, Complex128,(6, 6)) == spzeros(Complex128, 6, 6) - @test convert(Matrix, A) == Array(A) -end - @testset "float" begin local A A = sprand(Bool, 5, 5, 0.0) @@ -1945,15 +1935,6 @@ end " [2, 1] = 2.0\n [3, 2] = 3.0\n [4, 2] = 4.0\n [5, 3] = 5.0\n [6, 3] = 6.0") end -@testset "similar aliasing" begin - a = sparse(rand(3,3) .+ 0.1) - b = similar(a, Float32, Int32) - c = similar(b, Float32, Int32) - Base.SparseArrays.dropstored!(b, 1, 1) - @test length(c.rowval) == 9 - @test length(c.nzval) == 9 -end - @testset "check buffers" for n in 1:3 local A colptr = [1,2,3,4] @@ -2035,3 +2016,89 @@ end @test isfinite.(cov_sparse) == isfinite.(cov_dense) end end + +@testset "similar should not alias the input sparse array" begin + a = sparse(rand(3,3) .+ 0.1) + b = similar(a, Float32, Int32) + c = similar(b, Float32, Int32) + Base.SparseArrays.dropstored!(b, 1, 1) + @test length(c.rowval) == 9 + @test length(c.nzval) == 9 +end + +@testset "similar with type conversion" begin + local A = speye(5) + @test size(similar(A, Complex128, Int)) == (5, 5) + @test typeof(similar(A, Complex128, Int)) == SparseMatrixCSC{Complex128, Int} + @test size(similar(A, Complex128, Int8)) == (5, 5) + @test typeof(similar(A, Complex128, Int8)) == SparseMatrixCSC{Complex128, Int8} + @test similar(A, Complex128,(6, 6)) == spzeros(Complex128, 6, 6) + @test convert(Matrix, A) == Array(A) # lolwut, are you lost, test? +end + +@testset "similar for SparseMatrixCSC" begin + A = speye(5) + # test similar without specifications (preserves stored-entry structure) + simA = similar(A) + @test typeof(simA) == typeof(A) + @test size(simA) == size(A) + @test simA.colptr == A.colptr + @test simA.rowval == A.rowval + @test length(simA.nzval) == length(A.nzval) + # test similar with entry type specification (preserves stored-entry structure) + simA = similar(A, Float32) + @test typeof(simA) == SparseMatrixCSC{Float32,eltype(A.colptr)} + @test size(simA) == size(A) + @test simA.colptr == A.colptr + @test simA.rowval == A.rowval + @test length(simA.nzval) == length(A.nzval) + # test similar with entry and index type specification (preserves stored-entry structure) + simA = similar(A, Float32, Int8) + @test typeof(simA) == SparseMatrixCSC{Float32,Int8} + @test size(simA) == size(A) + @test simA.colptr == A.colptr + @test simA.rowval == A.rowval + @test length(simA.nzval) == length(A.nzval) + # test similar with Dims{2} specification (preserves storage space only, not stored-entry structure) + simA = similar(A, (6,6)) + @test typeof(simA) == typeof(A) + @test size(simA) == (6,6) + @test simA.colptr == ones(eltype(A.colptr), 6+1) + @test length(simA.rowval) == length(A.rowval) + @test length(simA.nzval) == length(A.nzval) + # test similar with entry type and Dims{2} specification (preserves storage space only) + simA = similar(A, Float32, (6,6)) + @test typeof(simA) == SparseMatrixCSC{Float32,eltype(A.colptr)} + @test size(simA) == (6,6) + @test simA.colptr == ones(eltype(A.colptr), 6+1) + @test length(simA.rowval) == length(A.rowval) + @test length(simA.nzval) == length(A.nzval) + # test similar with entry type, index type, and Dims{2} specification (preserves storage space only) + simA = similar(A, Float32, Int8, (6,6)) + @test typeof(simA) == SparseMatrixCSC{Float32, Int8} + @test size(simA) == (6,6) + @test simA.colptr == ones(eltype(A.colptr), 6+1) + @test length(simA.rowval) == length(A.rowval) + @test length(simA.nzval) == length(A.nzval) + # test similar with Dims{1} specification (preserves nothing) + simA = similar(A, (6,)) + @test typeof(simA) == SparseVector{eltype(A.nzval),eltype(A.colptr)} + @test size(simA) == (6,) + @test length(simA.nzind) == 0 + @test length(simA.nzval) == 0 + # test similar with entry type and Dims{1} specification (preserves nothing) + simA = similar(A, Float32, (6,)) + @test typeof(simA) == SparseVector{Float32,eltype(A.colptr)} + @test size(simA) == (6,) + @test length(simA.nzind) == 0 + @test length(simA.nzval) == 0 + # test similar with entry type, index type, and Dims{1} specification (preserves nothing) + simA = similar(A, Float32, Int8, (6,)) + @test typeof(simA) == SparseVector{Float32,Int8} + @test size(simA) == (6,) + @test length(simA.nzind) == 0 + @test length(simA.nzval) == 0 + # test entry points to similar with entry type, index type, and non-Dims shape specification + @test similar(A, Float32, Int8, 6, 6) == similar(A, Float32, Int8, (6, 6)) + @test similar(A, Float32, Int8, 6) == similar(A, Float32, Int8, (6,)) +end