Is it possible to change the seed of a random generator in NumPy?

Question:

Say I instantiated a random generator with

import numpy as np
rng = np.random.default_rng(seed=42)

and I want to change its seed. Is it possible to update the seed of the generator instead of instantiating a new generator with the new seed?

I managed to find that you can see the state of the generator with rng.__getstate__(), for example in this case it is

{'bit_generator': 'PCG64', 
 'state': {'state': 274674114334540486603088602300644985544, 
 'inc': 332724090758049132448979897138935081983}, 
 'has_uint32': 0, 
 'uinteger': 0}

and you can change it with rng.__setstate__ with arguments as printed above, but it is not clear to me how to set those arguments so that to have the initial state of the rng given a different seed. Is it possible to do so or instantiating a new generator is the only way?

Asked By: Tortar

||

Answers:

N.B. The other answer (https://stackoverflow.com/a/74474377/2954547) is better. Use that one, not this one.


This is maybe a silly hack, but one solution is to create a new RNG instance using the desired new seed, then replace the state of the existing RNG instance with the state of the new instance:

import numpy as np

seed = 12345

rng = np.random.default_rng(seed)
x1 = rng.normal(size=10)

rng.__setstate__(np.random.default_rng(seed).__getstate__())
x2 = rng.normal(size=10)

np.testing.assert_array_equal(x1, x2)

However this isn’t much different from just replacing the RNG instance.

Edit: To answer the question more directly, I don’t think it’s possible to replace the seed without constructing a new Generator or BitGenerator, unless you know how to correctly construct the state data for the particular BitGenerator inside your Generator. Creating a new RNG is fairly cheap, and while I understand the conceptual appeal of not instantiating a new one, the only alternative here is to post a feature request on the Numpy issue tracker or mailing list.

Answered By: shadowtalker

A Numpy call like default_rng() gives you a Generator with an implicitly created BitGenerator. The difference between these is that a BitGenerator is the low-level method that just knows how to generate uniform uint32s, uint64s, and doubles. The Generator can then take these and turn them into other distributions.

As you noticed you can use __getstate__ but this is designed for the pickle module and not really for what you’re using it for.

You’re better off accessing the bit_generator directly. Which means you don’t need to use any dunder methods.

The following code still uses default_rng but this means the BitGenerator could change in the future so I need a call to type to reseed. You’d probably be better off following the second example which uses an explicit BitGenerator.

import numpy as np

seed = 42

rng = np.random.default_rng()

# get the BitGenerator used by default_rng
BitGen = type(rng.bit_generator)

# use the state from a fresh bit generator
rng.bit_generator.state = BitGen(seed).state

# generate a random float
print(rng.random())

outputs 0.7739560485559633. If you’re happy fixing the BitGenerator you can avoid the call to type, e.g.:

rng = np.random.Generator(np.random.PCG64())

rng.bit_generator.state = np.random.PCG64(seed).state

rng.random()

which outputs the same value.

Answered By: Sam Mason