Skip to main content

u_analytics/
error.rs

1//! Refusals shared by the functions of more than one module.
2
3/// Why a time-series statistic was refused: [`crate::correlation::acf`],
4/// [`crate::correlation::pacf`], [`crate::testing::adf_test`] and
5/// [`crate::testing::kpss_test`].
6///
7/// The data is checked before the options, and the options before any
8/// computation: a refusal of an option never hides a refusal of the data,
9/// and [`TimeSeriesError::NoVariation`] / [`TimeSeriesError::DegenerateRegression`]
10/// only follow options that were in range.
11#[derive(Debug, Clone, Copy, PartialEq)]
12#[non_exhaustive]
13pub enum TimeSeriesError {
14    /// The series holds a NaN or an infinity.
15    ValueNotFinite {
16        /// Position of the first such value.
17        index: usize,
18    },
19    /// Fewer observations than the statistic is defined for.
20    TooFewObservations {
21        /// The fewest it takes.
22        needed: usize,
23        /// How many were given.
24        got: usize,
25    },
26    /// An option outside its domain, which depends on the number of
27    /// observations: `min` and `max` are both inclusive.
28    OptionOutOfRange {
29        /// The option's name, spelled as the wire formats spell it
30        /// (`max_lag`, `max_lags`, `lags`).
31        option: &'static str,
32        /// The value given (a count as a float).
33        got: f64,
34        /// Inclusive lower bound.
35        min: f64,
36        /// Inclusive upper bound.
37        max: f64,
38    },
39    /// The series does not vary (about its level, or its trend, for the KPSS
40    /// test) beyond the rounding of its own size, so the statistic divides
41    /// by zero.
42    NoVariation,
43    /// The ADF test regression is singular, or fits the differences exactly
44    /// (a deterministic series), so the t-ratio is undefined.
45    DegenerateRegression,
46}
47
48impl core::fmt::Display for TimeSeriesError {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        match self {
51            TimeSeriesError::ValueNotFinite { index } => {
52                write!(f, "the value at index {index} is not finite")
53            }
54            TimeSeriesError::TooFewObservations { needed, got } => {
55                write!(f, "at least {needed} values are needed, got {got}")
56            }
57            TimeSeriesError::OptionOutOfRange {
58                option,
59                got,
60                min,
61                max,
62            } => write!(f, "`{option}` must be in [{min}, {max}] (got {got})"),
63            TimeSeriesError::NoVariation => {
64                write!(f, "the series does not vary, so the statistic is undefined")
65            }
66            TimeSeriesError::DegenerateRegression => write!(
67                f,
68                "the test regression is singular, or an exact fit of the differences \
69                 (a deterministic series), so the t-ratio is undefined"
70            ),
71        }
72    }
73}
74
75impl std::error::Error for TimeSeriesError {}
76
77/// The series' own checks, in the order every time-series statistic applies
78/// them: a non-finite value, then too few values.
79pub(crate) fn check_series(data: &[f64], needed: usize) -> Result<(), TimeSeriesError> {
80    if let Some(index) = data.iter().position(|v| !v.is_finite()) {
81        return Err(TimeSeriesError::ValueNotFinite { index });
82    }
83    if data.len() < needed {
84        return Err(TimeSeriesError::TooFewObservations {
85            needed,
86            got: data.len(),
87        });
88    }
89    Ok(())
90}
91
92/// Refuses a count option outside `[min, max]`.
93pub(crate) fn check_option(
94    option: &'static str,
95    value: usize,
96    min: usize,
97    max: usize,
98) -> Result<usize, TimeSeriesError> {
99    if (min..=max).contains(&value) {
100        Ok(value)
101    } else {
102        Err(TimeSeriesError::OptionOutOfRange {
103            option,
104            got: value as f64,
105            min: min as f64,
106            max: max as f64,
107        })
108    }
109}