diff --git a/stdlib/Random/src/RNGs.jl b/stdlib/Random/src/RNGs.jl index 292ae00d33628..8606910911d50 100644 --- a/stdlib/Random/src/RNGs.jl +++ b/stdlib/Random/src/RNGs.jl @@ -333,7 +333,7 @@ typeof_rng(::_GLOBAL_RNG) = TaskLocalRNG """ default_rng() -> rng -Return the default global random number generator (RNG). +Return the default task-local random number generator (RNG). !!! note What the default RNG is is an implementation detail. Across different versions of @@ -346,8 +346,8 @@ Return the default global random number generator (RNG). @inline default_rng() = TaskLocalRNG() @inline default_rng(tid::Int) = TaskLocalRNG() -copy!(dst::Xoshiro, ::_GLOBAL_RNG) = copy!(dst, default_rng()) -copy!(::_GLOBAL_RNG, src::Xoshiro) = copy!(default_rng(), src) +copy!(dst::XoshiroSplit, ::_GLOBAL_RNG) = copy!(dst, default_rng()) +copy!(::_GLOBAL_RNG, src::XoshiroSplit) = copy!(default_rng(), src) copy(::_GLOBAL_RNG) = copy(default_rng()) GLOBAL_SEED = 0 diff --git a/stdlib/Random/src/Xoshiro.jl b/stdlib/Random/src/Xoshiro.jl index 3be276ad23754..196be286ed8b3 100644 --- a/stdlib/Random/src/Xoshiro.jl +++ b/stdlib/Random/src/Xoshiro.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license ## Xoshiro RNG -# Lots of implementation is shared with TaskLocalRNG +# Lots of implementation is shared with TaskLocalRNG and XoshiroSplit """ Xoshiro(seed) @@ -53,7 +53,13 @@ mutable struct Xoshiro <: AbstractRNG Xoshiro(seed=nothing) = seed!(new(), seed) end -function setstate!(x::Xoshiro, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) +# NON-PUBLIC +@inline function get_xoshiro_state(x::Xoshiro) + x.s0, x.s1, x.s2, x.s3 +end + +# NON-PUBLIC +@inline function set_xoshiro_state!(x::Xoshiro, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) x.s0 = s0 x.s1 = s1 x.s2 = s2 @@ -61,6 +67,11 @@ function setstate!(x::Xoshiro, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) x end +# NON-PUBLIC +function seedstate!(x::Xoshiro, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) + set_xoshiro_state!(x, s0, s1, s2, s3) +end + copy(rng::Xoshiro) = Xoshiro(rng.s0, rng.s1, rng.s2, rng.s3) function copy!(dst::Xoshiro, src::Xoshiro) @@ -72,21 +83,66 @@ function ==(a::Xoshiro, b::Xoshiro) a.s0 == b.s0 && a.s1 == b.s1 && a.s2 == b.s2 && a.s3 == b.s3 end -rng_native_52(::Xoshiro) = UInt64 -@inline function rand(rng::Xoshiro, ::SamplerType{UInt64}) - s0, s1, s2, s3 = rng.s0, rng.s1, rng.s2, rng.s3 - tmp = s0 + s3 - res = ((tmp << 23) | (tmp >> 41)) + s0 - t = s1 << 17 - s2 = xor(s2, s0) - s3 = xor(s3, s1) - s1 = xor(s1, s2) - s0 = xor(s0, s3) - s2 = xor(s2, t) - s3 = s3 << 45 | s3 >> 19 - rng.s0, rng.s1, rng.s2, rng.s3 = s0, s1, s2, s3 - res +""" + XoshiroSplit(seed) + XoshiroSplit() + +Creates the same stream as Xoshiro, but has an additional splitting ability. + +For more discussion, cf rng_split in task.c + +This is the type currently returned by `copy(default_rng())`. + +!!! note + What the default RNG is is an implementation detail. Across different versions of + Julia, you should not expect the default RNG to be always the same, nor that it will + return the same stream of random numbers for a given seed. +""" +mutable struct XoshiroSplit <: AbstractRNG + s0::UInt64 + s1::UInt64 + s2::UInt64 + s3::UInt64 + s4::UInt64 + + XoshiroSplit( + s0::Integer, s1::Integer, s2::Integer, s3::Integer, # xoshiro256 state + s4::Integer, # internal splitmix state + ) = new(s0, s1, s2, s3, s4) + XoshiroSplit(seed=nothing) = seed!(new(), seed) +end + +# NON-PUBLIC +@inline function get_xoshiro_state(x::XoshiroSplit) + x.s0, x.s1, x.s2, x.s3 +end + +# NON-PUBLIC +@inline function set_xoshiro_state!(x::XoshiroSplit, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) + x.s0 = s0 + x.s1 = s1 + x.s2 = s2 + x.s3 = s3 + x +end + +# NON-PUBLIC +function seedstate!(x::XoshiroSplit, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) + set_xoshiro_state!(x, s0, s1, s2, s3) + x.s4 = 1s0 + 3s1 + 5s2 + 7s3 + x +end + +copy(rng::XoshiroSplit) = XoshiroSplit(rng.s0, rng.s1, rng.s2, rng.s3, rng.s4) + +function copy!(dst::XoshiroSplit, src::XoshiroSplit) + dst.s0, dst.s1, dst.s2, dst.s3, dst.s4 = src.s0, src.s1, src.s2, src.s3, src.s4 + dst +end + +function ==(a::XoshiroSplit, b::XoshiroSplit) + a.s0 == b.s0 && a.s1 == b.s1 && a.s2 == b.s2 && a.s3 == b.s3 && a.s4 == b.s4 end @@ -111,25 +167,74 @@ is undefined behavior: it will work most of the time, and may sometimes fail sil """ struct TaskLocalRNG <: AbstractRNG end TaskLocalRNG(::Nothing) = TaskLocalRNG() -rng_native_52(::TaskLocalRNG) = UInt64 -function setstate!( - x::TaskLocalRNG, - s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64, # xoshiro256 state - s4::UInt64 = 1s0 + 3s1 + 5s2 + 7s3, # internal splitmix state -) +# NON-PUBLIC +@inline function get_xoshiro_state(x::TaskLocalRNG) + t = current_task() + t.rngState0, t.rngState1, t.rngState2, t.rngState3 +end + +# NON-PUBLIC +@inline function set_xoshiro_state!(x::TaskLocalRNG, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) t = current_task() t.rngState0 = s0 t.rngState1 = s1 t.rngState2 = s2 t.rngState3 = s3 - t.rngState4 = s4 x end -@inline function rand(::TaskLocalRNG, ::SamplerType{UInt64}) - task = current_task() - s0, s1, s2, s3 = task.rngState0, task.rngState1, task.rngState2, task.rngState3 +# NON-PUBLIC +function seedstate!(x::TaskLocalRNG, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) + t = current_task() + t.rngState0 = s0 + t.rngState1 = s1 + t.rngState2 = s2 + t.rngState3 = s3 + t.rngState4 = 1s0 + 3s1 + 5s2 + 7s3 + x +end + +function copy(rng::TaskLocalRNG) + t = current_task() + XoshiroSplit(t.rngState0, t.rngState1, t.rngState2, t.rngState3, t.rngState4) +end + +function copy!(dst::TaskLocalRNG, src::XoshiroSplit) + t = current_task() + t.rngState0 = src.s0 + t.rngState1 = src.s1 + t.rngState2 = src.s2 + t.rngState3 = src.s3 + t.rngState4 = src.s4 + return dst +end + +function copy!(dst::XoshiroSplit, src::TaskLocalRNG) + t = current_task() + dst.s0 = t.rngState0 + dst.s1 = t.rngState1 + dst.s2 = t.rngState2 + dst.s3 = t.rngState3 + dst.s4 = t.rngState4 + return dst +end + +function ==(a::XoshiroSplit, b::TaskLocalRNG) + t = current_task() + a.s0 == t.rngState0 && a.s1 == t.rngState1 && a.s2 == t.rngState2 && a.s3 == t.rngState3 && a.s4 == t.rngState4 +end + +==(a::TaskLocalRNG, b::XoshiroSplit) = b == a + +# Shared implementation between Xoshiro, XoshiroSplit, and TaskLocalRNG + +const XoshiroLike = Union{TaskLocalRNG, Xoshiro, XoshiroSplit} + +rng_native_52(::XoshiroLike) = UInt64 + +@inline function rand(rng::XoshiroLike, ::SamplerType{UInt64}) + s0, s1, s2, s3 = get_xoshiro_state(rng) tmp = s0 + s3 res = ((tmp << 23) | (tmp >> 41)) + s0 t = s1 << 17 @@ -139,78 +244,54 @@ end s0 ⊻= s3 s2 ⊻= t s3 = s3 << 45 | s3 >> 19 - task.rngState0, task.rngState1, task.rngState2, task.rngState3 = s0, s1, s2, s3 + set_xoshiro_state!(rng, s0, s1, s2, s3) res end -# Shared implementation between Xoshiro and TaskLocalRNG -- seeding - -function seed!(rng::Union{TaskLocalRNG,Xoshiro}) +function seed!(rng::XoshiroLike) # as we get good randomness from RandomDevice, we can skip hashing rd = RandomDevice() - setstate!(rng, rand(rd, UInt64), rand(rd, UInt64), rand(rd, UInt64), rand(rd, UInt64)) + seedstate!(rng, rand(rd, UInt64), rand(rd, UInt64), rand(rd, UInt64), rand(rd, UInt64)) end -function seed!(rng::Union{TaskLocalRNG,Xoshiro}, seed::Union{Vector{UInt32}, Vector{UInt64}}) +function seed!(rng::XoshiroLike, seed::Union{Vector{UInt32}, Vector{UInt64}}) c = SHA.SHA2_256_CTX() SHA.update!(c, reinterpret(UInt8, seed)) s0, s1, s2, s3 = reinterpret(UInt64, SHA.digest!(c)) - setstate!(rng, s0, s1, s2, s3) + seedstate!(rng, s0, s1, s2, s3) end -seed!(rng::Union{TaskLocalRNG, Xoshiro}, seed::Integer) = seed!(rng, make_seed(seed)) +seed!(rng::XoshiroLike, seed::Integer) = seed!(rng, make_seed(seed)) -@inline function rand(rng::Union{TaskLocalRNG, Xoshiro}, ::SamplerType{UInt128}) +@inline function rand(rng::XoshiroLike, ::SamplerType{UInt128}) first = rand(rng, UInt64) second = rand(rng,UInt64) second + UInt128(first) << 64 end -@inline rand(rng::Union{TaskLocalRNG, Xoshiro}, ::SamplerType{Int128}) = rand(rng, UInt128) % Int128 +@inline rand(rng::XoshiroLike, ::SamplerType{Int128}) = rand(rng, UInt128) % Int128 -@inline function rand(rng::Union{TaskLocalRNG, Xoshiro}, +@inline function rand(rng::XoshiroLike, T::SamplerUnion(Bool, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64)) S = T[] # use upper bits (rand(rng, UInt64) >>> (64 - 8*sizeof(S))) % S end -function copy(rng::TaskLocalRNG) - t = current_task() - Xoshiro(t.rngState0, t.rngState1, t.rngState2, t.rngState3) -end - -function copy!(dst::TaskLocalRNG, src::Xoshiro) - t = current_task() - setstate!(dst, src.s0, src.s1, src.s2, src.s3) - return dst -end - -function copy!(dst::Xoshiro, src::TaskLocalRNG) - t = current_task() - setstate!(dst, t.rngState0, t.rngState1, t.rngState2, t.rngState3) - return dst -end - -function ==(a::Xoshiro, b::TaskLocalRNG) - t = current_task() - a.s0 == t.rngState0 && a.s1 == t.rngState1 && a.s2 == t.rngState2 && a.s3 == t.rngState3 -end -==(a::TaskLocalRNG, b::Xoshiro) = b == a # for partial words, use upper bits from Xoshiro -rand(r::Union{TaskLocalRNG, Xoshiro}, ::SamplerTrivial{UInt52Raw{UInt64}}) = rand(r, UInt64) >>> 12 -rand(r::Union{TaskLocalRNG, Xoshiro}, ::SamplerTrivial{UInt52{UInt64}}) = rand(r, UInt64) >>> 12 -rand(r::Union{TaskLocalRNG, Xoshiro}, ::SamplerTrivial{UInt104{UInt128}}) = rand(r, UInt104Raw()) +rand(r::XoshiroLike, ::SamplerTrivial{UInt52Raw{UInt64}}) = rand(r, UInt64) >>> 12 +rand(r::XoshiroLike, ::SamplerTrivial{UInt52{UInt64}}) = rand(r, UInt64) >>> 12 +rand(r::XoshiroLike, ::SamplerTrivial{UInt104{UInt128}}) = rand(r, UInt104Raw()) -rand(r::Union{TaskLocalRNG, Xoshiro}, ::SamplerTrivial{CloseOpen01{Float16}}) = +rand(r::XoshiroLike, ::SamplerTrivial{CloseOpen01{Float16}}) = Float16(Float32(rand(r, UInt16) >>> 5) * Float32(0x1.0p-11)) -rand(r::Union{TaskLocalRNG, Xoshiro}, ::SamplerTrivial{CloseOpen01{Float32}}) = +rand(r::XoshiroLike, ::SamplerTrivial{CloseOpen01{Float32}}) = Float32(rand(r, UInt32) >>> 8) * Float32(0x1.0p-24) -rand(r::Union{TaskLocalRNG, Xoshiro}, ::SamplerTrivial{CloseOpen01_64}) = +rand(r::XoshiroLike, ::SamplerTrivial{CloseOpen01_64}) = Float64(rand(r, UInt64) >>> 11) * 0x1.0p-53 diff --git a/stdlib/Random/src/XoshiroSimd.jl b/stdlib/Random/src/XoshiroSimd.jl index 1a16baa4bce28..47b21afd5c18f 100644 --- a/stdlib/Random/src/XoshiroSimd.jl +++ b/stdlib/Random/src/XoshiroSimd.jl @@ -2,7 +2,7 @@ module XoshiroSimd # Getting the xoroshiro RNG to reliably vectorize is somewhat of a hassle without Simd.jl. -import ..Random: TaskLocalRNG, rand, rand!, Xoshiro, CloseOpen01, UnsafeView, +import ..Random: TaskLocalRNG, rand, rand!, XoshiroLike, CloseOpen01, UnsafeView, SamplerType, SamplerTrivial using Base: BitInteger_types using Base.Libc: memcpy @@ -122,12 +122,21 @@ for N in [4,8,16] end -function forkRand(rng::Union{TaskLocalRNG, Xoshiro}, ::Val{N}) where N - # constants have nothing up their sleeve. For more discussion, cf rng_split in task.c - # 0x02011ce34bce797f == hash(UInt(1))|0x01 - # 0x5a94851fb48a6e05 == hash(UInt(2))|0x01 - # 0x3688cf5d48899fa7 == hash(UInt(3))|0x01 - # 0x867b4bb4c42e5661 == hash(UInt(4))|0x01 +function forkRand(rng::XoshiroLike, ::Val{N}) where N + #= TODO: consider a less ad-hoc construction + Ideally we could just use the output of the random stream to seed the initial + state of the child. Out of an overabundance of caution we multiply with + effectively random coefficients, to break possible self-interactions. + It is not the goal to mix bits -- we work under the assumption that the + source is well-seeded, and its output looks effectively random. + However, xoshiro has never been studied in the mode where we seed the + initial state with the output of another xoshiro instance. + Constants have nothing up their sleeve: + 0x02011ce34bce797f == hash(UInt(1))|0x01 + 0x5a94851fb48a6e05 == hash(UInt(2))|0x01 + 0x3688cf5d48899fa7 == hash(UInt(3))|0x01 + 0x867b4bb4c42e5661 == hash(UInt(4))|0x01 + =# s0 = ntuple(i->VecElement(0x02011ce34bce797f * rand(rng, UInt64)), Val(N)) s1 = ntuple(i->VecElement(0x5a94851fb48a6e05 * rand(rng, UInt64)), Val(N)) s2 = ntuple(i->VecElement(0x3688cf5d48899fa7 * rand(rng, UInt64)), Val(N)) @@ -137,7 +146,7 @@ end _id(x, T) = x -@inline function xoshiro_bulk(rng::Union{TaskLocalRNG, Xoshiro}, dst::Ptr{UInt8}, len::Int, T::Union{Type{UInt8}, Type{Bool}, Type{Float32}, Type{Float64}}, ::Val{N}, f::F = _id) where {N, F} +@inline function xoshiro_bulk(rng::XoshiroLike, dst::Ptr{UInt8}, len::Int, T::Union{Type{UInt8}, Type{Bool}, Type{Float32}, Type{Float64}}, ::Val{N}, f::F = _id) where {N, F} if len >= simdThreshold(T) written = xoshiro_bulk_simd(rng, dst, len, T, Val(N), f) len -= written @@ -149,12 +158,12 @@ _id(x, T) = x nothing end -@noinline function xoshiro_bulk_nosimd(rng::Union{TaskLocalRNG, Xoshiro}, dst::Ptr{UInt8}, len::Int, ::Type{T}, f::F) where {T, F} +@noinline function xoshiro_bulk_nosimd(rng::XoshiroLike, dst::Ptr{UInt8}, len::Int, ::Type{T}, f::F) where {T, F} if rng isa TaskLocalRNG task = current_task() s0, s1, s2, s3 = task.rngState0, task.rngState1, task.rngState2, task.rngState3 else - (; s0, s1, s2, s3) = rng::Xoshiro + (; s0, s1, s2, s3) = rng end i = 0 @@ -191,12 +200,12 @@ end nothing end -@noinline function xoshiro_bulk_nosimd(rng::Union{TaskLocalRNG, Xoshiro}, dst::Ptr{UInt8}, len::Int, ::Type{Bool}, f) +@noinline function xoshiro_bulk_nosimd(rng::XoshiroLike, dst::Ptr{UInt8}, len::Int, ::Type{Bool}, f) if rng isa TaskLocalRNG task = current_task() s0, s1, s2, s3 = task.rngState0, task.rngState1, task.rngState2, task.rngState3 else - (; s0, s1, s2, s3) = rng::Xoshiro + (; s0, s1, s2, s3) = rng end i = 0 @@ -241,7 +250,7 @@ end end -@noinline function xoshiro_bulk_simd(rng::Union{TaskLocalRNG, Xoshiro}, dst::Ptr{UInt8}, len::Int, ::Type{T}, ::Val{N}, f::F) where {T,N,F} +@noinline function xoshiro_bulk_simd(rng::XoshiroLike, dst::Ptr{UInt8}, len::Int, ::Type{T}, ::Val{N}, f::F) where {T,N,F} s0, s1, s2, s3 = forkRand(rng, Val(N)) i = 0 @@ -260,7 +269,7 @@ end return i end -@noinline function xoshiro_bulk_simd(rng::Union{TaskLocalRNG, Xoshiro}, dst::Ptr{UInt8}, len::Int, ::Type{Bool}, ::Val{N}, f) where {N} +@noinline function xoshiro_bulk_simd(rng::XoshiroLike, dst::Ptr{UInt8}, len::Int, ::Type{Bool}, ::Val{N}, f) where {N} s0, s1, s2, s3 = forkRand(rng, Val(N)) msk = ntuple(i->VecElement(0x0101010101010101), Val(N)) i = 0 @@ -284,24 +293,24 @@ end end -function rand!(rng::Union{TaskLocalRNG, Xoshiro}, dst::Array{Float32}, ::SamplerTrivial{CloseOpen01{Float32}}) +function rand!(rng::XoshiroLike, dst::Array{Float32}, ::SamplerTrivial{CloseOpen01{Float32}}) GC.@preserve dst xoshiro_bulk(rng, convert(Ptr{UInt8}, pointer(dst)), length(dst)*4, Float32, xoshiroWidth(), _bits2float) dst end -function rand!(rng::Union{TaskLocalRNG, Xoshiro}, dst::Array{Float64}, ::SamplerTrivial{CloseOpen01{Float64}}) +function rand!(rng::XoshiroLike, dst::Array{Float64}, ::SamplerTrivial{CloseOpen01{Float64}}) GC.@preserve dst xoshiro_bulk(rng, convert(Ptr{UInt8}, pointer(dst)), length(dst)*8, Float64, xoshiroWidth(), _bits2float) dst end for T in BitInteger_types - @eval function rand!(rng::Union{TaskLocalRNG, Xoshiro}, dst::Union{Array{$T}, UnsafeView{$T}}, ::SamplerType{$T}) + @eval function rand!(rng::XoshiroLike, dst::Union{Array{$T}, UnsafeView{$T}}, ::SamplerType{$T}) GC.@preserve dst xoshiro_bulk(rng, convert(Ptr{UInt8}, pointer(dst)), length(dst)*sizeof($T), UInt8, xoshiroWidth()) dst end end -function rand!(rng::Union{TaskLocalRNG, Xoshiro}, dst::Array{Bool}, ::SamplerType{Bool}) +function rand!(rng::XoshiroLike, dst::Array{Bool}, ::SamplerType{Bool}) GC.@preserve dst xoshiro_bulk(rng, convert(Ptr{UInt8}, pointer(dst)), length(dst), Bool, xoshiroWidth()) dst end diff --git a/stdlib/Random/src/normal.jl b/stdlib/Random/src/normal.jl index c2738653a0438..747011268c16a 100644 --- a/stdlib/Random/src/normal.jl +++ b/stdlib/Random/src/normal.jl @@ -228,7 +228,7 @@ for randfun in [:randn, :randexp] end # optimization for Xoshiro, which randomizes natively Array{UInt64} - function $randfun!(rng::Union{Xoshiro, TaskLocalRNG}, A::Array{Float64}) + function $randfun!(rng::XoshiroLike, A::Array{Float64}) if length(A) < 7 for i in eachindex(A) @inbounds A[i] = $randfun(rng, Float64) diff --git a/stdlib/Random/test/runtests.jl b/stdlib/Random/test/runtests.jl index 3f570d862b743..e1a0bfad581aa 100644 --- a/stdlib/Random/test/runtests.jl +++ b/stdlib/Random/test/runtests.jl @@ -330,7 +330,7 @@ for f in (:<, :<=, :>, :>=, :(==), :(!=)) end # test all rand APIs -for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) +for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()], [Random.XoshiroSplit()]) ftypes = [Float16, Float32, Float64, FakeFloat64, BigFloat] cftypes = [ComplexF16, ComplexF32, ComplexF64, ftypes...] types = [Bool, Char, BigFloat, Base.BitInteger_types..., ftypes...] @@ -452,7 +452,7 @@ function hist(X, n) end # test uniform distribution of floats -for rng in [MersenneTwister(), RandomDevice(), Xoshiro()], +for rng in [MersenneTwister(), RandomDevice(), Xoshiro(), Random.XoshiroSplit()], T in [Float16, Float32, Float64, BigFloat], prec in (T == BigFloat ? [3, 53, 64, 100, 256, 1000] : [256]) setprecision(BigFloat, prec) do @@ -474,7 +474,7 @@ end # but also for 3 linear combinations of positions (for the array version) lcs = unique!.([rand(1:n, 2), rand(1:n, 3), rand(1:n, 5)]) aslcs = zeros(Int, 3) - for rng = (MersenneTwister(), RandomDevice(), Xoshiro()) + for rng = (MersenneTwister(), RandomDevice(), Xoshiro(), Random.XoshiroSplit()) for scalar = [false, true] fill!(a, 0) fill!(as, 0) @@ -499,7 +499,7 @@ end end end -@testset "reproducility of methods for $RNG" for RNG=(MersenneTwister,Xoshiro) +@testset "reproducility of methods for $RNG" for RNG=(MersenneTwister,Xoshiro,Random.XoshiroSplit) mta, mtb = RNG(42), RNG(42) @test rand(mta) == rand(mtb) @@ -685,7 +685,7 @@ end # this shouldn't crash (#22403) @test_throws MethodError rand!(Union{UInt,Int}[1, 2, 3]) -@testset "$RNG() & Random.seed!(rng::$RNG) initializes randomly" for RNG in (MersenneTwister, RandomDevice, Xoshiro) +@testset "$RNG() & Random.seed!(rng::$RNG) initializes randomly" for RNG in (MersenneTwister, RandomDevice, Xoshiro, Random.XoshiroSplit) m = RNG() a = rand(m, Int) m = RNG() @@ -706,9 +706,9 @@ end @test rand(m, Int) ∉ (a, b, c, d) end -@testset "$RNG(seed) & Random.seed!(m::$RNG, seed) produce the same stream" for RNG=(MersenneTwister,Xoshiro) +@testset "$RNG(seed) & Random.seed!(m::$RNG, seed) produce the same stream" for RNG=(MersenneTwister,Xoshiro,Random.XoshiroSplit) seeds = Any[0, 1, 2, 10000, 10001, rand(UInt32, 8), rand(UInt128, 3)...] - if RNG == Xoshiro + if RNG == Xoshiro || RNG == Random.XoshiroSplit push!(seeds, rand(UInt64, rand(1:4))) end for seed=seeds @@ -759,7 +759,7 @@ struct RandomStruct23964 end @test_throws MethodError rand(RandomStruct23964()) end -@testset "rand(::$(typeof(RNG)), ::UnitRange{$T}" for RNG ∈ (MersenneTwister(rand(UInt128)), RandomDevice(), Xoshiro()), +@testset "rand(::$(typeof(RNG)), ::UnitRange{$T}" for RNG ∈ (MersenneTwister(rand(UInt128)), RandomDevice(), Xoshiro(), Random.XoshiroSplit()), T ∈ (Int8, Int16, Int32, UInt32, Int64, Int128, UInt128) for S in (SamplerRangeInt, SamplerRangeFast, SamplerRangeNDL) S == SamplerRangeNDL && sizeof(T) > 8 && continue @@ -813,7 +813,7 @@ end @test Random.seed!(GLOBAL_RNG, 0) === LOCAL_RNG @test Random.seed!(GLOBAL_RNG) === LOCAL_RNG - xo = Xoshiro() + xo = Random.XoshiroSplit() @test copy!(xo, GLOBAL_RNG) === xo @test xo == LOCAL_RNG Random.seed!(xo, 2) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 25418f1f67ec4..dc23465ed8124 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1419,12 +1419,12 @@ method, which by default will return a list of the testset objects used in each iteration. Before the execution of the body of a `@testset`, there is an implicit -call to `Random.seed!(seed)` where `seed` is the current seed of the global RNG. -Moreover, after the execution of the body, the state of the global RNG is -restored to what it was before the `@testset`. This is meant to ease +call to `Random.seed!(seed)` where `seed` is the current global RNG seed. +Moreover, after the execution of the body, the state of the task-local RNG and +the global RNG seed are +restored to what they were before the `@testset`. This is meant to ease reproducibility in case of failure, and to allow seamless -re-arrangements of `@testset`s regardless of their side-effect on the -global RNG state. +re-arrangements of `@testset`s regardless of their RNG side-effects. ## Examples ```jldoctest; filter = r"trigonometric identities | 4 4 [0-9\\.]+s" @@ -2163,7 +2163,7 @@ Base.setindex!(a::GenericArray, x, i::Int) = a.a[i] = x Base.similar(A::GenericArray, s::Integer...) = GenericArray(similar(A.a, s...)) "`guardseed(f)` runs the function `f()` and then restores the -state of the global RNG as it was before." +state of the task-local RNG as it was before." function guardseed(f::Function, r::AbstractRNG=default_rng()) old = copy(r) try @@ -2173,10 +2173,11 @@ function guardseed(f::Function, r::AbstractRNG=default_rng()) end end -"`guardseed(f, seed)` is equivalent to running `Random.seed!(seed); f()` and -then restoring the state of the global RNG as it was before." +"`guardseed(f, seed)` is equivalent to running +`Random.seed!(Random.default_rng(), seed); f()` and +then restoring the state of the task-local RNG as it was before." guardseed(f::Function, seed::Union{Vector{UInt64},Vector{UInt32},Integer,NTuple{4,UInt64}}) = guardseed() do - Random.seed!(seed) + Random.seed!(Random.default_rng(), seed) f() end