v0.4.0 - Network Lifecycle

View Source

Overview

This release begins the Structural Phase by refactoring the network coordination and lifecycle management modules. The focus is on decomposing complex state machines and modularizing initialization code.

Phase: Structural Duration: 2 weeks Prerequisites: v0.3.0 (plasticity, neuron refactored)

Objectives

  1. Refactor cortex.erl with state record and clear transitions
  2. Refactor exoself.erl initialization into modular functions
  3. Add sensor and actuator process management
  4. Fix identified bugs in cortex

Modules to Refactor

1. cortex.erl (102 lines)

Analysis Reference: Section 3.1 of DXNN2_CODEBASE_ANALYSIS.md (lines 137-183)

Issues to Address:

  • Typo "termiante" (line 65)
  • 10-parameter loop function (line 50)
  • Code duplication in update_FitnessAcc (lines 90-95)
  • Process dictionary usage for state
  • Custom vector_add instead of lists:zipwith

Tasks:

  1. Fix typo (line 65)

    %% Before
    {self(), termiante}
    
    %% After
    {self(), terminate}
  2. Convert to state record

    %% Before: 10 parameters in function head
    loop(Id, ExoSelf_PId, SPIds, {[APId|APIds], MAPIds}, NPIds,
         CycleAcc, FitnessAcc, EFAcc, active, OpMode)
    
    %% After: state record
    -record(cortex_state, {
        id,
        exoself_pid,
        sensor_pids = [],
        neuron_pids = [],
        actuator_pids = [],
        backup_actuator_pids = [],    % For sync tracking
        cycle_count = 0,
        fitness_accumulator = [],
        end_flag_accumulator = 0,
        status = active,              % active | inactive
        operation_mode = gt           % gt | validation | test
    }).
  3. Decompose loop into state-specific functions

    %% Main entry point
    loop(State) ->
        receive
            Msg -> handle_message(Msg, State)
        end.
    
    %% Handle messages based on state
    handle_message({ActuatorPid, sync, Fitness, EndFlag}, State)
        when State#cortex_state.status == active ->
        handle_actuator_sync(ActuatorPid, Fitness, EndFlag, State);
    
    handle_message(terminate, State) ->
        handle_termination(State);
    
    handle_message({ExoPid, reset_prep}, State) ->
        handle_reset_prep(ExoPid, State).
  4. Consolidate duplicate fitness update

    %% Before: three identical clauses (lines 90-95)
    update_FitnessAcc(FitnessAcc, Fitness, gt) ->
        vector_add(Fitness, FitnessAcc, []);
    update_FitnessAcc(FitnessAcc, Fitness, validation) ->
        vector_add(Fitness, FitnessAcc, []);
    update_FitnessAcc(FitnessAcc, Fitness, test) ->
        vector_add(Fitness, FitnessAcc, []).
    
    %% After: single clause
    update_fitness_accumulator(Accumulator, Fitness, _OperationMode) ->
        vector_add(Fitness, Accumulator).
  5. Replace custom vector_add with lists:zipwith

    %% Before: custom recursive implementation
    vector_add([A|As], [B|Bs], Acc) ->
        vector_add(As, Bs, [A+B|Acc]);
    vector_add([], [], Acc) ->
        lists:reverse(Acc).
    
    %% After: standard library
    vector_add(Vec1, Vec2) ->
        lists:zipwith(fun(A, B) -> A + B end, Vec1, Vec2).
  6. Replace process dictionary with explicit state

    %% Before: put(goal_reached, true)
    
    %% After: state field
    State#cortex_state{goal_reached = true}
  7. Add comprehensive module documentation

    %% @module cortex
    %% @doc Neural network synchronization coordinator
    %%
    %% The cortex manages the sense-think-act cycle:
    %% 1. Sync Phase: Broadcast sync to all sensors
    %% 2. Think Phase: Neurons compute (handled by neuron processes)
    %% 3. Act Phase: Wait for all actuators to report
    %% 4. Accumulate: Aggregate fitness across cycles
    %%
    %% == State Machine ==
    %% Active: Processing sense-think-act cycles
    %% Inactive: Waiting for reset signal from exoself

2. exoself.erl (607 lines)

Analysis Reference: Section 3.3 of DXNN2_CODEBASE_ANALYSIS.md (lines 268-337)

Issues to Address:

  • 50+ line prep function
  • 24-field state record with unclear names
  • Inconsistent naming (sids vs spids)
  • idsNpids ETS table name

Tasks:

  1. Rename state fields consistently

    %% Before
    -record(state, {
        agent_id, morphology, generation, pm_pid,
        idsNpids,                    % Cryptic ETS table name
        cx_pid, spids, npids, apids, % Process IDs
        sids, nids, aids,            % Element IDs (inconsistent)
        ...
    }).
    
    %% After
    -record(exoself_state, {
        agent_id,
        morphology,
        generation,
        population_monitor_pid,
        id_to_process_map,           % ETS table (was idsNpids)
        cortex_pid,
        sensor_process_ids = [],
        neuron_process_ids = [],
        actuator_process_ids = [],
        sensor_ids = [],
        neuron_ids = [],
        actuator_ids = [],
        private_scape_pids = [],
        public_scape_pids = [],
        highest_fitness,
        evaluation_count = 0,
        cycle_count = 0,
        time_accumulated = 0,
        max_tuning_attempts = 15,
        current_attempt = 1,
        tuning_duration_function,
        tuning_selection_function,
        annealing_parameter,
        perturbation_range,
        substrate_pid,
        cpp_pids = [],
        cep_pids = [],
        operation_mode
    }).
  2. Decompose prep/3 into modular functions

    %% Before: 50+ line monolithic prep function
    
    %% After: modular preparation
    prep(Agent, PopMonitorPid, OpMode) ->
        State = initialize_base_state(Agent, PopMonitorPid, OpMode),
        {SensorPids, SensorIds} = spawn_sensors(State),
        {NeuronPids, NeuronIds} = spawn_neurons(State),
        {ActuatorPids, ActuatorIds} = spawn_actuators(State),
        CortexPid = spawn_cortex(State),
        SubstratePid = spawn_substrate_if_needed(State),
        link_network(State, SensorPids, NeuronPids, ActuatorPids, CortexPid),
        FinalState = State#exoself_state{
            sensor_process_ids = SensorPids,
            neuron_process_ids = NeuronPids,
            actuator_process_ids = ActuatorPids,
            cortex_pid = CortexPid
        },
        loop(FinalState).
  3. Extract network spawning functions

    -spec spawn_sensors(exoself_state()) -> {[pid()], [sensor_id()]}.
    spawn_sensors(State) ->
        Sensors = genotype:read_sensors(State#exoself_state.agent_id),
        lists:unzip([
            begin
                Pid = sensor:gen(self(), Sensor),
                ets:insert(State#exoself_state.id_to_process_map,
                          {Sensor#sensor.id, Pid}),
                {Pid, Sensor#sensor.id}
            end
            || Sensor <- Sensors
        ]).
    
    -spec spawn_neurons(exoself_state()) -> {[pid()], [neuron_id()]}.
    spawn_neurons(State) ->
        %% Similar structure
        ...
    
    -spec spawn_actuators(exoself_state()) -> {[pid()], [actuator_id()]}.
    spawn_actuators(State) ->
        ...
  4. Document tuning algorithm

    %% @doc Weight tuning loop using memetic algorithm
    %%
    %% The tuning process attempts to find better weights through:
    %% 1. Perturb: Add noise to subset of weights
    %% 2. Evaluate: Run network and measure fitness
    %% 3. Compare: Is new fitness better than best?
    %% 4. If better: Save weights as new best
    %% 5. If worse: Restore previous weights
    %% 6. Repeat for max_attempts with simulated annealing
    %%
    %% Simulated annealing reduces perturbation over time:
    %% perturbation = initial_range * (1 - attempt/max_attempts)^annealing_param
  5. Rename ETS table operations

    %% Create with meaningful name
    IdToProcessMap = ets:new(id_to_process_map, [set, private]),
    
    %% Lookup operations
    get_process_id(ElementId, State) ->
        [{ElementId, Pid}] = ets:lookup(
            State#exoself_state.id_to_process_map,
            ElementId
        ),
        Pid.

3. sensor.erl and actuator.erl

Tasks:

  1. Add type specifications
  2. Document process lifecycle
  3. Replace process dictionary with state
  4. Add consistent naming

Tests to Write

cortex_test.erl

-module(cortex_test).
-include_lib("eunit/include/eunit.hrl").
-include("records.hrl").

%% ============================================================================
%% State management tests
%% ============================================================================

initial_state_test() ->
    State = #cortex_state{
        id = {{0.0, 0.1}, cortex},
        status = active,
        operation_mode = gt
    },
    ?assertEqual(active, State#cortex_state.status),
    ?assertEqual([], State#cortex_state.fitness_accumulator).

%% ============================================================================
%% Vector operations tests
%% ============================================================================

vector_add_same_length_test() ->
    Vec1 = [1.0, 2.0, 3.0],
    Vec2 = [0.5, 1.5, 2.5],
    Result = cortex:vector_add(Vec1, Vec2),
    ?assertEqual([1.5, 3.5, 5.5], Result).

vector_add_empty_test() ->
    ?assertEqual([], cortex:vector_add([], [])).

%% ============================================================================
%% Fitness accumulation tests
%% ============================================================================

update_fitness_accumulator_test() ->
    Acc = [1.0, 2.0],
    Fitness = [0.5, 0.5],
    Result = cortex:update_fitness_accumulator(Acc, Fitness, gt),
    ?assertEqual([1.5, 2.5], Result).

update_fitness_accumulator_all_modes_test() ->
    %% Should work the same for all operation modes
    Acc = [1.0],
    Fitness = [0.5],
    ?assertEqual([1.5], cortex:update_fitness_accumulator(Acc, Fitness, gt)),
    ?assertEqual([1.5], cortex:update_fitness_accumulator(Acc, Fitness, validation)),
    ?assertEqual([1.5], cortex:update_fitness_accumulator(Acc, Fitness, test)).

%% ============================================================================
%% Process lifecycle tests (integration)
%% ============================================================================

cortex_spawn_and_terminate_test() ->
    Config = test_helpers:create_test_cortex_config(),
    Pid = cortex:gen(self(), Config),
    ?assert(is_pid(Pid)),
    Pid ! terminate,
    timer:sleep(10),
    ?assertNot(is_process_alive(Pid)).

%% ============================================================================
%% Sync cycle tests (integration)
%% ============================================================================

single_cycle_test() ->
    %% Setup mock network
    {CortexPid, ActuatorPids, _SensorPids} =
        test_helpers:create_mock_network(),

    %% Simulate actuator sync
    [ActuatorPid | _] = ActuatorPids,
    CortexPid ! {ActuatorPid, sync, [1.0], 0},

    %% Verify cortex processed message
    receive
        {cortex_cycle_complete, Fitness} ->
            ?assertEqual([1.0], Fitness)
    after 1000 ->
        ?assert(false)  % Timeout
    end.

exoself_test.erl

-module(exoself_test).
-include_lib("eunit/include/eunit.hrl").
-include("records.hrl").

%% ============================================================================
%% State initialization tests
%% ============================================================================

base_state_initialization_test() ->
    Agent = test_helpers:create_test_agent(),
    State = exoself:initialize_base_state(Agent, self(), gt),

    ?assertEqual(Agent#agent.id, State#exoself_state.agent_id),
    ?assertEqual(gt, State#exoself_state.operation_mode),
    ?assertEqual(15, State#exoself_state.max_tuning_attempts).

%% ============================================================================
%% ID to process mapping tests
%% ============================================================================

id_to_process_map_test() ->
    IdMap = ets:new(id_to_process_map, [set, private]),
    NeuronId = {{1.0, 0.5}, neuron},
    Pid = self(),

    ets:insert(IdMap, {NeuronId, Pid}),
    [{NeuronId, RetrievedPid}] = ets:lookup(IdMap, NeuronId),

    ?assertEqual(Pid, RetrievedPid),
    ets:delete(IdMap).

%% ============================================================================
%% Network spawning tests (integration)
%% ============================================================================

spawn_sensors_test() ->
    State = test_helpers:create_test_exoself_state(),
    {SensorPids, SensorIds} = exoself:spawn_sensors(State),

    ?assert(length(SensorPids) > 0),
    ?assert(length(SensorPids) == length(SensorIds)),
    lists:foreach(fun(Pid) ->
        ?assert(is_pid(Pid))
    end, SensorPids).

spawn_complete_network_test() ->
    Agent = test_helpers:create_test_agent(),
    State = exoself:initialize_base_state(Agent, self(), gt),

    {SensorPids, _} = exoself:spawn_sensors(State),
    {NeuronPids, _} = exoself:spawn_neurons(State),
    {ActuatorPids, _} = exoself:spawn_actuators(State),

    TotalProcesses = length(SensorPids) + length(NeuronPids) + length(ActuatorPids),
    ?assert(TotalProcesses > 0).

%% ============================================================================
%% Tuning tests
%% ============================================================================

annealing_schedule_test() ->
    %% Test that perturbation decreases over attempts
    InitialRange = 1.0,
    AnnealingParam = 1.0,
    MaxAttempts = 10,

    Pert1 = exoself:calculate_perturbation(InitialRange, 1, MaxAttempts, AnnealingParam),
    Pert5 = exoself:calculate_perturbation(InitialRange, 5, MaxAttempts, AnnealingParam),
    Pert10 = exoself:calculate_perturbation(InitialRange, 10, MaxAttempts, AnnealingParam),

    ?assert(Pert1 > Pert5),
    ?assert(Pert5 > Pert10).

Documentation Requirements

Required Documentation

  1. cortex.erl

    • State machine transitions
    • Sync cycle explanation
    • Fitness accumulation
    • All function specs
  2. exoself.erl

    • Lifecycle overview
    • Network spawning process
    • Tuning algorithm
    • Annealing schedule

Documentation Checklist

  • [ ] Cortex state record documented
  • [ ] Cortex state transitions diagrammed
  • [ ] Exoself state record documented
  • [ ] Network spawning process explained
  • [ ] Tuning algorithm documented
  • [ ] All function specs complete

Quality Gates

v0.4.0 Acceptance Criteria

  1. Bug Fixes

    • [ ] "termiante" typo fixed
    • [ ] Test verifies correct spelling
  2. State Records

    • [ ] Cortex uses state record
    • [ ] Exoself uses state record
    • [ ] All fields have meaningful names
  3. Code Quality

    • [ ] No function exceeds 30 lines
    • [ ] prep/3 decomposed into modules
    • [ ] No process dictionary usage
  4. Test Coverage

    • [ ] cortex.erl: 90%+ coverage
    • [ ] exoself.erl: 80%+ coverage
    • [ ] All tests pass
  5. Static Analysis

    • [ ] Zero dialyzer warnings

Known Limitations

  • Full integration testing deferred to v0.6.0
  • Substrate handling simplified
  • Complex error scenarios not fully covered

Next Steps

After v0.4.0 completion:

  1. v0.5.0 will refactor genome_mutator.erl
  2. Network lifecycle complete
  3. Evolution engine refactoring begins

Implementation Notes

State Machine Transition Diagram

Initial -> Active: on spawn
Active -> Active: on actuator sync (more pending)
Active -> Complete: on final actuator sync (cycle done)
Complete -> Active: on cycle_complete (more cycles)
Complete -> Inactive: on evaluation_complete (max cycles)
Inactive -> Active: on reset_prep
Any -> Terminated: on terminate

Network Linking Process

link_network(State, SensorPids, NeuronPids, ActuatorPids, CortexPid) ->
    %% Link cortex to all processes
    lists:foreach(fun(Pid) ->
        CortexPid ! {link, sensor, Pid}
    end, SensorPids),

    %% Link neurons to their inputs and outputs
    lists:foreach(fun(NeuronId) ->
        Pid = get_process_id(NeuronId, State),
        InputPids = get_input_pids(NeuronId, State),
        OutputPids = get_output_pids(NeuronId, State),
        Pid ! {link, inputs, InputPids},
        Pid ! {link, outputs, OutputPids}
    end, State#exoself_state.neuron_ids),

    ok.

Dependencies

External Dependencies

  • ETS for process mapping

Internal Dependencies

  • v0.3.0: neuron, plasticity
  • v0.2.0: signal_aggregator, functions
  • v0.1.0: types, test infrastructure

Effort Estimate

TaskEstimate
Cortex state record conversion1.5 days
Cortex tests1.5 days
Exoself state renaming1 day
Exoself prep decomposition2 days
Exoself tests2 days
Documentation2 days
Total10 days

Risks

RiskMitigation
State conversion breaks behaviorComprehensive tests first
Process interaction timingUse synchronization
Complex linking logicTest incrementally

Version: 0.4.0 Phase: Structural Status: Planned