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
22pub use crate::fitness::multi_objective::{ClosureMultiObjective, MultiObjectiveFitness};
26
27#[derive(Clone, Debug)]
29pub struct Nsga2Individual<G: EvolutionaryGenome> {
30 pub genome: G,
32 pub objectives: Vec<f64>,
34 pub rank: usize,
36 pub crowding_distance: f64,
38}
39
40impl<G: EvolutionaryGenome> Nsga2Individual<G> {
41 pub fn new(genome: G, objectives: Vec<f64>) -> Self {
43 Self {
44 genome,
45 objectives,
46 rank: usize::MAX,
47 crowding_distance: 0.0,
48 }
49 }
50
51 pub fn dominates(&self, other: &Self) -> bool {
54 let at_least_as_good = self
55 .objectives
56 .iter()
57 .zip(other.objectives.iter())
58 .all(|(a, b)| a <= b);
59 let strictly_better = self
60 .objectives
61 .iter()
62 .zip(other.objectives.iter())
63 .any(|(a, b)| a < b);
64 at_least_as_good && strictly_better
65 }
66
67 pub fn to_pareto_fitness(&self) -> ParetoFitness {
69 let mut pf = ParetoFitness::new(self.objectives.clone());
70 pf.rank = self.rank;
71 pf.crowding_distance = self.crowding_distance;
72 pf
73 }
74
75 pub fn to_individual(self) -> Individual<G, ParetoFitness> {
77 let fitness = self.to_pareto_fitness();
78 Individual::with_fitness(self.genome, fitness)
79 }
80}
81
82pub fn fast_non_dominated_sort<G: EvolutionaryGenome>(
86 population: &mut [Nsga2Individual<G>],
87) -> Vec<Vec<usize>> {
88 let n = population.len();
89 if n == 0 {
90 return vec![];
91 }
92
93 let mut domination_count = vec![0usize; n];
95 let mut dominated_set: Vec<Vec<usize>> = vec![vec![]; n];
97
98 for i in 0..n {
100 for j in (i + 1)..n {
101 if population[i].dominates(&population[j]) {
102 dominated_set[i].push(j);
103 domination_count[j] += 1;
104 } else if population[j].dominates(&population[i]) {
105 dominated_set[j].push(i);
106 domination_count[i] += 1;
107 }
108 }
109 }
110
111 let mut fronts: Vec<Vec<usize>> = vec![];
113 let mut current_front: Vec<usize> = (0..n).filter(|&i| domination_count[i] == 0).collect();
114
115 let mut rank = 0;
116 while !current_front.is_empty() {
117 for &i in ¤t_front {
119 population[i].rank = rank;
120 }
121
122 let mut next_front = vec![];
124 for &i in ¤t_front {
125 for &j in &dominated_set[i] {
126 domination_count[j] -= 1;
127 if domination_count[j] == 0 {
128 next_front.push(j);
129 }
130 }
131 }
132
133 fronts.push(current_front);
134 current_front = next_front;
135 rank += 1;
136 }
137
138 fronts
139}
140
141pub fn calculate_crowding_distance<G: EvolutionaryGenome>(
143 population: &mut [Nsga2Individual<G>],
144 front: &[usize],
145) {
146 let n = front.len();
147 if n <= 2 {
148 for &i in front {
149 population[i].crowding_distance = f64::INFINITY;
150 }
151 return;
152 }
153
154 for &i in front {
156 population[i].crowding_distance = 0.0;
157 }
158
159 let num_objectives = population[front[0]].objectives.len();
160
161 for obj in 0..num_objectives {
162 let mut sorted_indices: Vec<usize> = front.to_vec();
164 sorted_indices.sort_by(|&a, &b| {
165 population[a].objectives[obj]
166 .partial_cmp(&population[b].objectives[obj])
167 .unwrap_or(std::cmp::Ordering::Equal)
168 });
169
170 population[sorted_indices[0]].crowding_distance = f64::INFINITY;
172 population[sorted_indices[n - 1]].crowding_distance = f64::INFINITY;
173
174 let obj_min = population[sorted_indices[0]].objectives[obj];
176 let obj_max = population[sorted_indices[n - 1]].objectives[obj];
177 let obj_range = obj_max - obj_min;
178
179 if obj_range > 0.0 {
180 for i in 1..(n - 1) {
181 let idx = sorted_indices[i];
182 let prev_val = population[sorted_indices[i - 1]].objectives[obj];
183 let next_val = population[sorted_indices[i + 1]].objectives[obj];
184 population[idx].crowding_distance += (next_val - prev_val) / obj_range;
185 }
186 }
187 }
188}
189
190pub fn recompute_crowding_distance_per_front<G: EvolutionaryGenome>(
203 population: &mut [Nsga2Individual<G>],
204) {
205 use std::collections::BTreeMap;
206
207 let mut fronts: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
208 for (i, ind) in population.iter().enumerate() {
209 fronts.entry(ind.rank).or_default().push(i);
210 }
211
212 for front in fronts.values() {
213 calculate_crowding_distance(population, front);
214 }
215}
216
217pub fn crowded_comparison<G: EvolutionaryGenome>(
221 a: &Nsga2Individual<G>,
222 b: &Nsga2Individual<G>,
223) -> bool {
224 a.rank < b.rank || (a.rank == b.rank && a.crowding_distance > b.crowding_distance)
225}
226
227pub struct Nsga2<G, F, C, M> {
229 pub population_size: usize,
231 pub crossover_probability: f64,
233 pub mutation_probability: f64,
235 pub bounds: Option<MultiBounds>,
237 _phantom: PhantomData<(G, F, C, M)>,
239}
240
241impl<G, F, C, M> Nsga2<G, F, C, M>
242where
243 G: EvolutionaryGenome,
244 F: MultiObjectiveFitness<G>,
245 C: CrossoverOperator<G>,
246 M: MutationOperator<G>,
247{
248 pub fn new(population_size: usize) -> Self {
250 Self {
251 population_size,
252 crossover_probability: 0.9,
253 mutation_probability: 1.0,
254 bounds: None,
255 _phantom: PhantomData,
256 }
257 }
258
259 pub fn with_crossover_probability(mut self, prob: f64) -> Self {
261 self.crossover_probability = prob;
262 self
263 }
264
265 pub fn with_mutation_probability(mut self, prob: f64) -> Self {
267 self.mutation_probability = prob;
268 self
269 }
270
271 pub fn with_bounds(mut self, bounds: MultiBounds) -> Self {
273 self.bounds = Some(bounds);
274 self
275 }
276
277 pub fn initialize_population<R: Rng>(
279 &self,
280 fitness: &F,
281 bounds: &MultiBounds,
282 rng: &mut R,
283 ) -> Vec<Nsga2Individual<G>> {
284 (0..self.population_size)
285 .map(|_| {
286 let genome = G::generate(rng, bounds);
287 let objectives = fitness.evaluate(&genome);
288 Nsga2Individual::new(genome, objectives)
289 })
290 .collect()
291 }
292
293 pub fn tournament_select<'a, R: Rng>(
300 &self,
301 population: &'a [Nsga2Individual<G>],
302 rng: &mut R,
303 ) -> &'a Nsga2Individual<G> {
304 let len = population.len();
305 let i = rng.gen_range(0..len);
306 let j = if len > 1 {
309 let mut j = rng.gen_range(0..len - 1);
310 if j >= i {
311 j += 1;
312 }
313 j
314 } else {
315 i
316 };
317
318 if crowded_comparison(&population[i], &population[j]) {
319 &population[i]
320 } else {
321 &population[j]
322 }
323 }
324
325 pub fn create_offspring<R: Rng>(
327 &self,
328 population: &[Nsga2Individual<G>],
329 fitness: &F,
330 crossover: &C,
331 mutation: &M,
332 rng: &mut R,
333 ) -> Vec<Nsga2Individual<G>> {
334 let mut offspring = Vec::with_capacity(self.population_size);
335
336 while offspring.len() < self.population_size {
337 let parent1 = self.tournament_select(population, rng);
339 let parent2 = self.tournament_select(population, rng);
340
341 let (mut child1, mut child2) = if rng.gen::<f64>() < self.crossover_probability {
343 match crossover.crossover(&parent1.genome, &parent2.genome, rng) {
344 crate::error::OperatorResult::Success((c1, c2)) => (c1, c2),
345 _ => (parent1.genome.clone(), parent2.genome.clone()),
346 }
347 } else {
348 (parent1.genome.clone(), parent2.genome.clone())
349 };
350
351 if rng.gen::<f64>() < self.mutation_probability {
353 mutation.mutate(&mut child1, rng);
354 }
355 if rng.gen::<f64>() < self.mutation_probability {
356 mutation.mutate(&mut child2, rng);
357 }
358
359 let obj1 = fitness.evaluate(&child1);
361 let obj2 = fitness.evaluate(&child2);
362
363 offspring.push(Nsga2Individual::new(child1, obj1));
364 if offspring.len() < self.population_size {
365 offspring.push(Nsga2Individual::new(child2, obj2));
366 }
367 }
368
369 offspring
370 }
371
372 pub fn step<R: Rng>(
374 &self,
375 population: &mut Vec<Nsga2Individual<G>>,
376 fitness: &F,
377 crossover: &C,
378 mutation: &M,
379 rng: &mut R,
380 ) {
381 let offspring = self.create_offspring(population, fitness, crossover, mutation, rng);
383
384 let mut combined: Vec<Nsga2Individual<G>> =
386 population.drain(..).chain(offspring.into_iter()).collect();
387
388 let fronts = fast_non_dominated_sort(&mut combined);
390
391 let mut new_pop = Vec::with_capacity(self.population_size);
393
394 for front in fronts {
395 if new_pop.len() + front.len() <= self.population_size {
396 for &i in &front {
398 new_pop.push(combined[i].clone());
399 }
400 } else {
401 calculate_crowding_distance(&mut combined, &front);
403
404 let mut sorted_front: Vec<usize> = front.to_vec();
405 sorted_front.sort_by(|&a, &b| {
406 combined[b]
407 .crowding_distance
408 .partial_cmp(&combined[a].crowding_distance)
409 .unwrap_or(std::cmp::Ordering::Equal)
410 });
411
412 let remaining = self.population_size - new_pop.len();
413 for &i in sorted_front.iter().take(remaining) {
414 new_pop.push(combined[i].clone());
415 }
416 break;
417 }
418 }
419
420 recompute_crowding_distance_per_front(&mut new_pop);
422
423 *population = new_pop;
424 }
425
426 pub fn run<R: Rng>(
428 &self,
429 fitness: &F,
430 crossover: &C,
431 mutation: &M,
432 bounds: &MultiBounds,
433 max_generations: usize,
434 rng: &mut R,
435 ) -> EvoResult<Vec<Nsga2Individual<G>>> {
436 let mut population = self.initialize_population(fitness, bounds, rng);
437
438 fast_non_dominated_sort(&mut population);
440 recompute_crowding_distance_per_front(&mut population);
441
442 for _ in 0..max_generations {
443 self.step(&mut population, fitness, crossover, mutation, rng);
444 }
445
446 Ok(population)
447 }
448
449 pub fn get_pareto_front(population: &[Nsga2Individual<G>]) -> Vec<&Nsga2Individual<G>> {
451 population.iter().filter(|ind| ind.rank == 0).collect()
452 }
453}
454
455impl<G, F, C, M> Nsga2<G, F, C, M>
457where
458 G: EvolutionaryGenome,
459 F: MultiObjectiveFitness<G>,
460 C: BoundedCrossoverOperator<G>,
461 M: BoundedMutationOperator<G>,
462{
463 pub fn create_offspring_bounded<R: Rng>(
465 &self,
466 population: &[Nsga2Individual<G>],
467 fitness: &F,
468 crossover: &C,
469 mutation: &M,
470 bounds: &MultiBounds,
471 rng: &mut R,
472 ) -> Vec<Nsga2Individual<G>> {
473 let mut offspring = Vec::with_capacity(self.population_size);
474
475 while offspring.len() < self.population_size {
476 let parent1 = self.tournament_select(population, rng);
477 let parent2 = self.tournament_select(population, rng);
478
479 let (mut child1, mut child2) = if rng.gen::<f64>() < self.crossover_probability {
480 match crossover.crossover_bounded(&parent1.genome, &parent2.genome, bounds, rng) {
481 crate::error::OperatorResult::Success((c1, c2)) => (c1, c2),
482 _ => (parent1.genome.clone(), parent2.genome.clone()),
483 }
484 } else {
485 (parent1.genome.clone(), parent2.genome.clone())
486 };
487
488 if rng.gen::<f64>() < self.mutation_probability {
489 mutation.mutate_bounded(&mut child1, bounds, rng);
490 }
491 if rng.gen::<f64>() < self.mutation_probability {
492 mutation.mutate_bounded(&mut child2, bounds, rng);
493 }
494
495 let obj1 = fitness.evaluate(&child1);
496 let obj2 = fitness.evaluate(&child2);
497
498 offspring.push(Nsga2Individual::new(child1, obj1));
499 if offspring.len() < self.population_size {
500 offspring.push(Nsga2Individual::new(child2, obj2));
501 }
502 }
503
504 offspring
505 }
506
507 pub fn step_bounded<R: Rng>(
509 &self,
510 population: &mut Vec<Nsga2Individual<G>>,
511 fitness: &F,
512 crossover: &C,
513 mutation: &M,
514 bounds: &MultiBounds,
515 rng: &mut R,
516 ) {
517 let offspring =
518 self.create_offspring_bounded(population, fitness, crossover, mutation, bounds, rng);
519
520 let mut combined: Vec<Nsga2Individual<G>> =
521 population.drain(..).chain(offspring.into_iter()).collect();
522
523 let fronts = fast_non_dominated_sort(&mut combined);
524
525 let mut new_pop = Vec::with_capacity(self.population_size);
526
527 for front in fronts {
528 if new_pop.len() + front.len() <= self.population_size {
529 for &i in &front {
530 new_pop.push(combined[i].clone());
531 }
532 } else {
533 calculate_crowding_distance(&mut combined, &front);
534
535 let mut sorted_front: Vec<usize> = front.to_vec();
536 sorted_front.sort_by(|&a, &b| {
537 combined[b]
538 .crowding_distance
539 .partial_cmp(&combined[a].crowding_distance)
540 .unwrap_or(std::cmp::Ordering::Equal)
541 });
542
543 let remaining = self.population_size - new_pop.len();
544 for &i in sorted_front.iter().take(remaining) {
545 new_pop.push(combined[i].clone());
546 }
547 break;
548 }
549 }
550
551 recompute_crowding_distance_per_front(&mut new_pop);
553
554 *population = new_pop;
555 }
556
557 pub fn run_bounded<R: Rng>(
559 &self,
560 fitness: &F,
561 crossover: &C,
562 mutation: &M,
563 bounds: &MultiBounds,
564 max_generations: usize,
565 rng: &mut R,
566 ) -> EvoResult<Vec<Nsga2Individual<G>>> {
567 let mut population = self.initialize_population(fitness, bounds, rng);
568
569 fast_non_dominated_sort(&mut population);
570 recompute_crowding_distance_per_front(&mut population);
571
572 for _ in 0..max_generations {
573 self.step_bounded(&mut population, fitness, crossover, mutation, bounds, rng);
574 }
575
576 Ok(population)
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use crate::genome::real_vector::RealVector;
584 use crate::genome::traits::RealValuedGenome;
585 use crate::operators::crossover::SbxCrossover;
586 use crate::operators::mutation::PolynomialMutation;
587
588 struct Zdt1;
590
591 impl MultiObjectiveFitness<RealVector> for Zdt1 {
592 fn num_objectives(&self) -> usize {
593 2
594 }
595
596 fn evaluate(&self, genome: &RealVector) -> Vec<f64> {
597 let x = genome.genes();
598 let n = x.len() as f64;
599
600 let f1 = x[0];
601
602 let g: f64 = 1.0 + 9.0 * x[1..].iter().sum::<f64>() / (n - 1.0);
603 let f2 = g * (1.0 - (f1 / g).sqrt());
604
605 vec![f1, f2]
606 }
607 }
608
609 #[test]
610 fn test_domination() {
611 let a = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.0, 2.0]);
612 let b = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![2.0, 3.0]);
613 let c = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.5, 1.5]);
614
615 assert!(a.dominates(&b)); assert!(!b.dominates(&a));
617 assert!(!a.dominates(&c)); assert!(!c.dominates(&a)); }
620
621 #[test]
622 fn test_fast_non_dominated_sort() {
623 let mut population = vec![
624 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.0, 4.0]),
625 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![2.0, 3.0]),
626 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![3.0, 2.0]),
627 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![4.0, 1.0]),
628 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![3.0, 3.0]),
629 ];
630
631 let fronts = fast_non_dominated_sort(&mut population);
632
633 assert_eq!(fronts[0].len(), 4);
635 assert_eq!(fronts[1].len(), 1);
637
638 for &i in &fronts[0] {
639 assert_eq!(population[i].rank, 0);
640 }
641 for &i in &fronts[1] {
642 assert_eq!(population[i].rank, 1);
643 }
644 }
645
646 #[test]
647 fn test_crowding_distance() {
648 let mut population = vec![
649 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![0.0, 10.0]),
650 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![5.0, 5.0]),
651 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![10.0, 0.0]),
652 ];
653
654 let front: Vec<usize> = (0..population.len()).collect();
655 calculate_crowding_distance(&mut population, &front);
656
657 assert!(population[0].crowding_distance.is_infinite());
659 assert!(population[2].crowding_distance.is_infinite());
660 assert!(population[1].crowding_distance.is_finite());
662 assert!(population[1].crowding_distance > 0.0);
663 }
664
665 #[test]
666 fn test_crowding_distance_per_front() {
667 let build = || {
674 vec![
675 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.0, 4.0]), Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![2.0, 3.0]), Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![3.0, 2.0]), Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![4.0, 1.0]), Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![3.0, 3.0]), ]
681 };
682
683 let mut pop = build();
685 fast_non_dominated_sort(&mut pop);
686 assert_eq!(pop[0].rank, 0);
687 assert_eq!(pop[4].rank, 1, "E is dominated by B and C");
688 recompute_crowding_distance_per_front(&mut pop);
689 assert!(
690 pop[0].crowding_distance.is_infinite(),
691 "A is a front-0 boundary"
692 );
693 assert!(
694 pop[3].crowding_distance.is_infinite(),
695 "D is a front-0 boundary"
696 );
697 assert!(
698 (pop[1].crowding_distance - 4.0 / 3.0).abs() < 1e-9,
699 "B = 4/3"
700 );
701 assert!(
702 (pop[2].crowding_distance - 4.0 / 3.0).abs() < 1e-9,
703 "C = 4/3"
704 );
705 assert!(
706 pop[4].crowding_distance.is_infinite(),
707 "E is the sole member of its front and must be infinite"
708 );
709
710 let mut pop_all = build();
712 fast_non_dominated_sort(&mut pop_all);
713 let all: Vec<usize> = (0..pop_all.len()).collect();
714 calculate_crowding_distance(&mut pop_all, &all);
715 assert!(
716 pop_all[4].crowding_distance.is_finite(),
717 "whole-population crowding (pre-fix behaviour) makes E finite"
718 );
719 }
720
721 #[test]
722 fn test_tournament_select_draws_distinct_competitors() {
723 use rand::SeedableRng;
730
731 let mut population = vec![
732 Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![0.0, 0.0]),
733 Nsga2Individual::<RealVector>::new(RealVector::new(vec![1.0]), vec![1.0, 1.0]),
734 ];
735 fast_non_dominated_sort(&mut population);
736 assert_eq!(population[0].rank, 0);
737 assert_eq!(population[1].rank, 1);
738
739 let nsga2: Nsga2<RealVector, Zdt1, SbxCrossover, PolynomialMutation> = Nsga2::new(2);
740 let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
741 for _ in 0..2000 {
742 let selected = nsga2.tournament_select(&population, &mut rng);
743 assert_eq!(
744 selected.rank, 0,
745 "a distinct tournament must never return the dominated individual"
746 );
747 }
748 }
749
750 #[test]
751 fn test_closure_multiobjective_reports_true_count() {
752 let fitness = ClosureMultiObjective::new(3, |g: &RealVector| {
754 let x = g.genes()[0];
755 vec![x, x * x, x + 1.0]
756 });
757 assert_eq!(fitness.num_objectives(), 3);
758 assert_eq!(
759 fitness.evaluate(&RealVector::new(vec![2.0])),
760 vec![2.0, 4.0, 3.0]
761 );
762 }
763
764 #[test]
765 fn test_crowded_comparison() {
766 let mut a = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.0, 1.0]);
767 a.rank = 0;
768 a.crowding_distance = 2.0;
769
770 let mut b = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![2.0, 2.0]);
771 b.rank = 1;
772 b.crowding_distance = 3.0;
773
774 let mut c = Nsga2Individual::<RealVector>::new(RealVector::new(vec![0.0]), vec![1.5, 1.5]);
775 c.rank = 0;
776 c.crowding_distance = 1.0;
777
778 assert!(crowded_comparison(&a, &b)); assert!(crowded_comparison(&a, &c)); assert!(!crowded_comparison(&c, &a)); }
782
783 #[test]
784 fn test_nsga2_initialization() {
785 use crate::genome::bounds::Bounds;
786 let mut rng = rand::thread_rng();
787 let fitness = Zdt1;
788 let bounds = MultiBounds::new(vec![Bounds::new(0.0, 1.0); 10]);
789
790 let nsga2: Nsga2<RealVector, Zdt1, SbxCrossover, PolynomialMutation> = Nsga2::new(20);
791 let population = nsga2.initialize_population(&fitness, &bounds, &mut rng);
792
793 assert_eq!(population.len(), 20);
794 for ind in &population {
795 assert_eq!(ind.objectives.len(), 2);
796 }
797 }
798
799 #[test]
800 fn test_nsga2_run() {
801 use crate::genome::bounds::Bounds;
802 let mut rng = rand::thread_rng();
803 let fitness = Zdt1;
804 let bounds = MultiBounds::new(vec![Bounds::new(0.0, 1.0); 10]);
805 let crossover = SbxCrossover::new(15.0);
806 let mutation = PolynomialMutation::new(20.0);
807
808 let nsga2: Nsga2<RealVector, Zdt1, SbxCrossover, PolynomialMutation> = Nsga2::new(50);
809 let population = nsga2
810 .run_bounded(&fitness, &crossover, &mutation, &bounds, 10, &mut rng)
811 .unwrap();
812
813 assert_eq!(population.len(), 50);
814
815 let pareto_front =
817 Nsga2::<RealVector, Zdt1, SbxCrossover, PolynomialMutation>::get_pareto_front(
818 &population,
819 );
820 assert!(!pareto_front.is_empty());
821 }
822
823 #[test]
824 fn test_nsga2_pareto_front_quality() {
825 use crate::genome::bounds::Bounds;
826 let mut rng = rand::thread_rng();
827 let fitness = Zdt1;
828 let bounds = MultiBounds::new(vec![Bounds::new(0.0, 1.0); 10]);
829 let crossover = SbxCrossover::new(15.0);
830 let mutation = PolynomialMutation::new(20.0);
831
832 let nsga2: Nsga2<RealVector, Zdt1, SbxCrossover, PolynomialMutation> = Nsga2::new(100);
833 let population = nsga2
834 .run_bounded(&fitness, &crossover, &mutation, &bounds, 50, &mut rng)
835 .unwrap();
836
837 let pareto_front =
838 Nsga2::<RealVector, Zdt1, SbxCrossover, PolynomialMutation>::get_pareto_front(
839 &population,
840 );
841
842 for ind in &pareto_front {
844 assert_eq!(ind.rank, 0);
845 assert!(ind.objectives[0] >= 0.0);
848 assert!(ind.objectives[1] >= 0.0);
849 }
850 }
851}