Skip to main content

radiate_core/
replacement.rs

1use super::{Chromosome, Genotype, Population, random_provider};
2use crate::{Ecosystem, MetricSet, Phenotype, error::RadiateResult, metric_names};
3use radiate_error::radiate_bail;
4use radiate_expr::Expr;
5use radiate_utils::{AnyValue, DataType};
6use std::{collections::HashSet, sync::Arc};
7
8/// Trait for replacement strategies in the algorithms.
9///
10/// This trait defines a method for replacing a member of the [Population] with a new individual
11/// after the current individual has been determined to be invalid. Typically, this is done by
12/// replacing the individual with a new one generated by the encoder. But in some cases, it may
13/// be desirable to replace the individual in a different way, such as by sampling from the
14/// [Population].
15pub trait ReplacementStrategy<C: Chromosome>: Send + Sync {
16    fn replace(
17        &self,
18        population: &Population<C>,
19        encoder: Arc<dyn Fn() -> Genotype<C> + Send + Sync>,
20    ) -> Genotype<C>;
21
22    fn replace_at(
23        &self,
24        _: usize,
25        population: &Population<C>,
26        encoder: Arc<dyn Fn() -> Genotype<C> + Send + Sync>,
27    ) -> Genotype<C> {
28        self.replace(population, encoder)
29    }
30}
31
32/// Replacement strategy that replaces the individual with a new one generated by the encoder.
33/// This is the default replacement strategy used in genetic algorithms.
34pub struct EncodeReplace;
35
36impl<C: Chromosome> ReplacementStrategy<C> for EncodeReplace {
37    fn replace(
38        &self,
39        _: &Population<C>,
40        encoder: Arc<dyn Fn() -> Genotype<C> + Send + Sync>,
41    ) -> Genotype<C> {
42        encoder()
43    }
44}
45
46/// Replacement strategy that replaces the individual with a random member of the [Population].
47/// This can be useful in cases where the [Population] is large and diverse or when the
48/// chromosome grows or changes in size, thus encoding a new individual can result
49/// in a member that that lacks significant diversity.
50pub struct PopulationSampleReplace;
51
52impl<C: Chromosome + Clone> ReplacementStrategy<C> for PopulationSampleReplace {
53    fn replace(
54        &self,
55        population: &Population<C>,
56        _: Arc<dyn Fn() -> Genotype<C> + Send + Sync>,
57    ) -> Genotype<C> {
58        let random_member = random_provider::range(0..population.len());
59        population[random_member].genotype().clone()
60    }
61}
62
63pub trait EcosystemFilter<C: Chromosome>: Send + Sync {
64    fn filter(
65        &mut self,
66        generation: usize,
67        ecosystem: &mut Ecosystem<C>,
68        metrics: &mut MetricSet,
69        replacer: Arc<dyn ReplacementStrategy<C>>,
70        encoder: Arc<dyn Fn() -> Genotype<C> + Send + Sync>,
71    ) -> RadiateResult<()>;
72}
73
74pub struct UniqueScoreFilter {
75    filter_cond: Expr,
76}
77
78impl UniqueScoreFilter {
79    pub fn new(max_stagnation: usize, threshold: f32) -> Self {
80        Self {
81            filter_cond: Expr::select(metric_names::BEST_SCORES)
82                .stagnation(threshold)
83                .cast(DataType::Usize)
84                .gt(max_stagnation),
85        }
86    }
87}
88
89impl<C: Chromosome> EcosystemFilter<C> for UniqueScoreFilter {
90    fn filter(
91        &mut self,
92        generation: usize,
93        ecosystem: &mut Ecosystem<C>,
94        metrics: &mut MetricSet,
95        replacer: Arc<dyn ReplacementStrategy<C>>,
96        encoder: Arc<dyn Fn() -> Genotype<C> + Send + Sync>,
97    ) -> RadiateResult<()> {
98        let should_filter = self.filter_cond.evaluate(metrics)?;
99
100        let AnyValue::Bool(bool) = should_filter else {
101            radiate_bail!(Engine: "Filter condition must evaluate to a boolean value, but got: {:?}", should_filter);
102        };
103
104        if !bool {
105            metrics.upsert(metric_names::FILTER_UNIQUE_SCORES, 0);
106            return Ok(());
107        }
108
109        let mut unique_scores = HashSet::new();
110        let mut replaced_count = 0;
111
112        for i in 0..ecosystem.population.len() {
113            if let Some(phenotype) = ecosystem.get_phenotype(i) {
114                let score = match phenotype.score() {
115                    Some(score) => score.clone(),
116                    None => continue,
117                };
118
119                if !unique_scores.insert(score) {
120                    replaced_count += 1;
121                    let new_genotype =
122                        replacer.replace_at(i, ecosystem.population(), Arc::clone(&encoder));
123                    if let Some(phenotype_mut) = ecosystem.get_phenotype_mut(i) {
124                        *phenotype_mut = Phenotype::from((new_genotype, generation));
125                    }
126                }
127            }
128        }
129
130        metrics.upsert(metric_names::FILTER_UNIQUE_SCORES, replaced_count);
131
132        Ok(())
133    }
134}