1use crate::phantom::{MultiObjective, OptimizationProblem};
33use crate::OptimizationResult;
34
35use num_traits::Float;
36use rand::prelude::*;
37use std::cmp::Ordering;
38use std::marker::PhantomData;
39
40#[derive(Clone, Debug)]
42pub struct MultiObjectiveConfig<T: Float> {
43 pub population_size: usize,
45 pub max_generations: usize,
47 pub crossover_probability: T,
49 pub mutation_probability: T,
51 pub mutation_strength: T,
53 pub elite_ratio: T,
55 pub convergence_tolerance: T,
57 pub reference_point: Option<Vec<T>>,
59 pub preserve_diversity: bool,
61}
62
63impl<T: Float> Default for MultiObjectiveConfig<T> {
64 fn default() -> Self {
65 Self {
66 population_size: 100,
67 max_generations: 200, crossover_probability: T::from(0.9).unwrap(),
69 mutation_probability: T::from(0.2).unwrap(), mutation_strength: T::from(0.1).unwrap(),
71 elite_ratio: T::from(0.1).unwrap(),
72 convergence_tolerance: T::from(1e-4).unwrap(), reference_point: Some(vec![T::from(10.0).unwrap(), T::from(10.0).unwrap()]), preserve_diversity: true,
75 }
76 }
77}
78
79#[derive(Clone, Debug)]
81pub struct Individual<T: Float> {
82 pub variables: Vec<T>,
84 pub objectives: Vec<T>,
86 pub rank: usize,
88 pub crowding_distance: T,
90 pub constraint_violations: Vec<T>,
92}
93
94impl<T: Float> Individual<T> {
95 pub fn new(variables: Vec<T>) -> Self {
97 Self {
98 variables,
99 objectives: Vec::new(),
100 rank: 0,
101 crowding_distance: T::zero(),
102 constraint_violations: Vec::new(),
103 }
104 }
105
106 pub fn dominates(&self, other: &Individual<T>) -> bool {
108 if self.objectives.len() != other.objectives.len() {
109 return false;
110 }
111
112 let mut at_least_one_better = false;
113 for (my_obj, other_obj) in self.objectives.iter().zip(other.objectives.iter()) {
114 if *my_obj > *other_obj {
115 return false; }
117 if *my_obj < *other_obj {
118 at_least_one_better = true;
119 }
120 }
121
122 at_least_one_better
123 }
124
125 pub fn is_feasible(&self) -> bool {
127 self.constraint_violations.iter().all(|&v| v <= T::zero())
128 }
129}
130
131#[derive(Clone, Debug)]
133pub struct ParetoFront<T: Float> {
134 pub solutions: Vec<Individual<T>>,
136 pub hypervolume: Option<T>,
138 pub reference_point: Option<Vec<T>>,
140}
141
142impl<T: Float> ParetoFront<T> {
143 pub fn new() -> Self {
145 Self {
146 solutions: Vec::new(),
147 hypervolume: None,
148 reference_point: None,
149 }
150 }
151
152 pub fn add_if_non_dominated(&mut self, candidate: Individual<T>) -> bool {
154 for existing in &self.solutions {
156 if existing.dominates(&candidate) {
157 return false; }
159 }
160
161 self.solutions
163 .retain(|existing| !candidate.dominates(existing));
164
165 self.solutions.push(candidate);
167 true
168 }
169
170 pub fn calculate_hypervolume(&mut self, reference_point: &[T]) -> T {
172 if self.solutions.is_empty() {
173 return T::zero();
174 }
175
176 if self.solutions[0].objectives.len() == 2 {
178 self.hypervolume_2d(reference_point)
179 } else {
180 self.hypervolume_approximation(reference_point)
182 }
183 }
184
185 fn hypervolume_2d(&self, reference_point: &[T]) -> T {
187 if self.solutions.len() == 1 {
188 let sol = &self.solutions[0];
189 return (reference_point[0] - sol.objectives[0])
190 * (reference_point[1] - sol.objectives[1]);
191 }
192
193 let mut sorted_solutions = self.solutions.clone();
195 sorted_solutions.sort_by(|a, b| {
196 a.objectives[0]
197 .partial_cmp(&b.objectives[0])
198 .unwrap_or(Ordering::Equal)
199 });
200
201 let mut volume = T::zero();
202 let mut prev_y = reference_point[1];
203
204 for solution in &sorted_solutions {
205 let width = reference_point[0] - solution.objectives[0];
206 let height = prev_y - solution.objectives[1];
207 volume = volume + width * height;
208 prev_y = solution.objectives[1];
209 }
210
211 volume
212 }
213
214 fn hypervolume_approximation(&self, reference_point: &[T]) -> T {
216 let samples = 10000;
218 let mut dominated_count = 0;
219 let mut rng = thread_rng();
220
221 let obj_count = self.solutions[0].objectives.len();
222
223 for _ in 0..samples {
224 let random_point: Vec<T> = (0..obj_count)
226 .map(|i| {
227 let min_obj = self
228 .solutions
229 .iter()
230 .map(|s| s.objectives[i])
231 .fold(T::infinity(), |a, b| if a < b { a } else { b });
232 let range = reference_point[i] - min_obj;
233 min_obj + T::from(rng.gen::<f64>()).unwrap() * range
234 })
235 .collect();
236
237 for solution in &self.solutions {
239 let dominates = solution
240 .objectives
241 .iter()
242 .zip(random_point.iter())
243 .all(|(&obj, &rand)| obj <= rand);
244
245 if dominates {
246 dominated_count += 1;
247 break;
248 }
249 }
250 }
251
252 let total_volume = reference_point
254 .iter()
255 .zip(self.solutions.iter().flat_map(|s| s.objectives.iter()))
256 .fold(T::one(), |acc, (&ref_val, &_obj_val)| acc * ref_val);
257
258 total_volume * T::from(dominated_count as f64 / samples as f64).unwrap()
259 }
260}
261
262impl<T: Float> Default for ParetoFront<T> {
263 fn default() -> Self {
264 Self::new()
265 }
266}
267
268#[derive(Clone, Debug)]
270pub struct MultiObjectiveResult<T: Float> {
271 pub pareto_front: ParetoFront<T>,
273 pub generations: usize,
275 pub final_population: Vec<Individual<T>>,
277 pub converged: bool,
279 pub evolution_history: Option<Vec<ParetoFront<T>>>,
281}
282
283pub trait MultiObjectiveFunction<T: Float> {
285 fn num_objectives(&self) -> usize;
287
288 fn num_variables(&self) -> usize;
290
291 fn evaluate(&self, variables: &[T]) -> Vec<T>;
293
294 fn evaluate_constraints(&self, _variables: &[T]) -> Vec<T> {
296 Vec::new() }
298
299 fn variable_bounds(&self) -> Vec<(T, T)>;
301
302 fn ideal_point(&self) -> Option<Vec<T>> {
304 None
305 }
306
307 fn nadir_point(&self) -> Option<Vec<T>> {
309 None
310 }
311}
312
313#[derive(Clone, Debug)]
315pub struct NsgaII<T: Float> {
316 config: MultiObjectiveConfig<T>,
317 _phantom: PhantomData<T>,
318}
319
320impl<T: Float> NsgaII<T> {
321 pub fn new(config: MultiObjectiveConfig<T>) -> Self {
323 Self {
324 config,
325 _phantom: PhantomData,
326 }
327 }
328
329 pub fn with_default_config() -> Self {
331 Self::new(MultiObjectiveConfig::default())
332 }
333
334 pub fn optimize<
336 const DIM: usize,
337 C: crate::phantom::ConstraintState,
338 V: crate::phantom::ConvexityState,
339 M: crate::phantom::ManifoldState,
340 >(
341 &self,
342 _problem: &OptimizationProblem<DIM, C, MultiObjective, V, M>,
343 objective_function: &impl MultiObjectiveFunction<T>,
344 ) -> OptimizationResult<MultiObjectiveResult<T>> {
345 let mut rng = thread_rng();
346
347 let mut population = self.initialize_population(objective_function, &mut rng)?;
349
350 for individual in &mut population {
352 individual.objectives = objective_function.evaluate(&individual.variables);
353 individual.constraint_violations =
354 objective_function.evaluate_constraints(&individual.variables);
355 }
356
357 let mut evolution_history = if self.config.max_generations < 100 {
358 Some(Vec::with_capacity(self.config.max_generations))
359 } else {
360 None
361 };
362
363 let mut best_hypervolume = T::zero();
364 let mut stagnation_count = 0;
365
366 for generation in 0..self.config.max_generations {
367 let fronts = self.non_dominated_sort(&population);
369
370 let mut ranked_population = Vec::new();
372 for front in &fronts {
373 let mut front_with_distance = front.clone();
374 self.calculate_crowding_distance(&mut front_with_distance);
375 ranked_population.extend(front_with_distance);
376 }
377
378 let offspring =
380 self.generate_offspring(&ranked_population, objective_function, &mut rng)?;
381
382 population.extend(offspring);
384
385 population = self.environmental_selection(population);
387
388 let mut current_front = self.extract_pareto_front(&population);
390 let current_hypervolume = if let Some(ref_point) = &self.config.reference_point {
391 current_front.calculate_hypervolume(ref_point)
392 } else {
393 T::zero()
394 };
395
396 if (current_hypervolume - best_hypervolume).abs() < self.config.convergence_tolerance {
398 stagnation_count += 1;
399 } else {
400 stagnation_count = 0;
401 best_hypervolume = current_hypervolume;
402 }
403
404 if let Some(ref mut history) = evolution_history {
405 history.push(current_front);
406 }
407
408 if stagnation_count >= 100 {
410 let final_front = self.extract_pareto_front(&population);
411 return Ok(MultiObjectiveResult {
412 pareto_front: final_front,
413 generations: generation + 1,
414 final_population: population,
415 converged: true,
416 evolution_history,
417 });
418 }
419 }
420
421 let final_front = self.extract_pareto_front(&population);
423
424 Ok(MultiObjectiveResult {
425 pareto_front: final_front,
426 generations: self.config.max_generations,
427 final_population: population,
428 converged: true, evolution_history,
430 })
431 }
432
433 fn initialize_population(
435 &self,
436 objective_function: &impl MultiObjectiveFunction<T>,
437 rng: &mut ThreadRng,
438 ) -> OptimizationResult<Vec<Individual<T>>> {
439 let bounds = objective_function.variable_bounds();
440 let mut population = Vec::with_capacity(self.config.population_size);
441
442 for _ in 0..self.config.population_size {
443 let variables: Vec<T> = bounds
444 .iter()
445 .map(|(min, max)| {
446 let range = *max - *min;
447 *min + T::from(rng.gen::<f64>()).unwrap() * range
448 })
449 .collect();
450
451 population.push(Individual::new(variables));
452 }
453
454 Ok(population)
455 }
456
457 fn non_dominated_sort(&self, population: &[Individual<T>]) -> Vec<Vec<Individual<T>>> {
459 let mut fronts: Vec<Vec<Individual<T>>> = Vec::new();
460 let mut remaining = population.to_vec();
461
462 while !remaining.is_empty() {
463 let mut current_front = Vec::new();
464 let mut next_remaining = Vec::new();
465
466 for individual in &remaining {
467 let mut is_dominated = false;
468 for other in &remaining {
469 if other.dominates(individual) {
470 is_dominated = true;
471 break;
472 }
473 }
474
475 if !is_dominated {
476 current_front.push(individual.clone());
477 } else {
478 next_remaining.push(individual.clone());
479 }
480 }
481
482 if current_front.is_empty() {
483 break;
484 }
485
486 fronts.push(current_front);
487 remaining = next_remaining;
488 }
489
490 fronts
491 }
492
493 fn calculate_crowding_distance(&self, front: &mut [Individual<T>]) {
495 if front.len() <= 2 {
496 for individual in front {
497 individual.crowding_distance = T::infinity();
498 }
499 return;
500 }
501
502 let num_objectives = front[0].objectives.len();
503
504 for individual in front.iter_mut() {
506 individual.crowding_distance = T::zero();
507 }
508
509 for obj_idx in 0..num_objectives {
510 front.sort_by(|a, b| {
512 a.objectives[obj_idx]
513 .partial_cmp(&b.objectives[obj_idx])
514 .unwrap_or(Ordering::Equal)
515 });
516
517 front[0].crowding_distance = T::infinity();
519 front[front.len() - 1].crowding_distance = T::infinity();
520
521 let obj_range =
522 front[front.len() - 1].objectives[obj_idx] - front[0].objectives[obj_idx];
523 if obj_range > T::zero() {
524 for i in 1..front.len() - 1 {
525 let distance = (front[i + 1].objectives[obj_idx]
526 - front[i - 1].objectives[obj_idx])
527 / obj_range;
528 front[i].crowding_distance = front[i].crowding_distance + distance;
529 }
530 }
531 }
532 }
533
534 fn generate_offspring(
536 &self,
537 population: &[Individual<T>],
538 objective_function: &impl MultiObjectiveFunction<T>,
539 rng: &mut ThreadRng,
540 ) -> OptimizationResult<Vec<Individual<T>>> {
541 let mut offspring = Vec::with_capacity(self.config.population_size);
542
543 while offspring.len() < self.config.population_size {
544 let parent1 = self.tournament_selection(population, rng);
546 let parent2 = self.tournament_selection(population, rng);
547
548 let (mut child1, mut child2) =
550 if rng.gen::<f64>() < self.config.crossover_probability.to_f64().unwrap() {
551 self.simulated_binary_crossover(parent1, parent2, rng)
552 } else {
553 (parent1.clone(), parent2.clone())
554 };
555
556 if rng.gen::<f64>() < self.config.mutation_probability.to_f64().unwrap() {
558 self.polynomial_mutation(&mut child1, objective_function, rng);
559 }
560 if rng.gen::<f64>() < self.config.mutation_probability.to_f64().unwrap() {
561 self.polynomial_mutation(&mut child2, objective_function, rng);
562 }
563
564 child1.objectives = objective_function.evaluate(&child1.variables);
566 child1.constraint_violations =
567 objective_function.evaluate_constraints(&child1.variables);
568 child2.objectives = objective_function.evaluate(&child2.variables);
569 child2.constraint_violations =
570 objective_function.evaluate_constraints(&child2.variables);
571
572 offspring.push(child1);
573 if offspring.len() < self.config.population_size {
574 offspring.push(child2);
575 }
576 }
577
578 Ok(offspring)
579 }
580
581 fn tournament_selection<'a>(
583 &self,
584 population: &'a [Individual<T>],
585 rng: &mut ThreadRng,
586 ) -> &'a Individual<T> {
587 let tournament_size = 2;
588 let mut best = &population[rng.gen_range(0..population.len())];
589
590 for _ in 1..tournament_size {
591 let candidate = &population[rng.gen_range(0..population.len())];
592 if candidate.rank < best.rank
593 || (candidate.rank == best.rank
594 && candidate.crowding_distance > best.crowding_distance)
595 {
596 best = candidate;
597 }
598 }
599
600 best
601 }
602
603 fn simulated_binary_crossover(
605 &self,
606 parent1: &Individual<T>,
607 parent2: &Individual<T>,
608 rng: &mut ThreadRng,
609 ) -> (Individual<T>, Individual<T>) {
610 let eta_c = 20.0; let mut child1 = parent1.clone();
612 let mut child2 = parent2.clone();
613
614 for i in 0..parent1.variables.len() {
615 if rng.gen::<f64>() <= 0.5 {
616 let p1 = parent1.variables[i].to_f64().unwrap();
617 let p2 = parent2.variables[i].to_f64().unwrap();
618
619 let u = rng.gen::<f64>();
620 let beta = if u <= 0.5 {
621 (2.0 * u).powf(1.0 / (eta_c + 1.0))
622 } else {
623 (1.0 / (2.0 * (1.0 - u))).powf(1.0 / (eta_c + 1.0))
624 };
625
626 let c1 = 0.5 * ((1.0 + beta) * p1 + (1.0 - beta) * p2);
627 let c2 = 0.5 * ((1.0 - beta) * p1 + (1.0 + beta) * p2);
628
629 child1.variables[i] = T::from(c1).unwrap();
630 child2.variables[i] = T::from(c2).unwrap();
631 }
632 }
633
634 (child1, child2)
635 }
636
637 fn polynomial_mutation(
639 &self,
640 individual: &mut Individual<T>,
641 objective_function: &impl MultiObjectiveFunction<T>,
642 rng: &mut ThreadRng,
643 ) {
644 let bounds = objective_function.variable_bounds();
645 let eta_m = 20.0; let num_variables = individual.variables.len();
647
648 for (i, variable) in individual.variables.iter_mut().enumerate() {
649 if rng.gen::<f64>() <= (1.0 / num_variables as f64) {
650 let (lower, upper) = bounds[i];
651 let x = variable.to_f64().unwrap();
652 let xl = lower.to_f64().unwrap();
653 let xu = upper.to_f64().unwrap();
654
655 let delta1 = (x - xl) / (xu - xl);
656 let delta2 = (xu - x) / (xu - xl);
657
658 let rnd = rng.gen::<f64>();
659 let deltaq = if rnd <= 0.5 {
660 let xy = 1.0 - delta1;
661 let val = 2.0 * rnd + (1.0 - 2.0 * rnd) * xy.powf(eta_m + 1.0);
662 val.powf(1.0 / (eta_m + 1.0)) - 1.0
663 } else {
664 let xy = 1.0 - delta2;
665 let val = 2.0 * (1.0 - rnd) + 2.0 * (rnd - 0.5) * xy.powf(eta_m + 1.0);
666 1.0 - val.powf(1.0 / (eta_m + 1.0))
667 };
668
669 let new_x = x + deltaq * (xu - xl);
670 *variable = T::from(new_x.max(xl).min(xu)).unwrap();
671 }
672 }
673 }
674
675 fn environmental_selection(&self, population: Vec<Individual<T>>) -> Vec<Individual<T>> {
677 if population.len() <= self.config.population_size {
678 return population;
679 }
680
681 let fronts = self.non_dominated_sort(&population);
683 let mut selected = Vec::new();
684
685 for mut front in fronts {
686 if selected.len() + front.len() <= self.config.population_size {
687 selected.extend(front);
689 } else {
690 self.calculate_crowding_distance(&mut front);
692 front.sort_by(|a, b| {
693 b.crowding_distance
694 .partial_cmp(&a.crowding_distance)
695 .unwrap_or(Ordering::Equal)
696 });
697
698 let remaining_slots = self.config.population_size - selected.len();
699 selected.extend(front.into_iter().take(remaining_slots));
700 break;
701 }
702 }
703
704 selected
705 }
706
707 fn extract_pareto_front(&self, population: &[Individual<T>]) -> ParetoFront<T> {
709 let fronts = self.non_dominated_sort(population);
710 let mut pareto_front = ParetoFront::new();
711
712 if let Some(first_front) = fronts.first() {
713 pareto_front.solutions = first_front.clone();
714 }
715
716 if let Some(ref_point) = &self.config.reference_point {
717 pareto_front.hypervolume = Some(pareto_front.calculate_hypervolume(ref_point));
718 pareto_front.reference_point = Some(ref_point.clone());
719 }
720
721 pareto_front
722 }
723}
724
725#[cfg(test)]
726mod tests {
727 use super::*;
728
729 struct ZDT1 {
731 num_variables: usize,
732 }
733
734 impl MultiObjectiveFunction<f64> for ZDT1 {
735 fn num_objectives(&self) -> usize {
736 2
737 }
738
739 fn num_variables(&self) -> usize {
740 self.num_variables
741 }
742
743 fn evaluate(&self, variables: &[f64]) -> Vec<f64> {
744 let f1 = variables[0];
745 let g =
746 1.0 + 9.0 * variables[1..].iter().sum::<f64>() / (self.num_variables - 1) as f64;
747 let h = 1.0 - (f1 / g).sqrt();
748 let f2 = g * h;
749
750 vec![f1, f2]
751 }
752
753 fn variable_bounds(&self) -> Vec<(f64, f64)> {
754 vec![(0.0, 1.0); self.num_variables]
755 }
756
757 fn ideal_point(&self) -> Option<Vec<f64>> {
758 Some(vec![0.0, 0.0])
759 }
760 }
761
762 #[test]
763 fn test_individual_dominance() {
764 let mut ind1 = Individual::new(vec![1.0, 2.0]);
765 ind1.objectives = vec![0.5, 0.3];
766
767 let mut ind2 = Individual::new(vec![1.5, 1.8]);
768 ind2.objectives = vec![0.7, 0.4];
769
770 assert!(ind1.dominates(&ind2));
772 assert!(!ind2.dominates(&ind1));
773 }
774
775 #[test]
776 fn test_pareto_front_operations() {
777 let mut front = ParetoFront::new();
778
779 let mut sol1 = Individual::new(vec![1.0]);
780 sol1.objectives = vec![0.2, 0.8];
781
782 let mut sol2 = Individual::new(vec![2.0]);
783 sol2.objectives = vec![0.8, 0.2];
784
785 let mut sol3 = Individual::new(vec![3.0]);
786 sol3.objectives = vec![0.9, 0.9]; assert!(front.add_if_non_dominated(sol1));
789 assert!(front.add_if_non_dominated(sol2));
790 assert!(!front.add_if_non_dominated(sol3)); assert_eq!(front.solutions.len(), 2);
793 }
794
795 #[test]
796 fn test_hypervolume_2d() {
797 let mut front = ParetoFront::new();
798
799 let mut sol1 = Individual::new(vec![1.0]);
800 sol1.objectives = vec![0.2, 0.8];
801
802 let mut sol2 = Individual::new(vec![2.0]);
803 sol2.objectives = vec![0.8, 0.2];
804
805 front.solutions = vec![sol1, sol2];
806
807 let reference_point = vec![1.0, 1.0];
808 let hypervolume = front.calculate_hypervolume(&reference_point);
809
810 assert!(hypervolume > 0.0);
812 }
813
814 #[test]
815 fn test_nsga2_basic_functionality() {
816 let zdt1 = ZDT1 { num_variables: 3 };
817 let config = MultiObjectiveConfig {
818 population_size: 20,
819 max_generations: 10,
820 crossover_probability: 0.9,
821 mutation_probability: 0.1,
822 mutation_strength: 0.1,
823 elite_ratio: 0.1,
824 convergence_tolerance: 1e-6,
825 reference_point: Some(vec![1.0, 1.0]),
826 preserve_diversity: true,
827 };
828
829 let optimizer = NsgaII::new(config);
830
831 use crate::phantom::{Euclidean, NonConvex, Unconstrained};
833 let problem: OptimizationProblem<3, Unconstrained, MultiObjective, NonConvex, Euclidean> =
834 OptimizationProblem::new();
835
836 let result = optimizer.optimize(&problem, &zdt1);
837
838 assert!(result.is_ok());
839 let solution = result.unwrap();
840
841 assert!(!solution.pareto_front.solutions.is_empty());
843 assert!(solution.generations <= 10);
844 }
845
846 #[test]
847 fn test_non_dominated_sorting() {
848 let optimizer = NsgaII::<f64>::with_default_config();
849
850 let mut pop = Vec::new();
851
852 let mut ind1 = Individual::new(vec![1.0]);
854 ind1.objectives = vec![0.1, 0.9];
855 pop.push(ind1);
856
857 let mut ind2 = Individual::new(vec![2.0]);
858 ind2.objectives = vec![0.9, 0.1];
859 pop.push(ind2);
860
861 let mut ind3 = Individual::new(vec![3.0]);
863 ind3.objectives = vec![0.2, 0.95]; pop.push(ind3);
865
866 let fronts = optimizer.non_dominated_sort(&pop);
867
868 assert_eq!(fronts.len(), 2);
869 assert_eq!(fronts[0].len(), 2); assert_eq!(fronts[1].len(), 1); }
872
873 #[test]
874 fn test_crowding_distance_calculation() {
875 let optimizer = NsgaII::<f64>::with_default_config();
876
877 let mut front = vec![
878 {
879 let mut ind = Individual::new(vec![1.0]);
880 ind.objectives = vec![0.1, 0.9];
881 ind
882 },
883 {
884 let mut ind = Individual::new(vec![2.0]);
885 ind.objectives = vec![0.5, 0.5];
886 ind
887 },
888 {
889 let mut ind = Individual::new(vec![3.0]);
890 ind.objectives = vec![0.9, 0.1];
891 ind
892 },
893 ];
894
895 optimizer.calculate_crowding_distance(&mut front);
896
897 assert!(
899 front[0].crowding_distance.is_infinite() || front[2].crowding_distance.is_infinite()
900 );
901
902 assert!(front[1].crowding_distance.is_finite() && front[1].crowding_distance > 0.0);
904 }
905}