Skip to main content

red_queen_core/
evolution.rs

1//! Evolution engine.
2
3use crate::archive::Archive;
4use crate::fitness::Fitness;
5use crate::genome::Genome;
6use crate::population::{Individual, Population, PopulationConfig};
7use crate::selection::Selection;
8use crate::variation::{vary, VariationConfig};
9use rand::SeedableRng;
10use rand_chacha::ChaCha8Rng;
11
12/// Evolution configuration.
13#[derive(Debug, Clone)]
14pub struct EvolutionConfig {
15    /// Population configuration.
16    pub population: PopulationConfig,
17    /// Variation configuration.
18    pub variation: VariationConfig,
19    /// Random seed (None for random).
20    pub seed: Option<u64>,
21}
22
23impl Default for EvolutionConfig {
24    fn default() -> Self {
25        Self {
26            population: PopulationConfig::default(),
27            variation: VariationConfig::default(),
28            seed: None,
29        }
30    }
31}
32
33/// Results from evolution.
34#[derive(Debug)]
35pub struct EvolutionResult<G: Genome> {
36    /// Final population.
37    pub population: Population<G>,
38    /// Best individual found.
39    pub best: Individual<G>,
40    /// Number of generations run.
41    pub generations: usize,
42}
43
44/// The main evolution engine.
45pub struct Evolution<G, F, S>
46where
47    G: Genome,
48    F: Fitness<G>,
49    S: Selection<G>,
50{
51    population: Population<G>,
52    fitness: F,
53    selection: S,
54    archive: Option<Box<dyn Archive<G>>>,
55    config: EvolutionConfig,
56    rng: ChaCha8Rng,
57}
58
59impl<G, F, S> Evolution<G, F, S>
60where
61    G: Genome,
62    F: Fitness<G>,
63    S: Selection<G>,
64{
65    /// Create a new evolution instance.
66    pub fn new(fitness: F, selection: S, config: EvolutionConfig) -> Self {
67        let seed = config.seed.unwrap_or_else(rand::random);
68        let mut rng = ChaCha8Rng::seed_from_u64(seed);
69        let population = Population::random(config.population.clone(), &mut rng);
70
71        Self {
72            population,
73            fitness,
74            selection,
75            archive: None,
76            config,
77            rng,
78        }
79    }
80
81    /// Set the QD archive.
82    pub fn with_archive(mut self, archive: Box<dyn Archive<G>>) -> Self {
83        self.archive = Some(archive);
84        self
85    }
86
87    /// Run evolution for a number of generations.
88    pub fn run(&mut self, generations: usize) -> EvolutionResult<G> {
89        // Initial evaluation
90        self.evaluate_population();
91
92        for _gen in 0..generations {
93            // Create offspring
94            let offspring = self.create_offspring();
95
96            // Evaluate offspring
97            let evaluated = self.evaluate_batch(offspring);
98
99            // Update archive
100            if let Some(archive) = &mut self.archive {
101                for ind in &evaluated {
102                    archive.add(ind.clone());
103                }
104            }
105
106            // Survivor selection
107            self.select_survivors(evaluated);
108
109            self.population.generation += 1;
110        }
111
112        let best = self.population.best().cloned().unwrap_or_else(|| {
113            Individual::new(G::random(&mut self.rng))
114        });
115
116        EvolutionResult {
117            population: self.population.clone(),
118            best,
119            generations,
120        }
121    }
122
123    fn evaluate_population(&mut self) {
124        // Collect unevaluated individuals
125        let unevaluated: Vec<_> = self
126            .population
127            .individuals
128            .iter()
129            .enumerate()
130            .filter(|(_, ind)| ind.fitness.is_none())
131            .map(|(i, ind)| (i, ind.genome.clone()))
132            .collect();
133
134        if unevaluated.is_empty() {
135            return;
136        }
137
138        // Batch evaluate
139        let genomes: Vec<_> = unevaluated.iter().map(|(_, g)| g.clone()).collect();
140        let results = self.fitness.evaluate_batch(&genomes);
141
142        // Apply results
143        for ((idx, _), result) in unevaluated.into_iter().zip(results) {
144            self.population.individuals[idx].fitness = Some(result.value);
145            self.population.individuals[idx].behavior = result.behavior;
146        }
147    }
148
149    fn evaluate_batch(&self, mut individuals: Vec<Individual<G>>) -> Vec<Individual<G>> {
150        if individuals.is_empty() {
151            return individuals;
152        }
153
154        // Batch evaluate all genomes
155        let genomes: Vec<_> = individuals.iter().map(|ind| ind.genome.clone()).collect();
156        let results = self.fitness.evaluate_batch(&genomes);
157
158        // Apply results
159        for (ind, result) in individuals.iter_mut().zip(results) {
160            ind.fitness = Some(result.value);
161            ind.behavior = result.behavior;
162        }
163
164        individuals
165    }
166
167    fn create_offspring(&mut self) -> Vec<Individual<G>> {
168        let count = self.config.population.size - self.config.population.elitism;
169
170        (0..count)
171            .map(|_| {
172                let parent1 = self.selection.select(&self.population, &mut self.rng);
173                let parent2 = self.selection.select(&self.population, &mut self.rng);
174
175                let genome = vary(
176                    &parent1.genome,
177                    &parent2.genome,
178                    &self.config.variation,
179                    &mut self.rng,
180                );
181
182                Individual {
183                    genome,
184                    fitness: None,
185                    behavior: None,
186                    birth_generation: self.population.generation + 1,
187                }
188            })
189            .collect()
190    }
191
192    fn select_survivors(&mut self, offspring: Vec<Individual<G>>) {
193        // Keep elites
194        let elites: Vec<_> = self
195            .population
196            .top_n(self.config.population.elitism)
197            .into_iter()
198            .cloned()
199            .collect();
200
201        // Combine elites and offspring
202        let mut new_pop = elites;
203        new_pop.extend(offspring);
204
205        // Truncate to population size
206        new_pop.truncate(self.config.population.size);
207
208        self.population.individuals = new_pop;
209    }
210}
211
212// Need to add rand_chacha to dependencies
213use rand_chacha;
214
215use crate::archive::MapElitesArchive;
216
217/// Configuration for MAP-Elites evolution.
218#[derive(Debug, Clone)]
219pub struct MapElitesConfig {
220    /// Number of offspring to generate per generation (batch size).
221    pub batch_size: usize,
222    /// Variation configuration.
223    pub variation: VariationConfig,
224    /// Random seed (None for random).
225    pub seed: Option<u64>,
226    /// Number of random individuals to seed the archive with.
227    pub initial_population: usize,
228}
229
230impl Default for MapElitesConfig {
231    fn default() -> Self {
232        Self {
233            batch_size: 100,
234            variation: VariationConfig::default(),
235            seed: None,
236            initial_population: 100,
237        }
238    }
239}
240
241/// Results from MAP-Elites evolution.
242#[derive(Debug)]
243pub struct MapElitesResult<G: Genome> {
244    /// The final archive.
245    pub archive: MapElitesArchive<G>,
246    /// Best individual found (highest fitness).
247    pub best: Option<Individual<G>>,
248    /// Number of generations run.
249    pub generations: usize,
250    /// Total individuals evaluated.
251    pub total_evaluations: usize,
252}
253
254/// MAP-Elites evolution engine.
255///
256/// Unlike traditional evolution, MAP-Elites maintains a grid of cells
257/// where each cell can hold at most one individual. Individuals compete
258/// for cells based on their behavior descriptor, and only the fittest
259/// individual in each cell survives.
260pub struct MapElitesEvolution<G, F>
261where
262    G: Genome,
263    F: Fitness<G>,
264{
265    archive: MapElitesArchive<G>,
266    fitness: F,
267    config: MapElitesConfig,
268    rng: ChaCha8Rng,
269    total_evaluations: usize,
270}
271
272impl<G, F> MapElitesEvolution<G, F>
273where
274    G: Genome,
275    F: Fitness<G>,
276{
277    /// Create a new MAP-Elites evolution instance.
278    pub fn new(archive: MapElitesArchive<G>, fitness: F, config: MapElitesConfig) -> Self {
279        let seed = config.seed.unwrap_or_else(rand::random);
280        let rng = ChaCha8Rng::seed_from_u64(seed);
281
282        Self {
283            archive,
284            fitness,
285            config,
286            rng,
287            total_evaluations: 0,
288        }
289    }
290
291    /// Seed the archive with random individuals.
292    pub fn initialize(&mut self) {
293        // Generate all random genomes
294        let genomes: Vec<G> = (0..self.config.initial_population)
295            .map(|_| G::random(&mut self.rng))
296            .collect();
297
298        // Batch evaluate
299        let results = self.fitness.evaluate_batch(&genomes);
300
301        // Add to archive
302        for (genome, result) in genomes.into_iter().zip(results) {
303            let mut individual = Individual::new(genome);
304            individual.fitness = Some(result.value);
305            individual.behavior = result.behavior;
306            self.archive.add(individual);
307            self.total_evaluations += 1;
308        }
309    }
310
311    /// Run MAP-Elites for a number of generations.
312    pub fn run(&mut self, generations: usize) -> MapElitesResult<G> {
313        // Initialize if archive is empty
314        if self.archive.is_empty() {
315            self.initialize();
316        }
317
318        for _gen in 0..generations {
319            self.step();
320        }
321
322        let best = self.archive.best().cloned();
323        let dimensions = self.archive.dimensions().to_vec();
324        let archive = std::mem::replace(&mut self.archive, MapElitesArchive::new(dimensions));
325
326        MapElitesResult {
327            archive,
328            best,
329            generations,
330            total_evaluations: self.total_evaluations,
331        }
332    }
333
334    /// Run a single generation step.
335    pub fn step(&mut self) {
336        let offspring = self.create_offspring();
337
338        // Batch evaluate all offspring
339        let genomes: Vec<_> = offspring.iter().map(|ind| ind.genome.clone()).collect();
340        let results = self.fitness.evaluate_batch(&genomes);
341
342        // Add evaluated individuals to archive
343        for (mut ind, result) in offspring.into_iter().zip(results) {
344            ind.fitness = Some(result.value);
345            ind.behavior = result.behavior;
346            self.archive.add(ind);
347            self.total_evaluations += 1;
348        }
349    }
350
351    fn create_offspring(&mut self) -> Vec<Individual<G>> {
352        let mut offspring = Vec::with_capacity(self.config.batch_size);
353
354        for _ in 0..self.config.batch_size {
355            let child_genome = if self.archive.len() < 2 {
356                // Not enough individuals for crossover, create random
357                G::random(&mut self.rng)
358            } else {
359                // Select two parents from the archive
360                let parent1 = self.archive.select_random(&mut self.rng);
361                let parent2 = self.archive.select_random(&mut self.rng);
362
363                match (parent1, parent2) {
364                    (Some(p1), Some(p2)) => {
365                        vary(
366                            &p1.genome,
367                            &p2.genome,
368                            &self.config.variation,
369                            &mut self.rng,
370                        )
371                    }
372                    _ => G::random(&mut self.rng),
373                }
374            };
375
376            offspring.push(Individual::new(child_genome));
377        }
378
379        offspring
380    }
381
382    /// Get a reference to the current archive.
383    pub fn archive(&self) -> &MapElitesArchive<G> {
384        &self.archive
385    }
386
387    /// Get the total number of evaluations performed.
388    pub fn total_evaluations(&self) -> usize {
389        self.total_evaluations
390    }
391
392    /// Get coverage statistics.
393    pub fn coverage(&self) -> crate::archive::ArchiveCoverage {
394        self.archive.coverage()
395    }
396}