Skip to main content

radiate_engines/builder/
mod.rs

1mod alters;
2mod evaluators;
3mod objectives;
4mod population;
5mod problem;
6mod selectors;
7mod species;
8
9use crate::builder::evaluators::EvaluationParams;
10use crate::builder::objectives::OptimizeParams;
11use crate::builder::population::PopulationParams;
12use crate::builder::problem::ProblemParams;
13use crate::builder::selectors::SelectionParams;
14use crate::builder::species::SpeciesParams;
15use crate::genome::phenotype::Phenotype;
16#[cfg(feature = "serde")]
17use crate::io::CheckpointReader;
18use crate::objectives::{Objective, Optimize};
19use crate::pipeline::Pipeline;
20use crate::steps::{AuditStep, EngineStep, FilterStep, FrontStep, RecombineStep, SpeciateStep};
21use crate::{Chromosome, EvaluateStep, GeneticEngine};
22use crate::{
23    Crossover, EncodeReplace, EngineProblem, EventBus, EventHandler, Front, Mutate, Problem,
24    ReplacementStrategy, RouletteSelector, Select, TournamentSelector, context::Context,
25};
26use crate::{Generation, Result};
27use radiate_alters::{UniformCrossover, UniformMutator};
28use radiate_core::evaluator::BatchFitnessEvaluator;
29use radiate_core::problem::BatchEngineProblem;
30use radiate_core::{
31    Alterer, Diversity, Ecosystem, Evaluator, Executor, FitnessEvaluator, Genotype, Lineage, Rate,
32    Valid,
33};
34use radiate_core::{RadiateError, ensure, radiate_err};
35use radiate_expr::NamedExpr;
36#[cfg(feature = "serde")]
37use serde::Deserialize;
38use std::sync::{Arc, Mutex, RwLock};
39
40#[derive(Clone)]
41pub struct EngineParams<C, T>
42where
43    C: Chromosome + 'static,
44    T: Clone + 'static,
45{
46    pub population_params: PopulationParams<C>,
47    pub evaluation_params: EvaluationParams<C, T>,
48    pub species_params: SpeciesParams<C>,
49    pub selection_params: SelectionParams<C>,
50    pub optimization_params: OptimizeParams<C>,
51    pub problem_params: ProblemParams<C, T>,
52
53    pub alterers: Vec<Alterer<C>>,
54    pub replacement_strategy: Arc<dyn ReplacementStrategy<C>>,
55    pub handlers: Vec<Arc<Mutex<dyn EventHandler<T>>>>,
56    pub generation: Option<Generation<C, T>>,
57    pub exprs: Option<Arc<Mutex<Vec<NamedExpr>>>>,
58}
59
60/// Parameters for the genetic engine.
61/// This struct is used to configure the genetic engine before it is created.
62///
63/// When the `GeneticEngineBuilder`  calls the `build` method, it will create a new instance
64/// of the [GeneticEngine] with the given parameters. If any of the required parameters are not
65/// set, the `build` method will panic. At a minimum, the `codec` and `fitness_fn` must be set.
66/// The `GeneticEngineBuilder` struct is a builder pattern that allows you to set the parameters of
67/// the [GeneticEngine] in a fluent and functional way.
68///
69/// # Type Parameters
70/// - `C`: The type of chromosome used in the genotype, which must implement the [Chromosome] trait.
71/// - `T`: The type of the best individual in the population.
72pub struct GeneticEngineBuilder<C, T>
73where
74    C: Chromosome + Clone + 'static,
75    T: Clone + 'static,
76{
77    params: EngineParams<C, T>,
78    errors: Vec<RadiateError>,
79}
80
81impl<C, T> GeneticEngineBuilder<C, T>
82where
83    C: Chromosome + PartialEq + Clone,
84    T: Clone + Send,
85{
86    pub(self) fn add_error_if<F>(&mut self, condition: F, message: &str)
87    where
88        F: Fn() -> bool,
89    {
90        if condition() {
91            self.errors.push(radiate_err!(Builder: "{}", message));
92        }
93    }
94
95    /// The [ReplacementStrategy] is used to determine how a new individual is added to the [Population]
96    /// if an individual is deemed to be either invalid or reaches the maximum age.
97    ///
98    /// Default is [EncodeReplace], which means that a new individual will be created
99    /// be using the `Codec` to encode a new individual from scratch.
100    pub fn replace_strategy<R: ReplacementStrategy<C> + 'static>(mut self, replace: R) -> Self {
101        self.params.replacement_strategy = Arc::new(replace);
102        self
103    }
104
105    /// Subscribe to engine events with the given event handler.
106    /// The event handler will be called whenever an event is emitted by the engine.
107    /// You can use this to log events, or to perform custom actions
108    /// based on the events emitted by the engine.
109    pub fn subscribe<H>(mut self, handler: H) -> Self
110    where
111        H: EventHandler<T> + 'static,
112    {
113        self.params.handlers.push(Arc::new(Mutex::new(handler)));
114        self
115    }
116
117    /// Set the generation for the engine. This is typically used
118    /// when resuming a previously paused or stopped engine.
119    pub fn generation(mut self, generation: Generation<C, T>) -> Self {
120        self.params.generation = Some(generation);
121        self
122    }
123
124    pub fn register_metrics(mut self, exprs: Vec<impl Into<NamedExpr>>) -> Self {
125        self.params.exprs = Some(Arc::new(Mutex::new(
126            exprs.into_iter().map(|e| e.into()).collect(),
127        )));
128        self
129    }
130
131    /// Load a checkpoint from the given file path. This will
132    /// load the generation from the file and set it as the current generation
133    /// for the engine.
134    #[cfg(feature = "serde")]
135    pub fn load_checkpoint<P: AsRef<std::path::Path>>(
136        mut self,
137        path: P,
138        reader: impl CheckpointReader<C, T>,
139    ) -> Self
140    where
141        C: for<'de> Deserialize<'de>,
142        T: for<'de> Deserialize<'de>,
143    {
144        let read_generation = reader.read_checkpoint(path.as_ref().to_path_buf());
145        if let Err(e) = &read_generation {
146            self.add_error_if(|| true, &format!("Failed to read checkpoint: {}", e));
147        }
148        let generation = read_generation.expect("Failed to read checkpoint file");
149        self.generation(generation)
150    }
151}
152
153/// Static step builder for the genetic engine.
154impl<C, T> GeneticEngineBuilder<C, T>
155where
156    C: Chromosome + Clone + PartialEq + 'static,
157    T: Clone + Send + Sync + 'static,
158{
159    /// Build the genetic engine with the given parameters. This will create a new
160    /// instance of the [GeneticEngine] with the given parameters.
161    pub fn build(self) -> GeneticEngine<C, T> {
162        match self.try_build() {
163            Ok(engine) => engine,
164            Err(e) => panic!("{e}"),
165        }
166    }
167
168    pub fn try_build(mut self) -> Result<GeneticEngine<C, T>> {
169        if !self.errors.is_empty() {
170            return Err(radiate_err!(
171                Builder: "Failed to build GeneticEngine: {:?}",
172                self.errors
173            ));
174        }
175
176        self.build_problem()?;
177        self.build_population()?;
178        self.build_alterer()?;
179        self.build_front()?;
180
181        let config = EngineConfig::<C, T>::from(&self.params);
182
183        let mut pipeline = Pipeline::<C>::default();
184
185        pipeline.add_step(Self::build_eval_step(&config));
186        pipeline.add_step(Self::build_recombine_step(&config));
187        pipeline.add_step(Self::build_filter_step(&config));
188        pipeline.add_step(Self::build_eval_step(&config));
189        pipeline.add_step(Self::build_front_step(&config));
190        pipeline.add_step(Self::build_species_step(&config));
191        pipeline.add_step(Self::build_audit_step(&config));
192
193        let event_bus = EventBus::new(config.bus_executor(), config.handlers());
194        let context = Context::from(config);
195
196        Ok(GeneticEngine::<C, T>::new(context, pipeline, event_bus))
197    }
198
199    /// Build the problem of the genetic engine. This will create a new problem
200    /// using the codec and fitness function if the problem is not set. If the
201    /// problem is already set, this function will do nothing. Else, if the fitness function is
202    /// a batch fitness function, it will create a new [BatchEngineProblem] and swap the evaluator
203    /// to use a [BatchFitnessEvaluator].
204    fn build_problem(&mut self) -> Result<()> {
205        if self.params.problem_params.problem.is_some() {
206            return Ok(());
207        }
208
209        ensure!(
210            self.params.problem_params.codec.is_some(),
211            Builder: "Codec not set"
212        );
213
214        let raw_fitness_fn = self.params.problem_params.raw_fitness_fn.clone();
215        let fitness_fn = self.params.problem_params.fitness_fn.clone();
216        let batch_fitness_fn = self.params.problem_params.batch_fitness_fn.clone();
217        let raw_batch_fitness_fn = self.params.problem_params.raw_batch_fitness_fn.clone();
218
219        if batch_fitness_fn.is_some() || raw_batch_fitness_fn.is_some() {
220            self.params.problem_params.problem = Some(Arc::new(BatchEngineProblem {
221                objective: self.params.optimization_params.objectives.clone(),
222                codec: self.params.problem_params.codec.clone().unwrap(),
223                batch_fitness_fn,
224                raw_batch_fitness_fn,
225            }));
226
227            // Replace the evaluator with BatchFitnessEvaluator
228            self.params.evaluation_params.evaluator = Arc::new(BatchFitnessEvaluator::new(
229                self.params.evaluation_params.fitness_executor.clone(),
230            ));
231
232            Ok(())
233        } else if fitness_fn.is_some() || raw_fitness_fn.is_some() {
234            self.params.problem_params.problem = Some(Arc::new(EngineProblem {
235                objective: self.params.optimization_params.objectives.clone(),
236                codec: self.params.problem_params.codec.clone().unwrap(),
237                fitness_fn,
238                raw_fitness_fn,
239            }));
240
241            Ok(())
242        } else {
243            Err(radiate_err!(Builder: "Fitness function not set"))
244        }
245    }
246
247    /// Build the population of the genetic engine. This will create a new population
248    /// using the codec if the population is not set.
249    fn build_population(&mut self) -> Result<()> {
250        if self.params.population_params.ecosystem.is_some() {
251            return Ok(());
252        }
253
254        let ecosystem = match &self.params.population_params.ecosystem {
255            None => Some(match self.params.problem_params.problem.as_ref() {
256                Some(problem) => {
257                    let size = self.params.population_params.population_size;
258                    let mut phenotypes = Vec::with_capacity(size);
259
260                    for _ in 0..size {
261                        let genotype = problem.encode();
262
263                        if !genotype.is_valid() {
264                            return Err(radiate_err!(
265                                Builder: "Encoded genotype is not valid",
266                            ));
267                        }
268
269                        phenotypes.push(Phenotype::from((genotype, 0)));
270                    }
271
272                    Ecosystem::from(phenotypes)
273                }
274                None => return Err(radiate_err!(Builder: "Codec not set")),
275            }),
276            Some(ecosystem) => Some(ecosystem.clone()),
277        };
278
279        if let Some(ecosystem) = ecosystem {
280            self.params.population_params.ecosystem = Some(ecosystem);
281        }
282
283        Ok(())
284    }
285
286    /// Build the alterer of the genetic engine. This will create a
287    /// new `UniformCrossover` and `UniformMutator` if the alterer is not set.
288    /// with a 0.5 crossover rate and a 0.1 mutation rate.
289    fn build_alterer(&mut self) -> Result<()> {
290        if !self.params.alterers.is_empty() {
291            for alter in self.params.alterers.iter_mut() {
292                if !alter.rate().is_valid() {
293                    return Err(radiate_err!(
294                        Builder: "Alterer {} is not valid. Ensure rate {:?} is valid.", alter.name(), alter.rate()
295                    ));
296                }
297            }
298
299            return Ok(());
300        }
301
302        let crossover = UniformCrossover::new(0.5).alterer();
303        let mutator = UniformMutator::new(0.1).alterer();
304
305        self.params.alterers.push(crossover);
306        self.params.alterers.push(mutator);
307
308        Ok(())
309    }
310
311    /// Build the pareto front of the genetic engine. This will create a new `Front`
312    /// if the front is not set. The `Front` is used to store the best individuals
313    /// in the population and is used for multi-objective optimization problems.
314    fn build_front(&mut self) -> Result<()> {
315        if self.params.optimization_params.front.is_some() {
316            return Ok(());
317        } else if let Some(generation) = &self.params.generation {
318            if let Some(front) = generation.front() {
319                self.params.optimization_params.front = Some(front.clone());
320                return Ok(());
321            }
322        }
323
324        let front_obj = self.params.optimization_params.objectives.clone();
325        self.params.optimization_params.front = Some(Front::new(
326            self.params.optimization_params.front_range.clone(),
327            front_obj,
328        ));
329
330        Ok(())
331    }
332
333    fn build_eval_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
334        let eval_step = EvaluateStep {
335            objective: config.objective(),
336            problem: config.problem(),
337            evaluator: config.evaluator(),
338        };
339
340        Some(Box::new(eval_step))
341    }
342
343    fn build_recombine_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
344        let offspring_selector = config.offspring_selector();
345        let survivor_selector = config.survivor_selector();
346
347        let off_name = offspring_selector.name();
348        let offspring_base_name = radiate_utils::intern!(off_name);
349        let offspring_time_name = radiate_utils::intern!(format!("{}.time", offspring_base_name));
350
351        let surv_name = survivor_selector.name();
352        let survivor_base_name = radiate_utils::intern!(surv_name);
353        let survivor_time_name = radiate_utils::intern!(format!("{}.time", survivor_base_name));
354
355        let recombine_step = RecombineStep {
356            survivor_handle: crate::steps::SurvivorRecombineHandle {
357                count: config.survivor_count(),
358                objective: config.objective(),
359                selector: survivor_selector,
360                names: (survivor_base_name, survivor_time_name),
361            },
362            offspring_handle: crate::steps::OffspringRecombineHandle {
363                count: config.offspring_count(),
364                objective: config.objective(),
365                selector: offspring_selector,
366                alters: config.alters().to_vec(),
367                lineage: config.lineage(),
368                names: (offspring_base_name, offspring_time_name),
369            },
370        };
371
372        Some(Box::new(recombine_step))
373    }
374
375    fn build_filter_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
376        let filter_step = FilterStep {
377            replacer: config.replacement_strategy(),
378            encoder: config.encoder(),
379            max_age: config.max_age(),
380            max_species_age: config.max_species_age(),
381        };
382
383        Some(Box::new(filter_step))
384    }
385
386    fn build_audit_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
387        Some(Box::new(AuditStep::new(
388            config.objective().clone(),
389            config.lineage(),
390        )))
391    }
392
393    fn build_front_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
394        if config.objective().is_single() {
395            return None;
396        }
397
398        let front_step = FrontStep {
399            front: config.front(),
400        };
401
402        Some(Box::new(front_step))
403    }
404
405    fn build_species_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
406        if config.diversity().is_none() {
407            return None;
408        }
409
410        let species_step = SpeciateStep {
411            threshold: config.species_threshold(),
412            distance: config.diversity().unwrap(),
413            executor: config.species_executor(),
414            objective: config.objective(),
415            distances: Arc::new(Mutex::new(Vec::new())),
416            assignments: Arc::new(Mutex::new(Vec::new())),
417        };
418
419        Some(Box::new(species_step))
420    }
421}
422
423impl<C, T> Default for GeneticEngineBuilder<C, T>
424where
425    C: Chromosome + Clone + 'static,
426    T: Clone + Send + 'static,
427{
428    fn default() -> Self {
429        GeneticEngineBuilder {
430            params: EngineParams {
431                population_params: PopulationParams {
432                    population_size: 100,
433                    max_age: 20,
434                    ecosystem: None,
435                },
436                species_params: SpeciesParams {
437                    diversity: None,
438                    species_threshold: Rate::Fixed(0.5),
439                    max_species_age: 25,
440                },
441                evaluation_params: EvaluationParams {
442                    evaluator: Arc::new(FitnessEvaluator::default()),
443                    fitness_executor: Arc::new(Executor::default()),
444                    species_executor: Arc::new(Executor::default()),
445                    bus_executor: Arc::new(Executor::default()),
446                },
447                selection_params: SelectionParams {
448                    offspring_fraction: 0.8,
449                    survivor_selector: Arc::new(TournamentSelector::new(3)),
450                    offspring_selector: Arc::new(RouletteSelector::new()),
451                },
452                optimization_params: OptimizeParams {
453                    objectives: Objective::Single(Optimize::Maximize),
454                    front_range: 800..900,
455                    front: None,
456                },
457                problem_params: ProblemParams {
458                    codec: None,
459                    problem: None,
460                    fitness_fn: None,
461                    batch_fitness_fn: None,
462                    raw_fitness_fn: None,
463                    raw_batch_fitness_fn: None,
464                },
465
466                replacement_strategy: Arc::new(EncodeReplace),
467                alterers: Vec::new(),
468                handlers: Vec::new(),
469                exprs: None,
470                generation: None,
471            },
472            errors: Vec::new(),
473        }
474    }
475}
476
477#[derive(Clone)]
478pub(crate) struct EngineConfig<C: Chromosome, T: Clone> {
479    ecosystem: Ecosystem<C>,
480    problem: Arc<dyn Problem<C, T>>,
481    survivor_selector: Arc<dyn Select<C>>,
482    offspring_selector: Arc<dyn Select<C>>,
483    replacement_strategy: Arc<dyn ReplacementStrategy<C>>,
484    alterers: Vec<Alterer<C>>,
485    species_threshold: Rate,
486    diversity: Option<Arc<dyn Diversity<C>>>,
487    evaluator: Arc<dyn Evaluator<C, T>>,
488    objective: Objective,
489    max_age: usize,
490    max_species_age: usize,
491    front: Arc<RwLock<Front<Phenotype<C>>>>,
492    lineage: Arc<RwLock<Lineage>>,
493    offspring_fraction: f32,
494    executor: EvaluationParams<C, T>,
495    handlers: Vec<Arc<Mutex<dyn EventHandler<T>>>>,
496    exprs: Option<Arc<Mutex<Vec<NamedExpr>>>>,
497    generation: Option<Generation<C, T>>,
498}
499
500impl<C: Chromosome, T: Clone> EngineConfig<C, T> {
501    pub fn ecosystem(&self) -> &Ecosystem<C> {
502        &self.ecosystem
503    }
504
505    pub fn survivor_selector(&self) -> Arc<dyn Select<C>> {
506        Arc::clone(&self.survivor_selector)
507    }
508
509    pub fn offspring_selector(&self) -> Arc<dyn Select<C>> {
510        Arc::clone(&self.offspring_selector)
511    }
512
513    pub fn replacement_strategy(&self) -> Arc<dyn ReplacementStrategy<C>> {
514        Arc::clone(&self.replacement_strategy)
515    }
516
517    pub fn alters(&self) -> &[Alterer<C>] {
518        &self.alterers
519    }
520
521    pub fn objective(&self) -> Objective {
522        self.objective.clone()
523    }
524
525    pub fn max_age(&self) -> usize {
526        self.max_age
527    }
528
529    pub fn max_species_age(&self) -> usize {
530        self.max_species_age
531    }
532
533    pub fn species_threshold(&self) -> Rate {
534        self.species_threshold.clone()
535    }
536
537    pub fn diversity(&self) -> Option<Arc<dyn Diversity<C>>> {
538        self.diversity.clone()
539    }
540
541    pub fn front(&self) -> Arc<RwLock<Front<Phenotype<C>>>> {
542        Arc::clone(&self.front)
543    }
544
545    pub fn lineage(&self) -> Arc<RwLock<Lineage>> {
546        Arc::clone(&self.lineage)
547    }
548
549    pub fn evaluator(&self) -> Arc<dyn Evaluator<C, T>> {
550        Arc::clone(&self.evaluator)
551    }
552
553    pub fn survivor_count(&self) -> usize {
554        self.ecosystem.population().len() - self.offspring_count()
555    }
556
557    pub fn offspring_count(&self) -> usize {
558        (self.ecosystem.population().len() as f32 * self.offspring_fraction) as usize
559    }
560
561    pub fn bus_executor(&self) -> Arc<Executor> {
562        Arc::clone(&self.executor.bus_executor)
563    }
564
565    pub fn species_executor(&self) -> Arc<Executor> {
566        Arc::clone(&self.executor.species_executor)
567    }
568
569    pub fn handlers(&self) -> Vec<Arc<Mutex<dyn EventHandler<T>>>> {
570        self.handlers.clone()
571    }
572
573    pub fn problem(&self) -> Arc<dyn Problem<C, T>> {
574        Arc::clone(&self.problem)
575    }
576
577    pub fn generation(&self) -> Option<Generation<C, T>>
578    where
579        C: Clone,
580        T: Clone,
581    {
582        self.generation.clone()
583    }
584
585    pub fn encoder(&self) -> Arc<dyn Fn() -> Genotype<C> + Send + Sync>
586    where
587        C: 'static,
588        T: 'static,
589    {
590        let problem = Arc::clone(&self.problem);
591        Arc::new(move || problem.encode())
592    }
593
594    pub fn exprs(&self) -> Option<Arc<Mutex<Vec<NamedExpr>>>> {
595        self.exprs.clone()
596    }
597}
598
599impl<C, T> From<&EngineParams<C, T>> for EngineConfig<C, T>
600where
601    C: Chromosome + Clone + 'static,
602    T: Clone + Send + Sync + 'static,
603{
604    fn from(params: &EngineParams<C, T>) -> Self {
605        Self {
606            ecosystem: params.population_params.ecosystem.clone().unwrap(),
607            problem: params.problem_params.problem.clone().unwrap(),
608            survivor_selector: params.selection_params.survivor_selector.clone(),
609            offspring_selector: params.selection_params.offspring_selector.clone(),
610            replacement_strategy: params.replacement_strategy.clone(),
611            alterers: params.alterers.clone(),
612            objective: params.optimization_params.objectives.clone(),
613            max_age: params.population_params.max_age,
614            max_species_age: params.species_params.max_species_age,
615            species_threshold: params.species_params.species_threshold.clone(),
616            diversity: params.species_params.diversity.clone(),
617            front: Arc::new(RwLock::new(
618                params.optimization_params.front.clone().unwrap(),
619            )),
620            lineage: Arc::new(RwLock::new(Lineage::default())),
621            offspring_fraction: params.selection_params.offspring_fraction,
622            evaluator: params.evaluation_params.evaluator.clone(),
623            executor: params.evaluation_params.clone(),
624            handlers: params.handlers.clone(),
625            generation: params.generation.clone(),
626            exprs: params.exprs.clone(),
627        }
628    }
629}
630
631impl<C, T> Into<GeneticEngineBuilder<C, T>> for EngineConfig<C, T>
632where
633    C: Chromosome + Clone + 'static,
634    T: Clone + Send + Sync + 'static,
635{
636    fn into(self) -> GeneticEngineBuilder<C, T> {
637        GeneticEngineBuilder {
638            params: EngineParams {
639                population_params: PopulationParams {
640                    population_size: self.ecosystem.population().len(),
641                    max_age: self.max_age,
642                    ecosystem: Some(self.ecosystem),
643                },
644                species_params: SpeciesParams {
645                    diversity: self.diversity,
646                    species_threshold: self.species_threshold,
647                    max_species_age: self.max_species_age,
648                },
649                evaluation_params: self.executor,
650                selection_params: SelectionParams {
651                    offspring_fraction: self.offspring_fraction,
652                    survivor_selector: self.survivor_selector,
653                    offspring_selector: self.offspring_selector,
654                },
655                optimization_params: OptimizeParams {
656                    objectives: self.objective,
657                    front_range: self.front.read().unwrap().range().clone(),
658                    front: Some(self.front.read().unwrap().clone()),
659                },
660                problem_params: ProblemParams {
661                    codec: None,
662                    problem: Some(self.problem),
663                    fitness_fn: None,
664                    batch_fitness_fn: None,
665                    raw_fitness_fn: None,
666                    raw_batch_fitness_fn: None,
667                },
668
669                replacement_strategy: self.replacement_strategy,
670                alterers: self.alterers,
671                handlers: self.handlers,
672                exprs: self.exprs,
673                generation: self.generation,
674            },
675            errors: Vec::new(),
676        }
677    }
678}