Current section

Files

Jump to
macula_neuroevolution include neuroevolution.hrl
Raw

include/neuroevolution.hrl

%% @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>.<event_type>"
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.