vortex_array/expr/stats/
bound.rs1use std::cmp::Ordering;
5
6use vortex_error::VortexError;
7use vortex_error::VortexResult;
8
9use crate::expr::stats::Precision;
10use crate::expr::stats::Precision::Absent;
11use crate::expr::stats::Precision::Exact;
12use crate::expr::stats::Precision::Inexact;
13use crate::expr::stats::StatBound;
14use crate::partial_ord::partial_max;
15use crate::partial_ord::partial_min;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct LowerBound<T>(pub(crate) Precision<T>);
21
22impl<T> LowerBound<T> {
23 pub(crate) fn min_value(self) -> Option<T> {
24 self.0.into_inner()
25 }
26}
27
28impl<T> LowerBound<T> {
29 pub fn is_exact(&self) -> bool {
30 self.0.is_exact()
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum IntersectionResult<T> {
38 Value(T),
40 Empty,
42}
43
44impl<T> IntersectionResult<T> {
45 pub fn ok_or_else<F>(self, err: F) -> VortexResult<T>
46 where
47 F: FnOnce() -> VortexError,
48 {
49 match self {
50 IntersectionResult::Value(v) => Ok(v),
51 IntersectionResult::Empty => Err(err()),
52 }
53 }
54}
55
56impl<T: PartialOrd + Clone> StatBound<T> for LowerBound<T> {
57 fn lift(value: Precision<T>) -> Self {
58 Self(value)
59 }
60
61 fn union(&self, other: &Self) -> Option<LowerBound<T>> {
62 use Precision::*;
63
64 Some(LowerBound(match (&self.0, &other.0) {
65 (Exact(lhs), Exact(rhs)) => Exact(partial_min(lhs, rhs)?.clone()),
66 (Inexact(lhs), Inexact(rhs)) => Inexact(partial_min(lhs, rhs)?.clone()),
67 (Inexact(lhs), Exact(rhs)) => {
68 if rhs <= lhs {
69 Exact(rhs.clone())
70 } else {
71 Inexact(lhs.clone())
72 }
73 }
74 (Exact(lhs), Inexact(rhs)) => {
75 if rhs >= lhs {
76 Exact(lhs.clone())
77 } else {
78 Inexact(rhs.clone())
79 }
80 }
81 (Absent, _) | (_, Absent) => return None,
82 }))
83 }
84
85 fn intersection(&self, other: &Self) -> Option<IntersectionResult<LowerBound<T>>> {
87 Some(match (&self.0, &other.0) {
88 (Exact(lhs), Exact(rhs)) => {
89 if lhs == rhs {
90 IntersectionResult::Value(LowerBound(Exact(lhs.clone())))
91 } else {
92 IntersectionResult::Empty
94 }
95 }
96 (Inexact(lhs), Inexact(rhs)) => {
97 IntersectionResult::Value(LowerBound(Inexact(partial_max(lhs, rhs)?.clone())))
98 }
99 (Inexact(lhs), Exact(rhs)) => {
100 if rhs >= lhs {
101 IntersectionResult::Value(LowerBound(Exact(rhs.clone())))
102 } else {
103 IntersectionResult::Empty
105 }
106 }
107 (Exact(lhs), Inexact(rhs)) => {
108 if rhs <= lhs {
109 IntersectionResult::Value(LowerBound(Exact(lhs.clone())))
110 } else {
111 IntersectionResult::Empty
113 }
114 }
115 (Absent, _) | (_, Absent) => return None,
116 })
117 }
118
119 fn to_exact(&self) -> Option<&T> {
120 self.0.to_exact()
121 }
122
123 fn into_value(self) -> Precision<T> {
124 self.0
125 }
126}
127
128impl<T: PartialOrd> PartialEq<T> for LowerBound<T> {
129 fn eq(&self, other: &T) -> bool {
130 match &self.0 {
131 Exact(lhs) => lhs == other,
132 _ => false,
133 }
134 }
135}
136
137impl<T: PartialOrd> PartialOrd<T> for LowerBound<T> {
139 fn partial_cmp(&self, other: &T) -> Option<Ordering> {
140 match &self.0 {
141 Exact(lhs) => lhs.partial_cmp(other),
142 Inexact(lhs) => lhs.partial_cmp(other).filter(|&o| o != Ordering::Less),
143 Absent => None,
144 }
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct UpperBound<T>(pub(crate) Precision<T>);
151
152impl<T> UpperBound<T> {
153 pub(crate) fn max_value(self) -> Option<T> {
154 self.0.into_inner()
155 }
156}
157
158impl<T: PartialOrd + Clone> StatBound<T> for UpperBound<T> {
159 fn lift(value: Precision<T>) -> Self {
160 Self(value)
161 }
162
163 fn union(&self, other: &Self) -> Option<UpperBound<T>> {
165 Some(UpperBound(match (&self.0, &other.0) {
166 (Exact(lhs), Exact(rhs)) => Exact(partial_max(lhs, rhs)?.clone()),
167 (Inexact(lhs), Inexact(rhs)) => Inexact(partial_max(lhs, rhs)?.clone()),
168 (Inexact(lhs), Exact(rhs)) => {
169 if rhs >= lhs {
170 Exact(rhs.clone())
171 } else {
172 Inexact(lhs.clone())
173 }
174 }
175 (Exact(lhs), Inexact(rhs)) => {
176 if rhs <= lhs {
177 Exact(lhs.clone())
178 } else {
179 Inexact(rhs.clone())
180 }
181 }
182 (Absent, _) | (_, Absent) => return None,
183 }))
184 }
185
186 fn intersection(&self, other: &Self) -> Option<IntersectionResult<UpperBound<T>>> {
187 Some(match (&self.0, &other.0) {
188 (Exact(lhs), Exact(rhs)) => {
189 if lhs == rhs {
190 IntersectionResult::Value(UpperBound(Exact(lhs.clone())))
191 } else {
192 IntersectionResult::Empty
194 }
195 }
196 (Inexact(lhs), Inexact(rhs)) => {
197 IntersectionResult::Value(UpperBound(Inexact(partial_min(lhs, rhs)?.clone())))
198 }
199 (Inexact(lhs), Exact(rhs)) => {
200 if rhs <= lhs {
201 IntersectionResult::Value(UpperBound(Exact(rhs.clone())))
202 } else {
203 IntersectionResult::Empty
205 }
206 }
207 (Exact(lhs), Inexact(rhs)) => {
208 if rhs >= lhs {
209 IntersectionResult::Value(UpperBound(Exact(lhs.clone())))
210 } else {
211 IntersectionResult::Empty
213 }
214 }
215 (Absent, _) | (_, Absent) => return None,
216 })
217 }
218
219 fn to_exact(&self) -> Option<&T> {
220 self.0.to_exact()
221 }
222
223 fn into_value(self) -> Precision<T> {
224 self.0
225 }
226}
227
228impl<T: PartialOrd> PartialEq<T> for UpperBound<T> {
229 fn eq(&self, other: &T) -> bool {
230 match &self.0 {
231 Exact(lhs) => lhs == other,
232 _ => false,
233 }
234 }
235}
236
237impl<T: PartialOrd> PartialOrd<T> for UpperBound<T> {
239 fn partial_cmp(&self, other: &T) -> Option<Ordering> {
240 match &self.0 {
241 Exact(lhs) => lhs.partial_cmp(other),
242 Inexact(lhs) => lhs.partial_cmp(other).filter(|&o| o != Ordering::Greater),
243 Absent => None,
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use crate::expr::stats::LowerBound;
251 use crate::expr::stats::Precision;
252 use crate::expr::stats::StatBound;
253 use crate::expr::stats::UpperBound;
254 use crate::expr::stats::bound::IntersectionResult;
255
256 #[test]
257 fn test_upper_bound_cmp() {
258 let ub = UpperBound(Precision::exact(10i32));
259
260 assert_eq!(ub, 10);
261 assert!(ub > 9);
262 assert!(ub <= 10);
263 assert!(ub <= 10);
264
265 let ub = UpperBound(Precision::inexact(10i32));
266
267 assert_ne!(ub, 10);
268 assert!(ub < 11);
269 assert!(!(ub >= 9));
271 }
272
273 #[test]
274 fn test_upper_bound_union() {
275 let ub1: UpperBound<i32> = UpperBound(Precision::exact(10i32));
276 let ub2 = UpperBound(Precision::exact(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::exact(12i32));
282
283 assert_eq!(Some(ub2.clone()), ub1.union(&ub2));
284
285 let ub1: UpperBound<i32> = UpperBound(Precision::exact(10i32));
286 let ub2 = UpperBound(Precision::inexact(12i32));
287
288 assert_eq!(Some(ub2.clone()), ub1.union(&ub2));
289
290 let ub1: UpperBound<i32> = UpperBound(Precision::inexact(10i32));
291 let ub2 = UpperBound(Precision::inexact(12i32));
292
293 assert_eq!(Some(ub2.clone()), ub1.union(&ub2))
294 }
295
296 #[test]
297 fn test_upper_bound_intersection() {
298 let ub1: UpperBound<i32> = UpperBound(Precision::exact(10i32));
299 let ub2 = UpperBound(Precision::inexact(12i32));
300
301 assert_eq!(
302 Some(IntersectionResult::Value(ub1.clone())),
303 ub1.intersection(&ub2)
304 );
305
306 let ub1: UpperBound<i32> = UpperBound(Precision::exact(13i32));
307 let ub2 = UpperBound(Precision::inexact(12i32));
308
309 assert_eq!(Some(IntersectionResult::Empty), ub1.intersection(&ub2));
310 }
311
312 #[test]
313 fn test_lower_bound_intersection() {
314 let lb1: LowerBound<i32> = LowerBound(Precision::exact(12i32));
315 let lb2 = LowerBound(Precision::inexact(10i32));
316
317 assert_eq!(
318 Some(IntersectionResult::Value(lb1.clone())),
319 lb1.intersection(&lb2)
320 );
321
322 let lb1: LowerBound<i32> = LowerBound(Precision::exact(12i32));
323 let lb2 = LowerBound(Precision::inexact(13i32));
324
325 assert_eq!(Some(IntersectionResult::Empty), lb1.intersection(&lb2));
326 }
327}