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)]
26pub enum ReplacementStrategy {
27 #[default]
29 ReplaceWorst,
30 ReplaceRandom,
32 ReplaceParent,
34 TournamentWorst(usize),
36}
37
38#[derive(Clone, Debug)]
40pub struct SteadyStateConfig {
41 pub population_size: usize,
43 pub offspring_count: usize,
45 pub crossover_probability: f64,
47 pub replacement: ReplacementStrategy,
49 pub parallel_evaluation: bool,
51 pub steps_per_generation: usize,
53}
54
55impl Default for SteadyStateConfig {
56 fn default() -> Self {
57 Self {
58 population_size: 100,
59 offspring_count: 2,
60 crossover_probability: 0.9,
61 replacement: ReplacementStrategy::ReplaceWorst,
62 parallel_evaluation: false,
63 steps_per_generation: 50, }
65 }
66}
67
68pub struct SteadyStateBuilder<G, F, S, C, M, Fit, Term>
70where
71 G: EvolutionaryGenome,
72 F: FitnessValue,
73{
74 config: SteadyStateConfig,
75 bounds: Option<MultiBounds>,
76 selection: Option<S>,
77 crossover: Option<C>,
78 mutation: Option<M>,
79 fitness: Option<Fit>,
80 termination: Option<Term>,
81 _phantom: std::marker::PhantomData<(G, F)>,
82}
83
84impl<G, F> SteadyStateBuilder<G, F, (), (), (), (), ()>
85where
86 G: EvolutionaryGenome,
87 F: FitnessValue,
88{
89 pub fn new() -> Self {
91 Self {
92 config: SteadyStateConfig::default(),
93 bounds: None,
94 selection: None,
95 crossover: None,
96 mutation: None,
97 fitness: None,
98 termination: None,
99 _phantom: std::marker::PhantomData,
100 }
101 }
102}
103
104impl<G, F> Default for SteadyStateBuilder<G, F, (), (), (), (), ()>
105where
106 G: EvolutionaryGenome,
107 F: FitnessValue,
108{
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114impl<G, F, S, C, M, Fit, Term> SteadyStateBuilder<G, F, S, C, M, Fit, Term>
115where
116 G: EvolutionaryGenome,
117 F: FitnessValue,
118{
119 pub fn population_size(mut self, size: usize) -> Self {
121 self.config.population_size = size;
122 self
123 }
124
125 pub fn offspring_count(mut self, count: usize) -> Self {
127 self.config.offspring_count = count;
128 self
129 }
130
131 pub fn crossover_probability(mut self, probability: f64) -> Self {
133 self.config.crossover_probability = probability;
134 self
135 }
136
137 pub fn replacement(mut self, strategy: ReplacementStrategy) -> Self {
139 self.config.replacement = strategy;
140 self
141 }
142
143 pub fn parallel_evaluation(mut self, enabled: bool) -> Self {
145 self.config.parallel_evaluation = enabled;
146 self
147 }
148
149 pub fn steps_per_generation(mut self, steps: usize) -> Self {
151 self.config.steps_per_generation = steps;
152 self
153 }
154
155 pub fn bounds(mut self, bounds: MultiBounds) -> Self {
157 self.bounds = Some(bounds);
158 self
159 }
160
161 pub fn selection<NewS>(self, selection: NewS) -> SteadyStateBuilder<G, F, NewS, C, M, Fit, Term>
163 where
164 NewS: SelectionOperator<G>,
165 {
166 SteadyStateBuilder {
167 config: self.config,
168 bounds: self.bounds,
169 selection: Some(selection),
170 crossover: self.crossover,
171 mutation: self.mutation,
172 fitness: self.fitness,
173 termination: self.termination,
174 _phantom: std::marker::PhantomData,
175 }
176 }
177
178 pub fn crossover<NewC>(self, crossover: NewC) -> SteadyStateBuilder<G, F, S, NewC, M, Fit, Term>
180 where
181 NewC: CrossoverOperator<G>,
182 {
183 SteadyStateBuilder {
184 config: self.config,
185 bounds: self.bounds,
186 selection: self.selection,
187 crossover: Some(crossover),
188 mutation: self.mutation,
189 fitness: self.fitness,
190 termination: self.termination,
191 _phantom: std::marker::PhantomData,
192 }
193 }
194
195 pub fn mutation<NewM>(self, mutation: NewM) -> SteadyStateBuilder<G, F, S, C, NewM, Fit, Term>
197 where
198 NewM: MutationOperator<G>,
199 {
200 SteadyStateBuilder {
201 config: self.config,
202 bounds: self.bounds,
203 selection: self.selection,
204 crossover: self.crossover,
205 mutation: Some(mutation),
206 fitness: self.fitness,
207 termination: self.termination,
208 _phantom: std::marker::PhantomData,
209 }
210 }
211
212 pub fn fitness<NewFit>(self, fitness: NewFit) -> SteadyStateBuilder<G, F, S, C, M, NewFit, Term>
214 where
215 NewFit: Fitness<Genome = G, Value = F>,
216 {
217 SteadyStateBuilder {
218 config: self.config,
219 bounds: self.bounds,
220 selection: self.selection,
221 crossover: self.crossover,
222 mutation: self.mutation,
223 fitness: Some(fitness),
224 termination: self.termination,
225 _phantom: std::marker::PhantomData,
226 }
227 }
228
229 pub fn termination<NewTerm>(
231 self,
232 termination: NewTerm,
233 ) -> SteadyStateBuilder<G, F, S, C, M, Fit, NewTerm>
234 where
235 NewTerm: TerminationCriterion<G, F>,
236 {
237 SteadyStateBuilder {
238 config: self.config,
239 bounds: self.bounds,
240 selection: self.selection,
241 crossover: self.crossover,
242 mutation: self.mutation,
243 fitness: self.fitness,
244 termination: Some(termination),
245 _phantom: std::marker::PhantomData,
246 }
247 }
248
249 pub fn max_generations(
251 self,
252 max: usize,
253 ) -> SteadyStateBuilder<G, F, S, C, M, Fit, MaxGenerations> {
254 SteadyStateBuilder {
255 config: self.config,
256 bounds: self.bounds,
257 selection: self.selection,
258 crossover: self.crossover,
259 mutation: self.mutation,
260 fitness: self.fitness,
261 termination: Some(MaxGenerations::new(max)),
262 _phantom: std::marker::PhantomData,
263 }
264 }
265}
266
267impl<G, F, S, C, M, Fit, Term> SteadyStateBuilder<G, F, S, C, M, Fit, Term>
268where
269 G: EvolutionaryGenome + Send + Sync,
270 F: FitnessValue + Send,
271 S: SelectionOperator<G>,
272 C: CrossoverOperator<G>,
273 M: MutationOperator<G>,
274 Fit: Fitness<Genome = G, Value = F> + Sync,
275 Term: TerminationCriterion<G, F>,
276{
277 #[allow(clippy::type_complexity)]
279 pub fn build(self) -> Result<SteadyStateGA<G, F, S, C, M, Fit, Term>, EvolutionError> {
280 let bounds = self
281 .bounds
282 .ok_or_else(|| EvolutionError::Configuration("Bounds must be specified".to_string()))?;
283
284 let selection = self.selection.ok_or_else(|| {
285 EvolutionError::Configuration("Selection operator must be specified".to_string())
286 })?;
287
288 let crossover = self.crossover.ok_or_else(|| {
289 EvolutionError::Configuration("Crossover operator must be specified".to_string())
290 })?;
291
292 let mutation = self.mutation.ok_or_else(|| {
293 EvolutionError::Configuration("Mutation operator must be specified".to_string())
294 })?;
295
296 let fitness = self.fitness.ok_or_else(|| {
297 EvolutionError::Configuration("Fitness function must be specified".to_string())
298 })?;
299
300 let termination = self.termination.ok_or_else(|| {
301 EvolutionError::Configuration("Termination criterion must be specified".to_string())
302 })?;
303
304 Ok(SteadyStateGA {
305 config: self.config,
306 bounds,
307 selection,
308 crossover,
309 mutation,
310 fitness,
311 termination,
312 _phantom: std::marker::PhantomData,
313 })
314 }
315}
316
317pub struct SteadyStateGA<G, F, S, C, M, Fit, Term>
322where
323 G: EvolutionaryGenome,
324 F: FitnessValue,
325{
326 config: SteadyStateConfig,
327 bounds: MultiBounds,
328 selection: S,
329 crossover: C,
330 mutation: M,
331 fitness: Fit,
332 termination: Term,
333 _phantom: std::marker::PhantomData<(G, F)>,
334}
335
336impl<G, F, S, C, M, Fit, Term> SteadyStateGA<G, F, S, C, M, Fit, Term>
337where
338 G: EvolutionaryGenome + Send + Sync,
339 F: FitnessValue + Send,
340 S: SelectionOperator<G>,
341 C: CrossoverOperator<G>,
342 M: MutationOperator<G>,
343 Fit: Fitness<Genome = G, Value = F> + Sync,
344 Term: TerminationCriterion<G, F>,
345{
346 pub fn builder() -> SteadyStateBuilder<G, F, (), (), (), (), ()> {
348 SteadyStateBuilder::new()
349 }
350
351 fn find_replacement_index<R: Rng>(&self, population: &Population<G, F>, rng: &mut R) -> usize {
353 match &self.config.replacement {
354 ReplacementStrategy::ReplaceWorst => {
355 population
357 .iter()
358 .enumerate()
359 .min_by(|(_, a), (_, b)| {
360 a.fitness_value()
361 .partial_cmp(b.fitness_value())
362 .unwrap_or(std::cmp::Ordering::Equal)
363 })
364 .map(|(i, _)| i)
365 .unwrap_or(0)
366 }
367 ReplacementStrategy::ReplaceRandom => rng.gen_range(0..population.len()),
368 ReplacementStrategy::ReplaceParent => {
369 0
371 }
372 ReplacementStrategy::TournamentWorst(tournament_size) => {
373 let mut worst_idx = rng.gen_range(0..population.len());
375 let mut worst_fitness = population[worst_idx].fitness_value().to_f64();
376
377 for _ in 1..*tournament_size {
378 let idx = rng.gen_range(0..population.len());
379 let fitness = population[idx].fitness_value().to_f64();
380 if fitness < worst_fitness {
381 worst_idx = idx;
382 worst_fitness = fitness;
383 }
384 }
385 worst_idx
386 }
387 }
388 }
389
390 pub fn run<R: Rng>(&self, rng: &mut R) -> Result<EvolutionResult<G, F>, EvolutionError> {
392 let start_time = Instant::now();
393
394 let mut population: Population<G, F> =
396 Population::random(self.config.population_size, &self.bounds, rng);
397
398 if self.config.parallel_evaluation {
400 population.evaluate_parallel(&self.fitness);
401 } else {
402 population.evaluate(&self.fitness);
403 }
404
405 let mut stats = EvolutionStats::new();
406 let mut evaluations = population.len();
407 let mut fitness_history: Vec<f64> = Vec::new();
408 let mut step_count = 0usize;
409 let mut generation = 0usize;
410
411 let mut best_individual = population
413 .best()
414 .ok_or(EvolutionError::EmptyPopulation)?
415 .clone();
416
417 let gen_stats = GenerationStats::from_population(&population, 0, evaluations);
419 fitness_history.push(gen_stats.best_fitness);
420 stats.record(gen_stats);
421
422 loop {
424 let state = EvolutionState {
426 generation,
427 evaluations,
428 best_fitness: best_individual.fitness_value().to_f64(),
429 population: &population,
430 fitness_history: &fitness_history,
431 };
432
433 if self.termination.should_terminate(&state) {
434 stats.set_termination_reason(self.termination.reason());
435 break;
436 }
437
438 let step_start = Instant::now();
439
440 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
442
443 let parent1_idx = self.selection.select(&selection_pool, rng);
445 let parent2_idx = self.selection.select(&selection_pool, rng);
446 let parent1 = &selection_pool[parent1_idx].0;
447 let parent2 = &selection_pool[parent2_idx].0;
448
449 let (mut child1, mut child2) = if rng.gen::<f64>() < self.config.crossover_probability {
451 match self.crossover.crossover(parent1, parent2, rng).genome() {
452 Some((c1, c2)) => (c1, c2),
453 None => (parent1.clone(), parent2.clone()),
454 }
455 } else {
456 (parent1.clone(), parent2.clone())
457 };
458
459 self.mutation.mutate(&mut child1, rng);
461 self.mutation.mutate(&mut child2, rng);
462
463 let mut offspring: Vec<Individual<G, F>> =
465 vec![Individual::new(child1), Individual::new(child2)];
466
467 for ind in &mut offspring {
468 let fitness = self.fitness.evaluate(&ind.genome);
469 ind.set_fitness(fitness);
470 }
471 evaluations += offspring.len();
472
473 for child in offspring.into_iter().take(self.config.offspring_count) {
475 let replace_idx = match &self.config.replacement {
476 ReplacementStrategy::ReplaceParent => {
477 let p1_fit = selection_pool[parent1_idx].1;
479 let p2_fit = selection_pool[parent2_idx].1;
480 let child_fit = child.fitness_value().to_f64();
481
482 if child_fit > p1_fit.min(p2_fit) {
483 if p1_fit < p2_fit {
484 parent1_idx
485 } else {
486 parent2_idx
487 }
488 } else {
489 continue; }
491 }
492 _ => self.find_replacement_index(&population, rng),
493 };
494
495 if child
498 .fitness_value()
499 .is_better_than(population[replace_idx].fitness_value())
500 {
501 population[replace_idx] = child;
502 }
503 }
504
505 if let Some(best) = population.best() {
507 if best.is_better_than(&best_individual) {
508 best_individual = best.clone();
509 }
510 }
511
512 step_count += 1;
513
514 if step_count.is_multiple_of(self.config.steps_per_generation) {
516 generation += 1;
517 population.set_generation(generation);
518
519 let timing = TimingStats::new()
520 .with_total(step_start.elapsed() * self.config.steps_per_generation as u32);
521
522 let gen_stats =
523 GenerationStats::from_population(&population, generation, evaluations)
524 .with_timing(timing);
525 fitness_history.push(gen_stats.best_fitness);
526 stats.record(gen_stats);
527 }
528 }
529
530 stats.set_runtime(start_time.elapsed());
531
532 Ok(EvolutionResult::new(
533 best_individual.genome,
534 best_individual.fitness.unwrap(),
535 generation,
536 evaluations,
537 )
538 .with_stats(stats))
539 }
540}
541
542impl<G, F, S, C, M, Fit, Term> SteadyStateGA<G, F, S, C, M, Fit, Term>
544where
545 G: EvolutionaryGenome + Send + Sync,
546 F: FitnessValue + Send,
547 S: SelectionOperator<G>,
548 C: BoundedCrossoverOperator<G>,
549 M: BoundedMutationOperator<G>,
550 Fit: Fitness<Genome = G, Value = F> + Sync,
551 Term: TerminationCriterion<G, F>,
552{
553 pub fn run_bounded<R: Rng>(
555 &self,
556 rng: &mut R,
557 ) -> Result<EvolutionResult<G, F>, EvolutionError> {
558 let start_time = Instant::now();
559
560 let mut population: Population<G, F> =
562 Population::random(self.config.population_size, &self.bounds, rng);
563
564 if self.config.parallel_evaluation {
566 population.evaluate_parallel(&self.fitness);
567 } else {
568 population.evaluate(&self.fitness);
569 }
570
571 let mut stats = EvolutionStats::new();
572 let mut evaluations = population.len();
573 let mut fitness_history: Vec<f64> = Vec::new();
574 let mut step_count = 0usize;
575 let mut generation = 0usize;
576
577 let mut best_individual = population
579 .best()
580 .ok_or(EvolutionError::EmptyPopulation)?
581 .clone();
582
583 let gen_stats = GenerationStats::from_population(&population, 0, evaluations);
585 fitness_history.push(gen_stats.best_fitness);
586 stats.record(gen_stats);
587
588 loop {
590 let state = EvolutionState {
592 generation,
593 evaluations,
594 best_fitness: best_individual.fitness_value().to_f64(),
595 population: &population,
596 fitness_history: &fitness_history,
597 };
598
599 if self.termination.should_terminate(&state) {
600 stats.set_termination_reason(self.termination.reason());
601 break;
602 }
603
604 let selection_pool: Vec<(G, f64)> = population.as_fitness_pairs();
606
607 let parent1_idx = self.selection.select(&selection_pool, rng);
609 let parent2_idx = self.selection.select(&selection_pool, rng);
610 let parent1 = &selection_pool[parent1_idx].0;
611 let parent2 = &selection_pool[parent2_idx].0;
612
613 let (mut child1, mut child2) = if rng.gen::<f64>() < self.config.crossover_probability {
615 match self
616 .crossover
617 .crossover_bounded(parent1, parent2, &self.bounds, rng)
618 .genome()
619 {
620 Some((c1, c2)) => (c1, c2),
621 None => (parent1.clone(), parent2.clone()),
622 }
623 } else {
624 (parent1.clone(), parent2.clone())
625 };
626
627 self.mutation.mutate_bounded(&mut child1, &self.bounds, rng);
629 self.mutation.mutate_bounded(&mut child2, &self.bounds, rng);
630
631 let mut offspring: Vec<Individual<G, F>> =
633 vec![Individual::new(child1), Individual::new(child2)];
634
635 for ind in &mut offspring {
636 let fitness = self.fitness.evaluate(&ind.genome);
637 ind.set_fitness(fitness);
638 }
639 evaluations += offspring.len();
640
641 for child in offspring.into_iter().take(self.config.offspring_count) {
643 let replace_idx = match &self.config.replacement {
644 ReplacementStrategy::ReplaceParent => {
645 let p1_fit = selection_pool[parent1_idx].1;
646 let p2_fit = selection_pool[parent2_idx].1;
647 let child_fit = child.fitness_value().to_f64();
648
649 if child_fit > p1_fit.min(p2_fit) {
650 if p1_fit < p2_fit {
651 parent1_idx
652 } else {
653 parent2_idx
654 }
655 } else {
656 continue;
657 }
658 }
659 _ => self.find_replacement_index(&population, rng),
660 };
661
662 if child
663 .fitness_value()
664 .is_better_than(population[replace_idx].fitness_value())
665 {
666 population[replace_idx] = child;
667 }
668 }
669
670 if let Some(best) = population.best() {
672 if best.is_better_than(&best_individual) {
673 best_individual = best.clone();
674 }
675 }
676
677 step_count += 1;
678
679 if step_count.is_multiple_of(self.config.steps_per_generation) {
681 generation += 1;
682 population.set_generation(generation);
683
684 let gen_stats =
685 GenerationStats::from_population(&population, generation, evaluations);
686 fitness_history.push(gen_stats.best_fitness);
687 stats.record(gen_stats);
688 }
689 }
690
691 stats.set_runtime(start_time.elapsed());
692
693 Ok(EvolutionResult::new(
694 best_individual.genome,
695 best_individual.fitness.unwrap(),
696 generation,
697 evaluations,
698 )
699 .with_stats(stats))
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706 use crate::fitness::benchmarks::{OneMax, Sphere};
707 use crate::genome::traits::RealValuedGenome;
708 use crate::operators::crossover::{SbxCrossover, UniformCrossover};
709 use crate::operators::mutation::{BitFlipMutation, PolynomialMutation};
710 use crate::operators::selection::TournamentSelection;
711 use crate::termination::MaxEvaluations;
712
713 #[test]
714 fn test_steady_state_builder() {
715 let bounds = MultiBounds::symmetric(5.0, 10);
716 let ga = SteadyStateBuilder::new()
717 .population_size(50)
718 .offspring_count(2)
719 .bounds(bounds)
720 .selection(TournamentSelection::new(3))
721 .crossover(SbxCrossover::new(20.0))
722 .mutation(PolynomialMutation::new(20.0))
723 .fitness(Sphere::new(10))
724 .max_generations(10)
725 .build();
726
727 assert!(ga.is_ok());
728 }
729
730 #[test]
731 fn test_steady_state_sphere() {
732 let mut rng = rand::thread_rng();
733 let bounds = MultiBounds::symmetric(5.12, 10);
734
735 let ga = SteadyStateBuilder::new()
736 .population_size(50)
737 .offspring_count(2)
738 .steps_per_generation(25)
739 .bounds(bounds)
740 .selection(TournamentSelection::new(3))
741 .crossover(SbxCrossover::new(20.0))
742 .mutation(PolynomialMutation::new(20.0))
743 .fitness(Sphere::new(10))
744 .termination(MaxEvaluations::new(2000))
745 .build()
746 .unwrap();
747
748 let result = ga.run(&mut rng).unwrap();
749
750 assert!(
752 result.best_fitness > -200.0,
753 "Expected fitness > -200, got {}",
754 result.best_fitness
755 );
756 assert!(result.evaluations <= 2000);
757 }
758
759 #[test]
760 fn test_steady_state_onemax() {
761 let mut rng = rand::thread_rng();
762 let bounds = MultiBounds::uniform(crate::genome::bounds::Bounds::unit(), 20);
763
764 let ga = SteadyStateBuilder::new()
765 .population_size(50)
766 .offspring_count(2)
767 .steps_per_generation(25)
768 .bounds(bounds)
769 .selection(TournamentSelection::new(3))
770 .crossover(UniformCrossover::new())
771 .mutation(BitFlipMutation::new())
772 .fitness(OneMax::new(20))
773 .termination(MaxEvaluations::new(2000))
774 .build()
775 .unwrap();
776
777 let result = ga.run(&mut rng).unwrap();
778
779 assert!(result.best_fitness >= 15); }
782
783 #[test]
784 fn test_replacement_strategies() {
785 let mut rng = rand::thread_rng();
786 let bounds = MultiBounds::symmetric(5.12, 5);
787
788 let strategies = vec![
789 ReplacementStrategy::ReplaceWorst,
790 ReplacementStrategy::ReplaceRandom,
791 ReplacementStrategy::TournamentWorst(3),
792 ];
793
794 for strategy in strategies {
795 let ga = SteadyStateBuilder::new()
796 .population_size(30)
797 .offspring_count(2)
798 .replacement(strategy)
799 .bounds(bounds.clone())
800 .selection(TournamentSelection::new(3))
801 .crossover(SbxCrossover::new(20.0))
802 .mutation(PolynomialMutation::new(20.0))
803 .fitness(Sphere::new(5))
804 .termination(MaxEvaluations::new(500))
805 .build()
806 .unwrap();
807
808 let result = ga.run(&mut rng);
809 assert!(result.is_ok());
810 }
811 }
812
813 #[test]
814 fn test_steady_state_bounded() {
815 let mut rng = rand::thread_rng();
816 let bounds = MultiBounds::symmetric(5.12, 10);
817
818 let ga = SteadyStateBuilder::new()
819 .population_size(50)
820 .bounds(bounds.clone())
821 .selection(TournamentSelection::new(3))
822 .crossover(SbxCrossover::new(20.0))
823 .mutation(PolynomialMutation::new(20.0))
824 .fitness(Sphere::new(10))
825 .termination(MaxEvaluations::new(1000))
826 .build()
827 .unwrap();
828
829 let result = ga.run_bounded(&mut rng).unwrap();
830
831 for gene in result.best_genome.genes() {
833 assert!(*gene >= -5.12 && *gene <= 5.12);
834 }
835 }
836}