radiate_selectors/
nsga3.rs1use radiate_core::{Chromosome, Objective, Optimize, Population, Select, pareto};
2use std::sync::{Arc, Mutex};
3
4#[derive(Debug, Clone)]
5pub struct NSGA3Selector {
6 ref_dirs: Arc<Mutex<Vec<Vec<f32>>>>,
7 num_refs: usize,
8}
9
10impl NSGA3Selector {
11 pub fn new(ref_points: usize) -> Self {
12 Self {
13 ref_dirs: Arc::new(Mutex::new(Vec::new())),
14 num_refs: ref_points,
15 }
16 }
17
18 fn create_ref_dirs_if_needed(&self, num_objectives: usize, ref_points: usize) {
19 let mut dirs = self.ref_dirs.lock().unwrap();
20 if dirs.is_empty() {
21 *dirs = pareto::das_dennis(num_objectives, ref_points);
22 }
23 }
24}
25
26impl<C: Chromosome + Clone> Select<C> for NSGA3Selector {
27 fn name(&self) -> &'static str {
28 "nsga3_selector"
29 }
30
31 fn select(
32 &self,
33 population: &Population<C>,
34 objective: &Objective,
35 count: usize,
36 ) -> Population<C> {
37 self.create_ref_dirs_if_needed(objective.dims(), self.num_refs);
38 let raw = population.get_scores().collect::<Vec<_>>();
39
40 let scores_min = raw
41 .iter()
42 .map(|s| to_minimization_space(s.as_ref(), objective))
43 .collect::<Vec<_>>();
44
45 let ranks = pareto::rank(&scores_min, objective);
46
47 let fronts = fronts_from_ranks(&ranks);
48
49 let mut selected: Vec<usize> = Vec::with_capacity(count);
50 let mut fi = 0usize;
51
52 while fi < fronts.len() && selected.len() + fronts[fi].len() <= count {
53 selected.extend_from_slice(&fronts[fi]);
54 fi += 1;
55 }
56
57 if selected.len() < count && fi < fronts.len() {
58 let remaining = count - selected.len();
59 let extra = nsga3_niching_fill(
60 &scores_min,
61 &self.ref_dirs.lock().unwrap(),
62 &selected,
63 &fronts[fi],
64 remaining,
65 );
66 selected.extend(extra);
67 }
68
69 selected
70 .into_iter()
71 .take(count)
72 .map(|i| population[i].clone())
73 .collect::<Population<C>>()
74 }
75}
76
77#[inline]
78pub fn fronts_from_ranks(ranks: &[usize]) -> Vec<Vec<usize>> {
79 if ranks.is_empty() {
80 return Vec::new();
81 }
82 let max_rank = *ranks.iter().max().unwrap_or(&0);
83 let mut fronts = vec![Vec::<usize>::new(); max_rank + 1];
84 for (i, &r) in ranks.iter().enumerate() {
85 fronts[r].push(i);
86 }
87
88 while fronts.last().map_or(false, |f| f.is_empty()) {
89 fronts.pop();
90 }
91 fronts
92}
93
94#[inline]
95pub fn to_minimization_space(score: &[f32], objective: &Objective) -> Vec<f32> {
96 match objective {
97 Objective::Single(opt) => {
98 if *opt == Optimize::Minimize {
99 score.to_vec()
100 } else {
101 score.iter().map(|&x| -x).collect()
102 }
103 }
104 Objective::Multi(opts) => score
105 .iter()
106 .zip(opts.iter())
107 .map(|(&x, opt)| if *opt == Optimize::Minimize { x } else { -x })
108 .collect(),
109 }
110}
111
112#[inline]
113pub fn min_max_points(scores_min: &[Vec<f32>]) -> (Vec<f32>, Vec<f32>) {
114 let n = scores_min.len();
115 if n == 0 {
116 return (Vec::new(), Vec::new());
117 }
118 let m = scores_min[0].len();
119 let mut ideal = vec![f32::INFINITY; m];
120 let mut nadir = vec![f32::NEG_INFINITY; m];
121
122 for s in scores_min {
123 for d in 0..m {
124 ideal[d] = ideal[d].min(s[d]);
125 nadir[d] = nadir[d].max(s[d]);
126 }
127 }
128 (ideal, nadir)
129}
130
131#[inline]
132pub fn normalize_minmax(s: &[f32], ideal: &[f32], nadir: &[f32]) -> Vec<f32> {
133 let m = s.len();
134 let mut out = vec![0.0f32; m];
135 for d in 0..m {
136 let den = (nadir[d] - ideal[d]).abs();
137 if !den.is_finite() || den <= 1e-12 {
138 out[d] = 0.0;
139 } else {
140 out[d] = (s[d] - ideal[d]) / den;
141 }
142 }
143 out
144}
145
146#[inline]
147fn dot(a: &[f32], b: &[f32]) -> f32 {
148 a.iter().zip(b).map(|(&x, &y)| x * y).sum()
149}
150
151#[inline]
152fn norm2(a: &[f32]) -> f32 {
153 dot(a, a)
154}
155
156#[inline]
158pub fn associate_with_dist(y: &[f32], ref_dirs: &[Vec<f32>]) -> (usize, f32) {
159 let mut best_k = 0usize;
160 let mut best_d = f32::INFINITY;
161
162 for (k, r) in ref_dirs.iter().enumerate() {
163 let rr = norm2(r);
164 if rr <= 1e-12 || !rr.is_finite() {
165 continue;
166 }
167 let t = dot(y, r) / rr; let mut d2 = 0.0f32;
170 for i in 0..y.len() {
171 let diff = y[i] - t * r[i];
172 d2 += diff * diff;
173 }
174 if d2 < best_d {
175 best_d = d2;
176 best_k = k;
177 }
178 }
179
180 (best_k, best_d.sqrt())
181}
182
183pub fn nsga3_niching_fill(
188 scores: &[Vec<f32>], ref_dirs: &[Vec<f32>],
190 already_selected: &[usize],
191 last_front: &[usize],
192 remaining: usize,
193) -> Vec<usize> {
194 if remaining == 0 || last_front.is_empty() {
195 return Vec::new();
196 }
197
198 let (ideal, nadir) = min_max_points(scores);
200
201 let mut niche_count = vec![0usize; ref_dirs.len()];
202
203 for &idx in already_selected {
205 let y = normalize_minmax(&scores[idx], &ideal, &nadir);
206 let (k, _) = associate_with_dist(&y, ref_dirs);
207 niche_count[k] += 1;
208 }
209
210 let mut cand: Vec<(usize, usize, f32)> = last_front
212 .iter()
213 .map(|&idx| {
214 let y = normalize_minmax(&scores[idx], &ideal, &nadir);
215 let (k, d) = associate_with_dist(&y, ref_dirs);
216 (idx, k, d)
217 })
218 .collect();
219
220 let mut picked = Vec::with_capacity(remaining);
221
222 while picked.len() < remaining && !cand.is_empty() {
223 let mut best_k = None;
225 let mut best_cnt = usize::MAX;
226
227 for &(_, k, _) in &cand {
228 let c = niche_count[k];
229 if c < best_cnt {
230 best_cnt = c;
231 best_k = Some(k);
232 }
233 }
234 let k = best_k.unwrap();
235
236 let mut best_i = None;
239 let mut best_d = f32::INFINITY;
240
241 for (i, &(_, kk, d)) in cand.iter().enumerate() {
242 if kk == k && d < best_d {
243 best_d = d;
244 best_i = Some(i);
245 }
246 }
247
248 let (idx, _, _) = cand.swap_remove(best_i.unwrap());
249 picked.push(idx);
250 niche_count[k] += 1;
251 }
252
253 picked
254}