From c45b61fe5ca04597df7d61522a4f00c13f25ab0f Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 4 Sep 2023 14:44:02 +0200 Subject: [PATCH 01/28] Code quality cleanup (#438) --- src/linalg.jl | 2 +- test/ambiguous.jl | 23 +++++++++++++++++++---- test/linalg.jl | 12 ++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/linalg.jl b/src/linalg.jl index 8dd9cda3..f315300f 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -560,7 +560,7 @@ function LinearAlgebra.generic_trimatmul!(C::StridedVecOrMat, uploc, isunitc, tf end return C end -function LinearAlgebra.generic_trimatmul!(C::StridedVecOrMat, uploc, isunitc, _, xA::AdjOrTrans{<:Any,<:SparseMatrixCSCUnion}, B::AbstractVecOrMat) +function LinearAlgebra.generic_trimatmul!(C::StridedVecOrMat, uploc, isunitc, ::Function, xA::AdjOrTrans{<:Any,<:SparseMatrixCSCUnion}, B::AbstractVecOrMat) A = parent(xA) nrowC = size(C, 1) ncol = checksquare(A) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index e636944d..00c892f5 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -25,13 +25,13 @@ using Test, LinearAlgebra, SparseArrays, Aqua @testset "code quality" begin @testset "Method ambiguity" begin - # Aqua.test_ambiguities([SparseArrays, Base, Core]) + Aqua.test_ambiguities([SparseArrays, Base, Core]) end @testset "Unbound type parameters" begin @test_broken Aqua.detect_unbound_args_recursively(SparseArrays) == [] end @testset "Undefined exports" begin - Aqua.test_undefined_exports(SparseArrays) == [] + Aqua.test_undefined_exports(SparseArrays) end @testset "Compare Project.toml and test/Project.toml" begin Aqua.test_project_extras(SparseArrays) @@ -50,8 +50,23 @@ using Test, LinearAlgebra, SparseArrays, Aqua end end -@testset "detect_ambiguities" begin - @test_nowarn detect_ambiguities(SparseArrays; recursive=true, ambiguous_bottom=false) +let ambig = detect_ambiguities(SparseArrays; recursive=true) + @test isempty(ambig) + ambig = Set{Any}(((m1.sig, m2.sig) for (m1, m2) in ambig)) + expect = [] + good = true + while !isempty(ambig) + sigs = pop!(ambig) + i = findfirst(==(sigs), expect) + if i === nothing + println(stderr, "push!(expect, (", sigs[1], ", ", sigs[2], "))") + good = false + continue + end + deleteat!(expect, i) + end + @test isempty(expect) + @test good end ## This was the older version that was disabled diff --git a/test/linalg.jl b/test/linalg.jl index c1f5880f..7b8aba54 100644 --- a/test/linalg.jl +++ b/test/linalg.jl @@ -162,6 +162,18 @@ begin vMAW = tr(wr(view(MA, :, 1:n))) @test vAW * B ≈ vMAW * B end + a = sprand(rng, ComplexF64, n, n, 0.01) + ma = Matrix(a) + @testset "triangular multiply with conjugate matrices" for tr in (x -> adjoint(transpose(x)), x -> transpose(adjoint(x))), + wr in (UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular) + AW = tr(wr(a)) + MAW = tr(wr(ma)) + @test AW * B ≈ MAW * B + # and for SparseMatrixCSCView - a view of all rows and unit range of cols + vAW = tr(wr(view(a, :, 1:n))) + vMAW = tr(wr(view(ma, :, 1:n))) + @test vAW * B ≈ vMAW * B + end A = A - Diagonal(diag(A)) + 2I # avoid rounding errors by division MA = Matrix(A) @testset "triangular solver for $tr($wr)" for tr in (identity, adjoint, transpose), From 026e6a60599aed68c89406000baad67981d7c122 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 4 Sep 2023 16:31:27 +0200 Subject: [PATCH 02/28] Run CI on v1.10 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc25c10d..7b7086e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false matrix: version: - - 'nightly' + - '~1.10.0-0' os: - ubuntu-latest - macOS-latest From ddfed4c7b763c092f20c6a431398c4bbff12ef9c Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 1 Aug 2023 14:34:18 -0400 Subject: [PATCH 03/28] cat: ensure vararg is more inferrable Ensures that Union{} is not part of the output possibilities after type-piracy of Base.cat methods. Refs https://github.com/JuliaLang/julia/issues/50550 (cherry picked from commit c402d09cf05492179fad2def5632e354a81f5b30) --- src/sparsevector.jl | 48 +++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 22b1badb..cd75e5b1 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1194,13 +1194,14 @@ anysparse() = false anysparse(X) = X isa AbstractArray && issparse(X) anysparse(X, Xs...) = anysparse(X) || anysparse(Xs...) -function hcat(X::Union{Vector, AbstractSparseVector}...) +const _SparseVecConcatGroup = Union{Vector, AbstractSparseVector} +function hcat(X::_SparseVecConcatGroup...) if anysparse(X...) X = map(sparse, X) end return cat(X...; dims=Val(2)) end -function vcat(X::Union{Vector, AbstractSparseVector}...) +function vcat(X::_SparseVecConcatGroup...) if anysparse(X...) X = map(sparse, X) end @@ -1213,30 +1214,30 @@ end const _SparseConcatGroup = Union{AbstractVecOrMat{<:Number},Number} # `@constprop :aggressive` allows `dims` to be propagated as constant improving return type inference -Base.@constprop :aggressive function Base._cat(dims, X::_SparseConcatGroup...) - T = promote_eltype(X...) - if anysparse(X...) - X = (_sparse(first(X)), map(_makesparse, Base.tail(X))...) +Base.@constprop :aggressive function Base._cat(dims, X1::_SparseConcatGroup, X::_SparseConcatGroup...) + T = promote_eltype(X1, X...) + if anysparse(X1) || anysparse(X...) + X1, X = _sparse(X1), map(_makesparse, X) end - return Base._cat_t(dims, T, X...) + return Base._cat_t(dims, T, X1, X...) end -function hcat(X::_SparseConcatGroup...) - if anysparse(X...) - X = (_sparse(first(X)), map(_makesparse, Base.tail(X))...) +function hcat(X1::_SparseConcatGroup, X::_SparseConcatGroup...) + if anysparse(X1) || anysparse(X...) + X1, X = _sparse(X1), map(_makesparse, X) end - return cat(X..., dims=Val(2)) + return cat(X1, X..., dims=Val(2)) end -function vcat(X::_SparseConcatGroup...) - if anysparse(X...) - X = (_sparse(first(X)), map(_makesparse, Base.tail(X))...) +function vcat(X1::_SparseConcatGroup, X::_SparseConcatGroup...) + if anysparse(X1) || anysparse(X...) + X1, X = _sparse(X1), map(_makesparse, X) end - return cat(X..., dims=Val(1)) + return cat(X1, X..., dims=Val(1)) end -function hvcat(rows::Tuple{Vararg{Int}}, X::_SparseConcatGroup...) - if anysparse(X...) - vcat(_hvcat_rows(rows, X...)...) +function hvcat(rows::Tuple{Vararg{Int}}, X1::_SparseConcatGroup, X::_SparseConcatGroup...) + if anysparse(X1) || anysparse(X...) + vcat(_hvcat_rows(rows, X1, X...)...) else - Base.typed_hvcat(Base.promote_eltypeof(X...), rows, X...) + Base.typed_hvcat(Base.promote_eltypeof(X1, X...), rows, X1, X...) end end function _hvcat_rows((row1, rows...)::Tuple{Vararg{Int}}, X::_SparseConcatGroup...) @@ -1254,6 +1255,15 @@ function _hvcat_rows((row1, rows...)::Tuple{Vararg{Int}}, X::_SparseConcatGroup. end _hvcat_rows(::Tuple{}, X::_SparseConcatGroup...) = () +# disambiguation for type-piracy problems created above +hcat(n1::Number, ns::Vararg{Number}) = invoke(hcat, Tuple{Vararg{Number}}, n1, ns...) +vcat(n1::Number, ns::Vararg{Number}) = invoke(vcat, Tuple{Vararg{Number}}, n1, ns...) +hcat(n1::Type{N}, ns::Vararg{N}) where {N<:Number} = invoke(hcat, Tuple{Vararg{Number}}, n1, ns...) +vcat(n1::Type{N}, ns::Vararg{N}) where {N<:Number} = invoke(vcat, Tuple{Vararg{Number}}, n1, ns...) +hvcat(rows::Tuple{Vararg{Int}}, n1::Number, ns::Vararg{Number}) = invoke(hvcat, Tuple{typeof(rows), Vararg{Number}}, rows, n1, ns...) +hvcat(rows::Tuple{Vararg{Int}}, n1::N, ns::Vararg{N}) where {N<:Number} = invoke(hvcat, Tuple{typeof(rows), Vararg{N}}, rows, n1, ns...) + + # make sure UniformScaling objects are converted to sparse matrices for concatenation promote_to_array_type(A::Tuple{Vararg{Union{_SparseConcatGroup,UniformScaling}}}) = anysparse(A...) ? SparseMatrixCSC : Matrix promote_to_arrays_(n::Int, ::Type{SparseMatrixCSC}, J::UniformScaling) = sparse(J, n, n) From 4a6d98c3f4578ba08c20e7fa8567d32909234035 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 1 Aug 2023 14:36:07 -0400 Subject: [PATCH 04/28] fix inference of SparseVector cat This little gadget creates a closure over Type{T} instead of DataType. Fix https://github.com/JuliaLang/julia/issues/50623 (cherry picked from commit 68afc6e335942052ffa7d4f477d599ddaa02c1f0) --- src/sparsevector.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index cd75e5b1..7e66bc06 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1108,7 +1108,9 @@ function hcat(Xin::AbstractSparseVector...) X = map(_unsafe_unfix, Xin) Tv = promote_type(map(eltype, X)...) Ti = promote_type(map(indtype, X)...) - r = _absspvec_hcat(map(x -> convert(SparseVector{Tv,Ti}, x), X)...) + r = (function (::Type{SV}) where SV + _absspvec_hcat(map(x -> convert(SV, x), X)...) + end)(SparseVector{Tv,Ti}) return @if_move_fixed Xin... r end function _absspvec_hcat(X::AbstractSparseVector{Tv,Ti}...) where {Tv,Ti} @@ -1144,7 +1146,9 @@ function vcat(Xin::AbstractSparseVector...) X = map(_unsafe_unfix, Xin) Tv = promote_type(map(eltype, X)...) Ti = promote_type(map(indtype, X)...) - r = _absspvec_vcat(map(x -> convert(SparseVector{Tv,Ti}, x), X)...) + r = (function (::Type{SV}) where SV + _absspvec_vcat(map(x -> convert(SV, x), X)...) + end)(SparseVector{Tv,Ti}) return @if_move_fixed Xin... r end function _absspvec_vcat(X::AbstractSparseVector{Tv,Ti}...) where {Tv,Ti} From 2a84a1fbfc0bfc2804e7819c1a2bb686511905a7 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 1 Sep 2023 03:36:29 -0400 Subject: [PATCH 05/28] faster cat performance (#432) The generic `cat` does more shape analysis, that typed_cat does not always do (before either may then dispatch to _cat_t), so we can make this faster by calling it instead. Secondly, we can make `issparse` non-recursive once it hits a base case where all trailing elements are the same. This makes it much better at handling large splat, since we do not need to check each recursively smaller type down to the base case using generic code, and just generate one const method specialized on the full length instead. Fix JuliaLang/julia#51011 (cherry picked from commit 4e6776a825f2a26c3c580f9b77ba230a6598d7dd) --- src/sparsevector.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 7e66bc06..9fc8bec8 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1197,6 +1197,7 @@ _makesparse(x::AbstractMatrix) = convert(SparseMatrixCSC, issparse(x) ? x : spar anysparse() = false anysparse(X) = X isa AbstractArray && issparse(X) anysparse(X, Xs...) = anysparse(X) || anysparse(Xs...) +anysparse(X::T, Xs::T...) where {T} = anysparse(X) const _SparseVecConcatGroup = Union{Vector, AbstractSparseVector} function hcat(X::_SparseVecConcatGroup...) @@ -1229,13 +1230,13 @@ function hcat(X1::_SparseConcatGroup, X::_SparseConcatGroup...) if anysparse(X1) || anysparse(X...) X1, X = _sparse(X1), map(_makesparse, X) end - return cat(X1, X..., dims=Val(2)) + return Base.typed_hcat(Base.promote_eltype(X1, X...), X1, X...) end function vcat(X1::_SparseConcatGroup, X::_SparseConcatGroup...) if anysparse(X1) || anysparse(X...) X1, X = _sparse(X1), map(_makesparse, X) end - return cat(X1, X..., dims=Val(1)) + return Base.typed_vcat(Base.promote_eltype(X1, X...), X1, X...) end function hvcat(rows::Tuple{Vararg{Int}}, X1::_SparseConcatGroup, X::_SparseConcatGroup...) if anysparse(X1) || anysparse(X...) From baa418f9e182b14d24551048d31b0d42e50fcc90 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Thu, 24 Aug 2023 17:41:12 +0530 Subject: [PATCH 06/28] Respect `IOContext` while displaying a `SparseMatrixCSC` (#423) * Respect IOContext in printing column indices * Add docstring to ColumnIndices --- src/sparsematrix.jl | 36 +++++++++++++--------- test/sparsematrix_constructors_indexing.jl | 9 ++++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/sparsematrix.jl b/src/sparsematrix.jl index 10bd525a..1703c870 100644 --- a/src/sparsematrix.jl +++ b/src/sparsematrix.jl @@ -344,6 +344,26 @@ function Base.print_array(io::IO, S::AbstractSparseMatrixCSCInclAdjointAndTransp end end +""" + ColumnIndices(S::AbstractSparseMatrixCSC) + +Return the column indices of the stored values in `S`. +This is an internal type that is used in displaying sparse matrices, +and is not a part of the public interface. +""" +struct ColumnIndices{Ti,S<:AbstractSparseMatrixCSC{<:Any,Ti}} <: AbstractVector{Ti} + arr :: S +end + +size(C::ColumnIndices) = (nnz(C.arr),) +# returns the column index of the n-th non-zero value from the column pointer +@inline function getindex(C::ColumnIndices, i::Int) + @boundscheck checkbounds(C, i) + colptr = getcolptr(C.arr) + ind = searchsortedlast(colptr, i) + eltype(C)(ind) +end + # always show matrices as `sparse(I, J, K)` function Base.show(io::IO, _S::AbstractSparseMatrixCSCInclAdjointAndTranspose) _checkbuffers(_S) @@ -358,21 +378,7 @@ function Base.show(io::IO, _S::AbstractSparseMatrixCSCInclAdjointAndTranspose) print(io, "transpose(") end print(io, "sparse(", I, ", ") - if length(I) == 0 - print(io, eltype(getcolptr(S)), "[]") - else - print(io, "[") - il = nnz(S) - 1 - for col in 1:size(S, 2), - k in getcolptr(S)[col] : (getcolptr(S)[col+1]-1) - print(io, col) - if il > 0 - print(io, ", ") - il -= 1 - end - end - print(io, "]") - end + show(io, ColumnIndices(S)) print(io, ", ", K, ", ", m, ", ", n, ")") if _S isa Adjoint || _S isa Transpose print(io, ")") diff --git a/test/sparsematrix_constructors_indexing.jl b/test/sparsematrix_constructors_indexing.jl index 1ef19a1d..5aad5628 100644 --- a/test/sparsematrix_constructors_indexing.jl +++ b/test/sparsematrix_constructors_indexing.jl @@ -1574,6 +1574,15 @@ end _show_with_braille_patterns(ioc, _filled_sparse(8, 16)) @test String(take!(io)) == "⎡⣿⣿⎤\n" * "⎣⣿⣿⎦" + + # respect IOContext while displaying J + I, J, V = shuffle(1:50), shuffle(1:50), [1:50;] + S = sparse(I, J, V) + I, J, V = I[sortperm(J)], sort(J), V[sortperm(J)] + @test repr(S) == "sparse($I, $J, $V, $(size(S,1)), $(size(S,2)))" + limctxt(x) = repr(x, context=:limit=>true) + expstr = "sparse($(limctxt(I)), $(limctxt(J)), $(limctxt(V)), $(size(S,1)), $(size(S,2)))" + @test limctxt(S) == expstr end @testset "issparse for specialized matrix types" begin From a27210928fd7c2e2e5f7c64b69800012c1e7dcfa Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Tue, 10 Oct 2023 15:32:11 +0200 Subject: [PATCH 07/28] Adapt to Documenter v1 (#444) * Adapt to Documenter v1 --- docs/Project.toml | 3 +++ docs/make.jl | 3 +-- docs/src/solvers.md | 13 ++++++++----- src/solvers/cholmod.jl | 7 ++++--- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index dfa65cd1..1814eb33 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,2 +1,5 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" + +[compat] +Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl index de3381c5..53843712 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,8 +10,7 @@ makedocs( "SparseArrays" => "index.md", "Sparse Linear Algebra" => "solvers.md", ]; - # strict = true, - strict = Symbol[:doctest], + warnonly = [:missing_docs, :cross_references], ) deploydocs(repo = "github.com/JuliaSparse/SparseArrays.jl.git") diff --git a/docs/src/solvers.md b/docs/src/solvers.md index 91214c5c..e633be9d 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -14,11 +14,14 @@ Sparse matrix solvers call functions from [SuiteSparse](http://suitesparse.com). Other solvers such as [Pardiso.jl](https://github.com/JuliaSparse/Pardiso.jl/) are available as external packages. [Arpack.jl](https://julialinearalgebra.github.io/Arpack.jl/stable/) provides `eigs` and `svds` for iterative solution of eigensystems and singular value decompositions. -These factorizations are described in more detail in [`Linear Algebra`](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/) section of the manual: -1. [`cholesky`](@ref) -2. [`ldlt`](@ref) -3. [`lu`](@ref) -4. [`qr`](@ref) +These factorizations are described in more detail in the +[`Linear Algebra`](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/) +section of the manual: + +1. [`cholesky`](@ref SparseArrays.CHOLMOD.cholesky) +2. [`ldlt`](@ref SparseArrays.CHOLMOD.ldlt) +3. [`lu`](@ref SparseArrays.UMFPACK.lu) +4. [`qr`](@ref SparseArrays.SPQR.qr) ```@docs SparseArrays.CHOLMOD.cholesky diff --git a/src/solvers/cholmod.jl b/src/solvers/cholmod.jl index 9c832ef7..731eaa3f 100644 --- a/src/solvers/cholmod.jl +++ b/src/solvers/cholmod.jl @@ -18,7 +18,8 @@ using LinearAlgebra using LinearAlgebra: RealHermSymComplexHerm, AdjOrTrans import LinearAlgebra: (\), AdjointFactorization, cholesky, cholesky!, det, diag, ishermitian, isposdef, - issuccess, issymmetric, ldlt, ldlt!, logdet, lowrankdowndate! + issuccess, issymmetric, ldlt, ldlt!, logdet, + lowrankdowndate, lowrankdowndate!, lowrankupdate, lowrankupdate! using SparseArrays using SparseArrays: getcolptr, AbstractSparseVecOrMat @@ -1549,7 +1550,7 @@ factor will be `L*L' == P*A*P' + C'*C` `update`: `Cint(1)` for `A + CC'`, `Cint(0)` for `A - CC'` """ -lowrankdowndate! +lowrankupdowndate! #Helper functions for rank updates lowrank_reorder(V::AbstractArray,p) = Sparse(sparse(V[p,:])) @@ -1598,7 +1599,7 @@ lowrankupdate(F::Factor{Tv}, V::AbstractArray{Tv}) where {Tv<:VTypes} = lowrankupdate!(copy(F), V) """ - lowrankupdate(F::CHOLMOD.Factor, C::AbstractArray) -> FF::CHOLMOD.Factor + lowrankdowndate(F::CHOLMOD.Factor, C::AbstractArray) -> FF::CHOLMOD.Factor Get an `LDLt` Factorization of `A + C*C'` given an `LDLt` or `LLt` factorization `F` of `A`. From d7cf5961594e05888610f7184ac03eace450261c Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Thu, 19 Oct 2023 18:38:54 +0200 Subject: [PATCH 08/28] Backports to Julia v1.10, part 2 (#463) Bump SuiteSparse to 7.2.1 (#459) Co-authored-by: Viral B. Shah --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 749cb1f8..088818ff 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,7 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SuiteSparse_jll = "bea87d4a-7f5b-5778-9afe-8cc45184846c" [compat] -SuiteSparse_jll = "7.2" +SuiteSparse_jll = "7.2.1" julia = "1.10" [extras] From fa6269b0dc5660cbaddb9365c7d89b206fc55992 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 24 Oct 2023 03:33:06 -0500 Subject: [PATCH 09/28] fix h/vcat invoke dispatch arguments (#464) Fixes https://github.com/JuliaLang/julia/issues/51826 (cherry picked from commit 0b36fddcb54e00c7cf92fcb848911464105dc68b) --- src/sparsevector.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 9fc8bec8..b13c44b7 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1263,8 +1263,8 @@ _hvcat_rows(::Tuple{}, X::_SparseConcatGroup...) = () # disambiguation for type-piracy problems created above hcat(n1::Number, ns::Vararg{Number}) = invoke(hcat, Tuple{Vararg{Number}}, n1, ns...) vcat(n1::Number, ns::Vararg{Number}) = invoke(vcat, Tuple{Vararg{Number}}, n1, ns...) -hcat(n1::Type{N}, ns::Vararg{N}) where {N<:Number} = invoke(hcat, Tuple{Vararg{Number}}, n1, ns...) -vcat(n1::Type{N}, ns::Vararg{N}) where {N<:Number} = invoke(vcat, Tuple{Vararg{Number}}, n1, ns...) +hcat(n1::N, ns::Vararg{N}) where {N<:Number} = invoke(hcat, Tuple{Vararg{N}}, n1, ns...) +vcat(n1::N, ns::Vararg{N}) where {N<:Number} = invoke(vcat, Tuple{Vararg{N}}, n1, ns...) hvcat(rows::Tuple{Vararg{Int}}, n1::Number, ns::Vararg{Number}) = invoke(hvcat, Tuple{typeof(rows), Vararg{Number}}, rows, n1, ns...) hvcat(rows::Tuple{Vararg{Int}}, n1::N, ns::Vararg{N}) where {N<:Number} = invoke(hvcat, Tuple{typeof(rows), Vararg{N}}, rows, n1, ns...) From 5d42135751ed04aad489316cbdd488506a93c927 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Mon, 30 Oct 2023 09:23:04 -0400 Subject: [PATCH 10/28] Temp relaxation of SuiteSparse_jll version number for CI --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 088818ff..749cb1f8 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,7 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SuiteSparse_jll = "bea87d4a-7f5b-5778-9afe-8cc45184846c" [compat] -SuiteSparse_jll = "7.2.1" +SuiteSparse_jll = "7.2" julia = "1.10" [extras] From 19e5a9773c767c3a5daf4c3e471f495a58ec3b66 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Mon, 30 Oct 2023 09:24:12 -0400 Subject: [PATCH 11/28] Revert "Temp relaxation of SuiteSparse_jll version number for CI" This reverts commit 5d42135751ed04aad489316cbdd488506a93c927. --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 749cb1f8..088818ff 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,7 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SuiteSparse_jll = "bea87d4a-7f5b-5778-9afe-8cc45184846c" [compat] -SuiteSparse_jll = "7.2" +SuiteSparse_jll = "7.2.1" julia = "1.10" [extras] From c19b9f0cc926682d35d8763cce20da85e8e8e3a8 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 30 Oct 2023 15:00:35 +0100 Subject: [PATCH 12/28] [release] Backports to v1.10 (#465) Aggressive constprop in sparse * dense (#460) Co-authored-by: Jishnu Bhattacharya Co-authored-by: Viral B. Shah --- src/linalg.jl | 4 ++-- src/sparsevector.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/linalg.jl b/src/linalg.jl index f315300f..2251c3ae 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -35,7 +35,7 @@ LinearAlgebra.generic_matmatmul!(C::StridedMatrix, tA, tB, A::SparseMatrixCSCUni LinearAlgebra.generic_matvecmul!(C::StridedVecOrMat, tA, A::SparseMatrixCSCUnion, B::DenseInputVector, _add::MulAddMul) = spdensemul!(C, tA, 'N', A, B, _add) -function spdensemul!(C, tA, tB, A, B, _add) +Base.@constprop :aggressive function spdensemul!(C, tA, tB, A, B, _add) if tA == 'N' _spmatmul!(C, A, LinearAlgebra.wrap(B, tB), _add.alpha, _add.beta) elseif tA == 'T' @@ -97,7 +97,7 @@ end *(A::AdjOrTrans{<:Any,<:AbstractSparseMatrixCSC}, B::DenseTriangular) = (T = promote_op(matprod, eltype(A), eltype(B)); mul!(similar(B, T, (size(A, 1), size(B, 2))), A, B)) -function LinearAlgebra.generic_matmatmul!(C::StridedMatrix, tA, tB, A::DenseMatrixUnion, B::AbstractSparseMatrixCSC, _add::MulAddMul) +Base.@constprop :aggressive function LinearAlgebra.generic_matmatmul!(C::StridedMatrix, tA, tB, A::DenseMatrixUnion, B::AbstractSparseMatrixCSC, _add::MulAddMul) transA = tA == 'N' ? identity : tA == 'T' ? transpose : adjoint if tB == 'N' _spmul!(C, transA(A), B, _add.alpha, _add.beta) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index b13c44b7..37b365ab 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1798,7 +1798,7 @@ function (*)(A::_StridedOrTriangularMatrix{Ta}, x::AbstractSparseVector{Tx}) whe mul!(y, A, x) end -function LinearAlgebra.generic_matvecmul!(y::AbstractVector, tA, A::StridedMatrix, x::AbstractSparseVector, +Base.@constprop :aggressive function LinearAlgebra.generic_matvecmul!(y::AbstractVector, tA, A::StridedMatrix, x::AbstractSparseVector, _add::MulAddMul = MulAddMul()) if tA == 'N' _spmul!(y, A, x, _add.alpha, _add.beta) @@ -1918,7 +1918,7 @@ function densemv(A::AbstractSparseMatrixCSC, x::AbstractSparseVector; trans::Abs end # * and mul! -function LinearAlgebra.generic_matvecmul!(y::AbstractVector, tA, A::AbstractSparseMatrixCSC, x::AbstractSparseVector, +Base.@constprop :aggressive function LinearAlgebra.generic_matvecmul!(y::AbstractVector, tA, A::AbstractSparseMatrixCSC, x::AbstractSparseVector, _add::MulAddMul = MulAddMul()) if tA == 'N' _spmul!(y, A, x, _add.alpha, _add.beta) From 824671beccb15eeaeaed44d85c73b3305e2ca138 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 15 Nov 2023 14:25:16 -0500 Subject: [PATCH 13/28] Add Aqua compat. Create CompatHelper.yml (#470) * Create CompatHelper.yml * add Aqua compat (cherry picked from commit 7786a73cefe4147fb423a5fa529aa583ca6c877c) --- .github/workflows/CompatHelper.yml | 45 ++++++++++++++++++++++++++++++ Project.toml | 1 + 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/CompatHelper.yml diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 00000000..09181610 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,45 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v1 + with: + version: '1' + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} diff --git a/Project.toml b/Project.toml index 088818ff..bbb33a6f 100644 --- a/Project.toml +++ b/Project.toml @@ -10,6 +10,7 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SuiteSparse_jll = "bea87d4a-7f5b-5778-9afe-8cc45184846c" [compat] +Aqua = "0.7" SuiteSparse_jll = "7.2.1" julia = "1.10" From 4ddb0bfca4dca18abdf11c936685b8892a7cd424 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 15 Nov 2023 17:37:31 -0500 Subject: [PATCH 14/28] Fix compat. Allow newer Aqua. Only run Aqua on repo tests. (#471) * fix compat. Allow newer Aqua * rm removed Aqua test * only run aqua stuff on repo CI (cherry picked from commit f154de2b6801ec8d5afaf58b73b830c8e71013c3) --- .github/workflows/ci.yml | 2 ++ Project.toml | 11 ++++++++++- test/ambiguous.jl | 8 ++++---- test/runtests.jl | 4 ++++ test/testgroups | 1 - 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b7086e0..faeaf424 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,8 @@ jobs: - run: julia --color=yes .ci/test_and_change_uuid.jl - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 + env: + SPARSEARRAYS_AQUA_TEST: true - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 with: diff --git a/Project.toml b/Project.toml index bbb33a6f..fac1058d 100644 --- a/Project.toml +++ b/Project.toml @@ -10,8 +10,17 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SuiteSparse_jll = "bea87d4a-7f5b-5778-9afe-8cc45184846c" [compat] -Aqua = "0.7" +Aqua = "0.7, 0.8" +Dates = "<0.0.1, 1" +InteractiveUtils = "<0.0.1, 1" +Libdl = "<0.0.1, 1" +LinearAlgebra = "<0.0.1, 1" +Pkg = "<0.0.1, 1" +Printf = "<0.0.1, 1" +Random = "<0.0.1, 1" +Serialization = "<0.0.1, 1" SuiteSparse_jll = "7.2.1" +Test = "<0.0.1, 1" julia = "1.10" [extras] diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 00c892f5..4b81ae2f 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -14,7 +14,9 @@ if Base.find_package("Aqua") === nothing @debug "Installing Aqua.jl for SparseArrays.jl tests" iob = IOBuffer() try - Pkg.add("Aqua", io=iob) # Needed for custom julia version resolve tests + # TODO: make this version tie to compat in Project.toml + # or do this another safer way + Pkg.add(name="Aqua", version="0.8", io=iob) # Needed for custom julia version resolve tests catch println(String(take!(iob))) rethrow() @@ -42,9 +44,7 @@ using Test, LinearAlgebra, SparseArrays, Aqua @testset "Compat bounds" begin Aqua.test_deps_compat(SparseArrays) end - @testset "Project.toml formatting" begin - Aqua.test_project_toml_formatting(SparseArrays) - end + @testset "Piracy" begin @test_broken Aqua.Piracy.hunt(SparseArrays) == Method[] end diff --git a/test/runtests.jl b/test/runtests.jl index 6e3ca497..3eb1de97 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Test, LinearAlgebra, SparseArrays +if Base.get_bool_env("SPARSEARRAYS_AQUA_TEST", false) + include("ambiguous.jl") +end + for file in readlines(joinpath(@__DIR__, "testgroups")) file == "" && continue # skip empty lines include(file * ".jl") diff --git a/test/testgroups b/test/testgroups index bd8f928f..a547762c 100644 --- a/test/testgroups +++ b/test/testgroups @@ -1,5 +1,4 @@ allowscalar -ambiguous cholmod fixed higherorderfns From 5d3724a59255d3bdacd1eae146c20da317e30ccd Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 21 Oct 2024 17:05:38 +0200 Subject: [PATCH 15/28] Inline sparse-times-dense in-place multiplication --- src/linalg.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/linalg.jl b/src/linalg.jl index 2251c3ae..c76f26a6 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -28,11 +28,11 @@ for op ∈ (:+, :-) end end -LinearAlgebra.generic_matmatmul!(C::StridedMatrix, tA, tB, A::SparseMatrixCSCUnion, B::DenseMatrixUnion, _add::MulAddMul) = +@inline LinearAlgebra.generic_matmatmul!(C::StridedMatrix, tA, tB, A::SparseMatrixCSCUnion, B::DenseMatrixUnion, _add::MulAddMul) = spdensemul!(C, tA, tB, A, B, _add) -LinearAlgebra.generic_matmatmul!(C::StridedMatrix, tA, tB, A::SparseMatrixCSCUnion, B::AbstractTriangular, _add::MulAddMul) = +@inline LinearAlgebra.generic_matmatmul!(C::StridedMatrix, tA, tB, A::SparseMatrixCSCUnion, B::AbstractTriangular, _add::MulAddMul) = spdensemul!(C, tA, tB, A, B, _add) -LinearAlgebra.generic_matvecmul!(C::StridedVecOrMat, tA, A::SparseMatrixCSCUnion, B::DenseInputVector, _add::MulAddMul) = +@inline LinearAlgebra.generic_matvecmul!(C::StridedVecOrMat, tA, A::SparseMatrixCSCUnion, B::DenseInputVector, _add::MulAddMul) = spdensemul!(C, tA, 'N', A, B, _add) Base.@constprop :aggressive function spdensemul!(C, tA, tB, A, B, _add) From 91b0aa56a64136c920799533ab6a743d008118bf Mon Sep 17 00:00:00 2001 From: CyHan Date: Sat, 26 Oct 2024 22:29:08 +0800 Subject: [PATCH 16/28] doc: move solvers doc to `src\solvers.md` (#576) --- docs/src/index.md | 5 +++++ docs/src/solvers.md | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 5ddae840..ceca6fb9 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -202,6 +202,11 @@ section of the standard library reference. | [`sprandn(m,n,d)`](@ref) | [`randn(m,n)`](@ref) | Creates a *m*-by-*n* random matrix (of density *d*) with iid non-zero elements distributed according to the standard normal (Gaussian) distribution. | | [`sprandn(rng,m,n,d)`](@ref) | [`randn(rng,m,n)`](@ref) | Creates a *m*-by-*n* random matrix (of density *d*) with iid non-zero elements generated with the `rng` random number generator | + +```@meta +DocTestSetup = nothing +``` + # [SparseArrays API](@id stdlib-sparse-arrays) ```@docs diff --git a/docs/src/solvers.md b/docs/src/solvers.md index e633be9d..4763ffcb 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -4,7 +4,16 @@ DocTestSetup = :(using LinearAlgebra, SparseArrays) ``` -Sparse matrix solvers call functions from [SuiteSparse](http://suitesparse.com). The following factorizations are available: +## [Sparse Linear Algebra](@id stdlib-sparse-linalg) + +Sparse matrix solvers call functions from [SuiteSparse](http://suitesparse.com). + +The following factorizations are available: + +1. [`cholesky`](@ref SparseArrays.CHOLMOD.cholesky) +2. [`ldlt`](@ref SparseArrays.CHOLMOD.ldlt) +3. [`lu`](@ref SparseArrays.UMFPACK.lu) +4. [`qr`](@ref SparseArrays.SPQR.qr) | Type | Description | |:--------------------------------- |:--------------------------------------------- | @@ -12,18 +21,16 @@ Sparse matrix solvers call functions from [SuiteSparse](http://suitesparse.com). | `UMFPACK.UmfpackLU` | LU factorization | | `SPQR.QRSparse` | QR factorization | -Other solvers such as [Pardiso.jl](https://github.com/JuliaSparse/Pardiso.jl/) are available as external packages. [Arpack.jl](https://julialinearalgebra.github.io/Arpack.jl/stable/) provides `eigs` and `svds` for iterative solution of eigensystems and singular value decompositions. +Other solvers such as [Pardiso.jl](https://github.com/JuliaSparse/Pardiso.jl/) are available +as external packages. [Arpack.jl](https://julialinearalgebra.github.io/Arpack.jl/stable/) +provides `eigs` and `svds` for iterative solution of eigensystems and singular value +decompositions. These factorizations are described in more detail in the [`Linear Algebra`](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/) section of the manual: -1. [`cholesky`](@ref SparseArrays.CHOLMOD.cholesky) -2. [`ldlt`](@ref SparseArrays.CHOLMOD.ldlt) -3. [`lu`](@ref SparseArrays.UMFPACK.lu) -4. [`qr`](@ref SparseArrays.SPQR.qr) - -```@docs +```@docs; canonical=false SparseArrays.CHOLMOD.cholesky SparseArrays.CHOLMOD.cholesky! SparseArrays.CHOLMOD.ldlt From 30fbfc6b0f50a9f1932248e0543ba4714c4e6f35 Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Wed, 23 Aug 2023 18:28:46 -0400 Subject: [PATCH 17/28] Test suite: activate a temp project if we need to install Aqua.jl during the test suite (#425) --- test/ambiguous.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 4b81ae2f..c1d4ec58 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -5,6 +5,7 @@ original_depot_path = copy(Base.DEPOT_PATH) original_load_path = copy(Base.LOAD_PATH) original_env = copy(ENV) +original_project = Base.active_project() ### import Pkg @@ -13,6 +14,7 @@ import Pkg if Base.find_package("Aqua") === nothing @debug "Installing Aqua.jl for SparseArrays.jl tests" iob = IOBuffer() + Pkg.activate(; temp = true) try # TODO: make this version tie to compat in Project.toml # or do this another safer way @@ -103,4 +105,6 @@ end for (k, v) in pairs(original_env) ENV[k] = v end + +Base.set_active_project(original_project) ### From 546be180bbc79b99d0faba99864a19ecd1a031ab Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Sat, 26 Aug 2023 12:04:00 -0400 Subject: [PATCH 18/28] Fix docs conflict when building as part of full Julia docs (#430) Fix docs conflict --- docs/src/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index ceca6fb9..dc418088 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -7,7 +7,7 @@ DocTestSetup = :(using SparseArrays, LinearAlgebra) Julia has support for sparse vectors and [sparse matrices](https://en.wikipedia.org/wiki/Sparse_matrix) in the `SparseArrays` stdlib module. Sparse arrays are arrays that contain enough zeros that storing them in a special data structure leads to savings in space and execution time, compared to dense arrays. -External packages which implement different sparse storage types, multidimensional sparse arrays, and more can be found in [Noteworthy external packages](@ref man-csc) +External packages which implement different sparse storage types, multidimensional sparse arrays, and more can be found in [Noteworthy External Sparse Packages](@ref) ## [Compressed Sparse Column (CSC) Sparse Matrix Storage](@id man-csc) @@ -246,7 +246,7 @@ SparseArrays.ftranspose! ```@meta DocTestSetup = nothing ``` -# Noteworthy external packages +# Noteworthy External Sparse Packages Several other Julia packages provide sparse matrix implementations that should be mentioned: From 9b8cd143b0512239f6c3b513a9babe245985ff58 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Thu, 4 Apr 2024 08:24:39 +0530 Subject: [PATCH 19/28] SparseMatrixCSC constructor with a Tuple of Integers (#523) * SparseMatrixCSC constructor with a Tuple of Integers * SparseVector constructors --------- Co-authored-by: Viral B. Shah --- src/sparsematrix.jl | 2 ++ src/sparsevector.jl | 1 + test/sparsematrix_constructors_indexing.jl | 11 +++++++---- test/sparsevector.jl | 12 ++++++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/sparsematrix.jl b/src/sparsematrix.jl index 1703c870..4abb87ab 100644 --- a/src/sparsematrix.jl +++ b/src/sparsematrix.jl @@ -49,10 +49,12 @@ SparseMatrixCSC(m, n, colptr::ReadOnly, rowval::ReadOnly, nzval::Vector) = """ SparseMatrixCSC{Tv,Ti}(::UndefInitializer, m::Integer, n::Integer) + SparseMatrixCSC{Tv,Ti}(::UndefInitializer, (m,n)::NTuple{2,Integer}) Creates an empty sparse matrix with element type `Tv` and integer type `Ti` of size `m × n`. """ SparseMatrixCSC{Tv,Ti}(::UndefInitializer, m::Integer, n::Integer) where {Tv, Ti} = spzeros(Tv, Ti, m, n) +SparseMatrixCSC{Tv,Ti}(::UndefInitializer, mn::NTuple{2,Integer}) where {Tv, Ti} = spzeros(Tv, Ti, mn...) """ `FixedSparseCSC{Tv,Ti<:Integer} <: AbstractSparseMatrixCSC{Tv,Ti}` diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 37b365ab..1cfe3b4f 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -50,6 +50,7 @@ SparseVector(n::Integer, nzind::Vector{Ti}, nzval::Vector{Tv}) where {Tv,Ti} = SparseVector{Tv,Ti}(n, nzind, nzval) SparseVector{Tv, Ti}(::UndefInitializer, n::Integer) where {Tv, Ti} = SparseVector{Tv, Ti}(n, Ti[], Tv[]) +SparseVector{Tv, Ti}(::UndefInitializer, (n,)::Tuple{Integer}) where {Tv, Ti} = SparseVector{Tv, Ti}(n, Ti[], Tv[]) """ `FixedSparseVector{Tv,Ti<:Integer} <: AbstractCompressedVector{Tv,Ti}` diff --git a/test/sparsematrix_constructors_indexing.jl b/test/sparsematrix_constructors_indexing.jl index 5aad5628..a6f1a75c 100644 --- a/test/sparsematrix_constructors_indexing.jl +++ b/test/sparsematrix_constructors_indexing.jl @@ -57,10 +57,13 @@ end @test sparse([1, 1, 2, 2, 2], [1, 2, 1, 2, 2], -1.0, 2, 2, *) == sparse([1, 1, 2, 2], [1, 2, 1, 2], [-1.0, -1.0, -1.0, 1.0], 2, 2) @test sparse(sparse(Int32.(1:5), Int32.(1:5), trues(5))') isa SparseMatrixCSC{Bool,Int32} # undef initializer - m = SparseMatrixCSC{Float32, Int16}(undef, 3, 4) - @test size(m) == (3, 4) - @test eltype(m) === Float32 - @test m == spzeros(3, 4) + sz = (3, 4) + for m in (SparseMatrixCSC{Float32, Int16}(undef, sz...), SparseMatrixCSC{Float32, Int16}(undef, sz), + similar(SparseMatrixCSC{Float32, Int16}, sz)) + @test size(m) == sz + @test eltype(m) === Float32 + @test m == spzeros(sz...) + end end @testset "spzeros for pattern creation (structural zeros)" begin diff --git a/test/sparsevector.jl b/test/sparsevector.jl index 44659939..b93a4963 100644 --- a/test/sparsevector.jl +++ b/test/sparsevector.jl @@ -219,10 +219,14 @@ end end @testset "Undef initializer" begin - v = SparseVector{Float32, Int16}(undef, 4) - @test size(v) == (4, ) - @test eltype(v) === Float32 - @test v == spzeros(Float32, 4) + sz = (4,) + for v in (SparseVector{Float32, Int16}(undef, sz), + SparseVector{Float32, Int16}(undef, sz...), + similar(SparseVector{Float32, Int16}, sz)) + @test size(v) == sz + @test eltype(v) === Float32 + @test v == spzeros(Float32, sz...) + end end end ### Element access From d2a80a6741d721a3dfebf42d5a12531133fb23c7 Mon Sep 17 00:00:00 2001 From: Ben Corbett <32752943+corbett5@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:40:53 -0600 Subject: [PATCH 20/28] Change default QR tolerance to match SPQR (#557) SPQR uses just the double precision epsilon even for Float32. https://github.com/DrTimothyAldenDavis/SuiteSparse/blob/131471310ef0600b231b8fa7c10a55c3f70afbd9/SPQR/Source/spqr_tol.cpp#L29C6-L30C57 --- src/solvers/spqr.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/spqr.jl b/src/solvers/spqr.jl index d1a09f90..1b66f14b 100644 --- a/src/solvers/spqr.jl +++ b/src/solvers/spqr.jl @@ -146,7 +146,7 @@ Matrix{T}(Q::QRSparseQ) where {T} = lmul!(Q, Matrix{T}(I, size(Q, 1), min(size(Q # From SPQR manual p. 6 _default_tol(A::AbstractSparseMatrixCSC) = - 20*sum(size(A))*eps(real(eltype(A)))*maximum(norm(view(A, :, i)) for i in 1:size(A, 2)) + 20*sum(size(A))*eps()*maximum(norm(view(A, :, i)) for i in 1:size(A, 2)) """ qr(A::SparseMatrixCSC; tol=_default_tol(A), ordering=ORDERING_DEFAULT) -> QRSparse From fa49620908f5ce4cde92c680fc0ff81714110ebc Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Sat, 2 Nov 2024 11:50:04 -0400 Subject: [PATCH 21/28] Disable nested dissection --- src/solvers/cholmod.jl | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/solvers/cholmod.jl b/src/solvers/cholmod.jl index 731eaa3f..97ca426e 100644 --- a/src/solvers/cholmod.jl +++ b/src/solvers/cholmod.jl @@ -1258,25 +1258,33 @@ end ## Compute that symbolic factorization only function symbolic(A::Sparse{<:VTypes, Ti}; - perm::Union{Nothing,AbstractVector{<:Integer}}=nothing, - postorder::Bool=isnothing(perm)||isempty(perm), userperm_only::Bool=true) where Ti + perm::Union{Nothing,AbstractVector{<:Integer}}=nothing, + postorder::Bool=isnothing(perm)||isempty(perm), + userperm_only::Bool=true, + nested_dissection::Bool=false) where Ti sA = unsafe_load(pointer(A)) sA.stype == 0 && throw(ArgumentError("sparse matrix is not symmetric/Hermitian")) - @cholmod_param postorder = postorder begin - if perm === nothing || isempty(perm) # TODO: deprecate empty perm - return analyze(A) - else # user permutation provided - if userperm_only # use perm even if it is worse than AMD - @cholmod_param nmethods = 1 begin + # The default is to just use AMD. Use nested dissection only if explicitly asked for. + # https://github.com/JuliaSparse/SparseArrays.jl/issues/548 + # https://github.com/DrTimothyAldenDavis/SuiteSparse/blob/26ababc7f3af725c5fb9168a1b94850eab74b666/CHOLMOD/Include/cholmod.h#L555-L574 + @cholmod_param nmethods = (nested_dissection ? 0 : 2) begin + @cholmod_param postorder = postorder begin + if perm === nothing || isempty(perm) # TODO: deprecate empty perm + return analyze(A) + else # user permutation provided + if userperm_only # use perm even if it is worse than AMD + @cholmod_param nmethods = 1 begin + return analyze_p(A, Ti[p-1 for p in perm]) + end + else return analyze_p(A, Ti[p-1 for p in perm]) end - else - return analyze_p(A, Ti[p-1 for p in perm]) end end end + end function cholesky!(F::Factor{Tv}, A::Sparse{Tv}; From b539588ac83ce8bcef2800c0b7f224d127a3d66e Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Sat, 2 Nov 2024 11:50:12 -0400 Subject: [PATCH 22/28] Update CI --- .github/workflows/ci.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index faeaf424..1ad05321 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,21 +25,21 @@ jobs: - '~1.10.0-0' os: - ubuntu-latest - - macOS-latest - windows-latest arch: - x64 - - x86 - exclude: + include: - os: macOS-latest + arch: aarch64 + - os: ubuntu-latest arch: x86 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v4 env: cache-name: cache-artifacts with: @@ -55,17 +55,18 @@ jobs: env: SPARSEARRAYS_AQUA_TEST: true - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 with: file: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} + docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - # version: '1.6' - version: 'nightly' + version: '1.10' - 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"));' From 5c37298c6cd472d35be42fc9e9332d2f74d53886 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Sat, 2 Nov 2024 11:51:55 -0400 Subject: [PATCH 23/28] Add versions to include arch --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ad05321..26ced0f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false matrix: version: - - '~1.10.0-0' + - '~1.10' os: - ubuntu-latest - windows-latest @@ -31,8 +31,10 @@ jobs: include: - os: macOS-latest arch: aarch64 + version: '~1.10' - os: ubuntu-latest arch: x86 + version: '~1.10' steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 From 2d762b3593499ccf35e7ef0cc4baaaf3cdb20ce6 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" <744411+ViralBShah@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:09:45 -0400 Subject: [PATCH 24/28] Manual commit for PR #550 to backport to 1.10 (#577) * Disable nested dissection (from #550) * Update CI for Julia 1.10 --------- Co-authored-by: Viral B. Shah --- .github/workflows/ci.yml | 23 +++++++++++++---------- src/solvers/cholmod.jl | 28 ++++++++++++++++++---------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index faeaf424..5a9fad11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,24 +22,26 @@ jobs: fail-fast: false matrix: version: - - '~1.10.0-0' + - '1.10' os: - ubuntu-latest - - macOS-latest - windows-latest arch: - x64 - - x86 - exclude: + include: - os: macOS-latest + arch: aarch64 + version: '1.10' + - os: ubuntu-latest arch: x86 + version: '1.10' steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v4 env: cache-name: cache-artifacts with: @@ -55,17 +57,18 @@ jobs: env: SPARSEARRAYS_AQUA_TEST: true - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 with: file: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} + docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - # version: '1.6' - version: 'nightly' + version: '1.10' - 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/solvers/cholmod.jl b/src/solvers/cholmod.jl index 731eaa3f..97ca426e 100644 --- a/src/solvers/cholmod.jl +++ b/src/solvers/cholmod.jl @@ -1258,25 +1258,33 @@ end ## Compute that symbolic factorization only function symbolic(A::Sparse{<:VTypes, Ti}; - perm::Union{Nothing,AbstractVector{<:Integer}}=nothing, - postorder::Bool=isnothing(perm)||isempty(perm), userperm_only::Bool=true) where Ti + perm::Union{Nothing,AbstractVector{<:Integer}}=nothing, + postorder::Bool=isnothing(perm)||isempty(perm), + userperm_only::Bool=true, + nested_dissection::Bool=false) where Ti sA = unsafe_load(pointer(A)) sA.stype == 0 && throw(ArgumentError("sparse matrix is not symmetric/Hermitian")) - @cholmod_param postorder = postorder begin - if perm === nothing || isempty(perm) # TODO: deprecate empty perm - return analyze(A) - else # user permutation provided - if userperm_only # use perm even if it is worse than AMD - @cholmod_param nmethods = 1 begin + # The default is to just use AMD. Use nested dissection only if explicitly asked for. + # https://github.com/JuliaSparse/SparseArrays.jl/issues/548 + # https://github.com/DrTimothyAldenDavis/SuiteSparse/blob/26ababc7f3af725c5fb9168a1b94850eab74b666/CHOLMOD/Include/cholmod.h#L555-L574 + @cholmod_param nmethods = (nested_dissection ? 0 : 2) begin + @cholmod_param postorder = postorder begin + if perm === nothing || isempty(perm) # TODO: deprecate empty perm + return analyze(A) + else # user permutation provided + if userperm_only # use perm even if it is worse than AMD + @cholmod_param nmethods = 1 begin + return analyze_p(A, Ti[p-1 for p in perm]) + end + else return analyze_p(A, Ti[p-1 for p in perm]) end - else - return analyze_p(A, Ti[p-1 for p in perm]) end end end + end function cholesky!(F::Factor{Tv}, A::Sparse{Tv}; From ec38631e6b261eaea3a12be0764f18437e90c7a1 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" <744411+ViralBShah@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:19:41 -0400 Subject: [PATCH 25/28] Update ci.yml with more architectures --- .github/workflows/ci.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a6b9455..183f2ddc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,12 +28,13 @@ jobs: - windows-latest arch: - x64 + - x86 include: - os: macOS-latest arch: aarch64 version: '1.10' - - os: ubuntu-latest - arch: x86 + - os: macOS-13 + arch: x64 version: '1.10' steps: - uses: actions/checkout@v4 @@ -41,16 +42,7 @@ jobs: with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v4 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test-${{ matrix.os }} - ${{ runner.os }}- + - uses: julia-actions/cache@v2 - run: julia --color=yes .ci/test_and_change_uuid.jl - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 From c8dd8ad717ba0038e7f9b1191f9343e718733da4 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Wed, 13 Nov 2024 07:05:56 -0300 Subject: [PATCH 26/28] Backport 14333eae647464121150ae77d9f2dbe673aa244b to 1.10 (#580) --- src/sparsevector.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 1cfe3b4f..751ffe6a 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1239,13 +1239,16 @@ function vcat(X1::_SparseConcatGroup, X::_SparseConcatGroup...) end return Base.typed_vcat(Base.promote_eltype(X1, X...), X1, X...) end -function hvcat(rows::Tuple{Vararg{Int}}, X1::_SparseConcatGroup, X::_SparseConcatGroup...) +function hvcat_internal(rows::Tuple{Vararg{Int}}, X1::_SparseConcatGroup, X::_SparseConcatGroup...) if anysparse(X1) || anysparse(X...) vcat(_hvcat_rows(rows, X1, X...)...) else Base.typed_hvcat(Base.promote_eltypeof(X1, X...), rows, X1, X...) end end +function hvcat(rows::Tuple{Vararg{Int}}, X1::_SparseConcatGroup, X::_SparseConcatGroup...) + return hvcat_internal(rows, X1, X...) +end function _hvcat_rows((row1, rows...)::Tuple{Vararg{Int}}, X::_SparseConcatGroup...) if row1 ≤ 0 throw(ArgumentError("length of block row must be positive, got $row1")) @@ -1266,9 +1269,8 @@ hcat(n1::Number, ns::Vararg{Number}) = invoke(hcat, Tuple{Vararg{Number}}, n1, n vcat(n1::Number, ns::Vararg{Number}) = invoke(vcat, Tuple{Vararg{Number}}, n1, ns...) hcat(n1::N, ns::Vararg{N}) where {N<:Number} = invoke(hcat, Tuple{Vararg{N}}, n1, ns...) vcat(n1::N, ns::Vararg{N}) where {N<:Number} = invoke(vcat, Tuple{Vararg{N}}, n1, ns...) -hvcat(rows::Tuple{Vararg{Int}}, n1::Number, ns::Vararg{Number}) = invoke(hvcat, Tuple{typeof(rows), Vararg{Number}}, rows, n1, ns...) -hvcat(rows::Tuple{Vararg{Int}}, n1::N, ns::Vararg{N}) where {N<:Number} = invoke(hvcat, Tuple{typeof(rows), Vararg{N}}, rows, n1, ns...) - +hvcat(rows::Tuple{Vararg{Int}}, n1::Number, ns::Vararg{Number}) = hvcat_internal(rows, n1, ns...) +hvcat(rows::Tuple{Vararg{Int}}, n1::N, ns::Vararg{N}) where {N<:Number} = hvcat_internal(rows, n1, ns...) # make sure UniformScaling objects are converted to sparse matrices for concatenation promote_to_array_type(A::Tuple{Vararg{Union{_SparseConcatGroup,UniformScaling}}}) = anysparse(A...) ? SparseMatrixCSC : Matrix From 78035e1a4475b77f54964b3f9dd7ca6b92ac7f51 Mon Sep 17 00:00:00 2001 From: William Moses Date: Sun, 1 Dec 2024 12:49:10 -0500 Subject: [PATCH 27/28] Match base julia error messages for hvcat (#588) --- src/sparsevector.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 751ffe6a..24cae01f 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1243,6 +1243,15 @@ function hvcat_internal(rows::Tuple{Vararg{Int}}, X1::_SparseConcatGroup, X::_Sp if anysparse(X1) || anysparse(X...) vcat(_hvcat_rows(rows, X1, X...)...) else + # Needed to match error type with Base Julia. See https://github.com/JuliaLang/julia/pull/56543#issuecomment-2508637550 + matrix_len = if length(rows) == 0 + 0 + else + length(rows) * rows[1] + end + if matrix_len != 1 + length(X) + throw(ArgumentError("argument count does not match specified shape (expected $matrix_len, got $(1+length(X)))")) + end Base.typed_hvcat(Base.promote_eltypeof(X1, X...), rows, X1, X...) end end From 248d9f94cf31d9668e5b07f237f13a239472b2ec Mon Sep 17 00:00:00 2001 From: William Moses Date: Thu, 9 Jan 2025 15:04:10 -0500 Subject: [PATCH 28/28] More adhereance to 1.10 error types (hopefully) (#592) It looks like in base that error was only enabled on number inputs, lets see if this fixes it? cc @gbaraldi @KristofferC --------- Co-authored-by: Daniel Karrasch --- src/sparsevector.jl | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 24cae01f..d13b0714 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1243,15 +1243,17 @@ function hvcat_internal(rows::Tuple{Vararg{Int}}, X1::_SparseConcatGroup, X::_Sp if anysparse(X1) || anysparse(X...) vcat(_hvcat_rows(rows, X1, X...)...) else - # Needed to match error type with Base Julia. See https://github.com/JuliaLang/julia/pull/56543#issuecomment-2508637550 - matrix_len = if length(rows) == 0 - 0 - else - length(rows) * rows[1] - end - if matrix_len != 1 + length(X) - throw(ArgumentError("argument count does not match specified shape (expected $matrix_len, got $(1+length(X)))")) - end + if X1 isa Number && all(Base.Fix2(isa, Number), X) + # Needed to match error type with Base Julia. See https://github.com/JuliaLang/julia/pull/56543#issuecomment-2508637550 + matrix_len = if length(rows) == 0 + 0 + else + length(rows) * rows[1] + end + if matrix_len != 1 + length(X) + throw(ArgumentError("argument count does not match specified shape (expected $matrix_len, got $(1+length(X)))")) + end + end Base.typed_hvcat(Base.promote_eltypeof(X1, X...), rows, X1, X...) end end