From ceae4eb4572e48b82ce526c867c147dcce52ada6 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Mon, 26 Oct 2026 13:09:29 +0000 Subject: [PATCH 1/3] Pass the element type `T` of `sprand(m, n, p, rfn, T)` on to the RNG method Without an RNG, `sprand` with a value function dropped `T`, so the result took the eltype of `rfn` and `rfn` was called once more to recompute the default. Co-Authored-By: Claude Opus 5.5 --- src/constructors.jl | 2 +- test/constructors.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/constructors.jl b/src/constructors.jl index fbfc69e5..c4b9c622 100644 --- a/src/constructors.jl +++ b/src/constructors.jl @@ -720,7 +720,7 @@ function sprand(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, end sprand(m::Integer, n::Integer, density::AbstractFloat, rfn::Function, ::Type{T} = eltype(rfn(1))) where {T} = - sprand(default_rng(), m, n, density, (r, i) -> rfn(i)) + sprand(default_rng(), m, n, density, (r, i) -> rfn(i), T) truebools(r::AbstractRNG, n::Integer) = fill(true, n) diff --git a/test/constructors.jl b/test/constructors.jl index 1cc66d3d..3293a597 100644 --- a/test/constructors.jl +++ b/test/constructors.jl @@ -290,6 +290,7 @@ end @static if COMPREHENSIVE m = sprand(Float32, 10, 10, 0.1) @test eltype(m) == Float32 + @test eltype(sprand(10, 10, 0.5, k -> rand(Int8, k), Float64)) == Float64 end m = sprand(Float64, 10, 10, 0.1) @test eltype(m) == Float64 From 41c35d7aa1b716da3fb5cc2d935177061dba94bc Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Mon, 26 Oct 2026 13:48:29 +0000 Subject: [PATCH 2/3] Accept the documented rfn forms with T and drop the throwaway rfn call in sprand Co-Authored-By: Claude Opus 5.5 --- src/constructors.jl | 17 ++++++++++++++--- src/sparsevector.jl | 11 ++++++++--- test/sparsevector.jl | 8 ++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/constructors.jl b/src/constructors.jl index c4b9c622..d233814b 100644 --- a/src/constructors.jl +++ b/src/constructors.jl @@ -697,6 +697,7 @@ You can sample nonzero values from a different distribution by passing a custom `rfn` function instead of `rand`. This should be a function `rfn(k)` that returns an array of `k` random numbers sampled from the desired distribution; alternatively, if `rng` is supplied, it should instead be a function `rfn(rng, k)`. +Passing `T` after `rfn` converts the values that `rfn` returns to `T`. # Examples ```jldoctest; setup = :(using Random; Random.seed!(1234)) @@ -711,15 +712,25 @@ julia> sprand(Float64, 3, 0.75) [2] = 0.49425 ``` """ -function sprand(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, rfn::Function, ::Type{T}=eltype(rfn(r, 1))) where T +function sprand(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, rfn::Function, ::Type{T}) where T + I, V = _sprandvalues(r, m, n, density, rfn) + return sparse_sortedlinearindices!(I, convert(Vector{T}, V), m, n) +end +function sprand(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, rfn::Function) + I, V = _sprandvalues(r, m, n, density, rfn) + return sparse_sortedlinearindices!(I, convert(Vector{eltype(V)}, V), m, n) +end +function _sprandvalues(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, rfn::F) where {F} m, n = Int(m), Int(n) (m < 0 || n < 0) && throw(ArgumentError("invalid Array dimensions")) 0 <= density <= 1 || throw(ArgumentError("$density not in [0,1]")) I = randsubseq(r, 1:(m*n), density) - return sparse_sortedlinearindices!(I, convert(Vector{T}, rfn(r,length(I))), m, n) + return I, rfn(r, length(I)) end -sprand(m::Integer, n::Integer, density::AbstractFloat, rfn::Function, ::Type{T} = eltype(rfn(1))) where {T} = +sprand(m::Integer, n::Integer, density::AbstractFloat, rfn::Function) = + sprand(default_rng(), m, n, density, (r, i) -> rfn(i)) +sprand(m::Integer, n::Integer, density::AbstractFloat, rfn::Function, ::Type{T}) where {T} = sprand(default_rng(), m, n, density, (r, i) -> rfn(i), T) truebools(r::AbstractRNG, n::Integer) = fill(true, n) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index 5d8f997a..893b7f0f 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -768,11 +768,16 @@ end ### Rand Construction -sprand(n::Integer, p::AbstractFloat, rfn::Function, ::Type{T}) where {T} = sprand(default_rng(), n, p, rfn, T) +# `rfn(rng, T, k)` predates the documented `rfn(k)` and `rfn(rng, k)` and is still accepted +function sprand(n::Integer, p::AbstractFloat, rfn::Function, ::Type{T}) where {T} + r = default_rng() + applicable(rfn, r, T, 0) && return sprand(r, n, p, rfn, T) + return sprand(r, n, p, (r, i) -> rfn(i), T) +end function sprand(r::AbstractRNG, n::Integer, p::AbstractFloat, rfn::Function, ::Type{T}) where T I = randsubseq(r, 1:convert(Int, n), p) - V = rfn(r, T, length(I)) - SparseVector(n, I, V) + V = applicable(rfn, r, T, length(I)) ? rfn(r, T, length(I)) : rfn(r, length(I)) + SparseVector(n, I, convert(Vector{T}, V)) end sprand(n::Integer, p::AbstractFloat, rfn::Function) = sprand(default_rng(), n, p, (r, i) -> rfn(i)) diff --git a/test/sparsevector.jl b/test/sparsevector.jl index 303195a4..8628d1a9 100644 --- a/test/sparsevector.jl +++ b/test/sparsevector.jl @@ -242,6 +242,14 @@ end @test xv == xr @test sprand(20, 1, 0.5, rfn1) isa SparseMatrixCSC{Int8,Int} @test sprand(MersenneTwister(5), 20, 1, 0.5, rfn2) isa SparseMatrixCSC{Int8,Int} + # and with `T`, which the older `rfn(rng, T, k)` still accepts + @test sprand(20, 0.5, rfn1, Float64) isa SparseVector{Float64,Int} + @test sprand(MersenneTwister(5), 20, 0.5, rfn2, Float64) isa SparseVector{Float64,Int} + @test sprand(MersenneTwister(5), 20, 0.5, (r, T, k) -> rand(r, T, k), Float64) isa SparseVector{Float64,Int} + # without `T`, `rfn` is called once, for the values + calls = Ref(0) + sprand(MersenneTwister(5), 4, 4, 0.5, (r, k) -> (calls[] += 1; rand(r, Int8, k))) + @test calls[] == 1 end end end From 2f742f64fcc28185fc1c6eaf6e43e22e3929b78a Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Mon, 26 Oct 2026 14:45:17 +0000 Subject: [PATCH 3/3] Convert the dimensions of sprand to Int before building the matrix Co-Authored-By: Claude Opus 5.5 --- src/constructors.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/constructors.jl b/src/constructors.jl index d233814b..1625213a 100644 --- a/src/constructors.jl +++ b/src/constructors.jl @@ -713,15 +713,16 @@ julia> sprand(Float64, 3, 0.75) ``` """ function sprand(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, rfn::Function, ::Type{T}) where T + m, n = Int(m), Int(n) I, V = _sprandvalues(r, m, n, density, rfn) return sparse_sortedlinearindices!(I, convert(Vector{T}, V), m, n) end function sprand(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, rfn::Function) + m, n = Int(m), Int(n) I, V = _sprandvalues(r, m, n, density, rfn) return sparse_sortedlinearindices!(I, convert(Vector{eltype(V)}, V), m, n) end -function _sprandvalues(r::AbstractRNG, m::Integer, n::Integer, density::AbstractFloat, rfn::F) where {F} - m, n = Int(m), Int(n) +function _sprandvalues(r::AbstractRNG, m::Int, n::Int, density::AbstractFloat, rfn::F) where {F} (m < 0 || n < 0) && throw(ArgumentError("invalid Array dimensions")) 0 <= density <= 1 || throw(ArgumentError("$density not in [0,1]")) I = randsubseq(r, 1:(m*n), density)