Current section

Files

Jump to
macula_neuroevolution include meta_controller.hrl
Raw

include/meta_controller.hrl

%% @doc Meta-Controller record definitions for LTC-based hyperparameter optimization
%%
%% This module implements a meta-learning system that uses Liquid Time-Constant
%% (LTC) networks to dynamically control neuroevolution hyperparameters.
%%
%% The meta-controller operates at a higher timescale than the task networks,
%% learning to "optimize the optimizer" by adapting parameters based on
%% training dynamics.
%%
%% @author Macula.io
%% @copyright 2025 Macula.io
-ifndef(META_CONTROLLER_HRL).
-define(META_CONTROLLER_HRL, true).
%%% ============================================================================
%%% Types
%%% ============================================================================
-type meta_param() :: mutation_rate | mutation_strength | selection_ratio |
%% Resource-aware parameters
evaluations_per_individual | max_concurrent_evaluations |
population_size |
%% Topology control parameters
add_node_rate | add_connection_rate | complexity_penalty.
-type reward_component() :: convergence_speed | final_fitness | efficiency_ratio |
diversity_aware | normative_structure.
%%% ============================================================================
%%% Configuration Record
%%% ============================================================================
%% @doc Configuration for the meta-controller.
%%
%% Controls the LTC network architecture and reward signal composition.
%%
%% Inputs (11 total):
%% 1-8: Evolution metrics (fitness, improvement, variance, stagnation, progress, trend, diversity, entropy)
%% 9-11: Resource metrics (memory_pressure, cpu_pressure, process_pressure)
%%
%% Outputs (5 base, more if topology/population control enabled):
%% 1-3: Base evolution params (mutation_rate, mutation_strength, selection_ratio)
%% 4-5: Resource-aware params (evaluations_per_individual, max_concurrent_evaluations)
%% 6-8: Topology params if control_topology=true (add_node_rate, add_connection_rate, complexity_penalty)
%% 9: Population size if control_population_size=true
-record(meta_config, {
%% Network topology: {Inputs, HiddenLayers, Outputs}
%% Default: {11, [24, 16, 8], 5} - 11 inputs (8 evolution + 3 resource), 5 outputs (3 base + 2 resource)
network_topology = {11, [24, 16, 8], 5} :: {pos_integer(), [pos_integer()], pos_integer()},
%% LTC neuron type: cfc (fast) or ltc (accurate ODE)
neuron_type = cfc :: ltc | cfc,
%% Base time constant for meta-controller neurons
%% Higher values = slower adaptation = more stable
time_constant = 50.0 :: float(),
%% State bound for LTC neurons
state_bound = 1.0 :: float(),
%% Reward component weights (must sum to ~1.0)
reward_weights = #{
convergence_speed => 0.25,
final_fitness => 0.25,
efficiency_ratio => 0.20,
diversity_aware => 0.15,
normative_structure => 0.15
} :: #{reward_component() => float()},
%% Learning rate for gradient-based meta-training
learning_rate = 0.001 :: float(),
%% Parameter bounds: {ParamName, {Min, Max}}
param_bounds = #{
%% Base evolution parameters
mutation_rate => {0.01, 0.5},
mutation_strength => {0.05, 1.0},
selection_ratio => {0.10, 0.50},
%% Resource-aware parameters (always controlled)
evaluations_per_individual => {1.0, 20.0}, %% 1 = emergency low-memory mode
max_concurrent_evaluations => {1.0, 50.0}, %% Limits parallelism under load
%% Population control (when control_population_size=true)
population_size => {10.0, 200.0},
%% Topology control bounds (NEAT mode)
add_node_rate => {0.0, 0.10},
add_connection_rate => {0.0, 0.20},
complexity_penalty => {0.0, 0.5}
} :: #{meta_param() => {float(), float()}},
%% Whether to include population_size as a controllable parameter
%% (disabled by default as it affects computation more than other params)
control_population_size = false :: boolean(),
%% Whether to control topology mutation rates (NEAT mode)
%% When true, adds 3 outputs: add_node_rate, add_connection_rate, complexity_penalty
%% Requires additional inputs for topology metrics (complexity, diversity)
control_topology = false :: boolean(),
%% History window size for computing reward signals
history_window = 10 :: pos_integer(),
%% Momentum for parameter updates (smooths changes)
momentum = 0.9 :: float()
}).
-type meta_config() :: #meta_config{}.
%%% ============================================================================
%%% State Record
%%% ============================================================================
%% @doc Internal state for the meta-controller gen_server.
-record(meta_state, {
%% Server identifier
id :: term(),
%% Configuration
config :: meta_config(),
%% Current hyperparameter values
current_params = #{
mutation_rate => 0.10,
mutation_strength => 0.30,
selection_ratio => 0.20
} :: #{meta_param() => float()},
%% LTC network internal states (one per hidden neuron)
%% Maps neuron_id => internal_state
ltc_states = #{} :: #{term() => float()},
%% LTC network weights
%% Structure: #{layer => #{neuron_id => [{input_id, weight}]}}
ltc_weights = #{} :: map(),
%% Training metrics history (circular buffer of last N generations)
metrics_history = [] :: [generation_metrics()],
%% Parameter change momentum (for smooth updates)
param_momentum = #{} :: #{meta_param() => float()},
%% Generation counter
generation = 0 :: non_neg_integer(),
%% Cumulative reward for current training run
cumulative_reward = 0.0 :: float(),
%% Best fitness ever seen (for normalization)
best_fitness_ever = 0.0 :: float(),
%% Stagnation counter (generations without improvement)
stagnation_count = 0 :: non_neg_integer(),
%% Running state
running = false :: boolean()
}).
-type meta_state() :: #meta_state{}.
%%% ============================================================================
%%% Generation Metrics Record
%%% ============================================================================
%% @doc Metrics captured from a single generation for meta-learning.
-record(generation_metrics, {
%% Generation number
generation :: pos_integer(),
%% Fitness metrics
best_fitness :: float(),
avg_fitness :: float(),
worst_fitness :: float(),
fitness_std_dev :: float(),
%% Improvement metrics
fitness_delta :: float(), %% Change from previous generation
relative_improvement :: float(), %% Percentage improvement
%% Diversity metrics
population_diversity :: float(), %% Variance in fitness
strategy_entropy :: float(), %% Information content of strategies
%% Efficiency metrics
evaluations_used :: pos_integer(),
fitness_per_evaluation :: float(),
%% Normative structure metrics
diversity_corridors :: float(), %% Cluster distinctness
adaptation_readiness :: float(), %% Variance in adjacent traits
%% Current parameters used
params_used :: #{meta_param() => float()},
%% Timestamp
timestamp :: erlang:timestamp()
}).
-type generation_metrics() :: #generation_metrics{}.
%%% ============================================================================
%%% Reward Signal Record
%%% ============================================================================
%% @doc Computed reward signal for meta-controller training.
-record(meta_reward, {
%% Individual reward components
convergence_speed :: float(),
final_fitness :: float(),
efficiency_ratio :: float(),
diversity_aware :: float(),
normative_structure :: float(),
%% Composite reward (weighted sum)
total :: float(),
%% Generation this reward is for
generation :: pos_integer()
}).
-type meta_reward() :: #meta_reward{}.
%%% ============================================================================
%%% Network Structure Records
%%% ============================================================================
%% @doc LTC neuron state for the meta-controller network.
-record(meta_neuron, {
%% Neuron identifier: {layer, index}
id :: {pos_integer(), pos_integer()},
%% Internal state x(t)
internal_state = 0.0 :: float(),
%% Base time constant
time_constant :: float(),
%% State bound
state_bound :: float(),
%% Input weights: [{source_id, weight}]
input_weights = [] :: [{term(), float()}],
%% Bias
bias = 0.0 :: float(),
%% Backbone weights for CfC f() function
backbone_weights = [] :: [float()],
%% Head weights for CfC h() function
head_weights = [] :: [float()]
}).
-type meta_neuron() :: #meta_neuron{}.
%% @doc Output mapping for meta-controller.
%% Maps network outputs to parameter adjustments.
-record(output_mapping, {
%% Output index -> parameter name
index_to_param :: #{non_neg_integer() => meta_param()},
%% Parameter name -> output index
param_to_index :: #{meta_param() => non_neg_integer()},
%% Activation functions for each output (e.g., sigmoid for bounded params)
output_activations :: #{non_neg_integer() => atom()}
}).
-type output_mapping() :: #output_mapping{}.
%%% ============================================================================
%%% Training Event Record
%%% ============================================================================
%% @doc Training event for gradient-based meta-learning.
-record(meta_training_event, {
%% Generation
generation :: pos_integer(),
%% Input features (normalized metrics)
inputs :: [float()],
%% Output parameters (raw network outputs before scaling)
outputs :: [float()],
%% Reward received
reward :: float(),
%% Gradient estimate for this step
gradients :: #{term() => float()}
}).
-type meta_training_event() :: #meta_training_event{}.
%%% ============================================================================
%%% Structure Metrics Record (Normative Awareness)
%%% ============================================================================
%% @doc Structure metrics for normative awareness reward component.
%%
%% These metrics capture whether the population maintains the capacity
%% for future adaptation, not just current fitness.
-record(structure_metrics, {
%% Number of distinct strategy clusters in population
diversity_corridors = 0.0 :: float(),
%% Variance in traits that are adjacent to fitness (exploration potential)
adaptation_readiness = 0.0 :: float(),
%% Estimated distance to unexplored fitness regions
breakthrough_potential = 0.0 :: float(),
%% Shannon entropy of strategy distribution
strategy_entropy = 0.0 :: float()
}).
-type structure_metrics() :: #structure_metrics{}.
%%% ============================================================================
%%% L2 Guidance Record (L2→L1 Interface)
%%% ============================================================================
%% @doc L2 guidance output that controls L1 (task_silo) behavior.
%%
%% Instead of L2 directly outputting hyperparameters (mutation_rate, etc.),
%% it outputs meta-parameters that control HOW L1 adjusts hyperparameters.
%% This creates a proper hierarchical control system:
%%
%% L2 (Strategic) → L1 Guidance → L1 (Tactical) → Hyperparams → L0 (Bounds)
%%
%% Example:
%% - L2 outputs aggression_factor = 1.5
%% - L1 uses this: AdjustedMR = BaseMR * (1 + NetFactor * 1.5)
%% - Result: mutation_rate can increase by up to 150% instead of 50%
-record(l2_guidance, {
%% How aggressive L1 adjustments should be
%% Higher = stronger response to stagnation
%% Range: [0.0, 2.0], Default: 0.5
aggression_factor = 0.5 :: float(),
%% How fast exploration_boost increases per stagnating generation
%% Higher = faster escalation when stuck
%% Range: [0.05, 0.5], Default: 0.1
exploration_step = 0.1 :: float(),
%% Threshold for what counts as "improvement" (below = stagnating)
%% Lower = more sensitive to small gains
%% Range: [0.0001, 0.01], Default: 0.001
stagnation_sensitivity = 0.001 :: float(),
%% How much to boost topology mutations when heavily stagnating
%% Higher = more aggressive structural changes
%% Range: [1.0, 3.0], Default: 1.5
topology_aggression = 1.5 :: float(),
%% Weight for exploitation vs exploration in NetFactor calculation
%% Higher = more exploitation when improving
%% Range: [0.2, 0.8], Default: 0.5
exploitation_weight = 0.5 :: float(),
%% Generation this guidance was computed for
generation = 0 :: non_neg_integer()
}).
-type l2_guidance() :: #l2_guidance{}.
%% @doc Parameter bounds for L2 guidance outputs.
-define(L2_GUIDANCE_BOUNDS, #{
aggression_factor => {0.0, 2.0},
exploration_step => {0.05, 0.5},
stagnation_sensitivity => {0.0001, 0.01},
topology_aggression => {1.0, 3.0},
exploitation_weight => {0.2, 0.8}
}).
%% @doc Default L2 guidance (used when L2 is disabled).
-define(L2_GUIDANCE_DEFAULTS, #l2_guidance{
aggression_factor = 0.5,
exploration_step = 0.1,
stagnation_sensitivity = 0.001,
topology_aggression = 1.5,
exploitation_weight = 0.5,
generation = 0
}).
%%% ============================================================================
%%% Velocity-Based Stagnation State
%%% ============================================================================
%% @doc State for velocity-based stagnation detection.
%%
%% Tracks improvement velocity (fitness gain per 1000 evaluations) instead of
%% counting generations without improvement. This enables:
%%
%% 1. Reaction to slowdown BEFORE full plateau
%% 2. Continuous severity scale (0.0-1.0) instead of binary trigger
%% 3. Compatibility with variable batch sizes and steady-state evolution
%%
%% Algorithm:
%% velocity = (current_fitness - prev_fitness) / (current_evals - prev_evals) * 1000
%% avg_velocity = rolling_average(improvement_window)
%% stagnation_severity = clamp((threshold - avg_velocity) / threshold, 0.0, 1.0)
%%
%% Reference: Evaluation-centric evolution architecture plan.
-record(velocity_state, {
%% Rolling window of improvement velocities
improvement_window = [] :: [float()],
%% Number of checkpoints to track in window
window_size = 10 :: pos_integer(),
%% Current improvement velocity (fitness gain per 1000 evaluations)
current_velocity = 0.0 :: float(),
%% Velocity threshold: below this = stagnating
%% Default: 0.001 = 0.1% fitness improvement per 1000 evaluations
velocity_threshold = 0.001 :: float(),
%% Fitness checkpoints: [{total_evaluations, best_fitness}]
%% Used to compute velocity between checkpoints
fitness_checkpoints = [] :: [{non_neg_integer(), float()}],
%% Stagnation severity: 0.0 = healthy, 1.0 = critical
%% Continuous scale enables proportional response
stagnation_severity = 0.0 :: float(),
%% Total evaluations at last update
last_total_evaluations = 0 :: non_neg_integer()
}).
-type velocity_state() :: #velocity_state{}.
%% @doc Default velocity state initializer.
-define(VELOCITY_STATE_DEFAULTS, #velocity_state{
improvement_window = [],
window_size = 10,
current_velocity = 0.0,
velocity_threshold = 0.001,
fitness_checkpoints = [],
stagnation_severity = 0.0,
last_total_evaluations = 0
}).
-endif. %% META_CONTROLLER_HRL