Skip to main content

radiate_core/objectives/
optimize.rs

1use super::Scored;
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize};
4
5const MIN: &str = "min";
6const MAX: &str = "max";
7
8#[derive(Clone, Debug, PartialEq)]
9#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10pub enum Objective {
11    Single(Optimize),
12    Multi(Vec<Optimize>),
13}
14
15impl Objective {
16    pub fn is_single(&self) -> bool {
17        matches!(self, Objective::Single(_))
18    }
19
20    pub fn is_multi(&self) -> bool {
21        matches!(self, Objective::Multi(_))
22    }
23
24    pub fn dims(&self) -> usize {
25        match self {
26            Objective::Single(_) => 1,
27            Objective::Multi(opts) => opts.len(),
28        }
29    }
30
31    #[inline]
32    pub fn validate<T: AsRef<[K]>, K>(&self, values: &T) -> bool {
33        match self {
34            Objective::Single(_) => values.as_ref().len() == 1,
35            Objective::Multi(opts) => values.as_ref().len() == opts.len(),
36        }
37    }
38
39    pub fn cmp<T>(&self, a: &T, b: &T) -> std::cmp::Ordering
40    where
41        T: PartialOrd,
42    {
43        match self {
44            Objective::Single(opt) => {
45                if opt.is_better(a, b) {
46                    std::cmp::Ordering::Less
47                } else if opt.is_better(b, a) {
48                    std::cmp::Ordering::Greater
49                } else {
50                    std::cmp::Ordering::Equal
51                }
52            }
53            Objective::Multi(opts) => {
54                for &opt in opts {
55                    if opt.is_better(a, b) {
56                        return std::cmp::Ordering::Less;
57                    } else if opt.is_better(b, a) {
58                        return std::cmp::Ordering::Greater;
59                    }
60                }
61                std::cmp::Ordering::Equal
62            }
63        }
64    }
65
66    pub fn sort<T: AsMut<[K]>, K: Scored + PartialOrd>(&self, population: &mut T) {
67        match self {
68            Objective::Single(opt) => opt.sort(population),
69            Objective::Multi(_) => population.as_mut().sort_unstable_by(|one, two| {
70                if let (Some(score_one), Some(score_two)) = (one.score(), two.score()) {
71                    self.dominance_cmp(score_one.as_ref(), score_two.as_ref())
72                } else {
73                    std::cmp::Ordering::Equal
74                }
75            }),
76        }
77    }
78
79    fn dominance_cmp<T>(&self, a: &[T], b: &[T]) -> std::cmp::Ordering
80    where
81        T: PartialOrd,
82    {
83        match self {
84            Objective::Single(opt) => {
85                if opt.is_better(&a[0], &b[0]) {
86                    std::cmp::Ordering::Less
87                } else if opt.is_better(&b[0], &a[0]) {
88                    std::cmp::Ordering::Greater
89                } else {
90                    std::cmp::Ordering::Equal
91                }
92            }
93            Objective::Multi(opts) => {
94                for ((a, b), opt) in a.iter().zip(b.iter()).zip(opts) {
95                    if opt.is_better(a, b) {
96                        return std::cmp::Ordering::Less;
97                    } else if opt.is_better(b, a) {
98                        return std::cmp::Ordering::Greater;
99                    }
100                }
101                std::cmp::Ordering::Equal
102            }
103        }
104    }
105
106    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
107    where
108        T: PartialOrd,
109    {
110        match self {
111            Objective::Single(opt) => opt.is_better(a, b),
112            Objective::Multi(opts) => {
113                for &opt in opts {
114                    if !opt.is_better(a, b) {
115                        return false;
116                    }
117                }
118                true
119            }
120        }
121    }
122}
123
124impl AsRef<[Optimize]> for Objective {
125    fn as_ref(&self) -> &[Optimize] {
126        match self {
127            Objective::Single(opt) => std::slice::from_ref(opt),
128            Objective::Multi(opts) => opts.as_slice(),
129        }
130    }
131}
132
133impl Default for Objective {
134    fn default() -> Self {
135        Objective::Single(Optimize::Maximize)
136    }
137}
138
139impl From<Vec<Optimize>> for Objective {
140    fn from(opts: Vec<Optimize>) -> Self {
141        if opts.len() == 1 {
142            Objective::Single(opts[0])
143        } else {
144            Objective::Multi(opts)
145        }
146    }
147}
148
149impl From<Vec<&str>> for Objective {
150    fn from(values: Vec<&str>) -> Self {
151        let opts = values
152            .into_iter()
153            .map(Optimize::from)
154            .collect::<Vec<Optimize>>();
155
156        if opts.len() == 1 {
157            Objective::Single(opts[0])
158        } else {
159            Objective::Multi(opts)
160        }
161    }
162}
163
164impl From<Objective> for Vec<&str> {
165    fn from(objective: Objective) -> Self {
166        match objective {
167            Objective::Single(opt) => vec![opt.into()],
168            Objective::Multi(opts) => opts.into_iter().map(|opt| opt.into()).collect(),
169        }
170    }
171}
172
173#[derive(Clone, Copy, Debug, PartialEq)]
174#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
175pub enum Optimize {
176    Minimize,
177    Maximize,
178}
179
180impl Optimize {
181    pub fn sort<T: AsMut<[K]>, K: PartialOrd>(&self, population: &mut T) {
182        match self {
183            Optimize::Minimize => population
184                .as_mut()
185                .sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
186            Optimize::Maximize => population
187                .as_mut()
188                .sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)),
189        }
190    }
191
192    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
193    where
194        T: PartialOrd,
195    {
196        match self {
197            Optimize::Minimize => a < b,
198            Optimize::Maximize => a > b,
199        }
200    }
201
202    pub fn is_minimize(&self) -> bool {
203        matches!(self, Optimize::Minimize)
204    }
205
206    pub fn is_maximize(&self) -> bool {
207        matches!(self, Optimize::Maximize)
208    }
209}
210
211impl From<&str> for Optimize {
212    fn from(value: &str) -> Self {
213        match value.to_lowercase().as_str() {
214            MIN => Optimize::Minimize,
215            MAX => Optimize::Maximize,
216            _ => Optimize::Maximize,
217        }
218    }
219}
220
221impl From<Optimize> for &str {
222    fn from(opt: Optimize) -> Self {
223        match opt {
224            Optimize::Minimize => MIN,
225            Optimize::Maximize => MAX,
226        }
227    }
228}
229
230impl From<bool> for Optimize {
231    fn from(value: bool) -> Self {
232        if value {
233            Optimize::Maximize
234        } else {
235            Optimize::Minimize
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_optimize_is_better() {
246        assert!(Optimize::Minimize.is_better(&1, &2));
247        assert!(!Optimize::Minimize.is_better(&2, &1));
248        assert!(Optimize::Maximize.is_better(&2, &1));
249        assert!(!Optimize::Maximize.is_better(&1, &2));
250    }
251
252    #[test]
253    fn test_objective_is_better_single() {
254        let obj = Objective::Single(Optimize::Minimize);
255        assert!(obj.is_better(&1, &2));
256        assert!(!obj.is_better(&2, &1));
257        let obj = Objective::Single(Optimize::Maximize);
258        assert!(obj.is_better(&2, &1));
259        assert!(!obj.is_better(&1, &2));
260    }
261}