use ndarray::{Array1, Array2};
use rand::Rng;
use robust_rs_core::error::RobustError;
use robust_rs_core::rho::{RhoFunction, TukeyBiweight};
use robust_rs_core::solver::Control;
use crate::estimator::RegressionFit;
use crate::regression::SEstimator;
use crate::wls::weighted_least_squares;
#[derive(Debug, Clone, Copy)]
pub struct MMEstimator<RS = TukeyBiweight, RM = TukeyBiweight> {
s: SEstimator<RS>,
m_rho: RM,
control: Control,
}
impl Default for MMEstimator<TukeyBiweight, TukeyBiweight> {
fn default() -> Self {
Self {
s: SEstimator::default(),
m_rho: TukeyBiweight::default(),
control: Control::default(),
}
}
}
impl<RS, RM> MMEstimator<RS, RM>
where
RS: RhoFunction + Clone + 'static,
RM: RhoFunction + Clone + 'static,
{
pub fn new(s_rho: RS, m_rho: RM) -> Self {
Self {
s: SEstimator::new(s_rho),
m_rho,
control: Control::default(),
}
}
pub fn seed(mut self, seed: u64) -> Self {
self.s = self.s.seed(seed);
self
}
pub fn n_subsamples(mut self, n: usize) -> Self {
self.s = self.s.n_subsamples(n);
self
}
pub fn control(mut self, control: Control) -> Self {
self.control = control;
self
}
pub fn fit(&self, x: &Array2<f64>, y: &Array1<f64>) -> Result<RegressionFit, RobustError> {
let s_fit = self.s.fit(x, y)?;
self.m_step(x, y, s_fit)
}
pub fn fit_with_rng<G: Rng>(
&self,
x: &Array2<f64>,
y: &Array1<f64>,
rng: &mut G,
) -> Result<RegressionFit, RobustError> {
let s_fit = self.s.fit_with_rng(x, y, rng)?;
self.m_step(x, y, s_fit)
}
fn m_step(
&self,
x: &Array2<f64>,
y: &Array1<f64>,
s_fit: RegressionFit,
) -> Result<RegressionFit, RobustError> {
let scale = s_fit.scale; let breakdown = s_fit.breakdown_point; let s = scale.get();
let mut beta = s_fit.coefficients; let mut resid = y - &x.dot(&beta);
let mut converged = false;
for _ in 0..self.control.max_iter {
let w = resid.mapv(|r| self.m_rho.weight(r / s));
let beta_next = weighted_least_squares(x, y, &w)?;
let diff = &beta_next - β
let rel = diff.dot(&diff).sqrt() / (beta_next.dot(&beta_next).sqrt() + 1e-12);
beta = beta_next;
resid = y - &x.dot(&beta);
if rel <= self.control.tol {
converged = true;
break;
}
}
if !converged {
return Err(RobustError::NonConvergence {
iters: self.control.max_iter,
});
}
let weights = resid.mapv(|r| self.m_rho.weight(r / s));
Ok(RegressionFit {
coefficients: beta,
scale,
residuals: resid,
weights,
rho: Box::new(self.m_rho.clone()), breakdown_point: breakdown, })
}
}