vortex_array/stats/
precision.rs

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