scirs2_optimize/multi_objective/algorithms/
spea2.rs

1//! SPEA2 (Strength Pareto Evolutionary Algorithm 2)
2//!
3//! An improved version of SPEA with better fitness assignment and archive truncation.
4
5use super::{MultiObjectiveOptimizer, MultiObjectiveResult};
6use crate::error::OptimizeError;
7use crate::multi_objective::solutions::{Population, Solution};
8use ndarray::{Array1, ArrayView1};
9
10/// SPEA2 optimizer
11#[derive(Debug, Clone)]
12pub struct SPEA2 {
13    population_size: usize,
14    archive_size: usize,
15    n_objectives: usize,
16    n_variables: usize,
17    archive: Vec<Solution>,
18    population: Population,
19    generation: usize,
20    evaluations: usize,
21}
22
23impl SPEA2 {
24    /// Create new SPEA2 optimizer
25    pub fn new(population_size: usize, n_objectives: usize, n_variables: usize) -> Self {
26        Self {
27            population_size,
28            archive_size: population_size,
29            n_objectives,
30            n_variables,
31            archive: Vec::new(),
32            population: Population::with_capacity(population_size, n_objectives, n_variables),
33            generation: 0,
34            evaluations: 0,
35        }
36    }
37
38    /// Calculate strength values
39    fn calculate_strength(&self, population: &[Solution]) -> Vec<usize> {
40        let mut strengths = vec![0; population.len()];
41
42        for (i, sol_i) in population.iter().enumerate() {
43            for (j, sol_j) in population.iter().enumerate() {
44                if i != j && self.dominates(sol_i, sol_j) {
45                    strengths[i] += 1;
46                }
47            }
48        }
49
50        strengths
51    }
52
53    /// Check if solution a dominates solution b
54    fn dominates(&self, a: &Solution, b: &Solution) -> bool {
55        let mut at_least_one_better = false;
56
57        for i in 0..self.n_objectives {
58            if a.objectives[i] > b.objectives[i] {
59                return false;
60            }
61            if a.objectives[i] < b.objectives[i] {
62                at_least_one_better = true;
63            }
64        }
65
66        at_least_one_better
67    }
68
69    /// Calculate k-th nearest neighbor distance
70    fn kth_nearest_distance(&self, index: usize, population: &[Solution], k: usize) -> f64 {
71        let mut distances = Vec::new();
72        let current = &population[index];
73
74        for (i, other) in population.iter().enumerate() {
75            if i != index {
76                let dist = self.euclidean_distance(
77                    current.objectives.as_slice().unwrap(),
78                    other.objectives.as_slice().unwrap(),
79                );
80                distances.push(dist);
81            }
82        }
83
84        distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
85        distances
86            .get(k.min(distances.len() - 1))
87            .copied()
88            .unwrap_or(0.0)
89    }
90
91    fn euclidean_distance(&self, a: &[f64], b: &[f64]) -> f64 {
92        a.iter()
93            .zip(b.iter())
94            .map(|(x, y)| (x - y).powi(2))
95            .sum::<f64>()
96            .sqrt()
97    }
98}
99
100impl MultiObjectiveOptimizer for SPEA2 {
101    fn optimize<F>(&mut self, objective_function: F) -> Result<MultiObjectiveResult, OptimizeError>
102    where
103        F: Fn(&ArrayView1<f64>) -> Array1<f64> + Send + Sync,
104    {
105        // TODO: Implement SPEA2 algorithm
106        Ok(MultiObjectiveResult {
107            pareto_front: Vec::new(),
108            population: Vec::new(),
109            n_evaluations: 0,
110            n_generations: 0,
111            success: true,
112            message: "SPEA2 not yet implemented".to_string(),
113            hypervolume: Some(0.0),
114            metrics: Default::default(),
115        })
116    }
117
118    fn evolve_generation<F>(&mut self, _objective_function: &F) -> Result<(), OptimizeError>
119    where
120        F: Fn(&ArrayView1<f64>) -> Array1<f64> + Send + Sync,
121    {
122        self.generation += 1;
123        Ok(())
124    }
125
126    fn initialize_population(&mut self) -> Result<(), OptimizeError> {
127        // TODO: Implement population initialization
128        Ok(())
129    }
130
131    fn check_convergence(&self) -> bool {
132        // TODO: Implement convergence criteria
133        false
134    }
135
136    fn get_population(&self) -> &Population {
137        &self.population
138    }
139
140    fn get_generation(&self) -> usize {
141        self.generation
142    }
143
144    fn get_evaluations(&self) -> usize {
145        self.evaluations
146    }
147
148    fn name(&self) -> &str {
149        "SPEA2"
150    }
151}