evolutionary 0.1.1

A fully extensible Rust framework for using paralyzed genetic algorithms to solve problems.
Documentation
use std::time::Duration;
use std::{collections::HashMap, time::Instant};

use crate::plotting::plot_chart;

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Steps {
    Selection,
    Crossover,
    Mutation,
    Fitness,
    Elitism,
}

pub struct Metrics {
    pub best_fitnesses: Vec<f64>,
    pub avg_fitnesses: Vec<f64>,
    pub iterations: u32,
    pub gens_without_improvement: u32,
    start_time: Instant,
    end_time: Instant,
    pub step_times: HashMap<Steps, (bool, Instant, u128)>,
}

impl Metrics {
    pub fn new() -> Self {
        let mut step_times = HashMap::new();
        step_times.insert(Steps::Selection, (false, Instant::now(), 0));
        step_times.insert(Steps::Crossover, (false, Instant::now(), 0));
        step_times.insert(Steps::Mutation, (false, Instant::now(), 0));
        step_times.insert(Steps::Fitness, (false, Instant::now(), 0));
        step_times.insert(Steps::Elitism, (false, Instant::now(), 0));

        Self {
            best_fitnesses: Vec::new(),
            avg_fitnesses: Vec::new(),
            iterations: 0,
            gens_without_improvement: 0,
            start_time: Instant::now(),
            end_time: Instant::now(),
            step_times,
        }
    }

    pub fn record(&mut self, best_fitness: f64, avg_fitness: f64) {
        if self.best_fitnesses.len() > 0
            && self.best_fitnesses[self.best_fitnesses.len() - 1] == best_fitness
        {
            self.gens_without_improvement += 1;
        } else {
            self.gens_without_improvement = 0;
        }

        self.best_fitnesses.push(best_fitness);
        self.avg_fitnesses.push(avg_fitness);

        self.iterations += 1;
    }

    pub fn start_clock(&mut self) {
        self.start_time = Instant::now();
    }

    pub fn end_clock(&mut self) {
        self.end_time = Instant::now();
    }

    pub fn step_start(&mut self, step: Steps) {
        self.step_times.get_mut(&step).map(|a| {
            if a.0 {
                panic!("Step already started");
            }
            a.0 = true;
            a.1 = Instant::now();
        });
    }

    pub fn step_end(&mut self, step: Steps) {
        self.step_times.get_mut(&step).map(|a| {
            if !a.0 {
                panic!("Step not started");
            }
            a.0 = false;
            a.2 += a.1.elapsed().as_nanos();
        });
    }

    pub fn step_time(&self, step: Steps) -> Option<Duration> {
        self.step_times
            .get(&step)
            .map(|a| Duration::from_nanos(a.2 as u64))
    }

    pub fn total_time(&self) -> u128 {
        self.end_time.duration_since(self.start_time).as_nanos()
    }

    pub fn plot_chart(
        &self,
        path: &String,
        test_name: &String,
    ) -> Result<(), Box<dyn std::error::Error>> {
        plot_chart(&self.best_fitnesses, &self.avg_fitnesses, path, test_name)
    }
}