1use std::time::Instant;
6
7use rand::Rng;
8
9use crate::diagnostics::{EvolutionResult, EvolutionStats, GenerationStats, TimingStats};
10use crate::error::EvolutionError;
11use crate::fitness::traits::{Fitness, FitnessValue};
12use crate::genome::bit_string::BitString;
13use crate::genome::bounds::MultiBounds;
14use crate::genome::permutation::Permutation;
15use crate::genome::real_vector::RealVector;
16use crate::genome::traits::EvolutionaryGenome;
17use crate::hyperparameter::bayesian::{
18 ThompsonConfig, ThompsonSamplingTuner, TunableMutation, PARAM_CROSSOVER_PROB,
19 PARAM_MUTATION_RATE,
20};
21use crate::operators::crossover::{OxCrossover, SbxCrossover, UniformCrossover};
22use crate::operators::mutation::{BitFlipMutation, PermutationSwapMutation, PolynomialMutation};
23use crate::operators::selection::TournamentSelection;
24use crate::operators::traits::{
25 BoundedCrossoverOperator, BoundedMutationOperator, CrossoverOperator, MutationOperator,
26 SelectionOperator,
27};
28use crate::population::individual::Individual;
29use crate::population::population::Population;
30use crate::termination::{EvolutionState, MaxGenerations, TerminationCriterion};
31
32fn validate_config(config: &SimpleGAConfig, bounds: &MultiBounds) -> Result<(), EvolutionError> {
38 if config.population_size == 0 {
39 return Err(EvolutionError::Configuration(
40 "population_size must be at least 1".to_string(),
41 ));
42 }
43 if bounds.dimension() == 0 {
44 return Err(EvolutionError::Configuration(
45 "bounds must have at least one dimension".to_string(),
46 ));
47 }
48 if config.elitism && config.elite_count > config.population_size {
49 return Err(EvolutionError::Configuration(format!(
50 "elite_count ({}) cannot exceed population_size ({})",
51 config.elite_count, config.population_size
52 )));
53 }
54 if !(0.0..=1.0).contains(&config.crossover_probability) {
55 return Err(EvolutionError::Configuration(format!(
56 "crossover_probability must be in [0, 1], got {}",
57 config.crossover_probability
58 )));
59 }
60 Ok(())
61}
62
63#[derive(Clone, Debug)]
65pub struct SimpleGAConfig {
66 pub population_size: usize,
68 pub elitism: bool,
70 pub elite_count: usize,
72 pub crossover_probability: f64,
74 pub parallel_evaluation: bool,
76}
77
78impl Default for SimpleGAConfig {
79 fn default() -> Self {
80 Self {
81 population_size: 100,
82 elitism: true,
83 elite_count: 1,
84 crossover_probability: 0.9,
85 parallel_evaluation: true,
86 }
87 }
88}
89
90pub struct SimpleGABuilder<G, F, S, C, M, Fit, Term>
92where
93 G: EvolutionaryGenome,
94 F: FitnessValue,
95{
96 config: SimpleGAConfig,
97 bounds: Option<MultiBounds>,
98 selection: Option<S>,
99 crossover: Option<C>,
100 mutation: Option<M>,
101 fitness: Option<Fit>,
102 termination: Option<Term>,
103 adaptive: Option<ThompsonConfig>,
104 _phantom: std::marker::PhantomData<(G, F)>,
105}
106
107impl<G, F> SimpleGABuilder<G, F, (), (), (), (), ()>
108where
109 G: EvolutionaryGenome,
110 F: FitnessValue,
111{
112 pub fn new() -> Self {
114 Self {
115 config: SimpleGAConfig::default(),
116 bounds: None,
117 selection: None,
118 crossover: None,
119 mutation: None,
120 fitness: None,
121 termination: None,
122 adaptive: None,
123 _phantom: std::marker::PhantomData,
124 }
125 }
126}
127
128impl
129 SimpleGABuilder<RealVector, f64, TournamentSelection, SbxCrossover, PolynomialMutation, (), ()>
130{
131 pub fn real_valued() -> Self {
155 SimpleGABuilder::new()
156 .selection(TournamentSelection::new(3))
157 .crossover(SbxCrossover::new(20.0))
158 .mutation(PolynomialMutation::new(20.0))
159 }
160}
161
162impl
163 SimpleGABuilder<
164 BitString,
165 usize,
166 TournamentSelection,
167 UniformCrossover,
168 BitFlipMutation,
169 (),
170 (),
171 >
172{
173 pub fn bit_string() -> Self {
179 SimpleGABuilder::new()
180 .selection(TournamentSelection::new(3))
181 .crossover(UniformCrossover::new())
182 .mutation(BitFlipMutation::new())
183 }
184}
185
186impl
187 SimpleGABuilder<
188 Permutation,
189 f64,
190 TournamentSelection,
191 OxCrossover,
192 PermutationSwapMutation,
193 (),
194 (),
195 >
196{
197 pub fn permutation() -> Self {
203 SimpleGABuilder::new()
204 .selection(TournamentSelection::new(3))
205 .crossover(OxCrossover)
206 .mutation(PermutationSwapMutation::default())
207 }
208}
209
210impl<G, F> Default for SimpleGABuilder<G, F, (), (), (), (), ()>
211where
212 G: EvolutionaryGenome,
213 F: FitnessValue,
214{
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220impl<G, F, S, C, M, Fit, Term> SimpleGABuilder<G, F, S, C, M, Fit, Term>
221where
222 G: EvolutionaryGenome,
223 F: FitnessValue,
224{
225 pub fn population_size(mut self, size: usize) -> Self {
227 self.config.population_size = size;
228 self
229 }
230
231 pub fn elitism(mut self, enabled: bool) -> Self {
233 self.config.elitism = enabled;
234 self
235 }
236
237 pub fn elite_count(mut self, count: usize) -> Self {
239 self.config.elite_count = count;
240 self
241 }
242
243 pub fn crossover_probability(mut self, probability: f64) -> Self {
245 self.config.crossover_probability = probability;
246 self
247 }
248
249 pub fn parallel_evaluation(mut self, enabled: bool) -> Self {
251 self.config.parallel_evaluation = enabled;
252 self
253 }
254
255 pub fn bounds(mut self, bounds: MultiBounds) -> Self {
257 self.bounds = Some(bounds);
258 self
259 }
260
261 pub fn adaptive_operators(mut self, config: ThompsonConfig) -> Self {
270 self.adaptive = Some(config);
271 self
272 }
273
274 pub fn selection<NewS>(self, selection: NewS) -> SimpleGABuilder<G, F, NewS, C, M, Fit, Term>
276 where
277 NewS: SelectionOperator<G>,
278 {
279 SimpleGABuilder {
280 config: self.config,
281 bounds: self.bounds,
282 selection: Some(selection),
283 adaptive: self.adaptive,
284 crossover: self.crossover,
285 mutation: self.mutation,
286 fitness: self.fitness,
287 termination: self.termination,
288 _phantom: std::marker::PhantomData,
289 }
290 }
291
292 pub fn crossover<NewC>(self, crossover: NewC) -> SimpleGABuilder<G, F, S, NewC, M, Fit, Term>
294 where
295 NewC: CrossoverOperator<G>,
296 {
297 SimpleGABuilder {
298 config: self.config,
299 bounds: self.bounds,
300 selection: self.selection,
301 crossover: Some(crossover),
302 adaptive: self.adaptive,
303 mutation: self.mutation,
304 fitness: self.fitness,
305 termination: self.termination,
306 _phantom: std::marker::PhantomData,
307 }
308 }
309
310 pub fn mutation<NewM>(self, mutation: NewM) -> SimpleGABuilder<G, F, S, C, NewM, Fit, Term>
312 where
313 NewM: MutationOperator<G>,
314 {
315 SimpleGABuilder {
316 config: self.config,
317 bounds: self.bounds,
318 selection: self.selection,
319 crossover: self.crossover,
320 mutation: Some(mutation),
321 adaptive: self.adaptive,
322 fitness: self.fitness,
323 termination: self.termination,
324 _phantom: std::marker::PhantomData,
325 }
326 }
327
328 pub fn fitness<NewFit>(self, fitness: NewFit) -> SimpleGABuilder<G, F, S, C, M, NewFit, Term>
330 where
331 NewFit: Fitness<Genome = G, Value = F>,
332 {
333 SimpleGABuilder {
334 config: self.config,
335 bounds: self.bounds,
336 selection: self.selection,
337 crossover: self.crossover,
338 mutation: self.mutation,
339 fitness: Some(fitness),
340 adaptive: self.adaptive,
341 termination: self.termination,
342 _phantom: std::marker::PhantomData,
343 }
344 }
345
346 pub fn termination<NewTerm>(
348 self,
349 termination: NewTerm,
350 ) -> SimpleGABuilder<G, F, S, C, M, Fit, NewTerm>
351 where
352 NewTerm: TerminationCriterion<G, F>,
353 {
354 SimpleGABuilder {
355 config: self.config,
356 bounds: self.bounds,
357 selection: self.selection,
358 crossover: self.crossover,
359 mutation: self.mutation,
360 fitness: self.fitness,
361 termination: Some(termination),
362 adaptive: self.adaptive,
363 _phantom: std::marker::PhantomData,
364 }
365 }
366
367 pub fn max_generations(
369 self,
370 max: usize,
371 ) -> SimpleGABuilder<G, F, S, C, M, Fit, MaxGenerations> {
372 SimpleGABuilder {
373 config: self.config,
374 bounds: self.bounds,
375 selection: self.selection,
376 crossover: self.crossover,
377 mutation: self.mutation,
378 fitness: self.fitness,
379 termination: Some(MaxGenerations::new(max)),
380 adaptive: self.adaptive,
381 _phantom: std::marker::PhantomData,
382 }
383 }
384}
385
386#[cfg(feature = "parallel")]
388impl<G, F, S, C, M, Fit, Term> SimpleGABuilder<G, F, S, C, M, Fit, Term>
389where
390 G: EvolutionaryGenome + Send + Sync,
391 F: FitnessValue + Send,
392 S: SelectionOperator<G>,
393 C: CrossoverOperator<G>,
394 M: MutationOperator<G>,
395 Fit: Fitness<Genome = G, Value = F> + Sync,
396 Term: TerminationCriterion<G, F>,
397{
398 #[allow(clippy::type_complexity)]
400 pub fn build(self) -> Result<SimpleGA<G, F, S, C, M, Fit, Term>, EvolutionError> {
401 let bounds = self
402 .bounds
403 .ok_or_else(|| EvolutionError::Configuration("Bounds must be specified".to_string()))?;
404
405 let selection = self.selection.ok_or_else(|| {
406 EvolutionError::Configuration("Selection operator must be specified".to_string())
407 })?;
408
409 let crossover = self.crossover.ok_or_else(|| {
410 EvolutionError::Configuration("Crossover operator must be specified".to_string())
411 })?;
412
413 let mutation = self.mutation.ok_or_else(|| {
414 EvolutionError::Configuration("Mutation operator must be specified".to_string())
415 })?;
416
417 let fitness = self.fitness.ok_or_else(|| {
418 EvolutionError::Configuration("Fitness function must be specified".to_string())
419 })?;
420
421 let termination = self.termination.ok_or_else(|| {
422 EvolutionError::Configuration("Termination criterion must be specified".to_string())
423 })?;
424
425 validate_config(&self.config, &bounds)?;
428
429 let tuner = self.adaptive.map(|cfg| cfg.build_tuner());
430
431 Ok(SimpleGA {
432 config: self.config,
433 bounds,
434 selection,
435 crossover,
436 mutation,
437 fitness,
438 termination,
439 tuner,
440 _phantom: std::marker::PhantomData,
441 })
442 }
443}
444
445#[cfg(not(feature = "parallel"))]
447impl<G, F, S, C, M, Fit, Term> SimpleGABuilder<G, F, S, C, M, Fit, Term>
448where
449 G: EvolutionaryGenome,
450 F: FitnessValue,
451 S: SelectionOperator<G>,
452 C: CrossoverOperator<G>,
453 M: MutationOperator<G>,
454 Fit: Fitness<Genome = G, Value = F>,
455 Term: TerminationCriterion<G, F>,
456{
457 #[allow(clippy::type_complexity)]
459 pub fn build(self) -> Result<SimpleGA<G, F, S, C, M, Fit, Term>, EvolutionError> {
460 let bounds = self
461 .bounds
462 .ok_or_else(|| EvolutionError::Configuration("Bounds must be specified".to_string()))?;
463
464 let selection = self.selection.ok_or_else(|| {
465 EvolutionError::Configuration("Selection operator must be specified".to_string())
466 })?;
467
468 let crossover = self.crossover.ok_or_else(|| {
469 EvolutionError::Configuration("Crossover operator must be specified".to_string())
470 })?;
471
472 let mutation = self.mutation.ok_or_else(|| {
473 EvolutionError::Configuration("Mutation operator must be specified".to_string())
474 })?;
475
476 let fitness = self.fitness.ok_or_else(|| {
477 EvolutionError::Configuration("Fitness function must be specified".to_string())
478 })?;
479
480 let termination = self.termination.ok_or_else(|| {
481 EvolutionError::Configuration("Termination criterion must be specified".to_string())
482 })?;
483
484 validate_config(&self.config, &bounds)?;
487
488 let tuner = self.adaptive.map(|cfg| cfg.build_tuner());
489
490 Ok(SimpleGA {
491 config: self.config,
492 bounds,
493 selection,
494 crossover,
495 mutation,
496 fitness,
497 termination,
498 tuner,
499 _phantom: std::marker::PhantomData,
500 })
501 }
502}
503
504pub struct SimpleGA<G, F, S, C, M, Fit, Term>
508where
509 G: EvolutionaryGenome,
510 F: FitnessValue,
511{
512 config: SimpleGAConfig,
513 bounds: MultiBounds,
514 selection: S,
515 crossover: C,
516 mutation: M,
517 fitness: Fit,
518 termination: Term,
519 tuner: Option<ThompsonSamplingTuner>,
520 _phantom: std::marker::PhantomData<(G, F)>,
521}
522
523impl<G, F, S, C, M, Fit, Term> SimpleGA<G, F, S, C, M, Fit, Term>
529where
530 G: EvolutionaryGenome,
531 F: FitnessValue,
532 S: SelectionOperator<G>,
533 C: CrossoverOperator<G>,
534 M: MutationOperator<G> + TunableMutation + Clone,
535 Fit: Fitness<Genome = G, Value = F>,
536 Term: TerminationCriterion<G, F>,
537{
538 pub fn tuner(&self) -> Option<&ThompsonSamplingTuner> {
544 self.tuner.as_ref()
545 }
546
547 pub fn run_adaptive<R: Rng>(
556 &mut self,
557 rng: &mut R,
558 ) -> Result<EvolutionResult<G, F>, EvolutionError> {
559 let start_time = Instant::now();
560
561 let mut tuner = self
563 .tuner
564 .take()
565 .unwrap_or_else(|| ThompsonConfig::default().build_tuner());
566
567 let mut population: Population<G, F> =
568 Population::random(self.config.population_size, &self.bounds, rng);
569 population.evaluate(&self.fitness);
570
571 let mut stats = EvolutionStats::new();
572 let mut evaluations = population.len();
573 let mut fitness_history: Vec<f64> = Vec::new();
574
575 let mut best_individual = population
576 .best()
577 .ok_or(EvolutionError::EmptyPopulation)?
578 .clone();
579
580 let gen_stats = GenerationStats::from_population(&population, 0, evaluations);
581 fitness_history.push(gen_stats.best_fitness);
582 stats.record(gen_stats);
583 tuner.snapshot(0);
584
585 loop {
586 let state = EvolutionState {
587 generation: population.generation(),
588 evaluations,
589 best_fitness: best_individual.fitness_value().to_f64(),
590 population: &population,
591 fitness_history: &fitness_history,
592 };
593 if self.termination.should_terminate(&state) {
594 stats.set_termination_reason(self.termination.reason());
595 break;
596 }
597
598 let next_generation = population.generation() + 1;
599
600 tuner.select_all(rng);
602 let crossover_prob = tuner
603 .selected(PARAM_CROSSOVER_PROB)
604 .unwrap_or(self.config.crossover_probability);
605 let mut mutation = self.mutation.clone();
606 if let Some(mutation_prob) = tuner.selected(PARAM_MUTATION_RATE) {
607 mutation.set_mutation_probability(mutation_prob);
608 }
609
610 let mut new_population: Population<G, F> =
611 Population::with_capacity(self.config.population_size);
612
613 if self.config.elitism {
615 let mut sorted = population.clone();
616 sorted.sort_by_fitness();
617 for i in 0..self.config.elite_count.min(sorted.len()) {
618 new_population.push(sorted[i].clone());
619 }
620 }
621
622 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
623
624 while new_population.len() < self.config.population_size {
625 let p1 = self.selection.select(&selection_pool, rng);
626 let p2 = self.selection.select(&selection_pool, rng);
627 let parent1 = &selection_pool[p1].0;
628 let parent2 = &selection_pool[p2].0;
629 let parent_best = selection_pool[p1].1.max(selection_pool[p2].1);
631
632 let (mut child1, mut child2) = if rng.gen::<f64>() < crossover_prob {
633 match self.crossover.crossover(parent1, parent2, rng).genome() {
634 Some((c1, c2)) => (c1, c2),
635 None => (parent1.clone(), parent2.clone()),
636 }
637 } else {
638 (parent1.clone(), parent2.clone())
639 };
640
641 mutation.mutate(&mut child1, rng);
642 mutation.mutate(&mut child2, rng);
643
644 let f1 = self.fitness.evaluate(&child1);
646 tuner.observe(f1.to_f64() > parent_best);
647 evaluations += 1;
648 let mut ind1 = Individual::with_fitness(child1, f1);
649 ind1.birth_generation = next_generation;
650 new_population.push(ind1);
651
652 if new_population.len() < self.config.population_size {
653 let f2 = self.fitness.evaluate(&child2);
654 tuner.observe(f2.to_f64() > parent_best);
655 evaluations += 1;
656 let mut ind2 = Individual::with_fitness(child2, f2);
657 ind2.birth_generation = next_generation;
658 new_population.push(ind2);
659 }
660 }
661
662 while new_population.len() > self.config.population_size {
663 new_population.pop();
664 }
665
666 new_population.set_generation(next_generation);
667 population = new_population;
668
669 if let Some(best) = population.best() {
670 if best.is_better_than(&best_individual) {
671 best_individual = best.clone();
672 }
673 }
674
675 let gen_stats =
676 GenerationStats::from_population(&population, population.generation(), evaluations);
677 fitness_history.push(gen_stats.best_fitness);
678 stats.record(gen_stats);
679 tuner.snapshot(next_generation);
680 }
681
682 stats.set_runtime(start_time.elapsed());
683
684 self.tuner = Some(tuner);
686
687 Ok(EvolutionResult::new(
688 best_individual.genome,
689 best_individual.fitness.unwrap(),
690 population.generation(),
691 evaluations,
692 )
693 .with_stats(stats))
694 }
695}
696
697#[cfg(feature = "parallel")]
699impl<G, F, S, C, M, Fit, Term> SimpleGA<G, F, S, C, M, Fit, Term>
700where
701 G: EvolutionaryGenome + Send + Sync,
702 F: FitnessValue + Send,
703 S: SelectionOperator<G>,
704 C: CrossoverOperator<G>,
705 M: MutationOperator<G>,
706 Fit: Fitness<Genome = G, Value = F> + Sync,
707 Term: TerminationCriterion<G, F>,
708{
709 pub fn builder() -> SimpleGABuilder<G, F, (), (), (), (), ()> {
711 SimpleGABuilder::new()
712 }
713
714 pub fn run<R: Rng>(&self, rng: &mut R) -> Result<EvolutionResult<G, F>, EvolutionError> {
716 let start_time = Instant::now();
717
718 let mut population: Population<G, F> =
720 Population::random(self.config.population_size, &self.bounds, rng);
721
722 let eval_start = Instant::now();
724 if self.config.parallel_evaluation {
725 population.evaluate_parallel(&self.fitness);
726 } else {
727 population.evaluate(&self.fitness);
728 }
729 let eval_time = eval_start.elapsed();
730
731 let mut stats = EvolutionStats::new();
732 let mut evaluations = population.len();
733 let mut fitness_history: Vec<f64> = Vec::new();
734
735 let mut best_individual = population
737 .best()
738 .ok_or(EvolutionError::EmptyPopulation)?
739 .clone();
740
741 let gen_stats = GenerationStats::from_population(&population, 0, evaluations)
743 .with_timing(TimingStats::new().with_evaluation(eval_time));
744 fitness_history.push(gen_stats.best_fitness);
745 stats.record(gen_stats);
746
747 loop {
749 let state = EvolutionState {
751 generation: population.generation(),
752 evaluations,
753 best_fitness: best_individual.fitness_value().to_f64(),
754 population: &population,
755 fitness_history: &fitness_history,
756 };
757
758 if self.termination.should_terminate(&state) {
759 stats.set_termination_reason(self.termination.reason());
760 break;
761 }
762
763 let gen_start = Instant::now();
764
765 let mut new_population: Population<G, F> =
767 Population::with_capacity(self.config.population_size);
768
769 if self.config.elitism {
771 let mut sorted = population.clone();
772 sorted.sort_by_fitness();
773 for i in 0..self.config.elite_count.min(sorted.len()) {
774 new_population.push(sorted[i].clone());
775 }
776 }
777
778 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
780
781 let _selection_start = Instant::now();
783 let mut selection_time = std::time::Duration::ZERO;
784 let mut crossover_time = std::time::Duration::ZERO;
785 let mut mutation_time = std::time::Duration::ZERO;
786
787 while new_population.len() < self.config.population_size {
788 let sel_start = Instant::now();
790 let parent1_idx = self.selection.select(&selection_pool, rng);
791 let parent2_idx = self.selection.select(&selection_pool, rng);
792 selection_time += sel_start.elapsed();
793
794 let parent1 = &selection_pool[parent1_idx].0;
795 let parent2 = &selection_pool[parent2_idx].0;
796
797 let cross_start = Instant::now();
799 let (mut child1, mut child2) =
800 if rng.gen::<f64>() < self.config.crossover_probability {
801 match self.crossover.crossover(parent1, parent2, rng).genome() {
802 Some((c1, c2)) => (c1, c2),
803 None => (parent1.clone(), parent2.clone()),
804 }
805 } else {
806 (parent1.clone(), parent2.clone())
807 };
808 crossover_time += cross_start.elapsed();
809
810 let mut_start = Instant::now();
812 self.mutation.mutate(&mut child1, rng);
813 self.mutation.mutate(&mut child2, rng);
814 mutation_time += mut_start.elapsed();
815
816 new_population.push(Individual::with_generation(
818 child1,
819 population.generation() + 1,
820 ));
821 if new_population.len() < self.config.population_size {
822 new_population.push(Individual::with_generation(
823 child2,
824 population.generation() + 1,
825 ));
826 }
827 }
828
829 while new_population.len() > self.config.population_size {
831 new_population.pop();
832 }
833
834 let eval_start = Instant::now();
836 if self.config.parallel_evaluation {
837 new_population.evaluate_parallel(&self.fitness);
838 } else {
839 new_population.evaluate(&self.fitness);
840 }
841 let eval_time = eval_start.elapsed();
842 evaluations += new_population.len()
843 - (if self.config.elitism {
844 self.config.elite_count
845 } else {
846 0
847 });
848
849 new_population.set_generation(population.generation() + 1);
851 population = new_population;
852
853 if let Some(best) = population.best() {
855 if best.is_better_than(&best_individual) {
856 best_individual = best.clone();
857 }
858 }
859
860 let timing = TimingStats::new()
862 .with_selection(selection_time)
863 .with_crossover(crossover_time)
864 .with_mutation(mutation_time)
865 .with_evaluation(eval_time)
866 .with_total(gen_start.elapsed());
867
868 let gen_stats =
869 GenerationStats::from_population(&population, population.generation(), evaluations)
870 .with_timing(timing);
871 fitness_history.push(gen_stats.best_fitness);
872 stats.record(gen_stats);
873 }
874
875 stats.set_runtime(start_time.elapsed());
876
877 Ok(EvolutionResult::new(
878 best_individual.genome,
879 best_individual.fitness.unwrap(),
880 population.generation(),
881 evaluations,
882 )
883 .with_stats(stats))
884 }
885}
886
887#[cfg(feature = "parallel")]
889impl<G, F, S, C, M, Fit, Term> SimpleGA<G, F, S, C, M, Fit, Term>
890where
891 G: EvolutionaryGenome + Send + Sync,
892 F: FitnessValue + Send,
893 S: SelectionOperator<G>,
894 C: BoundedCrossoverOperator<G>,
895 M: BoundedMutationOperator<G>,
896 Fit: Fitness<Genome = G, Value = F> + Sync,
897 Term: TerminationCriterion<G, F>,
898{
899 pub fn run_bounded<R: Rng>(
901 &self,
902 rng: &mut R,
903 ) -> Result<EvolutionResult<G, F>, EvolutionError> {
904 let start_time = Instant::now();
905
906 let mut population: Population<G, F> =
908 Population::random(self.config.population_size, &self.bounds, rng);
909
910 if self.config.parallel_evaluation {
912 population.evaluate_parallel(&self.fitness);
913 } else {
914 population.evaluate(&self.fitness);
915 }
916
917 let mut stats = EvolutionStats::new();
918 let mut evaluations = population.len();
919 let mut fitness_history: Vec<f64> = Vec::new();
920
921 let mut best_individual = population
923 .best()
924 .ok_or(EvolutionError::EmptyPopulation)?
925 .clone();
926
927 let gen_stats = GenerationStats::from_population(&population, 0, evaluations);
929 fitness_history.push(gen_stats.best_fitness);
930 stats.record(gen_stats);
931
932 loop {
934 let state = EvolutionState {
936 generation: population.generation(),
937 evaluations,
938 best_fitness: best_individual.fitness_value().to_f64(),
939 population: &population,
940 fitness_history: &fitness_history,
941 };
942
943 if self.termination.should_terminate(&state) {
944 stats.set_termination_reason(self.termination.reason());
945 break;
946 }
947
948 let mut new_population: Population<G, F> =
950 Population::with_capacity(self.config.population_size);
951
952 if self.config.elitism {
954 let mut sorted = population.clone();
955 sorted.sort_by_fitness();
956 for i in 0..self.config.elite_count.min(sorted.len()) {
957 new_population.push(sorted[i].clone());
958 }
959 }
960
961 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
962
963 while new_population.len() < self.config.population_size {
964 let parent1_idx = self.selection.select(&selection_pool, rng);
965 let parent2_idx = self.selection.select(&selection_pool, rng);
966
967 let parent1 = &selection_pool[parent1_idx].0;
968 let parent2 = &selection_pool[parent2_idx].0;
969
970 let (mut child1, mut child2) =
971 if rng.gen::<f64>() < self.config.crossover_probability {
972 match self
973 .crossover
974 .crossover_bounded(parent1, parent2, &self.bounds, rng)
975 .genome()
976 {
977 Some((c1, c2)) => (c1, c2),
978 None => (parent1.clone(), parent2.clone()),
979 }
980 } else {
981 (parent1.clone(), parent2.clone())
982 };
983
984 self.mutation.mutate_bounded(&mut child1, &self.bounds, rng);
985 self.mutation.mutate_bounded(&mut child2, &self.bounds, rng);
986
987 new_population.push(Individual::with_generation(
988 child1,
989 population.generation() + 1,
990 ));
991 if new_population.len() < self.config.population_size {
992 new_population.push(Individual::with_generation(
993 child2,
994 population.generation() + 1,
995 ));
996 }
997 }
998
999 while new_population.len() > self.config.population_size {
1000 new_population.pop();
1001 }
1002
1003 if self.config.parallel_evaluation {
1004 new_population.evaluate_parallel(&self.fitness);
1005 } else {
1006 new_population.evaluate(&self.fitness);
1007 }
1008 evaluations += new_population.len()
1009 - (if self.config.elitism {
1010 self.config.elite_count
1011 } else {
1012 0
1013 });
1014
1015 new_population.set_generation(population.generation() + 1);
1016 population = new_population;
1017
1018 if let Some(best) = population.best() {
1019 if best.is_better_than(&best_individual) {
1020 best_individual = best.clone();
1021 }
1022 }
1023
1024 let gen_stats =
1025 GenerationStats::from_population(&population, population.generation(), evaluations);
1026 fitness_history.push(gen_stats.best_fitness);
1027 stats.record(gen_stats);
1028 }
1029
1030 stats.set_runtime(start_time.elapsed());
1031
1032 Ok(EvolutionResult::new(
1033 best_individual.genome,
1034 best_individual.fitness.unwrap(),
1035 population.generation(),
1036 evaluations,
1037 )
1038 .with_stats(stats))
1039 }
1040}
1041
1042#[cfg(not(feature = "parallel"))]
1044impl<G, F, S, C, M, Fit, Term> SimpleGA<G, F, S, C, M, Fit, Term>
1045where
1046 G: EvolutionaryGenome,
1047 F: FitnessValue,
1048 S: SelectionOperator<G>,
1049 C: CrossoverOperator<G>,
1050 M: MutationOperator<G>,
1051 Fit: Fitness<Genome = G, Value = F>,
1052 Term: TerminationCriterion<G, F>,
1053{
1054 pub fn builder() -> SimpleGABuilder<G, F, (), (), (), (), ()> {
1056 SimpleGABuilder::new()
1057 }
1058
1059 pub fn run<R: Rng>(&self, rng: &mut R) -> Result<EvolutionResult<G, F>, EvolutionError> {
1066 let mut state = self.init_run(rng)?;
1067 while self.step_generation(&mut state, rng)? {}
1068 Ok(self.finish_run(state))
1069 }
1070}
1071
1072impl<G, F, S, C, M, Fit, Term> SimpleGA<G, F, S, C, M, Fit, Term>
1080where
1081 G: EvolutionaryGenome,
1082 F: FitnessValue,
1083 S: SelectionOperator<G>,
1084 C: CrossoverOperator<G>,
1085 M: MutationOperator<G>,
1086 Fit: Fitness<Genome = G, Value = F>,
1087 Term: TerminationCriterion<G, F>,
1088{
1089 pub fn init_run<R: Rng>(&self, rng: &mut R) -> Result<SimpleGaRun<G, F>, EvolutionError> {
1098 let start_time = Instant::now();
1099
1100 let mut population: Population<G, F> =
1102 Population::random(self.config.population_size, &self.bounds, rng);
1103
1104 let eval_start = Instant::now();
1106 population.evaluate(&self.fitness);
1107 let eval_time = eval_start.elapsed();
1108
1109 let mut stats = EvolutionStats::new();
1110 let evaluations = population.len();
1111 let mut fitness_history: Vec<f64> = Vec::new();
1112
1113 let best_individual = population
1115 .best()
1116 .ok_or(EvolutionError::EmptyPopulation)?
1117 .clone();
1118
1119 let gen_stats = GenerationStats::from_population(&population, 0, evaluations)
1121 .with_timing(TimingStats::new().with_evaluation(eval_time));
1122 fitness_history.push(gen_stats.best_fitness);
1123 stats.record(gen_stats);
1124
1125 Ok(SimpleGaRun {
1126 population,
1127 best_individual,
1128 evaluations,
1129 fitness_history,
1130 stats,
1131 start_time,
1132 terminated: false,
1133 })
1134 }
1135
1136 pub fn step_generation<R: Rng>(
1144 &self,
1145 state: &mut SimpleGaRun<G, F>,
1146 rng: &mut R,
1147 ) -> Result<bool, EvolutionError> {
1148 if state.terminated {
1149 return Ok(false);
1150 }
1151
1152 let evo_state = EvolutionState {
1154 generation: state.population.generation(),
1155 evaluations: state.evaluations,
1156 best_fitness: state.best_individual.fitness_value().to_f64(),
1157 population: &state.population,
1158 fitness_history: &state.fitness_history,
1159 };
1160
1161 if self.termination.should_terminate(&evo_state) {
1162 state
1163 .stats
1164 .set_termination_reason(self.termination.reason());
1165 state.terminated = true;
1166 return Ok(false);
1167 }
1168
1169 let gen_start = Instant::now();
1170
1171 let mut new_population: Population<G, F> =
1173 Population::with_capacity(self.config.population_size);
1174
1175 if self.config.elitism {
1177 let mut sorted = state.population.clone();
1178 sorted.sort_by_fitness();
1179 for i in 0..self.config.elite_count.min(sorted.len()) {
1180 new_population.push(sorted[i].clone());
1181 }
1182 }
1183
1184 let selection_pool: Vec<(G, f64)> = state.population.as_fitness_pairs();
1186
1187 let _selection_start = Instant::now();
1189 let mut selection_time = std::time::Duration::ZERO;
1190 let mut crossover_time = std::time::Duration::ZERO;
1191 let mut mutation_time = std::time::Duration::ZERO;
1192
1193 while new_population.len() < self.config.population_size {
1194 let sel_start = Instant::now();
1196 let parent1_idx = self.selection.select(&selection_pool, rng);
1197 let parent2_idx = self.selection.select(&selection_pool, rng);
1198 selection_time += sel_start.elapsed();
1199
1200 let parent1 = &selection_pool[parent1_idx].0;
1201 let parent2 = &selection_pool[parent2_idx].0;
1202
1203 let cross_start = Instant::now();
1205 let (mut child1, mut child2) = if rng.gen::<f64>() < self.config.crossover_probability {
1206 match self.crossover.crossover(parent1, parent2, rng).genome() {
1207 Some((c1, c2)) => (c1, c2),
1208 None => (parent1.clone(), parent2.clone()),
1209 }
1210 } else {
1211 (parent1.clone(), parent2.clone())
1212 };
1213 crossover_time += cross_start.elapsed();
1214
1215 let mut_start = Instant::now();
1217 self.mutation.mutate(&mut child1, rng);
1218 self.mutation.mutate(&mut child2, rng);
1219 mutation_time += mut_start.elapsed();
1220
1221 new_population.push(Individual::with_generation(
1223 child1,
1224 state.population.generation() + 1,
1225 ));
1226 if new_population.len() < self.config.population_size {
1227 new_population.push(Individual::with_generation(
1228 child2,
1229 state.population.generation() + 1,
1230 ));
1231 }
1232 }
1233
1234 while new_population.len() > self.config.population_size {
1236 new_population.pop();
1237 }
1238
1239 let eval_start = Instant::now();
1241 new_population.evaluate(&self.fitness);
1242 let eval_time = eval_start.elapsed();
1243 state.evaluations += new_population.len()
1244 - (if self.config.elitism {
1245 self.config.elite_count
1246 } else {
1247 0
1248 });
1249
1250 new_population.set_generation(state.population.generation() + 1);
1252 state.population = new_population;
1253
1254 if let Some(best) = state.population.best() {
1256 if best.is_better_than(&state.best_individual) {
1257 state.best_individual = best.clone();
1258 }
1259 }
1260
1261 let timing = TimingStats::new()
1263 .with_selection(selection_time)
1264 .with_crossover(crossover_time)
1265 .with_mutation(mutation_time)
1266 .with_evaluation(eval_time)
1267 .with_total(gen_start.elapsed());
1268
1269 let gen_stats = GenerationStats::from_population(
1270 &state.population,
1271 state.population.generation(),
1272 state.evaluations,
1273 )
1274 .with_timing(timing);
1275 state.fitness_history.push(gen_stats.best_fitness);
1276 state.stats.record(gen_stats);
1277
1278 Ok(true)
1279 }
1280
1281 pub fn finish_run(&self, mut state: SimpleGaRun<G, F>) -> EvolutionResult<G, F> {
1284 state.stats.set_runtime(state.start_time.elapsed());
1285
1286 EvolutionResult::new(
1287 state.best_individual.genome,
1288 state
1289 .best_individual
1290 .fitness
1291 .expect("best individual is always evaluated"),
1292 state.population.generation(),
1293 state.evaluations,
1294 )
1295 .with_stats(state.stats)
1296 }
1297
1298 pub fn inject_migrants(&self, state: &mut SimpleGaRun<G, F>, migrants: Vec<G>) {
1308 if migrants.is_empty() {
1309 return;
1310 }
1311
1312 state.population.sort_by_fitness();
1314 let n = state.population.len();
1315 if n == 0 {
1316 return;
1317 }
1318 let k = migrants.len().min(n);
1319
1320 let evaluated: Vec<Individual<G, F>> = migrants
1321 .into_iter()
1322 .take(k)
1323 .map(|genome| {
1324 let value = self.fitness.evaluate(&genome);
1325 Individual::with_fitness(genome, value)
1326 })
1327 .collect();
1328
1329 {
1330 let individuals = state.population.individuals_mut();
1331 for (i, individual) in evaluated.into_iter().enumerate() {
1332 let idx = n - 1 - i; individuals[idx] = individual;
1334 }
1335 }
1336 state.evaluations += k;
1337
1338 if let Some(best) = state.population.best() {
1340 if best.is_better_than(&state.best_individual) {
1341 state.best_individual = best.clone();
1342 }
1343 }
1344 }
1345}
1346
1347impl<G, S, C, M, Fit, Term> SimpleGA<G, f64, S, C, M, Fit, Term>
1361where
1362 G: EvolutionaryGenome,
1363 S: SelectionOperator<G>,
1364 C: CrossoverOperator<G>,
1365 M: MutationOperator<G>,
1366 Fit: Fitness<Genome = G, Value = f64>,
1367 Term: TerminationCriterion<G, f64>,
1368{
1369 pub fn checkpoint_run<R>(
1379 &self,
1380 state: &SimpleGaRun<G, f64>,
1381 rng: &R,
1382 ) -> Result<crate::checkpoint::Checkpoint<G>, crate::error::CheckpointError>
1383 where
1384 R: crate::checkpoint::SnapshotRng,
1385 {
1386 let individuals: Vec<Individual<G>> = state.population.iter().cloned().collect();
1387 crate::checkpoint::Checkpoint::new(state.generation(), individuals)
1388 .with_evaluations(state.evaluations())
1389 .with_best(state.best_individual.clone())
1390 .with_statistics(state.stats.generations.clone())
1391 .with_rng(rng)
1392 }
1393
1394 pub fn resume<R>(
1406 &self,
1407 checkpoint: &crate::checkpoint::Checkpoint<G>,
1408 ) -> Result<(SimpleGaRun<G, f64>, R), crate::error::CheckpointError>
1409 where
1410 R: crate::checkpoint::SnapshotRng,
1411 {
1412 let rng = checkpoint.restore_rng::<R>()?.ok_or_else(|| {
1413 crate::error::CheckpointError::Corrupted(
1414 "checkpoint has no captured RNG state; bit-identical resume requires a \
1415 SnapshotRng captured via SimpleGA::checkpoint_run / Checkpoint::with_rng"
1416 .to_string(),
1417 )
1418 })?;
1419
1420 let mut population: Population<G, f64> =
1422 Population::with_capacity(checkpoint.population.len());
1423 for ind in &checkpoint.population {
1424 population.push(ind.clone());
1425 }
1426 population.set_generation(checkpoint.generation);
1427
1428 let best_individual = match &checkpoint.best {
1430 Some(best) => best.clone(),
1431 None => population.best().cloned().ok_or_else(|| {
1432 crate::error::CheckpointError::Corrupted("empty population".to_string())
1433 })?,
1434 };
1435
1436 let mut stats = EvolutionStats::new();
1440 stats.generations = checkpoint.statistics.clone();
1441 let fitness_history: Vec<f64> = checkpoint
1442 .statistics
1443 .iter()
1444 .map(|g| g.best_fitness)
1445 .collect();
1446
1447 let state = SimpleGaRun {
1448 population,
1449 best_individual,
1450 evaluations: checkpoint.evaluations,
1451 fitness_history,
1452 stats,
1453 start_time: Instant::now(),
1454 terminated: false,
1455 };
1456 Ok((state, rng))
1457 }
1458
1459 pub fn run_from_checkpoint<R>(
1468 &self,
1469 checkpoint: &crate::checkpoint::Checkpoint<G>,
1470 ) -> Result<EvolutionResult<G, f64>, EvolutionError>
1471 where
1472 R: crate::checkpoint::SnapshotRng + Rng,
1473 {
1474 let (mut state, mut rng) = self.resume::<R>(checkpoint)?;
1475 while self.step_generation(&mut state, &mut rng)? {}
1476 Ok(self.finish_run(state))
1477 }
1478}
1479
1480pub struct SimpleGaRun<G, F = f64>
1493where
1494 G: EvolutionaryGenome,
1495 F: FitnessValue,
1496{
1497 population: Population<G, F>,
1498 best_individual: Individual<G, F>,
1499 evaluations: usize,
1500 fitness_history: Vec<f64>,
1501 stats: EvolutionStats,
1502 start_time: Instant,
1503 terminated: bool,
1504}
1505
1506impl<G, F> SimpleGaRun<G, F>
1507where
1508 G: EvolutionaryGenome,
1509 F: FitnessValue,
1510{
1511 pub fn generation(&self) -> usize {
1513 self.population.generation()
1514 }
1515
1516 pub fn evaluations(&self) -> usize {
1518 self.evaluations
1519 }
1520
1521 pub fn best_fitness(&self) -> f64 {
1523 self.best_individual.fitness_value().to_f64()
1524 }
1525
1526 pub fn best_genome(&self) -> &G {
1528 &self.best_individual.genome
1529 }
1530
1531 pub fn fitness_history(&self) -> &[f64] {
1533 &self.fitness_history
1534 }
1535
1536 pub fn is_terminated(&self) -> bool {
1538 self.terminated
1539 }
1540
1541 pub fn best_genomes(&self, k: usize) -> Vec<G> {
1545 let mut sorted = self.population.clone();
1546 sorted.sort_by_fitness();
1547 sorted
1548 .iter()
1549 .take(k)
1550 .map(|ind| ind.genome.clone())
1551 .collect()
1552 }
1553}
1554
1555#[cfg(not(feature = "parallel"))]
1557impl<G, F, S, C, M, Fit, Term> SimpleGA<G, F, S, C, M, Fit, Term>
1558where
1559 G: EvolutionaryGenome,
1560 F: FitnessValue,
1561 S: SelectionOperator<G>,
1562 C: BoundedCrossoverOperator<G>,
1563 M: BoundedMutationOperator<G>,
1564 Fit: Fitness<Genome = G, Value = F>,
1565 Term: TerminationCriterion<G, F>,
1566{
1567 pub fn run_bounded<R: Rng>(
1569 &self,
1570 rng: &mut R,
1571 ) -> Result<EvolutionResult<G, F>, EvolutionError> {
1572 let start_time = Instant::now();
1573
1574 let mut population: Population<G, F> =
1576 Population::random(self.config.population_size, &self.bounds, rng);
1577
1578 population.evaluate(&self.fitness);
1580
1581 let mut stats = EvolutionStats::new();
1582 let mut evaluations = population.len();
1583 let mut fitness_history: Vec<f64> = Vec::new();
1584
1585 let mut best_individual = population
1587 .best()
1588 .ok_or(EvolutionError::EmptyPopulation)?
1589 .clone();
1590
1591 let gen_stats = GenerationStats::from_population(&population, 0, evaluations);
1593 fitness_history.push(gen_stats.best_fitness);
1594 stats.record(gen_stats);
1595
1596 loop {
1598 let state = EvolutionState {
1600 generation: population.generation(),
1601 evaluations,
1602 best_fitness: best_individual.fitness_value().to_f64(),
1603 population: &population,
1604 fitness_history: &fitness_history,
1605 };
1606
1607 if self.termination.should_terminate(&state) {
1608 stats.set_termination_reason(self.termination.reason());
1609 break;
1610 }
1611
1612 let mut new_population: Population<G, F> =
1614 Population::with_capacity(self.config.population_size);
1615
1616 if self.config.elitism {
1618 let mut sorted = population.clone();
1619 sorted.sort_by_fitness();
1620 for i in 0..self.config.elite_count.min(sorted.len()) {
1621 new_population.push(sorted[i].clone());
1622 }
1623 }
1624
1625 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
1626
1627 while new_population.len() < self.config.population_size {
1628 let parent1_idx = self.selection.select(&selection_pool, rng);
1629 let parent2_idx = self.selection.select(&selection_pool, rng);
1630
1631 let parent1 = &selection_pool[parent1_idx].0;
1632 let parent2 = &selection_pool[parent2_idx].0;
1633
1634 let (mut child1, mut child2) =
1635 if rng.gen::<f64>() < self.config.crossover_probability {
1636 match self
1637 .crossover
1638 .crossover_bounded(parent1, parent2, &self.bounds, rng)
1639 .genome()
1640 {
1641 Some((c1, c2)) => (c1, c2),
1642 None => (parent1.clone(), parent2.clone()),
1643 }
1644 } else {
1645 (parent1.clone(), parent2.clone())
1646 };
1647
1648 self.mutation.mutate_bounded(&mut child1, &self.bounds, rng);
1649 self.mutation.mutate_bounded(&mut child2, &self.bounds, rng);
1650
1651 new_population.push(Individual::with_generation(
1652 child1,
1653 population.generation() + 1,
1654 ));
1655 if new_population.len() < self.config.population_size {
1656 new_population.push(Individual::with_generation(
1657 child2,
1658 population.generation() + 1,
1659 ));
1660 }
1661 }
1662
1663 while new_population.len() > self.config.population_size {
1664 new_population.pop();
1665 }
1666
1667 new_population.evaluate(&self.fitness);
1669 evaluations += new_population.len()
1670 - (if self.config.elitism {
1671 self.config.elite_count
1672 } else {
1673 0
1674 });
1675
1676 new_population.set_generation(population.generation() + 1);
1677 population = new_population;
1678
1679 if let Some(best) = population.best() {
1680 if best.is_better_than(&best_individual) {
1681 best_individual = best.clone();
1682 }
1683 }
1684
1685 let gen_stats =
1686 GenerationStats::from_population(&population, population.generation(), evaluations);
1687 fitness_history.push(gen_stats.best_fitness);
1688 stats.record(gen_stats);
1689 }
1690
1691 stats.set_runtime(start_time.elapsed());
1692
1693 Ok(EvolutionResult::new(
1694 best_individual.genome,
1695 best_individual.fitness.unwrap(),
1696 population.generation(),
1697 evaluations,
1698 )
1699 .with_stats(stats))
1700 }
1701}
1702
1703#[cfg(test)]
1704mod tests {
1705 use super::*;
1706 use crate::fitness::benchmarks::{OneMax, Sphere};
1707 use crate::genome::traits::RealValuedGenome;
1708 use crate::operators::crossover::{SbxCrossover, UniformCrossover};
1709 use crate::operators::mutation::{BitFlipMutation, GaussianMutation, PolynomialMutation};
1710 use crate::operators::selection::TournamentSelection;
1711 use crate::termination::TargetFitness;
1712
1713 #[test]
1714 fn test_simple_ga_builder() {
1715 let bounds = MultiBounds::symmetric(5.0, 10);
1716 let ga = SimpleGABuilder::new()
1717 .population_size(50)
1718 .bounds(bounds)
1719 .selection(TournamentSelection::new(3))
1720 .crossover(SbxCrossover::new(20.0))
1721 .mutation(PolynomialMutation::new(20.0))
1722 .fitness(Sphere::new(10))
1723 .max_generations(10)
1724 .build();
1725
1726 assert!(ga.is_ok());
1727 }
1728
1729 #[test]
1730 fn test_simple_ga_missing_bounds() {
1731 let ga = SimpleGABuilder::new()
1735 .population_size(50)
1736 .selection(TournamentSelection::new(3))
1738 .crossover(SbxCrossover::new(20.0))
1739 .mutation(PolynomialMutation::new(20.0))
1740 .fitness(Sphere::new(10))
1741 .max_generations(10)
1742 .build();
1743
1744 assert!(ga.is_err());
1745 if let Err(e) = ga {
1746 assert!(e.to_string().contains("Bounds"));
1747 }
1748 }
1749
1750 #[test]
1751 fn test_simple_ga_sphere() {
1752 let mut rng = rand::thread_rng();
1753 let bounds = MultiBounds::symmetric(5.12, 10);
1754
1755 let ga = SimpleGABuilder::new()
1756 .population_size(50)
1757 .bounds(bounds)
1758 .selection(TournamentSelection::new(3))
1759 .crossover(SbxCrossover::new(20.0))
1760 .mutation(PolynomialMutation::new(20.0))
1761 .fitness(Sphere::new(10))
1762 .max_generations(100)
1763 .build()
1764 .unwrap();
1765
1766 let result = ga.run(&mut rng).unwrap();
1767
1768 assert!(
1772 result.best_fitness > -200.0,
1773 "Expected fitness > -200, got {}",
1774 result.best_fitness
1775 ); assert!(result.generations <= 100);
1777 assert!(result.evaluations > 0);
1778 }
1779
1780 #[cfg(not(feature = "parallel"))]
1784 #[test]
1785 fn test_step_api_matches_run() {
1786 use rand::SeedableRng;
1787
1788 let build = || {
1789 SimpleGABuilder::new()
1790 .population_size(30)
1791 .bounds(MultiBounds::symmetric(5.12, 6))
1792 .selection(TournamentSelection::new(3))
1793 .crossover(SbxCrossover::new(20.0))
1794 .mutation(PolynomialMutation::new(20.0))
1795 .fitness(Sphere::new(6))
1796 .max_generations(25)
1797 .build()
1798 .unwrap()
1799 };
1800
1801 let ga_a = build();
1803 let mut rng_a = rand::rngs::StdRng::seed_from_u64(2024);
1804 let run_result = ga_a.run(&mut rng_a).unwrap();
1805
1806 let ga_b = build();
1808 let mut rng_b = rand::rngs::StdRng::seed_from_u64(2024);
1809 let mut state = ga_b.init_run(&mut rng_b).unwrap();
1810 let mut steps = 0;
1811 while ga_b.step_generation(&mut state, &mut rng_b).unwrap() {
1812 steps += 1;
1813 }
1814 assert!(state.is_terminated());
1815 assert_eq!(steps, 25, "should take exactly max_generations steps");
1816 let step_result = ga_b.finish_run(state);
1817
1818 assert_eq!(run_result.generations, step_result.generations);
1819 assert_eq!(run_result.evaluations, step_result.evaluations);
1820 assert_eq!(run_result.best_fitness, step_result.best_fitness);
1821 assert_eq!(
1822 run_result.best_genome.genes(),
1823 step_result.best_genome.genes()
1824 );
1825 }
1826
1827 #[cfg(not(feature = "parallel"))]
1829 #[test]
1830 fn test_inject_migrants_replaces_worst() {
1831 use rand::SeedableRng;
1832
1833 let ga = SimpleGABuilder::new()
1834 .population_size(20)
1835 .bounds(MultiBounds::symmetric(5.12, 4))
1836 .selection(TournamentSelection::new(3))
1837 .crossover(SbxCrossover::new(20.0))
1838 .mutation(PolynomialMutation::new(20.0))
1839 .fitness(Sphere::new(4))
1840 .max_generations(10)
1841 .build()
1842 .unwrap();
1843
1844 let mut rng = rand::rngs::StdRng::seed_from_u64(7);
1845 let mut state = ga.init_run(&mut rng).unwrap();
1846 for _ in 0..3 {
1847 ga.step_generation(&mut state, &mut rng).unwrap();
1848 }
1849
1850 let evals_before = state.evaluations();
1851 let optimum = RealVector::new(vec![0.0; 4]);
1854 ga.inject_migrants(&mut state, vec![optimum]);
1855
1856 assert_eq!(state.evaluations(), evals_before + 1);
1857 assert!(
1858 state.best_fitness() > -1e-9,
1859 "injected optimum should become the best (got {})",
1860 state.best_fitness()
1861 );
1862
1863 let top = state.best_genomes(2);
1865 assert_eq!(top.len(), 2);
1866 }
1867
1868 #[test]
1869 fn test_simple_ga_onemax() {
1870 let mut rng = rand::thread_rng();
1871 let bounds = MultiBounds::uniform(crate::genome::bounds::Bounds::unit(), 20);
1872
1873 let ga = SimpleGABuilder::new()
1874 .population_size(50)
1875 .bounds(bounds)
1876 .selection(TournamentSelection::new(3))
1877 .crossover(UniformCrossover::new())
1878 .mutation(BitFlipMutation::new())
1879 .fitness(OneMax::new(20))
1880 .max_generations(50)
1881 .build()
1882 .unwrap();
1883
1884 let result = ga.run(&mut rng).unwrap();
1885
1886 assert!(result.best_fitness >= 15); }
1889
1890 #[test]
1891 fn test_simple_ga_target_fitness() {
1892 let mut rng = rand::thread_rng();
1893 let bounds = MultiBounds::symmetric(5.12, 5);
1894
1895 let ga = SimpleGABuilder::new()
1896 .population_size(100)
1897 .bounds(bounds)
1898 .selection(TournamentSelection::new(5))
1899 .crossover(SbxCrossover::new(20.0))
1900 .mutation(PolynomialMutation::new(20.0))
1901 .fitness(Sphere::new(5))
1902 .termination(TargetFitness::with_tolerance(0.0, 0.1)) .build()
1904 .unwrap();
1905
1906 let result = ga.run(&mut rng).unwrap();
1907
1908 assert!(result.best_fitness >= -0.1);
1910 assert_eq!(
1911 result.stats.termination_reason.as_deref(),
1912 Some("Target fitness reached")
1913 );
1914 }
1915
1916 #[test]
1917 fn test_simple_ga_bounded() {
1918 let mut rng = rand::thread_rng();
1919 let bounds = MultiBounds::symmetric(5.12, 10);
1920
1921 let ga = SimpleGABuilder::new()
1922 .population_size(50)
1923 .bounds(bounds.clone())
1924 .selection(TournamentSelection::new(3))
1925 .crossover(SbxCrossover::new(20.0))
1926 .mutation(PolynomialMutation::new(20.0))
1927 .fitness(Sphere::new(10))
1928 .max_generations(50)
1929 .build()
1930 .unwrap();
1931
1932 let result = ga.run_bounded(&mut rng).unwrap();
1933
1934 for gene in result.best_genome.genes() {
1936 assert!(*gene >= -5.12 && *gene <= 5.12);
1937 }
1938 }
1939
1940 #[test]
1941 fn test_simple_ga_elitism() {
1942 let mut rng = rand::thread_rng();
1943 let bounds = MultiBounds::symmetric(5.12, 5);
1944
1945 let ga_no_elite = SimpleGABuilder::new()
1947 .population_size(20)
1948 .elitism(false)
1949 .bounds(bounds.clone())
1950 .selection(TournamentSelection::new(2))
1951 .crossover(SbxCrossover::new(20.0))
1952 .mutation(PolynomialMutation::new(20.0))
1953 .fitness(Sphere::new(5))
1954 .max_generations(20)
1955 .build()
1956 .unwrap();
1957
1958 let ga_elite = SimpleGABuilder::new()
1960 .population_size(20)
1961 .elitism(true)
1962 .elite_count(2)
1963 .bounds(bounds)
1964 .selection(TournamentSelection::new(2))
1965 .crossover(SbxCrossover::new(20.0))
1966 .mutation(PolynomialMutation::new(20.0))
1967 .fitness(Sphere::new(5))
1968 .max_generations(20)
1969 .build()
1970 .unwrap();
1971
1972 let result_no_elite = ga_no_elite.run(&mut rng);
1974 let result_elite = ga_elite.run(&mut rng);
1975
1976 assert!(result_no_elite.is_ok());
1977 assert!(result_elite.is_ok());
1978 }
1979
1980 #[test]
1981 fn test_simple_ga_statistics() {
1982 let mut rng = rand::thread_rng();
1983 let bounds = MultiBounds::symmetric(5.12, 5);
1984
1985 let ga = SimpleGABuilder::new()
1986 .population_size(20)
1987 .bounds(bounds)
1988 .selection(TournamentSelection::new(2))
1989 .crossover(SbxCrossover::new(20.0))
1990 .mutation(PolynomialMutation::new(20.0))
1991 .fitness(Sphere::new(5))
1992 .max_generations(10)
1993 .build()
1994 .unwrap();
1995
1996 let result = ga.run(&mut rng).unwrap();
1997
1998 assert_eq!(result.stats.num_generations(), 11); assert!(result.stats.total_runtime_ms > 0.0);
2001
2002 let history = result.stats.best_fitness_history();
2004 for i in 1..history.len() {
2005 assert!(history[i] >= history[i - 1] - 0.001); }
2007 }
2008
2009 #[test]
2013 fn test_real_valued_constructor_no_turbofish() {
2014 let mut rng = rand::thread_rng();
2015 let ga = SimpleGABuilder::real_valued()
2016 .population_size(40)
2017 .bounds(MultiBounds::symmetric(5.12, 10))
2018 .fitness(Sphere::new(10))
2019 .max_generations(20)
2020 .build()
2021 .unwrap();
2022 let result = ga.run(&mut rng).unwrap();
2023 assert!(result.evaluations > 0);
2024 }
2025
2026 #[test]
2029 fn test_real_valued_constructor_override_operator() {
2030 let mut rng = rand::thread_rng();
2031 let ga = SimpleGABuilder::real_valued()
2032 .mutation(GaussianMutation::new(0.1))
2033 .population_size(30)
2034 .bounds(MultiBounds::symmetric(5.12, 5))
2035 .fitness(Sphere::new(5))
2036 .max_generations(10)
2037 .build()
2038 .unwrap();
2039 assert!(ga.run(&mut rng).is_ok());
2040 }
2041
2042 #[test]
2044 fn test_bit_string_constructor_no_turbofish() {
2045 let mut rng = rand::thread_rng();
2046 let ga = SimpleGABuilder::bit_string()
2047 .population_size(40)
2048 .bounds(MultiBounds::uniform(
2049 crate::genome::bounds::Bounds::unit(),
2050 20,
2051 ))
2052 .fitness(OneMax::new(20))
2053 .max_generations(30)
2054 .build()
2055 .unwrap();
2056 let result = ga.run(&mut rng).unwrap();
2057 assert!(result.best_fitness >= 10);
2058 }
2059
2060 #[test]
2062 fn test_permutation_constructor_no_turbofish() {
2063 use crate::fitness::traits::FnFitness;
2064 use crate::genome::permutation::Permutation;
2065 use crate::genome::traits::PermutationGenome;
2066
2067 let mut rng = rand::thread_rng();
2068 let fitness = FnFitness::new(|p: &Permutation| -> f64 {
2070 -(p.permutation()
2071 .iter()
2072 .enumerate()
2073 .map(|(i, &v)| (v as f64 - i as f64).abs())
2074 .sum::<f64>())
2075 });
2076 let ga = SimpleGABuilder::permutation()
2077 .population_size(30)
2078 .bounds(MultiBounds::symmetric(1.0, 8))
2079 .fitness(fitness)
2080 .max_generations(10)
2081 .build()
2082 .unwrap();
2083 assert!(ga.run(&mut rng).is_ok());
2084 }
2085
2086 #[test]
2089 fn test_build_rejects_zero_population() {
2090 let ga = SimpleGABuilder::real_valued()
2091 .population_size(0)
2092 .bounds(MultiBounds::symmetric(5.12, 10))
2093 .fitness(Sphere::new(10))
2094 .max_generations(10)
2095 .build();
2096 assert!(ga.is_err());
2097 let msg = ga.err().unwrap().to_string();
2098 assert!(msg.contains("population_size"), "got: {msg}");
2099 }
2100
2101 #[test]
2103 fn test_build_rejects_out_of_range_crossover_probability() {
2104 let ga = SimpleGABuilder::real_valued()
2105 .crossover_probability(1.5)
2106 .population_size(20)
2107 .bounds(MultiBounds::symmetric(5.12, 5))
2108 .fitness(Sphere::new(5))
2109 .max_generations(5)
2110 .build();
2111 assert!(ga.is_err());
2112 assert!(ga
2113 .err()
2114 .unwrap()
2115 .to_string()
2116 .contains("crossover_probability"));
2117 }
2118
2119 #[test]
2121 fn test_build_rejects_elite_count_exceeding_population() {
2122 let ga = SimpleGABuilder::real_valued()
2123 .population_size(10)
2124 .elite_count(50)
2125 .bounds(MultiBounds::symmetric(5.12, 5))
2126 .fitness(Sphere::new(5))
2127 .max_generations(5)
2128 .build();
2129 assert!(ga.is_err());
2130 assert!(ga.err().unwrap().to_string().contains("elite_count"));
2131 }
2132
2133 #[test]
2137 fn test_run_adaptive_feeds_tuner() {
2138 use crate::hyperparameter::bayesian::{ThompsonConfig, PARAM_MUTATION_RATE};
2139
2140 let mut rng = rand::thread_rng();
2141 let mut ga = SimpleGABuilder::real_valued()
2142 .population_size(30)
2143 .bounds(MultiBounds::symmetric(5.12, 8))
2144 .fitness(Sphere::new(8))
2145 .max_generations(15)
2146 .adaptive_operators(ThompsonConfig::default())
2147 .build()
2148 .unwrap();
2149
2150 let result = ga.run_adaptive(&mut rng).unwrap();
2151 assert!(result.evaluations > 0);
2152
2153 let tuner = ga
2154 .tuner()
2155 .expect("tuner should be present after run_adaptive");
2156 assert!(
2157 tuner.total_observations() > 0,
2158 "tuner must receive improvement feedback"
2159 );
2160 let mr = tuner.parameter(PARAM_MUTATION_RATE).unwrap();
2161 assert!(
2162 mr.total_observations() > 0.0,
2163 "mutation-rate arms must accumulate observations"
2164 );
2165 }
2166}