vortex_array/stats/
bound.rs

1use std::cmp::Ordering;
2
3use vortex_error::{VortexError, VortexResult};
4
5use crate::partial_ord::{partial_max, partial_min};
6use crate::stats::Precision::{Exact, Inexact};
7use crate::stats::{Precision, StatBound};
8
9/// Interpret the value as a lower bound.
10/// These form a partial order over successively more precise bounds
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct LowerBound<T>(pub(crate) Precision<T>);
13
14impl<T> LowerBound<T> {
15    pub(crate) fn min_value(self) -> T {
16        self.0.into_inner()
17    }
18}
19
20impl<T> LowerBound<T> {
21    pub fn is_exact(&self) -> bool {
22        self.0.is_exact()
23    }
24}
25
26/// The result of the fallible intersection of two bound, defined to avoid `Option`
27/// `IntersectionResult` mixup.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum IntersectionResult<T> {
30    /// An intersection result was found
31    Value(T),
32    /// Values has no intersection.
33    None,
34}
35
36impl<T> IntersectionResult<T> {
37    pub fn ok_or_else<F>(self, err: F) -> VortexResult<T>
38    where
39        F: FnOnce() -> VortexError,
40    {
41        match self {
42            IntersectionResult::Value(v) => Ok(v),
43            IntersectionResult::None => Err(err()),
44        }
45    }
46}
47
48impl<T: PartialOrd + Clone> StatBound<T> for LowerBound<T> {
49    fn lift(value: Precision<T>) -> Self {
50        Self(value)
51    }
52
53    // The meet or tightest covering bound
54    fn union(&self, other: &Self) -> Option<LowerBound<T>> {
55        Some(LowerBound(match (&self.0, &other.0) {
56            (Exact(lhs), Exact(rhs)) => Exact(partial_min(lhs, rhs)?.clone()),
57            (Inexact(lhs), Inexact(rhs)) => Inexact(partial_min(lhs, rhs)?.clone()),
58            (Inexact(lhs), Exact(rhs)) => {
59                if rhs <= lhs {
60                    Exact(rhs.clone())
61                } else {
62                    Inexact(lhs.clone())
63                }
64            }
65            (Exact(lhs), Inexact(rhs)) => {
66                if rhs >= lhs {
67                    Exact(lhs.clone())
68                } else {
69                    Inexact(rhs.clone())
70                }
71            }
72        }))
73    }
74
75    // The join of the smallest intersection of both bounds, this can fail.
76    fn intersection(&self, other: &Self) -> Option<IntersectionResult<LowerBound<T>>> {
77        Some(match (&self.0, &other.0) {
78            (Exact(lhs), Exact(rhs)) => {
79                if lhs == rhs {
80                    IntersectionResult::Value(LowerBound(Exact(lhs.clone())))
81                } else {
82                    // The two intervals do not overlap
83                    IntersectionResult::None
84                }
85            }
86            (Inexact(lhs), Inexact(rhs)) => {
87                IntersectionResult::Value(LowerBound(Inexact(partial_max(lhs, rhs)?.clone())))
88            }
89            (Inexact(lhs), Exact(rhs)) => {
90                if rhs >= lhs {
91                    IntersectionResult::Value(LowerBound(Exact(rhs.clone())))
92                } else {
93                    // The two intervals do not overlap
94                    IntersectionResult::None
95                }
96            }
97            (Exact(lhs), Inexact(rhs)) => {
98                if rhs <= lhs {
99                    IntersectionResult::Value(LowerBound(Exact(lhs.clone())))
100                } else {
101                    // The two intervals do not overlap
102                    IntersectionResult::None
103                }
104            }
105        })
106    }
107
108    fn to_exact(&self) -> Option<&T> {
109        self.0.to_exact()
110    }
111
112    fn into_value(self) -> Precision<T> {
113        self.0
114    }
115}
116
117impl<T: PartialOrd> PartialEq<T> for LowerBound<T> {
118    fn eq(&self, other: &T) -> bool {
119        match &self.0 {
120            Exact(lhs) => lhs == other,
121            _ => false,
122        }
123    }
124}
125
126// We can only compare exact values with values and Precision::inexact values can only be greater than a value
127impl<T: PartialOrd> PartialOrd<T> for LowerBound<T> {
128    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
129        match &self.0 {
130            Exact(lhs) => lhs.partial_cmp(other),
131            Inexact(lhs) => lhs
132                .partial_cmp(other)
133                .and_then(|o| if o == Ordering::Less { None } else { Some(o) }),
134        }
135    }
136}
137
138/// Interpret the value as an upper bound, see `LowerBound` for more details.
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct UpperBound<T>(pub(crate) Precision<T>);
141
142impl<T> UpperBound<T> {
143    pub(crate) fn max_value(self) -> T {
144        self.0.into_inner()
145    }
146}
147
148impl<T: PartialOrd + Clone> StatBound<T> for UpperBound<T> {
149    fn lift(value: Precision<T>) -> Self {
150        Self(value)
151    }
152
153    /// The meet or tightest covering bound
154    fn union(&self, other: &Self) -> Option<UpperBound<T>> {
155        Some(UpperBound(match (&self.0, &other.0) {
156            (Exact(lhs), Exact(rhs)) => Exact(partial_max(lhs, rhs)?.clone()),
157            (Inexact(lhs), Inexact(rhs)) => Inexact(partial_max(lhs, rhs)?.clone()),
158            (Inexact(lhs), Exact(rhs)) => {
159                if rhs >= lhs {
160                    Exact(rhs.clone())
161                } else {
162                    Inexact(lhs.clone())
163                }
164            }
165            (Exact(lhs), Inexact(rhs)) => {
166                if rhs <= lhs {
167                    Exact(lhs.clone())
168                } else {
169                    Inexact(rhs.clone())
170                }
171            }
172        }))
173    }
174
175    fn intersection(&self, other: &Self) -> Option<IntersectionResult<UpperBound<T>>> {
176        Some(match (&self.0, &other.0) {
177            (Exact(lhs), Exact(rhs)) => {
178                if lhs == rhs {
179                    IntersectionResult::Value(UpperBound(Exact(lhs.clone())))
180                } else {
181                    // The two intervals do not overlap
182                    IntersectionResult::None
183                }
184            }
185            (Inexact(lhs), Inexact(rhs)) => {
186                IntersectionResult::Value(UpperBound(Inexact(partial_min(lhs, rhs)?.clone())))
187            }
188            (Inexact(lhs), Exact(rhs)) => {
189                if rhs <= lhs {
190                    IntersectionResult::Value(UpperBound(Exact(rhs.clone())))
191                } else {
192                    // The two intervals do not overlap
193                    IntersectionResult::None
194                }
195            }
196            (Exact(lhs), Inexact(rhs)) => {
197                if rhs >= lhs {
198                    IntersectionResult::Value(UpperBound(Exact(lhs.clone())))
199                } else {
200                    // The two intervals do not overlap
201                    IntersectionResult::None
202                }
203            }
204        })
205    }
206
207    fn to_exact(&self) -> Option<&T> {
208        self.0.to_exact()
209    }
210
211    fn into_value(self) -> Precision<T> {
212        self.0
213    }
214}
215
216impl<T: PartialOrd> PartialEq<T> for UpperBound<T> {
217    fn eq(&self, other: &T) -> bool {
218        match &self.0 {
219            Exact(lhs) => lhs == other,
220            _ => false,
221        }
222    }
223}
224
225// We can only compare exact values with values and Precision::inexact values can only be greater than a value
226impl<T: PartialOrd> PartialOrd<T> for UpperBound<T> {
227    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
228        match &self.0 {
229            Exact(lhs) => lhs.partial_cmp(other),
230            Inexact(lhs) => lhs.partial_cmp(other).and_then(|o| {
231                if o == Ordering::Greater {
232                    None
233                } else {
234                    Some(o)
235                }
236            }),
237        }
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use crate::stats::bound::IntersectionResult;
244    use crate::stats::{LowerBound, Precision, StatBound, UpperBound};
245
246    #[test]
247    fn test_upper_bound_cmp() {
248        let ub = UpperBound(Precision::exact(10i32));
249
250        assert_eq!(ub, 10);
251        assert!(ub > 9);
252        assert!(ub <= 10);
253        assert!(ub <= 10);
254
255        let ub = UpperBound(Precision::inexact(10i32));
256
257        assert_ne!(ub, 10);
258        assert!(ub < 11);
259        // We cannot say anything about a value in the bound.
260        assert!(!(ub >= 9));
261    }
262
263    #[test]
264    fn test_upper_bound_union() {
265        let ub1: UpperBound<i32> = UpperBound(Precision::exact(10i32));
266        let ub2 = UpperBound(Precision::exact(12i32));
267
268        assert_eq!(Some(ub2.clone()), ub1.union(&ub2));
269
270        let ub1: UpperBound<i32> = UpperBound(Precision::inexact(10i32));
271        let ub2 = UpperBound(Precision::exact(12i32));
272
273        assert_eq!(Some(ub2.clone()), ub1.union(&ub2));
274
275        let ub1: UpperBound<i32> = UpperBound(Precision::exact(10i32));
276        let ub2 = UpperBound(Precision::inexact(12i32));
277
278        assert_eq!(Some(ub2.clone()), ub1.union(&ub2));
279
280        let ub1: UpperBound<i32> = UpperBound(Precision::inexact(10i32));
281        let ub2 = UpperBound(Precision::inexact(12i32));
282
283        assert_eq!(Some(ub2.clone()), ub1.union(&ub2))
284    }
285
286    #[test]
287    fn test_upper_bound_intersection() {
288        let ub1: UpperBound<i32> = UpperBound(Precision::exact(10i32));
289        let ub2 = UpperBound(Precision::inexact(12i32));
290
291        assert_eq!(
292            Some(IntersectionResult::Value(ub1.clone())),
293            ub1.intersection(&ub2)
294        );
295
296        let ub1: UpperBound<i32> = UpperBound(Precision::exact(13i32));
297        let ub2 = UpperBound(Precision::inexact(12i32));
298
299        assert_eq!(Some(IntersectionResult::None), ub1.intersection(&ub2));
300    }
301
302    #[test]
303    fn test_lower_bound_intersection() {
304        let lb1: LowerBound<i32> = LowerBound(Precision::exact(12i32));
305        let lb2 = LowerBound(Precision::inexact(10i32));
306
307        assert_eq!(
308            Some(IntersectionResult::Value(lb1.clone())),
309            lb1.intersection(&lb2)
310        );
311
312        let lb1: LowerBound<i32> = LowerBound(Precision::exact(12i32));
313        let lb2 = LowerBound(Precision::inexact(13i32));
314
315        assert_eq!(Some(IntersectionResult::None), lb1.intersection(&lb2));
316    }
317}