1use std::{fmt::Debug, ops::Deref};
10
11use crate::{
12 fitness::MultiObjective,
13 select::{rank_nondominated, utils::retain_indices},
14 Cached, Solution,
15};
16use itertools::Itertools;
17
18pub trait HallOfFame<T: Solution> {
20 fn record(&mut self, generation: &[Cached<T>]);
22}
23
24#[derive(Clone)]
33pub struct BestN<T: Solution> {
34 max: usize,
35 best: Vec<Cached<T>>,
36 got_new_best: bool,
37}
38
39impl<T: Solution> BestN<T> {
40 pub fn new(max: usize) -> Self {
43 BestN {
44 max,
45 best: Vec::with_capacity(max),
46 got_new_best: false,
47 }
48 }
49
50 pub fn best(&self) -> Option<&Cached<T>> {
55 self.best.first()
56 }
57
58 pub fn best_if_new(&self) -> Option<&Cached<T>> {
61 if self.got_new_best {
62 self.best()
63 } else {
64 None
65 }
66 }
67}
68
69impl<T> HallOfFame<T> for BestN<T>
70where
71 T: Solution,
72 T::Fitness: Into<f64>,
73{
74 fn record(&mut self, generation: &[Cached<T>]) {
75 self.got_new_best = false;
76 for ind in generation {
77 if let Some(idx) = self.find_index(ind) {
78 self.best.insert(idx, ind.clone());
79 } else if self.best.len() < self.max {
80 self.best.push(ind.clone());
81 }
82 }
83 self.best.truncate(self.max);
84 }
85}
86
87impl<T: Solution> IntoIterator for BestN<T> {
88 type Item = Cached<T>;
89 type IntoIter = IntoIter<T>;
90 fn into_iter(self) -> Self::IntoIter {
91 IntoIter {
92 inner: self.best.into_iter(),
93 }
94 }
95}
96
97impl<T> Debug for BestN<T>
98where
99 T: Solution,
100 Cached<T>: Debug,
101{
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 f.debug_list().entries(self.best.iter()).finish()
104 }
105}
106
107impl<T> Deref for BestN<T>
108where
109 T: Solution,
110{
111 type Target = [Cached<T>];
112 fn deref(&self) -> &Self::Target {
113 &self.best
114 }
115}
116
117impl<T, F> BestN<T>
118where
119 T: Solution<Fitness = F>,
120 F: Into<f64>,
121{
122 fn find_index(&mut self, ind: &Cached<T>) -> Option<usize> {
123 let fit = ind.evaluate().into();
124 if self.best.is_empty() || fit > self.best[0].evaluate().into() {
125 self.got_new_best = true;
126 return Some(0);
127 }
128
129 for (i, (a, b)) in self.best.iter().tuple_windows().enumerate() {
130 if fit > b.evaluate().into() && fit < a.evaluate().into() {
131 return Some(i + 1);
132 }
133 }
134
135 None
136 }
137}
138
139#[derive(Clone)]
147pub struct BestPareto<T, const M: usize>
148where
149 T: Solution<Fitness = MultiObjective<M>>,
150{
151 front: Vec<Cached<T>>,
152}
153
154impl<T, const M: usize> BestPareto<T, M>
155where
156 T: Solution<Fitness = MultiObjective<M>>,
157{
158 pub fn new() -> Self {
160 BestPareto {
161 front: Default::default(),
162 }
163 }
164
165 pub fn front(&self) -> &[Cached<T>] {
167 &self.front
168 }
169}
170
171impl<T, const M: usize> Default for BestPareto<T, M>
172where
173 T: Solution<Fitness = MultiObjective<M>>,
174{
175 fn default() -> Self {
176 BestPareto { front: Vec::new() }
177 }
178}
179
180impl<T, const M: usize> HallOfFame<T> for BestPareto<T, M>
181where
182 T: Solution<Fitness = MultiObjective<M>>,
183{
184 fn record(&mut self, generation: &[Cached<T>]) {
185 let pareto = rank_nondominated(generation);
186 for (ind, rank) in generation.iter().zip(pareto.ranks.into_iter()) {
187 if rank == 0 {
188 self.front.push(ind.clone());
189 }
190 }
191 let pareto2 = rank_nondominated(&self.front);
192 let indices = (0..self.front.len())
193 .filter(|i| pareto2.ranks[*i] == 0)
194 .collect();
195 retain_indices(&mut self.front, indices);
196 }
197}
198
199impl<T, const M: usize> IntoIterator for BestPareto<T, M>
200where
201 T: Solution<Fitness = MultiObjective<M>>,
202{
203 type Item = Cached<T>;
204 type IntoIter = IntoIter<T>;
205 fn into_iter(self) -> Self::IntoIter {
206 IntoIter {
207 inner: self.front.into_iter(),
208 }
209 }
210}
211
212impl<T, const M: usize> Debug for BestPareto<T, M>
213where
214 T: Solution<Fitness = MultiObjective<M>>,
215 Cached<T>: Debug,
216{
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 f.debug_list().entries(self.front.iter()).finish()
219 }
220}
221
222pub struct IntoIter<T: Solution> {
224 inner: std::vec::IntoIter<Cached<T>>,
225}
226
227impl<T: Solution> Iterator for IntoIter<T> {
228 type Item = Cached<T>;
229 fn next(&mut self) -> Option<Self::Item> {
230 self.inner.next()
231 }
232}
233
234impl<T: Solution> ExactSizeIterator for IntoIter<T> {
235 fn len(&self) -> usize {
236 self.inner.len()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::testutils::*;
244
245 macro_rules! pop {
246 ($ty:expr, $($val:expr),*) => {
247 &[
248 $(
249 Cached::new($ty($val))
250 ),*
251 ]
252 };
253 }
254
255 #[test]
256 fn bestn_size_1() {
257 let mut hof: BestN<One> = BestN::new(1);
258
259 hof.record(pop!(One, 1.0, 2.0, 3.0));
260 assert_eq!(hof.best.len(), 1);
261 assert_eq!(hof.best[0].evaluate(), 3.0);
262
263 hof.record(pop!(One, 1.5, 2.5, 3.5));
264 assert_eq!(hof.best[0].evaluate(), 3.5);
265 }
266
267 #[test]
268 fn bestn_size_3() {
269 let mut hof: BestN<One> = BestN::new(3);
270
271 hof.record(pop!(One, 1.0, 2.0, 3.0, 4.0, 5.0));
272 assert_eq!(hof.best.len(), 3);
273 assert_eq!(hof.best[0].evaluate(), 5.0);
274 assert_eq!(hof.best[1].evaluate(), 4.0);
275 assert_eq!(hof.best[2].evaluate(), 3.0);
276
277 hof.record(pop!(One, 1.5, 2.5, 3.5, 4.5, 5.5));
278 assert_eq!(hof.best.len(), 3);
279 assert_eq!(hof.best[0].evaluate(), 5.5);
280 assert_eq!(hof.best[1].evaluate(), 5.0);
281 assert_eq!(hof.best[2].evaluate(), 4.5);
282 }
283
284 #[test]
285 fn bestpareto() {
286 let mut hof: BestPareto<Foo, 2> = BestPareto::new();
287
288 hof.record(pop!(Foo, [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]));
289 assert_eq!(hof.front.len(), 3);
290
291 hof.record(pop!(Foo, [0.6, 0.6], [0.7, 0.7]));
292 assert_eq!(hof.front.len(), 3);
293
294 assert!(hof.front.contains(&Cached::new(Foo([0.7, 0.7]))));
295 assert!(hof.front.contains(&Cached::new(Foo([1.0, 0.0]))));
296 assert!(hof.front.contains(&Cached::new(Foo([0.0, 1.0]))));
297
298 assert!(!hof.front.contains(&Cached::new(Foo([0.5, 0.5]))));
299 assert!(!hof.front.contains(&Cached::new(Foo([0.6, 0.6]))));
300 }
301}