v0.4.0 - Network Lifecycle
View SourceOverview
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
- Refactor
cortex.erlwith state record and clear transitions - Refactor
exoself.erlinitialization into modular functions - Add sensor and actuator process management
- 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:
Fix typo (line 65)
%% Before {self(), termiante} %% After {self(), terminate}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 }).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).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).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).Replace process dictionary with explicit state
%% Before: put(goal_reached, true) %% After: state field State#cortex_state{goal_reached = true}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:
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 }).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).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) -> ...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_paramRename 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:
- Add type specifications
- Document process lifecycle
- Replace process dictionary with state
- 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
cortex.erl
- State machine transitions
- Sync cycle explanation
- Fitness accumulation
- All function specs
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
Bug Fixes
- [ ] "termiante" typo fixed
- [ ] Test verifies correct spelling
State Records
- [ ] Cortex uses state record
- [ ] Exoself uses state record
- [ ] All fields have meaningful names
Code Quality
- [ ] No function exceeds 30 lines
- [ ] prep/3 decomposed into modules
- [ ] No process dictionary usage
Test Coverage
- [ ] cortex.erl: 90%+ coverage
- [ ] exoself.erl: 80%+ coverage
- [ ] All tests pass
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:
- v0.5.0 will refactor
genome_mutator.erl - Network lifecycle complete
- 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 terminateNetwork 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
| Task | Estimate |
|---|---|
| Cortex state record conversion | 1.5 days |
| Cortex tests | 1.5 days |
| Exoself state renaming | 1 day |
| Exoself prep decomposition | 2 days |
| Exoself tests | 2 days |
| Documentation | 2 days |
| Total | 10 days |
Risks
| Risk | Mitigation |
|---|---|
| State conversion breaks behavior | Comprehensive tests first |
| Process interaction timing | Use synchronization |
| Complex linking logic | Test incrementally |
Version: 0.4.0 Phase: Structural Status: Planned