%% @doc Neuroevolution record definitions %% @author Macula.io %% @copyright 2025 Macula.io -ifndef(NEUROEVOLUTION_HRL). -define(NEUROEVOLUTION_HRL, true). %%% ============================================================================ %%% Types %%% ============================================================================ -type individual_id() :: term(). -type generation() :: pos_integer(). -type fitness() :: float(). -type network() :: term(). %% network_evaluator:network() -type metrics() :: map(). %%% ============================================================================ %%% Individual Record %%% ============================================================================ %% @doc Represents a single individual in the population. %% Each individual has a neural network and fitness metrics. -record(individual, { %% Unique identifier for this individual id :: individual_id(), %% Neural network (from network_evaluator) network :: network(), %% Lineage tracking parent1_id :: individual_id() | undefined, parent2_id :: individual_id() | undefined, %% Fitness and performance metrics fitness = 0.0 :: fitness(), metrics = #{} :: metrics(), %% Generation this individual was created in generation_born = 1 :: generation(), %% Flags for visualization/tracking is_survivor = false :: boolean(), is_offspring = false :: boolean() }). -type individual() :: #individual{}. %%% ============================================================================ %%% Speciation Records %%% ============================================================================ -type species_id() :: pos_integer(). %% @doc Represents a species - a cluster of genetically similar individuals. %% %% Species enable niching: individuals compete primarily within their species, %% allowing diverse strategies to coexist and explore different fitness peaks. -record(species, { %% Unique species identifier id :: species_id(), %% Representative individual (used for compatibility comparisons) %% New individuals are compared against this to determine species membership representative :: individual(), %% Member individual IDs in this species members = [] :: [individual_id()], %% Best fitness achieved by any member this generation best_fitness = 0.0 :: fitness(), %% Best fitness ever achieved by this species best_fitness_ever = 0.0 :: fitness(), %% Generation this species was created generation_created :: generation(), %% Number of generations this species has existed age = 0 :: non_neg_integer(), %% Number of generations without fitness improvement stagnant_generations = 0 :: non_neg_integer(), %% Offspring quota for next generation (based on relative fitness) offspring_quota = 0 :: non_neg_integer() }). -type species() :: #species{}. %% @doc Configuration for speciation behavior. -record(speciation_config, { %% Enable/disable speciation (default: disabled for backwards compatibility) enabled = false :: boolean(), %% Compatibility threshold - individuals with distance below this are same species %% Lower values = more species, higher values = fewer species compatibility_threshold = 3.0 :: float(), %% Weight coefficients for compatibility distance calculation %% Distance = c1*W + c2*excess_genes + c3*disjoint_genes %% For fixed-topology networks, we only use weight difference (c1) c1_weight_diff = 1.0 :: float(), %% Target number of species (threshold adjusts dynamically to maintain) target_species = 5 :: pos_integer(), %% How much to adjust threshold when species count is off target threshold_adjustment_rate = 0.1 :: float(), %% Minimum species size (species below this may be eliminated) min_species_size = 2 :: pos_integer(), %% Maximum generations a species can stagnate before elimination %% (0 = never eliminate for stagnation) max_stagnation = 15 :: non_neg_integer(), %% Fraction of each species that survives selection species_elitism = 0.20 :: float(), %% Probability of interspecies breeding (usually low) interspecies_mating_rate = 0.001 :: float() }). -type speciation_config() :: #speciation_config{}. %% @doc Event recording species lifecycle changes. -record(species_event, { generation :: generation(), species_id :: species_id(), event_type :: species_created | species_extinct | species_stagnant | champion_emerged, details :: map() }). -type species_event() :: #species_event{}. %%% ============================================================================ %%% Configuration Record %%% ============================================================================ %% @doc Configuration for the neuroevolution server. %% Controls population size, selection, mutation, and evaluation. -record(neuro_config, { %% Population size (number of individuals) population_size = 50 :: pos_integer(), %% Number of evaluations per individual per generation evaluations_per_individual = 10 :: pos_integer(), %% Selection ratio (fraction of population that survives) %% e.g., 0.20 means top 20% survive selection_ratio = 0.20 :: float(), %% Mutation parameters mutation_rate = 0.10 :: float(), %% Probability of mutating each weight mutation_strength = 0.3 :: float(), %% Magnitude of weight perturbation %% Maximum generations (infinity for unlimited) max_generations = infinity :: pos_integer() | infinity, %% Target fitness threshold (undefined for no threshold) %% Training stops when best fitness reaches or exceeds this value target_fitness = undefined :: float() | undefined, %% Network topology: {InputSize, HiddenLayers, OutputSize} %% e.g., {42, [16, 8], 6} for 42 inputs, 2 hidden layers (16, 8), 6 outputs network_topology :: {pos_integer(), [pos_integer()], pos_integer()}, %% Evaluator module (must implement neuroevolution_evaluator behaviour) evaluator_module :: module(), %% Options passed to evaluator evaluator_options = #{} :: map(), %% Event handler for notifications: {Module, InitArg} | undefined %% Module must export handle_event/2 %% DEPRECATED: Use event publishing instead (neuroevolution_events) event_handler :: {module(), term()} | undefined, %% Meta-controller configuration: meta_config() | undefined %% When set, an LTC meta-controller will dynamically adjust hyperparameters %% See meta_controller.hrl for meta_config record definition meta_controller_config :: term() | undefined, %% Speciation configuration: speciation_config() | undefined %% When set, enables NEAT-style speciation for niche-based evolution speciation_config :: speciation_config() | undefined, %% Realm for multi-tenancy and event topic scoping %% Events are published to "neuro.." realm = <<"default">> :: binary(), %% Whether to publish events via neuroevolution_events %% When true, events are published to topics in addition to (or instead of) callbacks publish_events = false :: boolean(), %% Evaluation mode: direct | distributed %% - direct: Call evaluator module directly (default, single-node) %% - distributed: Publish evaluate_request events, await results via events %% Requires evaluator workers subscribed to the realm's evaluate topic evaluation_mode = direct :: direct | distributed, %% Timeout for distributed evaluation (milliseconds) %% How long to wait for all evaluation results before timing out evaluation_timeout = 30000 :: pos_integer() }). -type neuro_config() :: #neuro_config{}. %%% ============================================================================ %%% Statistics Records %%% ============================================================================ %% @doc Statistics for a completed generation. -record(generation_stats, { %% Generation number generation :: generation(), %% Fitness metrics best_fitness :: fitness(), avg_fitness :: fitness(), worst_fitness :: fitness(), %% Best individual ID best_individual_id :: individual_id(), %% Lists of individual IDs survivors :: [individual_id()], eliminated :: [individual_id()], offspring :: [individual_id()] }). -type generation_stats() :: #generation_stats{}. %% @doc Records a breeding event (two parents producing offspring). -record(breeding_event, { parent1_id :: individual_id(), parent2_id :: individual_id(), child_id :: individual_id(), generation :: generation() }). -type breeding_event() :: #breeding_event{}. %%% ============================================================================ %%% Server State Record %%% ============================================================================ %% @doc Internal state for neuroevolution_server. -record(neuro_state, { %% Server identifier id :: term(), %% Configuration config :: neuro_config(), %% Current population population = [] :: [individual()], %% Current generation number generation = 1 :: generation(), %% Running state running = false :: boolean(), evaluating = false :: boolean(), %% Progress tracking games_completed = 0 :: non_neg_integer(), total_games = 0 :: non_neg_integer(), %% Historical stats best_fitness_ever = 0.0 :: fitness(), last_gen_best = 0.0 :: fitness(), last_gen_avg = 0.0 :: fitness(), generation_history = [] :: [{generation(), fitness(), fitness()}], %% Last generation results last_gen_results :: map() | undefined, breeding_events = [] :: [breeding_event()], %% Evaluation task reference eval_task :: reference() | undefined, %% Meta-controller process (LTC-based hyperparameter optimizer) %% When active, this gen_server dynamically adjusts hyperparameters meta_controller :: pid() | undefined, %% Speciation state species = [] :: [species()], species_events = [] :: [species_event()], next_species_id = 1 :: species_id(), %% Distributed evaluation state %% Maps request_id => {individual_id, individual_record} pending_evaluations = #{} :: #{reference() => {individual_id(), individual()}}, %% Timer ref for evaluation timeout eval_timeout_ref :: reference() | undefined }). -type neuro_state() :: #neuro_state{}. %%% ============================================================================ %%% Event Types %%% ============================================================================ %% Events sent to event_handler -type neuro_event() :: {generation_started, generation()} | {evaluation_progress, generation(), non_neg_integer(), non_neg_integer()} | {generation_complete, generation_stats()} | {training_started, neuro_config()} | {training_stopped, generation()} | {training_complete, training_result()} | {species_created, species_event()} | {species_extinct, species_event()} | {species_stagnant, species_event()} | {speciation_update, [species()]}. %% @doc Training completion result. %% Contains the reason for stopping and the best individual found. -type training_result() :: #{ reason := target_fitness_reached | max_generations_reached | stopped, generation := generation(), best_fitness := fitness(), best_individual := individual() }. -endif.