1use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
9use scirs2_core::random::Rng;
10use sklears_core::{
11 error::{Result as SklResult, SklearsError},
12 types::Float,
13};
14use std::cmp::Ordering;
15
16#[derive(Debug, Clone)]
18pub struct Individual {
19 pub variables: Array1<Float>,
21 pub objectives: Array1<Float>,
23 pub rank: usize,
25 pub crowding_distance: Float,
27}
28
29impl Individual {
30 pub fn new(variables: Array1<Float>) -> Self {
32 Self {
33 variables,
34 objectives: Array1::zeros(0), rank: 0,
36 crowding_distance: 0.0,
37 }
38 }
39
40 pub fn dominates(&self, other: &Individual) -> bool {
42 let mut at_least_one_better = false;
43
44 for i in 0..self.objectives.len() {
45 if self.objectives[i] > other.objectives[i] {
46 return false; }
48 if self.objectives[i] < other.objectives[i] {
49 at_least_one_better = true;
50 }
51 }
52
53 at_least_one_better
54 }
55}
56
57#[derive(Debug, Clone)]
86pub struct NSGAII {
87 population_size: usize,
89 n_generations: usize,
91 crossover_probability: Float,
93 mutation_probability: Float,
95 variable_bounds: Vec<(Float, Float)>,
97 random_state: Option<u64>,
99 crossover_eta: Float,
101 mutation_eta: Float,
103}
104
105impl Default for NSGAII {
106 fn default() -> Self {
107 Self {
108 population_size: 100,
109 n_generations: 100,
110 crossover_probability: 0.9,
111 mutation_probability: 0.1,
112 variable_bounds: Vec::new(),
113 random_state: None,
114 crossover_eta: 20.0,
115 mutation_eta: 20.0,
116 }
117 }
118}
119
120impl NSGAII {
121 pub fn new() -> Self {
123 Self::default()
124 }
125
126 pub fn population_size(mut self, size: usize) -> Self {
128 self.population_size = size;
129 self
130 }
131
132 pub fn n_generations(mut self, generations: usize) -> Self {
134 self.n_generations = generations;
135 self
136 }
137
138 pub fn crossover_probability(mut self, prob: Float) -> Self {
140 self.crossover_probability = prob;
141 self
142 }
143
144 pub fn mutation_probability(mut self, prob: Float) -> Self {
146 self.mutation_probability = prob;
147 self
148 }
149
150 pub fn variable_bounds(mut self, bounds: Vec<(Float, Float)>) -> Self {
152 self.variable_bounds = bounds;
153 self
154 }
155
156 pub fn random_state(mut self, seed: u64) -> Self {
158 self.random_state = Some(seed);
159 self
160 }
161
162 pub fn crossover_eta(mut self, eta: Float) -> Self {
164 self.crossover_eta = eta;
165 self
166 }
167
168 pub fn mutation_eta(mut self, eta: Float) -> Self {
170 self.mutation_eta = eta;
171 self
172 }
173
174 pub fn optimize<F>(&self, objective_fn: F, n_objectives: usize) -> SklResult<OptimizationResult>
176 where
177 F: Fn(&ArrayView1<Float>) -> Array1<Float>,
178 {
179 if self.variable_bounds.is_empty() {
180 return Err(SklearsError::InvalidInput(
181 "Variable bounds must be specified".to_string(),
182 ));
183 }
184
185 let n_variables = self.variable_bounds.len();
186 let mut rng = if let Some(seed) = self.random_state {
187 scirs2_core::random::seeded_rng(seed)
188 } else {
189 scirs2_core::random::seeded_rng(42)
190 };
191
192 let mut population = self.initialize_population(&mut rng, n_variables)?;
194
195 for individual in &mut population {
197 individual.objectives = objective_fn(&individual.variables.view());
198 }
199
200 let mut generation_stats = Vec::new();
201
202 for _generation in 0..self.n_generations {
204 let offspring = self.create_offspring(&population, &mut rng, n_variables)?;
206
207 let mut combined_population = population;
209 combined_population.extend(offspring);
210
211 for individual in &mut combined_population {
213 if individual.objectives.len() != n_objectives {
214 individual.objectives = objective_fn(&individual.variables.view());
215 }
216 }
217
218 let fronts = self.non_dominated_sort(&combined_population);
220
221 population = self.environmental_selection(combined_population, fronts)?;
223
224 let stats = self.calculate_generation_stats(&population);
226 generation_stats.push(stats);
227
228 }
230
231 let fronts = self.non_dominated_sort(&population);
233 let pareto_front = if fronts.is_empty() {
234 Vec::new()
235 } else {
236 fronts[0].clone()
237 };
238
239 Ok(OptimizationResult {
240 pareto_front,
241 final_population: population,
242 generation_stats,
243 n_generations: self.n_generations,
244 })
245 }
246
247 fn initialize_population<R: Rng>(
249 &self,
250 rng: &mut scirs2_core::random::CoreRandom<R>,
251 n_variables: usize,
252 ) -> SklResult<Vec<Individual>> {
253 let mut population = Vec::with_capacity(self.population_size);
254
255 for _ in 0..self.population_size {
256 let mut variables = Array1::zeros(n_variables);
257
258 for j in 0..n_variables {
259 let (min_val, max_val) = self.variable_bounds[j];
260 variables[j] = rng.gen_range(min_val..max_val + 1.0);
261 }
262
263 population.push(Individual::new(variables));
264 }
265
266 Ok(population)
267 }
268
269 fn create_offspring<R: Rng>(
271 &self,
272 population: &[Individual],
273 rng: &mut scirs2_core::random::CoreRandom<R>,
274 _n_variables: usize,
275 ) -> SklResult<Vec<Individual>> {
276 let mut offspring = Vec::with_capacity(self.population_size);
277
278 for _ in 0..self.population_size {
279 let parent1 = self.tournament_selection(population, rng);
281 let parent2 = self.tournament_selection(population, rng);
282
283 let mut child = if rng.random::<Float>() < self.crossover_probability {
285 self.sbx_crossover(parent1, parent2, rng)?
286 } else {
287 parent1.clone()
288 };
289
290 if rng.random::<Float>() < self.mutation_probability {
292 self.polynomial_mutation(&mut child, rng);
293 }
294
295 offspring.push(child);
296 }
297
298 Ok(offspring)
299 }
300
301 fn tournament_selection<'a, R: Rng>(
303 &self,
304 population: &'a [Individual],
305 rng: &mut scirs2_core::random::CoreRandom<R>,
306 ) -> &'a Individual {
307 let tournament_size = 2;
308 let mut best = &population[rng.gen_range(0..population.len())];
309
310 for _ in 1..tournament_size {
311 let candidate = &population[rng.gen_range(0..population.len())];
312 if self.compare_individuals(candidate, best) == Ordering::Less {
313 best = candidate;
314 }
315 }
316
317 best
318 }
319
320 fn compare_individuals(&self, a: &Individual, b: &Individual) -> Ordering {
322 match a.rank.cmp(&b.rank) {
324 Ordering::Equal => {
325 b.crowding_distance
327 .partial_cmp(&a.crowding_distance)
328 .unwrap_or(Ordering::Equal)
329 }
330 other => other,
331 }
332 }
333
334 fn sbx_crossover<R: Rng>(
336 &self,
337 parent1: &Individual,
338 parent2: &Individual,
339 rng: &mut scirs2_core::random::CoreRandom<R>,
340 ) -> SklResult<Individual> {
341 let n_variables = parent1.variables.len();
342 let mut child_variables = Array1::zeros(n_variables);
343
344 for i in 0..n_variables {
345 let p1 = parent1.variables[i];
346 let p2 = parent2.variables[i];
347 let (min_val, max_val) = self.variable_bounds[i];
348
349 if (p1 - p2).abs() > 1e-14 {
350 let u = rng.random::<Float>();
351 let beta = if u <= 0.5 {
352 (2.0 * u).powf(1.0 / (self.crossover_eta + 1.0))
353 } else {
354 (1.0 / (2.0 * (1.0 - u))).powf(1.0 / (self.crossover_eta + 1.0))
355 };
356
357 let c1 = 0.5 * (p1 + p2 - beta * (p1 - p2).abs());
358 child_variables[i] = c1.clamp(min_val, max_val);
359 } else {
360 child_variables[i] = p1;
361 }
362 }
363
364 Ok(Individual::new(child_variables))
365 }
366
367 fn polynomial_mutation<R: Rng>(
369 &self,
370 individual: &mut Individual,
371 rng: &mut scirs2_core::random::CoreRandom<R>,
372 ) {
373 for i in 0..individual.variables.len() {
374 if rng.random::<Float>() < (1.0 / individual.variables.len() as Float) {
375 let (min_val, max_val) = self.variable_bounds[i];
376 let x = individual.variables[i];
377 let u = rng.random::<Float>();
378
379 let delta = if u <= 0.5 {
380 let bl = (x - min_val) / (max_val - min_val);
381 let b = 2.0 * u + (1.0 - 2.0 * u) * (1.0 - bl).powf(self.mutation_eta + 1.0);
382 b.powf(1.0 / (self.mutation_eta + 1.0)) - 1.0
383 } else {
384 let bu = (max_val - x) / (max_val - min_val);
385 let b = 2.0 * (1.0 - u)
386 + 2.0 * (u - 0.5) * (1.0 - bu).powf(self.mutation_eta + 1.0);
387 1.0 - b.powf(1.0 / (self.mutation_eta + 1.0))
388 };
389
390 individual.variables[i] = (x + delta * (max_val - min_val)).clamp(min_val, max_val);
391 }
392 }
393 }
394
395 fn non_dominated_sort(&self, population: &[Individual]) -> Vec<Vec<Individual>> {
397 let mut fronts = Vec::new();
398 let mut domination_count = vec![0; population.len()];
399 let mut dominated_solutions = vec![Vec::new(); population.len()];
400
401 for i in 0..population.len() {
403 for j in 0..population.len() {
404 if population[i].dominates(&population[j]) {
405 dominated_solutions[i].push(j);
406 } else if population[j].dominates(&population[i]) {
407 domination_count[i] += 1;
408 }
409 }
410 }
411
412 let mut current_front = Vec::new();
414 for i in 0..population.len() {
415 if domination_count[i] == 0 {
416 current_front.push(population[i].clone());
417 }
418 }
419
420 while !current_front.is_empty() {
421 fronts.push(current_front.clone());
422 let mut next_front = Vec::new();
423
424 for individual in ¤t_front {
425 if let Some(ind_idx) = population.iter().position(|p| {
427 p.variables
428 .iter()
429 .zip(individual.variables.iter())
430 .all(|(a, b)| (a - b).abs() < 1e-10)
431 }) {
432 for &dominated_idx in &dominated_solutions[ind_idx] {
433 domination_count[dominated_idx] -= 1;
434 if domination_count[dominated_idx] == 0 {
435 next_front.push(population[dominated_idx].clone());
436 }
437 }
438 }
439 }
440
441 current_front = next_front;
442 }
443
444 fronts
445 }
446
447 fn environmental_selection(
449 &self,
450 _population: Vec<Individual>,
451 fronts: Vec<Vec<Individual>>,
452 ) -> SklResult<Vec<Individual>> {
453 let mut selected = Vec::new();
454
455 for front in &fronts {
456 if selected.len() + front.len() <= self.population_size {
457 selected.extend(front.clone());
459 } else {
460 let remaining = self.population_size - selected.len();
462 let mut front_with_distance = front.clone();
463
464 self.calculate_crowding_distance(&mut front_with_distance);
466
467 front_with_distance.sort_by(|a, b| {
469 b.crowding_distance
470 .partial_cmp(&a.crowding_distance)
471 .unwrap_or(Ordering::Equal)
472 });
473
474 selected.extend(front_with_distance.into_iter().take(remaining));
475 break;
476 }
477 }
478
479 for (rank, front) in fronts.iter().enumerate() {
481 for individual in &mut selected {
482 if front.iter().any(|f| {
483 f.variables
484 .iter()
485 .zip(individual.variables.iter())
486 .all(|(a, b)| (a - b).abs() < 1e-10)
487 }) {
488 individual.rank = rank;
489 }
490 }
491 }
492
493 Ok(selected)
494 }
495
496 fn calculate_crowding_distance(&self, front: &mut [Individual]) {
498 if front.len() <= 2 {
499 for individual in front {
501 individual.crowding_distance = Float::INFINITY;
502 }
503 return;
504 }
505
506 let n_objectives = front[0].objectives.len();
507
508 for individual in front.iter_mut() {
510 individual.crowding_distance = 0.0;
511 }
512
513 for obj_idx in 0..n_objectives {
515 front.sort_by(|a, b| {
517 a.objectives[obj_idx]
518 .partial_cmp(&b.objectives[obj_idx])
519 .unwrap_or(Ordering::Equal)
520 });
521
522 front[0].crowding_distance = Float::INFINITY;
524 front[front.len() - 1].crowding_distance = Float::INFINITY;
525
526 let max_obj = front[front.len() - 1].objectives[obj_idx];
528 let min_obj = front[0].objectives[obj_idx];
529
530 if max_obj - min_obj > 0.0 {
531 for i in 1..front.len() - 1 {
532 if front[i].crowding_distance != Float::INFINITY {
533 front[i].crowding_distance += (front[i + 1].objectives[obj_idx]
534 - front[i - 1].objectives[obj_idx])
535 / (max_obj - min_obj);
536 }
537 }
538 }
539 }
540 }
541
542 fn calculate_generation_stats(&self, population: &[Individual]) -> GenerationStats {
544 let mut hypervolume = 0.0;
545 let spacing = 0.0;
546 let pareto_front_size = population.iter().filter(|ind| ind.rank == 0).count();
547
548 let pareto_individuals: Vec<_> = population.iter().filter(|ind| ind.rank == 0).collect();
550 if !pareto_individuals.is_empty() {
551 let n_objectives = pareto_individuals[0].objectives.len();
552 let reference_point = Array1::from_elem(n_objectives, 10.0); for individual in &pareto_individuals {
555 let mut volume = 1.0;
556 for obj_idx in 0..n_objectives {
557 volume *= (reference_point[obj_idx] - individual.objectives[obj_idx]).max(0.0);
558 }
559 hypervolume += volume;
560 }
561 }
562
563 GenerationStats {
564 hypervolume,
565 spacing,
566 pareto_front_size,
567 }
568 }
569}
570
571#[derive(Debug, Clone)]
573pub struct OptimizationResult {
574 pub pareto_front: Vec<Individual>,
576 pub final_population: Vec<Individual>,
578 pub generation_stats: Vec<GenerationStats>,
580 pub n_generations: usize,
582}
583
584impl OptimizationResult {
585 pub fn pareto_front(&self) -> &[Individual] {
587 &self.pareto_front
588 }
589
590 pub fn final_population(&self) -> &[Individual] {
592 &self.final_population
593 }
594
595 pub fn generation_stats(&self) -> &[GenerationStats] {
597 &self.generation_stats
598 }
599
600 pub fn pareto_objectives(&self) -> Array2<Float> {
602 if self.pareto_front.is_empty() {
603 return Array2::zeros((0, 0));
604 }
605
606 let n_objectives = self.pareto_front[0].objectives.len();
607 let mut objectives = Array2::zeros((self.pareto_front.len(), n_objectives));
608
609 for (i, individual) in self.pareto_front.iter().enumerate() {
610 for (j, &obj_val) in individual.objectives.iter().enumerate() {
611 objectives[[i, j]] = obj_val;
612 }
613 }
614
615 objectives
616 }
617
618 pub fn pareto_variables(&self) -> Array2<Float> {
620 if self.pareto_front.is_empty() {
621 return Array2::zeros((0, 0));
622 }
623
624 let n_variables = self.pareto_front[0].variables.len();
625 let mut variables = Array2::zeros((self.pareto_front.len(), n_variables));
626
627 for (i, individual) in self.pareto_front.iter().enumerate() {
628 for (j, &var_val) in individual.variables.iter().enumerate() {
629 variables[[i, j]] = var_val;
630 }
631 }
632
633 variables
634 }
635}
636
637#[derive(Debug, Clone)]
639pub struct GenerationStats {
640 pub hypervolume: Float,
642 pub spacing: Float,
644 pub pareto_front_size: usize,
646}
647
648#[allow(non_snake_case)]
649#[cfg(test)]
650mod tests {
651 use super::*;
652 use approx::assert_abs_diff_eq;
653 use scirs2_core::ndarray::array;
655
656 #[test]
657 fn test_nsga2_creation() {
658 let nsga2 = NSGAII::new()
659 .population_size(50)
660 .n_generations(25)
661 .crossover_probability(0.8)
662 .mutation_probability(0.2)
663 .variable_bounds(vec![(-1.0, 1.0), (-1.0, 1.0)])
664 .random_state(42);
665
666 assert_eq!(nsga2.population_size, 50);
667 assert_eq!(nsga2.n_generations, 25);
668 assert_abs_diff_eq!(nsga2.crossover_probability, 0.8);
669 assert_abs_diff_eq!(nsga2.mutation_probability, 0.2);
670 assert_eq!(nsga2.variable_bounds.len(), 2);
671 assert_eq!(nsga2.random_state, Some(42));
672 }
673
674 #[test]
675 fn test_nsga2_optimization() {
676 let objective_fn = |x: &ArrayView1<Float>| {
678 let f1 = x[0];
679 let g = 1.0 + 9.0 * x.iter().skip(1).sum::<Float>() / (x.len() - 1) as Float;
680 let f2 = g * (1.0 - (f1 / g).sqrt());
681 array![f1, f2]
682 };
683
684 let nsga2 = NSGAII::new()
685 .population_size(20)
686 .n_generations(10)
687 .variable_bounds(vec![(0.0, 1.0), (0.0, 1.0)])
688 .random_state(42);
689
690 let result = nsga2
691 .optimize(objective_fn, 2)
692 .expect("operation should succeed");
693
694 assert!(!result.pareto_front().is_empty());
696 assert!(result.pareto_front().len() <= 20); for individual in result.pareto_front() {
700 assert_eq!(individual.objectives.len(), 2);
701 assert!(individual.objectives[0] >= 0.0 && individual.objectives[0] <= 1.0);
702 assert!(individual.objectives[1] >= 0.0);
703 }
704
705 assert_eq!(result.generation_stats().len(), 10);
707 assert!(result.generation_stats()[0].pareto_front_size > 0);
708 }
709
710 #[test]
711 fn test_individual_dominance() {
712 let ind1 = Individual {
713 variables: array![1.0, 2.0],
714 objectives: array![1.0, 2.0], rank: 0,
716 crowding_distance: 0.0,
717 };
718
719 let ind2 = Individual {
720 variables: array![2.0, 3.0],
721 objectives: array![2.0, 3.0], rank: 0,
723 crowding_distance: 0.0,
724 };
725
726 assert!(ind1.dominates(&ind2));
727 assert!(!ind2.dominates(&ind1));
728 }
729
730 #[test]
731 fn test_non_dominated_sort() {
732 let nsga2 = NSGAII::new();
733
734 let population = vec![
735 Individual {
736 variables: array![1.0],
737 objectives: array![1.0, 3.0], rank: 0,
739 crowding_distance: 0.0,
740 },
741 Individual {
742 variables: array![2.0],
743 objectives: array![2.0, 2.0], rank: 0,
745 crowding_distance: 0.0,
746 },
747 Individual {
748 variables: array![3.0],
749 objectives: array![3.0, 1.0], rank: 0,
751 crowding_distance: 0.0,
752 },
753 Individual {
754 variables: array![4.0],
755 objectives: array![2.0, 3.0], rank: 0,
757 crowding_distance: 0.0,
758 },
759 ];
760
761 let fronts = nsga2.non_dominated_sort(&population);
762
763 assert!(!fronts.is_empty());
764 assert_eq!(fronts[0].len(), 3); if fronts.len() > 1 {
766 assert_eq!(fronts[1].len(), 1); }
768 }
769
770 #[test]
771 fn test_optimization_result_accessors() {
772 let individuals = vec![
773 Individual {
774 variables: array![1.0, 2.0],
775 objectives: array![1.0, 2.0],
776 rank: 0,
777 crowding_distance: 0.0,
778 },
779 Individual {
780 variables: array![2.0, 1.0],
781 objectives: array![2.0, 1.0],
782 rank: 0,
783 crowding_distance: 0.0,
784 },
785 ];
786
787 let result = OptimizationResult {
788 pareto_front: individuals.clone(),
789 final_population: individuals,
790 generation_stats: vec![GenerationStats {
791 hypervolume: 1.0,
792 spacing: 0.5,
793 pareto_front_size: 2,
794 }],
795 n_generations: 10,
796 };
797
798 let objectives = result.pareto_objectives();
799 assert_eq!(objectives.shape(), &[2, 2]);
800 assert_abs_diff_eq!(objectives[[0, 0]], 1.0);
801 assert_abs_diff_eq!(objectives[[0, 1]], 2.0);
802
803 let variables = result.pareto_variables();
804 assert_eq!(variables.shape(), &[2, 2]);
805 assert_abs_diff_eq!(variables[[0, 0]], 1.0);
806 assert_abs_diff_eq!(variables[[0, 1]], 2.0);
807 }
808}