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