Disable global RNG

In my simulation, I make frequent use of random numbers. I want everything to be reproducible, so I explicitly seed a single RNG and pass it around as needed.

A few times now I’ve made the mistake of calling rand(n) rather than rand(rng, n). Because the former just falls back to the global RNG, it doesn’t produce errors and I don’t notice the problem until I look at my results closely and see small differences between runs.

To make it easier to catch these errors, is there any way to disable to the global RNG? Maybe replace it with something that fails when attempting to sample from it?

Doesn’t Random.seed! do what you want? It seeds the global RNG. I use random numbers quite a bit for my MC simulations as well. Suppose I have a main function which starts an independent simulation,

function main(simulation-number)
   Random.seed!(simulation-number)  # seed each simulation separately. 

   ... call a bunch of other functions that also call rand() all over the place
end 

This usually works for me and the results are reproducible. If you have to use your own generator, then you can seed that as well with Random.seed!(rng, _) but you’ll have to remember to pass your generator around (which is what your problem is I suppose).

Is the global RNG not sufficient for you?

Hm… I guess you’re right. For what I’m doing now, seeding a global RNG works just as well.

I think I started passing around the RNG so that I could use independent RNGs if I wanted to run the simulation on multiple threads. There’s no way to make the global RNG thread-local, is there?

I don’t think rand is threadsafe, and your solution is fine if you remember to pass the different RNGs to different threads. I am not sure about making global RNG thread-safe. Its out of my scope. Maybe someone more knowledgeable can chime in on thread-safety.

If you intend to make the code multithreaded, it’s good practice to use the RNG version, since you’ll run into problems otherwise. See this post for an example of how to obtain an independent RNG per thread. (Also scroll up in the post to see the type of problems that can occur otherwise.)

As for disabling the global RNG, you could do something like this:

julia> rand(1, 4)
1×4 Array{Float64,2}:
 0.305565  0.252981  0.639368  0.759004

julia> Base.rand(dims::Integer...) = throw(ErrorException("Disabled. Use rand(rng, dims) instead."))

julia> rand(1, 4)
ERROR: Disabled. Use rand(rng, dims) instead.

(Note: There are other rand methods too that you’d need to redefine.) However, use this with care: it changes rand globally, so it won’t just affect your code.

4 Likes

You can just make a copy of the global RNG and check that it was not used (which modifies its state):

using Random: GLOBAL_RNG
RNG_saved = copy(GLOBAL_RNG)
@assert RNG_saved == GLOBAL_RNG # OK
rand(1)
@assert RNG_saved == GLOBAL_RNG # will fail
9 Likes

I often add something like the following to a project, where I don’t want to use the global rng by accident:

julia> import Random

julia> @inline function rand(rng::Random.AbstractRNG, args...)
      # make sure this package always explicitly passes
      # rng to rand calls
     Random.rand(rng, args...)
end

rand (generic function with 1 method)

julia> rand(2)
ERROR: MethodError: no method matching rand(::Int64)
You may have intended to import Base.rand
Closest candidates are:
  rand(::Random.AbstractRNG, ::Any...) at REPL[10]:4
Stacktrace:
 [1] top-level scope at none:0

julia> rand(Random.MersenneTwister(23), 2)
2-element Array{Float64,1}:
 0.24048229227904683
 0.5303313896063526 

In contrast to @bennedich variant, this does not change rand for other modules.

4 Likes