From 268f1196f19ea1312530683bca70bc1ce57a8072 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 12 Feb 2025 17:42:42 -0500 Subject: [PATCH 01/24] Add `sprand` and `sprand!` --- Project.toml | 2 ++ src/abstractsparsearrayinterface.jl | 43 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/Project.toml b/Project.toml index 68d0814..7b339fc 100644 --- a/Project.toml +++ b/Project.toml @@ -11,6 +11,7 @@ Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MapBroadcast = "ebd9b9da-f48d-417c-9660-449667d60261" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] Accessors = "0.1.41" @@ -21,6 +22,7 @@ Dictionaries = "0.4.3" FillArrays = "1.13.0" LinearAlgebra = "1.10" MapBroadcast = "0.1.5" +Random = "1.10.0" SafeTestsets = "0.1" Suppressor = "0.2" Test = "1.10" diff --git a/src/abstractsparsearrayinterface.jl b/src/abstractsparsearrayinterface.jl index f8bd0e0..f36373e 100644 --- a/src/abstractsparsearrayinterface.jl +++ b/src/abstractsparsearrayinterface.jl @@ -3,6 +3,7 @@ using DerivableInterfaces: DerivableInterfaces, @derive, @interface, AbstractArr # This is to bring `ArrayLayouts.zero!` into the namespace # since it is considered part of the sparse array interface. using ArrayLayouts: zero! +using Random: Random, AbstractRNG, default_rng function eachstoredindex end function getstoredindex end @@ -32,6 +33,48 @@ function densearray(a::AbstractArray) return Array(a) end +@doc """ + sprand([rng], [T::Type], dims; density::Real) -> A::SparseArrayDOK{T} + +Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. +The optional `rng` argument specifies a random number generator, see also `Random`. +The optional `T` argument specifies the element type, which defaults to `Float64`. + +See also [`sprand!`](@ref). +""" sprand + +function sprand(::Type{T}, dims::Dims; density::Real=0.5) where {T} + return sprand(default_rng(), T, dims; density) +end +sprand(dims::Dims; density::Real=0.5) = sprand(default_rng(), Float64, dims; density) +function sprand(rng::AbstractRNG, dims::Dims; density::Real=0.5) + return sprand(rng, Float64, dims; density) +end +function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; density::Real=0.5) where {T} + A = SparseArrayDOK{T}(undef, dims) + sprand!(rng, A; density) + return A +end + +@doc """ + sprand!([rng], A::AbstractArray; density::Real=0.5) -> A + +Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. +The optional `rng` argument specifies a random number generator, see also `Random`. + +See also [`sprand`](@ref). +""" sprand! + +sprand!(A::AbstractArray; density::Real=0.5) = sprand!(default_rng(), A; density) +function sprand!(rng::AbstractRNG, A::AbstractArray; density::Real=0.5) + rand_inds = Random.randsubseq(rng, eachindex(A), density) + rand_entries = rand(rng, eltype(A), length(rand_inds)) + + for (I, v) in zip(rand_inds, rand_entries) + A[I] = v + end +end + # Minimal interface for `SparseArrayInterface`. # Fallbacks for dense/non-sparse arrays. @interface ::AbstractArrayInterface isstored(a::AbstractArray, I::Int...) = true From a4c599d8edb46813cf990e66feffd2b30111d2f9 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 12 Feb 2025 17:48:34 -0500 Subject: [PATCH 02/24] Be a bit more mindful with constructors --- examples/README.jl | 12 +++--- src/abstractsparsearrayinterface.jl | 2 +- src/sparsearraydok.jl | 60 +++++++++++++++++++++-------- test/test_sparsearraydok.jl | 54 +++++++++++++------------- 4 files changed, 78 insertions(+), 50 deletions(-) diff --git a/examples/README.jl b/examples/README.jl index da64f99..eb42f94 100644 --- a/examples/README.jl +++ b/examples/README.jl @@ -53,7 +53,7 @@ using SparseArraysBase: zero! using Test: @test, @test_throws -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) # AbstractArray interface: @@ -118,32 +118,32 @@ b = a[1:2, 2] @test b == [12, 0] @test storedlength(b) == 1 -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) a .= 2 for I in eachindex(a) @test a[I] == 2 end @test storedlength(a) == length(a) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) fill!(a, 2) for I in eachindex(a) @test a[I] == 2 end @test storedlength(a) == length(a) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) fill!(a, 0) @test iszero(a) @test iszero(storedlength(a)) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) a[1, 2] = 12 zero!(a) @test iszero(a) @test iszero(storedlength(a)) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) a[1, 2] = 12 b = zero(a) @test iszero(b) diff --git a/src/abstractsparsearrayinterface.jl b/src/abstractsparsearrayinterface.jl index f36373e..944834e 100644 --- a/src/abstractsparsearrayinterface.jl +++ b/src/abstractsparsearrayinterface.jl @@ -243,7 +243,7 @@ end a::AbstractArray, T::Type, size::Tuple{Vararg{Int}} ) # TODO: Define `default_similartype` or something like that? - return SparseArrayDOK{T}(size...) + return SparseArrayDOK{T}(undef, size) end # map over a specified subset of indices of the inputs. diff --git a/src/sparsearraydok.jl b/src/sparsearraydok.jl index 3161f95..78e928f 100644 --- a/src/sparsearraydok.jl +++ b/src/sparsearraydok.jl @@ -1,14 +1,56 @@ using Accessors: @set -using Dictionaries: Dictionary, IndexError, set! +using Dictionaries: AbstractDictionary, Dictionary, IndexError, set! function default_getunstoredindex(a::AbstractArray, I::Int...) return zero(eltype(a)) end +const DOKStorage{T,N} = Dictionary{CartesianIndex{N},T} + struct SparseArrayDOK{T,N,F} <: AbstractSparseArray{T,N} - storage::Dictionary{CartesianIndex{N},T} + storage::DOKStorage{T,N} size::NTuple{N,Int} getunstoredindex::F + + # bare constructor + function SparseArrayDOK{T,N,F}( + ::UndefInitializer, size::Dims{N}, getunstoredindex::F + ) where {T,N,F} + storage = DOKStorage{T,N}() + return new{T,N,F}(storage, size, getunstoredindex) + end + + # unchecked constructor from data + function SparseArrayDOK{T,N,F}( + storage::DOKStorage{T,N}, size::Dims{N}, getunstoredindex::F + ) where {T,N,F} + return new{T,N,F}(storage, size, getunstoredindex) + end +end + +# undef constructors +function SparseArrayDOK{T}( + ::UndefInitializer, dims::Dims, getunstoredindex=default_getunstoredindex +) where {T} + all(≥(0), dims) || throw(ArgumentError("Invalid dimensions: $dims")) + N = length(dims) + F = typeof(getunstoredindex) + return SparseArrayDOK{T,N,F}(undef, dims, getunstoredindex) +end +function SparseArrayDOK{T}(::UndefInitializer, dims::Int...) where {T} + return SparseArrayDOK{T}(undef, dims) +end + +# checked constructor from data: use `setindex!` to validate input +# does not take ownership of `storage`! +function SparseArrayDOK( + storage::Union{AbstractDictionary{I,T},AbstractDict{I,T}}, dims::Dims{N}, unstored... +) where {N,I<:Union{Int,CartesianIndex{N}},T} + A = SparseArrayDOK{T}(undef, dims, unstored...) + for (i, v) in pairs(storage) + A[i] = v + end + return A end function set_getunstoredindex(a::SparseArrayDOK, f) @@ -20,20 +62,6 @@ using DerivableInterfaces: DerivableInterfaces # This defines the destination type of various operations in DerivableInterfaces.jl. DerivableInterfaces.arraytype(::AbstractSparseArrayInterface, T::Type) = SparseArrayDOK{T} -function SparseArrayDOK{T,N}(size::Vararg{Int,N}) where {T,N} - getunstoredindex = default_getunstoredindex - F = typeof(getunstoredindex) - return SparseArrayDOK{T,N,F}(Dictionary{CartesianIndex{N},T}(), size, getunstoredindex) -end - -function SparseArrayDOK{T}(::UndefInitializer, size::Tuple{Vararg{Int}}) where {T} - return SparseArrayDOK{T,length(size)}(size...) -end - -function SparseArrayDOK{T}(size::Int...) where {T} - return SparseArrayDOK{T,length(size)}(size...) -end - using DerivableInterfaces: @array_aliases # Define `SparseMatrixDOK`, `AnySparseArrayDOK`, etc. @array_aliases SparseArrayDOK diff --git a/test/test_sparsearraydok.jl b/test/test_sparsearraydok.jl index 08cadf4..6d9f42e 100644 --- a/test/test_sparsearraydok.jl +++ b/test/test_sparsearraydok.jl @@ -23,7 +23,7 @@ arrayts = (Array,) dev(x) = adapt(arrayt, x) - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 @test a isa SparseArrayDOK{elt,2} @test size(a) == (2, 2) @@ -33,7 +33,7 @@ arrayts = (Array,) @test a[1, 2, 1] == 12 @test storedlength(a) == 1 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 for b in (similar(a, Float32, (3, 3)), similar(a, Float32, Base.OneTo.((3, 3)))) @test b isa SparseArrayDOK{Float32,2} @@ -41,7 +41,7 @@ arrayts = (Array,) @test size(b) == (3, 3) end - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = similar(a) bc = Broadcast.Broadcasted(x -> 2x, (a,)) @@ -50,49 +50,49 @@ arrayts = (Array,) @test b == [0 24; 0 0] @test storedlength(b) == 1 - a = SparseArrayDOK{elt}(3, 3, 3) + a = SparseArrayDOK{elt}(undef, 3, 3, 3) a[1, 2, 3] = 123 b = permutedims(a, (2, 3, 1)) @test b isa SparseArrayDOK{elt,3} @test b[2, 3, 1] == 123 @test storedlength(b) == 1 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = copy(a') @test b isa SparseArrayDOK{elt,2} @test b == [0 0; 12 0] @test storedlength(b) == 1 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = map(x -> 2x, a) @test b isa SparseArrayDOK{elt,2} @test b == [0 24; 0 0] @test storedlength(b) == 1 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = a * a' @test b isa SparseArrayDOK{elt,2} @test b == [144 0; 0 0] @test storedlength(b) == 1 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = a .+ 2 .* a' @test b isa SparseArrayDOK{elt,2} @test b == [0 12; 24 0] @test storedlength(b) == 2 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = a[1:2, 2] @test b isa SparseArrayDOK{elt,1} @test b == [12, 0] @test storedlength(b) == 1 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) @test iszero(a) a[2, 1] = 21 a[1, 2] = 12 @@ -101,7 +101,7 @@ arrayts = (Array,) @test sum(a) == 33 @test mapreduce(x -> 2x, +, a) == 66 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = similar(a) copyto!(b, a) @@ -110,58 +110,58 @@ arrayts = (Array,) @test b[1, 2] == 12 @test storedlength(b) == 1 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a .= 2 @test storedlength(a) == length(a) for I in eachindex(a) @test a[I] == 2 end - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) fill!(a, 2) @test storedlength(a) == length(a) for I in eachindex(a) @test a[I] == 2 end - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 zero!(a) @test iszero(a) @test iszero(storedlength(a)) - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 b = zero(a) @test b isa SparseArrayDOK{elt,2} @test iszero(b) @test iszero(storedlength(b)) - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 - b = SparseArrayDOK{elt}(4, 4) + b = SparseArrayDOK{elt}(undef, 4, 4) b[2:3, 2:3] .= a @test isone(storedlength(b)) @test b[2, 3] == 12 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 - b = SparseArrayDOK{elt}(4, 4) + b = SparseArrayDOK{elt}(undef, 4, 4) b[2:3, 2:3] = a @test isone(storedlength(b)) @test b[2, 3] == 12 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 - b = SparseArrayDOK{elt}(4, 4) + b = SparseArrayDOK{elt}(undef, 4, 4) c = @view b[2:3, 2:3] c .= a @test isone(storedlength(b)) @test b[2, 3] == 12 - a1 = SparseArrayDOK{elt}(2, 2) + a1 = SparseArrayDOK{elt}(undef, 2, 2) a1[1, 2] = 12 - a2 = SparseArrayDOK{elt}(2, 2) + a2 = SparseArrayDOK{elt}(undef, 2, 2) a2[2, 1] = 21 b = cat(a1, a2; dims=(1, 2)) @test b isa SparseArrayDOK{elt,2} @@ -173,21 +173,21 @@ arrayts = (Array,) # Printing # Not testing other element types since they change the # spacing so it isn't easy to make the test general. - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 2] = 12 @test sprint(show, "text/plain", a) == "$(summary(a)):\n ⋅ $(eltype(a)(12))\n ⋅ ⋅" end # Regression test for: # https://github.com/ITensor/SparseArraysBase.jl/issues/19 - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 1] = 1 a .*= 2 @test a == [2 0; 0 0] @test storedlength(a) == 1 # Test aliasing behavior. - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 1] = 11 a[1, 2] = 12 a[2, 2] = 22 @@ -202,7 +202,7 @@ arrayts = (Array,) @test storedlength(a) == 3 # Test aliasing behavior. - a = SparseArrayDOK{elt}(2, 2) + a = SparseArrayDOK{elt}(undef, 2, 2) a[1, 1] = 11 a[1, 2] = 12 a[2, 2] = 22 From 8e233094eb87eb3bd991fe9ac4f2b5d9da70a757 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 13 Feb 2025 11:03:09 -0500 Subject: [PATCH 03/24] Add `spzeros` --- src/abstractsparsearrayinterface.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/abstractsparsearrayinterface.jl b/src/abstractsparsearrayinterface.jl index 944834e..b767734 100644 --- a/src/abstractsparsearrayinterface.jl +++ b/src/abstractsparsearrayinterface.jl @@ -34,7 +34,14 @@ function densearray(a::AbstractArray) end @doc """ - sprand([rng], [T::Type], dims; density::Real) -> A::SparseArrayDOK{T} + spzeros([T::Type], dims) -> A::SparseArrayDOK{T} + +Create an empty size `dims` sparse array. +The optional `T` argument specifies the element type, which defualts to `Float64`. +""" spzeros + +spzeros(dims::Dims) = spzeros(Float64, dims) +spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. From a0f84451579af8ce2461a312b17463b9e3fef113 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 13 Feb 2025 11:03:33 -0500 Subject: [PATCH 04/24] update `sprand` to take in `rfn` function --- src/abstractsparsearrayinterface.jl | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/abstractsparsearrayinterface.jl b/src/abstractsparsearrayinterface.jl index b767734..a1a5828 100644 --- a/src/abstractsparsearrayinterface.jl +++ b/src/abstractsparsearrayinterface.jl @@ -43,6 +43,9 @@ The optional `T` argument specifies the element type, which defualts to `Float64 spzeros(dims::Dims) = spzeros(Float64, dims) spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) +@doc """ + sprand([rng], [T::Type], dims; density::Real=0.5, rfn::Function=rand) -> A::SparseArrayDOK{T} + Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. The optional `T` argument specifies the element type, which defaults to `Float64`. @@ -50,21 +53,21 @@ The optional `T` argument specifies the element type, which defaults to `Float64 See also [`sprand!`](@ref). """ sprand -function sprand(::Type{T}, dims::Dims; density::Real=0.5) where {T} - return sprand(default_rng(), T, dims; density) +function sprand(::Type{T}, dims::Dims; kwargs...) where {T} + return sprand(default_rng(), T, dims; kwargs...) end -sprand(dims::Dims; density::Real=0.5) = sprand(default_rng(), Float64, dims; density) -function sprand(rng::AbstractRNG, dims::Dims; density::Real=0.5) - return sprand(rng, Float64, dims; density) +sprand(dims::Dims; kwargs...) = sprand(default_rng(), Float64, dims; kwargs...) +function sprand(rng::AbstractRNG, dims::Dims; kwargs...) + return sprand(rng, Float64, dims; kwargs...) end -function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; density::Real=0.5) where {T} +function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; kwargs...) where {T} A = SparseArrayDOK{T}(undef, dims) - sprand!(rng, A; density) + sprand!(rng, A; kwargs...) return A end @doc """ - sprand!([rng], A::AbstractArray; density::Real=0.5) -> A + sprand!([rng], A::AbstractArray; density::Real=0.5, rfn::Function=rand) -> A Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. @@ -72,11 +75,13 @@ The optional `rng` argument specifies a random number generator, see also `Rando See also [`sprand`](@ref). """ sprand! -sprand!(A::AbstractArray; density::Real=0.5) = sprand!(default_rng(), A; density) -function sprand!(rng::AbstractRNG, A::AbstractArray; density::Real=0.5) +sprand!(A::AbstractArray; kwargs...) = sprand!(default_rng(), A; kwargs...) +function sprand!( + rng::AbstractRNG, A::AbstractArray; density::Real=0.5, rfn::Function=Random.rand +) + ArrayLayouts.zero!(A) rand_inds = Random.randsubseq(rng, eachindex(A), density) - rand_entries = rand(rng, eltype(A), length(rand_inds)) - + rand_entries = rfn(rng, eltype(A), length(rand_inds)) for (I, v) in zip(rand_inds, rand_entries) A[I] = v end From c47ef4422a68cbe784d57c4dfae7558380112a12 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 13 Feb 2025 12:26:07 -0500 Subject: [PATCH 05/24] update docstrings --- src/abstractsparsearrayinterface.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/abstractsparsearrayinterface.jl b/src/abstractsparsearrayinterface.jl index a1a5828..eb9fc46 100644 --- a/src/abstractsparsearrayinterface.jl +++ b/src/abstractsparsearrayinterface.jl @@ -37,7 +37,7 @@ end spzeros([T::Type], dims) -> A::SparseArrayDOK{T} Create an empty size `dims` sparse array. -The optional `T` argument specifies the element type, which defualts to `Float64`. +The optional `T` argument specifies the element type, which defaults to `Float64`. """ spzeros spzeros(dims::Dims) = spzeros(Float64, dims) @@ -49,6 +49,7 @@ spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. The optional `T` argument specifies the element type, which defaults to `Float64`. +The optional `rfn` argument can be used to control the type of random elements. See also [`sprand!`](@ref). """ sprand @@ -71,6 +72,7 @@ end Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. +The optional `rfn` argument can be used to control the type of random elements. See also [`sprand`](@ref). """ sprand! From b93fd0eb0cffee61b4bd088353b6449c26fb8c0b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 13 Feb 2025 12:29:24 -0500 Subject: [PATCH 06/24] update README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bdf69d5..68a60d3 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ using SparseArraysBase: zero! using Test: @test, @test_throws -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) ```` AbstractArray interface: @@ -122,32 +122,32 @@ b = a[1:2, 2] @test b == [12, 0] @test storedlength(b) == 1 -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) a .= 2 for I in eachindex(a) @test a[I] == 2 end @test storedlength(a) == length(a) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) fill!(a, 2) for I in eachindex(a) @test a[I] == 2 end @test storedlength(a) == length(a) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) fill!(a, 0) @test iszero(a) @test iszero(storedlength(a)) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) a[1, 2] = 12 zero!(a) @test iszero(a) @test iszero(storedlength(a)) -a = SparseArrayDOK{Float64}(2, 2) +a = SparseArrayDOK{Float64}(undef, 2, 2) a[1, 2] = 12 b = zero(a) @test iszero(b) From ddafce7e000ae31461b225d38044712dfed7676a Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 13 Feb 2025 12:32:23 -0500 Subject: [PATCH 07/24] Bump v0.3.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 7b339fc..b7acbad 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SparseArraysBase" uuid = "0d5efcca-f356-4864-8770-e1ed8d78f208" authors = ["ITensor developers and contributors"] -version = "0.2.11" +version = "0.3.0" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" From e4b3ae46472f4117f4c921502807e83d1612969c Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 13 Feb 2025 12:39:02 -0500 Subject: [PATCH 08/24] Bump skeleton --- .github/workflows/IntegrationTestRequest.yml | 14 +++++++++++ .github/workflows/Register.yml | 25 ++++++++++---------- LICENSE | 2 +- docs/make.jl | 2 +- docs/src/reference.md | 5 ++++ test/runtests.jl | 6 +++-- 6 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/IntegrationTestRequest.yml create mode 100644 docs/src/reference.md diff --git a/.github/workflows/IntegrationTestRequest.yml b/.github/workflows/IntegrationTestRequest.yml new file mode 100644 index 0000000..d42fcca --- /dev/null +++ b/.github/workflows/IntegrationTestRequest.yml @@ -0,0 +1,14 @@ +name: "Integration Test Request" + +on: + issue_comment: + types: [created] + +jobs: + integrationrequest: + if: | + github.event.issue.pull_request && + contains(fromJSON('["OWNER", "COLLABORATOR", "MEMBER"]'), github.event.comment.author_association) + uses: ITensor/ITensorActions/.github/workflows/IntegrationTestRequest.yml@main + with: + localregistry: https://github.com/ITensor/ITensorRegistry.git diff --git a/.github/workflows/Register.yml b/.github/workflows/Register.yml index 5b7cd3b..ac01aa1 100644 --- a/.github/workflows/Register.yml +++ b/.github/workflows/Register.yml @@ -1,16 +1,17 @@ name: Register Package on: workflow_dispatch: - inputs: - version: - description: Version to register or component to bump - required: true + pull_request: + types: + - closed + paths: + - 'Project.toml' + branches: + - 'master' + - 'main' jobs: - register: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: julia-actions/RegisterAction@latest - with: - token: ${{ secrets.GITHUB_TOKEN }} + Register: + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true + uses: "ITensor/ITensorActions/.github/workflows/Registrator.yml@main" + with: + localregistry: ITensor/ITensorRegistry diff --git a/LICENSE b/LICENSE index 7f5c8c6..89cf357 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 ITensor developers +Copyright (c) 2025 ITensor developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/make.jl b/docs/make.jl index 8d4c368..991d772 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -16,7 +16,7 @@ makedocs(; edit_link="main", assets=String[], ), - pages=["Home" => "index.md"], + pages=["Home" => "index.md", "Reference" => "reference.md"], ) deploydocs(; diff --git a/docs/src/reference.md b/docs/src/reference.md new file mode 100644 index 0000000..c1eadba --- /dev/null +++ b/docs/src/reference.md @@ -0,0 +1,5 @@ +# Reference + +```@autodocs +Modules = [SparseArraysBase] +``` diff --git a/test/runtests.jl b/test/runtests.jl index bd97441..1c52c3e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,9 +24,11 @@ isexamplefile(fn) = # tests in groups based on folder structure for testgroup in filter(isdir, readdir(@__DIR__)) if GROUP == "ALL" || GROUP == uppercase(testgroup) - for file in filter(istestfile, readdir(joinpath(@__DIR__, testgroup); join=true)) + groupdir = joinpath(@__DIR__, testgroup) + for file in filter(istestfile, readdir(groupdir)) + filename = joinpath(groupdir, file) @eval @safetestset $file begin - include($file) + include($filename) end end end From 7959946d663819e6d41ac9816243d3fdf16a506b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:26:28 -0500 Subject: [PATCH 09/24] move functions --- src/abstractsparsearray.jl | 60 +++++++++++++++++++++++++++++ src/abstractsparsearrayinterface.jl | 57 --------------------------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/abstractsparsearray.jl b/src/abstractsparsearray.jl index 14e01d1..b71875b 100644 --- a/src/abstractsparsearray.jl +++ b/src/abstractsparsearray.jl @@ -53,3 +53,63 @@ function getunstoredindex(a::ReplacedUnstoredSparseArray, I::Int...) end eachstoredindex(a::ReplacedUnstoredSparseArray) = eachstoredindex(parent(a)) @derive ReplacedUnstoredSparseArray AbstractArrayOps + +# Special-purpose constructors +# ---------------------------- +using Random: Random, AbstractRNG, default_rng + +@doc """ + spzeros([T::Type], dims) -> A::SparseArrayDOK{T} + +Create an empty size `dims` sparse array. +The optional `T` argument specifies the element type, which defaults to `Float64`. +""" spzeros + +spzeros(dims::Dims) = spzeros(Float64, dims) +spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) + +@doc """ + sprand([rng], [T::Type], dims; density::Real=0.5, rfn::Function=rand) -> A::SparseArrayDOK{T} + +Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. +The optional `rng` argument specifies a random number generator, see also `Random`. +The optional `T` argument specifies the element type, which defaults to `Float64`. +The optional `rfn` argument can be used to control the type of random elements. + +See also [`sprand!`](@ref). +""" sprand + +function sprand(::Type{T}, dims::Dims; kwargs...) where {T} + return sprand(default_rng(), T, dims; kwargs...) +end +sprand(dims::Dims; kwargs...) = sprand(default_rng(), Float64, dims; kwargs...) +function sprand(rng::AbstractRNG, dims::Dims; kwargs...) + return sprand(rng, Float64, dims; kwargs...) +end +function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; kwargs...) where {T} + A = SparseArrayDOK{T}(undef, dims) + sprand!(rng, A; kwargs...) + return A +end + +@doc """ + sprand!([rng], A::AbstractArray; density::Real=0.5, rfn::Function=rand) -> A + +Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. +The optional `rng` argument specifies a random number generator, see also `Random`. +The optional `rfn` argument can be used to control the type of random elements. + +See also [`sprand`](@ref). +""" sprand! + +sprand!(A::AbstractArray; kwargs...) = sprand!(default_rng(), A; kwargs...) +function sprand!( + rng::AbstractRNG, A::AbstractArray; density::Real=0.5, rfn::Function=Random.rand +) + ArrayLayouts.zero!(A) + rand_inds = Random.randsubseq(rng, eachindex(A), density) + rand_entries = rfn(rng, eltype(A), length(rand_inds)) + for (I, v) in zip(rand_inds, rand_entries) + A[I] = v + end +end diff --git a/src/abstractsparsearrayinterface.jl b/src/abstractsparsearrayinterface.jl index eb9fc46..82d984e 100644 --- a/src/abstractsparsearrayinterface.jl +++ b/src/abstractsparsearrayinterface.jl @@ -3,7 +3,6 @@ using DerivableInterfaces: DerivableInterfaces, @derive, @interface, AbstractArr # This is to bring `ArrayLayouts.zero!` into the namespace # since it is considered part of the sparse array interface. using ArrayLayouts: zero! -using Random: Random, AbstractRNG, default_rng function eachstoredindex end function getstoredindex end @@ -33,62 +32,6 @@ function densearray(a::AbstractArray) return Array(a) end -@doc """ - spzeros([T::Type], dims) -> A::SparseArrayDOK{T} - -Create an empty size `dims` sparse array. -The optional `T` argument specifies the element type, which defaults to `Float64`. -""" spzeros - -spzeros(dims::Dims) = spzeros(Float64, dims) -spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) - -@doc """ - sprand([rng], [T::Type], dims; density::Real=0.5, rfn::Function=rand) -> A::SparseArrayDOK{T} - -Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. -The optional `rng` argument specifies a random number generator, see also `Random`. -The optional `T` argument specifies the element type, which defaults to `Float64`. -The optional `rfn` argument can be used to control the type of random elements. - -See also [`sprand!`](@ref). -""" sprand - -function sprand(::Type{T}, dims::Dims; kwargs...) where {T} - return sprand(default_rng(), T, dims; kwargs...) -end -sprand(dims::Dims; kwargs...) = sprand(default_rng(), Float64, dims; kwargs...) -function sprand(rng::AbstractRNG, dims::Dims; kwargs...) - return sprand(rng, Float64, dims; kwargs...) -end -function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; kwargs...) where {T} - A = SparseArrayDOK{T}(undef, dims) - sprand!(rng, A; kwargs...) - return A -end - -@doc """ - sprand!([rng], A::AbstractArray; density::Real=0.5, rfn::Function=rand) -> A - -Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. -The optional `rng` argument specifies a random number generator, see also `Random`. -The optional `rfn` argument can be used to control the type of random elements. - -See also [`sprand`](@ref). -""" sprand! - -sprand!(A::AbstractArray; kwargs...) = sprand!(default_rng(), A; kwargs...) -function sprand!( - rng::AbstractRNG, A::AbstractArray; density::Real=0.5, rfn::Function=Random.rand -) - ArrayLayouts.zero!(A) - rand_inds = Random.randsubseq(rng, eachindex(A), density) - rand_entries = rfn(rng, eltype(A), length(rand_inds)) - for (I, v) in zip(rand_inds, rand_entries) - A[I] = v - end -end - # Minimal interface for `SparseArrayInterface`. # Fallbacks for dense/non-sparse arrays. @interface ::AbstractArrayInterface isstored(a::AbstractArray, I::Int...) = true From 39353644def897dc1507aac8b8f583b37fa57909 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:28:01 -0500 Subject: [PATCH 10/24] export new functions --- src/SparseArraysBase.jl | 1 + test/test_linalg.jl | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/SparseArraysBase.jl b/src/SparseArraysBase.jl index 0fa0ad2..9484b2b 100644 --- a/src/SparseArraysBase.jl +++ b/src/SparseArraysBase.jl @@ -12,6 +12,7 @@ export SparseArrayDOK, storedlength, storedpairs, storedvalues +export spzeros, sprand, sprand! include("abstractsparsearrayinterface.jl") include("sparsearrayinterface.jl") diff --git a/test/test_linalg.jl b/test/test_linalg.jl index f48585c..d532f13 100644 --- a/test/test_linalg.jl +++ b/test/test_linalg.jl @@ -5,17 +5,6 @@ using StableRNGs: StableRNG const rng = StableRNG(123) -# TODO: add this to main package -function sprand(rng::Random.AbstractRNG, ::Type{T}, sz::Base.Dims; p::Real=0.5) where {T} - A = SparseArrayDOK{T}(undef, sz) - for I in eachindex(A) - if rand(rng) < p - A[I] = rand(rng, T) - end - end - return A -end - @testset "mul!" begin T = Float64 szA = (2, 2) From 04862d248a3895305be5025c5023634d57a810c6 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:45:17 -0500 Subject: [PATCH 11/24] slightly re-organize constructors --- src/sparsearraydok.jl | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/sparsearraydok.jl b/src/sparsearraydok.jl index 78e928f..16d5eb5 100644 --- a/src/sparsearraydok.jl +++ b/src/sparsearraydok.jl @@ -28,24 +28,30 @@ struct SparseArrayDOK{T,N,F} <: AbstractSparseArray{T,N} end end -# undef constructors -function SparseArrayDOK{T}( +## constructors with T and N +# -> make SparseMatrix{T}(undef, ...) work +function SparseArrayDOK{T,N}( ::UndefInitializer, dims::Dims, getunstoredindex=default_getunstoredindex -) where {T} - all(≥(0), dims) || throw(ArgumentError("Invalid dimensions: $dims")) - N = length(dims) +) where {T,N} + (length(dims) == N && all(≥(0), dims)) || + throw(ArgumentError("Invalid dimensions: $dims")) F = typeof(getunstoredindex) return SparseArrayDOK{T,N,F}(undef, dims, getunstoredindex) end -function SparseArrayDOK{T}(::UndefInitializer, dims::Int...) where {T} - return SparseArrayDOK{T}(undef, dims) + +## constructors with T +function SparseArrayDOK{T}(::UndefInitializer, dims::Dims{N}, unstored...) where {T,N} + return SparseArrayDOK{T,N}(undef, dims, unstored...) +end + +function SparseArrayDOK{T}(::UndefInitializer, dims::Vararg{Int,N}) where {T,N} + return SparseArrayDOK{T,N}(undef, dims) end -# checked constructor from data: use `setindex!` to validate input -# does not take ownership of `storage`! -function SparseArrayDOK( - storage::Union{AbstractDictionary{I,T},AbstractDict{I,T}}, dims::Dims{N}, unstored... -) where {N,I<:Union{Int,CartesianIndex{N}},T} +# checked constructor from data: use `setindex!` to validate/convert input +function SparseArrayDOK{T}( + storage::Union{AbstractDictionary,AbstractDict}, dims::Dims, unstored... +) where {T} A = SparseArrayDOK{T}(undef, dims, unstored...) for (i, v) in pairs(storage) A[i] = v @@ -53,6 +59,14 @@ function SparseArrayDOK( return A end +## constructors without type parameters +function SparseArrayDOK( + storage::Union{AbstractDictionary,AbstractDict}, dims::Dims, unstored... +) + T = valtype(storage) + return SparseArrayDOK{T}(storage, dims, unstored...) +end + function set_getunstoredindex(a::SparseArrayDOK, f) @set a.getunstoredindex = f return a From d2e97ae706c396e2b1dda3111b1ed9e32c83c3de Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:52:39 -0500 Subject: [PATCH 12/24] update registrator action --- .github/workflows/{Register.yml => Registrator.yml} | 7 +++++++ 1 file changed, 7 insertions(+) rename .github/workflows/{Register.yml => Registrator.yml} (76%) diff --git a/.github/workflows/Register.yml b/.github/workflows/Registrator.yml similarity index 76% rename from .github/workflows/Register.yml rename to .github/workflows/Registrator.yml index ac01aa1..255e2af 100644 --- a/.github/workflows/Register.yml +++ b/.github/workflows/Registrator.yml @@ -9,9 +9,16 @@ on: branches: - 'master' - 'main' + +permissions: + contents: write + pull-requests: write + jobs: Register: if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true uses: "ITensor/ITensorActions/.github/workflows/Registrator.yml@main" with: localregistry: ITensor/ITensorRegistry + secrets: + REGISTRATOR_KEY: ${{ secrets.REGISTRATOR_KEY }} From e10bcbf30f69a6eb3622ee0a15a652085965970d Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:52:43 -0500 Subject: [PATCH 13/24] enable (some of) Aqua --- test/test_aqua.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_aqua.jl b/test/test_aqua.jl index 0379869..0bff54a 100644 --- a/test/test_aqua.jl +++ b/test/test_aqua.jl @@ -3,6 +3,5 @@ using Aqua: Aqua using Test: @testset @testset "Code quality (Aqua.jl)" begin - # TODO: Fix Aqua issues and add this back. - # Aqua.test_all(SparseArraysBase) + Aqua.test_all(SparseArraysBase; ambiguities=false) end From 4348f2bebcb9c30f4a46d905e8db90e1a4693e37 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:55:11 -0500 Subject: [PATCH 14/24] fix undefined export --- src/SparseArraysBase.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SparseArraysBase.jl b/src/SparseArraysBase.jl index 9484b2b..ec493a2 100644 --- a/src/SparseArraysBase.jl +++ b/src/SparseArraysBase.jl @@ -8,7 +8,7 @@ export SparseArrayDOK, OneElementVector, eachstoredindex, isstored, - oneelementarray, + oneelement, storedlength, storedpairs, storedvalues From e7053a835f419cd8bb205e10f6cc1fcd29af261c Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:57:28 -0500 Subject: [PATCH 15/24] remove tested exports (included in aqua testset) --- test/test_exports.jl | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/test_exports.jl diff --git a/test/test_exports.jl b/test/test_exports.jl deleted file mode 100644 index 56bde16..0000000 --- a/test/test_exports.jl +++ /dev/null @@ -1,20 +0,0 @@ -using SparseArraysBase: SparseArraysBase -using Test: @test, @testset -@testset "Test exports" begin - exports = [ - :SparseArraysBase, - :SparseArrayDOK, - :SparseMatrixDOK, - :SparseVectorDOK, - :OneElementArray, - :OneElementMatrix, - :OneElementVector, - :eachstoredindex, - :isstored, - :oneelementarray, - :storedlength, - :storedpairs, - :storedvalues, - ] - @test issetequal(names(SparseArraysBase), exports) -end From 5ab189a22efbf45755cc652322914841600b3635 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 14:59:40 -0500 Subject: [PATCH 16/24] update testfunction --- test/test_linalg.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/test_linalg.jl b/test/test_linalg.jl index d532f13..5f110f7 100644 --- a/test/test_linalg.jl +++ b/test/test_linalg.jl @@ -1,6 +1,5 @@ -using SparseArraysBase: SparseArrayDOK +using SparseArraysBase: sprand using LinearAlgebra: mul! -using Random: Random using StableRNGs: StableRNG const rng = StableRNG(123) @@ -11,10 +10,10 @@ const rng = StableRNG(123) szB = (2, 2) szC = (szA[1], szB[2]) - for p in 0.0:0.25:1 - C = sprand(rng, T, szC; p) - A = sprand(rng, T, szA; p) - B = sprand(rng, T, szB; p) + for density in 0.0:0.25:1 + C = sprand(rng, T, szC; density) + A = sprand(rng, T, szA; density) + B = sprand(rng, T, szB; density) check1 = mul!(Array(C), Array(A), Array(B)) @test mul!(copy(C), A, B) ≈ check1 From a55f7f6ee54233c7afaa63b98ab519841706b779 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 15:27:44 -0500 Subject: [PATCH 17/24] change `rfn` to `rand_function` --- src/abstractsparsearray.jl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/abstractsparsearray.jl b/src/abstractsparsearray.jl index b71875b..a5a1d98 100644 --- a/src/abstractsparsearray.jl +++ b/src/abstractsparsearray.jl @@ -69,12 +69,14 @@ spzeros(dims::Dims) = spzeros(Float64, dims) spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) @doc """ - sprand([rng], [T::Type], dims; density::Real=0.5, rfn::Function=rand) -> A::SparseArrayDOK{T} + sprand([rng], [T::Type], dims; density::Real=0.5, rand_function::Function=rand) -> A::SparseArrayDOK{T} Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. The optional `T` argument specifies the element type, which defaults to `Float64`. -The optional `rfn` argument can be used to control the type of random elements. +The optional `rand_function` argument can be used to control the type of random elements, and should support +the signature `rand_function(rng, T, N)` to generate `N` entries of type `T`. + See also [`sprand!`](@ref). """ sprand @@ -93,22 +95,23 @@ function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; kwargs...) where {T} end @doc """ - sprand!([rng], A::AbstractArray; density::Real=0.5, rfn::Function=rand) -> A + sprand!([rng], A::AbstractArray; density::Real=0.5, rand_function::Function=rand) -> A Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. -The optional `rfn` argument can be used to control the type of random elements. +The optional `rand_function` argument can be used to control the type of random elements, and should support +the signature `rand_function(rng, T, N)` to generate `N` entries of type `T`. See also [`sprand`](@ref). """ sprand! sprand!(A::AbstractArray; kwargs...) = sprand!(default_rng(), A; kwargs...) function sprand!( - rng::AbstractRNG, A::AbstractArray; density::Real=0.5, rfn::Function=Random.rand + rng::AbstractRNG, A::AbstractArray; density::Real=0.5, rand_function::Function=Random.rand ) ArrayLayouts.zero!(A) rand_inds = Random.randsubseq(rng, eachindex(A), density) - rand_entries = rfn(rng, eltype(A), length(rand_inds)) + rand_entries = rand_function(rng, eltype(A), length(rand_inds)) for (I, v) in zip(rand_inds, rand_entries) A[I] = v end From bafc0afa6018f16d2f844f4334e36e80cdaccaf6 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 15:28:27 -0500 Subject: [PATCH 18/24] Add `SparseArrayDOK` docstrings --- src/sparsearraydok.jl | 62 ++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/sparsearraydok.jl b/src/sparsearraydok.jl index 16d5eb5..12f5112 100644 --- a/src/sparsearraydok.jl +++ b/src/sparsearraydok.jl @@ -7,6 +7,12 @@ end const DOKStorage{T,N} = Dictionary{CartesianIndex{N},T} +""" + SparseArrayDOK{T,N,F} <: AbstractSparseArray{T,N} + +`N`-dimensional sparse Dictionary-of-keys (DOK) array with elements of type `T`, +optionally with a function of type `F` to instantiate non-stored elements. +""" struct SparseArrayDOK{T,N,F} <: AbstractSparseArray{T,N} storage::DOKStorage{T,N} size::NTuple{N,Int} @@ -28,8 +34,20 @@ struct SparseArrayDOK{T,N,F} <: AbstractSparseArray{T,N} end end -## constructors with T and N -# -> make SparseMatrix{T}(undef, ...) work +# Constructors +# ------------ +""" + SparseArrayDOK{T}(undef, dims, unstored...) + SparseArrayDOK{T}(undef, dims...) + SparseArrayDOK{T,N}(undef, dims, unstored...) + SparseArrayDOK{T,N}(undef, dims...) + +Construct an uninitialized `N`-dimensional [`SparseArrayDOK`](@ref) containing +elements of type `T`. `N` can either be supplied explicitly, or be determined by +the length or number of `dims`. +""" +SparseArrayDOK{T,N}(::UndefInitializer, dims, unstored...) + function SparseArrayDOK{T,N}( ::UndefInitializer, dims::Dims, getunstoredindex=default_getunstoredindex ) where {T,N} @@ -38,33 +56,45 @@ function SparseArrayDOK{T,N}( F = typeof(getunstoredindex) return SparseArrayDOK{T,N,F}(undef, dims, getunstoredindex) end - -## constructors with T function SparseArrayDOK{T}(::UndefInitializer, dims::Dims{N}, unstored...) where {T,N} return SparseArrayDOK{T,N}(undef, dims, unstored...) end - function SparseArrayDOK{T}(::UndefInitializer, dims::Vararg{Int,N}) where {T,N} return SparseArrayDOK{T,N}(undef, dims) end +""" + SparseArrayDOK(storage::Union{AbstractDict,AbstractDictionary}, dims, unstored...) + SparseArrayDOK{T}(storage::Union{AbstractDict,AbstractDictionary}, dims, unstored...) + SparseArrayDOK{T,N}(storage::Union{AbstractDict,AbstractDictionary}, dims, unstored...) + +Construct an `N`-dimensional [`SparseArrayDOK`](@ref) containing elements of type `T`. Both +`T` and `N` can either be supplied explicitly or be determined by the `storage` and the +length or number of `dims`. + +This constructor does not take ownership of the supplied storage, and will result in an +independent container. +""" +SparseArrayDOK{T,N}(::Union{AbstractDict,AbstractDictionary}, dims, unstored...) + +const AbstractDictOrDictionary = Union{AbstractDict,AbstractDictionary} # checked constructor from data: use `setindex!` to validate/convert input -function SparseArrayDOK{T}( - storage::Union{AbstractDictionary,AbstractDict}, dims::Dims, unstored... -) where {T} - A = SparseArrayDOK{T}(undef, dims, unstored...) +function SparseArrayDOK{T,N}( + storage::AbstractDictOrDictionary, dims::Dims, unstored... +) where {T,N} + A = SparseArrayDOK{T,N}(undef, dims, unstored...) for (i, v) in pairs(storage) A[i] = v end return A end - -## constructors without type parameters -function SparseArrayDOK( - storage::Union{AbstractDictionary,AbstractDict}, dims::Dims, unstored... -) - T = valtype(storage) - return SparseArrayDOK{T}(storage, dims, unstored...) +function SparseArrayDOK{T}( + storage::AbstractDictOrDictionary, dims::Dims, unstored... +) where {T} + return SparseArrayDOK{T,length(dims)}(storage, dims, unstored...) +end +function SparseArrayDOK(storage::AbstractDictOrDictionary, dims::Dims, unstored...) + return SparseArrayDOK{valtype(storage)}(storage, dims, unstored...) end function set_getunstoredindex(a::SparseArrayDOK, f) From eceadd269333f1506d2c4726e89f6b941ed31337 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 16:43:25 -0500 Subject: [PATCH 19/24] Revert "remove tested exports (included in aqua testset)" This reverts commit e7053a835f419cd8bb205e10f6cc1fcd29af261c. --- test/test_exports.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/test_exports.jl diff --git a/test/test_exports.jl b/test/test_exports.jl new file mode 100644 index 0000000..56bde16 --- /dev/null +++ b/test/test_exports.jl @@ -0,0 +1,20 @@ +using SparseArraysBase: SparseArraysBase +using Test: @test, @testset +@testset "Test exports" begin + exports = [ + :SparseArraysBase, + :SparseArrayDOK, + :SparseMatrixDOK, + :SparseVectorDOK, + :OneElementArray, + :OneElementMatrix, + :OneElementVector, + :eachstoredindex, + :isstored, + :oneelementarray, + :storedlength, + :storedpairs, + :storedvalues, + ] + @test issetequal(names(SparseArraysBase), exports) +end From e9c14898e00ef923bd77fd23edb88a25a4fe846a Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 27 Feb 2025 16:44:22 -0500 Subject: [PATCH 20/24] update tested exports --- test/test_exports.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_exports.jl b/test/test_exports.jl index 56bde16..aebecf7 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -11,10 +11,13 @@ using Test: @test, @testset :OneElementVector, :eachstoredindex, :isstored, - :oneelementarray, + :oneelement, :storedlength, :storedpairs, :storedvalues, + :spzeros, + :sprand, + :sprand!, ] @test issetequal(names(SparseArraysBase), exports) end From 45947eccd45ebe97b2567ffb344ee72850aef1e0 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 28 Feb 2025 06:19:44 -0500 Subject: [PATCH 21/24] merge export lists --- src/SparseArraysBase.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SparseArraysBase.jl b/src/SparseArraysBase.jl index ec493a2..41cd9c4 100644 --- a/src/SparseArraysBase.jl +++ b/src/SparseArraysBase.jl @@ -11,8 +11,10 @@ export SparseArrayDOK, oneelement, storedlength, storedpairs, - storedvalues -export spzeros, sprand, sprand! + storedvalues, + spzeros, + sprand, + sprand! include("abstractsparsearrayinterface.jl") include("sparsearrayinterface.jl") From 34551cf40dea5a6dd04666990cc265be1f3d0f23 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 28 Feb 2025 06:21:25 -0500 Subject: [PATCH 22/24] change `rand_function` to `randfun` --- src/abstractsparsearray.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/abstractsparsearray.jl b/src/abstractsparsearray.jl index a5a1d98..b735d7b 100644 --- a/src/abstractsparsearray.jl +++ b/src/abstractsparsearray.jl @@ -69,13 +69,13 @@ spzeros(dims::Dims) = spzeros(Float64, dims) spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) @doc """ - sprand([rng], [T::Type], dims; density::Real=0.5, rand_function::Function=rand) -> A::SparseArrayDOK{T} + sprand([rng], [T::Type], dims; density::Real=0.5, randfun::Function=rand) -> A::SparseArrayDOK{T} Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. The optional `T` argument specifies the element type, which defaults to `Float64`. -The optional `rand_function` argument can be used to control the type of random elements, and should support -the signature `rand_function(rng, T, N)` to generate `N` entries of type `T`. +The optional `randfun` argument can be used to control the type of random elements, and should support +the signature `randfun(rng, T, N)` to generate `N` entries of type `T`. See also [`sprand!`](@ref). @@ -95,24 +95,24 @@ function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; kwargs...) where {T} end @doc """ - sprand!([rng], A::AbstractArray; density::Real=0.5, rand_function::Function=rand) -> A + sprand!([rng], A::AbstractArray; density::Real=0.5, randfun::Function=rand) -> A Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. -The optional `rand_function` argument can be used to control the type of random elements, and should support -the signature `rand_function(rng, T, N)` to generate `N` entries of type `T`. +The optional `randfun` argument can be used to control the type of random elements, and should support +the signature `randfun(rng, T, N)` to generate `N` entries of type `T`. See also [`sprand`](@ref). """ sprand! sprand!(A::AbstractArray; kwargs...) = sprand!(default_rng(), A; kwargs...) function sprand!( - rng::AbstractRNG, A::AbstractArray; density::Real=0.5, rand_function::Function=Random.rand + rng::AbstractRNG, A::AbstractArray; density::Real=0.5, randfun::Function=Random.rand ) ArrayLayouts.zero!(A) rand_inds = Random.randsubseq(rng, eachindex(A), density) - rand_entries = rand_function(rng, eltype(A), length(rand_inds)) - for (I, v) in zip(rand_inds, rand_entries) + rand_entries = randfun(rng, eltype(A), length(rand_inds)) + @inbounds for (I, v) in zip(rand_inds, rand_entries) A[I] = v end end From 84406615c77fc9fa1e9c3ace6a986de78dd14019 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 28 Feb 2025 08:47:32 -0500 Subject: [PATCH 23/24] change `spzeros`, `sprand` and `sprand!` to `sparsezeros`, `sparserand` and `sparserand!` --- src/SparseArraysBase.jl | 6 +++--- src/abstractsparsearray.jl | 38 +++++++++++++++++++------------------- test/test_exports.jl | 6 +++--- test/test_linalg.jl | 8 ++++---- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/SparseArraysBase.jl b/src/SparseArraysBase.jl index 41cd9c4..ecbb04d 100644 --- a/src/SparseArraysBase.jl +++ b/src/SparseArraysBase.jl @@ -12,9 +12,9 @@ export SparseArrayDOK, storedlength, storedpairs, storedvalues, - spzeros, - sprand, - sprand! + sparsezeros, + sparserand, + sparserand! include("abstractsparsearrayinterface.jl") include("sparsearrayinterface.jl") diff --git a/src/abstractsparsearray.jl b/src/abstractsparsearray.jl index b735d7b..c427c4d 100644 --- a/src/abstractsparsearray.jl +++ b/src/abstractsparsearray.jl @@ -59,17 +59,17 @@ eachstoredindex(a::ReplacedUnstoredSparseArray) = eachstoredindex(parent(a)) using Random: Random, AbstractRNG, default_rng @doc """ - spzeros([T::Type], dims) -> A::SparseArrayDOK{T} + sparsezeros([T::Type], dims) -> A::SparseArrayDOK{T} Create an empty size `dims` sparse array. The optional `T` argument specifies the element type, which defaults to `Float64`. -""" spzeros +""" sparsezeros -spzeros(dims::Dims) = spzeros(Float64, dims) -spzeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) +sparsezeros(dims::Dims) = sparsezeros(Float64, dims) +sparsezeros(::Type{T}, dims::Dims) where {T} = SparseArrayDOK{T}(undef, dims) @doc """ - sprand([rng], [T::Type], dims; density::Real=0.5, randfun::Function=rand) -> A::SparseArrayDOK{T} + sparserand([rng], [T::Type], dims; density::Real=0.5, randfun::Function=rand) -> A::SparseArrayDOK{T} Create a random size `dims` sparse array in which the probability of any element being stored is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. @@ -78,35 +78,35 @@ The optional `randfun` argument can be used to control the type of random elemen the signature `randfun(rng, T, N)` to generate `N` entries of type `T`. -See also [`sprand!`](@ref). -""" sprand +See also [`sparserand!`](@ref). +""" sparserand -function sprand(::Type{T}, dims::Dims; kwargs...) where {T} - return sprand(default_rng(), T, dims; kwargs...) +function sparserand(::Type{T}, dims::Dims; kwargs...) where {T} + return sparserand(default_rng(), T, dims; kwargs...) end -sprand(dims::Dims; kwargs...) = sprand(default_rng(), Float64, dims; kwargs...) -function sprand(rng::AbstractRNG, dims::Dims; kwargs...) - return sprand(rng, Float64, dims; kwargs...) +sparserand(dims::Dims; kwargs...) = sparserand(default_rng(), Float64, dims; kwargs...) +function sparserand(rng::AbstractRNG, dims::Dims; kwargs...) + return sparserand(rng, Float64, dims; kwargs...) end -function sprand(rng::AbstractRNG, ::Type{T}, dims::Dims; kwargs...) where {T} +function sparserand(rng::AbstractRNG, ::Type{T}, dims::Dims; kwargs...) where {T} A = SparseArrayDOK{T}(undef, dims) - sprand!(rng, A; kwargs...) + sparserand!(rng, A; kwargs...) return A end @doc """ - sprand!([rng], A::AbstractArray; density::Real=0.5, randfun::Function=rand) -> A + sparserand!([rng], A::AbstractArray; density::Real=0.5, randfun::Function=rand) -> A Overwrite part of an array with random entries, where the probability of overwriting is independently given by `density`. The optional `rng` argument specifies a random number generator, see also `Random`. The optional `randfun` argument can be used to control the type of random elements, and should support the signature `randfun(rng, T, N)` to generate `N` entries of type `T`. -See also [`sprand`](@ref). -""" sprand! +See also [`sparserand`](@ref). +""" sparserand! -sprand!(A::AbstractArray; kwargs...) = sprand!(default_rng(), A; kwargs...) -function sprand!( +sparserand!(A::AbstractArray; kwargs...) = sparserand!(default_rng(), A; kwargs...) +function sparserand!( rng::AbstractRNG, A::AbstractArray; density::Real=0.5, randfun::Function=Random.rand ) ArrayLayouts.zero!(A) diff --git a/test/test_exports.jl b/test/test_exports.jl index aebecf7..575a8e1 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -15,9 +15,9 @@ using Test: @test, @testset :storedlength, :storedpairs, :storedvalues, - :spzeros, - :sprand, - :sprand!, + :sparsezeros, + :sparserand, + :sparserand!, ] @test issetequal(names(SparseArraysBase), exports) end diff --git a/test/test_linalg.jl b/test/test_linalg.jl index 5f110f7..7c67354 100644 --- a/test/test_linalg.jl +++ b/test/test_linalg.jl @@ -1,4 +1,4 @@ -using SparseArraysBase: sprand +using SparseArraysBase: sparserand using LinearAlgebra: mul! using StableRNGs: StableRNG @@ -11,9 +11,9 @@ const rng = StableRNG(123) szC = (szA[1], szB[2]) for density in 0.0:0.25:1 - C = sprand(rng, T, szC; density) - A = sprand(rng, T, szA; density) - B = sprand(rng, T, szB; density) + C = sparserand(rng, T, szC; density) + A = sparserand(rng, T, szA; density) + B = sparserand(rng, T, szB; density) check1 = mul!(Array(C), Array(A), Array(B)) @test mul!(copy(C), A, B) ≈ check1 From a3021999890c8024b05146ff21c1f35d53699440 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 28 Feb 2025 08:52:27 -0500 Subject: [PATCH 24/24] sort exports alphabetically --- src/SparseArraysBase.jl | 8 ++++---- test/test_exports.jl | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SparseArraysBase.jl b/src/SparseArraysBase.jl index ecbb04d..02b5970 100644 --- a/src/SparseArraysBase.jl +++ b/src/SparseArraysBase.jl @@ -9,12 +9,12 @@ export SparseArrayDOK, eachstoredindex, isstored, oneelement, + sparserand, + sparserand!, + sparsezeros, storedlength, storedpairs, - storedvalues, - sparsezeros, - sparserand, - sparserand! + storedvalues include("abstractsparsearrayinterface.jl") include("sparsearrayinterface.jl") diff --git a/test/test_exports.jl b/test/test_exports.jl index 575a8e1..952e478 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -12,12 +12,12 @@ using Test: @test, @testset :eachstoredindex, :isstored, :oneelement, + :sparserand, + :sparserand!, + :sparsezeros, :storedlength, :storedpairs, :storedvalues, - :sparsezeros, - :sparserand, - :sparserand!, ] @test issetequal(names(SparseArraysBase), exports) end