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