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