use ax_core::det;
pub const MODZ_K: f64 = 0.6745;
pub fn center_scale(xs: &[f64]) -> Option<(f64, f64, f64)> {
let center = det::median(xs)?;
let mad = det::mad(xs).unwrap_or(0.0);
if mad > 0.0 {
return Some((center, mad, MODZ_K));
}
match det::std_dev(xs) {
Some(sd) if sd > 0.0 => Some((center, sd, 1.0)),
_ => None,
}
}
pub fn score(x: f64, center: f64, scale: f64, k: f64) -> f64 {
(k * (x - center) / scale).abs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn score_exact_arithmetic() {
assert_eq!(score(20.0, 10.0, 2.0, 0.5), 2.5);
assert_eq!(score(0.0, 10.0, 2.0, 1.0), 5.0);
assert_eq!(score(4.0, 10.0, 3.0, 1.0), 2.0);
}
#[test]
fn center_scale_prefers_mad_then_sigma_then_none() {
let (c, s, k) = center_scale(&[1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
assert_eq!(c, 3.0);
assert_eq!(k, MODZ_K);
assert!(s > 0.0);
let (_, s2, k2) = center_scale(&[5.0, 5.0, 5.0, 5.0, 99.0]).unwrap();
assert_eq!(k2, 1.0);
assert!(s2 > 0.0);
assert_eq!(center_scale(&[7.0, 7.0, 7.0]), None);
}
}