v0.6.0 - Population Management

View Source

Overview

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

  1. Refactor population_monitor.erl state record and handlers
  2. Document evolutionary generation loop
  3. Clarify species management and selection
  4. Integrate with refactored genome_mutator
  5. 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:

  1. 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
    }).
  2. 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 replacement
  3. Decompose 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.
  4. 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().
  5. 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).
  6. 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:

  1. Document selection strategies
  2. Add type specifications
  3. Ensure integration with refactored population_monitor

3. fitness_postprocessor.erl

Tasks:

  1. Document postprocessing strategies
  2. Add type specifications
  3. Clarify multi-objective handling

4. genotype.erl (Integration)

Tasks:

  1. Add consistent read/write patterns
  2. Document database access patterns
  3. 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

  1. population_monitor.erl

    • Module overview with evolutionary cycle
    • State record field documentation
    • Selection algorithm descriptions
    • Termination conditions
  2. selection_algorithm.erl

    • Each selection strategy documented
    • Mathematical basis
  3. 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

  1. State Management

    • [ ] State record has clear field names
    • [ ] No "IdPs" naming
    • [ ] Species map properly typed
  2. Code Quality

    • [ ] gen_server handlers decomposed
    • [ ] No handler exceeds 30 lines
    • [ ] Species logic extracted
  3. Test Coverage

    • [ ] population_monitor.erl: 80%+ coverage
    • [ ] Full generation cycle tested
    • [ ] Selection algorithms tested
  4. Integration

    • [ ] Works with refactored genome_mutator
    • [ ] Exoself integration verified
    • [ ] End-to-end evolution test passes
  5. 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:

  1. v0.7.0 begins Robustness Phase
  2. Structural Phase complete
  3. 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

TaskEstimate
State record refactoring1.5 days
Handler decomposition2 days
Species management1.5 days
Unit tests2 days
Integration tests2 days
Documentation1 day
Total10 days

Risks

RiskMitigation
Complex state transitionsState machine diagram
Selection edge casesComprehensive tests
Integration failuresTest each component first

Version: 0.6.0 Phase: Structural Status: Planned