1use crate::client::proto::common::metrics::Bounds as PbBounds;
7use crate::quantity::{Current, Power, Quantity, ReactivePower};
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct Bounds<Q: Quantity> {
12 lower: Option<Q>,
15 upper: Option<Q>,
18}
19
20impl<Q: Quantity> Bounds<Q> {
21 pub fn new(lower: Option<Q>, upper: Option<Q>) -> Self {
23 Self { lower, upper }
24 }
25
26 pub fn lower(&self) -> Option<Q> {
28 self.lower
29 }
30
31 pub fn upper(&self) -> Option<Q> {
33 self.upper
34 }
35
36 pub fn combine_parallel(&self, other: &Self) -> Vec<Self> {
38 if self.intersect(other).is_none() {
39 return vec![self.clone(), other.clone()];
40 }
41 let lower = self.lower.and_then(|a| {
46 other.lower.map(|b| {
47 if a <= Q::zero() && b <= Q::zero() {
48 a + b
49 } else {
50 a.min(b)
51 }
52 })
53 });
54 let upper = self.upper.and_then(|a| {
57 other.upper.map(|b| {
58 if a >= Q::zero() && b >= Q::zero() {
59 a + b
60 } else {
61 a.max(b)
62 }
63 })
64 });
65 vec![Bounds { lower, upper }]
66 }
67
68 pub fn intersect(&self, other: &Self) -> Option<Self> {
71 let lower = Self::map_or_any(Q::max, self.lower, other.lower);
72 let upper = Self::map_or_any(Q::min, self.upper, other.upper);
73 if let (Some(lower), Some(upper)) = (lower, upper)
74 && lower > upper
75 {
76 return None;
77 }
78 Some(Bounds { lower, upper })
79 }
80
81 pub fn merge_if_overlapping(&self, other: &Self) -> Option<Self> {
84 self.intersect(other)?;
85 Some(Bounds {
86 lower: self.lower.and_then(|a| other.lower.map(|b| a.min(b))),
87 upper: self.upper.and_then(|a| other.upper.map(|b| a.max(b))),
88 })
89 }
90
91 fn map_or_any(f: impl FnOnce(Q, Q) -> Q, a: Option<Q>, b: Option<Q>) -> Option<Q> {
95 match (a, b) {
96 (Some(a), Some(b)) => Some(f(a, b)),
97 (Some(a), None) | (None, Some(a)) => Some(a),
98 (None, None) => None,
99 }
100 }
101}
102
103impl<Q: Quantity> From<(Option<Q>, Option<Q>)> for Bounds<Q> {
104 fn from(bounds: (Option<Q>, Option<Q>)) -> Self {
105 Self::new(bounds.0, bounds.1)
106 }
107}
108
109impl From<Bounds<Power>> for PbBounds {
110 fn from(bounds: Bounds<Power>) -> Self {
111 PbBounds {
112 lower: bounds.lower.map(|q| q.as_watts()),
113 upper: bounds.upper.map(|q| q.as_watts()),
114 }
115 }
116}
117
118impl From<Bounds<Current>> for PbBounds {
119 fn from(bounds: Bounds<Current>) -> Self {
120 PbBounds {
121 lower: bounds.lower.map(|q| q.as_amperes()),
122 upper: bounds.upper.map(|q| q.as_amperes()),
123 }
124 }
125}
126
127impl From<Bounds<ReactivePower>> for PbBounds {
128 fn from(bounds: Bounds<ReactivePower>) -> Self {
129 PbBounds {
130 lower: bounds.lower.map(|q| q.as_volt_amperes_reactive()),
131 upper: bounds.upper.map(|q| q.as_volt_amperes_reactive()),
132 }
133 }
134}
135
136impl From<PbBounds> for Bounds<Power> {
137 fn from(pb_bounds: PbBounds) -> Self {
138 Self::new(
139 pb_bounds.lower.map(Power::from_watts),
140 pb_bounds.upper.map(Power::from_watts),
141 )
142 }
143}
144
145impl From<PbBounds> for Bounds<Current> {
146 fn from(pb_bounds: PbBounds) -> Self {
147 Self::new(
148 pb_bounds.lower.map(Current::from_amperes),
149 pb_bounds.upper.map(Current::from_amperes),
150 )
151 }
152}
153
154impl From<PbBounds> for Bounds<ReactivePower> {
155 fn from(pb_bounds: PbBounds) -> Self {
156 Self::new(
157 pb_bounds
158 .lower
159 .map(ReactivePower::from_volt_amperes_reactive),
160 pb_bounds
161 .upper
162 .map(ReactivePower::from_volt_amperes_reactive),
163 )
164 }
165}
166
167pub(crate) fn combine_parallel_sets<Q: Quantity>(
169 a: &[Bounds<Q>],
170 b: &[Bounds<Q>],
171) -> Vec<Bounds<Q>> {
172 match (a, b) {
173 (a, []) | ([], a) => a.to_vec(),
174 (a, b) => {
175 let mut result = Vec::new();
176 for b1 in a {
177 for b2 in b {
178 result.extend(b1.combine_parallel(b2));
179 }
180 }
181 squash_bounds_sets(result)
182 }
183 }
184}
185
186pub(crate) fn intersect_bounds_sets<Q: Quantity>(
192 a: &[Bounds<Q>],
193 b: &[Bounds<Q>],
194) -> Vec<Bounds<Q>> {
195 let mut result = Vec::new();
196 for b1 in a {
197 for b2 in b {
198 if let Some(int) = b1.intersect(b2) {
199 result.push(int);
200 }
201 }
202 }
203 squash_bounds_sets(result)
204}
205
206fn squash_bounds_sets<Q: Quantity>(mut input: Vec<Bounds<Q>>) -> Vec<Bounds<Q>> {
208 if input.is_empty() {
209 return input;
210 }
211
212 input.sort_by(|a, b| {
213 a.lower
214 .unwrap_or(Q::MIN)
215 .partial_cmp(&b.lower.unwrap_or(Q::MIN))
216 .unwrap_or(std::cmp::Ordering::Equal)
217 });
218
219 let mut squashed = Vec::new();
220 let mut current = input[0].clone();
221
222 for next in &input[1..] {
223 if let Some(merged_bounds) = current.merge_if_overlapping(next) {
224 current = merged_bounds;
225 } else {
226 squashed.push(current);
227 current = next.clone();
228 }
229 }
230 squashed.push(current);
231
232 squashed
233}
234
235#[cfg(test)]
236mod tests {
237 use super::{Bounds, combine_parallel_sets, intersect_bounds_sets};
238
239 #[test]
240 fn test_bounds_addition() {
241 let b1 = Bounds::new(Some(-5.0), Some(5.0));
242 let b2 = Bounds::new(Some(-3.0), Some(3.0));
243 assert_eq!(
244 b1.combine_parallel(&b2),
245 vec![Bounds::new(Some(-8.0), Some(8.0))]
246 );
247
248 let b1 = Bounds::new(Some(-15.0), Some(-5.0));
249 let b2 = Bounds::new(Some(-10.0), Some(-2.0));
250 assert_eq!(
251 b1.combine_parallel(&b2),
252 vec![Bounds::new(Some(-25.0), Some(-2.0))]
253 );
254
255 let b1 = Bounds::new(Some(5.0), Some(15.0));
256 let b2 = Bounds::new(Some(2.0), Some(10.0));
257 assert_eq!(
258 b1.combine_parallel(&b2),
259 vec![Bounds::new(Some(2.0), Some(25.0))]
260 );
261
262 let b1 = Bounds::new(Some(5.0), Some(15.0));
263 let b2 = Bounds::new(None, Some(10.0));
264 assert_eq!(
265 b1.combine_parallel(&b2),
266 vec![Bounds::new(None, Some(25.0))]
267 );
268
269 let b1 = Bounds::new(Some(5.0), Some(15.0));
270 let b2 = Bounds::new(Some(-5.0), None);
271 assert_eq!(
272 b1.combine_parallel(&b2),
273 vec![Bounds::new(Some(-5.0), None)]
274 );
275
276 let b1 = Bounds::new(Some(5.0), Some(15.0));
277 let b2 = Bounds::new(None, None);
278 assert_eq!(b1.combine_parallel(&b2), vec![Bounds::new(None, None)]);
279
280 let b1 = Bounds::new(Some(-10.0), Some(-5.0));
281 let b2 = Bounds::new(Some(5.0), Some(15.0));
282 assert_eq!(b1.combine_parallel(&b2), vec![b1, b2]);
283 }
284
285 #[test]
286 fn test_combine_parallel_sets() {
287 let b1 = vec![Bounds::new(Some(-5.0), Some(5.0))];
288 let b2 = vec![
289 Bounds::new(Some(-5.0), Some(-2.0)),
290 Bounds::new(Some(2.0), Some(5.0)),
291 ];
292 let result = combine_parallel_sets(&b1, &b2);
293 assert_eq!(result, vec![Bounds::new(Some(-10.0), Some(10.0))]);
294
295 let b1 = vec![Bounds::new(Some(-5.0), Some(-1.0))];
296 let b2 = vec![
297 Bounds::new(Some(-5.0), Some(-2.0)),
298 Bounds::new(Some(2.0), Some(5.0)),
299 ];
300 let result = combine_parallel_sets(&b1, &b2);
301 assert_eq!(
302 result,
303 vec![
304 Bounds::new(Some(-10.0), Some(-1.0)),
305 Bounds::new(Some(2.0), Some(5.0))
306 ]
307 );
308 }
309
310 #[test]
311 fn test_intersect_bounds_sets() {
312 let vb1 = vec![
313 Bounds::new(Some(-30.0), Some(-10.0)),
314 Bounds::new(Some(10.0), Some(30.0)),
315 ];
316 let vb2 = vec![
317 Bounds::new(Some(-20.0), Some(0.0)),
318 Bounds::new(Some(20.0), Some(40.0)),
319 ];
320 let intersection = intersect_bounds_sets(&vb1, &vb2);
321 assert_eq!(
322 intersection,
323 vec![
324 Bounds::new(Some(-20.0), Some(-10.0)),
325 Bounds::new(Some(20.0), Some(30.0)),
326 ]
327 );
328
329 let vb2 = vec![
330 Bounds::new(Some(-20.0), None),
331 Bounds::new(None, Some(40.0)),
332 ];
333 let intersection = intersect_bounds_sets(&vb1, &vb2);
334 assert_eq!(
335 intersection,
336 vec![
337 Bounds::new(Some(-30.0), Some(-10.0)),
338 Bounds::new(Some(10.0), Some(30.0)),
339 ]
340 );
341
342 let vb2 = vec![
343 Bounds::new(None, Some(-20.0)),
344 Bounds::new(Some(20.0), None),
345 ];
346 let intersection = intersect_bounds_sets(&vb1, &vb2);
347 assert_eq!(
348 intersection,
349 vec![
350 Bounds::new(Some(-30.0), Some(-20.0)),
351 Bounds::new(Some(20.0), Some(30.0)),
352 ]
353 );
354
355 let vb2 = vec![Bounds::new(Some(-25.0), Some(25.0))];
356 let intersection = intersect_bounds_sets(&vb1, &vb2);
357 assert_eq!(
358 intersection,
359 vec![
360 Bounds::new(Some(-25.0), Some(-10.0)),
361 Bounds::new(Some(10.0), Some(25.0)),
362 ]
363 );
364
365 let vb2 = vec![Bounds::new(Some(-5.0), Some(5.0))];
366 let intersection = intersect_bounds_sets(&vb1, &vb2);
367 assert_eq!(intersection, vec![]);
368 }
369
370 #[test]
373 fn intersect_single_point_is_non_empty() {
374 let a = Bounds::new(Some(5.0), Some(10.0));
375 let b = Bounds::new(Some(10.0), Some(15.0));
376 assert_eq!(a.intersect(&b), Some(Bounds::new(Some(10.0), Some(10.0))));
377 }
378
379 #[test]
382 fn squash_merges_touching_endpoints() {
383 let a = [Bounds::new(Some(1.0), Some(5.0))];
384 let b = [Bounds::new(Some(5.0), Some(10.0))];
385 let result = intersect_bounds_sets(
387 &[Bounds::new(Some(0.0), Some(20.0))],
388 &a.iter().chain(b.iter()).cloned().collect::<Vec<_>>(),
389 );
390 assert_eq!(result, vec![Bounds::new(Some(1.0), Some(10.0))]);
391 }
392
393 #[test]
396 fn combine_parallel_preserves_fully_unbounded() {
397 let a = Bounds::<f32>::new(None, None);
398 let b = Bounds::<f32>::new(None, None);
399 assert_eq!(a.combine_parallel(&b), vec![Bounds::new(None, None)]);
400 }
401}