windowed_stats/experimental/series/
statistic.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Statistics and sample aggregation.
6
7use num::{Num, NumCast, Zero};
8use std::convert::Infallible;
9use std::fmt::Debug;
10use std::num::NonZeroUsize;
11use std::{cmp, io};
12use thiserror::Error;
13
14use crate::experimental::clock::MonotonicityError;
15use crate::experimental::series::buffer::{BufferStrategy, RingBuffer};
16use crate::experimental::series::interpolation::InterpolationKind;
17use crate::experimental::series::interval::SamplingInterval;
18use crate::experimental::series::{BitSet, Counter, DataSemantic, Gauge};
19use crate::experimental::vec1::Vec1;
20
21pub mod recipe {
22    //! Type definitions and respellings of common or interesting statistic types.
23
24    use crate::experimental::series::statistic::{ArithmeticMean, Transform};
25
26    pub type ArithmeticMeanTransform<T, A> = Transform<ArithmeticMean<T>, A>;
27}
28
29/// Sample folding error.
30///
31/// Describes errors that can occur when folding a sample into a [`Statistic`].
32#[derive(Debug, Error)]
33#[non_exhaustive]
34pub enum FoldError {
35    #[error(transparent)]
36    Io(#[from] io::Error),
37    #[error(transparent)]
38    Monotonicity(#[from] MonotonicityError),
39    #[error("overflow in statistic computation")]
40    Overflow,
41    // TODO(https://fxbug.dev/432323121): Provide the data that could not be sent into the channel
42    //                                    here. This requires an input type parameter on
43    //                                    `FoldError`, a `dyn Any` trait object, etc.
44    #[error("timed sample dropped: channel is closed or buffer is exhausted")]
45    Buffer,
46    #[error("failed to fold one or more buffered samples")]
47    Flush(Vec1<FoldError>),
48}
49
50impl From<Infallible> for FoldError {
51    fn from(_: Infallible) -> Self {
52        unreachable!()
53    }
54}
55
56/// A statistical function type that folds samples into an aggregation.
57pub trait Statistic: Clone {
58    /// The type of data semantic associated with samples.
59    type Semantic: DataSemantic;
60    /// The type of samples.
61    type Sample: Clone;
62    /// The type of the statistical aggregation.
63    type Aggregation: Clone;
64
65    /// Folds a sample into the aggregation of the statistic.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if folding the sample causes an overflow.
70    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError>;
71
72    /// Folds a sample into the aggregation of a statistic `n` (one or more) times.
73    ///
74    /// For some `Statistic`s, this function is significantly more efficient than multiple calls to
75    /// [`fold`]. For example, [`Max`] need not fold more than once for any given sample regardless
76    /// of `n`. Prefer this function when the same sample must be folded more than once.
77    ///
78    /// # Errors
79    ///
80    /// Returns an error if folding the sample causes an overflow.
81    fn fill(&mut self, sample: Self::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
82        for _ in 0..n.get() {
83            self.fold(sample.clone())?;
84        }
85        Ok(())
86    }
87
88    /// Resets the state (and aggregation) of the statistic.
89    ///
90    /// The state of a statistic after a reset is arbitrary, but most types reset to a reasonable
91    /// initial state via `Default`. Some types do nothing, such as [`LatchMax`], which operates
92    /// across [`SamplingInterval`]s.
93    ///
94    /// Statistics can be configured to reset to any given state via [`Reset`].
95    ///
96    /// [`LatchMax`] crate::experimental::series::statistic::LatchMax
97    /// [`Reset`] crate::experimental::series::statistic::Reset
98    fn reset(&mut self);
99
100    /// Gets the statistical aggregation.
101    ///
102    /// Returns `None` if no aggregation is ready.
103    fn aggregation(&self) -> Option<Self::Aggregation>;
104}
105
106/// Extension methods for `Statistic`s.
107pub trait StatisticExt: Statistic {
108    /// Gets the statistical aggregation and resets the statistic.
109    fn get_aggregation_and_reset(&mut self) -> Option<Self::Aggregation> {
110        let aggregation = self.aggregation();
111        self.reset();
112        aggregation
113    }
114}
115
116impl<F> StatisticExt for F where F: Statistic {}
117
118/// A [`Statistic`] for which an in-memory ring buffer encoding is defined.
119///
120/// A serial statistic can be used with a [`TimeMatrix`]. A series of aggregations are stored in an
121/// instance of the defined buffer, which is typically optimized for the data semantic and data
122/// type of the statistic.
123///
124/// [`TimeMatrix`]: crate::experimental::series::TimeMatrix
125pub trait SerialStatistic<P>: Statistic
126where
127    P: InterpolationKind,
128{
129    type Buffer: Clone + RingBuffer<Self::Aggregation>;
130
131    fn buffer(interval: &SamplingInterval) -> Self::Buffer;
132}
133
134// This blanket implementation essentially forwards the `BufferStrategy` implementation of the
135// associated `DataSemantic`. `DataSemantic` types like `Gauge` provide the core implementation and
136// the `SerialStatistic` trait relates the necessary types and provides a more brief way to express
137// bounds.
138impl<F, P> SerialStatistic<P> for F
139where
140    F: Statistic,
141    // The data semantic must implement `BufferStrategy` for the `Statistic`'s aggregation type and
142    // the given `Interpolation` type `P`.
143    F::Semantic: BufferStrategy<F::Aggregation, P>,
144    P: InterpolationKind,
145{
146    type Buffer = <F::Semantic as BufferStrategy<F::Aggregation, P>>::Buffer;
147
148    fn buffer(interval: &SamplingInterval) -> Self::Buffer {
149        <F::Semantic>::buffer(interval)
150    }
151}
152
153/// The associated data semantic type of a `Statistic`.
154pub type Semantic<F> = <F as Statistic>::Semantic;
155
156/// The associated metadata type of a `Statistic`.
157pub type Metadata<F> = <Semantic<F> as DataSemantic>::Metadata;
158
159/// The associated sample type of a `Statistic`.
160pub type Sample<F> = <F as Statistic>::Sample;
161
162/// The associated aggregation type of a `Statistic`.
163pub type Aggregation<F> = <F as Statistic>::Aggregation;
164
165/// Arithmetic mean statistic.
166///
167/// The arithmetic mean sums samples within their domain and computes the mean as a real number
168/// represented using floating-point. For floating-point samples, `NaN`s are discarded and the sum
169/// saturates to infinity without error.
170///
171/// This statistic is sensitive to overflow in the count of samples.
172#[derive(Clone, Debug)]
173pub struct ArithmeticMean<T> {
174    /// The sum of samples.
175    sum: T,
176    /// The count of samples.
177    n: u64,
178}
179
180impl<T> ArithmeticMean<T> {
181    pub fn with_sum(sum: T) -> Self {
182        ArithmeticMean { sum, n: 0 }
183    }
184
185    fn fill(&mut self, sample: T, n: NonZeroUsize) -> Result<(), FoldError>
186    where
187        Self: Statistic<Sample = T>,
188        T: Clone + Num + NumCast,
189    {
190        Ok(match num::cast::<_, T>(n.get()) {
191            Some(m) => {
192                self.fold(sample * m)?;
193                self.increment((n.get() as u64) - 1)?;
194            }
195            _ => {
196                for _ in 0..n.get() {
197                    self.fold(sample.clone())?;
198                }
199            }
200        })
201    }
202
203    fn increment(&mut self, m: u64) -> Result<(), FoldError> {
204        // TODO(https://fxbug.dev/351848566): On overflow, either saturate `self.n` or leave both
205        //                                    `self.sum` and `self.n` unchanged (here and in
206        //                                    `Sampler::fold` and `Fill::fill` implementations; see
207        //                                    below).
208        self.n.checked_add(m).inspect(|sum| self.n = *sum).map(|_| ()).ok_or(FoldError::Overflow)
209    }
210
211    fn reset(&mut self)
212    where
213        T: Zero,
214    {
215        *self = Default::default()
216    }
217}
218
219impl<T> Default for ArithmeticMean<T>
220where
221    T: Zero,
222{
223    fn default() -> Self {
224        ArithmeticMean::with_sum(T::zero())
225    }
226}
227
228impl Statistic for ArithmeticMean<f32> {
229    type Semantic = Gauge;
230    type Sample = f32;
231    type Aggregation = f32;
232
233    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
234        // Discard `NaN` terms and avoid some floating-point exceptions.
235        self.sum = match sample {
236            _ if sample.is_nan() => self.sum,
237            sample if sample.is_infinite() => sample,
238            // Because neither term is `NaN` and `sample` is finite, this saturates at negative or
239            // positive infinity.
240            sample => self.sum + sample,
241        };
242        self.increment(1)
243    }
244
245    fn fill(&mut self, sample: Self::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
246        ArithmeticMean::fill(self, sample, n)
247    }
248
249    fn reset(&mut self) {
250        ArithmeticMean::reset(self)
251    }
252
253    fn aggregation(&self) -> Option<Self::Aggregation> {
254        // This is lossy and lossiness correlates to the magnitude of `n`. See details of
255        // `u64 as f32` casts.
256        (self.n > 0).then(|| self.sum / (self.n as f32))
257    }
258}
259
260impl Statistic for ArithmeticMean<i64> {
261    type Semantic = Gauge;
262    type Sample = i64;
263    type Aggregation = f32;
264
265    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
266        // TODO(https://fxbug.dev/351848566): On overflow, either saturate `self.n` or leave both
267        //                                    `self.sum` and `self.n` unchanged (here and in
268        //                                    `Sampler::fold` and `Fill::fill` implementations; see
269        //                                    below).
270        self.sum = self.sum.checked_add(sample).ok_or(FoldError::Overflow)?;
271        self.increment(1)
272    }
273
274    fn fill(&mut self, sample: Self::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
275        ArithmeticMean::fill(self, sample, n)
276    }
277
278    fn reset(&mut self) {
279        ArithmeticMean::reset(self)
280    }
281
282    fn aggregation(&self) -> Option<Self::Aggregation> {
283        // This is lossy and lossiness correlates to the magnitude of `n`. See details of
284        // `i64 as f32` casts.
285        (self.n > 0).then(|| self.sum as f32 / (self.n as f32))
286    }
287}
288
289impl Statistic for ArithmeticMean<u64> {
290    type Semantic = Gauge;
291    type Sample = u64;
292    type Aggregation = f32;
293
294    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
295        // TODO(https://fxbug.dev/351848566): On overflow, either saturate `self.n` or leave both
296        //                                    `self.sum` and `self.n` unchanged (here and in
297        //                                    `Sampler::fold` and `Fill::fill` implementations; see
298        //                                    below).
299        self.sum = self.sum.checked_add(sample).ok_or(FoldError::Overflow)?;
300        self.increment(1)
301    }
302
303    fn fill(&mut self, sample: Self::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
304        ArithmeticMean::fill(self, sample, n)
305    }
306
307    fn reset(&mut self) {
308        ArithmeticMean::reset(self)
309    }
310
311    fn aggregation(&self) -> Option<Self::Aggregation> {
312        // This is lossy and lossiness correlates to the magnitude of `n`. See details of
313        // `u64 as f32` casts.
314        (self.n > 0).then(|| self.sum as f32 / (self.n as f32))
315    }
316}
317
318/// Sum statistic.
319///
320/// The sum directly computes the aggregation in the domain of samples.
321///
322/// This statistic is sensitive to overflow in the sum of samples.
323#[derive(Clone, Debug)]
324pub struct Sum<T> {
325    /// The sum of samples.
326    sum: T,
327}
328
329impl<T> Sum<T> {
330    pub fn with_sum(sum: T) -> Self {
331        Sum { sum }
332    }
333
334    fn fill(&mut self, sample: T, n: NonZeroUsize) -> Result<(), FoldError>
335    where
336        Self: Statistic<Sample = T>,
337        T: Clone + Num + NumCast,
338    {
339        if let Some(n) = num::cast::<_, T>(n.get()) {
340            self.fold(sample * n)
341        } else {
342            Ok(for _ in 0..n.get() {
343                self.fold(sample.clone())?;
344            })
345        }
346    }
347
348    fn reset(&mut self)
349    where
350        T: Zero,
351    {
352        *self = Default::default();
353    }
354}
355
356impl<T> Default for Sum<T>
357where
358    T: Zero,
359{
360    fn default() -> Self {
361        Sum::with_sum(T::zero())
362    }
363}
364
365impl Statistic for Sum<u64> {
366    type Semantic = Gauge;
367    type Sample = u64;
368    type Aggregation = u64;
369
370    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
371        // TODO(https://fxbug.dev/351848566): Saturate `self.sum` on overflow.
372        self.sum
373            .checked_add(sample)
374            .inspect(|sum| self.sum = *sum)
375            .map(|_| ())
376            .ok_or(FoldError::Overflow)
377    }
378
379    fn fill(&mut self, sample: Self::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
380        Sum::fill(self, sample, n)
381    }
382
383    fn reset(&mut self) {
384        Sum::reset(self)
385    }
386
387    fn aggregation(&self) -> Option<Self::Aggregation> {
388        let sum = self.sum;
389        Some(sum)
390    }
391}
392
393impl Statistic for Sum<i64> {
394    type Semantic = Gauge;
395    type Sample = i64;
396    type Aggregation = i64;
397
398    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
399        // TODO(https://fxbug.dev/351848566): Saturate `self.sum` on overflow.
400        self.sum
401            .checked_add(sample)
402            .inspect(|sum| self.sum = *sum)
403            .map(|_| ())
404            .ok_or(FoldError::Overflow)
405    }
406
407    fn fill(&mut self, sample: Self::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
408        Sum::fill(self, sample, n)
409    }
410
411    fn reset(&mut self) {
412        Sum::reset(self)
413    }
414
415    fn aggregation(&self) -> Option<Self::Aggregation> {
416        let sum = self.sum;
417        Some(sum)
418    }
419}
420
421/// Minimum statistic.
422#[derive(Clone, Debug)]
423pub struct Min<T> {
424    /// The minimum of samples.
425    min: Option<T>,
426}
427
428impl<T> Min<T> {
429    pub fn with_min(min: T) -> Self {
430        Min { min: Some(min) }
431    }
432}
433
434impl<T> Default for Min<T> {
435    fn default() -> Self {
436        Min { min: Default::default() }
437    }
438}
439
440impl<T> Statistic for Min<T>
441where
442    T: Ord + Copy + Zero + Num + NumCast,
443{
444    type Semantic = Gauge;
445    type Sample = T;
446    type Aggregation = T;
447
448    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
449        self.min = Some(match self.min {
450            Some(min) => cmp::min(min, sample),
451            _ => sample,
452        });
453        Ok(())
454    }
455
456    fn fill(&mut self, sample: Self::Sample, _n: NonZeroUsize) -> Result<(), FoldError> {
457        self.fold(sample)
458    }
459
460    fn reset(&mut self) {
461        *self = Default::default();
462    }
463
464    fn aggregation(&self) -> Option<Self::Aggregation> {
465        self.min
466    }
467}
468
469/// Maximum statistic.
470#[derive(Clone, Debug)]
471pub struct Max<T> {
472    /// The maximum of samples.
473    max: Option<T>,
474}
475
476impl<T> Max<T> {
477    pub fn with_max(max: T) -> Self {
478        Max { max: Some(max) }
479    }
480}
481
482impl<T> Default for Max<T> {
483    fn default() -> Self {
484        Max { max: Default::default() }
485    }
486}
487
488impl<T> Statistic for Max<T>
489where
490    T: Ord + Copy + Zero + Num + NumCast,
491{
492    type Semantic = Gauge;
493    type Sample = T;
494    type Aggregation = T;
495
496    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
497        self.max = Some(match self.max {
498            Some(max) => cmp::max(max, sample),
499            _ => sample,
500        });
501        Ok(())
502    }
503
504    fn fill(&mut self, sample: Self::Sample, _n: NonZeroUsize) -> Result<(), FoldError> {
505        self.fold(sample)
506    }
507
508    fn reset(&mut self) {
509        *self = Default::default();
510    }
511
512    fn aggregation(&self) -> Option<Self::Aggregation> {
513        self.max
514    }
515}
516
517/// Statistic for keeping the most recent sample.
518#[derive(Clone, Debug)]
519pub struct Last<T> {
520    /// The most recent sample.
521    last: Option<T>,
522}
523
524impl<T> Last<T> {
525    pub fn with_sample(sample: T) -> Self {
526        Last { last: Some(sample) }
527    }
528}
529
530impl<T> Default for Last<T> {
531    fn default() -> Self {
532        Last { last: Default::default() }
533    }
534}
535
536impl<T> Statistic for Last<T>
537where
538    T: Copy + Zero + Num + NumCast,
539{
540    type Semantic = Gauge;
541    type Sample = T;
542    type Aggregation = T;
543
544    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
545        self.last = Some(sample);
546        Ok(())
547    }
548
549    fn fill(&mut self, sample: Self::Sample, _n: NonZeroUsize) -> Result<(), FoldError> {
550        self.fold(sample)
551    }
552
553    fn reset(&mut self) {
554        *self = Default::default();
555    }
556
557    fn aggregation(&self) -> Option<Self::Aggregation> {
558        self.last
559    }
560}
561
562#[derive(Clone, Debug)]
563pub struct Union<T> {
564    bits: T,
565}
566
567impl<T> Union<T> {
568    pub fn with_bits(bits: T) -> Self {
569        Union { bits }
570    }
571}
572
573impl<T> Default for Union<T>
574where
575    T: Zero,
576{
577    fn default() -> Self {
578        Union::with_bits(T::zero())
579    }
580}
581
582impl Statistic for Union<u64> {
583    type Semantic = BitSet;
584    type Sample = u64;
585    type Aggregation = u64;
586
587    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
588        self.bits = self.bits | sample;
589        Ok(())
590    }
591
592    fn fill(&mut self, sample: Self::Sample, _n: NonZeroUsize) -> Result<(), FoldError> {
593        self.fold(sample)
594    }
595
596    fn reset(&mut self) {
597        *self = Default::default();
598    }
599
600    fn aggregation(&self) -> Option<Self::Aggregation> {
601        Some(self.bits)
602    }
603}
604
605/// Maximum statistic that sums non-monotonic samples into the maximum.
606///
607/// This statistic is sensitive to overflow in the sum of samples with the non-monotonic sum.
608#[derive(Clone, Debug)]
609pub struct LatchMax<T> {
610    /// The last observed sample.
611    last: Option<T>,
612    /// The maximum of samples and the non-monotonic sum.
613    max: Option<T>,
614    /// The sum of non-monotonic samples.
615    sum: T,
616}
617
618impl<T> LatchMax<T> {
619    pub fn with_max(max: T) -> Self
620    where
621        T: Zero,
622    {
623        LatchMax::with_max_and_sum(max, T::zero())
624    }
625
626    pub fn with_max_and_sum(max: T, sum: T) -> Self {
627        LatchMax { last: None, max: Some(max), sum }
628    }
629}
630
631impl<T> Default for LatchMax<T>
632where
633    T: Zero,
634{
635    fn default() -> Self {
636        LatchMax { last: None, max: None, sum: T::zero() }
637    }
638}
639
640impl Statistic for LatchMax<u64> {
641    type Semantic = Counter;
642    type Sample = u64;
643    type Aggregation = u64;
644
645    fn fold(&mut self, sample: Self::Sample) -> Result<(), FoldError> {
646        match self.last {
647            Some(last) if sample < last => {
648                // TODO(https://fxbug.dev/351848566): Saturate `self.sum` on overflow.
649                self.sum = self.sum.checked_add(last).ok_or(FoldError::Overflow)?;
650            }
651            _ => {}
652        }
653        self.last = Some(sample);
654
655        let sum = sample.checked_add(self.sum).ok_or(FoldError::Overflow)?;
656        self.max = Some(match self.max {
657            Some(max) => cmp::max(max, sum),
658            _ => sum,
659        });
660        Ok(())
661    }
662
663    fn fill(&mut self, sample: Self::Sample, _n: NonZeroUsize) -> Result<(), FoldError> {
664        self.fold(sample)
665    }
666
667    fn reset(&mut self) {}
668
669    fn aggregation(&self) -> Option<Self::Aggregation> {
670        self.max
671    }
672}
673
674/// Post-aggregation transform.
675///
676/// A post-aggregation composes a [`Statistic`] and arbitrarily maps its aggregation. For example,
677/// a transform may discretize the continuous aggregation of a statistic.
678///
679/// [`Statistic`]: crate::experimental::series::statistic::Statistic
680#[derive(Clone, Copy, Debug)]
681pub struct PostAggregation<F, R> {
682    statistic: F,
683    transform: R,
684}
685
686/// A post-aggregation of a function pointer.
687///
688/// This type definition describes a stateless and pure transform with a type name that can be
689/// spelled in any context (i.e., no closure or other unnameable types occur).
690pub type Transform<F, A> = PostAggregation<F, fn(Aggregation<F>) -> A>;
691
692impl<F, R> PostAggregation<F, R> {
693    pub fn with(statistic: F, transform: R) -> Self {
694        PostAggregation { statistic, transform }
695    }
696
697    /// Constructs a `PostAggregation` from a transform function.
698    ///
699    /// The default statistic is composed.
700    pub fn from_transform(transform: R) -> Self
701    where
702        F: Default,
703    {
704        PostAggregation { statistic: F::default(), transform }
705    }
706}
707
708impl<A, F, R> Statistic for PostAggregation<F, R>
709where
710    F: Statistic,
711    R: Clone + Fn(F::Aggregation) -> A,
712    A: Clone,
713{
714    type Semantic = F::Semantic;
715    type Sample = F::Sample;
716    type Aggregation = A;
717
718    fn fold(&mut self, sample: F::Sample) -> Result<(), FoldError> {
719        self.statistic.fold(sample)
720    }
721
722    fn fill(&mut self, sample: F::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
723        self.statistic.fill(sample, n)
724    }
725
726    fn reset(&mut self) {
727        self.statistic.reset()
728    }
729
730    fn aggregation(&self) -> Option<Self::Aggregation> {
731        self.statistic.aggregation().map(|aggregation| (self.transform)(aggregation))
732    }
733}
734
735/// Applies an arbitrary [reset function][`reset`] to a [`Statistic`].
736///
737/// [`reset`]: crate::experimental::series::statistic::Statistic::reset
738/// [`Statistic`]: crate::experimental::series::statistic::Statistic
739#[derive(Clone, Copy, Debug)]
740pub struct Reset<F, R> {
741    statistic: F,
742    reset: R,
743}
744
745impl<F, R> Reset<F, R>
746where
747    F: Statistic,
748    R: FnMut() -> F,
749{
750    pub fn with(statistic: F, reset: R) -> Self {
751        Reset { statistic, reset }
752    }
753}
754
755impl<F, R> Statistic for Reset<F, R>
756where
757    F: Statistic,
758    R: Clone + FnMut() -> F,
759{
760    type Semantic = F::Semantic;
761    type Sample = F::Sample;
762    type Aggregation = F::Aggregation;
763
764    fn fold(&mut self, sample: F::Sample) -> Result<(), FoldError> {
765        self.statistic.fold(sample)
766    }
767
768    fn fill(&mut self, sample: F::Sample, n: NonZeroUsize) -> Result<(), FoldError> {
769        self.statistic.fill(sample, n)
770    }
771
772    fn reset(&mut self) {
773        self.statistic = (self.reset)();
774    }
775
776    fn aggregation(&self) -> Option<Self::Aggregation> {
777        self.statistic.aggregation()
778    }
779}
780
781#[cfg(test)]
782mod tests {
783    use assert_matches::assert_matches;
784    use std::num::NonZeroUsize;
785
786    use crate::experimental::series::statistic::{
787        ArithmeticMean, FoldError, Last, LatchMax, Max, Min, PostAggregation, Reset, Statistic,
788        Sum, Union,
789    };
790
791    #[test]
792    fn arithmetic_mean_aggregation() {
793        let mut mean = ArithmeticMean::<f32>::default();
794        mean.fold(1.0).unwrap();
795        mean.fold(1.0).unwrap();
796        mean.fold(1.0).unwrap();
797        let aggregation = mean.aggregation().unwrap();
798        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
799
800        let mut mean = ArithmeticMean::<f32>::default();
801        mean.fold(0.0).unwrap();
802        mean.fold(1.0).unwrap();
803        mean.fold(2.0).unwrap();
804        let aggregation = mean.aggregation().unwrap();
805        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
806    }
807
808    #[test]
809    fn arithmetic_mean_aggregation_fill() {
810        let mut mean = ArithmeticMean::<f32>::default();
811        mean.fill(1.0, NonZeroUsize::new(1000).unwrap()).unwrap();
812        let aggregation = mean.aggregation().unwrap();
813        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
814    }
815
816    #[test]
817    fn arithmetic_mean_count_overflow() {
818        let mut mean = ArithmeticMean::<f32> { sum: 1.0, n: u64::MAX };
819        let result = mean.fold(1.0);
820        assert_matches!(result, Err(FoldError::Overflow));
821    }
822
823    #[test]
824    fn arithmetic_mean_i64_aggregation() {
825        let mut mean = ArithmeticMean::<i64>::default();
826        mean.fold(1).unwrap();
827        mean.fold(1).unwrap();
828        mean.fold(1).unwrap();
829        let aggregation = mean.aggregation().unwrap();
830        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
831
832        let mut mean = ArithmeticMean::<i64>::default();
833        mean.fold(0).unwrap();
834        mean.fold(1).unwrap();
835        mean.fold(2).unwrap();
836        let aggregation = mean.aggregation().unwrap();
837        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
838    }
839
840    #[test]
841    fn arithmetic_mean_i64_aggregation_fill() {
842        let mut mean = ArithmeticMean::<i64>::default();
843        mean.fill(1, NonZeroUsize::new(1000).unwrap()).unwrap();
844        let aggregation = mean.aggregation().unwrap();
845        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
846    }
847
848    #[test]
849    fn arithmetic_mean_i64_sum_overflow() {
850        let mut mean = ArithmeticMean::<i64> { sum: i64::MAX, n: 1 };
851        let result = mean.fold(1);
852        assert_matches!(result, Err(FoldError::Overflow));
853    }
854
855    #[test]
856    fn arithmetic_mean_i64_count_overflow() {
857        let mut mean = ArithmeticMean::<i64> { sum: 1, n: u64::MAX };
858        let result = mean.fold(1);
859        assert_matches!(result, Err(FoldError::Overflow));
860    }
861
862    #[test]
863    fn arithmetic_mean_u64_aggregation() {
864        let mut mean = ArithmeticMean::<u64>::default();
865        mean.fold(1).unwrap();
866        mean.fold(1).unwrap();
867        mean.fold(1).unwrap();
868        let aggregation = mean.aggregation().unwrap();
869        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
870
871        let mut mean = ArithmeticMean::<u64>::default();
872        mean.fold(0).unwrap();
873        mean.fold(1).unwrap();
874        mean.fold(2).unwrap();
875        let aggregation = mean.aggregation().unwrap();
876        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
877    }
878
879    #[test]
880    fn arithmetic_mean_u64_aggregation_fill() {
881        let mut mean = ArithmeticMean::<u64>::default();
882        mean.fill(1, NonZeroUsize::new(1000).unwrap()).unwrap();
883        let aggregation = mean.aggregation().unwrap();
884        assert!(aggregation > 0.99 && aggregation < 1.01); // ~ 1.0
885    }
886
887    #[test]
888    fn arithmetic_mean_u64_sum_overflow() {
889        let mut mean = ArithmeticMean::<u64> { sum: u64::MAX, n: 1 };
890        let result = mean.fold(1);
891        assert_matches!(result, Err(FoldError::Overflow));
892    }
893
894    #[test]
895    fn arithmetic_mean_u64_count_overflow() {
896        let mut mean = ArithmeticMean::<u64> { sum: 1, n: u64::MAX };
897        let result = mean.fold(1);
898        assert_matches!(result, Err(FoldError::Overflow));
899    }
900
901    #[test]
902    fn sum_aggregation() {
903        let mut sum = Sum::<u64>::default();
904        sum.fold(1).unwrap();
905        sum.fold(1).unwrap();
906        sum.fold(1).unwrap();
907        let aggregation = sum.aggregation().unwrap();
908        assert_eq!(aggregation, 3);
909
910        let mut sum = Sum::<u64>::default();
911        sum.fold(0).unwrap();
912        sum.fold(1).unwrap();
913        sum.fold(2).unwrap();
914        let aggregation = sum.aggregation().unwrap();
915        assert_eq!(aggregation, 3);
916    }
917
918    #[test]
919    fn sum_aggregation_fill() {
920        let mut sum = Sum::<u64>::default();
921        sum.fill(10, NonZeroUsize::new(1000).unwrap()).unwrap();
922        let aggregation = sum.aggregation().unwrap();
923        assert_eq!(aggregation, 10_000);
924    }
925
926    #[test]
927    fn sum_overflow() {
928        let mut sum = Sum::<u64> { sum: u64::MAX };
929        let result = sum.fold(1);
930        assert_matches!(result, Err(FoldError::Overflow));
931    }
932
933    #[test]
934    fn sum_i64_aggregation() {
935        let mut sum = Sum::<i64>::default();
936        sum.fold(1).unwrap();
937        sum.fold(1).unwrap();
938        sum.fold(1).unwrap();
939        let aggregation = sum.aggregation().unwrap();
940        assert_eq!(aggregation, 3);
941
942        let mut sum = Sum::<i64>::default();
943        sum.fold(0).unwrap();
944        sum.fold(1).unwrap();
945        sum.fold(2).unwrap();
946        let aggregation = sum.aggregation().unwrap();
947        assert_eq!(aggregation, 3);
948    }
949
950    #[test]
951    fn sum_i64_aggregation_fill() {
952        let mut sum = Sum::<i64>::default();
953        sum.fill(10, NonZeroUsize::new(1000).unwrap()).unwrap();
954        let aggregation = sum.aggregation().unwrap();
955        assert_eq!(aggregation, 10_000);
956    }
957
958    #[test]
959    fn sum_i64_overflow() {
960        let mut sum = Sum::<i64> { sum: i64::MAX };
961        let result = sum.fold(1);
962        assert_matches!(result, Err(FoldError::Overflow));
963    }
964
965    #[test]
966    fn min_aggregation() {
967        let mut min = Min::<u64>::default();
968        min.fold(10).unwrap();
969        min.fold(1337).unwrap();
970        min.fold(42).unwrap();
971        let aggregation = min.aggregation().unwrap();
972        assert_eq!(aggregation, 10);
973    }
974
975    #[test]
976    fn min_aggregation_fill() {
977        let mut min = Min::<u64>::default();
978        min.fill(42, NonZeroUsize::new(1000).unwrap()).unwrap();
979        let aggregation = min.aggregation().unwrap();
980        assert_eq!(aggregation, 42);
981    }
982
983    #[test]
984    fn max_aggregation() {
985        let mut max = Max::<u64>::default();
986        max.fold(0).unwrap();
987        max.fold(1337).unwrap();
988        max.fold(42).unwrap();
989        let aggregation = max.aggregation().unwrap();
990        assert_eq!(aggregation, 1337);
991    }
992
993    #[test]
994    fn max_aggregation_fill() {
995        let mut max = Max::<u64>::default();
996        max.fill(42, NonZeroUsize::new(1000).unwrap()).unwrap();
997        let aggregation = max.aggregation().unwrap();
998        assert_eq!(aggregation, 42);
999    }
1000
1001    #[test]
1002    fn last_aggregation() {
1003        let mut last = Last::<u64>::default();
1004        last.fold(0).unwrap();
1005        last.fold(1337).unwrap();
1006        last.fold(42).unwrap();
1007        let aggregation = last.aggregation().unwrap();
1008        assert_eq!(aggregation, 42);
1009    }
1010
1011    #[test]
1012    fn last_aggregation_fill() {
1013        let mut last = Last::<u64>::default();
1014        last.fill(42, NonZeroUsize::new(1000).unwrap()).unwrap();
1015        let aggregation = last.aggregation().unwrap();
1016        assert_eq!(aggregation, 42);
1017    }
1018
1019    #[test]
1020    fn union_aggregation() {
1021        let mut value = Union::<u64>::default();
1022        value.fold(1 << 1).unwrap();
1023        value.fold(1 << 3).unwrap();
1024        value.fold(1 << 5).unwrap();
1025        let aggregation = value.aggregation().unwrap();
1026        assert_eq!(aggregation, 0b101010);
1027    }
1028
1029    #[test]
1030    fn union_aggregation_fill() {
1031        let mut value = Union::<u64>::default();
1032        value.fill(1 << 2, NonZeroUsize::new(1000).unwrap()).unwrap();
1033        let aggregation = value.aggregation().unwrap();
1034        assert_eq!(aggregation, 0b100);
1035    }
1036
1037    #[test]
1038    fn latch_max_aggregation() {
1039        let mut max = LatchMax::<u64>::default();
1040        max.fold(1).unwrap();
1041        max.fold(1).unwrap();
1042        max.fold(1).unwrap();
1043        let aggregation = max.aggregation().unwrap();
1044        assert_eq!(aggregation, 1);
1045
1046        let mut max = LatchMax::<u64>::default();
1047        max.fold(0).unwrap();
1048        max.fold(1).unwrap();
1049        max.fold(2).unwrap();
1050        let aggregation = max.aggregation().unwrap();
1051        assert_eq!(aggregation, 2);
1052
1053        let mut max = LatchMax::<u64>::default();
1054        max.fold(1).unwrap();
1055        max.fold(5).unwrap();
1056        max.fold(6).unwrap();
1057        max.fold(2).unwrap(); // Non-monotonic sum is six.
1058        max.fold(9).unwrap();
1059        max.fold(3).unwrap(); // Non-monotonic sum is 15 (6 + 9).
1060        max.fold(5).unwrap();
1061        let aggregation = max.aggregation().unwrap();
1062        assert_eq!(aggregation, 20);
1063    }
1064
1065    #[test]
1066    fn latch_max_aggregation_fill() {
1067        let mut max = LatchMax::<u64>::default();
1068        max.fill(10, NonZeroUsize::new(1000).unwrap()).unwrap();
1069        let aggregation = max.aggregation().unwrap();
1070        assert_eq!(aggregation, 10);
1071    }
1072
1073    #[test]
1074    fn latch_max_overflow_max() {
1075        let mut max = LatchMax::<u64>::default();
1076        max.fold(1).unwrap();
1077        max.fold(0).unwrap(); // Non-monotonic sum is one.
1078        let result = max.fold(u64::MAX); // Non-monotonic sum of one is added to `u64::MAX`.
1079        assert_matches!(result, Err(FoldError::Overflow));
1080    }
1081
1082    #[test]
1083    fn post_sum_aggregation() {
1084        let mut sum = PostAggregation::<Sum<u64>, _>::from_transform(|sum| sum + 1);
1085        sum.fold(0).unwrap();
1086        sum.fold(1).unwrap();
1087        sum.fold(2).unwrap();
1088        let aggregation = sum.aggregation().unwrap();
1089        assert_eq!(aggregation, 4);
1090    }
1091
1092    #[test]
1093    fn reset_with_function() {
1094        let mut sum = Reset::with(Sum::<u64>::default(), || Sum::with_sum(3));
1095
1096        assert_eq!(sum.aggregation().unwrap(), 0);
1097        sum.fold(1).unwrap();
1098        assert_eq!(sum.aggregation().unwrap(), 1);
1099
1100        sum.reset();
1101        assert_eq!(sum.aggregation().unwrap(), 3);
1102        sum.fold(1).unwrap();
1103        assert_eq!(sum.aggregation().unwrap(), 4);
1104    }
1105}