use crate::models::laplace::dist::Gaussian;
use crate::models::laplace::leaf::Leaf;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DriftLeaf {
alpha: f64,
level: Option<f64>,
drift: f64,
prev: Option<f64>,
n: usize,
ss: f64,
mean_resid: f64,
}
impl DriftLeaf {
pub fn new(alpha: f64) -> Self {
Self {
alpha: alpha.clamp(1e-3, 1.0 - 1e-3),
level: None,
drift: 0.0,
prev: None,
n: 0,
ss: 0.0,
mean_resid: 0.0,
}
}
pub fn from_batch(alpha: f64, values: &[f64]) -> Self {
let mut leaf = Self::new(alpha);
if values.len() < 5 {
return leaf;
}
let (_, beta) = ols_slope(values);
leaf.level = values.last().copied();
leaf.drift = beta;
leaf.prev = values.last().copied();
leaf
}
}
pub(crate) fn ols_slope(values: &[f64]) -> (f64, f64) {
let n = values.len();
if n < 2 {
return (values.first().copied().unwrap_or(0.0), 0.0);
}
let n_f = n as f64;
let mean_t = (n_f - 1.0) / 2.0;
let mean_y: f64 = values.iter().sum::<f64>() / n_f;
let mut num = 0.0;
let mut den = 0.0;
for (i, &y) in values.iter().enumerate() {
let dt = i as f64 - mean_t;
num += dt * (y - mean_y);
den += dt * dt;
}
let beta = if den > 1e-12 { num / den } else { 0.0 };
let alpha = mean_y - beta * mean_t;
(alpha, beta)
}
impl DriftLeaf {
fn sigma(&self) -> f64 {
if self.n < 2 {
return 1.0;
}
(self.ss / (self.n as f64 - 1.0)).sqrt().max(1e-9)
}
}
impl Leaf for DriftLeaf {
fn name(&self) -> &'static str {
"drift"
}
fn predict(&self, horizon: usize) -> Vec<Gaussian> {
let level = self.level.unwrap_or(0.0);
let base = self.sigma();
(1..=horizon)
.map(|h| Gaussian::new(level + h as f64 * self.drift, base * (h as f64).sqrt()))
.collect()
}
#[inline]
fn predict_one(&self) -> Gaussian {
Gaussian::new(self.level.unwrap_or(0.0) + self.drift, self.sigma())
}
fn observe(&mut self, y: f64) {
let predicted = self.level.map(|l| l + self.drift).unwrap_or(y);
let resid = y - predicted;
self.n += 1;
let delta = resid - self.mean_resid;
self.mean_resid += delta / self.n as f64;
self.ss += delta * (resid - self.mean_resid);
if let Some(prev) = self.prev {
let step = y - prev;
self.drift = self.alpha * step + (1.0 - self.alpha) * self.drift;
}
self.level = Some(match self.level {
Some(l) => self.alpha * y + (1.0 - self.alpha) * l,
None => y,
});
self.prev = Some(y);
}
}