1use std::time::Instant;
8
9use rand::Rng;
10
11use crate::diagnostics::{EvolutionResult, EvolutionStats, GenerationStats, TimingStats};
12use crate::error::EvolutionError;
13use crate::fitness::traits::{Fitness, FitnessValue};
14use crate::genome::bounds::MultiBounds;
15use crate::genome::traits::EvolutionaryGenome;
16use crate::operators::traits::{
17 BoundedCrossoverOperator, BoundedMutationOperator, CrossoverOperator, MutationOperator,
18 SelectionOperator,
19};
20use crate::population::individual::Individual;
21use crate::population::population::Population;
22use crate::termination::{EvolutionState, MaxGenerations, TerminationCriterion};
23
24#[derive(Clone, Debug, Default)]
32pub enum ReplacementStrategy {
33 #[default]
37 ReplaceWorst,
38 ReplaceRandom,
41 ReplaceParent,
44 ReplaceIfBetter,
47 TournamentWorst(usize),
50}
51
52impl ReplacementStrategy {
53 fn requires_improvement(&self) -> bool {
57 matches!(self, ReplacementStrategy::ReplaceIfBetter)
58 }
59}
60
61#[derive(Clone, Debug)]
63pub struct SteadyStateConfig {
64 pub population_size: usize,
66 pub offspring_count: usize,
68 pub crossover_probability: f64,
70 pub replacement: ReplacementStrategy,
72 pub parallel_evaluation: bool,
74 pub steps_per_generation: usize,
76 pub prevent_duplicates: bool,
81}
82
83impl Default for SteadyStateConfig {
84 fn default() -> Self {
85 Self {
86 population_size: 100,
87 offspring_count: 2,
88 crossover_probability: 0.9,
89 replacement: ReplacementStrategy::ReplaceWorst,
90 parallel_evaluation: false,
91 steps_per_generation: 50, prevent_duplicates: false,
93 }
94 }
95}
96
97pub struct SteadyStateBuilder<G, F, S, C, M, Fit, Term>
99where
100 G: EvolutionaryGenome,
101 F: FitnessValue,
102{
103 config: SteadyStateConfig,
104 bounds: Option<MultiBounds>,
105 selection: Option<S>,
106 crossover: Option<C>,
107 mutation: Option<M>,
108 fitness: Option<Fit>,
109 termination: Option<Term>,
110 _phantom: std::marker::PhantomData<(G, F)>,
111}
112
113impl<G, F> SteadyStateBuilder<G, F, (), (), (), (), ()>
114where
115 G: EvolutionaryGenome,
116 F: FitnessValue,
117{
118 pub fn new() -> Self {
120 Self {
121 config: SteadyStateConfig::default(),
122 bounds: None,
123 selection: None,
124 crossover: None,
125 mutation: None,
126 fitness: None,
127 termination: None,
128 _phantom: std::marker::PhantomData,
129 }
130 }
131}
132
133impl<G, F> Default for SteadyStateBuilder<G, F, (), (), (), (), ()>
134where
135 G: EvolutionaryGenome,
136 F: FitnessValue,
137{
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl<G, F, S, C, M, Fit, Term> SteadyStateBuilder<G, F, S, C, M, Fit, Term>
144where
145 G: EvolutionaryGenome,
146 F: FitnessValue,
147{
148 pub fn population_size(mut self, size: usize) -> Self {
150 self.config.population_size = size;
151 self
152 }
153
154 pub fn offspring_count(mut self, count: usize) -> Self {
156 self.config.offspring_count = count;
157 self
158 }
159
160 pub fn crossover_probability(mut self, probability: f64) -> Self {
162 self.config.crossover_probability = probability;
163 self
164 }
165
166 pub fn replacement(mut self, strategy: ReplacementStrategy) -> Self {
168 self.config.replacement = strategy;
169 self
170 }
171
172 pub fn parallel_evaluation(mut self, enabled: bool) -> Self {
174 self.config.parallel_evaluation = enabled;
175 self
176 }
177
178 pub fn steps_per_generation(mut self, steps: usize) -> Self {
180 self.config.steps_per_generation = steps;
181 self
182 }
183
184 pub fn prevent_duplicates(mut self, enabled: bool) -> Self {
191 self.config.prevent_duplicates = enabled;
192 self
193 }
194
195 pub fn bounds(mut self, bounds: MultiBounds) -> Self {
197 self.bounds = Some(bounds);
198 self
199 }
200
201 pub fn selection<NewS>(self, selection: NewS) -> SteadyStateBuilder<G, F, NewS, C, M, Fit, Term>
203 where
204 NewS: SelectionOperator<G>,
205 {
206 SteadyStateBuilder {
207 config: self.config,
208 bounds: self.bounds,
209 selection: Some(selection),
210 crossover: self.crossover,
211 mutation: self.mutation,
212 fitness: self.fitness,
213 termination: self.termination,
214 _phantom: std::marker::PhantomData,
215 }
216 }
217
218 pub fn crossover<NewC>(self, crossover: NewC) -> SteadyStateBuilder<G, F, S, NewC, M, Fit, Term>
220 where
221 NewC: CrossoverOperator<G>,
222 {
223 SteadyStateBuilder {
224 config: self.config,
225 bounds: self.bounds,
226 selection: self.selection,
227 crossover: Some(crossover),
228 mutation: self.mutation,
229 fitness: self.fitness,
230 termination: self.termination,
231 _phantom: std::marker::PhantomData,
232 }
233 }
234
235 pub fn mutation<NewM>(self, mutation: NewM) -> SteadyStateBuilder<G, F, S, C, NewM, Fit, Term>
237 where
238 NewM: MutationOperator<G>,
239 {
240 SteadyStateBuilder {
241 config: self.config,
242 bounds: self.bounds,
243 selection: self.selection,
244 crossover: self.crossover,
245 mutation: Some(mutation),
246 fitness: self.fitness,
247 termination: self.termination,
248 _phantom: std::marker::PhantomData,
249 }
250 }
251
252 pub fn fitness<NewFit>(self, fitness: NewFit) -> SteadyStateBuilder<G, F, S, C, M, NewFit, Term>
254 where
255 NewFit: Fitness<Genome = G, Value = F>,
256 {
257 SteadyStateBuilder {
258 config: self.config,
259 bounds: self.bounds,
260 selection: self.selection,
261 crossover: self.crossover,
262 mutation: self.mutation,
263 fitness: Some(fitness),
264 termination: self.termination,
265 _phantom: std::marker::PhantomData,
266 }
267 }
268
269 pub fn termination<NewTerm>(
271 self,
272 termination: NewTerm,
273 ) -> SteadyStateBuilder<G, F, S, C, M, Fit, NewTerm>
274 where
275 NewTerm: TerminationCriterion<G, F>,
276 {
277 SteadyStateBuilder {
278 config: self.config,
279 bounds: self.bounds,
280 selection: self.selection,
281 crossover: self.crossover,
282 mutation: self.mutation,
283 fitness: self.fitness,
284 termination: Some(termination),
285 _phantom: std::marker::PhantomData,
286 }
287 }
288
289 pub fn max_generations(
291 self,
292 max: usize,
293 ) -> SteadyStateBuilder<G, F, S, C, M, Fit, MaxGenerations> {
294 SteadyStateBuilder {
295 config: self.config,
296 bounds: self.bounds,
297 selection: self.selection,
298 crossover: self.crossover,
299 mutation: self.mutation,
300 fitness: self.fitness,
301 termination: Some(MaxGenerations::new(max)),
302 _phantom: std::marker::PhantomData,
303 }
304 }
305}
306
307impl<G, F, S, C, M, Fit, Term> SteadyStateBuilder<G, F, S, C, M, Fit, Term>
308where
309 G: EvolutionaryGenome + Send + Sync,
310 F: FitnessValue + Send,
311 S: SelectionOperator<G>,
312 C: CrossoverOperator<G>,
313 M: MutationOperator<G>,
314 Fit: Fitness<Genome = G, Value = F> + Sync,
315 Term: TerminationCriterion<G, F>,
316{
317 #[allow(clippy::type_complexity)]
319 pub fn build(self) -> Result<SteadyStateGA<G, F, S, C, M, Fit, Term>, EvolutionError> {
320 let bounds = self
321 .bounds
322 .ok_or_else(|| EvolutionError::Configuration("Bounds must be specified".to_string()))?;
323
324 let selection = self.selection.ok_or_else(|| {
325 EvolutionError::Configuration("Selection operator must be specified".to_string())
326 })?;
327
328 let crossover = self.crossover.ok_or_else(|| {
329 EvolutionError::Configuration("Crossover operator must be specified".to_string())
330 })?;
331
332 let mutation = self.mutation.ok_or_else(|| {
333 EvolutionError::Configuration("Mutation operator must be specified".to_string())
334 })?;
335
336 let fitness = self.fitness.ok_or_else(|| {
337 EvolutionError::Configuration("Fitness function must be specified".to_string())
338 })?;
339
340 let termination = self.termination.ok_or_else(|| {
341 EvolutionError::Configuration("Termination criterion must be specified".to_string())
342 })?;
343
344 Ok(SteadyStateGA {
345 config: self.config,
346 bounds,
347 selection,
348 crossover,
349 mutation,
350 fitness,
351 termination,
352 _phantom: std::marker::PhantomData,
353 })
354 }
355}
356
357pub struct SteadyStateGA<G, F, S, C, M, Fit, Term>
362where
363 G: EvolutionaryGenome,
364 F: FitnessValue,
365{
366 config: SteadyStateConfig,
367 bounds: MultiBounds,
368 selection: S,
369 crossover: C,
370 mutation: M,
371 fitness: Fit,
372 termination: Term,
373 _phantom: std::marker::PhantomData<(G, F)>,
374}
375
376impl<G, F, S, C, M, Fit, Term> SteadyStateGA<G, F, S, C, M, Fit, Term>
377where
378 G: EvolutionaryGenome + Send + Sync,
379 F: FitnessValue + Send,
380 S: SelectionOperator<G>,
381 C: CrossoverOperator<G>,
382 M: MutationOperator<G>,
383 Fit: Fitness<Genome = G, Value = F> + Sync,
384 Term: TerminationCriterion<G, F>,
385{
386 pub fn builder() -> SteadyStateBuilder<G, F, (), (), (), (), ()> {
388 SteadyStateBuilder::new()
389 }
390
391 fn find_replacement_index<R: Rng>(&self, population: &Population<G, F>, rng: &mut R) -> usize {
393 match &self.config.replacement {
394 ReplacementStrategy::ReplaceWorst | ReplacementStrategy::ReplaceIfBetter => {
397 population
399 .iter()
400 .enumerate()
401 .min_by(|(_, a), (_, b)| {
402 a.fitness_value()
403 .partial_cmp(b.fitness_value())
404 .unwrap_or(std::cmp::Ordering::Equal)
405 })
406 .map(|(i, _)| i)
407 .unwrap_or(0)
408 }
409 ReplacementStrategy::ReplaceRandom => rng.gen_range(0..population.len()),
410 ReplacementStrategy::ReplaceParent => {
411 0
413 }
414 ReplacementStrategy::TournamentWorst(tournament_size) => {
415 let mut worst_idx = rng.gen_range(0..population.len());
417 let mut worst_fitness = population[worst_idx].fitness_value().to_f64();
418
419 for _ in 1..*tournament_size {
420 let idx = rng.gen_range(0..population.len());
421 let fitness = population[idx].fitness_value().to_f64();
422 if fitness < worst_fitness {
423 worst_idx = idx;
424 worst_fitness = fitness;
425 }
426 }
427 worst_idx
428 }
429 }
430 }
431
432 #[allow(clippy::type_complexity)]
439 fn generate_offspring<R: Rng>(
440 &self,
441 selection_pool: &[(G, f64)],
442 rng: &mut R,
443 ) -> Vec<(Individual<G, F>, (usize, f64), (usize, f64))> {
444 let target = self.config.offspring_count;
445 let mut offspring = Vec::with_capacity(target);
446
447 while offspring.len() < target {
448 let p1_idx = self.selection.select(selection_pool, rng);
449 let p2_idx = self.selection.select(selection_pool, rng);
450 let p1 = (p1_idx, selection_pool[p1_idx].1);
451 let p2 = (p2_idx, selection_pool[p2_idx].1);
452 let parent1 = &selection_pool[p1_idx].0;
453 let parent2 = &selection_pool[p2_idx].0;
454
455 let (mut child1, mut child2) = if rng.gen::<f64>() < self.config.crossover_probability {
456 match self.crossover.crossover(parent1, parent2, rng).genome() {
457 Some((c1, c2)) => (c1, c2),
458 None => (parent1.clone(), parent2.clone()),
459 }
460 } else {
461 (parent1.clone(), parent2.clone())
462 };
463
464 self.mutation.mutate(&mut child1, rng);
465 let mut ind1 = Individual::new(child1);
466 ind1.set_fitness(self.fitness.evaluate(ind1.genome()));
467 offspring.push((ind1, p1, p2));
468
469 if offspring.len() < target {
470 self.mutation.mutate(&mut child2, rng);
471 let mut ind2 = Individual::new(child2);
472 ind2.set_fitness(self.fitness.evaluate(ind2.genome()));
473 offspring.push((ind2, p1, p2));
474 }
475 }
476
477 offspring
478 }
479
480 fn place_offspring<R: Rng>(
489 &self,
490 population: &mut Population<G, F>,
491 child: Individual<G, F>,
492 parent1: (usize, f64),
493 parent2: (usize, f64),
494 rng: &mut R,
495 ) where
496 G: PartialEq,
497 {
498 if self.config.prevent_duplicates
501 && population.iter().any(|ind| ind.genome() == child.genome())
502 {
503 return;
504 }
505
506 let replace_idx = match &self.config.replacement {
507 ReplacementStrategy::ReplaceParent => {
508 let (p1_idx, p1_fit) = parent1;
509 let (p2_idx, p2_fit) = parent2;
510 let child_fit = child.fitness_value().to_f64();
511 if child_fit > p1_fit.min(p2_fit) {
512 if p1_fit < p2_fit {
513 p1_idx
514 } else {
515 p2_idx
516 }
517 } else {
518 return; }
520 }
521 _ => self.find_replacement_index(population, rng),
522 };
523
524 if self.config.replacement.requires_improvement()
528 && !child
529 .fitness_value()
530 .is_better_than(population[replace_idx].fitness_value())
531 {
532 return;
533 }
534
535 population[replace_idx] = child;
536 }
537
538 pub fn run<R: Rng>(&self, rng: &mut R) -> Result<EvolutionResult<G, F>, EvolutionError>
544 where
545 G: PartialEq,
546 {
547 let start_time = Instant::now();
548
549 let mut population: Population<G, F> =
551 Population::random(self.config.population_size, &self.bounds, rng);
552
553 if self.config.parallel_evaluation {
555 population.evaluate_parallel(&self.fitness);
556 } else {
557 population.evaluate(&self.fitness);
558 }
559
560 let mut stats = EvolutionStats::new();
561 let mut evaluations = population.len();
562 let mut fitness_history: Vec<f64> = Vec::new();
563 let mut step_count = 0usize;
564 let mut generation = 0usize;
565
566 let mut best_individual = population
568 .best()
569 .ok_or(EvolutionError::EmptyPopulation)?
570 .clone();
571
572 let gen_stats = GenerationStats::from_population(&population, 0, evaluations);
574 fitness_history.push(gen_stats.best_fitness);
575 stats.record(gen_stats);
576
577 loop {
579 let state = EvolutionState {
581 generation,
582 evaluations,
583 best_fitness: best_individual.fitness_value().to_f64(),
584 population: &population,
585 fitness_history: &fitness_history,
586 };
587
588 if self.termination.should_terminate(&state) {
589 stats.set_termination_reason(self.termination.reason());
590 break;
591 }
592
593 let step_start = Instant::now();
594
595 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
597
598 let offspring = self.generate_offspring(&selection_pool, rng);
602 evaluations += offspring.len();
603
604 for (child, parent1, parent2) in offspring {
606 self.place_offspring(&mut population, child, parent1, parent2, rng);
607 }
608
609 if let Some(best) = population.best() {
611 if best.is_better_than(&best_individual) {
612 best_individual = best.clone();
613 }
614 }
615
616 step_count += 1;
617
618 if step_count.is_multiple_of(self.config.steps_per_generation) {
620 generation += 1;
621 population.set_generation(generation);
622
623 let timing = TimingStats::new()
624 .with_total(step_start.elapsed() * self.config.steps_per_generation as u32);
625
626 let gen_stats =
627 GenerationStats::from_population(&population, generation, evaluations)
628 .with_timing(timing);
629 fitness_history.push(gen_stats.best_fitness);
630 stats.record(gen_stats);
631 }
632 }
633
634 stats.set_runtime(start_time.elapsed());
635
636 Ok(EvolutionResult::new(
637 best_individual.genome,
638 best_individual.fitness.unwrap(),
639 generation,
640 evaluations,
641 )
642 .with_stats(stats))
643 }
644}
645
646impl<G, F, S, C, M, Fit, Term> SteadyStateGA<G, F, S, C, M, Fit, Term>
648where
649 G: EvolutionaryGenome + Send + Sync,
650 F: FitnessValue + Send,
651 S: SelectionOperator<G>,
652 C: BoundedCrossoverOperator<G>,
653 M: BoundedMutationOperator<G>,
654 Fit: Fitness<Genome = G, Value = F> + Sync,
655 Term: TerminationCriterion<G, F>,
656{
657 #[allow(clippy::type_complexity)]
660 fn generate_offspring_bounded<R: Rng>(
661 &self,
662 selection_pool: &[(G, f64)],
663 rng: &mut R,
664 ) -> Vec<(Individual<G, F>, (usize, f64), (usize, f64))> {
665 let target = self.config.offspring_count;
666 let mut offspring = Vec::with_capacity(target);
667
668 while offspring.len() < target {
669 let p1_idx = self.selection.select(selection_pool, rng);
670 let p2_idx = self.selection.select(selection_pool, rng);
671 let p1 = (p1_idx, selection_pool[p1_idx].1);
672 let p2 = (p2_idx, selection_pool[p2_idx].1);
673 let parent1 = &selection_pool[p1_idx].0;
674 let parent2 = &selection_pool[p2_idx].0;
675
676 let (mut child1, mut child2) = if rng.gen::<f64>() < self.config.crossover_probability {
677 match self
678 .crossover
679 .crossover_bounded(parent1, parent2, &self.bounds, rng)
680 .genome()
681 {
682 Some((c1, c2)) => (c1, c2),
683 None => (parent1.clone(), parent2.clone()),
684 }
685 } else {
686 (parent1.clone(), parent2.clone())
687 };
688
689 self.mutation.mutate_bounded(&mut child1, &self.bounds, rng);
690 let mut ind1 = Individual::new(child1);
691 ind1.set_fitness(self.fitness.evaluate(ind1.genome()));
692 offspring.push((ind1, p1, p2));
693
694 if offspring.len() < target {
695 self.mutation.mutate_bounded(&mut child2, &self.bounds, rng);
696 let mut ind2 = Individual::new(child2);
697 ind2.set_fitness(self.fitness.evaluate(ind2.genome()));
698 offspring.push((ind2, p1, p2));
699 }
700 }
701
702 offspring
703 }
704
705 pub fn run_bounded<R: Rng>(&self, rng: &mut R) -> Result<EvolutionResult<G, F>, EvolutionError>
709 where
710 G: PartialEq,
711 {
712 let start_time = Instant::now();
713
714 let mut population: Population<G, F> =
716 Population::random(self.config.population_size, &self.bounds, rng);
717
718 if self.config.parallel_evaluation {
720 population.evaluate_parallel(&self.fitness);
721 } else {
722 population.evaluate(&self.fitness);
723 }
724
725 let mut stats = EvolutionStats::new();
726 let mut evaluations = population.len();
727 let mut fitness_history: Vec<f64> = Vec::new();
728 let mut step_count = 0usize;
729 let mut generation = 0usize;
730
731 let mut best_individual = population
733 .best()
734 .ok_or(EvolutionError::EmptyPopulation)?
735 .clone();
736
737 let gen_stats = GenerationStats::from_population(&population, 0, evaluations);
739 fitness_history.push(gen_stats.best_fitness);
740 stats.record(gen_stats);
741
742 loop {
744 let state = EvolutionState {
746 generation,
747 evaluations,
748 best_fitness: best_individual.fitness_value().to_f64(),
749 population: &population,
750 fitness_history: &fitness_history,
751 };
752
753 if self.termination.should_terminate(&state) {
754 stats.set_termination_reason(self.termination.reason());
755 break;
756 }
757
758 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
760
761 let offspring = self.generate_offspring_bounded(&selection_pool, rng);
764 evaluations += offspring.len();
765
766 for (child, parent1, parent2) in offspring {
768 self.place_offspring(&mut population, child, parent1, parent2, rng);
769 }
770
771 if let Some(best) = population.best() {
773 if best.is_better_than(&best_individual) {
774 best_individual = best.clone();
775 }
776 }
777
778 step_count += 1;
779
780 if step_count.is_multiple_of(self.config.steps_per_generation) {
782 generation += 1;
783 population.set_generation(generation);
784
785 let gen_stats =
786 GenerationStats::from_population(&population, generation, evaluations);
787 fitness_history.push(gen_stats.best_fitness);
788 stats.record(gen_stats);
789 }
790 }
791
792 stats.set_runtime(start_time.elapsed());
793
794 Ok(EvolutionResult::new(
795 best_individual.genome,
796 best_individual.fitness.unwrap(),
797 generation,
798 evaluations,
799 )
800 .with_stats(stats))
801 }
802}
803
804#[cfg(test)]
805mod tests {
806 use super::*;
807 use crate::fitness::benchmarks::{OneMax, Sphere};
808 use crate::genome::real_vector::RealVector;
809 use crate::genome::traits::RealValuedGenome;
810 use crate::operators::crossover::{SbxCrossover, UniformCrossover};
811 use crate::operators::mutation::{BitFlipMutation, PolynomialMutation};
812 use crate::operators::selection::TournamentSelection;
813 use crate::termination::MaxEvaluations;
814 use rand::SeedableRng;
815
816 #[test]
817 fn test_steady_state_builder() {
818 let bounds = MultiBounds::symmetric(5.0, 10);
819 let ga = SteadyStateBuilder::new()
820 .population_size(50)
821 .offspring_count(2)
822 .bounds(bounds)
823 .selection(TournamentSelection::new(3))
824 .crossover(SbxCrossover::new(20.0))
825 .mutation(PolynomialMutation::new(20.0))
826 .fitness(Sphere::new(10))
827 .max_generations(10)
828 .build();
829
830 assert!(ga.is_ok());
831 }
832
833 #[test]
834 fn test_steady_state_sphere() {
835 let mut rng = rand::thread_rng();
836 let bounds = MultiBounds::symmetric(5.12, 10);
837
838 let ga = SteadyStateBuilder::new()
839 .population_size(50)
840 .offspring_count(2)
841 .steps_per_generation(25)
842 .bounds(bounds)
843 .selection(TournamentSelection::new(3))
844 .crossover(SbxCrossover::new(20.0))
845 .mutation(PolynomialMutation::new(20.0))
846 .fitness(Sphere::new(10))
847 .termination(MaxEvaluations::new(2000))
848 .build()
849 .unwrap();
850
851 let result = ga.run(&mut rng).unwrap();
852
853 assert!(
855 result.best_fitness > -200.0,
856 "Expected fitness > -200, got {}",
857 result.best_fitness
858 );
859 assert!(result.evaluations <= 2000);
860 }
861
862 #[test]
863 fn test_steady_state_onemax() {
864 let mut rng = rand::thread_rng();
865 let bounds = MultiBounds::uniform(crate::genome::bounds::Bounds::unit(), 20);
866
867 let ga = SteadyStateBuilder::new()
868 .population_size(50)
869 .offspring_count(2)
870 .steps_per_generation(25)
871 .bounds(bounds)
872 .selection(TournamentSelection::new(3))
873 .crossover(UniformCrossover::new())
874 .mutation(BitFlipMutation::new())
875 .fitness(OneMax::new(20))
876 .termination(MaxEvaluations::new(2000))
877 .build()
878 .unwrap();
879
880 let result = ga.run(&mut rng).unwrap();
881
882 assert!(result.best_fitness >= 15); }
885
886 #[test]
887 fn test_replacement_strategies() {
888 let mut rng = rand::thread_rng();
889 let bounds = MultiBounds::symmetric(5.12, 5);
890
891 let strategies = vec![
892 ReplacementStrategy::ReplaceWorst,
893 ReplacementStrategy::ReplaceRandom,
894 ReplacementStrategy::TournamentWorst(3),
895 ];
896
897 for strategy in strategies {
898 let ga = SteadyStateBuilder::new()
899 .population_size(30)
900 .offspring_count(2)
901 .replacement(strategy)
902 .bounds(bounds.clone())
903 .selection(TournamentSelection::new(3))
904 .crossover(SbxCrossover::new(20.0))
905 .mutation(PolynomialMutation::new(20.0))
906 .fitness(Sphere::new(5))
907 .termination(MaxEvaluations::new(500))
908 .build()
909 .unwrap();
910
911 let result = ga.run(&mut rng);
912 assert!(result.is_ok());
913 }
914 }
915
916 #[test]
917 fn test_steady_state_bounded() {
918 let mut rng = rand::thread_rng();
919 let bounds = MultiBounds::symmetric(5.12, 10);
920
921 let ga = SteadyStateBuilder::new()
922 .population_size(50)
923 .bounds(bounds.clone())
924 .selection(TournamentSelection::new(3))
925 .crossover(SbxCrossover::new(20.0))
926 .mutation(PolynomialMutation::new(20.0))
927 .fitness(Sphere::new(10))
928 .termination(MaxEvaluations::new(1000))
929 .build()
930 .unwrap();
931
932 let result = ga.run_bounded(&mut rng).unwrap();
933
934 for gene in result.best_genome.genes() {
936 assert!(*gene >= -5.12 && *gene <= 5.12);
937 }
938 }
939
940 #[test]
945 fn test_offspring_count_one_evaluates_one_per_step() {
946 let mut rng = rand::rngs::StdRng::seed_from_u64(11);
947 let bounds = MultiBounds::symmetric(5.12, 5);
948 let ga = SteadyStateBuilder::new()
949 .population_size(10)
950 .offspring_count(1)
951 .steps_per_generation(1)
952 .bounds(bounds)
953 .selection(TournamentSelection::new(2))
954 .crossover(SbxCrossover::new(20.0))
955 .mutation(PolynomialMutation::new(20.0))
956 .fitness(Sphere::new(5))
957 .termination(MaxEvaluations::new(15))
958 .build()
959 .unwrap();
960
961 let result = ga.run(&mut rng).unwrap();
962 assert_eq!(result.evaluations, 15);
963 }
964
965 #[test]
970 fn test_replace_random_accepts_worse_offspring() {
971 let mut rng = rand::rngs::StdRng::seed_from_u64(20);
972 let bounds = MultiBounds::symmetric(5.12, 5);
973 let ga = SteadyStateBuilder::new()
974 .population_size(20)
975 .offspring_count(1)
976 .steps_per_generation(1)
977 .replacement(ReplacementStrategy::ReplaceRandom)
978 .bounds(bounds)
979 .selection(TournamentSelection::new(2))
980 .crossover(SbxCrossover::new(2.0))
981 .mutation(PolynomialMutation::new(2.0))
982 .fitness(Sphere::new(5))
983 .termination(MaxEvaluations::new(400))
984 .build()
985 .unwrap();
986
987 let result = ga.run(&mut rng).unwrap();
988 let worst: Vec<f64> = result
989 .stats
990 .generations
991 .iter()
992 .map(|g| g.worst_fitness)
993 .collect();
994 let degraded = worst.windows(2).any(|w| w[1] < w[0] - 1e-12);
995 assert!(
996 degraded,
997 "ReplaceRandom must let the population worst degrade at least once"
998 );
999 }
1000
1001 #[test]
1004 fn test_prevent_duplicates_rejects_existing_genome() {
1005 let mut rng = rand::rngs::StdRng::seed_from_u64(3);
1006 let bounds = MultiBounds::symmetric(5.0, 3);
1007 let ga = SteadyStateBuilder::new()
1008 .population_size(5)
1009 .offspring_count(1)
1010 .replacement(ReplacementStrategy::ReplaceRandom)
1011 .prevent_duplicates(true)
1012 .bounds(bounds)
1013 .selection(TournamentSelection::new(2))
1014 .crossover(SbxCrossover::new(20.0))
1015 .mutation(PolynomialMutation::new(20.0))
1016 .fitness(Sphere::new(3))
1017 .max_generations(1)
1018 .build()
1019 .unwrap();
1020
1021 let mut pop: Population<RealVector, f64> = Population::new();
1023 for i in 0..5 {
1024 let mut ind = Individual::new(RealVector::new(vec![i as f64, 0.0, 0.0]));
1025 ind.set_fitness(-(i as f64));
1026 pop.push(ind);
1027 }
1028
1029 let mut dup = Individual::new(RealVector::new(vec![2.0, 0.0, 0.0]));
1032 dup.set_fitness(100.0);
1033 let before: Vec<RealVector> = pop.iter().map(|i| i.genome().clone()).collect();
1034 ga.place_offspring(&mut pop, dup, (0, 0.0), (0, 0.0), &mut rng);
1035 let after: Vec<RealVector> = pop.iter().map(|i| i.genome().clone()).collect();
1036 assert_eq!(before, after, "duplicate offspring must be rejected");
1037
1038 let mut novel = Individual::new(RealVector::new(vec![42.0, 0.0, 0.0]));
1040 novel.set_fitness(-999.0);
1041 let novel_genome = novel.genome().clone();
1042 ga.place_offspring(&mut pop, novel, (0, 0.0), (0, 0.0), &mut rng);
1043 assert!(
1044 pop.iter().any(|i| *i.genome() == novel_genome),
1045 "a novel genome should be inserted"
1046 );
1047 }
1048}