@@ -12,7 +12,7 @@ The entropy is obtained from the operating system.
12
12
"""
13
13
struct RandomDevice <: AbstractRNG ; end
14
14
RandomDevice (seed:: Nothing ) = RandomDevice ()
15
- seed! (rng:: RandomDevice ) = rng
15
+ seed! (rng:: RandomDevice , :: Nothing ) = rng
16
16
17
17
rand (rd:: RandomDevice , sp:: SamplerBoolBitInteger ) = Libc. getrandom! (Ref {sp[]} ())[]
18
18
rand (rd:: RandomDevice , :: SamplerType{Bool} ) = rand (rd, UInt8) % Bool
@@ -44,7 +44,7 @@ const MT_CACHE_I = 501 << 4 # number of bytes in the UInt128 cache
44
44
@assert dsfmt_get_min_array_size () <= MT_CACHE_F
45
45
46
46
mutable struct MersenneTwister <: AbstractRNG
47
- seed:: Vector{UInt32}
47
+ seed:: Any
48
48
state:: DSFMT_state
49
49
vals:: Vector{Float64}
50
50
ints:: Vector{UInt128}
@@ -70,7 +70,7 @@ mutable struct MersenneTwister <: AbstractRNG
70
70
end
71
71
end
72
72
73
- MersenneTwister (seed:: Vector{UInt32} , state:: DSFMT_state ) =
73
+ MersenneTwister (seed, state:: DSFMT_state ) =
74
74
MersenneTwister (seed, state,
75
75
Vector {Float64} (undef, MT_CACHE_F),
76
76
Vector {UInt128} (undef, MT_CACHE_I >> 4 ),
@@ -92,19 +92,17 @@ See the [`seed!`](@ref) function for reseeding an already existing `MersenneTwis
92
92
93
93
# Examples
94
94
```jldoctest
95
- julia> rng = MersenneTwister(1234 );
95
+ julia> rng = MersenneTwister(123 );
96
96
97
97
julia> x1 = rand(rng, 2)
98
98
2-element Vector{Float64}:
99
- 0.5908446386657102
100
- 0.7667970365022592
99
+ 0.37453777969575874
100
+ 0.8735343642013971
101
101
102
- julia> rng = MersenneTwister(1234);
103
-
104
- julia> x2 = rand(rng, 2)
102
+ julia> x2 = rand(MersenneTwister(123), 2)
105
103
2-element Vector{Float64}:
106
- 0.5908446386657102
107
- 0.7667970365022592
104
+ 0.37453777969575874
105
+ 0.8735343642013971
108
106
109
107
julia> x1 == x2
110
108
true
@@ -115,7 +113,7 @@ MersenneTwister(seed=nothing) =
115
113
116
114
117
115
function copy! (dst:: MersenneTwister , src:: MersenneTwister )
118
- copyto! ( resize! ( dst. seed, length (src . seed)), src. seed)
116
+ dst. seed = src. seed
119
117
copy! (dst. state, src. state)
120
118
copyto! (dst. vals, src. vals)
121
119
copyto! (dst. ints, src. ints)
@@ -129,7 +127,7 @@ function copy!(dst::MersenneTwister, src::MersenneTwister)
129
127
end
130
128
131
129
copy (src:: MersenneTwister ) =
132
- MersenneTwister (copy ( src. seed) , copy (src. state), copy (src. vals), copy (src. ints),
130
+ MersenneTwister (src. seed, copy (src. state), copy (src. vals), copy (src. ints),
133
131
src. idxF, src. idxI, src. adv, src. adv_jump, src. adv_vals, src. adv_ints)
134
132
135
133
@@ -144,12 +142,10 @@ hash(r::MersenneTwister, h::UInt) =
144
142
145
143
function show (io:: IO , rng:: MersenneTwister )
146
144
# seed
147
- seed = from_seed (rng. seed)
148
- seed_str = seed <= typemax (Int) ? string (seed) : " 0x" * string (seed, base= 16 ) # DWIM
149
145
if rng. adv_jump == 0 && rng. adv == 0
150
- return print (io, MersenneTwister, " (" , seed_str , " )" )
146
+ return print (io, MersenneTwister, " (" , repr (rng . seed) , " )" )
151
147
end
152
- print (io, MersenneTwister, " (" , seed_str , " , (" )
148
+ print (io, MersenneTwister, " (" , repr (rng . seed) , " , (" )
153
149
# state
154
150
adv = Integer[rng. adv_jump, rng. adv]
155
151
if rng. adv_vals != - 1 || rng. adv_ints != - 1
@@ -277,76 +273,84 @@ end
277
273
278
274
# ## seeding
279
275
280
- # ### make_seed ()
276
+ # ### random_seed() & hash_seed ()
281
277
282
- # make_seed produces values of type Vector{UInt32}, suitable for MersenneTwister seeding
283
- function make_seed ()
278
+ # random_seed tries to produce a random seed of type UInt128 from system entropy
279
+ function random_seed ()
284
280
try
285
- return rand (RandomDevice (), UInt32, 4 )
281
+ # as MersenneTwister prints its seed when `show`ed, 128 bits is a good compromise for
282
+ # almost surely always getting distinct seeds, while having them printed reasonably tersely
283
+ return rand (RandomDevice (), UInt128)
286
284
catch ex
287
285
ex isa IOError || rethrow ()
288
286
@warn " Entropy pool not available to seed RNG; using ad-hoc entropy sources."
289
- return make_seed ( Libc. rand () )
287
+ return Libc. rand ()
290
288
end
291
289
end
292
290
293
- """
294
- make_seed(n::Integer) -> Vector{UInt32}
295
-
296
- Transform `n` into a bit pattern encoded as a `Vector{UInt32}`, suitable for
297
- RNG seeding routines.
298
-
299
- `make_seed` is "injective" : if `n != m`, then `make_seed(n) != `make_seed(m)`.
300
- Moreover, if `n == m`, then `make_seed(n) == make_seed(m)`.
301
-
302
- This is an internal function, subject to change.
303
- """
304
- function make_seed (n:: Integer )
305
- neg = signbit (n)
291
+ function hash_seed (seed:: Integer )
292
+ ctx = SHA. SHA2_256_CTX ()
293
+ neg = signbit (seed)
306
294
if neg
307
- n = ~ n
308
- end
309
- @assert n >= 0
310
- seed = UInt32[]
311
- # we directly encode the bit pattern of `n` into the resulting vector `seed`;
312
- # to greatly limit breaking the streams of random numbers, we encode the sign bit
313
- # as the upper bit of `seed[end]` (i.e. for most positive seeds, `make_seed` returns
314
- # the same vector as when we didn't encode the sign bit)
315
- while ! iszero (n)
316
- push! (seed, n & 0xffffffff )
317
- n >>>= 32
295
+ seed = ~ seed
318
296
end
319
- if isempty (seed) || ! iszero (seed[end ] & 0x80000000 )
320
- push! (seed, zero (UInt32))
321
- end
322
- if neg
323
- seed[end ] |= 0x80000000
297
+ @assert seed >= 0
298
+ while true
299
+ word = (seed % UInt32) & 0xffffffff
300
+ seed >>>= 32
301
+ SHA. update! (ctx, reinterpret (NTuple{4 , UInt8}, word))
302
+ iszero (seed) && break
324
303
end
325
- seed
304
+ # make sure the hash of negative numbers is different from the hash of positive numbers
305
+ neg && SHA. update! (ctx, (0x01 ,))
306
+ SHA. digest! (ctx)
326
307
end
327
308
328
- # inverse of make_seed(::Integer)
329
- function from_seed (a:: Vector{UInt32} ):: BigInt
330
- neg = ! iszero (a[end ] & 0x80000000 )
331
- seed = sum ((i == length (a) ? a[i] & 0x7fffffff : a[i]) * big (2 )^ (32 * (i- 1 ))
332
- for i in 1 : length (a))
333
- neg ? ~ seed : seed
309
+ function hash_seed (seed:: Union{AbstractArray{UInt32}, AbstractArray{UInt64}} )
310
+ ctx = SHA. SHA2_256_CTX ()
311
+ for xx in seed
312
+ SHA. update! (ctx, reinterpret (NTuple{8 , UInt8}, UInt64 (xx)))
313
+ end
314
+ # discriminate from hash_seed(::Integer)
315
+ SHA. update! (ctx, (0x10 ,))
316
+ SHA. digest! (ctx)
334
317
end
335
318
336
319
320
+ """
321
+ hash_seed(seed) -> AbstractVector{UInt8}
322
+
323
+ Return a cryptographic hash of `seed` of size 256 bits (32 bytes).
324
+ `seed` can currently be of type `Union{Integer, DenseArray{UInt32}, DenseArray{UInt64}}`,
325
+ but modules can extend this function for types they own.
326
+
327
+ `hash_seed` is "injective" : if `n != m`, then `hash_seed(n) != `hash_seed(m)`.
328
+ Moreover, if `n == m`, then `hash_seed(n) == hash_seed(m)`.
329
+
330
+ This is an internal function subject to change.
331
+ """
332
+ hash_seed
333
+
337
334
# ### seed!()
338
335
339
- function seed! (r:: MersenneTwister , seed:: Vector{UInt32} )
340
- copyto! (resize! (r. seed, length (seed)), seed)
341
- dsfmt_init_by_array (r. state, r. seed)
336
+ function initstate! (r:: MersenneTwister , data:: StridedVector , seed)
337
+ # we deepcopy `seed` because the caller might mutate it, and it's useful
338
+ # to keep it constant inside `MersenneTwister`; but multiple instances
339
+ # can share the same seed without any problem (e.g. in `copy`)
340
+ r. seed = deepcopy (seed)
341
+ dsfmt_init_by_array (r. state, reinterpret (UInt32, data))
342
342
reset_caches! (r)
343
343
r. adv = 0
344
344
r. adv_jump = 0
345
345
return r
346
346
end
347
347
348
- seed! (r:: MersenneTwister ) = seed! (r, make_seed ())
349
- seed! (r:: MersenneTwister , n:: Integer ) = seed! (r, make_seed (n))
348
+ # when a seed is not provided, we generate one via `RandomDevice()` in `random_seed()` rather
349
+ # than calling directly `initstate!` with `rand(RandomDevice(), UInt32, whatever)` because the
350
+ # seed is printed in `show(::MersenneTwister)`, so we need one; the cost of `hash_seed` is a
351
+ # small overhead compared to `initstate!`, so this simple solution is fine
352
+ seed! (r:: MersenneTwister , :: Nothing ) = seed! (r, random_seed ())
353
+ seed! (r:: MersenneTwister , seed) = initstate! (r, hash_seed (seed), seed)
350
354
351
355
352
356
# ## Global RNG
713
717
function _randjump (r:: MersenneTwister , jumppoly:: DSFMT.GF2X )
714
718
adv = r. adv
715
719
adv_jump = r. adv_jump
716
- s = MersenneTwister (copy ( r. seed) , DSFMT. dsfmt_jump (r. state, jumppoly))
720
+ s = MersenneTwister (r. seed, DSFMT. dsfmt_jump (r. state, jumppoly))
717
721
reset_caches! (s)
718
722
s. adv = adv
719
723
s. adv_jump = adv_jump
0 commit comments