use crate::util::median;
use robust_rs_core::error::RobustError;
pub const THEIL_SEN_BREAKDOWN: f64 = 1.0 - std::f64::consts::FRAC_1_SQRT_2;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TheilSenFit {
pub slope: f64,
pub intercept: f64,
}
impl TheilSenFit {
pub fn slope(&self) -> f64 {
self.slope
}
pub fn intercept(&self) -> f64 {
self.intercept
}
pub fn predict(&self, x: f64) -> f64 {
self.intercept + self.slope * x
}
pub fn breakdown_point(&self) -> f64 {
THEIL_SEN_BREAKDOWN
}
}
pub fn theil_sen(x: &[f64], y: &[f64]) -> Result<TheilSenFit, RobustError> {
let n = x.len();
if y.len() != n {
return Err(RobustError::DimensionMismatch {
expected: n,
got: y.len(),
});
}
if n < 2 {
return Err(RobustError::InsufficientData { needed: 2, got: n });
}
let mut slopes = Vec::with_capacity(n * (n - 1) / 2);
for i in 0..n {
for j in (i + 1)..n {
let dx = x[j] - x[i];
if dx != 0.0 {
slopes.push((y[j] - y[i]) / dx);
}
}
}
if slopes.is_empty() {
return Err(RobustError::SingularDesign); }
let slope = median(&mut slopes);
let mut offsets: Vec<f64> = x.iter().zip(y).map(|(&xi, &yi)| yi - slope * xi).collect();
let intercept = median(&mut offsets);
Ok(TheilSenFit { slope, intercept })
}