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 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}