neuroevolution_genetic (macula_neuroevolution v0.29.1)
View SourceGenetic operators for neuroevolution.
This module provides crossover and mutation operators for evolving neural networks. Supports two modes:
NEAT Topology Evolution
When individuals have genomes (connection_genes with innovation numbers), uses NEAT-style crossover and structural mutations via genome_factory. This enables topology-evolving networks.
Fixed Topology (Legacy)
For backward compatibility, when individuals don't have genomes, uses weight-only evolution with uniform crossover and perturbation mutation.
Reference: Stanley, K.O. and Miikkulainen, R. (2002). "Evolving Neural Networks through Augmenting Topologies." Evolutionary Computation, 10(2).
Summary
Functions
Create offspring from two parent individuals.
Create offspring using NEAT topology evolution.
Uniform crossover of two weight lists.
Mutate weights with given rate and strength.
Mutate weights with layer-specific rates.
Types
-type fitness() :: float() | undefined.
-type generation() :: non_neg_integer().
-type genome() :: #genome{connection_genes :: [#connection_gene{innovation :: pos_integer() | undefined, from_id :: term(), to_id :: term(), weight :: float(), enabled :: boolean()}], input_count :: non_neg_integer(), hidden_count :: non_neg_integer(), output_count :: non_neg_integer()}.
-type individual() :: #individual{id :: individual_id(), network :: network(), genome :: genome() | undefined, parent1_id :: individual_id() | undefined, parent2_id :: individual_id() | undefined, fitness :: fitness(), metrics :: metrics(), generation_born :: generation(), birth_evaluation :: non_neg_integer(), max_age :: pos_integer(), is_survivor :: boolean(), is_offspring :: boolean()}.
-type individual_id() :: term().
-type metrics() :: map().
-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 network() :: term().
-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 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()}.
Functions
-spec create_offspring(Parent1, Parent2, Config, Generation) -> Offspring when Parent1 :: individual(), Parent2 :: individual(), Config :: neuro_config(), Generation :: generation(), Offspring :: individual().
Create offspring from two parent individuals.
Automatically selects the appropriate mode: - NEAT mode: When both parents have genomes and topology_mutation_config is set - Legacy mode: Weight-only evolution with uniform crossover
For NEAT mode: 1. Determine fitter parent 2. Perform NEAT crossover (gene alignment by innovation number) 3. Apply structural and weight mutations 4. Convert genome to network for evaluation
For Legacy mode: 1. Extract weights from both parent networks 2. Perform uniform crossover 3. Apply mutation 4. Create new network with child weights
Returns a new individual record with lineage tracking.
-spec create_offspring_neat(Parent1, Parent2, Config, Generation) -> Offspring when Parent1 :: individual(), Parent2 :: individual(), Config :: neuro_config(), Generation :: generation(), Offspring :: individual().
Create offspring using NEAT topology evolution.
Uses genome_factory to perform NEAT-style crossover and mutation. Both parents must have genomes for this to work.
-spec crossover_uniform(Weights1, Weights2) -> ChildWeights when Weights1 :: [float()], Weights2 :: [float()], ChildWeights :: [float()].
Uniform crossover of two weight lists.
For each weight position, randomly selects from either parent with equal probability (50/50).
Both weight lists must be the same length.
Example: Parent1 = [1.0, 2.0, 3.0, 4.0], Parent2 = [5.0, 6.0, 7.0, 8.0], %% Might produce: [1.0, 6.0, 3.0, 8.0] Child = neuroevolution_genetic:crossover_uniform(Parent1, Parent2).
-spec mutate_weights(Weights, MutationRate, MutationStrength) -> MutatedWeights when Weights :: [float()], MutationRate :: float(), MutationStrength :: float(), MutatedWeights :: [float()].
Mutate weights with given rate and strength.
Each weight has MutationRate probability of being perturbed. When mutated, a random value in [-Strength, +Strength] is added.
Example: Weights = [1.0, 2.0, 3.0], Rate = 0.1, %% 10% of weights mutated Strength = 0.3, %% Changes up to +/- 0.3 Mutated = neuroevolution_genetic:mutate_weights(Weights, Rate, Strength).
-spec mutate_weights_layered(Weights, Topology, ReservoirParams, ReadoutParams, FallbackParams) -> MutatedWeights when Weights :: [float()], Topology :: {pos_integer(), [pos_integer()], pos_integer()}, ReservoirParams :: {float(), float()} | undefined, ReadoutParams :: {float(), float()} | undefined, FallbackParams :: {float(), float()}, MutatedWeights :: [float()].
Mutate weights with layer-specific rates.
Applies different mutation rates to reservoir (hidden) and readout (output) layers. The reservoir typically benefits from lower mutation rates for stability, while the readout can adapt faster with higher rates.
Reference: See guides/training-strategies.md for rationale.
Example: Weights = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6], %% 4 reservoir, 2 readout Topology = {2, [2], 2}, %% 2 inputs, 2 hidden, 2 outputs ReservoirParams = {0.05, 0.2}, %% Low rate for reservoir ReadoutParams = {0.20, 0.5}, %% High rate for readout Mutated = neuroevolution_genetic:mutate_weights_layered( Weights, Topology, ReservoirParams, ReadoutParams ).