radiate_core/objectives/
front.rs1use crate::objectives::{Objective, Scored, pareto};
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize};
4use std::{cmp::Ordering, ops::Range};
5
6const DEFAULT_ENTROPY_BINS: usize = 20;
7
8#[derive(Debug)]
9pub struct FrontAddResult {
10 pub added_count: usize,
11 pub removed_count: usize,
12 pub comparisons: usize,
13 pub filter_count: usize,
14 pub size: usize,
15}
16
17#[derive(Clone, Default)]
18struct FrontScratch {
19 remove_buff: Vec<usize>,
20 index_buff: Vec<usize>,
21 crowding_buff: Vec<f32>,
22 filter_buff: Vec<bool>,
23}
24
25#[derive(Clone)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct Front<T: Scored> {
28 values: Vec<T>,
29 range: Range<usize>,
30 objective: Objective,
31
32 #[cfg_attr(feature = "serde", serde(skip))]
33 scratch: FrontScratch,
34}
35
36impl<T: Scored> Front<T> {
37 pub fn new(range: Range<usize>, objective: Objective) -> Self {
38 Front {
39 values: Vec::new(),
40 range,
41 objective,
42 scratch: FrontScratch::default(),
43 }
44 }
45
46 pub fn len(&self) -> usize {
47 self.values.len()
48 }
49
50 pub fn range(&self) -> Range<usize> {
51 self.range.clone()
52 }
53
54 pub fn objective(&self) -> Objective {
55 self.objective.clone()
56 }
57
58 pub fn is_empty(&self) -> bool {
59 self.values.is_empty()
60 }
61
62 pub fn values(&self) -> &[T] {
63 &self.values
64 }
65
66 pub fn crowding_distance(&mut self) -> Option<&[f32]> {
67 let scores = self
68 .values
69 .iter()
70 .filter_map(|v| v.score())
71 .collect::<Vec<_>>();
72
73 if scores.is_empty() {
74 return None;
75 }
76
77 self.scratch.crowding_buff.clear();
78 self.scratch.crowding_buff.resize(scores.len(), 0.0);
79
80 pareto::buffered_crowding_distance(&scores, &mut self.scratch.crowding_buff);
81
82 Some(&self.scratch.crowding_buff[..])
83 }
84
85 pub fn entropy(&mut self) -> Option<f32> {
86 let scores = self
87 .values
88 .iter()
89 .filter_map(|v| v.score())
90 .collect::<Vec<_>>();
91
92 if scores.is_empty() {
93 return None;
94 }
95
96 Some(pareto::entropy(scores.as_slice(), DEFAULT_ENTROPY_BINS))
97 }
98
99 pub fn hypervolume(&self, reference: &[f32]) -> Option<f32> {
105 if self.values.is_empty() || reference.len() != self.objective.dims() {
106 return None;
107 }
108
109 let scores = self
110 .values
111 .iter()
112 .filter_map(|v| v.score())
113 .collect::<Vec<_>>();
114
115 Some(pareto::hypervolume(&scores, reference, &self.objective))
116 }
117
118 pub fn try_add_all<'a>(&mut self, items: impl Iterator<Item = &'a T>) -> FrontAddResult
119 where
120 T: Eq + Clone + 'static,
121 {
122 let mut added_count = 0;
123 let mut removed_count = 0;
124 let mut comparisons = 0;
125 let mut filter_count = 0;
126
127 for new_member in items.into_iter() {
128 self.scratch.remove_buff.clear();
129
130 let mut accept = true;
131 for (idx, existing) in self.values.iter().enumerate() {
132 if existing == new_member {
133 accept = false;
134 break;
135 }
136
137 match self.dom_cmp(existing, new_member) {
138 Ordering::Greater => {
139 accept = false;
141 comparisons += 1;
142 break;
143 }
144 Ordering::Less => {
145 self.scratch.remove_buff.push(idx);
147 comparisons += 1;
148 }
149 Ordering::Equal => comparisons += 1,
150 }
151 }
152
153 if !accept {
154 continue;
155 }
156
157 if !self.scratch.remove_buff.is_empty() {
160 self.scratch.remove_buff.sort_unstable();
161 self.scratch.remove_buff.dedup();
162
163 removed_count += self.scratch.remove_buff.len();
164
165 for &idx in self.scratch.remove_buff.iter().rev() {
166 self.values.swap_remove(idx);
167 }
168 }
169
170 self.values.push(new_member.clone());
171 added_count += 1;
172
173 if self.values.len() > self.range.end {
174 self.fast_filter();
175 filter_count += 1;
176 }
177 }
178
179 FrontAddResult {
180 added_count,
181 removed_count,
182 comparisons,
183 filter_count,
184 size: self.values.len(),
185 }
186 }
187
188 #[inline]
191 pub fn remove_outliers(&mut self, trim: f32) -> Option<usize> {
192 if self.values.len() < 4 {
193 return None;
194 }
195
196 let trim = trim.clamp(0.0, 0.5);
197 if trim == 0.0 {
198 return None;
199 }
200
201 let (n, _) = self.score_dims()?;
202
203 let drop = ((n as f32) * trim).floor() as usize;
204 if drop == 0 {
205 return None;
206 }
207
208 let scores = self
209 .values
210 .iter()
211 .filter_map(|v| v.score())
212 .collect::<Vec<_>>();
213
214 self.scratch.crowding_buff.clear();
215 self.scratch.crowding_buff.resize(scores.len(), 0.0);
216
217 self.scratch.index_buff.clear();
218 self.scratch.index_buff.extend(0..scores.len());
219
220 pareto::buffered_crowding_distance(&scores, &mut self.scratch.crowding_buff);
221
222 self.scratch.index_buff.sort_unstable_by(|&i, &j| {
223 self.scratch.crowding_buff[j]
224 .partial_cmp(&self.scratch.crowding_buff[i])
225 .unwrap_or(Ordering::Equal)
226 });
227
228 self.scratch.index_buff.truncate(drop);
229 self.scratch.index_buff.sort_unstable();
230 self.scratch.index_buff.dedup();
231
232 let removed = self.scratch.index_buff.len();
233 for &idx in self.scratch.index_buff.iter().rev() {
234 self.values.swap_remove(idx);
235 }
236
237 Some(removed)
238 }
239
240 pub fn fronts(&mut self) -> Vec<Front<T>>
241 where
242 T: Clone + Eq + Send + Sync + 'static,
243 {
244 let mut fronts: Vec<Front<T>> = Vec::new();
245 for member in self.values.iter() {
246 let mut updated = false;
247
248 for front in fronts.iter_mut() {
249 let result = front.try_add_all(std::iter::once(member));
250
251 if result.added_count > 0 {
252 updated = true;
253 break;
254 }
255 }
256
257 if !updated {
258 let mut new_front = Front::new(self.range.clone(), self.objective.clone());
259 new_front.try_add_all(std::iter::once(member));
260 fronts.push(new_front);
261 }
262 }
263
264 fronts
265 }
266
267 fn fast_filter(&mut self) {
268 let keep = self.range.start.min(self.values.len());
269 if keep == 0 || self.values.len() <= keep {
270 return;
271 }
272
273 let scores = self
274 .values
275 .iter()
276 .filter_map(|v| v.score())
277 .collect::<Vec<_>>();
278
279 self.scratch.crowding_buff.clear();
280 self.scratch.crowding_buff.resize(scores.len(), 0.0);
281
282 self.scratch.index_buff.clear();
283 self.scratch.index_buff.extend(0..scores.len());
284
285 pareto::buffered_crowding_distance(&scores, &mut self.scratch.crowding_buff);
286
287 self.scratch
288 .index_buff
289 .select_nth_unstable_by(keep, |&a, &b| {
290 self.scratch.crowding_buff[b]
291 .partial_cmp(&self.scratch.crowding_buff[a])
292 .unwrap_or(Ordering::Equal)
293 });
294 self.scratch.index_buff.truncate(keep);
295
296 self.retain_indices();
297 }
298
299 #[inline]
300 fn dom_cmp(&self, one: &T, two: &T) -> Ordering {
301 let one_score = one.score();
302 let two_score = two.score();
303
304 if one_score.is_none() || two_score.is_none() {
305 return Ordering::Equal;
306 }
307
308 if let Some((a, b)) = one_score.zip(two_score) {
309 if pareto::dominance(a, b, &self.objective) {
310 return Ordering::Greater;
311 } else if pareto::dominance(b, a, &self.objective) {
312 return Ordering::Less;
313 }
314 }
315 Ordering::Equal
316 }
317
318 fn retain_indices(&mut self) {
322 self.scratch.filter_buff.clear();
323 self.scratch.filter_buff.resize(self.values.len(), false);
324
325 for &idx in self.scratch.index_buff.iter() {
326 self.scratch.filter_buff[idx] = true;
327 }
328
329 let values = &mut self.values;
333 let keep_true = &self.scratch.filter_buff;
334
335 let mut idx = 0;
336 values.retain(|_| {
337 let retain = keep_true[idx];
338 idx += 1;
339 retain
340 });
341 }
342
343 #[inline]
344 fn score_dims(&self) -> Option<(usize, usize)> {
345 let n = self.values.len();
346
347 if n == 0 {
348 return None;
349 }
350
351 let first = self.values.iter().find_map(|v| v.score())?;
352 Some((n, first.len()))
353 }
354}
355
356impl<T> Default for Front<T>
357where
358 T: Scored,
359{
360 fn default() -> Self {
361 Front::new(0..0, Objective::default())
362 }
363}