samplespace.repeatablerandom
- Repeatable Random Sequences¶
RepeatableRandomSequence
allows for generating repeatable,
deterministic random sequences. It is compatible with the built-in
random
module as a drop-in replacement.
A key feature of RepeatableRandomSequence
is its ability to
get, serialize, and restore internal state. This is especially useful
when generating procedural content from a fixed seed.
A RepeatableRandomSequence
can also be used for unit testing
by replacing the built-in random
module. Because each random
sequence is deterministic and repeatable for a given seed, expected
values can be recorded and compared against within unit tests.
RepeatableRandomSequence
produces high-quality pseudo-random
values. See Random Generator Quality for results from randomness tests.
-
class
samplespace.repeatablerandom.
RepeatableRandomSequence
(seed=None)¶ A deterministic and repeatable random number generator compatible with Python’s builtin
random
module.
-
RepeatableRandomSequence.
BLOCK_SIZE_BITS
: int = 64¶ The number of bits generated for each unique index.
-
RepeatableRandomSequence.
BLOCK_MASK
: int = 18446744073709551615¶ A bitmask corresponding to
(1 << BLOCK_SIZE_BITS) - 1
Bookkeeping functions¶
-
RepeatableRandomSequence.
seed
(value=None) → None¶ Re-initialize the random generator with a new seed. Resets the sequence to its first value.
Caution
This method cannot be called from within
cascade()
, and will raise aRuntimeError
if attempted.- Parameters
value (int, str, bytes, bytearray) – The value to seed with. If this is a sequence type, the sequence is first hashed with seed 0.
- Raises
ValueError – If the seed value is not a supported type.
-
RepeatableRandomSequence.
getseed
()¶ Returns: the original value passed to
RepeatableRandomSequence()
orseed()
.
-
RepeatableRandomSequence.
getstate
() → samplespace.repeatablerandom.RepeatableRandomSequenceState¶ Returns an opaque object representing the sequence’s current state, used for saving, restoring, and serializing the sequence. This object can be passed to
setstate()
to restore the sequence’s state.
-
RepeatableRandomSequence.
setstate
(state: samplespace.repeatablerandom.RepeatableRandomSequenceState) → None¶ Restore the sequence’s state from previous call to
getstate()
.
-
RepeatableRandomSequence.
reset
() → None¶ Reset the sequence to the beginning, setting
index
to 0.Caution
This method cannot be called from within
cascade()
, and will raise aRuntimeError
if attempted.
-
RepeatableRandomSequence.
index
¶ The sequence’s current index. Generating random values will always increase the index.
Tip
Prefer
getstate()
andsetstate()
over index manipulation when saving and restoring state.Caution
The index cannot be read or written within
cascade()
, and will raise aRuntimeError
if attempted.- Type
-
RepeatableRandomSequence.
getnextblock
() → int¶ Get a block of random bits from the random generator.
Tip
Calling this method will advance the sequence exactly one time. The resulting index depends on whether or not the call occurs within a cascade.
- Returns
A block of
BLOCK_SIZE_BITS
random bits as an int
-
RepeatableRandomSequence.
getrandbits
(k: int) → int¶ Generate an int with k random bits.
This method may call
getnextblock()
multiple times if k is greater thanBLOCK_SIZE_BITS
; regardless of the number of calls, the index will only advance once.Tip
Note that
k == 0
is a valid input, which will advance the sequence even though no elements are chosen.- Parameters
k (int) – The number of random bits to generate.
- Returns
A random integer in [0,
max(2^k, 1)
)
-
RepeatableRandomSequence.
cascade
()¶ Returns a context manager that defines a generation cascade.
Cascades are not required for most applications, but may be useful for certain advanced procedural generation techniques.
A cascade allows multiple random samples to be treated as part the same logical unit.
For example, generating a random position in space can be treated as a single “transaction” by grouping the fields into a single cascade:
>>> print(rrs.index) 10 >>> with rrs.cascade(): ... x = rrs.random() # index is 10 ... y = rrs.random() # index is the previously-generated random block ... z = rrs.random() # index is the previously generated random block ... >>> print(rrs.index) 11
The use of cascades ensure that random values are repeatable and based only on the number of previously-generated values, not the type of the values themselves. This is often useful when creating procedurally-generated content using pre-defined seeds.
While cascading, subsequent calls to random generation functions use the result of the most recently generated random data rather than incrementing the index. When a cascade completes, the index is set to one more than the index when the cascade began.
-
class
samplespace.repeatablerandom.
RepeatableRandomSequenceState
(_seed: Any, _hash_input: bytes, _index: int)¶ An object representing a
RepeatableRandomSequence
’s internal state.-
as_dict
()¶ Return the sequence state as a dictionary for serialization.
-
classmethod
from_dict
(as_dict)¶ Construct a new sequence state from a dictionary, as returned by
as_dict()
.Examples
>>> rrs = RepeatableRandomSequence() >>> >>> state_as_dict = rrs.getstate().as_dict() >>> state_as_dict {'seed': 0, 'hash_input': 'NMlqzcrbG7s=', 'index': 0} >>> >>> new_state = RepeatableRandomSequenceState.from_dict(state_as_dict) >>> rrs.setstate(new_state)
-
Integer distributions¶
-
RepeatableRandomSequence.
randrange
(start: int, stop: Optional[int] = None, step: int = 1) → int¶ Generate a random integer from
range(start[, stop][, step]).
If only one argument is provided, samples from
range(start)
.- Parameters
- Raises
TypeError – if any arguments are not integral.
ValueError – if step is 0
-
RepeatableRandomSequence.
randint
(a: int, b: int) → int¶ Generate a random integer in the range [a, b].
This is an alias for
randrange(a, b + 1)
, included for compatibility with the builtinrandom
module.
-
RepeatableRandomSequence.
randbytes
(num_bytes) → bytes¶ Generate a sequence of random bytes.
The index will only increment once, regardless of the number of bytes in the sequence.
This method produces similar results to
with rrs.cascade(): result = bytes([rrs.randrange(256) for _ in range(num_bytes])
but offers significantly-improved performance and does not discard excess random bits.
- Returns
A
bytes
object with num_bytes random integers in [0, 255].
-
RepeatableRandomSequence.
geometric
(mean: float, include_zero: bool = False) → int¶ Generate integers according to a geometric distribution.
If include_zero is
False
, returns integers from 1 to infinity according to \(\text{Pr}(x = k) = p {(1 - p)}^{k - 1}\) where \(p = \frac{1}{\mathit{mean}}\).If include_zero is
True
, returns integers from 0 to infinity according to \(\text{Pr}(x = k) = p {(1 - p)}^{k}\) where \(p = \frac{1}{\mathit{mean} + 1}\).- Parameters
- Raises
ValueError – if mean is less than 1 if include_zero is
False
, or less than 0 if include_zero isTrue
.
-
RepeatableRandomSequence.
finitegeometric
(s: float, n: int)¶ Generate a random integer according to a geometric-like distribution with exponent s and finite support {1, …, n}.
The finite geometric distribution is defined by the equation
\[\text{Pr}(x=k) = \frac{s^{k}}{\sum_{i=1}^{N} s^{i}}\]- Raises
ValueError – if n is not at least 1
-
RepeatableRandomSequence.
zipfmandelbrot
(s: float, q: float, n: int)¶ Generate a random integer according to a Zipf-Mandelbrot distribution with exponent s, offset q, and support {1, …, n}.
The Zipf-Mandelbrot distribution is defined by the equation
\[\text{Pr}(x=k) = \frac{(k + q)^{-s}}{\sum_{i=1}^{N} (i+q)^{-s}}\]- Raises
ValueError – if n is not at least 1
Categorical distributions¶
-
RepeatableRandomSequence.
choice
(sequence: Sequence)¶ Choose a single random element from within a sequence.
- Raises
IndexError – if the sequence is empty.
-
RepeatableRandomSequence.
choices
(population: Sequence, weights: Optional[Sequence[float]] = None, *, cum_weights: Optional[Sequence[float]] = None, k: int = 1) → Sequence¶ Choose k elements from a population, with replacement.
Either relative (via weights) or cumulative (via cum_weights) weights may be specified. If no weights are specified, selections are made uniformly with equal probability.
Tip
Note that
k == 0
is a valid input, which returns an empty list an advances the sequence once even though no elements are chosen.- Parameters
population (sequence) – The population to sample from, which may include duplicate elements.
weights (sequence[float], optional) – Relative weights for each element in the population. Need not sum to 1.
cum_weights (sequence[float], optional) – Cumulative weights for each element in the population, as calculated by something like
list(accumulate(weights))
.k (int) – The number of elements to choose.
- Raises
IndexError – The population is empty.
ValueError – The length of weights or cum_weights does not match the population size.
TypeError – Both weights and cum_weights are specified.
-
RepeatableRandomSequence.
shuffle
(sequence: Sequence) → None¶ Shuffle a sequence in place.
The index is only incremented once.
-
RepeatableRandomSequence.
sample
(population, k: int) → Sequence¶ Choose k unique random elements from a population, without replacement.
Elements are returned in selection order, so all subsets of the returned list are valid samples.
If the population includes duplicate values, each occurrence is as distinct possible selection in the result.
Tip
Note that
k == 0
is a valid input, which returns an empty list an advances the sequence once even though no elements are chosen.- Parameters
- Raises
IndexError – if population is empty.
ValueError – if k is greater than the population size.
Continuous distributions¶
-
RepeatableRandomSequence.
uniform
(a: float, b: float) → float¶ Return a random float uniformly distributed in [a, b).
-
RepeatableRandomSequence.
triangular
(low: float = 0.0, high: float = 1.0, mode: Optional[float] = None) → float¶ Sample from a triangular distribution with lower limit low, upper limit low, and mode mode.
The triangular distribution is defined by
\[\begin{split}\text{P}(x) = \begin{cases} 0 & \text{for } x \lt l, \\ \frac{2(x-l)}{(h-l)(m-l)} & \text{for }l\le x \lt h, \\ \frac{2}{h-l} & \text{for } x = m, \\ \frac{2(h-x)}{(h-l)(h-m)} & \text{for } m \lt x \le h, \\ 0 & \text{for } h \lt x \end{cases}\end{split}\]- Raises
ValueError – if the mode is not in [low, high].
-
RepeatableRandomSequence.
uniformproduct
(n: int) → float¶ Sample from a distribution whose values are the product of N uniformly distributed variables.
This distribution has the following PDF
\[\begin{split}\text{P}(x) = \begin{cases} \frac{(-1)^{n-1} log^{n-1}(x)}{(n - 1)!} & \text{for } x \in [0, 1) \\ 0 & \text{otherwise} \end{cases}\end{split}\]- Raises
ValueError – if n is not at least 1.
-
RepeatableRandomSequence.
gauss
(mu: float, sigma: float) → float¶ Sample from a Gaussian distribution with parameters mu and sigma.
The Gaussian, or Normal distribution is defined by
\[\text{P}(x) = \frac{1}{\sigma \sqrt{2 \pi}} e^{-\frac{1}{2} \left(\frac{x - \mu}{\sigma}\right)^2}\]Tip
If multiple normally-distributed values are required, consider using
gausspair()
for improved performance.
-
RepeatableRandomSequence.
gausspair
(mu: float, sigma: float) → Tuple[float, float]¶ Return a pair of independent samples from a Gaussian distribution with parameters mu and sigma.
This method produces similar results to
with rrs.cascade(): result = rrs.gauss(mu, sigma), rrs.gauss(mu, sigma)
but offers improved performance.
-
RepeatableRandomSequence.
lognormvariate
(mu: float, sigma: float) → float¶ Sample from a log-normal distribution with parameters mu and sigma.
The logarithms of values sampled from a log-normal distribution are normally distributed with mean mu and standard deviation sigma. The distribution is defined by
\[\text{P}(x) = \frac{1}{x \sigma \sqrt{2 \pi}} \exp{\left(- \frac{(\ln{x} - \mu)^2}{2 \sigma ^2}\right)}\]
-
RepeatableRandomSequence.
expovariate
(lambd: float) → float¶ Sample from an exponential distribution with rate lambd.
The probability density function for the exponential distribution is defined by \(\text{P}(x)= \lambda e^{-\lambda x}\) where \(x\ge 0\)
-
RepeatableRandomSequence.
vonmisesvariate
(mu: float, kappa: float) → float¶ Sample from a von Mises distribution with parameters mu and kappa.
Samples from a von Mises distribution represent randomly-chosen angles clustered around a mean angle. This distribution is an approximation to the wrapped normal distribution and is defined by
\[\text{P}(x) = \frac{e^{\kappa \cos{(x - \mu)}}}{2 \pi I_0(\kappa)}\]where \(I_0(\kappa)\) is the modified Bessel function of order 0.
-
RepeatableRandomSequence.
gammavariate
(alpha: float, beta: float) → float¶ Sample from a gamma distribution with parameters alpha and beta. Not to be confused with
math.gamma()
!The gamma distribution is defined as
\[\text{P}(x) = \frac{x^{\alpha - 1} e^{-\frac{x}{\beta}}} {\Gamma(\alpha) \beta^{\alpha}}\]Caution
This implementation defines its parameters to match
random.gammavariate()
. The parametrization differs from most common definitions of the gamma distribution, as defined on Wikipedia, et al. Take care when setting alpha and beta!- Raises
ValueError – if either alpha or beta is not greater than 0.
-
RepeatableRandomSequence.
betavariate
(alpha: float, beta: float) → float¶ Sample from a beta distribution with parameters alpha and beta.
The beta distribution is defined by
\[\text{P}(x) = x^{\alpha - 1} (1 - x)^{\beta - 1} \frac{\Gamma(\alpha + \beta)}{\Gamma(\alpha)\Gamma(\beta)}\]- Returns
A random, beta-distributed value in [0.0, 1.0]
- Raises
ValueError – if either alpha or beta is not greater than 0.
-
RepeatableRandomSequence.
paretovariate
(alpha: float) → float¶ Sample from a Pareto distribution with shape parameter alpha and minimum value 1.
The Pareto distribution has PDF
\[\text{P}(x) = \frac{\alpha}{x^{\alpha + 1}}\]- Raises
ValueError – if alpha is zero.
-
RepeatableRandomSequence.
weibullvariate
(alpha: float, beta: float) → float¶ Sample from a Weibull distribution with scale parameter alpha and shape parameter beta.
The distribution is defined by
\[\text{P}(x) = \frac{\beta}{\alpha} \left(\frac{x}{\alpha}\right)^{k-1} e^{-(x/\alpha)^k}\]where \(x \ge 0\).
- Raises
ValueError – if alpha is zero.
Examples¶
Generating random values:
import samplespace
rrs = samplespace.RepeatableRandomSequence(seed=1234)
samples = [rrs.randrange(30) for _ in range(10)]
print(samples)
# Will always print:
# [21, 13, 28, 19, 16, 29, 28, 24, 29, 25]
Distinct seeds produce unique results:
import samplespace
# Each seed will generate a unique sequence of values
for seed in range(5):
rrs = samplespace.RepeatableRandomSequence(seed=seed)
samples = [rrs.random() for _ in range(5)]
print('Seed: {0}\tResults: {1}'.format(
seed,
' '.join('{:.3f}'.format(x) for x in samples)))
Results depend only on number of previous calls, not their type:
import samplespace
rrs = samplespace.RepeatableRandomSequence(seed=1234)
dummy = rrs.random()
x1 = rrs.random()
rrs.reset()
dummy = rrs.gauss(6.0, 2.0)
x2 = rrs.random()
assert x2 == x1
rrs.reset()
dummy = rrs.randrange(50)
x3 = rrs.random()
assert x3 == x1
rrs.reset()
dummy = list('abcdefg')
rrs.shuffle(dummy)
x4 = rrs.random()
assert x4 == x1
rrs.reset()
with rrs.cascade():
dummy = [rrs.random() for _ in range(10)]
x5 = rrs.random()
assert x5 == x1
Replay random sequences using RepeatableRandomSequence.reset()
:
import samplespace
rrs = samplespace.RepeatableRandomSequence(seed=1234)
samples = [rrs.random() for _ in range(10)]
print(' '.join(samples))
# Using reset() returns the sequence to its initial state
rrs.reset()
samples2 = [rrs.random() for _ in range(10)]
print(' '.join(samples2))
assert samples == sample2
Replay random sequences using RepeatableRandomSequence.getstate()
/
RepeatableRandomSequence.setstate()
:
import samplespace
rrs = samplespace.RepeatableRandomSequence(seed=12345)
# Generate some random values to advance the state
[rrs.random() for _ in range(100)]
# Save the state for later recall
state = rrs.getstate()
print(rrs.random())
# Will print 0.2736967629462168
# Generate some more values
[rrs.random() for _ in range(100)]
# Return the sequence to the saved state. The next value will match
# the value following when the state was saved.
rrs.setstate(state)
print(rrs.random())
# Will also print 0.2736967629462168
Replay random sequences using RepeatableRandomSequence.index()
:
import samplespace
rrs = samplespace.RepeatableRandomSequence(seed=5)
# Generate a sequence of values
samples = [rrs.randrange(10) for _ in range(15)]
print(samples)
# Will print
# [0, 2, 2, 7, 9, 4, 1, 5, 5, 6, 7, 1, 7, 6, 8]
# Rewind the sequence by 5
rrs.index -= 5
# Generate a new sequence, will overlap by 5 elements
samples = [rrs.randrange(10) for _ in range(15)]
print(samples)
# Will print
# [7, 1, 7, 6, 8, 6, 6, 6, 3, 7, 6, 9, 5, 2, 7]
Serialize sequence state as simple data types:
import samplespace
import samplespace.repeatablerandom
import json
rrs = samplespace.RepeatableRandomSequence(seed=12345)
# Generate some random values to advance the state
[rrs.random() for _ in range(100)]
# Save the state for later recall
# State can be serialzied to a dict and serialized as JSON
state = rrs.getstate()
state_as_dict = state.as_dict()
state_as_json = json.dumps(state_as_dict)
print(state_as_json)
# Prints {"seed": 12345, "hash_input": "gxzNfDj4Ypc=", "index": 100}
print(rrs.random())
# Will print 0.2736967629462168
# Generate some more values
[rrs.random() for _ in range(100)]
# Return the sequence to the saved state. The next value will match
# the value following when the state was saved.
new_state_as_dict = json.loads(state_as_json)
new_state = samplespace.repeatablerandom.RepeatableRandomSequenceState.from_dict(new_state_as_dict)
rrs.setstate(new_state)
print(rrs.random())
# Will also print 0.2736967629462168