use super::helpers::{build_band, percentile_sorted};
use super::{MultiplierDistribution, ToleranceBand};
use crate::error::FdarError;
use crate::fdata::mean_1d;
use crate::iter_maybe_parallel;
use crate::matrix::FdMatrix;
use crate::smoothing::local_polynomial;
use rand::prelude::*;
use rand_distr::StandardNormal;
#[cfg(feature = "parallel")]
use rayon::iter::ParallelIterator;
pub(super) fn residual_sigma(data: &FdMatrix, center: &[f64], n: usize, m: usize) -> Vec<f64> {
let nf_minus1 = (n as f64 - 1.0).max(1.0);
(0..m)
.map(|j| {
let var: f64 = (0..n).map(|i| (data[(i, j)] - center[j]).powi(2)).sum();
(var / nf_minus1).sqrt().max(1e-15)
})
.collect()
}
pub(super) fn generate_multiplier_weights(
rng: &mut StdRng,
n: usize,
multiplier: MultiplierDistribution,
) -> Vec<f64> {
(0..n)
.map(|_| match multiplier {
MultiplierDistribution::Gaussian => rng.sample::<f64, _>(StandardNormal),
MultiplierDistribution::Rademacher => {
if rng.gen_bool(0.5) {
1.0
} else {
-1.0
}
}
})
.collect()
}
pub fn scb_mean_degras(
data: &FdMatrix,
argvals: &[f64],
bandwidth: f64,
nb: usize,
confidence: f64,
multiplier: MultiplierDistribution,
) -> Result<ToleranceBand, FdarError> {
let (n, m) = data.shape();
if n < 3 || m == 0 {
return Err(FdarError::InvalidDimension {
parameter: "data",
expected: "at least 3 rows and 1 column".to_string(),
actual: format!("{n} x {m}"),
});
}
if argvals.len() != m {
return Err(FdarError::InvalidDimension {
parameter: "argvals",
expected: format!("length {m} (matching data columns)"),
actual: format!("length {}", argvals.len()),
});
}
if bandwidth <= 0.0 {
return Err(FdarError::InvalidParameter {
parameter: "bandwidth",
message: format!("must be positive, got {bandwidth}"),
});
}
if nb == 0 {
return Err(FdarError::InvalidParameter {
parameter: "nb",
message: "must be >= 1".to_string(),
});
}
if confidence <= 0.0 || confidence >= 1.0 {
return Err(FdarError::InvalidParameter {
parameter: "confidence",
message: format!("must be in (0, 1), got {confidence}"),
});
}
let raw_mean = mean_1d(data);
let center = local_polynomial(argvals, &raw_mean, argvals, bandwidth, 1, "epanechnikov")?;
let sigma_hat = residual_sigma(data, ¢er, n, m);
let sqrt_n = (n as f64).sqrt();
let mut sup_stats: Vec<f64> = iter_maybe_parallel!(0..nb)
.map(|b| {
let mut rng = StdRng::seed_from_u64(42 + b as u64);
let weights = generate_multiplier_weights(&mut rng, n, multiplier);
(0..m)
.map(|j| {
let z: f64 = (0..n)
.map(|i| weights[i] * (data[(i, j)] - center[j]))
.sum::<f64>()
/ (sqrt_n * sigma_hat[j]);
z.abs()
})
.fold(0.0_f64, f64::max)
})
.collect();
let c = percentile_sorted(&mut sup_stats, confidence);
let half_width: Vec<f64> = sigma_hat.iter().map(|&s| c * s / sqrt_n).collect();
Ok(build_band(center, half_width))
}