Core Concepts

This guide explains the fundamental concepts and abstractions in fugue-evo. Understanding these will help you use the library effectively and create custom components.

The Evolutionary Loop

At its core, genetic algorithms follow an iterative process:

┌─────────────────────────────────────────────────────────┐
│                    EVOLUTIONARY LOOP                      │
│                                                           │
│  ┌──────────┐    ┌───────────┐    ┌──────────────────┐  │
│  │Initialize│ -> │ Evaluate  │ -> │    Selection     │  │
│  │Population│    │  Fitness  │    │                  │  │
│  └──────────┘    └───────────┘    └────────┬─────────┘  │
│                                             │            │
│  ┌──────────┐    ┌───────────┐    ┌────────▼─────────┐  │
│  │  Check   │ <- │  Replace  │ <- │    Crossover     │  │
│  │Terminate │    │Population │    │   & Mutation     │  │
│  └──────────┘    └───────────┘    └──────────────────┘  │
└─────────────────────────────────────────────────────────┘

Fugue-evo provides abstractions for each step while handling the loop orchestration.

Genomes

A genome represents a candidate solution to your optimization problem. The EvolutionaryGenome trait is the core abstraction:

pub trait EvolutionaryGenome: Clone + Send + Sync {
    /// Convert genome to a Fugue trace for probabilistic operations
    fn to_trace(&self) -> Trace;

    /// Reconstruct genome from a trace
    fn from_trace(trace: &Trace) -> Result<Self, GenomeError>;
}

Built-in Genome Types

TypeUse CaseExample
RealVectorContinuous optimizationFunction minimization
BitStringBinary optimizationFeature selection, knapsack
PermutationOrdering problemsTSP, job scheduling
TreeGenomeGenetic programmingSymbolic regression

Example: RealVector

use fugue_evo::prelude::*;

// Create a 5-dimensional real vector
let genome = RealVector::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);

// Access genes
println!("Genes: {:?}", genome.genes());
println!("Dimension: {}", genome.dimension());

// Vector operations
let other = RealVector::new(vec![0.5, 0.5, 0.5, 0.5, 0.5]);
let distance = genome.euclidean_distance(&other);

Fitness Functions

A fitness function evaluates how good a solution is. Higher fitness = better solution.

pub trait Fitness<G>: Send + Sync {
    type Value: FitnessValue;

    fn evaluate(&self, genome: &G) -> Self::Value;
}

Built-in Benchmarks

Fugue-evo includes standard optimization benchmarks:

// Sphere function: f(x) = Σxᵢ²
// Global minimum at origin
let sphere = Sphere::new(10);

// Rastrigin function: highly multimodal
let rastrigin = Rastrigin::new(10);

// Rosenbrock function: curved valley
let rosenbrock = Rosenbrock::new(10);

Custom Fitness

Implement Fitness for your own evaluation:

struct MyFitness;

impl Fitness<RealVector> for MyFitness {
    type Value = f64;

    fn evaluate(&self, genome: &RealVector) -> f64 {
        // Your evaluation logic here
        let genes = genome.genes();
        // Return fitness (higher = better)
        -genes.iter().map(|x| x * x).sum::<f64>()
    }
}

Operators

Operators manipulate genomes to explore the solution space.

Selection

Selection chooses parents for reproduction based on fitness:

// Tournament: randomly sample k individuals, select best
let selection = TournamentSelection::new(3);

// Roulette wheel: probability proportional to fitness
let selection = RouletteWheelSelection::new();

// Rank-based: probability based on fitness rank
let selection = RankSelection::new();

Crossover

Crossover combines two parents to create offspring:

// SBX: Simulated Binary Crossover for real values
let crossover = SbxCrossover::new(20.0); // eta parameter

// Uniform: swap genes with 50% probability
let crossover = UniformCrossover::new();

// One-point: single crossover point
let crossover = OnePointCrossover::new();

Mutation

Mutation introduces random variation:

// Polynomial mutation for real values
let mutation = PolynomialMutation::new(20.0);

// Gaussian noise
let mutation = GaussianMutation::new(0.1); // std dev

// Bit-flip for binary genomes
let mutation = BitFlipMutation::new(0.01); // per-bit rate

Bounds

For continuous optimization, bounds constrain the search space:

// Symmetric bounds: [-5.12, 5.12] for all dimensions
let bounds = MultiBounds::symmetric(5.12, 10);

// Asymmetric bounds per dimension
let bounds = MultiBounds::new(vec![
    Bounds::new(-10.0, 10.0),
    Bounds::new(0.0, 100.0),
    Bounds::new(-1.0, 1.0),
]);

// Uniform bounds for all dimensions
let bounds = MultiBounds::uniform(Bounds::new(0.0, 1.0), 5);

Population

A population is a collection of individuals being evolved:

// Create random population within bounds
let mut population: Population<RealVector, f64> =
    Population::random(100, &bounds, &mut rng);

// Evaluate all individuals
population.evaluate(&fitness);

// Access best individual
if let Some(best) = population.best() {
    println!("Best fitness: {}", best.fitness_value());
}

Termination Criteria

Define when evolution should stop:

// Fixed number of generations
let term = MaxGenerations::new(500);

// Target fitness achieved
let term = TargetFitness::new(-0.001); // for minimization

// Fitness stagnation (no improvement for N generations)
let term = FitnessStagnation::new(50);

// Combine criteria
let term = AnyOf::new(vec![
    Box::new(MaxGenerations::new(1000)),
    Box::new(TargetFitness::new(0.0)),
]);

Builder Pattern

Algorithms are configured using type-safe builders:

let result = SimpleGABuilder::<RealVector, f64, _, _, _, _, _>::new()
    .population_size(100)      // Population size
    .bounds(bounds)            // Search space
    .selection(selection)      // Selection operator
    .crossover(crossover)      // Crossover operator
    .mutation(mutation)        // Mutation operator
    .fitness(fitness)          // Fitness function
    .max_generations(200)      // Termination criterion
    .elitism(true)             // Keep best individual
    .elite_count(2)            // Number of elites
    .build()?                  // Build algorithm
    .run(&mut rng)?;           // Execute

Results

After evolution completes, results provide comprehensive statistics:

let result = ga.run(&mut rng)?;

println!("Best fitness: {}", result.best_fitness);
println!("Best genome: {:?}", result.best_genome);
println!("Generations: {}", result.generations);
println!("Evaluations: {}", result.evaluations);

// Detailed statistics
println!("{}", result.stats.summary());

Next Steps

Now that you understand the core concepts: