Skip to main content

radiate_engines/builder/
mod.rs

1mod alters;
2pub(crate) mod config;
3mod evaluators;
4mod objectives;
5mod population;
6mod problem;
7mod selectors;
8mod species;
9
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;
17#[cfg(feature = "serde")]
18use crate::io::FileReader;
19use crate::objectives::{Objective, Optimize};
20use crate::pipeline::Pipeline;
21use crate::steps::{
22    EngineStep, FilterStep, FrontStep, MetricStep, RecombineStep, SelectConfig, SpeciateStep,
23};
24use crate::{Chromosome, EvaluateStep, GeneticEngine};
25use crate::{
26    Crossover, EncodeReplace, EventBus, EventHandler, Front, Mutate, ReplacementStrategy,
27    RouletteSelector, TournamentSelector, context::EvolutionContext,
28};
29use crate::{Generation, Result};
30use config::EngineConfig;
31use radiate_alters::{UniformCrossover, UniformMutator};
32use radiate_core::MetricQuery;
33use radiate_core::evaluator::BatchFitnessEvaluator;
34use radiate_core::problem::{BatchEngineProblem, EngineProblem};
35use radiate_core::{Alterer, Ecosystem, Executor, FitnessEvaluator, Rate, Valid};
36use radiate_core::{RadiateError, ensure, radiate_err};
37use radiate_utils::VersionedCounts;
38#[cfg(feature = "serde")]
39use serde::Deserialize;
40use std::sync::{Arc, Mutex};
41
42#[derive(Clone)]
43pub struct EngineParams<C, T>
44where
45    C: Chromosome + 'static,
46    T: Clone + 'static,
47{
48    pub population_params: PopulationParams<C>,
49    pub evaluation_params: EvaluationParams<C, T>,
50    pub species_params: SpeciesParams<C>,
51    pub selection_params: SelectionParams<C>,
52    pub optimization_params: OptimizeParams<C>,
53    pub problem_params: ProblemParams<C, T>,
54
55    pub alterers: Vec<Alterer<C>>,
56    pub replacement_strategy: Arc<dyn ReplacementStrategy<C>>,
57    pub handlers: Vec<Arc<Mutex<dyn EventHandler<T>>>>,
58    pub generation: Option<Generation<C, T>>,
59    pub exprs: Option<Arc<Mutex<Vec<MetricQuery>>>>,
60}
61
62/// Parameters for the genetic engine.
63/// This struct is used to configure the genetic engine before it is created.
64///
65/// When the `GeneticEngineBuilder`  calls the `build` method, it will create a new instance
66/// of the [GeneticEngine] with the given parameters. If any of the required parameters are not
67/// set, the `build` method will panic. At a minimum, the `codec` and `fitness_fn` must be set.
68/// The `GeneticEngineBuilder` struct is a builder pattern that allows you to set the parameters of
69/// the [GeneticEngine] in a fluent and functional way.
70///
71/// # Type Parameters
72/// - `C`: The type of chromosome used in the genotype, which must implement the [Chromosome] trait.
73/// - `T`: The type of the best individual in the population.
74pub struct GeneticEngineBuilder<C, T>
75where
76    C: Chromosome + 'static,
77    T: Clone + 'static,
78{
79    params: EngineParams<C, T>,
80    errors: Vec<RadiateError>,
81}
82
83impl<C, T> GeneticEngineBuilder<C, T>
84where
85    C: Chromosome + PartialEq + Clone,
86    T: Clone + Send,
87{
88    pub(self) fn add_error_if<F>(&mut self, condition: F, message: &str)
89    where
90        F: Fn() -> bool,
91    {
92        if condition() {
93            self.errors.push(radiate_err!(Builder: "{}", message));
94        }
95    }
96
97    /// The [ReplacementStrategy] is used to determine how a new individual is added to the [Population]
98    /// if an individual is deemed to be either invalid or reaches the maximum age.
99    ///
100    /// Default is [EncodeReplace], which means that a new individual will be created
101    /// be using the `Codec` to encode a new individual from scratch.
102    pub fn replace_strategy<R: ReplacementStrategy<C> + 'static>(mut self, replace: R) -> Self {
103        self.params.replacement_strategy = Arc::new(replace);
104        self
105    }
106
107    /// Subscribe to engine events with the given event handler.
108    /// The event handler will be called whenever an event is emitted by the engine.
109    /// You can use this to log events, or to perform custom actions
110    /// based on the events emitted by the engine.
111    pub fn subscribe<H>(mut self, handler: H) -> Self
112    where
113        H: EventHandler<T> + 'static,
114    {
115        self.params.handlers.push(Arc::new(Mutex::new(handler)));
116        self
117    }
118
119    /// Set the generation for the engine. This is typically used
120    /// when resuming a previously paused or stopped engine.
121    pub fn generation(mut self, generation: Generation<C, T>) -> Self {
122        self.params.generation = Some(generation);
123        self
124    }
125
126    pub fn register_metrics(mut self, exprs: Vec<impl Into<MetricQuery>>) -> Self {
127        self.params.exprs = Some(Arc::new(Mutex::new(
128            exprs.into_iter().map(|e| e.into()).collect(),
129        )));
130        self
131    }
132
133    /// Load a checkpoint from the given file path. This will
134    /// load the generation from the file and set it as the current generation
135    /// for the engine.
136    #[cfg(feature = "serde")]
137    pub fn load_checkpoint<P: AsRef<std::path::Path>>(
138        mut self,
139        path: P,
140        reader: impl FileReader<Generation<C, T>>,
141    ) -> Self
142    where
143        C: for<'de> Deserialize<'de>,
144        T: for<'de> Deserialize<'de>,
145    {
146        let read_generation = reader.read(path.as_ref().to_path_buf());
147        if let Err(e) = &read_generation {
148            self.add_error_if(|| true, &format!("Failed to read checkpoint: {}", e));
149        }
150        let generation = read_generation.expect("Failed to read checkpoint file");
151        self.generation(generation)
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 = EvolutionContext::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_mut() {
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            && let Some(front) = generation.front()
321        {
322            self.params.optimization_params.front = Some(front.clone());
323            return Ok(());
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 offspring_selector = config.offspring_selector();
347        let survivor_selector = config.survivor_selector();
348
349        let off_name = offspring_selector.name();
350        let offspring_base_name = radiate_utils::intern!(off_name);
351        let offspring_time_name = radiate_utils::intern!(format!("{}.time", offspring_base_name));
352
353        let surv_name = survivor_selector.name();
354        let survivor_base_name = radiate_utils::intern!(surv_name);
355        let survivor_time_name = radiate_utils::intern!(format!("{}.time", survivor_base_name));
356
357        let survivor_select = SelectConfig {
358            selector: survivor_selector,
359            count: config.survivor_count(),
360            names: (survivor_base_name, survivor_time_name),
361        };
362
363        let offspring_select = SelectConfig {
364            selector: offspring_selector,
365            count: config.offspring_count(),
366            names: (offspring_base_name, offspring_time_name),
367        };
368
369        let recombine_step = RecombineStep {
370            survivor: crate::steps::SurvivorConfig {
371                select: survivor_select,
372            },
373            offspring: crate::steps::OffspringConfig {
374                select: offspring_select,
375                alters: config.alters().to_vec(),
376            },
377            objective: config.objective(),
378            survivor_counts: VersionedCounts::new(),
379            offspring_counts: VersionedCounts::new(),
380        };
381
382        Some(Box::new(recombine_step))
383    }
384
385    fn build_filter_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
386        let filter_step = FilterStep {
387            replacer: config.replacement_strategy(),
388            encoder: config.encoder(),
389            max_age: config.max_age(),
390            max_species_age: config.max_species_age(),
391        };
392
393        Some(Box::new(filter_step))
394    }
395
396    fn build_audit_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
397        Some(Box::new(MetricStep::new(
398            config.objective().clone(),
399            config.exprs().clone(),
400        )))
401    }
402
403    fn build_front_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
404        if config.objective().is_single() {
405            return None;
406        }
407
408        let front_step = FrontStep {
409            front: config.front(),
410        };
411
412        Some(Box::new(front_step))
413    }
414
415    fn build_species_step(config: &EngineConfig<C, T>) -> Option<Box<dyn EngineStep<C>>> {
416        let diversity = config.diversity()?;
417
418        let species_step = SpeciateStep {
419            threshold: config.species_threshold(),
420            distance: diversity,
421            executor: config.species_executor(),
422            objective: config.objective(),
423            distances: Vec::new(),
424            assignments: Arc::new(Mutex::new(Vec::new())),
425        };
426
427        Some(Box::new(species_step))
428    }
429}
430
431impl<C, T> Default for GeneticEngineBuilder<C, T>
432where
433    C: Chromosome + 'static,
434    T: Clone + Send + 'static,
435{
436    fn default() -> Self {
437        GeneticEngineBuilder {
438            params: EngineParams {
439                population_params: PopulationParams {
440                    population_size: 100,
441                    max_age: 20,
442                    ecosystem: None,
443                },
444                species_params: SpeciesParams {
445                    diversity: None,
446                    species_threshold: Rate::Fixed(0.5),
447                    max_species_age: 25,
448                    target_species_count: None,
449                },
450                evaluation_params: EvaluationParams {
451                    evaluator: Arc::new(FitnessEvaluator::default()),
452                    fitness_executor: Arc::new(Executor::default()),
453                    species_executor: Arc::new(Executor::default()),
454                    bus_executor: Arc::new(Executor::default()),
455                },
456                selection_params: SelectionParams {
457                    offspring_fraction: 0.8,
458                    survivor_selector: Arc::new(TournamentSelector::new(3)),
459                    offspring_selector: Arc::new(RouletteSelector::new()),
460                },
461                optimization_params: OptimizeParams {
462                    objectives: Objective::Single(Optimize::Maximize),
463                    front_range: 800..900,
464                    front: None,
465                },
466                problem_params: ProblemParams {
467                    codec: None,
468                    problem: None,
469                    fitness_fn: None,
470                    batch_fitness_fn: None,
471                    raw_fitness_fn: None,
472                    raw_batch_fitness_fn: None,
473                },
474
475                replacement_strategy: Arc::new(EncodeReplace),
476                alterers: Vec::new(),
477                handlers: Vec::new(),
478                exprs: None,
479                generation: None,
480            },
481            errors: Vec::new(),
482        }
483    }
484}