v0.6.0 - Population Management
View SourceOverview
This release completes the Structural Phase by refactoring population_monitor.erl and related modules. The focus is on clarifying state management, documenting the evolutionary loop, and improving species handling.
Phase: Structural Duration: 2 weeks Prerequisites: v0.5.0 (genome_mutator, selection utilities refactored)
Objectives
- Refactor
population_monitor.erlstate record and handlers - Document evolutionary generation loop
- Clarify species management and selection
- Integrate with refactored genome_mutator
- Add comprehensive integration tests
Modules to Refactor
1. population_monitor.erl (1,142 lines)
Analysis Reference: Section 3.5 of DXNN2_CODEBASE_ANALYSIS.md (lines 436-441)
Issues to Address:
- 18-field state record with unclear purposes
- "activeAgent_IdPs" naming convention
- Complex gen_server handlers
- Generation loop not well documented
Tasks:
Clarify and rename state record fields
%% Before: cryptic names -record(state, { op_mode, population_id, activeAgent_IdPs=[], % Unclear agent_ids=[], tot_agents, % Abbreviated agents_left, op_tag, ... }). %% After: clear names -record(population_state, { operation_mode, % gt | validation | test population_id, active_agent_processes=[], % [{AgentId, Pid}] agent_ids=[], total_agents, remaining_agents, operation_tag, generation_count = 0, fitness_goal, max_generations, evaluation_limit, selection_algorithm, fitness_postprocessor, evolutionary_strategy, current_best_fitness, specie_size_limit, species_map = #{}, % SpecieId -> [AgentIds] timestamp_started, survival_rate }).Document evolutionary generation loop
%% @doc Main evolutionary loop %% %% Each generation follows these steps: %% 1. Spawn Phase: Launch all agent processes in parallel %% 2. Evaluation Phase: Agents run sense-think-act cycles %% 3. Collection Phase: Gather fitness results %% 4. Selection Phase: Select survivors based on fitness %% 5. Reproduction Phase: Replicate and mutate survivors %% 6. Speciation Phase: Update species assignments %% 7. Termination Check: Goal reached or max generations? %% %% == Fitness Collection == %% As each agent completes, it reports fitness to monitor. %% Monitor accumulates results and triggers next phase %% when all agents complete. %% %% == Selection Algorithms == %% - competition: Tournament selection %% - top_x: Top X% survive %% - steady_state: Continuous replacementDecompose gen_server handlers
%% Before: large handle_cast with multiple patterns %% After: separate handler functions handle_cast({agent_terminated, AgentId, Fitness}, State) -> handle_agent_termination(AgentId, Fitness, State); handle_cast({evaluation_complete, AgentId, Fitness, Cycles}, State) -> handle_evaluation_result(AgentId, Fitness, Cycles, State); handle_cast(next_generation, State) -> handle_generation_advance(State). %% Handler implementations handle_agent_termination(AgentId, Fitness, State) -> UpdatedActive = remove_agent(AgentId, State#population_state.active_agent_processes), UpdatedState = State#population_state{ active_agent_processes = UpdatedActive, remaining_agents = State#population_state.remaining_agents - 1 }, case UpdatedState#population_state.remaining_agents of 0 -> trigger_selection_phase(UpdatedState); _ -> {noreply, UpdatedState} end.Add type specifications for callbacks
-spec init(Args) -> {ok, State} when Args :: population_init_args(), State :: population_state(). -spec handle_call(Request, From, State) -> {reply, Reply, State} when Request :: population_request(), From :: {pid(), term()}, State :: population_state(), Reply :: population_response().Extract species management functions
%% Create species_manager.erl or inline clear functions -spec assign_to_species(agent_id(), population_state()) -> specie_id(). assign_to_species(AgentId, State) -> %% Determine species based on behavioral signature Agent = genotype:read({agent, AgentId}), Signature = calculate_behavioral_signature(Agent), find_matching_species(Signature, State#population_state.species_map). -spec update_species_fitness(specie_id(), [fitness()], population_state()) -> population_state(). update_species_fitness(SpecieId, Fitnesses, State) -> %% Update species average fitness for selection AvgFitness = lists:sum(Fitnesses) / length(Fitnesses), update_specie_record(SpecieId, AvgFitness, State).Add module documentation
%% @module population_monitor %% @doc Population-level evolutionary process manager %% %% This gen_server manages the evolutionary process for a population %% of neural network agents. It coordinates: %% %% - Agent lifecycle (spawn, evaluate, terminate) %% - Fitness collection and aggregation %% - Selection and reproduction %% - Species formation and management %% - Termination condition checking %% %% == Population Hierarchy == %% Population contains multiple species. %% Species contains multiple agents with similar behavior. %% Agents compete within species for selection. %% %% == Multi-Objective Fitness == %% Fitness is a vector [F1, F2, ...] supporting multi-objective %% optimization with different aggregation strategies.
2. selection_algorithm.erl
Tasks:
- Document selection strategies
- Add type specifications
- Ensure integration with refactored population_monitor
3. fitness_postprocessor.erl
Tasks:
- Document postprocessing strategies
- Add type specifications
- Clarify multi-objective handling
4. genotype.erl (Integration)
Tasks:
- Add consistent read/write patterns
- Document database access patterns
- Ensure clean integration with population_monitor
Tests to Write
population_monitor_test.erl
-module(population_monitor_test).
-include_lib("eunit/include/eunit.hrl").
-include("records.hrl").
%% ============================================================================
%% State initialization tests
%% ============================================================================
initial_state_test() ->
State = #population_state{
population_id = test_pop,
operation_mode = gt,
total_agents = 10
},
?assertEqual(gt, State#population_state.operation_mode),
?assertEqual(10, State#population_state.total_agents).
%% ============================================================================
%% Agent management tests
%% ============================================================================
track_active_agent_test() ->
State = #population_state{
active_agent_processes = []
},
AgentId = {{0.1, 0.2}, agent},
Pid = self(),
Updated = add_active_agent(AgentId, Pid, State),
?assertEqual([{AgentId, Pid}], Updated#population_state.active_agent_processes).
remove_active_agent_test() ->
AgentId = {{0.1, 0.2}, agent},
State = #population_state{
active_agent_processes = [{AgentId, self()}],
remaining_agents = 1
},
Updated = remove_active_agent(AgentId, State),
?assertEqual([], Updated#population_state.active_agent_processes).
%% ============================================================================
%% Fitness collection tests
%% ============================================================================
collect_fitness_test() ->
%% Test fitness accumulation
State = #population_state{
remaining_agents = 3
},
%% Simulate three agents completing
{_, State1} = handle_agent_fitness(agent1, [1.0], State),
{_, State2} = handle_agent_fitness(agent2, [2.0], State1),
{Result, State3} = handle_agent_fitness(agent3, [3.0], State2),
%% After all agents, should trigger selection
?assertEqual(trigger_selection, Result),
?assertEqual(0, State3#population_state.remaining_agents).
%% ============================================================================
%% Selection phase tests
%% ============================================================================
selection_phase_survivors_test() ->
%% Test that selection produces correct number of survivors
Fitnesses = [
{agent1, [5.0]},
{agent2, [3.0]},
{agent3, [4.0]},
{agent4, [1.0]},
{agent5, [2.0]}
],
SurvivalRate = 0.4, % Keep top 40%
Survivors = population_monitor:select_survivors(Fitnesses, SurvivalRate),
?assertEqual(2, length(Survivors)),
?assert(lists:member(agent1, Survivors)),
?assert(lists:member(agent3, Survivors)).
%% ============================================================================
%% Generation advancement tests
%% ============================================================================
generation_increment_test() ->
State = #population_state{
generation_count = 5
},
Updated = advance_generation(State),
?assertEqual(6, Updated#population_state.generation_count).
termination_by_goal_test() ->
%% Should terminate when fitness goal reached
State = #population_state{
fitness_goal = [10.0],
current_best_fitness = [15.0]
},
?assertEqual(true, should_terminate(State)).
termination_by_generations_test() ->
%% Should terminate when max generations reached
State = #population_state{
max_generations = 100,
generation_count = 100
},
?assertEqual(true, should_terminate(State)).
%% ============================================================================
%% Integration tests
%% ============================================================================
full_generation_cycle_test() ->
%% Setup test population
{ok, PopMonPid} = test_helpers:start_test_population_monitor(),
%% Run one generation
gen_server:cast(PopMonPid, start_evaluation),
%% Wait for generation to complete
receive
{generation_complete, GenNum, BestFitness} ->
?assertEqual(1, GenNum),
?assert(is_list(BestFitness))
after 5000 ->
?assert(false) % Timeout
end,
test_helpers:stop_population_monitor(PopMonPid).species_management_test.erl
-module(species_management_test).
-include_lib("eunit/include/eunit.hrl").
%% ============================================================================
%% Species assignment tests
%% ============================================================================
assign_new_species_test() ->
%% First agent creates new species
SpeciesMap = #{},
AgentSignature = [0.5, 0.3, 0.8],
{SpecieId, UpdatedMap} = assign_species(AgentSignature, SpeciesMap),
?assert(maps:is_key(SpecieId, UpdatedMap)).
assign_existing_species_test() ->
%% Similar agent joins existing species
ExistingSignature = [0.5, 0.3, 0.8],
SpeciesMap = #{specie1 => {ExistingSignature, [agent1]}},
SimilarSignature = [0.51, 0.29, 0.79], % Within threshold
{SpecieId, _} = assign_species(SimilarSignature, SpeciesMap),
?assertEqual(specie1, SpecieId).
%% ============================================================================
%% Species fitness tests
%% ============================================================================
species_average_fitness_test() ->
Fitnesses = [[1.0], [2.0], [3.0]],
AvgFitness = calculate_species_average(Fitnesses),
?assertEqual([2.0], AvgFitness).Documentation Requirements
Required Documentation
population_monitor.erl
- Module overview with evolutionary cycle
- State record field documentation
- Selection algorithm descriptions
- Termination conditions
selection_algorithm.erl
- Each selection strategy documented
- Mathematical basis
fitness_postprocessor.erl
- Postprocessing strategies
- Multi-objective handling
Documentation Checklist
- [ ] Evolutionary loop fully documented
- [ ] State record all fields explained
- [ ] Selection algorithms described
- [ ] Species management explained
- [ ] Termination conditions listed
- [ ] All function specs complete
Quality Gates
v0.6.0 Acceptance Criteria
State Management
- [ ] State record has clear field names
- [ ] No "IdPs" naming
- [ ] Species map properly typed
Code Quality
- [ ] gen_server handlers decomposed
- [ ] No handler exceeds 30 lines
- [ ] Species logic extracted
Test Coverage
- [ ] population_monitor.erl: 80%+ coverage
- [ ] Full generation cycle tested
- [ ] Selection algorithms tested
Integration
- [ ] Works with refactored genome_mutator
- [ ] Exoself integration verified
- [ ] End-to-end evolution test passes
Static Analysis
- [ ] Zero dialyzer warnings
Known Limitations
- Complex species dynamics simplified
- Multi-objective optimization basic
- Performance not optimized for large populations
Next Steps
After v0.6.0 completion:
- v0.7.0 begins Robustness Phase
- Structural Phase complete
- Focus shifts to bug fixes and error handling
Implementation Notes
Generation Cycle Flow
start_evaluation
|
v
spawn_all_agents
|
v
[wait for all agents to complete]
|
v
collect_all_fitness
|
v
postprocess_fitness
|
v
select_survivors
|
v
replicate_and_mutate
|
v
update_species
|
v
check_termination -> [done] | [continue -> start_evaluation]Selection Algorithm Integration
select_survivors(AgentFitnesses, State) ->
Algorithm = State#population_state.selection_algorithm,
SurvivalRate = State#population_state.survival_rate,
selection_algorithm:Algorithm(AgentFitnesses, SurvivalRate).Multi-Objective Fitness Handling
%% Fitness is always a vector
-type fitness() :: [float()].
%% Compare using dominance or weighted sum
compare_fitness(F1, F2, Strategy) ->
case Strategy of
pareto -> pareto_dominates(F1, F2);
weighted_sum -> sum(F1) > sum(F2);
lexicographic -> lexicographic_compare(F1, F2)
end.Dependencies
External Dependencies
- gen_server (OTP)
- Mnesia for persistence
Internal Dependencies
- v0.5.0: genome_mutator, selection_utils
- v0.4.0: exoself
- v0.1.0: types, test infrastructure
Effort Estimate
| Task | Estimate |
|---|---|
| State record refactoring | 1.5 days |
| Handler decomposition | 2 days |
| Species management | 1.5 days |
| Unit tests | 2 days |
| Integration tests | 2 days |
| Documentation | 1 day |
| Total | 10 days |
Risks
| Risk | Mitigation |
|---|---|
| Complex state transitions | State machine diagram |
| Selection edge cases | Comprehensive tests |
| Integration failures | Test each component first |
Version: 0.6.0 Phase: Structural Status: Planned