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