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#[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 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 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 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 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 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}