radiate_core/objectives/
optimize.rs

1use super::Scored;
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, PartialEq)]
6#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7pub enum Objective {
8    Single(Optimize),
9    Multi(Vec<Optimize>),
10}
11
12impl Objective {
13    pub fn is_single(&self) -> bool {
14        matches!(self, Objective::Single(_))
15    }
16
17    pub fn is_multi(&self) -> bool {
18        matches!(self, Objective::Multi(_))
19    }
20
21    pub fn dimensions(&self) -> usize {
22        match self {
23            Objective::Single(_) => 1,
24            Objective::Multi(opts) => opts.len(),
25        }
26    }
27
28    pub fn validate<T: AsRef<[K]>, K>(&self, values: &T) -> bool {
29        match self {
30            Objective::Single(_) => values.as_ref().len() == 1,
31            Objective::Multi(opts) => values.as_ref().len() == opts.len(),
32        }
33    }
34
35    pub fn cmp<T>(&self, a: &T, b: &T) -> std::cmp::Ordering
36    where
37        T: PartialOrd,
38    {
39        match self {
40            Objective::Single(opt) => {
41                if opt.is_better(a, b) {
42                    std::cmp::Ordering::Less
43                } else if opt.is_better(b, a) {
44                    std::cmp::Ordering::Greater
45                } else {
46                    std::cmp::Ordering::Equal
47                }
48            }
49            Objective::Multi(opts) => {
50                for &opt in opts {
51                    if opt.is_better(a, b) {
52                        return std::cmp::Ordering::Less;
53                    } else if opt.is_better(b, a) {
54                        return std::cmp::Ordering::Greater;
55                    }
56                }
57                std::cmp::Ordering::Equal
58            }
59        }
60    }
61
62    pub fn sort<T: AsMut<[K]>, K: Scored + PartialOrd>(&self, population: &mut T) {
63        match self {
64            Objective::Single(opt) => opt.sort(population),
65            Objective::Multi(_) => population.as_mut().sort_unstable_by(|one, two| {
66                if let (Some(score_one), Some(score_two)) = (one.score(), two.score()) {
67                    self.dominance_cmp(score_one.as_ref(), score_two.as_ref())
68                } else {
69                    std::cmp::Ordering::Equal
70                }
71            }),
72        }
73    }
74
75    fn dominance_cmp<T>(&self, a: &[T], b: &[T]) -> std::cmp::Ordering
76    where
77        T: PartialOrd,
78    {
79        match self {
80            Objective::Single(opt) => {
81                if opt.is_better(&a[0], &b[0]) {
82                    std::cmp::Ordering::Less
83                } else if opt.is_better(&b[0], &a[0]) {
84                    std::cmp::Ordering::Greater
85                } else {
86                    std::cmp::Ordering::Equal
87                }
88            }
89            Objective::Multi(opts) => {
90                for ((a, b), opt) in a.iter().zip(b.iter()).zip(opts) {
91                    if opt.is_better(a, b) {
92                        return std::cmp::Ordering::Less;
93                    } else if opt.is_better(b, a) {
94                        return std::cmp::Ordering::Greater;
95                    }
96                }
97                std::cmp::Ordering::Equal
98            }
99        }
100    }
101
102    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
103    where
104        T: PartialOrd,
105    {
106        match self {
107            Objective::Single(opt) => opt.is_better(a, b),
108            Objective::Multi(opts) => {
109                for &opt in opts {
110                    if !opt.is_better(a, b) {
111                        return false;
112                    }
113                }
114                true
115            }
116        }
117    }
118}
119
120impl AsRef<[Optimize]> for Objective {
121    fn as_ref(&self) -> &[Optimize] {
122        match self {
123            Objective::Single(opt) => std::slice::from_ref(opt),
124            Objective::Multi(opts) => opts.as_slice(),
125        }
126    }
127}
128
129impl Default for Objective {
130    fn default() -> Self {
131        Objective::Single(Optimize::Maximize)
132    }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq)]
136#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
137pub enum Optimize {
138    Minimize,
139    Maximize,
140}
141
142impl Optimize {
143    pub fn sort<T: AsMut<[K]>, K: PartialOrd>(&self, population: &mut T) {
144        match self {
145            Optimize::Minimize => population
146                .as_mut()
147                .sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
148            Optimize::Maximize => population
149                .as_mut()
150                .sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)),
151        }
152    }
153
154    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
155    where
156        T: PartialOrd,
157    {
158        match self {
159            Optimize::Minimize => a < b,
160            Optimize::Maximize => a > b,
161        }
162    }
163
164    pub fn is_minimize(&self) -> bool {
165        matches!(self, Optimize::Minimize)
166    }
167
168    pub fn is_maximize(&self) -> bool {
169        matches!(self, Optimize::Maximize)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_optimize_is_better() {
179        assert!(Optimize::Minimize.is_better(&1, &2));
180        assert!(!Optimize::Minimize.is_better(&2, &1));
181        assert!(Optimize::Maximize.is_better(&2, &1));
182        assert!(!Optimize::Maximize.is_better(&1, &2));
183    }
184
185    #[test]
186    fn test_objective_is_better_single() {
187        let obj = Objective::Single(Optimize::Minimize);
188        assert!(obj.is_better(&1, &2));
189        assert!(!obj.is_better(&2, &1));
190        let obj = Objective::Single(Optimize::Maximize);
191        assert!(obj.is_better(&2, &1));
192        assert!(!obj.is_better(&1, &2));
193    }
194}