1use serde::{Deserialize, Serialize};
6
7use crate::error::GenomeError;
8
9#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
11pub struct Bounds {
12 pub min: f64,
14 pub max: f64,
16}
17
18impl Bounds {
19 pub fn new(min: f64, max: f64) -> Self {
29 Self::try_new(min, max).unwrap_or_else(|e| panic!("{e}"))
30 }
31
32 pub fn try_new(min: f64, max: f64) -> Result<Self, GenomeError> {
37 if matches!(
42 min.partial_cmp(&max),
43 None | Some(std::cmp::Ordering::Greater)
44 ) {
45 return Err(GenomeError::InvalidStructure(format!(
46 "Invalid bounds: min ({min}) must be <= max ({max})"
47 )));
48 }
49 Ok(Self { min, max })
50 }
51
52 pub fn symmetric(half_width: f64) -> Self {
54 Self::new(-half_width, half_width)
55 }
56
57 pub fn unit() -> Self {
59 Self::new(0.0, 1.0)
60 }
61
62 pub fn range(&self) -> f64 {
64 self.max - self.min
65 }
66
67 pub fn center(&self) -> f64 {
69 (self.min + self.max) / 2.0
70 }
71
72 pub fn contains(&self, value: f64) -> bool {
74 value >= self.min && value <= self.max
75 }
76
77 pub fn clamp(&self, value: f64) -> f64 {
79 value.clamp(self.min, self.max)
80 }
81
82 pub fn normalize(&self, value: f64) -> f64 {
88 let range = self.range();
89 if range <= 0.0 {
90 return 0.5;
91 }
92 (value - self.min) / range
93 }
94
95 pub fn denormalize(&self, value: f64) -> f64 {
100 let range = self.range();
101 if range <= 0.0 {
102 return self.min;
103 }
104 self.min + value * range
105 }
106}
107
108impl Default for Bounds {
109 fn default() -> Self {
110 Self::symmetric(5.12) }
112}
113
114impl From<(f64, f64)> for Bounds {
115 fn from((min, max): (f64, f64)) -> Self {
116 Self::new(min, max)
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122pub struct MultiBounds {
123 pub bounds: Vec<Bounds>,
125}
126
127impl MultiBounds {
128 pub fn new(bounds: Vec<Bounds>) -> Self {
130 Self { bounds }
131 }
132
133 pub fn uniform(bound: Bounds, dimension: usize) -> Self {
135 Self {
136 bounds: vec![bound; dimension],
137 }
138 }
139
140 pub fn symmetric(half_width: f64, dimension: usize) -> Self {
142 Self::uniform(Bounds::symmetric(half_width), dimension)
143 }
144
145 pub fn dimension(&self) -> usize {
147 self.bounds.len()
148 }
149
150 pub fn get(&self, index: usize) -> Option<&Bounds> {
152 self.bounds.get(index)
153 }
154
155 pub fn clamp_vec(&self, values: &mut [f64]) {
157 for (i, value) in values.iter_mut().enumerate() {
158 if let Some(b) = self.bounds.get(i) {
159 *value = b.clamp(*value);
160 }
161 }
162 }
163
164 pub fn contains_vec(&self, values: &[f64]) -> bool {
166 values
167 .iter()
168 .enumerate()
169 .all(|(i, &v)| self.bounds.get(i).is_some_and(|b| b.contains(v)))
170 }
171}
172
173impl FromIterator<Bounds> for MultiBounds {
174 fn from_iter<I: IntoIterator<Item = Bounds>>(iter: I) -> Self {
175 Self {
176 bounds: iter.into_iter().collect(),
177 }
178 }
179}
180
181impl FromIterator<(f64, f64)> for MultiBounds {
182 fn from_iter<I: IntoIterator<Item = (f64, f64)>>(iter: I) -> Self {
183 Self {
184 bounds: iter.into_iter().map(Bounds::from).collect(),
185 }
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_bounds_new() {
195 let b = Bounds::new(-5.0, 5.0);
196 assert_eq!(b.min, -5.0);
197 assert_eq!(b.max, 5.0);
198 }
199
200 #[test]
201 #[should_panic(expected = "Invalid bounds")]
202 fn test_bounds_invalid() {
203 Bounds::new(5.0, -5.0);
204 }
205
206 #[test]
207 fn test_bounds_symmetric() {
208 let b = Bounds::symmetric(3.0);
209 assert_eq!(b.min, -3.0);
210 assert_eq!(b.max, 3.0);
211 }
212
213 #[test]
214 fn test_bounds_unit() {
215 let b = Bounds::unit();
216 assert_eq!(b.min, 0.0);
217 assert_eq!(b.max, 1.0);
218 }
219
220 #[test]
221 fn test_bounds_range() {
222 let b = Bounds::new(-5.0, 5.0);
223 assert_eq!(b.range(), 10.0);
224 }
225
226 #[test]
227 fn test_bounds_center() {
228 let b = Bounds::new(-2.0, 6.0);
229 assert_eq!(b.center(), 2.0);
230 }
231
232 #[test]
233 fn test_bounds_contains() {
234 let b = Bounds::new(-5.0, 5.0);
235 assert!(b.contains(0.0));
236 assert!(b.contains(-5.0));
237 assert!(b.contains(5.0));
238 assert!(!b.contains(-5.1));
239 assert!(!b.contains(5.1));
240 }
241
242 #[test]
243 fn test_bounds_clamp() {
244 let b = Bounds::new(-5.0, 5.0);
245 assert_eq!(b.clamp(0.0), 0.0);
246 assert_eq!(b.clamp(-10.0), -5.0);
247 assert_eq!(b.clamp(10.0), 5.0);
248 }
249
250 #[test]
251 fn test_bounds_normalize() {
252 let b = Bounds::new(0.0, 10.0);
253 assert_eq!(b.normalize(0.0), 0.0);
254 assert_eq!(b.normalize(5.0), 0.5);
255 assert_eq!(b.normalize(10.0), 1.0);
256 }
257
258 #[test]
259 fn test_bounds_denormalize() {
260 let b = Bounds::new(0.0, 10.0);
261 assert_eq!(b.denormalize(0.0), 0.0);
262 assert_eq!(b.denormalize(0.5), 5.0);
263 assert_eq!(b.denormalize(1.0), 10.0);
264 }
265
266 #[test]
267 fn test_bounds_try_new_rejects_min_gt_max() {
268 let result = Bounds::try_new(5.0, -5.0);
270 assert!(result.is_err());
271 assert!(Bounds::try_new(-5.0, 5.0).is_ok());
272 assert!(Bounds::try_new(3.0, 3.0).is_ok());
274 }
275
276 #[test]
277 fn test_bounds_try_new_rejects_nan() {
278 assert!(Bounds::try_new(f64::NAN, 5.0).is_err());
281 assert!(Bounds::try_new(-5.0, f64::NAN).is_err());
282 assert!(Bounds::try_new(f64::NAN, f64::NAN).is_err());
283 }
284
285 #[test]
286 fn test_bounds_degenerate_normalize_denormalize() {
287 let b = Bounds::new(3.0, 3.0);
290 assert_eq!(b.range(), 0.0);
291
292 assert_eq!(b.normalize(3.0), 0.5);
295 assert!(b.normalize(3.0).is_finite());
296 assert_eq!(b.normalize(100.0), 0.5);
297 assert!(b.normalize(100.0).is_finite());
298
299 assert_eq!(b.denormalize(0.0), 3.0);
301 assert_eq!(b.denormalize(0.5), 3.0);
302 assert_eq!(b.denormalize(1.0), 3.0);
303 }
304
305 #[test]
306 fn test_multi_bounds_uniform() {
307 let mb = MultiBounds::symmetric(5.0, 3);
308 assert_eq!(mb.dimension(), 3);
309 assert_eq!(mb.get(0), Some(&Bounds::symmetric(5.0)));
310 assert_eq!(mb.get(1), Some(&Bounds::symmetric(5.0)));
311 assert_eq!(mb.get(2), Some(&Bounds::symmetric(5.0)));
312 assert_eq!(mb.get(3), None);
313 }
314
315 #[test]
316 fn test_multi_bounds_clamp_vec() {
317 let mb = MultiBounds::symmetric(5.0, 3);
318 let mut values = vec![-10.0, 0.0, 10.0];
319 mb.clamp_vec(&mut values);
320 assert_eq!(values, vec![-5.0, 0.0, 5.0]);
321 }
322
323 #[test]
324 fn test_multi_bounds_contains_vec() {
325 let mb = MultiBounds::symmetric(5.0, 3);
326 assert!(mb.contains_vec(&[0.0, 0.0, 0.0]));
327 assert!(mb.contains_vec(&[-5.0, 5.0, 0.0]));
328 assert!(!mb.contains_vec(&[-6.0, 0.0, 0.0]));
329 }
330
331 #[test]
332 fn test_multi_bounds_from_tuples() {
333 let mb: MultiBounds = vec![(0.0, 1.0), (-10.0, 10.0)].into_iter().collect();
334 assert_eq!(mb.dimension(), 2);
335 assert_eq!(mb.get(0), Some(&Bounds::new(0.0, 1.0)));
336 assert_eq!(mb.get(1), Some(&Bounds::new(-10.0, 10.0)));
337 }
338}