use crate::renormalize::RenormalizationGroup;
#[derive(Debug, Clone)]
pub struct DriftDetector {
pub law_index: usize,
pub threshold: f64,
pub breaking_scale: Option<f64>,
drift_history: Vec<(f64, f64)>, }
impl DriftDetector {
pub fn new(law_index: usize, threshold: f64) -> Self {
Self {
law_index,
threshold,
breaking_scale: None,
drift_history: Vec::new(),
}
}
pub fn record_drift(&mut self, time: f64, drift: f64) {
self.drift_history.push((time, drift));
if drift > self.threshold && self.breaking_scale.is_none() {
self.detect_breaking_scale();
}
}
pub fn is_drifting(&self) -> bool {
self.drift_history
.last()
.is_some_and(|(_, d)| *d > self.threshold)
}
pub fn max_drift(&self) -> f64 {
self.drift_history
.iter()
.map(|(_, d)| *d)
.fold(0.0_f64, f64::max)
}
pub fn first_violation_time(&self) -> Option<f64> {
self.drift_history
.iter()
.find(|(_, d)| *d > self.threshold)
.map(|(t, _)| *t)
}
fn detect_breaking_scale(&mut self) {
if self.drift_history.len() < 4 {
return;
}
let times: Vec<f64> = self.drift_history.iter().map(|(t, _)| *t).collect();
let durations: Vec<f64> = times.windows(2).map(|w| w[1] - w[0]).collect();
if durations.is_empty() {
return;
}
let base_dt: f64 = durations.iter().sum::<f64>() / durations.len() as f64;
if base_dt <= 0.0 {
return;
}
let rg = RenormalizationGroup::new(&self.drift_history);
self.breaking_scale = rg.find_breaking_scale();
}
pub fn sample_count(&self) -> usize {
self.drift_history.len()
}
}