Skip to content

Commit

Permalink
Random: allow string seeds
Browse files Browse the repository at this point in the history
We used to be able to seed RNGs with a string, but that string was interpreted
as the filename containing the actual seed. This was deprecated in #21359, in
order to later allow using a string seed directly, which this patch does.
  • Loading branch information
rfourquet committed Sep 30, 2023
1 parent d988f8f commit a0394f0
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 5 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Standard library changes

#### Random
* `rand` now supports sampling over `Tuple` types ([#50251]).
* Most random number generators from `Random` can now be seeded by a string, e.g.
`seed!(rng, "a random seed")` ([#51527]).

* When seeding RNGs provided by `Random`, negative integer seeds can now be used ([#51416]).

Expand Down
14 changes: 12 additions & 2 deletions stdlib/Random/src/RNGs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ MersenneTwister(seed, state::DSFMT_state) =
Create a `MersenneTwister` RNG object. Different RNG objects can have
their own seeds, which may be useful for generating different streams
of random numbers.
The `seed` may be an integer or a vector of `UInt32` integers.
The `seed` may be an integer, a string, or a vector of `UInt32` integers.
If no seed is provided, a randomly generated one is created (using entropy from the system).
See the [`seed!`](@ref) function for reseeding an already existing `MersenneTwister` object.
Expand Down Expand Up @@ -316,12 +316,22 @@ function hash_seed(seed::Union{AbstractArray{UInt32}, AbstractArray{UInt64}})
SHA.digest!(ctx)
end

function hash_seed(str::AbstractString)
ctx = SHA.SHA2_256_CTX()
for chr in str
SHA.update!(ctx, reinterpret(NTuple{4, UInt8}, UInt32(chr)))
end
SHA.update!(ctx, (0x05,))
SHA.digest!(ctx)
end


"""
hash_seed(seed) -> AbstractVector{UInt8}
Return a cryptographic hash of `seed` of size 256 bits (32 bytes).
`seed` can currently be of type `Union{Integer, DenseArray{UInt32}, DenseArray{UInt64}}`,
`seed` can currently be of type
`Union{Integer, AbstractString, AbstractArray{UInt32}, AbstractArray{UInt64}}`,
but modules can extend this function for types they own.
`hash_seed` is "injective" : if `n != m`, then `hash_seed(n) != `hash_seed(m)`.
Expand Down
2 changes: 1 addition & 1 deletion stdlib/Random/src/Xoshiro.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Lots of implementation is shared with TaskLocalRNG

"""
Xoshiro(seed::Integer)
Xoshiro(seed::Union{Integer, AbstractString})
Xoshiro()
Xoshiro256++ is a fast pseudorandom number generator described by David Blackman and
Expand Down
22 changes: 20 additions & 2 deletions stdlib/Random/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ end
# test that the following is not an error (#16925)
@test Random.seed!(m..., typemax(UInt)) === m2
@test Random.seed!(m..., typemax(UInt128)) === m2
@test Random.seed!(m..., "a random seed") === m2
end
end

Expand Down Expand Up @@ -701,7 +702,7 @@ end
end

@testset "$RNG(seed) & Random.seed!(m::$RNG, seed) produce the same stream" for RNG=(MersenneTwister,Xoshiro)
seeds = Any[0, 1, 2, 10000, 10001, rand(UInt32, 8), rand(UInt128, 3)...]
seeds = Any[0, 1, 2, 10000, 10001, rand(UInt32, 8), randstring(), randstring(), rand(UInt128, 3)...]
if RNG == Xoshiro
push!(seeds, rand(UInt64, rand(1:4)))
end
Expand All @@ -714,7 +715,7 @@ end
end

@testset "Random.seed!(seed) sets Random.GLOBAL_SEED" begin
seeds = Any[0, rand(UInt128), rand(UInt64, 4)]
seeds = Any[0, rand(UInt128), rand(UInt64, 4), randstring(20)]

for seed=seeds
Random.seed!(seed)
Expand Down Expand Up @@ -923,6 +924,12 @@ end
@test string(m) == "MersenneTwister(-3)"
Random.seed!(m, typemin(Int8))
@test string(m) == "MersenneTwister(-128)"

# string seeds
Random.seed!(m, "seed 1")
@test string(m) == "MersenneTwister(\"seed 1\")"
x = rand(m)
@test x == rand(MersenneTwister("seed 1"))
end

@testset "RandomDevice" begin
Expand Down Expand Up @@ -1179,4 +1186,15 @@ end
hash32 = Random.hash_seed(seed32)
@test Random.hash_seed(map(UInt64, seed32)) == hash32
@test hash32 keys(vseeds)

seed_str = randstring()
seed_gstr = GenericString(seed_str)
@test Random.hash_seed(seed_str) == Random.hash_seed(seed_gstr)
string_seeds = Set{Vector{UInt8}}()
for ch = 'A':'z'
vseed = Random.hash_seed(string(ch))
@test vseed keys(vseeds)
@test vseed string_seeds
push!(string_seeds, vseed)
end
end

0 comments on commit a0394f0

Please sign in to comment.