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