Skip to main content

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