1use super::solutions::MultiObjectiveSolution;
17use std::cmp::Ordering;
18
19pub fn dominates(a: &[f64], b: &[f64]) -> bool {
23 debug_assert_eq!(a.len(), b.len(), "Objective vectors must have equal length");
24
25 let mut at_least_one_strict = false;
26 for (ai, bi) in a.iter().zip(b.iter()) {
27 if ai > bi {
28 return false;
29 }
30 if ai < bi {
31 at_least_one_strict = true;
32 }
33 }
34 at_least_one_strict
35}
36
37pub fn solution_dominates(a: &MultiObjectiveSolution, b: &MultiObjectiveSolution) -> bool {
41 a.dominates(b)
42}
43
44pub fn fast_non_dominated_sort(objectives: &[Vec<f64>]) -> Vec<Vec<usize>> {
53 let n = objectives.len();
54 if n == 0 {
55 return vec![];
56 }
57
58 let mut domination_counts = vec![0usize; n];
59 let mut dominated_sets: Vec<Vec<usize>> = vec![Vec::new(); n];
60
61 for i in 0..n {
63 for j in (i + 1)..n {
64 if dominates(&objectives[i], &objectives[j]) {
65 dominated_sets[i].push(j);
66 domination_counts[j] += 1;
67 } else if dominates(&objectives[j], &objectives[i]) {
68 dominated_sets[j].push(i);
69 domination_counts[i] += 1;
70 }
71 }
72 }
73
74 let mut fronts: Vec<Vec<usize>> = Vec::new();
76 let mut current_front: Vec<usize> = Vec::new();
77
78 for i in 0..n {
80 if domination_counts[i] == 0 {
81 current_front.push(i);
82 }
83 }
84
85 while !current_front.is_empty() {
86 let mut next_front = Vec::new();
87 for &i in ¤t_front {
88 for &j in &dominated_sets[i] {
89 domination_counts[j] -= 1;
90 if domination_counts[j] == 0 {
91 next_front.push(j);
92 }
93 }
94 }
95 fronts.push(current_front);
96 current_front = next_front;
97 }
98
99 fronts
100}
101
102pub fn non_dominated_sort_solutions(solutions: &mut [MultiObjectiveSolution]) -> Vec<Vec<usize>> {
106 let objectives: Vec<Vec<f64>> = solutions.iter().map(|s| s.objectives.to_vec()).collect();
107
108 let fronts = fast_non_dominated_sort(&objectives);
109
110 for (rank, front) in fronts.iter().enumerate() {
112 for &idx in front {
113 solutions[idx].rank = rank;
114 }
115 }
116
117 fronts
118}
119
120pub fn crowding_distance(
128 solutions: &[MultiObjectiveSolution],
129 front_indices: &[usize],
130) -> Vec<f64> {
131 let n = front_indices.len();
132 if n == 0 {
133 return vec![];
134 }
135 if n <= 2 {
136 return vec![f64::INFINITY; n];
137 }
138
139 let n_objectives = solutions[front_indices[0]].objectives.len();
140 let mut distances = vec![0.0_f64; n];
141
142 for obj in 0..n_objectives {
144 let mut sorted: Vec<(usize, f64)> = (0..n)
146 .map(|local_idx| {
147 let global_idx = front_indices[local_idx];
148 (local_idx, solutions[global_idx].objectives[obj])
149 })
150 .collect();
151
152 sorted.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
153
154 let obj_min = sorted[0].1;
155 let obj_max = sorted[n - 1].1;
156 let obj_range = obj_max - obj_min;
157
158 distances[sorted[0].0] = f64::INFINITY;
160 distances[sorted[n - 1].0] = f64::INFINITY;
161
162 if obj_range > 0.0 {
163 for i in 1..n - 1 {
164 let local_idx = sorted[i].0;
165 if distances[local_idx].is_finite() {
166 let neighbor_dist = (sorted[i + 1].1 - sorted[i - 1].1) / obj_range;
167 distances[local_idx] += neighbor_dist;
168 }
169 }
170 }
171 }
172
173 distances
174}
175
176pub fn assign_crowding_distances(
178 solutions: &mut [MultiObjectiveSolution],
179 front_indices: &[usize],
180) {
181 let distances = crowding_distance(solutions, front_indices);
182 for (local_idx, &global_idx) in front_indices.iter().enumerate() {
183 solutions[global_idx].crowding_distance = distances[local_idx];
184 }
185}
186
187pub fn pareto_front(solutions: &[MultiObjectiveSolution]) -> Vec<MultiObjectiveSolution> {
191 if solutions.is_empty() {
192 return vec![];
193 }
194
195 let mut front: Vec<MultiObjectiveSolution> = Vec::new();
196
197 for candidate in solutions {
198 let mut is_dominated = false;
199
200 for existing in &front {
202 if existing.dominates(candidate) {
203 is_dominated = true;
204 break;
205 }
206 }
207
208 if !is_dominated {
209 front.retain(|existing| !candidate.dominates(existing));
211 front.push(candidate.clone());
212 }
213 }
214
215 front
216}
217
218pub fn hypervolume(solutions: &[MultiObjectiveSolution], reference_point: &[f64]) -> f64 {
226 if solutions.is_empty() {
227 return 0.0;
228 }
229
230 let n_objectives = reference_point.len();
231
232 let valid_solutions: Vec<&MultiObjectiveSolution> = solutions
234 .iter()
235 .filter(|sol| {
236 sol.objectives
237 .iter()
238 .zip(reference_point.iter())
239 .all(|(&obj, &rp)| obj < rp)
240 })
241 .collect();
242
243 if valid_solutions.is_empty() {
244 return 0.0;
245 }
246
247 match n_objectives {
248 1 => hypervolume_1d(&valid_solutions, reference_point),
249 2 => hypervolume_2d(&valid_solutions, reference_point),
250 _ => hypervolume_wfg(&valid_solutions, reference_point),
251 }
252}
253
254fn hypervolume_1d(solutions: &[&MultiObjectiveSolution], reference_point: &[f64]) -> f64 {
256 let min_obj = solutions
257 .iter()
258 .map(|s| s.objectives[0])
259 .fold(f64::INFINITY, f64::min);
260
261 reference_point[0] - min_obj
262}
263
264fn hypervolume_2d(solutions: &[&MultiObjectiveSolution], reference_point: &[f64]) -> f64 {
269 let mut points: Vec<(f64, f64)> = solutions
270 .iter()
271 .map(|s| (s.objectives[0], s.objectives[1]))
272 .collect();
273
274 points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
276
277 let mut staircase: Vec<(f64, f64)> = Vec::new();
283 let mut min_y_so_far = f64::INFINITY;
284 for &(x, y) in &points {
285 if y < min_y_so_far {
286 staircase.push((x, y));
287 min_y_so_far = y;
288 }
289 }
290
291 if staircase.is_empty() {
298 return 0.0;
299 }
300
301 let mut volume = 0.0;
305 for i in 0..staircase.len() {
306 let (x, y) = staircase[i];
307 let x_next = if i + 1 < staircase.len() {
308 staircase[i + 1].0
309 } else {
310 reference_point[0]
311 };
312 volume += (x_next - x) * (reference_point[1] - y);
313 }
314
315 volume
316}
317
318fn hypervolume_wfg(solutions: &[&MultiObjectiveSolution], reference_point: &[f64]) -> f64 {
323 let n_objectives = reference_point.len();
324
325 let points: Vec<Vec<f64>> = solutions.iter().map(|s| s.objectives.to_vec()).collect();
327
328 wfg_hv_recursive(&points, reference_point, n_objectives)
329}
330
331fn wfg_hv_recursive(points: &[Vec<f64>], reference_point: &[f64], dim: usize) -> f64 {
343 if points.is_empty() {
344 return 0.0;
345 }
346
347 if dim == 1 {
348 let min_val = points.iter().map(|p| p[0]).fold(f64::INFINITY, f64::min);
350 return reference_point[0] - min_val;
351 }
352
353 if dim == 2 {
354 let mut pts: Vec<(f64, f64)> = points.iter().map(|p| (p[0], p[1])).collect();
358 pts.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
359
360 let mut staircase: Vec<(f64, f64)> = Vec::new();
362 let mut min_y = f64::INFINITY;
363 for &(x, y) in &pts {
364 if y < min_y {
365 staircase.push((x, y));
366 min_y = y;
367 }
368 }
369
370 let mut vol = 0.0;
371 for i in 0..staircase.len() {
372 let x_next = if i + 1 < staircase.len() {
373 staircase[i + 1].0
374 } else {
375 reference_point[0]
376 };
377 vol += (x_next - staircase[i].0) * (reference_point[1] - staircase[i].1);
378 }
379 return vol;
380 }
381
382 let last_dim = dim - 1;
399 let mut sorted_points = points.to_vec();
400 sorted_points.sort_by(|a, b| {
401 a[last_dim]
402 .partial_cmp(&b[last_dim])
403 .unwrap_or(Ordering::Equal)
404 });
405
406 let mut volume = 0.0;
407 let mut contributing: Vec<Vec<f64>> = Vec::new();
410
411 for (idx, point) in sorted_points.iter().enumerate() {
412 let new_proj: Vec<f64> = point[..last_dim].to_vec();
414 let mut dominated_by_existing = false;
415 for existing in &contributing {
416 let ex_proj: Vec<f64> = existing[..last_dim].to_vec();
417 if dominates(&ex_proj, &new_proj) {
418 dominated_by_existing = true;
419 break;
420 }
421 }
422 if !dominated_by_existing {
423 contributing.retain(|existing| {
424 let ex_proj: Vec<f64> = existing[..last_dim].to_vec();
425 !dominates(&new_proj, &ex_proj)
426 });
427 contributing.push(point.clone());
428 }
429
430 let z_current = point[last_dim];
432 let z_next = if idx + 1 < sorted_points.len() {
433 sorted_points[idx + 1][last_dim]
434 } else {
435 reference_point[last_dim]
436 };
437 let slab_height = z_next - z_current;
438
439 if slab_height > 0.0 {
441 let projected: Vec<Vec<f64>> = contributing
442 .iter()
443 .map(|p| p[..last_dim].to_vec())
444 .collect();
445 let ref_projected = &reference_point[..last_dim];
446 let slice_hv = wfg_hv_recursive(&projected, ref_projected, last_dim);
447 volume += slice_hv * slab_height;
448 }
449 }
450
451 volume
452}
453
454pub fn hypervolume_from_objectives(objectives: &[Vec<f64>], reference_point: &[f64]) -> f64 {
458 use scirs2_core::ndarray::Array1;
459
460 let solutions: Vec<MultiObjectiveSolution> = objectives
461 .iter()
462 .map(|objs| {
463 MultiObjectiveSolution::new(
464 Array1::zeros(1), Array1::from_vec(objs.clone()),
466 )
467 })
468 .collect();
469
470 hypervolume(&solutions, reference_point)
471}
472
473pub fn crowded_comparison(a: &MultiObjectiveSolution, b: &MultiObjectiveSolution) -> Ordering {
478 match a.rank.cmp(&b.rank) {
479 Ordering::Less => Ordering::Less, Ordering::Greater => Ordering::Greater, Ordering::Equal => {
482 b.crowding_distance
484 .partial_cmp(&a.crowding_distance)
485 .unwrap_or(Ordering::Equal)
486 }
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use scirs2_core::ndarray::array;
494
495 #[test]
498 fn test_dominates_strict() {
499 assert!(dominates(&[1.0, 2.0], &[2.0, 3.0]));
501 }
502
503 #[test]
504 fn test_dominates_weak_one_equal() {
505 assert!(dominates(&[1.0, 2.0], &[1.0, 3.0]));
507 }
508
509 #[test]
510 fn test_dominates_equal_returns_false() {
511 assert!(!dominates(&[1.0, 2.0], &[1.0, 2.0]));
513 }
514
515 #[test]
516 fn test_dominates_trade_off_returns_false() {
517 assert!(!dominates(&[1.0, 3.0], &[2.0, 2.0]));
519 assert!(!dominates(&[2.0, 2.0], &[1.0, 3.0]));
520 }
521
522 #[test]
523 fn test_dominates_three_objectives() {
524 assert!(dominates(&[1.0, 2.0, 3.0], &[2.0, 3.0, 4.0]));
525 assert!(!dominates(&[1.0, 2.0, 5.0], &[2.0, 3.0, 4.0]));
526 }
527
528 #[test]
529 fn test_solution_dominates_with_constraints() {
530 let feasible =
531 MultiObjectiveSolution::new_with_constraints(array![1.0], array![5.0, 5.0], 0.0);
532 let infeasible =
533 MultiObjectiveSolution::new_with_constraints(array![1.0], array![1.0, 1.0], 1.0);
534 assert!(solution_dominates(&feasible, &infeasible));
536 assert!(!solution_dominates(&infeasible, &feasible));
537 }
538
539 #[test]
542 fn test_fast_non_dominated_sort_single_front() {
543 let objectives = vec![vec![1.0, 3.0], vec![2.0, 2.0], vec![3.0, 1.0]];
544 let fronts = fast_non_dominated_sort(&objectives);
545 assert_eq!(fronts.len(), 1);
546 assert_eq!(fronts[0].len(), 3);
547 }
548
549 #[test]
550 fn test_fast_non_dominated_sort_two_fronts() {
551 let objectives = vec![
552 vec![1.0, 3.0], vec![3.0, 1.0], vec![2.0, 2.0], vec![3.0, 3.0], ];
557 let fronts = fast_non_dominated_sort(&objectives);
558 assert_eq!(fronts.len(), 2);
559 assert_eq!(fronts[0].len(), 3);
560 assert_eq!(fronts[1].len(), 1);
561 assert!(fronts[1].contains(&3));
562 }
563
564 #[test]
565 fn test_fast_non_dominated_sort_three_fronts() {
566 let objectives = vec![
567 vec![1.0, 4.0], vec![4.0, 1.0], vec![2.0, 3.0], vec![3.0, 3.0], vec![4.0, 4.0], ];
573 let fronts = fast_non_dominated_sort(&objectives);
577 assert_eq!(fronts.len(), 3);
578 assert_eq!(fronts[0].len(), 3); assert_eq!(fronts[1].len(), 1); assert_eq!(fronts[2].len(), 1); }
582
583 #[test]
584 fn test_fast_non_dominated_sort_empty() {
585 let objectives: Vec<Vec<f64>> = vec![];
586 let fronts = fast_non_dominated_sort(&objectives);
587 assert!(fronts.is_empty());
588 }
589
590 #[test]
591 fn test_non_dominated_sort_solutions_assigns_ranks() {
592 let mut solutions = vec![
593 MultiObjectiveSolution::new(array![1.0], array![1.0, 3.0]),
594 MultiObjectiveSolution::new(array![2.0], array![3.0, 1.0]),
595 MultiObjectiveSolution::new(array![3.0], array![4.0, 4.0]),
596 ];
597 let fronts = non_dominated_sort_solutions(&mut solutions);
598 assert_eq!(fronts.len(), 2);
599 assert_eq!(solutions[0].rank, 0);
600 assert_eq!(solutions[1].rank, 0);
601 assert_eq!(solutions[2].rank, 1);
602 }
603
604 #[test]
607 fn test_crowding_distance_boundary_infinite() {
608 let solutions = vec![
609 MultiObjectiveSolution::new(array![1.0], array![1.0, 5.0]),
610 MultiObjectiveSolution::new(array![2.0], array![3.0, 3.0]),
611 MultiObjectiveSolution::new(array![3.0], array![5.0, 1.0]),
612 ];
613 let front = vec![0, 1, 2];
614 let distances = crowding_distance(&solutions, &front);
615
616 assert_eq!(distances[0], f64::INFINITY);
618 assert_eq!(distances[2], f64::INFINITY);
619 assert!(distances[1].is_finite());
621 assert!(distances[1] > 0.0);
622 }
623
624 #[test]
625 fn test_crowding_distance_two_solutions() {
626 let solutions = vec![
627 MultiObjectiveSolution::new(array![1.0], array![1.0, 3.0]),
628 MultiObjectiveSolution::new(array![2.0], array![3.0, 1.0]),
629 ];
630 let front = vec![0, 1];
631 let distances = crowding_distance(&solutions, &front);
632 assert_eq!(distances[0], f64::INFINITY);
633 assert_eq!(distances[1], f64::INFINITY);
634 }
635
636 #[test]
637 fn test_crowding_distance_uniform_spacing() {
638 let solutions = vec![
640 MultiObjectiveSolution::new(array![0.0], array![0.0, 4.0]),
641 MultiObjectiveSolution::new(array![1.0], array![1.0, 3.0]),
642 MultiObjectiveSolution::new(array![2.0], array![2.0, 2.0]),
643 MultiObjectiveSolution::new(array![3.0], array![3.0, 1.0]),
644 MultiObjectiveSolution::new(array![4.0], array![4.0, 0.0]),
645 ];
646 let front = vec![0, 1, 2, 3, 4];
647 let distances = crowding_distance(&solutions, &front);
648
649 assert!(distances[0].is_infinite());
651 assert!(distances[4].is_infinite());
652 let d1 = distances[1];
653 let d2 = distances[2];
654 let d3 = distances[3];
655 assert!((d1 - d2).abs() < 1e-10);
656 assert!((d2 - d3).abs() < 1e-10);
657 }
658
659 #[test]
660 fn test_crowding_distance_empty() {
661 let solutions: Vec<MultiObjectiveSolution> = vec![];
662 let front: Vec<usize> = vec![];
663 let distances = crowding_distance(&solutions, &front);
664 assert!(distances.is_empty());
665 }
666
667 #[test]
668 fn test_assign_crowding_distances() {
669 let mut solutions = vec![
670 MultiObjectiveSolution::new(array![1.0], array![1.0, 5.0]),
671 MultiObjectiveSolution::new(array![2.0], array![3.0, 3.0]),
672 MultiObjectiveSolution::new(array![3.0], array![5.0, 1.0]),
673 ];
674 let front = vec![0, 1, 2];
675 assign_crowding_distances(&mut solutions, &front);
676
677 assert_eq!(solutions[0].crowding_distance, f64::INFINITY);
678 assert_eq!(solutions[2].crowding_distance, f64::INFINITY);
679 assert!(solutions[1].crowding_distance.is_finite());
680 }
681
682 #[test]
685 fn test_pareto_front_extraction() {
686 let solutions = vec![
687 MultiObjectiveSolution::new(array![1.0], array![1.0, 3.0]),
688 MultiObjectiveSolution::new(array![2.0], array![2.0, 2.0]),
689 MultiObjectiveSolution::new(array![3.0], array![3.0, 1.0]),
690 MultiObjectiveSolution::new(array![4.0], array![2.5, 2.5]), ];
692 let front = pareto_front(&solutions);
693 assert_eq!(front.len(), 3);
694 }
695
696 #[test]
697 fn test_pareto_front_all_non_dominated() {
698 let solutions = vec![
699 MultiObjectiveSolution::new(array![1.0], array![1.0, 5.0]),
700 MultiObjectiveSolution::new(array![2.0], array![3.0, 3.0]),
701 MultiObjectiveSolution::new(array![3.0], array![5.0, 1.0]),
702 ];
703 let front = pareto_front(&solutions);
704 assert_eq!(front.len(), 3);
705 }
706
707 #[test]
708 fn test_pareto_front_single_dominant() {
709 let solutions = vec![
710 MultiObjectiveSolution::new(array![1.0], array![1.0, 1.0]),
711 MultiObjectiveSolution::new(array![2.0], array![2.0, 2.0]),
712 MultiObjectiveSolution::new(array![3.0], array![3.0, 3.0]),
713 ];
714 let front = pareto_front(&solutions);
715 assert_eq!(front.len(), 1);
716 assert_eq!(front[0].objectives[0], 1.0);
717 }
718
719 #[test]
720 fn test_pareto_front_empty() {
721 let solutions: Vec<MultiObjectiveSolution> = vec![];
722 let front = pareto_front(&solutions);
723 assert!(front.is_empty());
724 }
725
726 #[test]
727 fn test_pareto_front_three_objectives() {
728 let solutions = vec![
729 MultiObjectiveSolution::new(array![1.0], array![1.0, 5.0, 5.0]),
730 MultiObjectiveSolution::new(array![2.0], array![5.0, 1.0, 5.0]),
731 MultiObjectiveSolution::new(array![3.0], array![5.0, 5.0, 1.0]),
732 MultiObjectiveSolution::new(array![4.0], array![3.0, 3.0, 3.0]), MultiObjectiveSolution::new(array![5.0], array![6.0, 6.0, 6.0]), ];
735 let front = pareto_front(&solutions);
736 assert_eq!(front.len(), 4); }
738
739 #[test]
742 fn test_hypervolume_2d_simple() {
743 let solutions = vec![MultiObjectiveSolution::new(array![0.0], array![1.0, 1.0])];
745 let hv = hypervolume(&solutions, &[2.0, 2.0]);
746 assert!((hv - 1.0).abs() < 1e-10, "Expected 1.0, got {}", hv);
747 }
748
749 #[test]
750 fn test_hypervolume_2d_two_points() {
751 let solutions = vec![
755 MultiObjectiveSolution::new(array![0.0], array![1.0, 3.0]),
756 MultiObjectiveSolution::new(array![0.0], array![3.0, 1.0]),
757 ];
758 let hv = hypervolume(&solutions, &[4.0, 4.0]);
759 assert!((hv - 5.0).abs() < 1e-10, "Expected 5.0, got {}", hv);
760 }
761
762 #[test]
763 fn test_hypervolume_2d_three_points() {
764 let solutions = vec![
767 MultiObjectiveSolution::new(array![0.0], array![1.0, 3.0]),
768 MultiObjectiveSolution::new(array![0.0], array![2.0, 2.0]),
769 MultiObjectiveSolution::new(array![0.0], array![3.0, 1.0]),
770 ];
771 let hv = hypervolume(&solutions, &[4.0, 4.0]);
772 assert!((hv - 6.0).abs() < 1e-10, "Expected 6.0, got {}", hv);
773 }
774
775 #[test]
776 fn test_hypervolume_empty() {
777 let solutions: Vec<MultiObjectiveSolution> = vec![];
778 let hv = hypervolume(&solutions, &[4.0, 4.0]);
779 assert_eq!(hv, 0.0);
780 }
781
782 #[test]
783 fn test_hypervolume_point_at_reference_boundary() {
784 let solutions = vec![MultiObjectiveSolution::new(array![0.0], array![4.0, 4.0])];
786 let hv = hypervolume(&solutions, &[4.0, 4.0]);
787 assert_eq!(hv, 0.0);
788 }
789
790 #[test]
791 fn test_hypervolume_1d() {
792 let solutions = vec![
793 MultiObjectiveSolution::new(array![0.0], array![2.0]),
794 MultiObjectiveSolution::new(array![0.0], array![3.0]),
795 ];
796 let hv = hypervolume(&solutions, &[5.0]);
797 assert!((hv - 3.0).abs() < 1e-10);
799 }
800
801 #[test]
802 fn test_hypervolume_3d() {
803 let solutions = vec![MultiObjectiveSolution::new(
805 array![0.0],
806 array![1.0, 1.0, 1.0],
807 )];
808 let hv = hypervolume(&solutions, &[2.0, 2.0, 2.0]);
809 assert!((hv - 1.0).abs() < 1e-10, "Expected 1.0, got {}", hv);
810 }
811
812 #[test]
813 fn test_hypervolume_from_objectives_convenience() {
814 let objectives = vec![vec![1.0, 3.0], vec![3.0, 1.0]];
815 let hv = hypervolume_from_objectives(&objectives, &[4.0, 4.0]);
816 assert!((hv - 5.0).abs() < 1e-10);
817 }
818
819 #[test]
822 fn test_crowded_comparison_different_ranks() {
823 let mut a = MultiObjectiveSolution::new(array![1.0], array![1.0, 1.0]);
824 a.rank = 0;
825 a.crowding_distance = 1.0;
826
827 let mut b = MultiObjectiveSolution::new(array![2.0], array![2.0, 2.0]);
828 b.rank = 1;
829 b.crowding_distance = 5.0;
830
831 assert_eq!(crowded_comparison(&a, &b), Ordering::Less);
833 }
834
835 #[test]
836 fn test_crowded_comparison_same_rank_different_distance() {
837 let mut a = MultiObjectiveSolution::new(array![1.0], array![1.0, 1.0]);
838 a.rank = 0;
839 a.crowding_distance = 5.0;
840
841 let mut b = MultiObjectiveSolution::new(array![2.0], array![2.0, 2.0]);
842 b.rank = 0;
843 b.crowding_distance = 1.0;
844
845 assert_eq!(crowded_comparison(&a, &b), Ordering::Less);
847 }
848
849 #[test]
850 fn test_crowded_comparison_equal() {
851 let mut a = MultiObjectiveSolution::new(array![1.0], array![1.0, 1.0]);
852 a.rank = 0;
853 a.crowding_distance = 3.0;
854
855 let mut b = MultiObjectiveSolution::new(array![2.0], array![2.0, 2.0]);
856 b.rank = 0;
857 b.crowding_distance = 3.0;
858
859 assert_eq!(crowded_comparison(&a, &b), Ordering::Equal);
860 }
861}