Source code for neurokit2.misc.check_random_state
import copy
import numbers
import numpy as np
[docs]
def check_random_state(seed=None):
"""**Turn seed into a random number generator**
Parameters
----------
seed : None, int, numpy.random.RandomState or numpy.random.Generator
Seed for the random number generator. If seed is None, a numpy.random.Generator is created with fresh,
unpredictable entropy. If seed is an int, a new numpy.random.RandomState instance is created, seeded with
seed. If seed is already a Generator or RandomState instance then that instance is used.
The manin difference between the legacy RandomState class and the new Generator class is that the former
has better reproducibililty and compatibililty guarantees (it is effectively frozen from NumPy v1.16)
while the latter has better statistical "randomness" properties and lower computational cost.
See: https://numpy.org/doc/stable/reference/random/legacy.html for further information.
Note: to initialise the new Generator class with an integer seed, use, e.g.:
``check_random_state(np.random.SeedSequence(123))``.
Returns
-------
rng: numpy.random.Generator or numpy.random.RandomState
Random number generator.
"""
# If seed is an integer, use the legacy RandomState class
if isinstance(seed, numbers.Integral):
return np.random.RandomState(seed)
# If seed is already a random number generator class return it as it is
if isinstance(seed, (np.random.Generator, np.random.RandomState)):
return seed
# If seed is something else, use the new Generator class
return np.random.default_rng(seed)
[docs]
def spawn_random_state(rng, n_children=1):
"""**Create new independent children random number generators from parent generator/seed**
Parameters
----------
rng : None, int, numpy.random.RandomState or numpy.random.Generator
Random number generator to be spawned (numpy.random.RandomState or numpy.random.Generator). If it is None
or an int seed, then a parent random number generator is first created with ``misc.check_random_state``.
n_children : int
Number of children generators to be spawned.
Returns
-------
children_generators : list of generators
List of children random number generators.
Examples
----------
* **Example 1**: Simulate data for a cohort of participants
.. ipython:: python
import neurokit2 as nk
master_seed = 42
n_participants = 8
participants_RNGs = nk.misc.spawn_random_state(master_seed, n_children=n_participants)
PPGs = []
for i in range(n_participants):
PPGs.append(nk.ppg_simulate(random_state=participants_RNGs[i]))
"""
rng = check_random_state(rng)
try:
# Try to spawn the rng by using the new API
return rng.spawn(n_children)
except AttributeError:
# It looks like this version of numpy does not implement rng.spawn(), so we do its job
# manually; see: https://github.com/numpy/numpy/pull/23195
if rng._bit_generator._seed_seq is not None:
rng_class = type(rng)
bit_generator_class = type(rng._bit_generator)
return [
rng_class(bit_generator_class(seed=s))
for s in rng._bit_generator._seed_seq.spawn(n_children)
]
except TypeError:
# The rng does not support spawning through SeedSequence, see below
pass
# Implement a rudimentary but reproducible substitute for spawning rng's that also works for
# RandomState with the legacy MT19937 bit generator
# NOTE: Spawning the same generator multiple times is not supported (may lead to mutually
# dependent spawned generators). Spawning the children (in a tree structure) is allowed.
# Start by creating an rng to sample integers (to be used as seeds for the children) without
# advancing the original rng
temp_rng = rng._bit_generator.jumped()
# Generate and return children initialised with the seeds obtained from temp_rng
return [np.random.RandomState(seed=s) for s in temp_rng.random_raw(n_children)]
[docs]
def check_random_state_children(
random_state_parent, random_state_children, n_children=1
):
"""**Create new independent children random number generators to be used in sub-functions**
Parameters
----------
random_state_parent : None, int, numpy.random.RandomState or numpy.random.Generator
Parent's random state (see ``misc.check_random_state``).
random_state_children : {'legacy', 'spawn'}, None, int, numpy.random.RandomState or numpy.random.Generator
If ``"legacy"``, use the same random state as the parent (discouraged as it generates dependent random
streams). If ``"spawn"``, spawn independent children random number generators from the parent random
state. If any of the other types, generate independent children random number generators from the
random_state_children provided.
n_children : int
Number of children generators to be spawned.
Returns
-------
children_generators : list of generators
List of children random number generators.
"""
if random_state_children == "legacy":
return [copy.copy(random_state_parent) for _ in range(n_children)]
elif random_state_children == "spawn":
return spawn_random_state(random_state_parent, n_children)
else:
return spawn_random_state(random_state_children, n_children)