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