genetic_algorithm/strategy/
builder.rs

1use crate::crossover::Crossover;
2pub use crate::errors::TryFromStrategyBuilderError as TryFromBuilderError;
3use crate::extension::{Extension, ExtensionNoop};
4use crate::fitness::{Fitness, FitnessCache, FitnessOrdering, FitnessValue};
5use crate::genotype::{EvolveGenotype, HillClimbGenotype, PermutateGenotype};
6use crate::mutate::Mutate;
7use crate::select::Select;
8use crate::strategy::evolve::EvolveBuilder;
9use crate::strategy::hill_climb::HillClimbBuilder;
10use crate::strategy::permutate::PermutateBuilder;
11use crate::strategy::{Strategy, StrategyReporter, StrategyReporterNoop, StrategyVariant};
12
13/// The superset builder for all strategies.
14///
15/// *Note: Only Genotypes which implement all strategies are eligable for the superset builder.*
16/// *RangeGenotype and other floating point range based genotypes currently do not support Permutation*
17#[derive(Clone, Debug)]
18pub struct Builder<
19    G: EvolveGenotype + HillClimbGenotype + PermutateGenotype,
20    M: Mutate,
21    F: Fitness<Genotype = G>,
22    S: Crossover,
23    C: Select,
24    E: Extension,
25    SR: StrategyReporter<Genotype = G>,
26> {
27    pub genotype: Option<G>,
28    pub variant: Option<StrategyVariant>,
29    pub crossover: Option<S>,
30    pub extension: E,
31    pub fitness: Option<F>,
32    pub fitness_ordering: FitnessOrdering,
33    pub fitness_cache: Option<FitnessCache>,
34    pub max_chromosome_age: Option<usize>,
35    pub max_stale_generations: Option<usize>,
36    pub mutate: Option<M>,
37    pub par_fitness: bool,
38    pub replace_on_equal_fitness: bool,
39    pub reporter: SR,
40    pub rng_seed: Option<u64>,
41    pub select: Option<C>,
42    pub target_fitness_score: Option<FitnessValue>,
43    pub target_population_size: usize,
44    pub valid_fitness_score: Option<FitnessValue>,
45}
46
47impl<
48        G: EvolveGenotype + HillClimbGenotype + PermutateGenotype,
49        M: Mutate,
50        F: Fitness<Genotype = G>,
51        S: Crossover,
52        C: Select,
53    > Default for Builder<G, M, F, S, C, ExtensionNoop, StrategyReporterNoop<G>>
54{
55    fn default() -> Self {
56        Self {
57            genotype: None,
58            variant: None,
59            target_population_size: 0,
60            max_stale_generations: None,
61            max_chromosome_age: None,
62            target_fitness_score: None,
63            valid_fitness_score: None,
64            fitness_ordering: FitnessOrdering::Maximize,
65            fitness_cache: None,
66            par_fitness: false,
67            replace_on_equal_fitness: false,
68            mutate: None,
69            fitness: None,
70            crossover: None,
71            select: None,
72            extension: ExtensionNoop::new(),
73            reporter: StrategyReporterNoop::new(),
74            rng_seed: None,
75        }
76    }
77}
78impl<
79        G: EvolveGenotype + HillClimbGenotype + PermutateGenotype,
80        M: Mutate,
81        F: Fitness<Genotype = G>,
82        S: Crossover,
83        C: Select,
84    > Builder<G, M, F, S, C, ExtensionNoop, StrategyReporterNoop<G>>
85{
86    pub fn new() -> Self {
87        Self::default()
88    }
89}
90
91#[allow(clippy::type_complexity)]
92impl<
93        G: EvolveGenotype + HillClimbGenotype + PermutateGenotype,
94        M: Mutate,
95        F: Fitness<Genotype = G>,
96        S: Crossover,
97        C: Select,
98        E: Extension,
99        SR: StrategyReporter<Genotype = G>,
100    > Builder<G, M, F, S, C, E, SR>
101{
102    pub fn with_genotype(mut self, genotype: G) -> Self {
103        self.genotype = Some(genotype);
104        self
105    }
106    pub fn with_variant(mut self, variant: StrategyVariant) -> Self {
107        self.variant = Some(variant);
108        self
109    }
110    pub fn with_target_population_size(mut self, target_population_size: usize) -> Self {
111        self.target_population_size = target_population_size;
112        self
113    }
114    pub fn with_max_stale_generations(mut self, max_stale_generations: usize) -> Self {
115        self.max_stale_generations = Some(max_stale_generations);
116        self
117    }
118    pub fn with_max_stale_generations_option(
119        mut self,
120        max_stale_generations_option: Option<usize>,
121    ) -> Self {
122        self.max_stale_generations = max_stale_generations_option;
123        self
124    }
125    pub fn with_max_chromosome_age(mut self, max_chromosome_age: usize) -> Self {
126        self.max_chromosome_age = Some(max_chromosome_age);
127        self
128    }
129    pub fn with_max_chromosome_age_option(
130        mut self,
131        max_chromosome_age_option: Option<usize>,
132    ) -> Self {
133        self.max_chromosome_age = max_chromosome_age_option;
134        self
135    }
136    pub fn with_target_fitness_score(mut self, target_fitness_score: FitnessValue) -> Self {
137        self.target_fitness_score = Some(target_fitness_score);
138        self
139    }
140    pub fn with_target_fitness_score_option(
141        mut self,
142        target_fitness_score_option: Option<FitnessValue>,
143    ) -> Self {
144        self.target_fitness_score = target_fitness_score_option;
145        self
146    }
147    pub fn with_valid_fitness_score(mut self, valid_fitness_score: FitnessValue) -> Self {
148        self.valid_fitness_score = Some(valid_fitness_score);
149        self
150    }
151    pub fn with_valid_fitness_score_option(
152        mut self,
153        valid_fitness_score_option: Option<FitnessValue>,
154    ) -> Self {
155        self.valid_fitness_score = valid_fitness_score_option;
156        self
157    }
158    pub fn with_fitness_ordering(mut self, fitness_ordering: FitnessOrdering) -> Self {
159        self.fitness_ordering = fitness_ordering;
160        self
161    }
162    /// Only works when genes_hash is stored on chromosome, as this is the cache key.
163    /// Only useful for long stale runs, but better to increase population diversity.
164    /// Silently ignore cache_size of zero, to support superset builder which delays specialization
165    pub fn with_fitness_cache(mut self, fitness_cache_size: usize) -> Self {
166        match FitnessCache::try_new(fitness_cache_size) {
167            Ok(cache) => self.fitness_cache = Some(cache),
168            Err(_error) => (),
169        }
170        self
171    }
172    pub fn with_par_fitness(mut self, par_fitness: bool) -> Self {
173        self.par_fitness = par_fitness;
174        self
175    }
176    pub fn with_replace_on_equal_fitness(mut self, replace_on_equal_fitness: bool) -> Self {
177        self.replace_on_equal_fitness = replace_on_equal_fitness;
178        self
179    }
180    pub fn with_mutate(mut self, mutate: M) -> Self {
181        self.mutate = Some(mutate);
182        self
183    }
184    pub fn with_fitness(mut self, fitness: F) -> Self {
185        self.fitness = Some(fitness);
186        self
187    }
188    pub fn with_crossover(mut self, crossover: S) -> Self {
189        self.crossover = Some(crossover);
190        self
191    }
192    pub fn with_select(mut self, select: C) -> Self {
193        self.select = Some(select);
194        self
195    }
196    pub fn with_extension<E2: Extension>(self, extension: E2) -> Builder<G, M, F, S, C, E2, SR> {
197        Builder {
198            genotype: self.genotype,
199            variant: self.variant,
200            target_population_size: self.target_population_size,
201            max_stale_generations: self.max_stale_generations,
202            max_chromosome_age: self.max_chromosome_age,
203            target_fitness_score: self.target_fitness_score,
204            valid_fitness_score: self.valid_fitness_score,
205            fitness_ordering: self.fitness_ordering,
206            fitness_cache: self.fitness_cache,
207            par_fitness: self.par_fitness,
208            replace_on_equal_fitness: self.replace_on_equal_fitness,
209            mutate: self.mutate,
210            fitness: self.fitness,
211            crossover: self.crossover,
212            select: self.select,
213            extension,
214            reporter: self.reporter,
215            rng_seed: self.rng_seed,
216        }
217    }
218    pub fn with_reporter<SR2: StrategyReporter<Genotype = G>>(
219        self,
220        reporter: SR2,
221    ) -> Builder<G, M, F, S, C, E, SR2> {
222        Builder {
223            genotype: self.genotype,
224            variant: self.variant,
225            target_population_size: self.target_population_size,
226            max_stale_generations: self.max_stale_generations,
227            max_chromosome_age: self.max_chromosome_age,
228            target_fitness_score: self.target_fitness_score,
229            valid_fitness_score: self.valid_fitness_score,
230            fitness_ordering: self.fitness_ordering,
231            fitness_cache: self.fitness_cache,
232            par_fitness: self.par_fitness,
233            replace_on_equal_fitness: self.replace_on_equal_fitness,
234            mutate: self.mutate,
235            fitness: self.fitness,
236            crossover: self.crossover,
237            select: self.select,
238            extension: self.extension,
239            reporter,
240            rng_seed: self.rng_seed,
241        }
242    }
243    pub fn with_rng_seed_from_u64(mut self, rng_seed: u64) -> Self {
244        self.rng_seed = Some(rng_seed);
245        self
246    }
247    pub fn with_rng_seed_from_u64_option(mut self, rng_seed_option: Option<u64>) -> Self {
248        self.rng_seed = rng_seed_option;
249        self
250    }
251}
252
253#[allow(clippy::type_complexity)]
254impl<
255        'a,
256        G: EvolveGenotype + HillClimbGenotype + PermutateGenotype + 'a,
257        M: Mutate + 'a,
258        F: Fitness<Genotype = G> + 'a,
259        S: Crossover + 'a,
260        C: Select + 'a,
261        E: Extension + 'a,
262        SR: StrategyReporter<Genotype = G> + 'a,
263    > Builder<G, M, F, S, C, E, SR>
264{
265    pub fn build(self) -> Result<Box<dyn Strategy<G> + 'a>, TryFromBuilderError> {
266        match self.variant {
267            Some(StrategyVariant::Permutate(_)) => {
268                Ok(Box::new(self.to_permutate_builder().build()?))
269            }
270            Some(StrategyVariant::Evolve(_)) => Ok(Box::new(self.to_evolve_builder().build()?)),
271            Some(StrategyVariant::HillClimb(hill_climb_variant)) => Ok(Box::new(
272                self.to_hill_climb_builder()
273                    .with_variant(hill_climb_variant)
274                    .build()?,
275            )),
276            None => Err(TryFromBuilderError("StrategyVariant is required")),
277        }
278    }
279    pub fn to_permutate_builder(self) -> PermutateBuilder<G, F, SR> {
280        PermutateBuilder {
281            genotype: self.genotype,
282            fitness_ordering: self.fitness_ordering,
283            par_fitness: self.par_fitness,
284            replace_on_equal_fitness: self.replace_on_equal_fitness,
285            fitness: self.fitness,
286            reporter: self.reporter,
287        }
288    }
289    pub fn to_evolve_builder(self) -> EvolveBuilder<G, M, F, S, C, E, SR> {
290        EvolveBuilder {
291            genotype: self.genotype,
292            target_population_size: self.target_population_size,
293            max_stale_generations: self.max_stale_generations,
294            max_chromosome_age: self.max_chromosome_age,
295            target_fitness_score: self.target_fitness_score,
296            valid_fitness_score: self.valid_fitness_score,
297            fitness_ordering: self.fitness_ordering,
298            fitness_cache: self.fitness_cache,
299            par_fitness: self.par_fitness,
300            replace_on_equal_fitness: self.replace_on_equal_fitness,
301            mutate: self.mutate,
302            fitness: self.fitness,
303            crossover: self.crossover,
304            select: self.select,
305            extension: self.extension,
306            reporter: self.reporter,
307            rng_seed: self.rng_seed,
308        }
309    }
310    pub fn to_hill_climb_builder(self) -> HillClimbBuilder<G, F, SR> {
311        HillClimbBuilder {
312            genotype: self.genotype,
313            variant: None,
314            max_stale_generations: self.max_stale_generations,
315            target_fitness_score: self.target_fitness_score,
316            valid_fitness_score: self.valid_fitness_score,
317            fitness_ordering: self.fitness_ordering,
318            fitness_cache: self.fitness_cache,
319            par_fitness: self.par_fitness,
320            replace_on_equal_fitness: self.replace_on_equal_fitness,
321            fitness: self.fitness,
322            reporter: self.reporter,
323            rng_seed: self.rng_seed,
324        }
325    }
326}
327
328#[allow(clippy::type_complexity)]
329impl<
330        'a,
331        G: EvolveGenotype + HillClimbGenotype + PermutateGenotype + 'a,
332        M: Mutate + 'a,
333        F: Fitness<Genotype = G> + 'a,
334        S: Crossover + 'a,
335        C: Select + 'a,
336        E: Extension + 'a,
337        SR: StrategyReporter<Genotype = G> + 'a,
338    > Builder<G, M, F, S, C, E, SR>
339{
340    pub fn call(self) -> Result<Box<dyn Strategy<G> + 'a>, TryFromBuilderError> {
341        let mut strategy = self.build()?;
342        strategy.call();
343        Ok(strategy)
344    }
345
346    /// Permutate: call (once)
347    /// Evolve: call_repeatedly
348    /// HillClimb: call_repeatedly
349    pub fn call_repeatedly(
350        self,
351        max_repeats: usize,
352    ) -> Result<(Box<dyn Strategy<G> + 'a>, Vec<Box<dyn Strategy<G> + 'a>>), TryFromBuilderError>
353    {
354        match self.variant {
355            Some(StrategyVariant::Permutate(_)) => {
356                let run = self.to_permutate_builder().call()?;
357                Ok((Box::new(run), vec![]))
358            }
359            Some(StrategyVariant::Evolve(_)) => {
360                let (run, runs) = self.to_evolve_builder().call_repeatedly(max_repeats)?;
361                Ok((
362                    Box::new(run),
363                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
364                ))
365            }
366            Some(StrategyVariant::HillClimb(hill_climb_variant)) => {
367                let (run, runs) = self
368                    .to_hill_climb_builder()
369                    .with_variant(hill_climb_variant)
370                    .call_repeatedly(max_repeats)?;
371                Ok((
372                    Box::new(run),
373                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
374                ))
375            }
376            None => Err(TryFromBuilderError("StrategyVariant is required")),
377        }
378    }
379
380    /// Permutate: call (force with_par_fitness)
381    /// Evolve: call_par_repeatedly
382    /// HillClimb: call_par_repeatedly
383    pub fn call_par_repeatedly(
384        self,
385        max_repeats: usize,
386    ) -> Result<(Box<dyn Strategy<G> + 'a>, Vec<Box<dyn Strategy<G> + 'a>>), TryFromBuilderError>
387    {
388        match self.variant {
389            Some(StrategyVariant::Permutate(_)) => {
390                let run = self.to_permutate_builder().with_par_fitness(true).call()?;
391                Ok((Box::new(run), vec![]))
392            }
393            Some(StrategyVariant::Evolve(_)) => {
394                let (run, runs) = self.to_evolve_builder().call_par_repeatedly(max_repeats)?;
395                Ok((
396                    Box::new(run),
397                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
398                ))
399            }
400            Some(StrategyVariant::HillClimb(hill_climb_variant)) => {
401                let (run, runs) = self
402                    .to_hill_climb_builder()
403                    .with_variant(hill_climb_variant)
404                    .call_par_repeatedly(max_repeats)?;
405                Ok((
406                    Box::new(run),
407                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
408                ))
409            }
410            None => Err(TryFromBuilderError("StrategyVariant is required")),
411        }
412    }
413
414    /// Permutate: call (once)
415    /// Evolve: call_speciated
416    /// HillClimb: call_repeatedly
417    pub fn call_speciated(
418        self,
419        number_of_species: usize,
420    ) -> Result<(Box<dyn Strategy<G> + 'a>, Vec<Box<dyn Strategy<G> + 'a>>), TryFromBuilderError>
421    {
422        match self.variant {
423            Some(StrategyVariant::Permutate(_)) => {
424                let run = self.to_permutate_builder().call()?;
425                Ok((Box::new(run), vec![]))
426            }
427            Some(StrategyVariant::Evolve(_)) => {
428                let (run, runs) = self.to_evolve_builder().call_speciated(number_of_species)?;
429                Ok((
430                    Box::new(run),
431                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
432                ))
433            }
434            Some(StrategyVariant::HillClimb(hill_climb_variant)) => {
435                let (run, runs) = self
436                    .to_hill_climb_builder()
437                    .with_variant(hill_climb_variant)
438                    .call_repeatedly(number_of_species)?;
439                Ok((
440                    Box::new(run),
441                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
442                ))
443            }
444            None => Err(TryFromBuilderError("StrategyVariant is required")),
445        }
446    }
447
448    /// Permutate: call (force with_par_fitness)
449    /// Evolve: call_par_speciated
450    /// HillClimb: call_par_repeatedly
451    pub fn call_par_speciated(
452        self,
453        number_of_species: usize,
454    ) -> Result<(Box<dyn Strategy<G> + 'a>, Vec<Box<dyn Strategy<G> + 'a>>), TryFromBuilderError>
455    {
456        match self.variant {
457            Some(StrategyVariant::Permutate(_)) => {
458                let run = self.to_permutate_builder().with_par_fitness(true).call()?;
459                Ok((Box::new(run), vec![]))
460            }
461            Some(StrategyVariant::Evolve(_)) => {
462                let (run, runs) = self
463                    .to_evolve_builder()
464                    .call_par_speciated(number_of_species)?;
465                Ok((
466                    Box::new(run),
467                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
468                ))
469            }
470            Some(StrategyVariant::HillClimb(hill_climb_variant)) => {
471                let (run, runs) = self
472                    .to_hill_climb_builder()
473                    .with_variant(hill_climb_variant)
474                    .call_par_repeatedly(number_of_species)?;
475                Ok((
476                    Box::new(run),
477                    runs.into_iter().map(|r| Box::new(r) as _).collect(),
478                ))
479            }
480            None => Err(TryFromBuilderError("StrategyVariant is required")),
481        }
482    }
483}