radiate-ui 1.3.1

A Rust library for genetic algorithms and artificial evolution.
Documentation
use super::chart::{ChartState, MetricChartType};
use crate::chart::RollingLineChart;
use crate::widgets::num_pairs;
use radiate_engines::{
    Chromosome, Ecosystem, Front, MetricSet, Objective, Optimize, Phenotype, Score, Species,
    events::LogLevel,
};
use radiate_utils::WindowBuffer;
use std::sync::{Arc, RwLock};

const MAX_IMPROVEMENT_LOG: usize = 100;
const MAX_EVENT_LOG: usize = 200;

pub struct ImprovementEntry {
    pub generation: usize,
    pub score: f32,
    pub delta: f32,
}

pub struct EventLogEntry {
    pub generation: usize,
    pub level: LogLevel,
    pub message: String,
}

pub struct FrontEventEntry {
    pub generation: usize,
    pub front_size: usize,
    pub additions: usize,
    pub removals: usize,
    pub comparisons: usize,
    pub filtered: bool,
}

pub struct ObjectiveState {
    pub objective: Objective,
    pub charts_visible: usize,
    pub chart_start_index: usize,
    pub objective_index: usize,
}

#[allow(dead_code)]
pub struct EvoState<C: Chromosome> {
    pub best_phenotype: Option<Phenotype<C>>,
    pub ecosystem: Option<Ecosystem<C>>,
    pub front: Arc<RwLock<Front<Phenotype<C>>>>,
    pub metrics: MetricSet,
    pub charts: ChartState,
    pub index: usize,
    pub score: Score,
    pub best_score: Score,
    pub pareto: ObjectiveState,
    pub improvement_log: WindowBuffer<ImprovementEntry>,
    pub front_event_log: WindowBuffer<FrontEventEntry>,
    pub event_log: WindowBuffer<EventLogEntry>,
}

impl<C: Chromosome> EvoState<C> {
    pub fn update_score(&mut self, new_score: Score) {
        if self.score.is_empty() {
            self.score = new_score.clone();
            return;
        }

        if self.pareto.objective.is_single() {
            let prev = self.score.as_f32();
            let next = new_score.as_f32();
            let delta = match &self.pareto.objective {
                Objective::Single(Optimize::Minimize) => prev - next,
                _ => next - prev,
            };

            if delta > 0.0 {
                self.best_score = new_score.clone();
                self.improvement_log.push_front(ImprovementEntry {
                    generation: self.index,
                    score: next,
                    delta,
                });
            }
        }

        self.score = new_score;
    }

    pub fn update_index(&mut self, index: usize) {
        self.index = index;
    }

    pub fn update_ecosystem(&mut self, ecosystem: Ecosystem<C>)
    where
        C: Clone,
    {
        self.ecosystem = Some(ecosystem);
        let phenotype = self
            .ecosystem
            .as_ref()
            .and_then(|eco| eco.get_phenotype(0))
            .cloned();
        self.best_phenotype = phenotype;
    }

    pub fn update_metrics(&mut self, metrics: MetricSet) {
        for metric in metrics.iter() {
            self.charts.update_from_metric(metric);
        }

        self.metrics = metrics;
        self.update_front_events();
    }

    fn update_front_events(&mut self) {
        if self.pareto.objective.is_single() {
            return;
        }

        let additions = self
            .metrics
            .front_additions()
            .map(|m| m.last_value() as usize)
            .unwrap_or(0);

        if additions == 0 {
            return;
        }
        let removals = self
            .metrics
            .front_removals()
            .map(|m| m.last_value() as usize)
            .unwrap_or(0);

        let front_size = self
            .metrics
            .front_size()
            .map(|m| m.last_value() as usize)
            .unwrap_or(0);

        let front_comparisons = self
            .metrics
            .front_comparisons()
            .map(|m| m.last_value() as usize)
            .unwrap_or(0);

        let front_filters = self
            .metrics
            .front_filters()
            .map(|m| m.last_value() > 0.0)
            .unwrap_or(false);

        self.front_event_log.push_front(FrontEventEntry {
            generation: self.index,
            front_size,
            additions,
            removals,
            comparisons: front_comparisons,
            filtered: front_filters,
        });
    }

    pub fn push_event_log_entry(&mut self, level: LogLevel, message: String) {
        self.event_log.push_front(EventLogEntry {
            generation: self.index,
            level,
            message,
        });
    }

    pub fn get_chart_by_key(
        &self,
        key: &str,
        chart_type: MetricChartType,
    ) -> Option<&RollingLineChart> {
        self.charts.get_line_chart(key, chart_type)
    }

    pub fn get_species(&self) -> Option<&Vec<Species<C>>> {
        self.ecosystem.as_ref().and_then(|eco| eco.species())
    }

    pub fn is_multi(&self) -> bool {
        !self.pareto.objective.is_single()
    }

    pub fn has_species(&self) -> bool {
        self.get_species()
            .is_some_and(|species| !species.is_empty())
    }

    pub fn set_objective_index(&mut self, index: usize) {
        if index < self.pareto.objective.dims() {
            self.pareto.objective_index = index;
        }
    }

    pub fn expand_objective_pairs(&mut self) {
        self.pareto.charts_visible = self
            .pareto
            .charts_visible
            .saturating_add(1)
            .min(num_pairs(self.pareto.objective.dims()));
    }

    pub fn shrink_objective_pairs(&mut self) {
        if self.pareto.charts_visible > 1 {
            self.pareto.charts_visible -= 1;
        }
    }

    pub fn next_objective_pair_page(&mut self) {
        let step = self.pareto.charts_visible.max(1);
        let total = num_pairs(self.pareto.objective.dims());
        let current = self.pareto.chart_start_index;
        if current + step < total {
            self.pareto.chart_start_index += step;
        }
    }

    pub fn previous_objective_pair_page(&mut self) {
        let step = self.pareto.charts_visible.max(1);
        let current = self.pareto.chart_start_index;
        if current >= step {
            self.pareto.chart_start_index -= step;
        } else {
            self.pareto.chart_start_index = 0;
        }
    }
}

impl<C: Chromosome> Default for EvoState<C> {
    fn default() -> Self {
        Self {
            best_phenotype: None,
            front: Arc::new(RwLock::new(Front::default())),
            metrics: MetricSet::new(),
            charts: ChartState::new(),
            ecosystem: None,
            index: 0,
            score: Score::default(),
            best_score: Score::default(),
            improvement_log: WindowBuffer::with_capacity(MAX_IMPROVEMENT_LOG),
            front_event_log: WindowBuffer::with_capacity(MAX_IMPROVEMENT_LOG),
            event_log: WindowBuffer::with_capacity(MAX_EVENT_LOG),
            pareto: ObjectiveState {
                objective: Objective::Single(Optimize::Maximize),
                charts_visible: 2,
                chart_start_index: 0,
                objective_index: 0,
            },
        }
    }
}