genotype_rand (faber_tweann v2.4.0)

View Source

The genotype layer's own random generator, so a run can be a function of its seed and a library draw can never perturb a caller's stream.

========================================================================== THE FAILURE THIS EXISTS TO PREVENT ==========================================================================

Every draw in genotype construction and mutation used to be a bare rand:uniform/0, which reads and writes the ONE rand state that Erlang keeps in the calling process's dictionary. That is the state the caller is also using. So calling genome_mutator:mutate/1 silently advanced the caller's generator by an unpredictable number of steps, and a caller who had seeded deliberately no longer got the sequence they seeded for.

A downstream project has this on record. hecate-dronex's island module carries its generator through every function rather than reaching for it, under a banner that reads: "Register D.5: one unrecorded draw in a library constructor was enough to make the benchmark irreproducible and to break the property that a genome specifies a controller." The unrecorded draw in a library constructor was this one.

========================================================================== WHY A SEPARATE STATE RATHER THAN THREADING ==========================================================================

Threading a rand:state() through every constructor and every mutation operator is the textbook answer and it would change the arity and the return shape of genotype:construct_Agent/3, clone_Agent/1, genome_mutator:mutate/1 and every operator behind them. That is a breaking change to the whole public surface, for a property most callers do not ask for.

This module keeps its own state, under its own key, touched by nothing else. That alone fixes the actual defect, because the genotype layer no longer shares a generator with anybody.

And a caller who DOES want pure-value semantics can have them, by threading at the call boundary rather than through the call:

ok = genotype_rand:set_state(IslandRand), ok = genome_mutator:mutate(AgentId), NextIslandRand = genotype_rand:state()

which is three lines and needs no signature to change.

========================================================================== WHAT IS ROUTED THROUGH HERE, AND WHAT IS NOT ==========================================================================

Routed, which is everything genotype:construct_Agent/3 and genome_mutator:mutate/1,2 reach: genotype, mutation_helpers, ltc_mutations, perturbation_utils and selection_utils.

NOT routed, and a run using them is not yet reproducible from a seed: crossover, genome_crossover, selection_algorithm, tuning_selection, species_identifier, network_evaluator's weight initialisation, the ES optimisers and the scapes. Stated rather than implied, because "the run is reproducible" is exactly the kind of claim that costs when it is only mostly true.

Summary

Functions

One element of a non-empty list.

Seed the genotype generator for this process.

Install a generator state, so a run can be resumed or replayed exactly.

The current generator state, for a caller that wants to carry it.

A float in [0.0, 1.0).

An integer in 1..N.

Functions

element_of(List)

-spec element_of([T, ...]) -> T.

One element of a non-empty list.

seed(Seed)

-spec seed(integer() | {integer(), integer(), integer()}) -> ok.

Seed the genotype generator for this process.

Two runs seeded the same way draw the same sequence, so a surprising genotype can be built again.

set_state(State)

-spec set_state(rand:state()) -> ok.

Install a generator state, so a run can be resumed or replayed exactly.

state()

-spec state() -> rand:state().

The current generator state, for a caller that wants to carry it.

Initialises from the system on first use, so a caller that never seeds still gets ordinary randomness rather than a fixed sequence.

uniform()

-spec uniform() -> float().

A float in [0.0, 1.0).

uniform(N)

-spec uniform(pos_integer()) -> pos_integer().

An integer in 1..N.