1use crate::genome::{BehaviorDescriptor, Genome};
4use crate::population::Individual;
5use rand::Rng;
6use std::collections::HashMap;
7
8pub trait Archive<G: Genome>: Send + Sync {
10 fn add(&mut self, individual: Individual<G>) -> bool;
13
14 fn get_all(&self) -> Vec<&Individual<G>>;
16
17 fn coverage(&self) -> ArchiveCoverage;
19
20 fn len(&self) -> usize;
22
23 fn is_empty(&self) -> bool {
25 self.len() == 0
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct ArchiveCoverage {
32 pub filled: usize,
34 pub total: usize,
36}
37
38impl ArchiveCoverage {
39 pub fn percentage(&self) -> f64 {
41 self.filled as f64 / self.total as f64
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct BehaviorDimension {
48 pub name: String,
50 pub min: f64,
52 pub max: f64,
54 pub bins: usize,
56}
57
58pub struct MapElitesArchive<G: Genome> {
60 grid: HashMap<Vec<usize>, Individual<G>>,
62 dimensions: Vec<BehaviorDimension>,
64}
65
66impl<G: Genome> std::fmt::Debug for MapElitesArchive<G> {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("MapElitesArchive")
69 .field("cells_filled", &self.grid.len())
70 .field("dimensions", &self.dimensions)
71 .finish()
72 }
73}
74
75impl<G: Genome> MapElitesArchive<G> {
76 pub fn new(dimensions: Vec<BehaviorDimension>) -> Self {
78 Self {
79 grid: HashMap::new(),
80 dimensions,
81 }
82 }
83
84 fn behavior_to_cell(&self, behavior: &BehaviorDescriptor) -> Vec<usize> {
86 self.dimensions
87 .iter()
88 .zip(behavior.values.iter())
89 .map(|(dim, value)| {
90 let normalized = (value - dim.min) / (dim.max - dim.min);
91 let bin = (normalized * dim.bins as f64).floor() as usize;
92 bin.min(dim.bins - 1)
93 })
94 .collect()
95 }
96
97 fn total_cells(&self) -> usize {
99 self.dimensions.iter().map(|d| d.bins).product()
100 }
101}
102
103impl<G: Genome> Archive<G> for MapElitesArchive<G> {
104 fn add(&mut self, individual: Individual<G>) -> bool {
105 let Some(behavior) = &individual.behavior else {
106 return false;
107 };
108
109 let cell = self.behavior_to_cell(behavior);
110
111 let dominated = self
113 .grid
114 .get(&cell)
115 .map(|existing| individual.fitness_value() > existing.fitness_value())
116 .unwrap_or(true);
117
118 if dominated {
119 self.grid.insert(cell, individual);
120 true
121 } else {
122 false
123 }
124 }
125
126 fn get_all(&self) -> Vec<&Individual<G>> {
127 self.grid.values().collect()
128 }
129
130 fn coverage(&self) -> ArchiveCoverage {
131 ArchiveCoverage {
132 filled: self.grid.len(),
133 total: self.total_cells(),
134 }
135 }
136
137 fn len(&self) -> usize {
138 self.grid.len()
139 }
140}
141
142impl<G: Genome> MapElitesArchive<G> {
143 pub fn select_random<R: Rng>(&self, rng: &mut R) -> Option<&Individual<G>> {
145 if self.grid.is_empty() {
146 return None;
147 }
148 let idx = rng.gen_range(0..self.grid.len());
149 self.grid.values().nth(idx)
150 }
151
152 pub fn best(&self) -> Option<&Individual<G>> {
154 self.grid
155 .values()
156 .max_by(|a, b| {
157 a.fitness_value()
158 .partial_cmp(&b.fitness_value())
159 .unwrap_or(std::cmp::Ordering::Equal)
160 })
161 }
162
163 pub fn top_n(&self, n: usize) -> Vec<&Individual<G>> {
165 let mut individuals: Vec<_> = self.grid.values().collect();
166 individuals.sort_by(|a, b| {
167 b.fitness_value()
168 .partial_cmp(&a.fitness_value())
169 .unwrap_or(std::cmp::Ordering::Equal)
170 });
171 individuals.truncate(n);
172 individuals
173 }
174
175 pub fn dimensions(&self) -> &[BehaviorDimension] {
177 &self.dimensions
178 }
179
180 pub fn get_cell(&self, indices: &[usize]) -> Option<&Individual<G>> {
182 self.grid.get(indices)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::fitness::FitnessValue;
190 use rand::Rng;
191
192 #[derive(Clone, Debug)]
194 struct TestGenome {
195 value: f64,
196 }
197
198 impl Genome for TestGenome {
199 type Phenotype = f64;
200
201 fn random<R: Rng>(rng: &mut R) -> Self {
202 Self {
203 value: rng.gen_range(0.0..1.0),
204 }
205 }
206
207 fn mutate<R: Rng>(&mut self, rng: &mut R, _rate: f64) {
208 self.value = rng.gen_range(0.0..1.0);
209 }
210
211 fn crossover<R: Rng>(&self, other: &Self, _rng: &mut R) -> Self {
212 Self {
213 value: (self.value + other.value) / 2.0,
214 }
215 }
216
217 fn to_phenotype(&self) -> f64 {
218 self.value
219 }
220 }
221
222 fn make_individual(value: f64, fitness: f64, behavior: Vec<f64>) -> Individual<TestGenome> {
223 Individual {
224 genome: TestGenome { value },
225 fitness: Some(FitnessValue::Single(fitness)),
226 behavior: Some(BehaviorDescriptor::new(behavior)),
227 birth_generation: 0,
228 }
229 }
230
231 #[test]
232 fn test_archive_coverage_percentage() {
233 let coverage = ArchiveCoverage {
234 filled: 25,
235 total: 100,
236 };
237 assert!((coverage.percentage() - 0.25).abs() < 1e-10);
238 }
239
240 #[test]
241 fn test_map_elites_empty() {
242 let dimensions = vec![
243 BehaviorDimension {
244 name: "dim1".to_string(),
245 min: 0.0,
246 max: 1.0,
247 bins: 10,
248 },
249 BehaviorDimension {
250 name: "dim2".to_string(),
251 min: 0.0,
252 max: 1.0,
253 bins: 10,
254 },
255 ];
256 let archive: MapElitesArchive<TestGenome> = MapElitesArchive::new(dimensions);
257
258 assert_eq!(archive.get_all().len(), 0);
259 let coverage = archive.coverage();
260 assert_eq!(coverage.filled, 0);
261 assert_eq!(coverage.total, 100); }
263
264 #[test]
265 fn test_map_elites_add_individual() {
266 let dimensions = vec![BehaviorDimension {
267 name: "dim1".to_string(),
268 min: 0.0,
269 max: 1.0,
270 bins: 10,
271 }];
272 let mut archive: MapElitesArchive<TestGenome> = MapElitesArchive::new(dimensions);
273
274 let ind = make_individual(0.5, 1.0, vec![0.5]);
275 assert!(archive.add(ind));
276
277 assert_eq!(archive.get_all().len(), 1);
278 assert_eq!(archive.coverage().filled, 1);
279 }
280
281 #[test]
282 fn test_map_elites_reject_no_behavior() {
283 let dimensions = vec![BehaviorDimension {
284 name: "dim1".to_string(),
285 min: 0.0,
286 max: 1.0,
287 bins: 10,
288 }];
289 let mut archive: MapElitesArchive<TestGenome> = MapElitesArchive::new(dimensions);
290
291 let ind = Individual {
292 genome: TestGenome { value: 0.5 },
293 fitness: Some(FitnessValue::Single(1.0)),
294 behavior: None, birth_generation: 0,
296 };
297
298 assert!(!archive.add(ind));
299 assert_eq!(archive.get_all().len(), 0);
300 }
301
302 #[test]
303 fn test_map_elites_replace_with_better() {
304 let dimensions = vec![BehaviorDimension {
305 name: "dim1".to_string(),
306 min: 0.0,
307 max: 1.0,
308 bins: 10,
309 }];
310 let mut archive: MapElitesArchive<TestGenome> = MapElitesArchive::new(dimensions);
311
312 let ind1 = make_individual(0.5, 0.3, vec![0.55]);
314 assert!(archive.add(ind1));
315
316 let ind2 = make_individual(0.6, 0.8, vec![0.55]);
318 assert!(archive.add(ind2));
319
320 assert_eq!(archive.get_all().len(), 1);
322 assert!((archive.get_all()[0].fitness_value() - 0.8).abs() < 1e-10);
323 }
324
325 #[test]
326 fn test_map_elites_reject_worse() {
327 let dimensions = vec![BehaviorDimension {
328 name: "dim1".to_string(),
329 min: 0.0,
330 max: 1.0,
331 bins: 10,
332 }];
333 let mut archive: MapElitesArchive<TestGenome> = MapElitesArchive::new(dimensions);
334
335 let ind1 = make_individual(0.5, 0.9, vec![0.55]);
337 assert!(archive.add(ind1));
338
339 let ind2 = make_individual(0.6, 0.3, vec![0.55]);
341 assert!(!archive.add(ind2));
342
343 assert!((archive.get_all()[0].fitness_value() - 0.9).abs() < 1e-10);
345 }
346
347 #[test]
348 fn test_map_elites_different_cells() {
349 let dimensions = vec![BehaviorDimension {
350 name: "dim1".to_string(),
351 min: 0.0,
352 max: 1.0,
353 bins: 10,
354 }];
355 let mut archive: MapElitesArchive<TestGenome> = MapElitesArchive::new(dimensions);
356
357 let ind1 = make_individual(0.1, 0.5, vec![0.15]); let ind2 = make_individual(0.5, 0.5, vec![0.55]); let ind3 = make_individual(0.9, 0.5, vec![0.95]); assert!(archive.add(ind1));
363 assert!(archive.add(ind2));
364 assert!(archive.add(ind3));
365
366 assert_eq!(archive.get_all().len(), 3);
367 assert_eq!(archive.coverage().filled, 3);
368 }
369
370 #[test]
371 fn test_map_elites_2d_grid() {
372 let dimensions = vec![
373 BehaviorDimension {
374 name: "x".to_string(),
375 min: 0.0,
376 max: 1.0,
377 bins: 5,
378 },
379 BehaviorDimension {
380 name: "y".to_string(),
381 min: 0.0,
382 max: 1.0,
383 bins: 5,
384 },
385 ];
386 let mut archive: MapElitesArchive<TestGenome> = MapElitesArchive::new(dimensions);
387
388 archive.add(make_individual(0.1, 0.5, vec![0.1, 0.1])); archive.add(make_individual(0.2, 0.5, vec![0.9, 0.1])); archive.add(make_individual(0.3, 0.5, vec![0.1, 0.9])); archive.add(make_individual(0.4, 0.5, vec![0.9, 0.9])); assert_eq!(archive.coverage().total, 25); assert_eq!(archive.coverage().filled, 4);
396 }
397}