Current section
Files
Jump to
Current section
Files
src/lc_morphologies/lc_l2_morphology.erl
%% @doc L2 Strategic Layer Morphology for Liquid Conglomerate.
%%
%% The L2 layer operates at the highest level of temporal abstraction (tau=100).
%% It observes long-term evolution metrics and outputs strategic signals that
%% feed into the L1 tactical layer.
%%
%% == Input Sensors ==
%%
%% Evolution metrics from the neuroevolution process:
%% - best_fitness: Current best fitness in population
%% - avg_fitness: Average population fitness
%% - fitness_improvement: Delta from previous generation
%% - fitness_variance: Population fitness variance
%% - stagnation_counter: Generations without improvement
%% - generation_progress: Current gen / max gen ratio
%% - population_diversity: Genotype diversity measure
%% - species_count: Number of active species
%%
%% == Output Actuators ==
%%
%% Strategic signals (fed to L1 inputs):
%% - strategic_signal_1 through strategic_signal_4
%%
%% These outputs have no predefined semantics - the network learns what
%% information to pass to L1 for effective hyperparameter control.
%%
%% @author Macula.io
%% @copyright 2025 Macula.io
-module(lc_l2_morphology).
-behaviour(morphology_behaviour).
-include_lib("macula_tweann/include/records.hrl").
%% morphology_behaviour callbacks
-export([get_sensors/1, get_actuators/1]).
%%==============================================================================
%% Callbacks
%%==============================================================================
%% @doc Get sensors for L2 strategic layer.
%%
%% Returns sensors that observe evolution metrics. All sensors are initially
%% connected to provide full observability of the evolution process.
%%
%% @param lc_l2 The morphology name
%% @returns List of sensor records for evolution metrics
-spec get_sensors(lc_l2) -> [#sensor{}].
get_sensors(lc_l2) ->
[
#sensor{
name = l2_best_fitness,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => best_fitness, normalize => true}
},
#sensor{
name = l2_avg_fitness,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => avg_fitness, normalize => true}
},
#sensor{
name = l2_fitness_improvement,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => fitness_improvement, normalize => true}
},
#sensor{
name = l2_fitness_variance,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => fitness_variance, normalize => true}
},
#sensor{
name = l2_stagnation_counter,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => stagnation_counter, max_value => 50}
},
#sensor{
name = l2_generation_progress,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => generation_progress} % Already 0-1
},
#sensor{
name = l2_population_diversity,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => population_diversity, normalize => true}
},
#sensor{
name = l2_species_count,
type = lc_evolution_metric,
scape = {private, lc_chain},
vl = 1,
parameters = #{metric => species_count, max_value => 20}
}
];
get_sensors(_) ->
error(invalid_morphology).
%% @doc Get actuators for L2 strategic layer.
%%
%% Returns actuators that output strategic signals. These signals become
%% the inputs to the L1 tactical layer.
%%
%% The number of outputs (4) was chosen to provide sufficient capacity
%% for strategic information while keeping the L1 input size manageable.
%%
%% @param lc_l2 The morphology name
%% @returns List of actuator records for strategic signals
-spec get_actuators(lc_l2) -> [#actuator{}].
get_actuators(lc_l2) ->
[
#actuator{
name = l2_strategic_signal_1,
type = lc_chain_output,
scape = {private, lc_chain},
vl = 1,
parameters = #{target_level => l1, output_index => 0}
},
#actuator{
name = l2_strategic_signal_2,
type = lc_chain_output,
scape = {private, lc_chain},
vl = 1,
parameters = #{target_level => l1, output_index => 1}
},
#actuator{
name = l2_strategic_signal_3,
type = lc_chain_output,
scape = {private, lc_chain},
vl = 1,
parameters = #{target_level => l1, output_index => 2}
},
#actuator{
name = l2_strategic_signal_4,
type = lc_chain_output,
scape = {private, lc_chain},
vl = 1,
parameters = #{target_level => l1, output_index => 3}
}
];
get_actuators(_) ->
error(invalid_morphology).