radiate_core/objectives/
optimize.rs1use 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}