use crate::error::AnalysisError;
pub fn adaptive_threshold_median_mad(
values: &[f32],
k: f32,
) -> Result<f32, AnalysisError> {
if values.is_empty() {
return Err(AnalysisError::InvalidInput("Empty values for threshold calculation".to_string()));
}
if k < 0.0 {
return Err(AnalysisError::InvalidInput("MAD multiplier k must be non-negative".to_string()));
}
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let median = if sorted.len() % 2 == 0 {
(sorted[sorted.len() / 2 - 1] + sorted[sorted.len() / 2]) * 0.5
} else {
sorted[sorted.len() / 2]
};
let mut deviations: Vec<f32> = values.iter()
.map(|&v| (v - median).abs())
.collect();
deviations.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mad = if deviations.len() % 2 == 0 {
(deviations[deviations.len() / 2 - 1] + deviations[deviations.len() / 2]) * 0.5
} else {
deviations[deviations.len() / 2]
};
let threshold = median + k * mad;
Ok(threshold)
}
pub fn percentile_threshold(
values: &[f32],
percentile: f32,
) -> Result<f32, AnalysisError> {
if values.is_empty() {
return Err(AnalysisError::InvalidInput("Empty values for threshold calculation".to_string()));
}
if percentile < 0.0 || percentile > 1.0 {
return Err(AnalysisError::InvalidInput(
format!("Percentile must be in [0.0, 1.0], got {}", percentile)
));
}
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let threshold_idx = ((sorted.len() as f32) * percentile) as usize;
let threshold_idx = threshold_idx.min(sorted.len() - 1);
Ok(sorted[threshold_idx])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adaptive_threshold_median_mad_basic() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 100.0]; let threshold = adaptive_threshold_median_mad(&values, 2.5).unwrap();
assert!(threshold > 3.5);
assert!(threshold < 50.0); }
#[test]
fn test_adaptive_threshold_median_mad_empty() {
let result = adaptive_threshold_median_mad(&[], 2.5);
assert!(result.is_err());
}
#[test]
fn test_adaptive_threshold_median_mad_single_value() {
let values = vec![5.0];
let threshold = adaptive_threshold_median_mad(&values, 2.5).unwrap();
assert_eq!(threshold, 5.0); }
#[test]
fn test_percentile_threshold_basic() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let threshold = percentile_threshold(&values, 0.8).unwrap();
assert!((threshold - 5.0).abs() < 0.1);
let threshold_50 = percentile_threshold(&values, 0.5).unwrap();
assert!((threshold_50 - 3.0).abs() < 0.1);
}
#[test]
fn test_percentile_threshold_empty() {
let result = percentile_threshold(&[], 0.8);
assert!(result.is_err());
}
#[test]
fn test_percentile_threshold_invalid_percentile() {
let values = vec![1.0, 2.0, 3.0];
assert!(percentile_threshold(&values, -0.1).is_err());
assert!(percentile_threshold(&values, 1.1).is_err());
}
}