1use std::marker::PhantomData;
10
11use rand::Rng;
12
13use crate::error::EvoResult;
14use crate::fitness::traits::ParetoFitness;
15use crate::genome::bounds::MultiBounds;
16use crate::genome::traits::EvolutionaryGenome;
17use crate::operators::traits::{
18 BoundedCrossoverOperator, BoundedMutationOperator, CrossoverOperator, MutationOperator,
19};
20use crate::population::individual::Individual;
21
22#[cfg(feature = "parallel")]
24pub trait MultiObjectiveFitness<G>: Send + Sync {
25 fn num_objectives(&self) -> usize;
27
28 fn evaluate(&self, genome: &G) -> Vec<f64>;
30}
31
32#[cfg(not(feature = "parallel"))]
34pub trait MultiObjectiveFitness<G> {
35 fn num_objectives(&self) -> usize;
37
38 fn evaluate(&self, genome: &G) -> Vec<f64>;
40}
41
42#[cfg(feature = "parallel")]
44impl<G, F> MultiObjectiveFitness<G> for F
45where
46 F: Fn(&G) -> Vec<f64> + Send + Sync,
47{
48 fn num_objectives(&self) -> usize {
49 2 }
53
54 fn evaluate(&self, genome: &G) -> Vec<f64> {
55 self(genome)
56 }
57}
58
59#[cfg(not(feature = "parallel"))]
61impl<G, F> MultiObjectiveFitness<G> for F
62where
63 F: Fn(&G) -> Vec<f64>,
64{
65 fn num_objectives(&self) -> usize {
66 2 }
68
69 fn evaluate(&self, genome: &G) -> Vec<f64> {
70 self(genome)
71 }
72}
73
74#[derive(Clone, Debug)]
76pub struct Nsga2Individual<G: EvolutionaryGenome> {
77 pub genome: G,
79 pub objectives: Vec<f64>,
81 pub rank: usize,
83 pub crowding_distance: f64,
85}
86
87impl<G: EvolutionaryGenome> Nsga2Individual<G> {
88 pub fn new(genome: G, objectives: Vec<f64>) -> Self {
90 Self {
91 genome,
92 objectives,
93 rank: usize::MAX,
94 crowding_distance: 0.0,
95 }
96 }
97
98 pub fn dominates(&self, other: &Self) -> bool {
101 let at_least_as_good = self
102 .objectives
103 .iter()
104 .zip(other.objectives.iter())
105 .all(|(a, b)| a <= b);
106 let strictly_better = self
107 .objectives
108 .iter()
109 .zip(other.objectives.iter())
110 .any(|(a, b)| a < b);
111 at_least_as_good && strictly_better
112 }
113
114 pub fn to_pareto_fitness(&self) -> ParetoFitness {
116 let mut pf = ParetoFitness::new(self.objectives.clone());
117 pf.rank = self.rank;
118 pf.crowding_distance = self.crowding_distance;
119 pf
120 }
121
122 pub fn to_individual(self) -> Individual<G, ParetoFitness> {
124 let fitness = self.to_pareto_fitness();
125 Individual::with_fitness(self.genome, fitness)
126 }
127}
128
129pub fn fast_non_dominated_sort<G: EvolutionaryGenome>(
133 population: &mut [Nsga2Individual<G>],
134) -> Vec<Vec<usize>> {
135 let n = population.len();
136 if n == 0 {
137 return vec![];
138 }
139
140 let mut domination_count = vec![0usize; n];
142 let mut dominated_set: Vec<Vec<usize>> = vec![vec![]; n];
144
145 for i in 0..n {
147 for j in (i + 1)..n {
148 if population[i].dominates(&population[j]) {
149 dominated_set[i].push(j);
150 domination_count[j] += 1;
151 } else if population[j].dominates(&population[i]) {
152 dominated_set[j].push(i);
153 domination_count[i] += 1;
154 }
155 }
156 }
157
158 let mut fronts: Vec<Vec<usize>> = vec![];
160 let mut current_front: Vec<usize> = (0..n).filter(|&i| domination_count[i] == 0).collect();
161
162 let mut rank = 0;
163 while !current_front.is_empty() {
164 for &i in ¤t_front {
166 population[i].rank = rank;
167 }
168
169 let mut next_front = vec![];
171 for &i in ¤t_front {
172 for &j in &dominated_set[i] {
173 domination_count[j] -= 1;
174 if domination_count[j] == 0 {
175 next_front.push(j);
176 }
177 }
178 }
179
180 fronts.push(current_front);
181 current_front = next_front;
182 rank += 1;
183 }
184
185 fronts
186}
187
188pub fn calculate_crowding_distance<G: EvolutionaryGenome>(
190 population: &mut [Nsga2Individual<G>],
191 front: &[usize],
192) {
193 let n = front.len();
194 if n <= 2 {
195 for &i in front {
196 population[i].crowding_distance = f64::INFINITY;
197 }
198 return;
199 }
200
201 for &i in front {
203 population[i].crowding_distance = 0.0;
204 }
205
206 let num_objectives = population[front[0]].objectives.len();
207
208 for obj in 0..num_objectives {
209 let mut sorted_indices: Vec<usize> = front.to_vec();
211 sorted_indices.sort_by(|&a, &b| {
212 population[a].objectives[obj]
213 .partial_cmp(&population[b].objectives[obj])
214 .unwrap_or(std::cmp::Ordering::Equal)
215 });
216
217 population[sorted_indices[0]].crowding_distance = f64::INFINITY;
219 population[sorted_indices[n - 1]].crowding_distance = f64::INFINITY;
220
221 let obj_min = population[sorted_indices[0]].objectives[obj];
223 let obj_max = population[sorted_indices[n - 1]].objectives[obj];
224 let obj_range = obj_max - obj_min;
225
226 if obj_range > 0.0 {
227 for i in 1..(n - 1) {
228 let idx = sorted_indices[i];
229 let prev_val = population[sorted_indices[i - 1]].objectives[obj];
230 let next_val = population[sorted_indices[i + 1]].objectives[obj];
231 population[idx].crowding_distance += (next_val - prev_val) / obj_range;
232 }
233 }
234 }
235}
236
237pub fn crowded_comparison<G: EvolutionaryGenome>(
241 a: &Nsga2Individual<G>,
242 b: &Nsga2Individual<G>,
243) -> bool {
244 a.rank < b.rank || (a.rank == b.rank && a.crowding_distance > b.crowding_distance)
245}
246
247pub struct Nsga2<G, F, C, M> {
249 pub population_size: usize,
251 pub crossover_probability: f64,
253 pub mutation_probability: f64,
255 pub bounds: Option<MultiBounds>,
257 _phantom: PhantomData<(G, F, C, M)>,
259}
260
261impl<G, F, C, M> Nsga2<G, F, C, M>
262where
263 G: EvolutionaryGenome,
264 F: MultiObjectiveFitness<G>,
265 C: CrossoverOperator<G>,
266 M: MutationOperator<G>,
267{
268 pub fn new(population_size: usize) -> Self {
270 Self {
271 population_size,
272 crossover_probability: 0.9,
273 mutation_probability: 1.0,
274 bounds: None,
275 _phantom: PhantomData,
276 }
277 }
278
279 pub fn with_crossover_probability(mut self, prob: f64) -> Self {
281 self.crossover_probability = prob;
282 self
283 }
284
285 pub fn with_mutation_probability(mut self, prob: f64) -> Self {
287 self.mutation_probability = prob;
288 self
289 }
290
291 pub fn with_bounds(mut self, bounds: MultiBounds) -> Self {
293 self.bounds = Some(bounds);
294 self
295 }
296
297 pub fn initialize_population<R: Rng>(
299 &self,
300 fitness: &F,
301 bounds: &MultiBounds,
302 rng: &mut R,
303 ) -> Vec<Nsga2Individual<G>> {
304 (0..self.population_size)
305 .map(|_| {
306 let genome = G::generate(rng, bounds);
307 let objectives = fitness.evaluate(&genome);
308 Nsga2Individual::new(genome, objectives)
309 })
310 .collect()
311 }
312
313 pub fn tournament_select<'a, R: Rng>(
315 &self,
316 population: &'a [Nsga2Individual<G>],
317 rng: &mut R,
318 ) -> &'a Nsga2Individual<G> {
319 let i = rng.gen_range(0..population.len());
320 let j = rng.gen_range(0..population.len());
321
322 if crowded_comparison(&population[i], &population[j]) {
323 &population[i]
324 } else {
325 &population[j]
326 }
327 }
328
329 pub fn create_offspring<R: Rng>(
331 &self,
332 population: &[Nsga2Individual<G>],
333 fitness: &F,
334 crossover: &C,
335 mutation: &M,
336 rng: &mut R,
337 ) -> Vec<Nsga2Individual<G>> {
338 let mut offspring = Vec::with_capacity(self.population_size);
339
340 while offspring.len() < self.population_size {
341 let parent1 = self.tournament_select(population, rng);
343 let parent2 = self.tournament_select(population, rng);
344
345 let (mut child1, mut child2) = if rng.gen::<f64>() < self.crossover_probability {
347 match crossover.crossover(&parent1.genome, &parent2.genome, rng) {
348 crate::error::OperatorResult::Success((c1, c2)) => (c1, c2),
349 _ => (parent1.genome.clone(), parent2.genome.clone()),
350 }
351 } else {
352 (parent1.genome.clone(), parent2.genome.clone())
353 };
354
355 if rng.gen::<f64>() < self.mutation_probability {
357 mutation.mutate(&mut child1, rng);
358 }
359 if rng.gen::<f64>() < self.mutation_probability {
360 mutation.mutate(&mut child2, rng);
361 }
362
363 let obj1 = fitness.evaluate(&child1);
365 let obj2 = fitness.evaluate(&child2);
366
367 offspring.push(Nsga2Individual::new(child1, obj1));
368 if offspring.len() < self.population_size {
369 offspring.push(Nsga2Individual::new(child2, obj2));
370 }
371 }
372
373 offspring
374 }
375
376 pub fn step<R: Rng>(
378 &self,
379 population: &mut Vec<Nsga2Individual<G>>,
380 fitness: &F,
381 crossover: &C,
382 mutation: &M,
383 rng: &mut R,
384 ) {
385 let offspring = self.create_offspring(population, fitness, crossover, mutation, rng);
387
388 let mut combined: Vec<Nsga2Individual<G>> =
390 population.drain(..).chain(offspring.into_iter()).collect();
391
392 let fronts = fast_non_dominated_sort(&mut combined);
394
395 let mut new_pop = Vec::with_capacity(self.population_size);
397
398 for front in fronts {
399 if new_pop.len() + front.len() <= self.population_size {
400 for &i in &front {
402 new_pop.push(combined[i].clone());
403 }
404 } else {
405 calculate_crowding_distance(&mut combined, &front);
407
408 let mut sorted_front: Vec<usize> = front.to_vec();
409 sorted_front.sort_by(|&a, &b| {
410 combined[b]
411 .crowding_distance
412 .partial_cmp(&combined[a].crowding_distance)
413 .unwrap_or(std::cmp::Ordering::Equal)
414 });
415
416 let remaining = self.population_size - new_pop.len();
417 for &i in sorted_front.iter().take(remaining) {
418 new_pop.push(combined[i].clone());
419 }
420 break;
421 }
422 }
423
424 let all_indices: Vec<usize> = (0..new_pop.len()).collect();
426 calculate_crowding_distance(&mut new_pop, &all_indices);
427
428 *population = new_pop;
429 }
430
431 pub fn run<R: Rng>(
433 &self,
434 fitness: &F,
435 crossover: &C,
436 mutation: &M,
437 bounds: &MultiBounds,
438 max_generations: usize,
439 rng: &mut R,
440 ) -> EvoResult<Vec<Nsga2Individual<G>>> {
441 let mut population = self.initialize_population(fitness, bounds, rng);
442
443 fast_non_dominated_sort(&mut population);
445 let all_indices: Vec<usize> = (0..population.len()).collect();
446 calculate_crowding_distance(&mut population, &all_indices);
447
448 for _ in 0..max_generations {
449 self.step(&mut population, fitness, crossover, mutation, rng);
450 }
451
452 Ok(population)
453 }
454
455 pub fn get_pareto_front(population: &[Nsga2Individual<G>]) -> Vec<&Nsga2Individual<G>> {
457 population.iter().filter(|ind| ind.rank == 0).collect()
458 }
459}
460
461impl<G, F, C, M> Nsga2<G, F, C, M>
463where
464 G: EvolutionaryGenome,
465 F: MultiObjectiveFitness<G>,
466 C: BoundedCrossoverOperator<G>,
467 M: BoundedMutationOperator<G>,
468{
469 pub fn create_offspring_bounded<R: Rng>(
471 &self,
472 population: &[Nsga2Individual<G>],
473 fitness: &F,
474 crossover: &C,
475 mutation: &M,
476 bounds: &MultiBounds,
477 rng: &mut R,
478 ) -> Vec<Nsga2Individual<G>> {
479 let mut offspring = Vec::with_capacity(self.population_size);
480
481 while offspring.len() < self.population_size {
482 let parent1 = self.tournament_select(population, rng);
483 let parent2 = self.tournament_select(population, rng);
484
485 let (mut child1, mut child2) = if rng.gen::<f64>() < self.crossover_probability {
486 match crossover.crossover_bounded(&parent1.genome, &parent2.genome, bounds, rng) {
487 crate::error::OperatorResult::Success((c1, c2)) => (c1, c2),
488 _ => (parent1.genome.clone(), parent2.genome.clone()),
489 }
490 } else {
491 (parent1.genome.clone(), parent2.genome.clone())
492 };
493
494 if rng.gen::<f64>() < self.mutation_probability {
495 mutation.mutate_bounded(&mut child1, bounds, rng);
496 }
497 if rng.gen::<f64>() < self.mutation_probability {
498 mutation.mutate_bounded(&mut child2, bounds, rng);
499 }
500
501 let obj1 = fitness.evaluate(&child1);
502 let obj2 = fitness.evaluate(&child2);
503
504 offspring.push(Nsga2Individual::new(child1, obj1));
505 if offspring.len() < self.population_size {
506 offspring.push(Nsga2Individual::new(child2, obj2));
507 }
508 }
509
510 offspring
511 }
512
513 pub fn step_bounded<R: Rng>(
515 &self,
516 population: &mut Vec<Nsga2Individual<G>>,
517 fitness: &F,
518 crossover: &C,
519 mutation: &M,
520 bounds: &MultiBounds,
521 rng: &mut R,
522 ) {
523 let offspring =
524 self.create_offspring_bounded(population, fitness, crossover, mutation, bounds, rng);
525
526 let mut combined: Vec<Nsga2Individual<G>> =
527 population.drain(..).chain(offspring.into_iter()).collect();
528
529 let fronts = fast_non_dominated_sort(&mut combined);
530
531 let mut new_pop = Vec::with_capacity(self.population_size);
532
533 for front in fronts {
534 if new_pop.len() + front.len() <= self.population_size {
535 for &i in &front {
536 new_pop.push(combined[i].clone());
537 }
538 } else {
539 calculate_crowding_distance(&mut combined, &front);
540
541 let mut sorted_front: Vec<usize> = front.to_vec();
542 sorted_front.sort_by(|&a, &b| {
543 combined[b]
544 .crowding_distance
545 .partial_cmp(&combined[a].crowding_distance)
546 .unwrap_or(std::cmp::Ordering::Equal)
547 });
548
549 let remaining = self.population_size - new_pop.len();
550 for &i in sorted_front.iter().take(remaining) {
551 new_pop.push(combined[i].clone());
552 }
553 break;
554 }
555 }
556
557 let all_indices: Vec<usize> = (0..new_pop.len()).collect();
558 calculate_crowding_distance(&mut new_pop, &all_indices);
559
560 *population = new_pop;
561 }
562
563 pub fn run_bounded<R: Rng>(
565 &self,
566 fitness: &F,
567 crossover: &C,
568 mutation: &M,
569 bounds: &MultiBounds,
570 max_generations: usize,
571 rng: &mut R,
572 ) -> EvoResult<Vec<Nsga2Individual<G>>> {
573 let mut population = self.initialize_population(fitness, bounds, rng);
574
575 fast_non_dominated_sort(&mut population);
576 let all_indices: Vec<usize> = (0..population.len()).collect();
577 calculate_crowding_distance(&mut population, &all_indices);
578
579 for _ in 0..max_generations {
580 self.step_bounded(&mut population, fitness, crossover, mutation, bounds, rng);
581 }
582
583 Ok(population)
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590 use crate::genome::real_vector::RealVector;
591 use crate::genome::traits::RealValuedGenome;
592 use crate::operators::crossover::SbxCrossover;
593 use crate::operators::mutation::PolynomialMutation;
594
595 struct Zdt1;
597
598 impl MultiObjectiveFitness<RealVector> for Zdt1 {
599 fn num_objectives(&self) -> usize {
600 2
601 }
602
603 fn evaluate(&self, genome: &RealVector) -> Vec<f64> {
604 let x = genome.genes();
605 let n = x.len() as f64;
606
607 let f1 = x[0];
608
609 let g: f64 = 1.0 + 9.0 * x[1..].iter().sum::<f64>() / (n - 1.0);
610 let f2 = g * (1.0 - (f1 / g).sqrt());
611
612 vec![f1, f2]
613 }
614 }
615
616 #[test]
617 fn test_domination() {
618 let a = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.0, 2.0]);
619 let b = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![2.0, 3.0]);
620 let c = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.5, 1.5]);
621
622 assert!(a.dominates(&b)); assert!(!b.dominates(&a));
624 assert!(!a.dominates(&c)); assert!(!c.dominates(&a)); }
627
628 #[test]
629 fn test_fast_non_dominated_sort() {
630 let mut population = vec![
631 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.0, 4.0]),
632 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![2.0, 3.0]),
633 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![3.0, 2.0]),
634 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![4.0, 1.0]),
635 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![3.0, 3.0]),
636 ];
637
638 let fronts = fast_non_dominated_sort(&mut population);
639
640 assert_eq!(fronts[0].len(), 4);
642 assert_eq!(fronts[1].len(), 1);
644
645 for &i in &fronts[0] {
646 assert_eq!(population[i].rank, 0);
647 }
648 for &i in &fronts[1] {
649 assert_eq!(population[i].rank, 1);
650 }
651 }
652
653 #[test]
654 fn test_crowding_distance() {
655 let mut population = vec![
656 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![0.0, 10.0]),
657 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![5.0, 5.0]),
658 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![10.0, 0.0]),
659 ];
660
661 let front: Vec<usize> = (0..population.len()).collect();
662 calculate_crowding_distance(&mut population, &front);
663
664 assert!(population[0].crowding_distance.is_infinite());
666 assert!(population[2].crowding_distance.is_infinite());
667 assert!(population[1].crowding_distance.is_finite());
669 assert!(population[1].crowding_distance > 0.0);
670 }
671
672 #[test]
673 fn test_crowded_comparison() {
674 let mut a = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.0, 1.0]);
675 a.rank = 0;
676 a.crowding_distance = 2.0;
677
678 let mut b = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![2.0, 2.0]);
679 b.rank = 1;
680 b.crowding_distance = 3.0;
681
682 let mut c = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.5, 1.5]);
683 c.rank = 0;
684 c.crowding_distance = 1.0;
685
686 assert!(crowded_comparison(&a, &b)); assert!(crowded_comparison(&a, &c)); assert!(!crowded_comparison(&c, &a)); }
690
691 #[test]
692 fn test_nsga2_initialization() {
693 use crate::genome::bounds::Bounds;
694 let mut rng = rand::thread_rng();
695 let fitness = Zdt1;
696 let bounds = MultiBounds::new(vec![Bounds::new(0.0, 1.0); 10]);
697
698 let nsga2: Nsga2<RealVector, Zdt1, SbxCrossover, PolynomialMutation> = Nsga2::new(20);
699 let population = nsga2.initialize_population(&fitness, &bounds, &mut rng);
700
701 assert_eq!(population.len(), 20);
702 for ind in &population {
703 assert_eq!(ind.objectives.len(), 2);
704 }
705 }
706
707 #[test]
708 fn test_nsga2_run() {
709 use crate::genome::bounds::Bounds;
710 let mut rng = rand::thread_rng();
711 let fitness = Zdt1;
712 let bounds = MultiBounds::new(vec![Bounds::new(0.0, 1.0); 10]);
713 let crossover = SbxCrossover::new(15.0);
714 let mutation = PolynomialMutation::new(20.0);
715
716 let nsga2: Nsga2<RealVector, Zdt1, SbxCrossover, PolynomialMutation> = Nsga2::new(50);
717 let population = nsga2
718 .run_bounded(&fitness, &crossover, &mutation, &bounds, 10, &mut rng)
719 .unwrap();
720
721 assert_eq!(population.len(), 50);
722
723 let pareto_front =
725 Nsga2::<RealVector, Zdt1, SbxCrossover, PolynomialMutation>::get_pareto_front(
726 &population,
727 );
728 assert!(!pareto_front.is_empty());
729 }
730
731 #[test]
732 fn test_nsga2_pareto_front_quality() {
733 use crate::genome::bounds::Bounds;
734 let mut rng = rand::thread_rng();
735 let fitness = Zdt1;
736 let bounds = MultiBounds::new(vec![Bounds::new(0.0, 1.0); 10]);
737 let crossover = SbxCrossover::new(15.0);
738 let mutation = PolynomialMutation::new(20.0);
739
740 let nsga2: Nsga2<RealVector, Zdt1, SbxCrossover, PolynomialMutation> = Nsga2::new(100);
741 let population = nsga2
742 .run_bounded(&fitness, &crossover, &mutation, &bounds, 50, &mut rng)
743 .unwrap();
744
745 let pareto_front =
746 Nsga2::<RealVector, Zdt1, SbxCrossover, PolynomialMutation>::get_pareto_front(
747 &population,
748 );
749
750 for ind in &pareto_front {
752 assert_eq!(ind.rank, 0);
753 assert!(ind.objectives[0] >= 0.0);
756 assert!(ind.objectives[1] >= 0.0);
757 }
758 }
759}