island_strategy (macula_neuroevolution v0.29.0)

View Source

Island model evolution strategy implementation.

The island model runs multiple isolated subpopulations (islands) in parallel, with periodic migration of individuals between islands. This maintains diversity and enables exploration of multiple fitness peaks simultaneously.

Key features: - Multiple islands, each running its own sub-strategy - Configurable migration topology (ring, full, random, custom) - Various migrant selection methods (best, random, diverse) - Periodic migration events based on evaluation count

Migration topologies: - ring: Each island sends to the next (circular) - full: Every island can send to every other island - random: Random destination for each migration - custom: User-specified connections

Summary

Functions

Apply parameter updates from meta-controller.

Get normalized inputs for meta-controller.

Get a snapshot of the population across all islands.

Handle an individual evaluation result.

Initialize the island model strategy.

Clean up when strategy terminates.

Periodic tick for maintenance operations.

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().

island_params/0

-type island_params() ::
          #island_params{island_count :: pos_integer(),
                         population_per_island :: pos_integer(),
                         migration_interval :: pos_integer(),
                         migration_count :: pos_integer(),
                         migration_selection :: best | random | diverse,
                         topology :: ring | full | random | custom,
                         custom_connections :: [{island_id(), island_id()}],
                         island_strategy :: strategy_module(),
                         island_strategy_params :: map()}.

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()}.

mutation_config/0

-type mutation_config() ::
          #mutation_config{weight_mutation_rate :: float(),
                           weight_perturb_rate :: float(),
                           weight_perturb_strength :: float(),
                           add_node_rate :: float(),
                           add_connection_rate :: float(),
                           toggle_connection_rate :: float(),
                           add_sensor_rate :: float(),
                           add_actuator_rate :: float(),
                           mutate_neuron_type_rate :: float(),
                           mutate_time_constant_rate :: float()}.

neuro_config/0

-type neuro_config() ::
          #neuro_config{population_size :: pos_integer(),
                        evaluations_per_individual :: pos_integer(),
                        selection_ratio :: float(),
                        mutation_rate :: float(),
                        mutation_strength :: float(),
                        reservoir_mutation_rate :: float() | undefined,
                        reservoir_mutation_strength :: float() | undefined,
                        readout_mutation_rate :: float() | undefined,
                        readout_mutation_strength :: float() | undefined,
                        topology_mutation_config :: mutation_config() | undefined,
                        max_evaluations :: pos_integer() | infinity,
                        max_generations :: pos_integer() | infinity,
                        target_fitness :: float() | undefined,
                        network_topology :: {pos_integer(), [pos_integer()], pos_integer()},
                        evaluator_module :: module(),
                        evaluator_options :: map(),
                        event_handler :: {module(), term()} | undefined,
                        meta_controller_config :: term() | undefined,
                        speciation_config :: speciation_config() | undefined,
                        self_play_config :: self_play_config() | undefined,
                        realm :: binary(),
                        publish_events :: boolean(),
                        evaluation_mode :: direct | distributed | mesh,
                        mesh_config :: map() | undefined,
                        evaluation_timeout :: pos_integer(),
                        max_concurrent_evaluations :: pos_integer() | undefined,
                        strategy_config :: term() | undefined,
                        lc_chain_config :: term() | undefined,
                        checkpoint_interval :: pos_integer() | undefined,
                        checkpoint_config :: map() | undefined}.

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()}.

self_play_config/0

-type self_play_config() ::
          #self_play_config{enabled :: boolean(),
                            archive_size :: pos_integer(),
                            archive_threshold :: float() | auto,
                            min_fitness_percentile :: float()}.

speciation_config/0

-type speciation_config() ::
          #speciation_config{enabled :: boolean(),
                             compatibility_threshold :: float(),
                             c1_excess :: float(),
                             c2_disjoint :: float(),
                             c3_weight_diff :: float(),
                             target_species :: pos_integer(),
                             threshold_adjustment_rate :: float(),
                             min_species_size :: pos_integer(),
                             max_stagnation :: non_neg_integer(),
                             species_elitism :: float(),
                             interspecies_mating_rate :: float()}.

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().

timestamp/0

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

Functions

apply_meta_params(MetaParams, Island_state)

-spec apply_meta_params(MetaParams :: map(),
                        State ::
                            #island_state{config :: neuro_config(),
                                          params :: island_params(),
                                          network_factory :: module(),
                                          islands ::
                                              #{island_id() =>
                                                    #island{id :: island_id(),
                                                            strategy_module :: module(),
                                                            strategy_state :: term(),
                                                            evaluations_since_migration ::
                                                                non_neg_integer()}},
                                          topology :: #{island_id() => [island_id()]},
                                          total_evaluations :: non_neg_integer(),
                                          best_fitness_ever :: float()}) ->
                           #island_state{config :: neuro_config(),
                                         params :: island_params(),
                                         network_factory :: module(),
                                         islands ::
                                             #{island_id() =>
                                                   #island{id :: island_id(),
                                                           strategy_module :: module(),
                                                           strategy_state :: term(),
                                                           evaluations_since_migration ::
                                                               non_neg_integer()}},
                                         topology :: #{island_id() => [island_id()]},
                                         total_evaluations :: non_neg_integer(),
                                         best_fitness_ever :: float()}.

Apply parameter updates from meta-controller.

get_meta_inputs(Island_state)

-spec get_meta_inputs(State ::
                          #island_state{config :: neuro_config(),
                                        params :: island_params(),
                                        network_factory :: module(),
                                        islands ::
                                            #{island_id() =>
                                                  #island{id :: island_id(),
                                                          strategy_module :: module(),
                                                          strategy_state :: term(),
                                                          evaluations_since_migration ::
                                                              non_neg_integer()}},
                                        topology :: #{island_id() => [island_id()]},
                                        total_evaluations :: non_neg_integer(),
                                        best_fitness_ever :: float()}) ->
                         [float()].

Get normalized inputs for meta-controller.

get_population_snapshot(Island_state)

-spec get_population_snapshot(State ::
                                  #island_state{config :: neuro_config(),
                                                params :: island_params(),
                                                network_factory :: module(),
                                                islands ::
                                                    #{island_id() =>
                                                          #island{id :: island_id(),
                                                                  strategy_module :: module(),
                                                                  strategy_state :: term(),
                                                                  evaluations_since_migration ::
                                                                      non_neg_integer()}},
                                                topology :: #{island_id() => [island_id()]},
                                                total_evaluations :: non_neg_integer(),
                                                best_fitness_ever :: float()}) ->
                                 population_snapshot().

Get a snapshot of the population across all islands.

handle_evaluation_result(IndividualId, FitnessResult, State)

-spec handle_evaluation_result(IndividualId, FitnessResult, State) -> Result
                                  when
                                      IndividualId :: individual_id(),
                                      FitnessResult :: #{fitness := float(), metrics => map()},
                                      State ::
                                          #island_state{config :: neuro_config(),
                                                        params :: island_params(),
                                                        network_factory :: module(),
                                                        islands ::
                                                            #{island_id() =>
                                                                  #island{id :: island_id(),
                                                                          strategy_module :: module(),
                                                                          strategy_state :: term(),
                                                                          evaluations_since_migration ::
                                                                              non_neg_integer()}},
                                                        topology :: #{island_id() => [island_id()]},
                                                        total_evaluations :: non_neg_integer(),
                                                        best_fitness_ever :: float()},
                                      Result ::
                                          {[strategy_action()],
                                           [lifecycle_event()],
                                           #island_state{config :: neuro_config(),
                                                         params :: island_params(),
                                                         network_factory :: module(),
                                                         islands ::
                                                             #{island_id() =>
                                                                   #island{id :: island_id(),
                                                                           strategy_module :: module(),
                                                                           strategy_state :: term(),
                                                                           evaluations_since_migration ::
                                                                               non_neg_integer()}},
                                                         topology :: #{island_id() => [island_id()]},
                                                         total_evaluations :: non_neg_integer(),
                                                         best_fitness_ever :: float()}}.

Handle an individual evaluation result.

Routes the result to the appropriate island based on individual ID.

init(Config)

-spec init(Config :: map()) ->
              {ok,
               #island_state{config :: neuro_config(),
                             params :: island_params(),
                             network_factory :: module(),
                             islands ::
                                 #{island_id() =>
                                       #island{id :: island_id(),
                                               strategy_module :: module(),
                                               strategy_state :: term(),
                                               evaluations_since_migration :: non_neg_integer()}},
                             topology :: #{island_id() => [island_id()]},
                             total_evaluations :: non_neg_integer(),
                             best_fitness_ever :: float()},
               [lifecycle_event()]}.

Initialize the island model strategy.

terminate(Reason, Island_state)

-spec terminate(Reason :: term(),
                State ::
                    #island_state{config :: neuro_config(),
                                  params :: island_params(),
                                  network_factory :: module(),
                                  islands ::
                                      #{island_id() =>
                                            #island{id :: island_id(),
                                                    strategy_module :: module(),
                                                    strategy_state :: term(),
                                                    evaluations_since_migration :: non_neg_integer()}},
                                  topology :: #{island_id() => [island_id()]},
                                  total_evaluations :: non_neg_integer(),
                                  best_fitness_ever :: float()}) ->
                   ok.

Clean up when strategy terminates.

tick(Island_state)

-spec tick(State ::
               #island_state{config :: neuro_config(),
                             params :: island_params(),
                             network_factory :: module(),
                             islands ::
                                 #{island_id() =>
                                       #island{id :: island_id(),
                                               strategy_module :: module(),
                                               strategy_state :: term(),
                                               evaluations_since_migration :: non_neg_integer()}},
                             topology :: #{island_id() => [island_id()]},
                             total_evaluations :: non_neg_integer(),
                             best_fitness_ever :: float()}) ->
              {[strategy_action()],
               [lifecycle_event()],
               #island_state{config :: neuro_config(),
                             params :: island_params(),
                             network_factory :: module(),
                             islands ::
                                 #{island_id() =>
                                       #island{id :: island_id(),
                                               strategy_module :: module(),
                                               strategy_state :: term(),
                                               evaluations_since_migration :: non_neg_integer()}},
                             topology :: #{island_id() => [island_id()]},
                             total_evaluations :: non_neg_integer(),
                             best_fitness_ever :: float()}}.

Periodic tick for maintenance operations.