augurs_ets/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
//! Exponential smoothing models.
//!
//! This crate provides exponential smoothing models for time series forecasting.
//! The models are implemented in Rust and are based on the [statsforecast][] Python package.
//!
//! **Important**: This crate is still in development and the API is subject to change.
//! Seasonal models are not yet implemented, and some model types have not been tested.
//!
//! # Example
//!
//! ```
//! use augurs_core::prelude::*;
//! use augurs_ets::AutoETS;
//!
//! let data: Vec<_> = (0..10).map(|x| x as f64).collect();
//! let mut search = AutoETS::new(1, "ZZN")
//! .expect("ZZN is a valid model search specification string");
//! let model = search.fit(&data).expect("fit should succeed");
//! let forecast = model.predict(5, 0.95).expect("predict should succeed");
//! assert_eq!(forecast.point.len(), 5);
//! assert_eq!(forecast.point, vec![10.0, 11.0, 12.0, 13.0, 14.0]);
//! ```
//!
//! [statsforecast]: https://nixtla.github.io/statsforecast/models.html#autoets
mod auto;
mod ets;
pub mod model;
mod stat;
#[cfg(feature = "mstl")]
pub mod trend;
use augurs_core::ModelError;
pub use auto::{AutoETS, AutoSpec, FittedAutoETS};
/// Errors returned by this crate.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// An error occurred while parsing an error specification string.
#[error("invalid error component string '{0}', must be one of 'A', 'M', 'Z'")]
InvalidErrorComponentString(char),
/// An error occurred while parsing a trend or seasonal specification string.
#[error("invalid component string '{0}', must be one of 'N', 'A', 'M', 'Z'")]
InvalidComponentString(char),
/// An error occurred while parsing a model specification string.
#[error("invalid model specification '{0}'")]
InvalidModelSpec(String),
/// The bounds of a parameter were inconsistent, i.e. the lower bound was
/// greater than the upper bound.
#[error("inconsistent parameter boundaries")]
InconsistentBounds,
/// One or more of the provided parameters was out of range.
/// The definition of 'out of range' depends on the type of
/// [`Bounds`][model::Bounds] used.
#[error("parameters out of range")]
ParamsOutOfRange,
/// Not enough data was provided to fit a model.
#[error("not enough data")]
NotEnoughData,
/// An error occurred solving a linear system while initializing state.
#[error("least squares: {0}")]
LeastSquares(&'static str),
/// No suitable model was found.
#[error("no model found")]
NoModelFound,
/// The model has not yet been fit.
#[error("model not fit")]
ModelNotFit,
}
impl ModelError for Error {}
type Result<T> = std::result::Result<T, Error>;
// Commented out because I haven't implemented seasonal models yet.
// fn fourier(y: &[f64], period: &[usize], K: &[usize]) -> DMatrix<f64> {
// let times: Vec<_> = (1..y.len() + 1).collect();
// let len_p = K.iter().fold(0, |sum, k| sum + k.min(&0));
// let mut p = vec![f64::NAN; len_p];
// let idx = 0;
// for (j, p_) in period.iter().enumerate() {
// let k = K[j];
// if k > 0 {
// for (i, x) in p[idx..(idx + k)].iter_mut().enumerate() {
// *x = i as f64 / *p_ as f64;
// }
// }
// }
// p.dedup();
// // Determine columns where sinpi=0.
// let k: Vec<bool> = zip(
// p.iter().map(|x| 2.0 * x),
// p.iter().map(|x| (2.0 * x).round()),
// )
// .map(|(a, b)| a - b > f64::MIN)
// .collect();
// let mut x = DMatrix::from_element(times.len(), 2 * p.len(), f64::NAN);
// for (j, p_) in p.iter().enumerate() {
// if k[j] {
// x.column_mut(2 * j - 1)
// .iter_mut()
// .enumerate()
// .for_each(|(i, val)| {
// *val = (2.0 * p_ * (i + 1) as f64 * std::f64::consts::PI).sin()
// });
// }
// x.column_mut(2 * j)
// .iter_mut()
// .enumerate()
// .for_each(|(i, val)| *val = (2.0 * p_ * (i + 1) as f64 * std::f64::consts::PI).cos());
// }
// let cols_to_delete: Vec<_> = x
// .column_sum()
// .iter()
// .enumerate()
// .filter_map(|(i, sum)| if sum.is_nan() { Some(i) } else { None })
// .collect();
// x.remove_columns_at(&cols_to_delete)
// }