use ndarray::{Array1, Array2, Axis};
use robust_rs_core::error::RobustError;
use robust_rs_core::solver::Control;
use crate::util::substream;
use crate::wls::weighted_least_squares;
#[derive(Debug, Clone, Copy)]
pub(crate) struct SearchConfig {
pub n_subsamples: usize,
pub n_keep: usize,
pub initial_csteps: usize,
pub control: Control,
}
impl Default for SearchConfig {
fn default() -> Self {
Self {
n_subsamples: 500,
n_keep: 5,
initial_csteps: 2,
control: Control::default(),
}
}
}
pub(crate) fn fast_resample<S, C>(
x: &Array2<f64>,
y: &Array1<f64>,
master_seed: u64,
cfg: &SearchConfig,
score: S,
cstep: C,
) -> Result<(Array1<f64>, f64), RobustError>
where
S: Fn(&Array1<f64>) -> Result<f64, RobustError>,
C: Fn(&Array1<f64>) -> Result<Array1<f64>, RobustError>,
{
let (n, p) = x.dim();
if y.len() != n {
return Err(RobustError::DimensionMismatch {
expected: n,
got: y.len(),
});
}
if p == 0 {
return Err(RobustError::SingularDesign);
}
if n < p {
return Err(RobustError::InsufficientData { needed: p, got: n });
}
let ones = Array1::ones(p);
let mut candidates: Vec<(f64, Array1<f64>)> = Vec::new();
let max_attempts = cfg.n_subsamples.saturating_mul(20).max(50);
let mut valid = 0usize;
let mut attempt = 0u64;
while valid < cfg.n_subsamples && (attempt as usize) < max_attempts {
let mut sub = substream(master_seed, attempt);
attempt += 1;
let idx = rand::seq::index::sample(&mut sub, n, p).into_vec();
let xs = x.select(Axis(0), &idx);
let ys = y.select(Axis(0), &idx);
let beta0 = match weighted_least_squares(&xs, &ys, &ones) {
Ok(b) => b,
Err(_) => continue, };
valid += 1;
if let Ok((beta, sc)) =
concentrate(beta0, &score, &cstep, cfg.initial_csteps, cfg.control.tol)
{
candidates.push((sc, beta));
}
}
if candidates.is_empty() {
return Err(RobustError::SubsampleFailure);
}
candidates.sort_by(|a, b| a.0.total_cmp(&b.0));
candidates.truncate(cfg.n_keep);
let mut best_score = f64::INFINITY;
let mut best_beta: Option<Array1<f64>> = None;
for (_, beta0) in candidates {
if let Ok((beta, sc)) =
concentrate(beta0, &score, &cstep, cfg.control.max_iter, cfg.control.tol)
{
if sc < best_score {
best_score = sc;
best_beta = Some(beta);
}
}
}
let beta = best_beta.ok_or(RobustError::SubsampleFailure)?;
Ok((beta, best_score))
}
fn concentrate<S, C>(
beta0: Array1<f64>,
score: &S,
cstep: &C,
max_steps: usize,
tol: f64,
) -> Result<(Array1<f64>, f64), RobustError>
where
S: Fn(&Array1<f64>) -> Result<f64, RobustError>,
C: Fn(&Array1<f64>) -> Result<Array1<f64>, RobustError>,
{
let mut beta = beta0;
let mut sc = score(&beta)?;
for _ in 0..max_steps {
let beta_next = match cstep(&beta) {
Ok(b) => b,
Err(_) => break,
};
let sc_next = match score(&beta_next) {
Ok(s) => s,
Err(_) => break,
};
let converged = (sc - sc_next).abs() <= tol * sc;
beta = beta_next;
sc = sc_next;
if converged {
break;
}
}
Ok((beta, sc))
}