diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87fd07a3..125e0b41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: branches: - 'main' - 'release-*' + - 'backports-release-*' push: branches: - 'main' @@ -22,8 +23,8 @@ jobs: fail-fast: false matrix: version: - # - '1.6' - - 'nightly' + - '1.8' + # - 'nightly' os: - ubuntu-latest - macOS-latest @@ -63,8 +64,8 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - # version: '1.6' - version: 'nightly' + version: '1.8' + # version: 'nightly' - name: Generate docs run: | julia --color=yes -e 'write("Project.toml", replace(read("Project.toml", String), r"uuid = .*?\n" =>"uuid = \"3f01184e-e22b-5df5-ae63-d93ebab69eaf\"\n"));' diff --git a/src/SparseArrays.jl b/src/SparseArrays.jl index 873a7fb3..3c609e54 100644 --- a/src/SparseArrays.jl +++ b/src/SparseArrays.jl @@ -11,6 +11,11 @@ using Base.Sort: Forward using LinearAlgebra using LinearAlgebra: AdjOrTrans, matprod +# Temporary workaround for simplifying SparseArrays.jl upgrade in JuliaLang/julia +# to workaround circshift! bug, see https://github.com/JuliaLang/julia/pull/46759 +const CIRCSHIFT_WRONG_DIRECTION = circshift!([1, 2, 3], 1) != circshift([1, 2, 3], 1) + + import Base: +, -, *, \, /, &, |, xor, ==, zero import LinearAlgebra: mul!, ldiv!, rdiv!, cholesky, adjoint!, diag, eigen, dot, issymmetric, istril, istriu, lu, tr, transpose!, tril!, triu!, isbanded, diff --git a/src/sparsematrix.jl b/src/sparsematrix.jl index e4547cc7..48c410dc 100644 --- a/src/sparsematrix.jl +++ b/src/sparsematrix.jl @@ -3938,16 +3938,16 @@ function Base.swaprows!(A::AbstractSparseMatrixCSC, i, j) rows[rr[iidx]] = j jidx == 0 && continue rotate_range = rr[iidx]:jrange[jidx] - circshift!(@view(vals[rotate_range]), -1) - circshift!(@view(rows[rotate_range]), -1) + circshift!(@view(vals[rotate_range]), CIRCSHIFT_WRONG_DIRECTION ? -1 : 1) + circshift!(@view(rows[rotate_range]), CIRCSHIFT_WRONG_DIRECTION ? -1 : 1) else # Same as i, but in the opposite direction @assert has_j rows[jrange[jidx]] = i iidx > length(rr) && continue rotate_range = rr[iidx]:jrange[jidx] - circshift!(@view(vals[rotate_range]), 1) - circshift!(@view(rows[rotate_range]), 1) + circshift!(@view(vals[rotate_range]), CIRCSHIFT_WRONG_DIRECTION ? 1 : -1) + circshift!(@view(rows[rotate_range]), CIRCSHIFT_WRONG_DIRECTION ? 1 : -1) end end return nothing diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 1f3d0793..750096ef 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -40,6 +40,7 @@ const AdjOrTransSparseVectorUnion{Tv,Ti} = LinearAlgebra.AdjOrTrans{Tv, <:Sparse ### Basic properties +length(x::SparseVector) = getfield(x, :n) size(x::SparseVector) = (getfield(x, :n),) count(f, x::SparseVector) = count(f, nonzeros(x)) + f(zero(eltype(x)))*(length(x) - nnz(x)) @@ -1075,20 +1076,27 @@ const _Annotated_SparseConcatArrays = Union{_Triangular_SparseConcatArrays, _Sym const _SparseConcatGroup = Union{_DenseConcatGroup, _SparseConcatArrays, _Annotated_SparseConcatArrays} # Concatenations involving un/annotated sparse/special matrices/vectors should yield sparse arrays + +# the output array type is determined by the first element of the to be concatenated objects +# if this is a Number, the output would be dense by the fallback abstractarray.jl code (see cat_similar) +# so make sure that if that happens, the "array" is sparse (if more sparse arrays are involved, of course) +_sparse(x::Number) = sparsevec([1], [x], 1) +_sparse(A) = _makesparse(A) _makesparse(x::Number) = x -_makesparse(x::AbstractArray) = SparseMatrixCSC(issparse(x) ? x : sparse(x)) +_makesparse(x::AbstractVector) = convert(SparseVector, issparse(x) ? x : sparse(x))::SparseVector +_makesparse(x::AbstractMatrix) = convert(SparseMatrixCSC, issparse(x) ? x : sparse(x))::SparseMatrixCSC function Base._cat(dims, Xin::_SparseConcatGroup...) - X = map(_makesparse, Xin) + X = (_sparse(first(Xin)), map(_makesparse, Base.tail(Xin))...) T = promote_eltype(Xin...) Base.cat_t(T, X...; dims=dims) end function hcat(Xin::_SparseConcatGroup...) - X = map(_makesparse, Xin) + X = (_sparse(first(Xin)), map(_makesparse, Base.tail(Xin))...) return cat(X..., dims=Val(2)) end function vcat(Xin::_SparseConcatGroup...) - X = map(_makesparse, Xin) + X = (_sparse(first(Xin)), map(_makesparse, Base.tail(Xin))...) return cat(X..., dims=Val(1)) end hvcat(rows::Tuple{Vararg{Int}}, X::_SparseConcatGroup...) = @@ -1122,9 +1130,9 @@ Concatenate along dimension 2. Return a SparseMatrixCSC object. the concatenation with specialized "sparse" matrix types from LinearAlgebra.jl automatically yielded sparse output even in the absence of any SparseArray argument. """ -sparse_hcat(Xin::Union{AbstractVecOrMat,Number}...) = cat(map(_makesparse, Xin)..., dims=Val(2)) +sparse_hcat(Xin::Union{AbstractVecOrMat,Number}...) = cat(_sparse(first(Xin)), map(_makesparse, Base.tail(Xin))..., dims=Val(2)) function sparse_hcat(X::Union{AbstractVecOrMat,UniformScaling,Number}...) - LinearAlgebra._hcat(X...; array_type = SparseMatrixCSC) + LinearAlgebra._hcat(_sparse(first(X)), map(_makesparse, Base.tail(X))...; array_type = SparseMatrixCSC) end """ @@ -1137,9 +1145,9 @@ Concatenate along dimension 1. Return a SparseMatrixCSC object. the concatenation with specialized "sparse" matrix types from LinearAlgebra.jl automatically yielded sparse output even in the absence of any SparseArray argument. """ -sparse_vcat(Xin::Union{AbstractVecOrMat,Number}...) = cat(map(_makesparse, Xin)..., dims=Val(1)) +sparse_vcat(Xin::Union{AbstractVecOrMat,Number}...) = cat(_sparse(first(Xin)), map(_makesparse, Base.tail(Xin))..., dims=Val(1)) function sparse_vcat(X::Union{AbstractVecOrMat,UniformScaling,Number}...) - LinearAlgebra._vcat(X...; array_type = SparseMatrixCSC) + LinearAlgebra._vcat(_sparse(first(X)), map(_makesparse, Base.tail(X))...; array_type = SparseMatrixCSC) end """ @@ -1155,10 +1163,10 @@ arguments to concatenate in each block row. automatically yielded sparse output even in the absence of any SparseArray argument. """ function sparse_hvcat(rows::Tuple{Vararg{Int}}, Xin::Union{AbstractVecOrMat,Number}...) - hvcat(rows, map(_makesparse, Xin)...) + hvcat(rows, _sparse(first(Xin)), map(_makesparse, Base.tail(Xin))...) end function sparse_hvcat(rows::Tuple{Vararg{Int}}, X::Union{AbstractVecOrMat,UniformScaling,Number}...) - LinearAlgebra._hvcat(rows, X...; array_type = SparseMatrixCSC) + LinearAlgebra._hvcat(rows, _sparse(first(X)), map(_makesparse, Base.tail(X))...; array_type = SparseMatrixCSC) end ### math functions @@ -1434,7 +1442,12 @@ for (fun, comp, word) in ((:findmin, :(<), "minimum"), (:findmax, :(>), "maximum # we try to avoid findfirst(iszero, x) sindex = findfirst(iszero, nzvals) # first stored zero, if any zindex = findfirst(i -> i < nzinds[i], eachindex(nzinds)) # first non-stored zero - index = isnothing(sindex) ? zindex : min(sindex, zindex) + index = if isnothing(sindex) + # non-stored zero are contiguous and at the end + isnothing(zindex) && last(nzinds) < lastindex(x) ? last(nzinds) + 1 : zindex + else + min(sindex, zindex) + end return zeroval, index end end @@ -2120,8 +2133,8 @@ function subvector_shifter!(R::AbstractVector, V::AbstractVector, start::Integer end end # ...but rowval should be sorted within columns - circshift!(@view(R[start:fin]), split-start+1) - circshift!(@view(V[start:fin]), split-start+1) + circshift!(@view(R[start:fin]), (CIRCSHIFT_WRONG_DIRECTION ? (+) : (-))(split-start+1)) + circshift!(@view(V[start:fin]), (CIRCSHIFT_WRONG_DIRECTION ? (+) : (-))(split-start+1)) end function circshift!(O::SparseVector, X::SparseVector, (r,)::Base.DimsInteger{1}) diff --git a/test/sparsevector.jl b/test/sparsevector.jl index 6eaaba98..e53fc577 100644 --- a/test/sparsevector.jl +++ b/test/sparsevector.jl @@ -33,9 +33,12 @@ x1_full[SparseArrays.nonzeroinds(spv_x1)] = nonzeros(spv_x1) @test SparseArrays.nonzeroinds(x) == [2, 5, 6] @test nonzeros(x) == [1.25, -0.75, 3.5] @test count(SparseVector(8, [2, 5, 6], [true,false,true])) == 2 - y = SparseVector(typemax(Int128), Int128[4], [5]) + y = SparseVector(8, Int128[4], [5]) @test y isa SparseVector{Int,Int128} - @test @inferred size(y) == (@inferred(length(y)),) + @test @inferred size(y) == (@inferred(length(y))::Int128,) + y = SparseVector(8, Int8[4], [5.0]) + @test y isa SparseVector{Float64,Int8} + @test @inferred size(y) == (@inferred(length(y))::Int8,) end @testset "isstored" begin @@ -537,6 +540,22 @@ end @test length(V) == m * n Vr = vec(Hr) @test Array(V) == Vr + Vnum = vcat(A..., zero(Float64)) + Vnum2 = sparse_vcat(map(Array, A)..., zero(Float64)) + @test Vnum isa SparseVector{Float64,Int} + @test Vnum2 isa SparseVector{Float64,Int} + @test length(Vnum) == length(Vnum2) == m*n + 1 + @test Array(Vnum) == Array(Vnum2) == [Vr; 0] + Vnum = vcat(zero(Float64), A...) + Vnum2 = sparse_vcat(zero(Float64), map(Array, A)...) + @test Vnum isa SparseVector{Float64,Int} + @test Vnum2 isa SparseVector{Float64,Int} + @test length(Vnum) == length(Vnum2) == m*n + 1 + @test Array(Vnum) == Array(Vnum2) == [0; Vr] + # case with rowwise a Number as first element, should still yield a sparse matrix + x = sparsevec([1], [3.0], 1) + X = [3.0 x; 3.0 x] + @test issparse(X) end @testset "concatenation of sparse vectors with other types" begin @@ -898,6 +917,24 @@ end @test_throws ArgumentError findmin(x) @test_throws ArgumentError findmax(x) end + + let v = spzeros(3) #Julia #44978 + v[1] = 2 + @test argmin(v) == 2 + @test argmax(v) == 1 + v[2] = 2 + @test argmin(v) == 3 + v[1] = 0 + v[2] = 0 + v[3] = 2 + @test argmin(v) == 1 + @test argmax(v) == 3 + end + + let v = spzeros(3) #Julia #44978 + v[3] = 2 + @test argmax(v) == 3 + end end ### linalg