# `Monty.Random.Numpy`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/random/numpy.ex#L1)

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.

# `t`

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

# `new`

```elixir
@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`

```elixir
@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`

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

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

# `random_sample`

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

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

# `uniform`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
