Toolbox.Random (toolbox v1.1.0)
Functions for working with deterministic (pseudo)randomness in scenarios.
In some use cases, scenarios need to behave “randomly”, for example, when a scenario serves as a demo events generator. Scenarios are by design deterministic which means that they can only behave randomly in the sense that they make certain decisions “on their own” (without the programmer explicitly choosing the outcome) but every time a scenario run is repeated, the same “random” decisions are made. To make this possible, the seed of the random number generator has to be always initialized to the same value and managed in the way that it is preserved throughout the run. And that is what this module helps with.
The module wraps around Erlang :rand
module for random number generation.
Functions in the :rand
module use process dictionary for storing the
random number generator state (i.e. the seed). To be able to restore a run,
the random number generator state is also kept in the state of a unit in
an opaque field called __rand_seed
.
There are three steps (functions of this module) in order to use randomness
in scenarios. First, you have to initialize the unit that uses randomness
with init/2
. And then, every time before you want to use randomness, you
have to call load/1
with the particular unit. And then store the new
seed with save/1
. An example is given below. See the documentation for
each of the functions for more detailed information.
example
Example
defmodule Scenarios.MyScenario.MyTemplate
@behaviour Toolbox.Scenario.Template.StageBased
alias Toolbox.Random
def instances do
# singleton unit
[{ :unit,"generator", %{} }]
end
def init(_start_from, unit) do
unit = Random.init(unit, 293732)
{:ok, [], unit}
end
def handle_message(msg, unit) do
Random.load(unit)
... do something that uses :rand calls
... (e.g. :rand.uniform/0 or Enum.random/1)
{:noreply, oas, Random.save(unit)}
end
end
Link to this section Summary
Link to this section Types
input_seed()
@type input_seed() :: term()
seed()
@type seed() :: :rand.seed()
Link to this section Functions
Shortcut for init/2
called with __MODULE__
as the seed.
It is a bit shorter, but on the other hand you have to require Toolbox.Random
to be able to use this macro. Beware that the generated values will differ when
you rename your template module.
init(unit, seed)
@spec init(Toolbox.Runtime.Stage.Unit.t(), input_seed()) :: Toolbox.Runtime.Stage.Unit.t()
Initializes a unit for random number generation.
You need to provide an initial seed which may be any term, in particular
any integer, string or atom. The value doesn't matter (it only affects the
randomly generated values) as long as it is constant. Typically, you would
use this function inside the init/2
callback of your template.
In case you are using a singleton unit, you may, for example, hardcode an
integer here or use __MODULE__
as the initial seed:
def init(_start_from, unit) do
unit = Random.init(unit, __MODULE__)
{:ok, [], unit}
end
If you have multiple units (e.g. unit for each person), you may initialize each unit with a different seed (e.g. the ID of that person):
def init(_start_from, unit) do
unit = Random.init(unit, unit.attributes.id)
{:ok, [], unit}
end
This function saves the initial seed into the unit's state under
an opaque field called __rand_seed
and return the modified unit.
Therefore, to use randomness, the unit's state has to be a map.
The function also sets the seed in the process dictionary so you
can start using randomness right away. But you will probably want
to do so later in the handle_message/2
callback.
load(unit)
@spec load(Toolbox.Runtime.Stage.Unit.t()) :: :ok
Loads a seed from the unit's state into the process dictionary.
Typically, you would call this function in the beginning of a handle_message/2
callback that deals with randomness. It is sufficient to call it once per invocation
of the callback.
example
Example
def handle_message(msg, unit) do
Random.load(unit)
person = Enum.random(unit.state.people)
room = Enum.random(unit.state.rooms)
# generate an event that $person went into $room
{:noreply, oas, Random.save(unit)}
end
save(unit)
@spec save(Toolbox.Runtime.Stage.Unit.t()) :: Toolbox.Runtime.Stage.Unit.t()
Stores the random generator state in the unit's state.
Typically, you would use this function after generating random values
(and thus modifying the generator's seed) in the handle_message/2
callback.
The function returns the modified unit.