use crate::error::RobustError;
use crate::scale::ScaleEstimator;
use crate::types::Scale;
#[derive(Debug, Clone, Copy)]
pub struct Qn {
pub consistency: f64,
pub finite_sample_correction: bool,
}
impl Default for Qn {
fn default() -> Self {
Self {
consistency: 2.2219,
finite_sample_correction: true,
}
}
}
impl ScaleEstimator for Qn {
fn scale(&self, residuals: &[f64]) -> Result<Scale, RobustError> {
let n = residuals.len();
if n < 2 {
return Err(RobustError::InsufficientData { needed: 2, got: n });
}
let mut diffs = Vec::with_capacity(n * (n - 1) / 2);
for i in 0..n {
for j in (i + 1)..n {
diffs.push((residuals[i] - residuals[j]).abs());
}
}
let h = n / 2 + 1;
let k = h * (h - 1) / 2;
let (_, kth, _) = diffs.select_nth_unstable_by(k - 1, f64::total_cmp);
let kth = *kth;
let dn = if self.finite_sample_correction {
qn_correction(n)
} else {
1.0
};
let s = self.consistency * dn * kth;
if !s.is_finite() || s <= 0.0 {
return Err(RobustError::DegenerateScale);
}
Scale::new(s)
}
}
fn qn_correction(n: usize) -> f64 {
const SMALL: [f64; 8] = [0.399, 0.994, 0.512, 0.844, 0.611, 0.857, 0.669, 0.872];
if n <= 9 {
SMALL[n - 2]
} else if n % 2 == 0 {
n as f64 / (n as f64 + 3.8)
} else {
n as f64 / (n as f64 + 1.4)
}
}