///|
/// `Cusum` implements the Cumulative Sum algorithm for detecting shifts in the mean.
pub struct Cusum {
target_mean : Double
control_limit : Double
drift : Double
mut g_positive : Double
mut g_negative : Double
mut t : Int
}
///|
/// Creates a new CUSUM detector.
pub fn Cusum::new(
target_mean? : Double = 0.0,
control_limit? : Double = 5.0,
drift? : Double = 0.5,
) -> Cusum {
{ target_mean, control_limit, drift, g_positive: 0.0, g_negative: 0.0, t: 0 }
}
///|
/// Updates the CUSUM detector with a new value and returns true if a change is detected.
pub fn Cusum::update(self : Cusum, value : Double) -> Bool {
self.t += 1
let diff = value - self.target_mean
// Update positive and negative accumulators
self.g_positive = if self.g_positive + diff - self.drift > 0.0 {
self.g_positive + diff - self.drift
} else {
0.0
}
self.g_negative = if self.g_negative - diff - self.drift > 0.0 {
self.g_negative - diff - self.drift
} else {
0.0
}
if self.g_positive > self.control_limit ||
self.g_negative > self.control_limit {
self.reset()
return true
}
return false
}
///|
/// Resets the internal accumulators.
pub fn Cusum::reset(self : Cusum) -> Unit {
self.g_positive = 0.0
self.g_negative = 0.0
}
///|
/// Rich result form of `update`, retaining the direction and evidence.
pub fn Cusum::update_result(
self : Cusum,
value : Double,
index? : Int = 0,
) -> DetectionResult {
let delta = value - self.target_mean
let changed = self.update(value)
let evidence = if delta >= 0.0 { self.g_positive } else { self.g_negative }
let score = if self.control_limit <= 0.0 {
0.0
} else {
evidence / self.control_limit
}
DetectionResult::new(
changed,
score,
clamp_probability(score / (score + 1.0)),
direction_for_delta(delta),
index,
evidence~,
)
}