radiate_engines/builder/
mod.rs

1mod alters;
2mod evaluators;
3mod objectives;
4mod population;
5mod problem;
6mod selectors;
7mod species;
8
9use crate::Result;
10use crate::builder::evaluators::EvaluationParams;
11use crate::builder::objectives::OptimizeParams;
12use crate::builder::population::PopulationParams;
13use crate::builder::problem::ProblemParams;
14use crate::builder::selectors::SelectionParams;
15use crate::builder::species::SpeciesParams;
16use crate::genome::phenotype::Phenotype;
17use crate::genome::population::Population;
18use crate::objectives::{Objective, Optimize};
19use crate::pipeline::Pipeline;
20use crate::steps::{AuditStep, EngineStep, FilterStep, FrontStep, RecombineStep, SpeciateStep};
21use crate::{
22    Alter, Crossover, EncodeReplace, EngineProblem, EventBus, EventHandler, Front, Mutate, Problem,
23    ReplacementStrategy, RouletteSelector, Select, TournamentSelector, context::Context, pareto,
24};
25use crate::{Chromosome, EvaluateStep, GeneticEngine};
26use radiate_alters::{UniformCrossover, UniformMutator};
27use radiate_core::evaluator::BatchFitnessEvaluator;
28use radiate_core::problem::BatchEngineProblem;
29use radiate_core::{Diversity, Evaluator, Executor, FitnessEvaluator, Genotype, Valid};
30use radiate_core::{RadiateError, ensure, radiate_err};
31use std::cmp::Ordering;
32use std::sync::{Arc, Mutex, RwLock};
33
34#[derive(Clone)]
35pub struct EngineParams<C, T>
36where
37    C: Chromosome + 'static,
38    T: Clone + 'static,
39{
40    pub population_params: PopulationParams<C>,
41    pub evaluation_params: EvaluationParams<C, T>,
42    pub species_params: SpeciesParams<C>,
43    pub selection_params: SelectionParams<C>,
44    pub optimization_params: OptimizeParams<C>,
45    pub problem_params: ProblemParams<C, T>,
46
47    pub alterers: Vec<Arc<dyn Alter<C>>>,
48    pub replacement_strategy: Arc<dyn ReplacementStrategy<C>>,
49    pub handlers: Vec<Arc<Mutex<dyn EventHandler<T>>>>,
50}
51
52/// Parameters for the genetic engine.
53/// This struct is used to configure the genetic engine before it is created.
54///
55/// When the `GeneticEngineParams`  calls the `build` method, it will create a new instance
56/// of the `GeneticEngine` with the given parameters. If any of the required parameters are not
57/// set, the `build` method will panic. At a minimum, the `codec` and `fitness_fn` must be set.
58/// The `GeneticEngineParams` struct is a builder pattern that allows you to set the parameters of
59/// the `GeneticEngine` in a fluent and functional way.
60///
61/// # Type Parameters
62/// - `C`: The type of chromosome used in the genotype, which must implement the `Chromosome` trait.
63/// - `T`: The type of the best individual in the population.
64pub struct GeneticEngineBuilder<C, T>
65where
66    C: Chromosome + Clone + 'static,
67    T: Clone + 'static,
68{
69    params: EngineParams<C, T>,
70    errors: Vec<RadiateError>,
71}
72
73impl<C, T> GeneticEngineBuilder<C, T>
74where
75    C: Chromosome + PartialEq + Clone,
76    T: Clone + Send,
77{
78    pub(self) fn add_error_if<F>(&mut self, condition: F, message: &str)
79    where
80        F: Fn() -> bool,
81    {
82        if condition() {
83            self.errors.push(radiate_err!(Builder: "{}", message));
84        }
85    }
86    /// The [ReplacementStrategy] is used to determine how a new individual is added to the [Population]
87    /// if an individual is deemed to be either invalid or reaches the maximum age.
88    ///
89    /// Default is [EncodeReplace], which means that a new individual will be created
90    /// be using the `Codec` to encode a new individual from scratch.
91    pub fn replace_strategy<R: ReplacementStrategy<C> + 'static>(mut self, replace: R) -> Self {
92        self.params.replacement_strategy = Arc::new(replace);
93        self
94    }
95
96    /// Subscribe to engine events with the given event handler.
97    /// The event handler will be called whenever an event is emitted by the engine.
98    /// You can use this to log events, or to perform custom actions
99    /// based on the events emitted by the engine.
100    pub fn subscribe<H>(mut self, handler: H) -> Self
101    where
102        H: EventHandler<T> + 'static,
103    {
104        self.params.handlers.push(Arc::new(Mutex::new(handler)));
105        self
106    }
107}
108
109/// Static step builder for the genetic engine.
110impl<C, T> GeneticEngineBuilder<C, T>
111where
112    C: Chromosome + Clone + PartialEq + 'static,
113    T: Clone + Send + Sync + 'static,
114{
115    /// Build the genetic engine with the given parameters. This will create a new
116    /// instance of the `GeneticEngine` with the given parameters.
117    pub fn build(self) -> GeneticEngine<C, T> {
118        match self.try_build() {
119            Ok(engine) => engine,
120            Err(e) => panic!("{e}"),
121        }
122    }
123
124    pub fn try_build(mut self) -> Result<GeneticEngine<C, T>> {
125        if !self.errors.is_empty() {
126            return Err(radiate_err!(
127                Builder: "Failed to build GeneticEngine: {:?}",
128                self.errors
129            ));
130        }
131
132        self.build_problem()?;
133        self.build_population()?;
134        self.build_alterer()?;
135        self.build_front()?;
136
137        let config = EngineConfig::<C, T>::from(&self.params);
138
139        let mut pipeline = Pipeline::<C>::default();
140
141        pipeline.add_step(Self::build_eval_step(&config));
142        pipeline.add_step(Self::build_recombine_step(&config));
143        pipeline.add_step(Self::build_filter_step(&config));
144        pipeline.add_step(Self::build_eval_step(&config));
145        pipeline.add_step(Self::build_front_step(&config));
146        pipeline.add_step(Self::build_species_step(&config));
147        pipeline.add_step(Self::build_audit_step(&config));
148
149        let event_bus = EventBus::new(config.bus_executor(), config.handlers());
150        let context = Context::from(config);
151
152        Ok(GeneticEngine::<C, T>::new(context, pipeline, event_bus))
153    }
154
155    fn build_eval_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
156        let evaluator = config.evaluator.clone();
157
158        let eval_step = EvaluateStep {
159            objective: config.objective.clone(),
160            problem: config.problem.clone(),
161            evaluator: evaluator.clone(),
162        };
163
164        Some(Box::new(eval_step))
165    }
166
167    fn build_recombine_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
168        let recombine_step = RecombineStep {
169            survivor_selector: config.survivor_selector(),
170            offspring_selector: config.offspring_selector(),
171            alters: config.alters().to_vec(),
172            survivor_count: config.survivor_count(),
173            offspring_count: config.offspring_count(),
174            objective: config.objective(),
175        };
176
177        Some(Box::new(recombine_step))
178    }
179
180    fn build_filter_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
181        let filter_step = FilterStep {
182            replacer: config.replacement_strategy(),
183            encoder: config.encoder(),
184            max_age: config.max_age(),
185            max_species_age: config.max_species_age(),
186        };
187
188        Some(Box::new(filter_step))
189    }
190
191    fn build_audit_step(_: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
192        Some(Box::new(AuditStep::default()))
193    }
194
195    fn build_front_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
196        if config.objective().is_single() {
197            return None;
198        }
199
200        let front_step = FrontStep {
201            front: config.front().clone(),
202        };
203
204        Some(Box::new(front_step))
205    }
206
207    fn build_species_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
208        if config.diversity().is_none() {
209            return None;
210        }
211
212        let species_step = SpeciateStep {
213            threashold: config.species_threshold(),
214            diversity: config.diversity().clone().unwrap(),
215            executor: config.executor().species_executor.clone(),
216            objective: config.objective(),
217        };
218
219        Some(Box::new(species_step))
220    }
221
222    /// Build the problem of the genetic engine. This will create a new problem
223    /// using the codec and fitness function if the problem is not set. If the
224    /// problem is already set, this function will do nothing. Else, if the fitness function is
225    /// a batch fitness function, it will create a new `BatchFitnessProblem` and swap the evaluator
226    /// to use a `BatchFitnessEvaluator`.
227    fn build_problem(&mut self) -> Result<()> {
228        if self.params.problem_params.problem.is_some() {
229            return Ok(());
230        }
231
232        ensure!(
233            self.params.problem_params.codec.is_some(),
234            Builder: "Codec not set"
235        );
236
237        if let Some(batch_fn) = &self.params.problem_params.batch_fitness_fn {
238            self.params.problem_params.problem = Some(Arc::new(BatchEngineProblem {
239                objective: self.params.optimization_params.objectives.clone(),
240                codec: self.params.problem_params.codec.clone().unwrap(),
241                batch_fitness_fn: batch_fn.clone(),
242            }));
243
244            // Replace the evaluator with BatchFitnessEvaluator
245            self.params.evaluation_params.evaluator = Arc::new(BatchFitnessEvaluator::new(
246                self.params.evaluation_params.fitness_executor.clone(),
247            ));
248
249            Ok(())
250        } else if let Some(fitness_fn) = &self.params.problem_params.fitness_fn {
251            self.params.problem_params.problem = Some(Arc::new(EngineProblem {
252                objective: self.params.optimization_params.objectives.clone(),
253                codec: self.params.problem_params.codec.clone().unwrap(),
254                fitness_fn: fitness_fn.clone(),
255            }));
256
257            Ok(())
258        } else {
259            Err(radiate_err!(Builder: "Fitness function not set"))
260        }
261    }
262
263    /// Build the population of the genetic engine. This will create a new population
264    /// using the codec if the population is not set.
265    fn build_population(&mut self) -> Result<()> {
266        self.params.population_params.population = match &self.params.population_params.population {
267            None => Some(match self.params.problem_params.problem.as_ref() {
268                Some(problem) => {
269                    let size = self.params.population_params.population_size;
270                    let mut phenotypes = Vec::with_capacity(size);
271
272                    for _ in 0..size {
273                        let genotype = problem.encode();
274
275                        if !genotype.is_valid() {
276                            return Err(radiate_err!(
277                                Builder: "Encoded genotype is not valid",
278                            ));
279                        }
280
281                        phenotypes.push(Phenotype::from((genotype, 0)));
282                    }
283
284                    Population::from(phenotypes)
285                }
286                None => return Err(radiate_err!(Builder: "Codec not set")),
287            }),
288            Some(pop) => Some(pop.clone()),
289        };
290
291        Ok(())
292    }
293
294    /// Build the alterer of the genetic engine. This will create a
295    /// new `UniformCrossover` and `UniformMutator` if the alterer is not set.
296    /// with a 0.5 crossover rate and a 0.1 mutation rate.
297    fn build_alterer(&mut self) -> Result<()> {
298        if !self.params.alterers.is_empty() {
299            for alter in self.params.alterers.iter() {
300                if !(0.0..=1.0).contains(&alter.rate()) {
301                    return Err(radiate_err!(
302                        Builder: "Alterer rate must be between 0 and 1 - found {}", alter.rate()
303                    ));
304                }
305            }
306
307            return Ok(());
308        }
309
310        let crossover = Arc::new(UniformCrossover::new(0.5).alterer()) as Arc<dyn Alter<C>>;
311        let mutator = Arc::new(UniformMutator::new(0.1).alterer()) as Arc<dyn Alter<C>>;
312
313        self.params.alterers.push(crossover);
314        self.params.alterers.push(mutator);
315
316        Ok(())
317    }
318
319    /// Build the pareto front of the genetic engine. This will create a new `Front`
320    /// if the front is not set. The `Front` is used to store the best individuals
321    /// in the population and is used for multi-objective optimization problems.
322    fn build_front(&mut self) -> Result<()> {
323        if self.params.optimization_params.front.is_some() {
324            return Ok(());
325        }
326
327        let front_obj = self.params.optimization_params.objectives.clone();
328        self.params.optimization_params.front = Some(Front::new(
329            self.params.optimization_params.front_range.clone(),
330            front_obj.clone(),
331            move |one: &Phenotype<C>, two: &Phenotype<C>| {
332                if one.score().is_none() || two.score().is_none() {
333                    return Ordering::Equal;
334                }
335
336                if let (Some(one), Some(two)) = (one.score(), two.score()) {
337                    if pareto::dominance(one, two, &front_obj) {
338                        return Ordering::Greater;
339                    } else if pareto::dominance(two, one, &front_obj) {
340                        return Ordering::Less;
341                    }
342                }
343
344                Ordering::Equal
345            },
346        ));
347
348        Ok(())
349    }
350}
351
352impl<C, T> Default for GeneticEngineBuilder<C, T>
353where
354    C: Chromosome + Clone + 'static,
355    T: Clone + Send + 'static,
356{
357    fn default() -> Self {
358        GeneticEngineBuilder {
359            params: EngineParams {
360                population_params: PopulationParams {
361                    population_size: 100,
362                    max_age: 20,
363                    population: None,
364                },
365                species_params: SpeciesParams {
366                    diversity: None,
367                    species_threshold: 0.5,
368                    max_species_age: 25,
369                },
370                evaluation_params: EvaluationParams {
371                    evaluator: Arc::new(FitnessEvaluator::default()),
372                    fitness_executor: Arc::new(Executor::default()),
373                    species_executor: Arc::new(Executor::default()),
374                    bus_executor: Arc::new(Executor::default()),
375                },
376                selection_params: SelectionParams {
377                    offspring_fraction: 0.8,
378                    survivor_selector: Arc::new(TournamentSelector::new(3)),
379                    offspring_selector: Arc::new(RouletteSelector::new()),
380                },
381                optimization_params: OptimizeParams {
382                    objectives: Objective::Single(Optimize::Maximize),
383                    front_range: 800..900,
384                    front: None,
385                },
386                problem_params: ProblemParams {
387                    codec: None,
388                    problem: None,
389                    fitness_fn: None,
390                    batch_fitness_fn: None,
391                },
392
393                replacement_strategy: Arc::new(EncodeReplace),
394                alterers: Vec::new(),
395                handlers: Vec::new(),
396            },
397            errors: Vec::new(),
398        }
399    }
400}
401
402#[derive(Clone)]
403pub(crate) struct EngineConfig<C: Chromosome, T: Clone> {
404    population: Population<C>,
405    problem: Arc<dyn Problem<C, T>>,
406    survivor_selector: Arc<dyn Select<C>>,
407    offspring_selector: Arc<dyn Select<C>>,
408    replacement_strategy: Arc<dyn ReplacementStrategy<C>>,
409    alterers: Vec<Arc<dyn Alter<C>>>,
410    species_threshold: f32,
411    diversity: Option<Arc<dyn Diversity<C>>>,
412    evaluator: Arc<dyn Evaluator<C, T>>,
413    objective: Objective,
414    max_age: usize,
415    max_species_age: usize,
416    front: Arc<RwLock<Front<Phenotype<C>>>>,
417    offspring_fraction: f32,
418    executor: EvaluationParams<C, T>,
419    handlers: Vec<Arc<Mutex<dyn EventHandler<T>>>>,
420}
421
422impl<C: Chromosome, T: Clone> EngineConfig<C, T> {
423    pub fn population(&self) -> &Population<C> {
424        &self.population
425    }
426
427    pub fn survivor_selector(&self) -> Arc<dyn Select<C>> {
428        Arc::clone(&self.survivor_selector)
429    }
430
431    pub fn offspring_selector(&self) -> Arc<dyn Select<C>> {
432        Arc::clone(&self.offspring_selector)
433    }
434
435    pub fn replacement_strategy(&self) -> Arc<dyn ReplacementStrategy<C>> {
436        Arc::clone(&self.replacement_strategy)
437    }
438
439    pub fn alters(&self) -> &[Arc<dyn Alter<C>>] {
440        &self.alterers
441    }
442
443    pub fn objective(&self) -> Objective {
444        self.objective.clone()
445    }
446
447    pub fn max_age(&self) -> usize {
448        self.max_age
449    }
450
451    pub fn max_species_age(&self) -> usize {
452        self.max_species_age
453    }
454
455    pub fn species_threshold(&self) -> f32 {
456        self.species_threshold
457    }
458
459    pub fn diversity(&self) -> Option<Arc<dyn Diversity<C>>> {
460        self.diversity.clone()
461    }
462
463    pub fn front(&self) -> Arc<RwLock<Front<Phenotype<C>>>> {
464        Arc::clone(&self.front)
465    }
466
467    pub fn survivor_count(&self) -> usize {
468        self.population.len() - self.offspring_count()
469    }
470
471    pub fn offspring_count(&self) -> usize {
472        (self.population.len() as f32 * self.offspring_fraction) as usize
473    }
474
475    pub fn executor(&self) -> &EvaluationParams<C, T> {
476        &self.executor
477    }
478
479    pub fn bus_executor(&self) -> Arc<Executor> {
480        Arc::clone(&self.executor.bus_executor)
481    }
482
483    pub fn handlers(&self) -> Vec<Arc<Mutex<dyn EventHandler<T>>>> {
484        self.handlers.clone()
485    }
486
487    pub fn problem(&self) -> Arc<dyn Problem<C, T>> {
488        Arc::clone(&self.problem)
489    }
490
491    pub fn encoder(&self) -> Arc<dyn Fn() -> Genotype<C> + Send + Sync>
492    where
493        C: 'static,
494        T: 'static,
495    {
496        let problem = Arc::clone(&self.problem);
497        Arc::new(move || problem.encode())
498    }
499}
500
501impl<C, T> From<&EngineParams<C, T>> for EngineConfig<C, T>
502where
503    C: Chromosome + Clone + 'static,
504    T: Clone + Send + Sync + 'static,
505{
506    fn from(params: &EngineParams<C, T>) -> Self {
507        Self {
508            population: params.population_params.population.clone().unwrap(),
509            problem: params.problem_params.problem.clone().unwrap(),
510            survivor_selector: params.selection_params.survivor_selector.clone(),
511            offspring_selector: params.selection_params.offspring_selector.clone(),
512            replacement_strategy: params.replacement_strategy.clone(),
513            alterers: params.alterers.clone(),
514            objective: params.optimization_params.objectives.clone(),
515            max_age: params.population_params.max_age,
516            max_species_age: params.species_params.max_species_age,
517            species_threshold: params.species_params.species_threshold,
518            diversity: params.species_params.diversity.clone(),
519            front: Arc::new(RwLock::new(
520                params.optimization_params.front.clone().unwrap(),
521            )),
522            offspring_fraction: params.selection_params.offspring_fraction,
523            evaluator: params.evaluation_params.evaluator.clone(),
524            executor: params.evaluation_params.clone(),
525            handlers: params.handlers.clone(),
526        }
527    }
528}