use crate::collector::Collector;
use crate::core::population::Population;
use crate::core::state::State;
use crate::fitness::{FitnessComparator, FitnessEvaluator};
use std::time::{Duration, Instant};
pub struct Standard<F> {
start_time: Option<Instant>,
best_fitness: Vec<F>,
generation_durations: Vec<Duration>,
last_tick: Instant,
}
impl<F> Default for Standard<F> {
fn default() -> Self {
Self {
start_time: None,
best_fitness: Vec::new(),
generation_durations: Vec::new(),
last_tick: Instant::now(),
}
}
}
#[derive(Debug)]
pub struct RunResult<G, F> {
population: Population<G, F>,
generations: usize,
total_duration: Duration,
best_fitness: Vec<F>,
generation_durations: Vec<Duration>,
}
impl<G, F> RunResult<G, F> {
pub fn population(&self) -> &Population<G, F> {
&self.population
}
pub fn generations(&self) -> usize {
self.generations
}
pub fn total_duration(&self) -> Duration {
self.total_duration
}
pub fn best_fitness(&self) -> &[F] {
&self.best_fitness
}
pub fn generation_durations(&self) -> &[Duration] {
&self.generation_durations
}
pub fn into_population(self) -> Population<G, F> {
self.population
}
pub fn generation(&self, index: usize) -> Option<GenerationRecord<'_, F>> {
if index >= self.best_fitness.len() {
return None;
}
Some(GenerationRecord {
best_fitness: &self.best_fitness[index],
duration: self.generation_durations[index],
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct GenerationRecord<'a, F> {
best_fitness: &'a F,
duration: Duration,
}
impl<'a, F> GenerationRecord<'a, F> {
pub fn best_fitness(&self) -> &F {
self.best_fitness
}
pub fn duration(&self) -> Duration {
self.duration
}
}
impl<G, F, Fe, C> Collector<G, F, Fe, C> for Standard<F>
where
F: Clone + PartialOrd,
Fe: FitnessEvaluator<G, F>,
C: FitnessComparator<F>,
{
type Result = RunResult<G, F>;
fn on_start(&mut self, _state: &State<G, F>, _fe: &Fe, _cmp: &C) {
let now = Instant::now();
self.start_time = Some(now);
self.last_tick = now;
}
fn on_generation(&mut self, state: &State<G, F>, fe: &Fe, cmp: &C) {
let now = Instant::now();
self.generation_durations.push(now - self.last_tick);
self.last_tick = now;
let best = state.population().best(fe, cmp);
self.best_fitness.push(best.fitness(fe).clone());
}
fn finalize(self, state: State<G, F>) -> Self::Result {
let total_duration = self
.start_time
.map(|s| s.elapsed())
.unwrap_or(Duration::ZERO);
RunResult {
generations: state.generation(),
population: state.into_population(),
total_duration,
best_fitness: self.best_fitness,
generation_durations: self.generation_durations,
}
}
}
#[cfg(test)]
mod test {
use crate::{
algorithm::EvolutionaryAlgorithm,
fitness::Maximize,
initialization::Random,
operators::sequential::{combinator::Fill, mutation::RandomReset},
termination::MaxGenerations,
};
use std::num::NonZero;
fn run_standard() -> super::RunResult<[u8; 2], u16> {
let mut ga = EvolutionaryAlgorithm::new(
Random::new(),
MaxGenerations::new(10),
|g: &[u8; 2]| g[0] as u16 + g[1] as u16,
Fill::from_population_size(RandomReset::new()),
NonZero::new(20).unwrap(),
rand::rng(),
Maximize,
);
ga.run()
}
#[test]
fn correct_generation_count() {
let result = run_standard();
assert_eq!(result.generations(), 10);
}
#[test]
fn best_fitness_has_one_entry_per_generation() {
let result = run_standard();
assert_eq!(result.best_fitness().len(), 10);
}
#[test]
fn generation_durations_has_one_entry_per_generation() {
let result = run_standard();
assert_eq!(result.generation_durations().len(), 10);
}
#[test]
fn generation_returns_correct_data() {
let result = run_standard();
let record = result.generation(0).unwrap();
assert_eq!(record.best_fitness(), &result.best_fitness()[0]);
assert_eq!(record.duration(), result.generation_durations()[0]);
}
#[test]
fn generation_out_of_bounds_returns_none() {
let result = run_standard();
assert!(result.generation(10).is_none());
assert!(result.generation(100).is_none());
}
}