radiate_core/objectives/
optimize.rs

1use super::Scored;
2
3#[derive(Clone, Debug, PartialEq)]
4pub enum Objective {
5    Single(Optimize),
6    Multi(Vec<Optimize>),
7}
8
9impl Objective {
10    pub fn is_single(&self) -> bool {
11        matches!(self, Objective::Single(_))
12    }
13
14    pub fn is_multi(&self) -> bool {
15        matches!(self, Objective::Multi(_))
16    }
17
18    pub fn num_objectives(&self) -> usize {
19        match self {
20            Objective::Single(_) => 1,
21            Objective::Multi(opts) => opts.len(),
22        }
23    }
24
25    pub fn validate<T: AsRef<[K]>, K>(&self, values: &T) -> bool {
26        match self {
27            Objective::Single(_) => values.as_ref().len() == 1,
28            Objective::Multi(opts) => values.as_ref().len() == opts.len(),
29        }
30    }
31
32    pub fn cmp<T>(&self, a: &T, b: &T) -> std::cmp::Ordering
33    where
34        T: PartialOrd,
35    {
36        match self {
37            Objective::Single(opt) => {
38                if opt.is_better(a, b) {
39                    std::cmp::Ordering::Less
40                } else if opt.is_better(b, a) {
41                    std::cmp::Ordering::Greater
42                } else {
43                    std::cmp::Ordering::Equal
44                }
45            }
46            Objective::Multi(opts) => {
47                for &opt in opts {
48                    if opt.is_better(a, b) {
49                        return std::cmp::Ordering::Less;
50                    } else if opt.is_better(b, a) {
51                        return std::cmp::Ordering::Greater;
52                    }
53                }
54                std::cmp::Ordering::Equal
55            }
56        }
57    }
58
59    pub fn sort<T: AsMut<[K]>, K: Scored + PartialOrd>(&self, population: &mut T) {
60        match self {
61            Objective::Single(opt) => opt.sort(population),
62            Objective::Multi(_) => population.as_mut().sort_by(|one, two| {
63                if let (Some(score_one), Some(score_two)) = (one.score(), two.score()) {
64                    self.dominance_cmp(score_one.as_ref(), score_two.as_ref())
65                } else {
66                    std::cmp::Ordering::Equal
67                }
68            }),
69        }
70    }
71
72    fn dominance_cmp<T>(&self, a: &[T], b: &[T]) -> std::cmp::Ordering
73    where
74        T: PartialOrd,
75    {
76        match self {
77            Objective::Single(opt) => {
78                if opt.is_better(&a[0], &b[0]) {
79                    std::cmp::Ordering::Less
80                } else if opt.is_better(&b[0], &a[0]) {
81                    std::cmp::Ordering::Greater
82                } else {
83                    std::cmp::Ordering::Equal
84                }
85            }
86            Objective::Multi(opts) => {
87                for ((a, b), opt) in a.iter().zip(b.iter()).zip(opts) {
88                    if opt.is_better(a, b) {
89                        return std::cmp::Ordering::Less;
90                    } else if opt.is_better(b, a) {
91                        return std::cmp::Ordering::Greater;
92                    }
93                }
94                std::cmp::Ordering::Equal
95            }
96        }
97    }
98
99    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
100    where
101        T: PartialOrd,
102    {
103        match self {
104            Objective::Single(opt) => opt.is_better(a, b),
105            Objective::Multi(opts) => {
106                for &opt in opts {
107                    if !opt.is_better(a, b) {
108                        return false;
109                    }
110                }
111                true
112            }
113        }
114    }
115}
116
117impl AsRef<[Optimize]> for Objective {
118    fn as_ref(&self) -> &[Optimize] {
119        match self {
120            Objective::Single(opt) => std::slice::from_ref(opt),
121            Objective::Multi(opts) => opts.as_slice(),
122        }
123    }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq)]
127pub enum Optimize {
128    Minimize,
129    Maximize,
130}
131
132impl Optimize {
133    pub fn sort<T: AsMut<[K]>, K: PartialOrd>(&self, population: &mut T) {
134        match self {
135            Optimize::Minimize => population
136                .as_mut()
137                .sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
138            Optimize::Maximize => population
139                .as_mut()
140                .sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)),
141        }
142    }
143
144    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
145    where
146        T: PartialOrd,
147    {
148        match self {
149            Optimize::Minimize => a < b,
150            Optimize::Maximize => a > b,
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_optimize_is_better() {
161        assert!(Optimize::Minimize.is_better(&1, &2));
162        assert!(!Optimize::Minimize.is_better(&2, &1));
163        assert!(Optimize::Maximize.is_better(&2, &1));
164        assert!(!Optimize::Maximize.is_better(&1, &2));
165    }
166
167    #[test]
168    fn test_objective_is_better_single() {
169        let obj = Objective::Single(Optimize::Minimize);
170        assert!(obj.is_better(&1, &2));
171        assert!(!obj.is_better(&2, &1));
172        let obj = Objective::Single(Optimize::Maximize);
173        assert!(obj.is_better(&2, &1));
174        assert!(!obj.is_better(&1, &2));
175    }
176}