vortex_array/stats/
precision.rs

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