evolution_strategy behaviour (faber_neuroevolution v1.2.4)

View Source

Behaviour definition for pluggable evolution strategies.

This behaviour allows different evolution paradigms to be implemented as interchangeable modules. Each strategy controls:

- When individuals are created and removed - How selection and reproduction work - What events are emitted - What inputs are provided to meta-controllers

Implementing a Strategy

A strategy module must implement these callbacks:

- init/1 - Initialize strategy state from configuration - handle_evaluation_result/3 - Process a single evaluation result - tick/1 - Periodic callback for time-based actions - get_population_snapshot/1 - Return current population state for UI - get_meta_inputs/1 - Return inputs for meta-controller - apply_meta_params/2 - Apply meta-controller parameter adjustments

Built-in Strategies

- generational_strategy - Traditional (mu,lambda) batch evolution - steady_state_strategy - Continuous replacement, no generations - island_strategy - Parallel populations with migration - novelty_strategy - Behavioral novelty search - map_elites_strategy - Quality-diversity with niche grid

Actions and Events

Strategies return {Actions, Events, NewState} tuples:

Actions are requests for the server to perform: - {create_individual, ParentIds, Metadata} - {remove_individual, Id, Reason} - {evaluate_individual, Id} - {evaluate_batch, [Id]}

Events are lifecycle notifications: - #individual_born{} - #individual_died{} - #generation_advanced{} - etc.

Summary

Functions

Apply meta-controller parameters to strategy.

Get meta-controller inputs from strategy.

Get population snapshot from strategy.

Check if a strategy module exports an optional callback.

Initialize a strategy.

Dispatch tick to strategy.

Types

birth_origin/0

-type birth_origin() :: initial | crossover | mutation | migration | insertion.

death_reason/0

-type death_reason() ::
          selection_pressure | stagnation | age_limit | niche_competition | migration |
          population_limit | extinction.

fitness/0

-type fitness() :: float() | undefined.

individual_id/0

-type individual_id() :: term().

individual_summary/0

-type individual_summary() ::
          #{id := individual_id(),
            fitness := fitness(),
            is_survivor => boolean(),
            is_offspring => boolean(),
            species_id => species_id(),
            age => non_neg_integer()}.

island_id/0

-type island_id() :: pos_integer() | atom().

lifecycle_event/0

-type lifecycle_event() ::
          #individual_born{id :: individual_id(),
                           parent_ids :: [individual_id()],
                           timestamp :: timestamp(),
                           origin :: birth_origin(),
                           metadata :: map()} |
          #individual_died{id :: individual_id(),
                           reason :: death_reason(),
                           final_fitness :: float() | undefined,
                           timestamp :: timestamp(),
                           metadata :: map()} |
          #individual_evaluated{id :: individual_id(),
                                fitness :: float(),
                                metrics :: map(),
                                timestamp :: timestamp(),
                                metadata :: map()} |
          #species_emerged{species_id :: species_id(),
                           founder_id :: individual_id(),
                           parent_species_id :: species_id() | undefined,
                           timestamp :: timestamp(),
                           metadata :: map()} |
          #species_extinct{species_id :: species_id(),
                           reason :: stagnation | empty | merged | eliminated,
                           final_stats :: map(),
                           timestamp :: timestamp()} |
          #cohort_evaluated{generation :: pos_integer(),
                            best_fitness :: float(),
                            avg_fitness :: float(),
                            worst_fitness :: float(),
                            population_size :: pos_integer(),
                            timestamp :: timestamp()} |
          #breeding_complete{generation :: pos_integer(),
                             survivor_count :: non_neg_integer(),
                             eliminated_count :: non_neg_integer(),
                             offspring_count :: non_neg_integer(),
                             timestamp :: timestamp()} |
          #generation_advanced{generation :: pos_integer(),
                               previous_best_fitness :: float(),
                               previous_avg_fitness :: float(),
                               population_size :: pos_integer(),
                               species_count :: non_neg_integer(),
                               timestamp :: timestamp()} |
          #steady_state_replacement{replaced_ids :: [individual_id()],
                                    offspring_ids :: [individual_id()],
                                    best_fitness :: float() | undefined,
                                    avg_fitness :: float() | undefined,
                                    timestamp :: timestamp()} |
          #island_migration{individual_id :: individual_id(),
                            from_island :: island_id(),
                            to_island :: island_id(),
                            fitness :: float(),
                            timestamp :: timestamp()} |
          #island_topology_changed{islands :: [island_id()],
                                   connections :: [{island_id(), island_id()}],
                                   change_type :: island_added | island_removed | connection_changed,
                                   timestamp :: timestamp()} |
          #niche_discovered{niche_id :: niche_id(),
                            behavior_descriptor :: [float()],
                            individual_id :: individual_id(),
                            fitness :: float(),
                            timestamp :: timestamp()} |
          #niche_updated{niche_id :: niche_id(),
                         old_individual_id :: individual_id(),
                         new_individual_id :: individual_id(),
                         old_fitness :: float(),
                         new_fitness :: float(),
                         improvement :: float(),
                         timestamp :: timestamp()} |
          #archive_updated{size :: non_neg_integer(),
                           coverage :: float(),
                           qd_score :: float(),
                           updates_since_last :: non_neg_integer(),
                           timestamp :: timestamp()} |
          #competitor_updated{competitor_id :: term(),
                              change_type :: generation_advanced | champion_changed | strategy_shift,
                              champion_fitness :: float() | undefined,
                              timestamp :: timestamp()} |
          #arms_race_event{event_type :: fitness_surge | counter_adaptation | stalemate | breakthrough,
                           populations :: [term()],
                           metrics :: map(),
                           timestamp :: timestamp()} |
          #competition_result{competitors :: [individual_id()],
                              scores :: [{individual_id(), float()}],
                              winner_id :: individual_id() | draw,
                              competition_type ::
                                  tournament | round_robin | elimination | ranked_match | team_vs_team,
                              metadata :: map(),
                              timestamp :: timestamp()} |
          #capability_emerged{capability_id :: term(),
                              description :: binary(),
                              exhibitors :: [individual_id()],
                              timestamp :: timestamp()} |
          #complexity_increased{metric :: genome_size | network_depth | behavior_repertoire | term(),
                                old_value :: number(),
                                new_value :: number(),
                                increase_pct :: float(),
                                timestamp :: timestamp()} |
          #progress_checkpoint{total_evaluations :: non_neg_integer(),
                               evaluations_since_last :: non_neg_integer(),
                               cohort :: non_neg_integer(),
                               best_fitness :: float(),
                               avg_fitness :: float(),
                               worst_fitness :: float(),
                               population_size :: non_neg_integer(),
                               species_count :: pos_integer(),
                               improvement :: float(),
                               elapsed_ms :: non_neg_integer(),
                               evals_per_second :: float(),
                               checkpoint_interval :: non_neg_integer(),
                               timestamp :: timestamp()} |
          #environment_changed{environment_id :: term(),
                               change_type ::
                                   difficulty_increased | difficulty_decreased | task_shifted |
                                   condition_changed | curriculum_advanced,
                               description :: binary(),
                               metrics :: map(),
                               timestamp :: timestamp()} |
          #individual_aged_out{id :: individual_id(),
                               final_age :: pos_integer(),
                               final_fitness :: float(),
                               lifetime_stats ::
                                   #{total_evaluations := non_neg_integer(),
                                     avg_fitness := float(),
                                     best_fitness := float(),
                                     offspring_count := non_neg_integer()},
                               timestamp :: timestamp()}.

meta_inputs/0

-type meta_inputs() :: [float()].

meta_params/0

-type meta_params() ::
          #{mutation_rate => float(),
            mutation_strength => float(),
            selection_ratio => float(),
            migration_rate => float(),
            novelty_weight => float(),
            atom() => number()}.

niche_id/0

-type niche_id() :: term().

population_snapshot/0

-type population_snapshot() ::
          #{size := non_neg_integer(),
            individuals := [individual_summary()],
            best_fitness := fitness(),
            avg_fitness := fitness(),
            worst_fitness := fitness(),
            species_count => non_neg_integer(),
            generation => pos_integer(),
            extra => map()}.

species_id/0

-type species_id() :: pos_integer().

strategy_action/0

-type strategy_action() ::
          {create_individual, ParentIds :: [individual_id()], Metadata :: map()} |
          {remove_individual, individual_id(), Reason :: death_reason()} |
          {evaluate_individual, individual_id()} |
          {evaluate_batch, [individual_id()]} |
          {update_config, ConfigUpdates :: map()} |
          {migrate_individual, individual_id(), ToIsland :: island_id()} |
          {update_archive, ArchiveUpdate :: term()} |
          {emit_event, lifecycle_event()} |
          noop.

strategy_module/0

-type strategy_module() :: module().

strategy_result/0

-type strategy_result() ::
          {Actions :: [strategy_action()], Events :: [lifecycle_event()], NewState :: strategy_state()}.

strategy_state/0

-type strategy_state() :: term().

timestamp/0

-type timestamp() :: erlang:timestamp().

Callbacks

apply_meta_params/2

-callback apply_meta_params(Params :: meta_params(), State :: strategy_state()) -> strategy_state().

get_archive_state/1

(optional)
-callback get_archive_state(State :: strategy_state()) ->
                               {ok, ArchiveState :: map()} | {error, not_supported}.

get_island_state/2

(optional)
-callback get_island_state(IslandId :: island_id(), State :: strategy_state()) ->
                              {ok, IslandState :: map()} | {error, not_found}.

get_meta_inputs/1

-callback get_meta_inputs(State :: strategy_state()) -> meta_inputs().

get_population_snapshot/1

-callback get_population_snapshot(State :: strategy_state()) -> population_snapshot().

handle_archive_update/3

(optional)
-callback handle_archive_update(IndividualId :: individual_id(),
                                Novelty :: float(),
                                State :: strategy_state()) ->
                                   strategy_result().

handle_evaluation_result/3

-callback handle_evaluation_result(IndividualId :: individual_id(),
                                   FitnessResult :: #{fitness := fitness(), metrics => map()},
                                   State :: strategy_state()) ->
                                      strategy_result().

handle_migration/4

(optional)
-callback handle_migration(IndividualId :: individual_id(),
                           FromIsland :: island_id(),
                           ToIsland :: island_id(),
                           State :: strategy_state()) ->
                              strategy_result().

handle_niche_update/3

(optional)
-callback handle_niche_update(NicheId :: niche_id(),
                              NewIndividualId :: individual_id(),
                              State :: strategy_state()) ->
                                 strategy_result().

init/1

-callback init(Config :: map()) -> {ok, State :: strategy_state()} | {error, Reason :: term()}.

tick/1

-callback tick(State :: strategy_state()) -> strategy_result().

Functions

apply_meta_params(Module, Params, State)

-spec apply_meta_params(Module, Params, State) -> strategy_state()
                           when
                               Module :: strategy_module(),
                               Params :: meta_params(),
                               State :: strategy_state().

Apply meta-controller parameters to strategy.

get_meta_inputs(Module, State)

-spec get_meta_inputs(Module, State) -> meta_inputs()
                         when Module :: strategy_module(), State :: strategy_state().

Get meta-controller inputs from strategy.

get_population_snapshot(Module, State)

-spec get_population_snapshot(Module, State) -> population_snapshot()
                                 when Module :: strategy_module(), State :: strategy_state().

Get population snapshot from strategy.

handle_evaluation_result(Module, IndividualId, FitnessResult, State)

-spec handle_evaluation_result(Module, IndividualId, FitnessResult, State) -> Result
                                  when
                                      Module :: strategy_module(),
                                      IndividualId :: individual_id(),
                                      FitnessResult :: map(),
                                      State :: strategy_state(),
                                      Result :: strategy_result().

Dispatch evaluation result to strategy.

has_callback(Module, Function, Arity)

-spec has_callback(Module, Function, Arity) -> boolean()
                      when Module :: strategy_module(), Function :: atom(), Arity :: non_neg_integer().

Check if a strategy module exports an optional callback.

init(Module, Config)

-spec init(Module, Config) -> Result
              when
                  Module :: strategy_module(),
                  Config :: map(),
                  Result :: {ok, strategy_state()} | {error, term()}.

Initialize a strategy.

tick(Module, State)

-spec tick(Module, State) -> Result
              when Module :: strategy_module(), State :: strategy_state(), Result :: strategy_result().

Dispatch tick to strategy.