use crate::changepoint::pelt::Pelt;
use crate::changepoint::CostFunction;
use crate::error::Result;
#[derive(Debug, Clone, PartialEq)]
pub struct AutoRecencyConfig {
pub fallback_fraction: f64,
pub penalty: Option<f64>,
pub cost_fn: CostFunction,
pub min_segment_length: usize,
}
impl Default for AutoRecencyConfig {
fn default() -> Self {
Self {
fallback_fraction: 0.3,
penalty: None, cost_fn: CostFunction::LinearTrend,
min_segment_length: 5,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Recency {
Window(usize),
Fraction(f64),
Full,
Auto(AutoRecencyConfig),
}
impl Default for Recency {
fn default() -> Self {
Recency::Fraction(0.3)
}
}
impl Recency {
pub fn auto() -> Self {
Recency::Auto(AutoRecencyConfig::default())
}
pub fn resolve(&self, n: usize) -> (usize, usize) {
if n == 0 {
return (0, 0);
}
let min_window = 3.min(n);
let start = match self {
Recency::Full => 0,
Recency::Window(w) => n.saturating_sub(*w),
Recency::Fraction(f) => {
let frac = f.clamp(0.0, 1.0);
let window = (n as f64 * frac).ceil() as usize;
n.saturating_sub(window.max(min_window))
}
Recency::Auto(config) => {
let frac = config.fallback_fraction.clamp(0.0, 1.0);
let window = (n as f64 * frac).ceil() as usize;
n.saturating_sub(window.max(min_window))
}
};
let start = start.min(n.saturating_sub(min_window));
(start, n)
}
pub fn resolve_with_data(&self, values: &[f64]) -> (usize, usize) {
match self {
Recency::Auto(config) => {
let n = values.len();
if n < 6 {
return self.resolve(n);
}
let penalty = config.penalty.unwrap_or_else(|| (n as f64).ln());
let result = Pelt::new(config.cost_fn)
.penalty(penalty)
.min_size(config.min_segment_length)
.detect(values);
if let Some(&last_cp) = result.changepoints.last() {
let min_window = 3.min(n);
let start = last_cp.min(n.saturating_sub(min_window));
(start, n)
} else {
Recency::Fraction(config.fallback_fraction).resolve(n)
}
}
_ => self.resolve(values.len()),
}
}
pub fn detect_changepoints(&self, values: &[f64]) -> Option<Vec<usize>> {
match self {
Recency::Auto(config) => {
let n = values.len();
if n < 6 {
return Some(Vec::new());
}
let penalty = config.penalty.unwrap_or_else(|| (n as f64).ln());
let result = Pelt::new(config.cost_fn)
.penalty(penalty)
.min_size(config.min_segment_length)
.detect(values);
Some(result.changepoints)
}
_ => None,
}
}
}
pub trait SeasonalComponent {
fn fit_seasonal(&mut self, values: &[f64], period: usize) -> Result<()>;
fn fitted_seasonal(&self) -> &[f64];
fn predict_seasonal(&self, n_ahead: usize) -> Vec<f64>;
fn seasonal_features(&self) -> Vec<(&str, f64)>;
fn seasonal_name(&self) -> &str;
fn n_params(&self) -> usize {
0
}
}
pub trait TrendComponent {
fn fit_trend(&mut self, values: &[f64]) -> Result<()>;
fn fitted_trend(&self) -> &[f64];
fn predict_trend(&self, n_ahead: usize) -> Vec<f64>;
fn trend_features(&self) -> Vec<(&str, f64)>;
fn trend_name(&self) -> &str;
fn n_params(&self) -> usize {
0
}
}