%% @doc Lifecycle event record definitions for evolution strategies. %% %% These events provide a universal vocabulary for all evolution strategies. %% The UI and meta-controllers subscribe to these events rather than %% strategy-specific mechanisms like "generation_complete". %% %% == Universal Events (all strategies emit these) == %% %% - `individual_born' - When any individual is created (initial, offspring, migrant) %% - `individual_died' - When any individual is removed from the population %% - `individual_evaluated' - When an individual's fitness is computed %% - `species_emerged' - When a new species is formed (if speciation enabled) %% - `species_extinct' - When a species dies out %% %% == Strategy-Specific Events == %% %% Generational: %% - `cohort_evaluated' - All individuals in generation evaluated %% - `breeding_complete' - Selection and breeding finished %% - `generation_advanced' - New generation begins %% %% Steady-State: %% - (uses individual_born/individual_died directly, no cohort events) %% %% Island Model: %% - `island_migration' - Individual migrated between islands %% - `island_topology_changed' - Island connectivity changed %% %% Novelty Search / MAP-Elites: %% - `archive_updated' - Novelty archive changed %% - `niche_discovered' - New behavioral niche found %% - `niche_updated' - Better individual found for niche %% %% @author Macula.io %% @copyright 2025 Macula.io -ifndef(LIFECYCLE_EVENTS_HRL). -define(LIFECYCLE_EVENTS_HRL, true). %% Include neuroevolution.hrl for individual_id() and species_id() types -include("neuroevolution.hrl"). %%% ============================================================================ %%% Type Definitions %%% ============================================================================ %% Note: individual_id() and species_id() are defined in neuroevolution.hrl %% We only define types specific to lifecycle events here. -type island_id() :: pos_integer() | atom(). -type niche_id() :: term(). -type timestamp() :: erlang:timestamp(). %% Birth origins - how the individual came to exist -type birth_origin() :: initial | % Created at population initialization crossover | % Sexual reproduction from two parents mutation | % Asexual mutation of single parent migration | % Migrated from another island insertion. % Externally inserted (e.g., from archive) %% Death reasons - why the individual was removed -type death_reason() :: selection_pressure | % Lost in fitness-based selection stagnation | % Removed due to lack of improvement age_limit | % Exceeded maximum age niche_competition | % Replaced by better individual in same niche migration | % Migrated to another island (died here, born there) population_limit | % Population size exceeded maximum extinction. % Entire species/island eliminated %%% ============================================================================ %%% Universal Lifecycle Events %%% ============================================================================ %% @doc An individual has been created and added to the population. %% %% This event is emitted by ALL strategies when any individual is created, %% regardless of how (initial population, breeding, migration, etc.). -record(individual_born, { %% Unique identifier for the new individual id :: individual_id(), %% Parent IDs (empty for initial population, 1 for mutation, 2 for crossover) parent_ids = [] :: [individual_id()], %% When this individual was born timestamp :: timestamp(), %% How this individual was created origin :: birth_origin(), %% Strategy-specific metadata %% May include: island_id, niche_id, mutation_type, species_id, etc. metadata = #{} :: map() }). -type individual_born() :: #individual_born{}. %% @doc An individual has been removed from the population. %% %% This event is emitted by ALL strategies when any individual is removed, %% regardless of why (selection, stagnation, migration out, etc.). -record(individual_died, { %% Identifier of the deceased individual id :: individual_id(), %% Why this individual was removed reason :: death_reason(), %% Final fitness at time of death (undefined if never evaluated) final_fitness :: float() | undefined, %% When this individual died timestamp :: timestamp(), %% Strategy-specific metadata metadata = #{} :: map() }). -type individual_died() :: #individual_died{}. %% @doc An individual has been evaluated and assigned fitness. %% %% Emitted after fitness evaluation completes for any individual. -record(individual_evaluated, { %% Which individual was evaluated id :: individual_id(), %% The computed fitness value fitness :: float(), %% Additional metrics from evaluation metrics = #{} :: map(), %% When evaluation completed timestamp :: timestamp(), %% Strategy-specific metadata (evaluator node, batch info, etc.) metadata = #{} :: map() }). -type individual_evaluated() :: #individual_evaluated{}. %%% ============================================================================ %%% Species Lifecycle Events %%% ============================================================================ %% @doc A new species has emerged from speciation. %% %% Emitted when an individual is sufficiently different from existing %% species to warrant creating a new one. -record(species_emerged, { %% Unique identifier for the new species species_id :: species_id(), %% The individual that founded this species (becomes representative) founder_id :: individual_id(), %% If this species split from an existing one, its ID (undefined for novel) parent_species_id :: species_id() | undefined, %% When this species was created timestamp :: timestamp(), %% Initial compatibility threshold, representative genome info, etc. metadata = #{} :: map() }). -type species_emerged() :: #species_emerged{}. %% @doc A species has gone extinct. %% %% Emitted when a species has no remaining members or is eliminated %% due to stagnation. -record(species_extinct, { %% Which species died species_id :: species_id(), %% Why this species went extinct reason :: stagnation | empty | merged | eliminated, %% Final statistics at time of extinction final_stats :: map(), %% When this species went extinct timestamp :: timestamp() }). -type species_extinct() :: #species_extinct{}. %%% ============================================================================ %%% Generational Strategy Events %%% ============================================================================ %% @doc All individuals in the current cohort have been evaluated. %% %% Specific to generational strategies. Marks the end of the evaluation phase. -record(cohort_evaluated, { %% Which generation completed evaluation generation :: pos_integer(), %% Statistics for the evaluated cohort best_fitness :: float(), avg_fitness :: float(), worst_fitness :: float(), %% Population size population_size :: pos_integer(), timestamp :: timestamp() }). -type cohort_evaluated() :: #cohort_evaluated{}. %% @doc Selection and breeding have completed for a generation. %% %% Specific to generational strategies. Contains information about %% survivors, eliminated, and new offspring. -record(breeding_complete, { generation :: pos_integer(), %% How many survived selection survivor_count :: non_neg_integer(), %% How many were eliminated eliminated_count :: non_neg_integer(), %% How many offspring were created offspring_count :: non_neg_integer(), timestamp :: timestamp() }). -type breeding_complete() :: #breeding_complete{}. %%% ============================================================================ %%% Steady-State Strategy Events %%% ============================================================================ %% @doc A replacement cycle has occurred in steady-state evolution. %% %% Emitted when one or more individuals have been replaced. -record(steady_state_replacement, { %% IDs of individuals that were replaced replaced_ids :: [individual_id()], %% IDs of the new offspring that replaced them offspring_ids :: [individual_id()], %% Current population statistics best_fitness :: float() | undefined, avg_fitness :: float() | undefined, timestamp :: timestamp() }). -type steady_state_replacement() :: #steady_state_replacement{}. %% @doc A new generation is beginning. %% %% Replaces the old "generation_complete" event. This marks the transition %% to a new generation after breeding is complete. -record(generation_advanced, { %% The new generation number generation :: pos_integer(), %% Summary of what happened in the previous generation previous_best_fitness :: float(), previous_avg_fitness :: float(), %% Current population size population_size :: pos_integer(), %% Species count (if speciation enabled) species_count :: non_neg_integer(), timestamp :: timestamp() }). -type generation_advanced() :: #generation_advanced{}. %%% ============================================================================ %%% Island Model Events %%% ============================================================================ %% @doc An individual migrated from one island to another. -record(island_migration, { %% Which individual migrated individual_id :: individual_id(), %% Source and destination islands from_island :: island_id(), to_island :: island_id(), %% Fitness at time of migration fitness :: float(), timestamp :: timestamp() }). -type island_migration() :: #island_migration{}. %% @doc The island topology has changed. %% %% Emitted when islands are added, removed, or connectivity changes. -record(island_topology_changed, { %% Current island IDs islands :: [island_id()], %% Connectivity graph (which islands can exchange with which) connections :: [{island_id(), island_id()}], %% What changed change_type :: island_added | island_removed | connection_changed, timestamp :: timestamp() }). -type island_topology_changed() :: #island_topology_changed{}. %%% ============================================================================ %%% Quality-Diversity Events (Novelty Search, MAP-Elites) %%% ============================================================================ %% @doc A new behavioral niche has been discovered. %% %% Emitted in MAP-Elites when an individual occupies a previously empty cell. -record(niche_discovered, { %% Identifier for this niche (grid coordinates, behavior hash, etc.) niche_id :: niche_id(), %% The behavior descriptor that defines this niche behavior_descriptor :: [float()], %% The individual that discovered this niche individual_id :: individual_id(), %% Fitness of the discoverer fitness :: float(), timestamp :: timestamp() }). -type niche_discovered() :: #niche_discovered{}. %% @doc A niche now has a better occupant. %% %% Emitted in MAP-Elites when a new individual beats the current elite. -record(niche_updated, { %% Which niche was updated niche_id :: niche_id(), %% The old and new occupants old_individual_id :: individual_id(), new_individual_id :: individual_id(), %% Fitness improvement old_fitness :: float(), new_fitness :: float(), improvement :: float(), timestamp :: timestamp() }). -type niche_updated() :: #niche_updated{}. %% @doc The novelty/QD archive has been updated. %% %% Emitted periodically with aggregate archive statistics. -record(archive_updated, { %% Total individuals in archive size :: non_neg_integer(), %% Fraction of niches occupied (MAP-Elites) coverage :: float(), %% Quality-Diversity score (sum of elite fitnesses) qd_score :: float(), %% Number of updates since last event updates_since_last :: non_neg_integer(), timestamp :: timestamp() }). -type archive_updated() :: #archive_updated{}. %%% ============================================================================ %%% Coevolution Events %%% ============================================================================ %% @doc A competitor population has been updated. -record(competitor_updated, { %% Which competitor population changed competitor_id :: term(), %% Summary of change change_type :: generation_advanced | champion_changed | strategy_shift, %% Current champion fitness (if applicable) champion_fitness :: float() | undefined, timestamp :: timestamp() }). -type competitor_updated() :: #competitor_updated{}. %% @doc An arms race event occurred in coevolution. %% %% Tracks competitive dynamics like Red Queen effects. -record(arms_race_event, { %% What type of arms race event event_type :: fitness_surge | counter_adaptation | stalemate | breakthrough, %% Which populations are involved populations :: [term()], %% Metrics about the competitive dynamics metrics :: map(), timestamp :: timestamp() }). -type arms_race_event() :: #arms_race_event{}. %%% ============================================================================ %%% Open-Ended Evolution Events %%% ============================================================================ %% @doc A new capability has emerged. -record(capability_emerged, { %% Identifier for this capability capability_id :: term(), %% Description of what this capability enables description :: binary(), %% The individual(s) that first exhibited this capability exhibitors :: [individual_id()], timestamp :: timestamp() }). -type capability_emerged() :: #capability_emerged{}. %% @doc Complexity has increased significantly. -record(complexity_increased, { %% What complexity metric increased metric :: genome_size | network_depth | behavior_repertoire | term(), %% Previous and new values old_value :: number(), new_value :: number(), %% Percentage increase increase_pct :: float(), timestamp :: timestamp() }). -type complexity_increased() :: #complexity_increased{}. %%% ============================================================================ %%% Aggregate Event Type %%% ============================================================================ %% Union type for all lifecycle events -type lifecycle_event() :: #individual_born{} | #individual_died{} | #individual_evaluated{} | #species_emerged{} | #species_extinct{} | #cohort_evaluated{} | #breeding_complete{} | #steady_state_replacement{} | #generation_advanced{} | #island_migration{} | #island_topology_changed{} | #niche_discovered{} | #niche_updated{} | #archive_updated{} | #competitor_updated{} | #arms_race_event{} | #capability_emerged{} | #complexity_increased{}. -endif.