Skip to main content

cb_digest/
ddsketch.rs

1use std::error;
2use std::fmt;
3
4use crate::config::Config;
5use crate::store::Store;
6
7#[cfg(feature = "use_serde")]
8use serde::{Deserialize, Serialize};
9
10#[cfg(feature = "use_rkyv")]
11use rkyv::{Deserialize, Serialize, Archive};
12
13type Result<T> = std::result::Result<T, DDSketchError>;
14
15/// General error type for DDSketch, represents either an invalid quantile or an
16/// incompatible merge operation.
17///
18#[derive(Debug, Clone)]
19pub enum DDSketchError {
20    Quantile,
21    Merge,
22}
23impl fmt::Display for DDSketchError {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        match self {
26            DDSketchError::Quantile => {
27                write!(f, "Invalid quantile, must be between 0 and 1 (inclusive)")
28            }
29            DDSketchError::Merge => write!(f, "Can not merge sketches with different configs"),
30        }
31    }
32}
33impl error::Error for DDSketchError {
34    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
35        // Generic
36        None
37    }
38}
39
40/// This struct represents a [DDSketch](https://arxiv.org/pdf/1908.10693.pdf)
41#[derive(Clone)]
42#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "use_rkyv", derive(Serialize, Deserialize, Archive))]
44pub struct DDSketch {
45    config: Config,
46    store: Store,
47    negative_store: Store,
48    min: f64,
49    max: f64,
50    sum: f64,
51    zero_count: u64,
52}
53
54impl Default for DDSketch {
55    fn default() -> Self {
56        Self::new(Default::default())
57    }
58}
59
60// XXX: functions should return Option<> in the case of empty
61impl DDSketch {
62    /// Construct a `DDSketch`. Requires a `Config` specifying the parameters of the sketch
63    pub fn new(config: Config) -> Self {
64        DDSketch {
65            config,
66            store: Store::new(config.max_num_bins as usize),
67            negative_store: Store::new(config.max_num_bins as usize),
68            min: f64::INFINITY,
69            max: f64::NEG_INFINITY,
70            sum: 0.0,
71            zero_count: 0,
72        }
73    }
74
75    /// Add the sample to the sketch
76    pub fn add(&mut self, v: f64) {
77        if v > self.config.min_possible() {
78            let key = self.config.key(v);
79            self.store.add(key);
80        } else if v < -self.config.min_possible() {
81            let key = self.config.key(-v);
82            self.negative_store.add(key);
83        } else {
84            self.zero_count += 1;
85        }
86
87        if v < self.min {
88            self.min = v;
89        }
90        if self.max < v {
91            self.max = v;
92        }
93        self.sum += v;
94    }
95
96    /// Return the quantile value for quantiles between 0.0 and 1.0. Result is an error, represented
97    /// as DDSketchError::Quantile if the requested quantile is outside of that range.
98    ///
99    /// If the sketch is empty the result is None, else Some(v) for the quantile value.
100    pub fn quantile(&self, q: f64) -> Result<Option<f64>> {
101        if q < 0.0 || q > 1.0 {
102            return Err(DDSketchError::Quantile);
103        }
104
105        if self.empty() {
106            return Ok(None);
107        }
108
109        if q == 0.0 {
110            return Ok(Some(self.min));
111        } else if q == 1.0 {
112            return Ok(Some(self.max));
113        }
114
115        let rank = (q * (self.count() as f64 - 1.0)) as u64;
116        let quantile;
117        if rank < self.negative_store.count() {
118            let reversed_rank = self.negative_store.count() - rank - 1;
119            let key = self.negative_store.key_at_rank(reversed_rank);
120            quantile = -self.config.value(key);
121        } else if rank < self.zero_count + self.negative_store.count() {
122            quantile = 0.0;
123        } else {
124            let key = self
125                .store
126                .key_at_rank(rank - self.zero_count - self.negative_store.count());
127            quantile = self.config.value(key);
128        }
129
130        Ok(Some(quantile))
131    }
132
133    /// Returns the minimum value seen, or None if sketch is empty
134    pub fn min(&self) -> Option<f64> {
135        if self.empty() {
136            None
137        } else {
138            Some(self.min)
139        }
140    }
141
142    /// Returns the maximum value seen, or None if sketch is empty
143    pub fn max(&self) -> Option<f64> {
144        if self.empty() {
145            None
146        } else {
147            Some(self.max)
148        }
149    }
150
151    /// Returns the sum of values seen, or None if sketch is empty
152    pub fn sum(&self) -> Option<f64> {
153        if self.empty() {
154            None
155        } else {
156            Some(self.sum)
157        }
158    }
159
160    /// Returns the number of values added to the sketch
161    pub fn count(&self) -> usize {
162        (self.store.count() + self.zero_count + self.negative_store.count()) as usize
163    }
164
165    /// Returns the length of the underlying `Store`. This is mainly only useful for understanding
166    /// how much the sketch has grown given the inserted values.
167    pub fn length(&self) -> usize {
168        self.store.length() as usize + self.negative_store.length() as usize
169    }
170
171    /// Merge the contents of another sketch into this one. The sketch that is merged into this one
172    /// is unchanged after the merge.
173    pub fn merge(&mut self, o: &DDSketch) -> Result<()> {
174        if self.config != o.config {
175            return Err(DDSketchError::Merge);
176        }
177
178        let was_empty = self.store.count() == 0;
179
180        // Merge the stores
181        self.store.merge(&o.store);
182        self.negative_store.merge(&o.negative_store);
183        self.zero_count += o.zero_count;
184
185        // Need to ensure we don't override min/max with initializers
186        // if either store were empty
187        if was_empty {
188            self.min = o.min;
189            self.max = o.max;
190        } else if o.store.count() > 0 {
191            if o.min < self.min {
192                self.min = o.min
193            }
194            if o.max > self.max {
195                self.max = o.max;
196            }
197        }
198        self.sum += o.sum;
199
200        Ok(())
201    }
202
203    fn empty(&self) -> bool {
204        self.count() == 0
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use approx::assert_relative_eq;
211
212    use crate::Config;
213    use crate::DDSketch;
214
215    #[test]
216    fn test_add_zero() {
217        let alpha = 0.01;
218        let c = Config::new(alpha, 2048, 10e-9);
219        let mut dd = DDSketch::new(c);
220        dd.add(0.0);
221    }
222
223    #[test]
224    fn test_quartiles() {
225        let alpha = 0.01;
226        let c = Config::new(alpha, 2048, 10e-9);
227        let mut dd = DDSketch::new(c);
228
229        // Initialize sketch with {1.0, 2.0, 3.0, 4.0}
230        for i in 1..5 {
231            dd.add(i as f64);
232        }
233
234        // We expect the following mappings from quantile to value:
235        // [0,0.33]: 1.0, (0.34,0.66]: 2.0, (0.67,0.99]: 3.0, (0.99, 1.0]: 4.0
236        let test_cases = vec![
237            (0.0, 1.0),
238            (0.25, 1.0),
239            (0.33, 1.0),
240            (0.34, 2.0),
241            (0.5, 2.0),
242            (0.66, 2.0),
243            (0.67, 3.0),
244            (0.75, 3.0),
245            (0.99, 3.0),
246            (1.0, 4.0),
247        ];
248
249        for (q, val) in test_cases {
250            assert_relative_eq!(dd.quantile(q).unwrap().unwrap(), val, max_relative = alpha);
251        }
252    }
253
254    #[test]
255    fn test_neg_quartiles() {
256        let alpha = 0.01;
257        let c = Config::new(alpha, 2048, 10e-9);
258        let mut dd = DDSketch::new(c);
259
260        // Initialize sketch with {1.0, 2.0, 3.0, 4.0}
261        for i in 1..5 {
262            dd.add(-i as f64);
263        }
264
265        let test_cases = vec![
266            (0.0, -4.0),
267            (0.25, -4.0),
268            (0.5, -3.0),
269            (0.75, -2.0),
270            (1.0, -1.0),
271        ];
272
273        for (q, val) in test_cases {
274            assert_relative_eq!(dd.quantile(q).unwrap().unwrap(), val, max_relative = alpha);
275        }
276    }
277
278    #[test]
279    fn test_simple_quantile() {
280        let c = Config::defaults();
281        let mut dd = DDSketch::new(c);
282
283        for i in 1..101 {
284            dd.add(i as f64);
285        }
286
287        assert_eq!(dd.quantile(0.95).unwrap().unwrap().ceil(), 95.0);
288
289        assert!(dd.quantile(-1.01).is_err());
290        assert!(dd.quantile(1.01).is_err());
291    }
292
293    #[test]
294    fn test_empty_sketch() {
295        let c = Config::defaults();
296        let dd = DDSketch::new(c);
297
298        assert_eq!(dd.quantile(0.98).unwrap(), None);
299        assert_eq!(dd.max(), None);
300        assert_eq!(dd.min(), None);
301        assert_eq!(dd.sum(), None);
302        assert_eq!(dd.count(), 0);
303
304        assert!(dd.quantile(1.01).is_err());
305    }
306
307    #[test]
308    fn test_basic_histogram_data() {
309        let values = &[
310            0.754225035,
311            0.752900282,
312            0.752812246,
313            0.752602367,
314            0.754310155,
315            0.753525981,
316            0.752981082,
317            0.752715536,
318            0.751667941,
319            0.755079054,
320            0.753528150,
321            0.755188464,
322            0.752508723,
323            0.750064549,
324            0.753960428,
325            0.751139298,
326            0.752523560,
327            0.753253428,
328            0.753498342,
329            0.751858358,
330            0.752104636,
331            0.753841300,
332            0.754467374,
333            0.753814334,
334            0.750881719,
335            0.753182556,
336            0.752576884,
337            0.753945708,
338            0.753571911,
339            0.752314573,
340            0.752586651,
341        ];
342
343        let c = Config::defaults();
344        let mut dd = DDSketch::new(c);
345
346        for value in values {
347            dd.add(*value);
348        }
349
350        assert_eq!(dd.max(), Some(0.755188464));
351        assert_eq!(dd.min(), Some(0.750064549));
352        assert_eq!(dd.count(), 31);
353        assert_eq!(dd.sum(), Some(23.343630625000003));
354
355        assert!(dd.quantile(0.25).unwrap().is_some());
356        assert!(dd.quantile(0.5).unwrap().is_some());
357        assert!(dd.quantile(0.75).unwrap().is_some());
358    }
359
360    #[test]
361    fn test_length() {
362        let mut dd = DDSketch::default();
363        assert_eq!(dd.length(), 0);
364
365        dd.add(1.0);
366        assert_eq!(dd.length(), 128);
367        dd.add(2.0);
368        dd.add(3.0);
369        assert_eq!(dd.length(), 128);
370
371        dd.add(-1.0);
372        assert_eq!(dd.length(), 256);
373        dd.add(-2.0);
374        dd.add(-3.0);
375        assert_eq!(dd.length(), 256);
376    }
377}