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