1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
9pub struct Bounds {
10 pub min: f64,
12 pub max: f64,
14}
15
16impl Bounds {
17 pub fn new(min: f64, max: f64) -> Self {
22 assert!(
23 min <= max,
24 "Invalid bounds: min ({}) must be <= max ({})",
25 min,
26 max
27 );
28 Self { min, max }
29 }
30
31 pub fn symmetric(half_width: f64) -> Self {
33 Self::new(-half_width, half_width)
34 }
35
36 pub fn unit() -> Self {
38 Self::new(0.0, 1.0)
39 }
40
41 pub fn range(&self) -> f64 {
43 self.max - self.min
44 }
45
46 pub fn center(&self) -> f64 {
48 (self.min + self.max) / 2.0
49 }
50
51 pub fn contains(&self, value: f64) -> bool {
53 value >= self.min && value <= self.max
54 }
55
56 pub fn clamp(&self, value: f64) -> f64 {
58 value.clamp(self.min, self.max)
59 }
60
61 pub fn normalize(&self, value: f64) -> f64 {
63 (value - self.min) / self.range()
64 }
65
66 pub fn denormalize(&self, value: f64) -> f64 {
68 self.min + value * self.range()
69 }
70}
71
72impl Default for Bounds {
73 fn default() -> Self {
74 Self::symmetric(5.12) }
76}
77
78impl From<(f64, f64)> for Bounds {
79 fn from((min, max): (f64, f64)) -> Self {
80 Self::new(min, max)
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct MultiBounds {
87 pub bounds: Vec<Bounds>,
89}
90
91impl MultiBounds {
92 pub fn new(bounds: Vec<Bounds>) -> Self {
94 Self { bounds }
95 }
96
97 pub fn uniform(bound: Bounds, dimension: usize) -> Self {
99 Self {
100 bounds: vec![bound; dimension],
101 }
102 }
103
104 pub fn symmetric(half_width: f64, dimension: usize) -> Self {
106 Self::uniform(Bounds::symmetric(half_width), dimension)
107 }
108
109 pub fn dimension(&self) -> usize {
111 self.bounds.len()
112 }
113
114 pub fn get(&self, index: usize) -> Option<&Bounds> {
116 self.bounds.get(index)
117 }
118
119 pub fn clamp_vec(&self, values: &mut [f64]) {
121 for (i, value) in values.iter_mut().enumerate() {
122 if let Some(b) = self.bounds.get(i) {
123 *value = b.clamp(*value);
124 }
125 }
126 }
127
128 pub fn contains_vec(&self, values: &[f64]) -> bool {
130 values
131 .iter()
132 .enumerate()
133 .all(|(i, &v)| self.bounds.get(i).is_some_and(|b| b.contains(v)))
134 }
135}
136
137impl FromIterator<Bounds> for MultiBounds {
138 fn from_iter<I: IntoIterator<Item = Bounds>>(iter: I) -> Self {
139 Self {
140 bounds: iter.into_iter().collect(),
141 }
142 }
143}
144
145impl FromIterator<(f64, f64)> for MultiBounds {
146 fn from_iter<I: IntoIterator<Item = (f64, f64)>>(iter: I) -> Self {
147 Self {
148 bounds: iter.into_iter().map(Bounds::from).collect(),
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_bounds_new() {
159 let b = Bounds::new(-5.0, 5.0);
160 assert_eq!(b.min, -5.0);
161 assert_eq!(b.max, 5.0);
162 }
163
164 #[test]
165 #[should_panic(expected = "Invalid bounds")]
166 fn test_bounds_invalid() {
167 Bounds::new(5.0, -5.0);
168 }
169
170 #[test]
171 fn test_bounds_symmetric() {
172 let b = Bounds::symmetric(3.0);
173 assert_eq!(b.min, -3.0);
174 assert_eq!(b.max, 3.0);
175 }
176
177 #[test]
178 fn test_bounds_unit() {
179 let b = Bounds::unit();
180 assert_eq!(b.min, 0.0);
181 assert_eq!(b.max, 1.0);
182 }
183
184 #[test]
185 fn test_bounds_range() {
186 let b = Bounds::new(-5.0, 5.0);
187 assert_eq!(b.range(), 10.0);
188 }
189
190 #[test]
191 fn test_bounds_center() {
192 let b = Bounds::new(-2.0, 6.0);
193 assert_eq!(b.center(), 2.0);
194 }
195
196 #[test]
197 fn test_bounds_contains() {
198 let b = Bounds::new(-5.0, 5.0);
199 assert!(b.contains(0.0));
200 assert!(b.contains(-5.0));
201 assert!(b.contains(5.0));
202 assert!(!b.contains(-5.1));
203 assert!(!b.contains(5.1));
204 }
205
206 #[test]
207 fn test_bounds_clamp() {
208 let b = Bounds::new(-5.0, 5.0);
209 assert_eq!(b.clamp(0.0), 0.0);
210 assert_eq!(b.clamp(-10.0), -5.0);
211 assert_eq!(b.clamp(10.0), 5.0);
212 }
213
214 #[test]
215 fn test_bounds_normalize() {
216 let b = Bounds::new(0.0, 10.0);
217 assert_eq!(b.normalize(0.0), 0.0);
218 assert_eq!(b.normalize(5.0), 0.5);
219 assert_eq!(b.normalize(10.0), 1.0);
220 }
221
222 #[test]
223 fn test_bounds_denormalize() {
224 let b = Bounds::new(0.0, 10.0);
225 assert_eq!(b.denormalize(0.0), 0.0);
226 assert_eq!(b.denormalize(0.5), 5.0);
227 assert_eq!(b.denormalize(1.0), 10.0);
228 }
229
230 #[test]
231 fn test_multi_bounds_uniform() {
232 let mb = MultiBounds::symmetric(5.0, 3);
233 assert_eq!(mb.dimension(), 3);
234 assert_eq!(mb.get(0), Some(&Bounds::symmetric(5.0)));
235 assert_eq!(mb.get(1), Some(&Bounds::symmetric(5.0)));
236 assert_eq!(mb.get(2), Some(&Bounds::symmetric(5.0)));
237 assert_eq!(mb.get(3), None);
238 }
239
240 #[test]
241 fn test_multi_bounds_clamp_vec() {
242 let mb = MultiBounds::symmetric(5.0, 3);
243 let mut values = vec![-10.0, 0.0, 10.0];
244 mb.clamp_vec(&mut values);
245 assert_eq!(values, vec![-5.0, 0.0, 5.0]);
246 }
247
248 #[test]
249 fn test_multi_bounds_contains_vec() {
250 let mb = MultiBounds::symmetric(5.0, 3);
251 assert!(mb.contains_vec(&[0.0, 0.0, 0.0]));
252 assert!(mb.contains_vec(&[-5.0, 5.0, 0.0]));
253 assert!(!mb.contains_vec(&[-6.0, 0.0, 0.0]));
254 }
255
256 #[test]
257 fn test_multi_bounds_from_tuples() {
258 let mb: MultiBounds = vec![(0.0, 1.0), (-10.0, 10.0)].into_iter().collect();
259 assert_eq!(mb.dimension(), 2);
260 assert_eq!(mb.get(0), Some(&Bounds::new(0.0, 1.0)));
261 assert_eq!(mb.get(1), Some(&Bounds::new(-10.0, 10.0)));
262 }
263}