radiate_engines/
generation.rs

1use crate::Chromosome;
2use crate::context::Context;
3use radiate_core::objectives::Scored;
4use radiate_core::{Ecosystem, Front, MetricSet, Objective, Phenotype, Population, Score, Species};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use std::fmt::Debug;
8use std::time::Duration;
9
10/// A snapshot of an ecosystem, either owned or shared.
11///
12/// Owned ecosystems contain their own data, while shared ecosystems
13/// contain reference counted clones of the data. This allows for
14/// efficient sharing of ecosystems between generations without
15/// unnecessary cloning. However, this means that a shared ecosystem
16/// should not be modified directly, as it may affect other generations
17/// that share the same data.
18#[derive(Clone)]
19#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20pub enum EcosystemSnapshot<C: Chromosome> {
21    Owned(Ecosystem<C>),
22    Shared(Ecosystem<C>),
23}
24
25/// A [Generation] represents a single generation in the evolutionary process.
26/// It contains the ecosystem, best solution, index, metrics, score, objective,
27/// and optionally the Pareto front for multi-objective problems.
28///
29/// The [Generation] struct is designed to be efficient in terms of memory usage
30/// by utilizing reference counting for the ecosystem data when possible. This allows for
31/// multiple generations to share the same ecosystem data without unnecessary duplication. However,
32/// because of this, the generation's ecosystem is treated as 'copy on read' if it is shared. So,
33/// the first time you access the ecosystem, it will be cloned if it is shared.
34///
35/// This is the main structure returned by the engine after each epoch, and it provides
36/// access to all relevant information about that generation.
37///
38/// # Example
39/// ```rust
40/// use radiate_core::*;
41/// use radiate_engines::*;
42/// use std::time::Duration;
43///
44/// let engine = GeneticEngine::builder()
45///     .codec(FloatChromosome::from((10, 0.0..1.0)))
46///     .fitness_fn(|vec: Vec<f32>| -vec.iter().map(|x| x * x).sum::<f32>())
47///     .build();
48///
49/// let mut generation = engine.iter().take(10).last().unwrap();
50///
51/// {
52///     // triggers a clone of the ecosystem if it is shared. it is in this case.
53///     let ecosystem: &Ecosystem<FloatChromosome> = generation.ecosystem();
54/// }
55///
56/// {
57///     // Would trigger a clone of the ecosystem if it is shared. It is NOT in this case
58///     // because it was just converted to an owned ecosystem above.
59///     let population: &Population<FloatChromosome> = generation.population();
60///     assert!(population.len() == 100);
61/// }
62///
63/// let solution: &Vec<f32> = generation.value();
64/// let index: usize = generation.index();
65/// let score: &Score = generation.score();
66/// let time: Duration = generation.time();
67///
68/// assert!(solution.len() == 10);
69/// assert!(index == 10);
70/// ```
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72pub struct Generation<C, T>
73where
74    C: Chromosome,
75{
76    ecosystem: EcosystemSnapshot<C>,
77    value: T,
78    index: usize,
79    metrics: MetricSet,
80    score: Score,
81    objective: Objective,
82    front: Option<Front<Phenotype<C>>>,
83}
84
85impl<C, T> Generation<C, T>
86where
87    C: Chromosome,
88{
89    pub fn score(&self) -> &Score {
90        &self.score
91    }
92
93    pub fn front(&self) -> Option<&Front<Phenotype<C>>> {
94        self.front.as_ref()
95    }
96
97    pub fn value(&self) -> &T {
98        &self.value
99    }
100
101    pub fn index(&self) -> usize {
102        self.index
103    }
104
105    pub fn metrics(&self) -> &MetricSet {
106        &self.metrics
107    }
108
109    pub fn objective(&self) -> &Objective {
110        &self.objective
111    }
112
113    /// Access the ecosystem, cloning it if it is shared. When this is called,
114    /// if the ecosystem is in the [EcosystemSnapshot::Shared] variant, it
115    /// will be cloned into the [EcosystemSnapshot::Owned] variant for future
116    /// accesses. When the generation is created from a [Context], the ecosystem
117    /// is always in the shared variant to avoid unnecessary cloning of the ecosystem.
118    pub fn ecosystem(&mut self) -> &Ecosystem<C>
119    where
120        C: Clone,
121    {
122        if let EcosystemSnapshot::Owned(ref eco) = self.ecosystem {
123            return eco;
124        } else if let EcosystemSnapshot::Shared(eco) = &self.ecosystem {
125            self.ecosystem = EcosystemSnapshot::Owned(eco.clone());
126        }
127
128        self.ecosystem()
129    }
130
131    /// Access the population from the ecosystem. Just like [Generation::ecosystem],
132    /// if the ecosystem is shared, it will be cloned on first access.
133    pub fn population(&mut self) -> &Population<C>
134    where
135        C: Clone,
136    {
137        &self.ecosystem().population()
138    }
139
140    /// Access the species from the ecosystem. Just like [Generation::ecosystem],
141    /// if the ecosystem is shared, it will be cloned on first access.
142    pub fn species(&mut self) -> Option<&[Species<C>]>
143    where
144        C: Clone,
145    {
146        self.ecosystem().species().map(|s| s.as_slice())
147    }
148
149    pub fn time(&self) -> Duration {
150        self.metrics()
151            .time()
152            .map(|m| m.time_statistic().map(|t| t.sum()))
153            .flatten()
154            .unwrap_or_default()
155    }
156
157    pub fn seconds(&self) -> f64 {
158        self.time().as_secs_f64()
159    }
160}
161
162impl<C: Chromosome, T> Scored for Generation<C, T> {
163    fn score(&self) -> Option<&Score> {
164        Some(&self.score)
165    }
166}
167
168impl<C, T> From<&Context<C, T>> for Generation<C, T>
169where
170    C: Chromosome + Clone,
171    T: Clone,
172{
173    fn from(context: &Context<C, T>) -> Self {
174        Generation {
175            ecosystem: EcosystemSnapshot::Shared(Ecosystem::clone_ref(&context.ecosystem)),
176            value: context.best.clone(),
177            index: context.index,
178            metrics: context.metrics.clone(),
179            score: context.score.clone().unwrap(),
180            objective: context.objective.clone(),
181            front: match context.objective {
182                Objective::Multi(_) => Some(context.front.read().unwrap().clone()),
183                _ => None,
184            },
185        }
186    }
187}
188
189impl<C, T> Clone for Generation<C, T>
190where
191    C: Chromosome + Clone,
192    T: Clone,
193{
194    fn clone(&self) -> Self {
195        Generation {
196            ecosystem: match &self.ecosystem {
197                EcosystemSnapshot::Owned(eco) => EcosystemSnapshot::Owned(eco.clone()),
198                EcosystemSnapshot::Shared(eco) => EcosystemSnapshot::Owned(eco.clone()),
199            },
200            value: self.value.clone(),
201            index: self.index,
202            metrics: self.metrics.clone(),
203            score: self.score.clone(),
204            objective: self.objective.clone(),
205            front: self.front.as_ref().map(|f| f.clone()),
206        }
207    }
208}
209
210impl<C, T> Debug for Generation<C, T>
211where
212    C: Chromosome,
213    T: Debug,
214{
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        let ecosystem = match &self.ecosystem {
217            EcosystemSnapshot::Owned(eco) => eco,
218            EcosystemSnapshot::Shared(eco) => eco,
219        };
220
221        write!(f, "Generation {{\n")?;
222        write!(f, "  metrics: {:?},\n", self.metrics)?;
223        write!(f, "  value: {:?},\n", self.value)?;
224        write!(f, "  score: {:?},\n", self.score)?;
225        write!(f, "  index: {:?},\n", self.index)?;
226        write!(f, "  size: {:?},\n", ecosystem.population().len())?;
227        write!(f, "  duration: {:?},\n", self.time())?;
228        write!(f, "  objective: {:?},\n", self.objective)?;
229
230        if let Some(species) = &ecosystem.species {
231            for s in species {
232                write!(f, "  species: {:?},\n", s)?;
233            }
234        }
235
236        write!(f, "}}")
237    }
238}
239
240impl<C, T> FromIterator<Generation<C, T>> for Front<Phenotype<C>>
241where
242    C: Chromosome + Clone,
243{
244    fn from_iter<I: IntoIterator<Item = Generation<C, T>>>(iter: I) -> Self {
245        iter.into_iter()
246            .last()
247            .map(|generation| generation.front().map(|front| front.clone()))
248            .flatten()
249            .unwrap_or_default()
250    }
251}