vortex_array/expr/stats/
precision.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7
8use vortex_dtype::DType;
9use vortex_error::VortexResult;
10use vortex_scalar::Scalar;
11use vortex_scalar::ScalarValue;
12
13use crate::expr::stats::precision::Precision::Exact;
14use crate::expr::stats::precision::Precision::Inexact;
15
16/// A statistic has a precision `Exact` or `Inexact`. This represents uncertainty in that value.
17/// Exact values are computed, where can inexact values are likely inferred from compute functions.
18///
19/// Inexact statistics form a range of possible values that the statistic could be.
20/// This is statistic specific, for max this will be an upper bound. Meaning that the actual max
21/// in an array is guaranteed to be less than or equal to the inexact value, but equal to the exact
22/// value.
23#[derive(Debug, PartialEq, Eq)]
24pub enum Precision<T> {
25    Exact(T),
26    Inexact(T),
27}
28
29impl<T> Clone for Precision<T>
30where
31    T: Clone,
32{
33    fn clone(&self) -> Self {
34        match self {
35            Exact(e) => Exact(e.clone()),
36            Inexact(ie) => Inexact(ie.clone()),
37        }
38    }
39}
40
41impl<T> Precision<Option<T>> {
42    /// Transpose the `Option<Precision<T>>` into `Option<Precision<T>>`.
43    pub fn transpose(self) -> Option<Precision<T>> {
44        match self {
45            Exact(Some(x)) => Some(Exact(x)),
46            Inexact(Some(x)) => Some(Inexact(x)),
47            Exact(None) | Inexact(None) => None,
48        }
49    }
50}
51
52impl<T> Precision<T>
53where
54    T: Copy,
55{
56    pub fn to_inexact(&self) -> Self {
57        match self {
58            Exact(v) => Exact(*v),
59            Inexact(v) => Inexact(*v),
60        }
61    }
62}
63
64impl<T> Precision<T> {
65    /// Creates an exact value
66    pub fn exact<S: Into<T>>(s: S) -> Precision<T> {
67        Exact(s.into())
68    }
69
70    /// Creates an inexact value
71    pub fn inexact<S: Into<T>>(s: S) -> Precision<T> {
72        Inexact(s.into())
73    }
74
75    /// Pushed the ref into the Precision enum
76    pub fn as_ref(&self) -> Precision<&T> {
77        match self {
78            Exact(val) => Exact(val),
79            Inexact(val) => Inexact(val),
80        }
81    }
82
83    /// Converts `self` into an inexact bound
84    pub fn into_inexact(self) -> Self {
85        match self {
86            Exact(val) => Inexact(val),
87            Inexact(_) => self,
88        }
89    }
90
91    /// Returns the exact value from the bound, if that value is inexact, otherwise `None`.
92    pub fn as_exact(self) -> Option<T> {
93        match self {
94            Exact(val) => Some(val),
95            _ => None,
96        }
97    }
98
99    /// Returns the exact value from the bound, if that value is inexact, otherwise `None`.
100    pub fn as_inexact(self) -> Option<T> {
101        match self {
102            Inexact(val) => Some(val),
103            _ => None,
104        }
105    }
106
107    /// True iff self == Exact(_)
108    pub fn is_exact(&self) -> bool {
109        matches!(self, Exact(_))
110    }
111
112    /// Map the value of either precision value
113    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Precision<U> {
114        match self {
115            Exact(value) => Exact(f(value)),
116            Inexact(value) => Inexact(f(value)),
117        }
118    }
119
120    /// Zip two `Precision` values into a tuple, keeping the inexactness if any.
121    pub fn zip<U>(self, other: Precision<U>) -> Precision<(T, U)> {
122        match (self, other) {
123            (Exact(lhs), Exact(rhs)) => Exact((lhs, rhs)),
124            (Inexact(lhs), Exact(rhs))
125            | (Exact(lhs), Inexact(rhs))
126            | (Inexact(lhs), Inexact(rhs)) => Inexact((lhs, rhs)),
127        }
128    }
129
130    /// Similar to `map` but handles functions that can fail.
131    pub fn try_map<U, F: FnOnce(T) -> VortexResult<U>>(self, f: F) -> VortexResult<Precision<U>> {
132        let precision = match self {
133            Exact(value) => Exact(f(value)?),
134            Inexact(value) => Inexact(f(value)?),
135        };
136        Ok(precision)
137    }
138
139    /// Unwrap the underlying value
140    pub fn into_inner(self) -> T {
141        match self {
142            Exact(val) | Inexact(val) => val,
143        }
144    }
145}
146
147impl<T: Display> Display for Precision<T> {
148    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
149        match self {
150            Exact(v) => {
151                write!(f, "{v}")
152            }
153            Inexact(v) => {
154                write!(f, "~{v}")
155            }
156        }
157    }
158}
159
160impl<T: PartialEq> PartialEq<T> for Precision<T> {
161    fn eq(&self, other: &T) -> bool {
162        match self {
163            Exact(v) => v == other,
164            _ => false,
165        }
166    }
167}
168
169impl Precision<ScalarValue> {
170    pub fn into_scalar(self, dtype: DType) -> Precision<Scalar> {
171        self.map(|v| Scalar::new(dtype, v))
172    }
173}
174
175impl Precision<&ScalarValue> {
176    pub fn into_scalar(self, dtype: DType) -> Precision<Scalar> {
177        self.map(|v| Scalar::new(dtype, v.clone()))
178    }
179}