neuroevolution_evaluator behaviour (faber_neuroevolution v1.2.4)
View SourceBehaviour for domain-specific evaluators.
This module defines the behaviour that domain-specific evaluators must implement. Evaluators are responsible for:
- Running an individual through domain-specific tests/games/simulations
- Calculating metrics (score, ticks survived, wins, etc.)
- Optionally calculating fitness from metrics
Implementing an Evaluator
To create a custom evaluator, implement the evaluate/2 callback:
-module(my_evaluator). -behaviour(neuroevolution_evaluator).
-export([evaluate/2]).
evaluate(Individual, Options) -> Network = Individual#individual.network, %% Run your evaluation logic Score = run_game(Network), Ticks = get_survival_time(), %% Return updated individual with metrics UpdatedIndividual = Individual#individual{ metrics = #{score => Score, ticks => Ticks} }, {ok, UpdatedIndividual}.
Optional Fitness Calculation
By default, fitness is calculated by the neuroevolution server using a standard formula. You can override this by implementing calculate_fitness/1:
-export([calculate_fitness/1]).
calculate_fitness(Metrics) -> Score = maps:get(score, Metrics, 0), Ticks = maps:get(ticks, Metrics, 0), Score * 50.0 + Ticks / 50.0.
Summary
Functions
Default fitness calculation from standard metrics.
Evaluate a batch distributed across all connected BEAM nodes.
Evaluate a batch of individuals in parallel using Elixir PartitionSupervisor.
Evaluate an individual using the specified evaluator module.
Get list of connected worker nodes.
Types
-type fitness() :: float() | undefined.
-type generation() :: non_neg_integer().
-type genome() :: #genome{connection_genes :: [#connection_gene{innovation :: pos_integer() | undefined, from_id :: term(), to_id :: term(), weight :: float(), enabled :: boolean()}], input_count :: non_neg_integer(), hidden_count :: non_neg_integer(), output_count :: non_neg_integer()}.
-type individual() :: #individual{id :: individual_id(), network :: network(), genome :: genome() | undefined, parent1_id :: individual_id() | undefined, parent2_id :: individual_id() | undefined, fitness :: fitness(), metrics :: metrics(), generation_born :: generation(), birth_evaluation :: non_neg_integer(), max_age :: pos_integer(), is_survivor :: boolean(), is_offspring :: boolean()}.
-type individual_id() :: term().
-type metrics() :: map().
-type network() :: term().
Callbacks
-callback evaluate(Individual, Options) -> Result when Individual :: individual(), Options :: map(), Result :: {ok, EvaluatedIndividual :: individual()} | {error, term()}.
Functions
Default fitness calculation from standard metrics.
Uses a formula that balances: - Score (food eaten, points, etc.) - primary goal - Survival (ticks lived) - secondary goal - Wins - bonus
Formula: Score * 50 + Ticks / 50 + Wins * 2
-spec evaluate_batch_distributed(Population, EvaluatorModule, Options) -> Results when Population :: [individual()], EvaluatorModule :: module(), Options :: map(), Results :: [individual()].
Evaluate a batch distributed across all connected BEAM nodes.
This function splits the population across all available nodes and uses erpc to distribute work. When worker nodes are available, evaluations are spread across all nodes for horizontal scaling. Falls back to local parallel evaluation if no workers are connected.
The coordinator splits the population, dispatches batches to workers via erpc, evaluates a local batch in parallel, and collects all results.
See assets/distributed_evaluation.svg for the architecture diagram.
-spec evaluate_batch_parallel(Population, EvaluatorModule, Options) -> Results when Population :: [individual()], EvaluatorModule :: module(), Options :: map(), Results :: [{ok, individual()} | {error, term()}].
Evaluate a batch of individuals in parallel using Elixir PartitionSupervisor.
This function delegates batch evaluation to the Elixir EvaluationPool module, which uses PartitionSupervisor to distribute work across all CPU cores. This provides true multi-core parallelism compared to spawn_link which creates processes on the same scheduler as the spawner.
Falls back to sequential evaluation if the Elixir pool is not available.
-spec evaluate_individual(Individual, EvaluatorModule, Options) -> Result when Individual :: individual(), EvaluatorModule :: module(), Options :: map(), Result :: {ok, individual()} | {error, term()}.
Evaluate an individual using the specified evaluator module.
Delegates to the evaluator module's evaluate/2 callback. Returns the evaluated individual with metrics populated. Catches and logs any exceptions from the evaluator.
PERFORMANCE: Compiles the network for NIF evaluation before calling the evaluator. Speedup over pure Erlang is not measured; see ROADMAP.md. Compared to pure Erlang. The compiled_ref is stripped by the caller after evaluation to prevent memory leaks (see neuroevolution_server:strip_compiled_ref_from_individual/1).
-spec get_worker_nodes() -> [node()].
Get list of connected worker nodes.