Monty.Random.Numpy (ExTbpMonty.Core v0.1.0)

Copy Markdown View Source

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

t()

@type t() :: %Monty.Random.Numpy{
  gauss: float(),
  has_gauss: boolean(),
  key: tuple(),
  pos: non_neg_integer()
}

Functions

new(seed)

@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.

normal(state, loc, scale)

@spec normal(t(), float(), float()) :: {float(), t()}

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.

normals(state, count, loc, scale)

@spec normals(t(), non_neg_integer(), float(), float()) :: {[float()], t()}

count normal draws: numpy's normal(loc, scale, size=count).

random_sample(state)

@spec random_sample(t()) :: {float(), t()}

A float drawn uniformly from [0, 1): numpy's random_sample, and rand.

uniform(state, low, high)

@spec uniform(t(), float(), float()) :: {float(), t()}

A float drawn uniformly from [low, high): numpy's uniform.