NumPy's RandomState, as far as Monty draws from it.
Monty's randomness is numpy.random.RandomState, and a number that is not
NumPy's is a different experiment rather than a different implementation of the
same one: the salience module dithers its scores with rng.normal and the
agent turns towards whichever point the dither leaves in front of it, so the
first action of an episode — and with it every later one — is a function of
these numbers. Hence a port of the generator itself, not of its statistics.
Ported: MT19937 seeded as NumPy seeds it (init_genrand, which is what
RandomState(seed) does for a seed below 2³²), numpy's random_sample
(rk_double, two 32-bit words per double), and normal (rk_gauss: Marsaglia's
polar method keeping NumPy's cached second value, which is part of its state and
survives between calls — including between calls of odd length, where the spare
is what the next call returns).
Not ported: bounded integers and choice. Monty uses those on the world side
only (environment.py), where its own code runs — see
scripts/monty_simulator_peer.py — so a copy here would be a second answer to
a question this side never asks.
State is a value, so a caller threads it:
{f, state} = Numpy.random_sample(Numpy.new(42))which is what lets a stream be part of a struct or a fold without a process.
Summary
Functions
The generator numpy.random.RandomState(seed) is, at its first draw.
A normal draw: numpy's normal(loc, scale, size=1).
count normal draws: numpy's normal(loc, scale, size=count).
A float drawn uniformly from [0, 1): numpy's random_sample, and rand.
A float drawn uniformly from [low, high): numpy's uniform.
Types
@type t() :: %Monty.Random.Numpy{ gauss: float(), has_gauss: boolean(), key: tuple(), pos: non_neg_integer() }
Functions
@spec new(non_neg_integer()) :: t()
The generator numpy.random.RandomState(seed) is, at its first draw.
NumPy refuses a seed it cannot seed a Mersenne Twister with in one word, and so
does this: Monty's episode seeds are already below 2³²
(Monty.Frameworks.Experiments.Seed), and a larger one would silently be a
different stream.
A normal draw: numpy's normal(loc, scale, size=1).
The second value of each polar pair is kept in the state, as NumPy keeps it, so a caller drawing one at a time gets numpy's numbers and not half of them.
count normal draws: numpy's normal(loc, scale, size=count).
A float drawn uniformly from [0, 1): numpy's random_sample, and rand.
A float drawn uniformly from [low, high): numpy's uniform.