island_strategy (macula_neuroevolution v0.29.0)
View SourceIsland 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
-type birth_origin() :: initial | crossover | mutation | migration | insertion.
-type death_reason() ::
selection_pressure | stagnation | age_limit | niche_competition | migration |
population_limit | extinction.
-type fitness() :: float() | undefined.
-type individual_id() :: term().
-type individual_summary() :: #{id := individual_id(), fitness := fitness(), is_survivor => boolean(), is_offspring => boolean(), species_id => species_id(), age => non_neg_integer()}.
-type island_id() :: pos_integer() | atom().
-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()}.
-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()}.
-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()}.
-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}.
-type niche_id() :: term().
-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()}.
-type self_play_config() :: #self_play_config{enabled :: boolean(), archive_size :: pos_integer(), archive_threshold :: float() | auto, min_fitness_percentile :: float()}.
-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()}.
-type species_id() :: pos_integer().
-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.
-type strategy_module() :: module().
-type timestamp() :: erlang:timestamp().
Functions
-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.
-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.
-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.
-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.
-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.
-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.
-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.