use crate::{smooth, statistics::Metrics};
#[cfg(test)]
mod validation_tests {
use super::*;
#[test]
fn test_small_dataset_not_oversmoothed() {
let data = vec![1.0, 3.2, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0, 1.0];
let smoothed = smooth(&data, 2);
assert!(smoothed.len() >= 2,
"Small dataset was over-smoothed: {} points -> {} points",
data.len(), smoothed.len());
let original_metrics = Metrics::new(data.clone());
if smoothed.len() < data.len() {
let smoothed_metrics = Metrics::new(smoothed);
assert!(smoothed_metrics.roughness() <= original_metrics.roughness(),
"Smoothing increased roughness from {} to {}",
original_metrics.roughness(), smoothed_metrics.roughness());
}
}
#[test]
fn test_zigzag_pattern_smoothing() {
let data: Vec<f64> = (0..50).map(|i| if i % 2 == 0 { 10.0 } else { 0.0 }).collect();
let original_metrics = Metrics::new(data.clone());
let smoothed = smooth(&data, 10);
assert!(smoothed.len() >= 5,
"Zigzag pattern over-smoothed: {} -> {} points",
data.len(), smoothed.len());
let smoothed_metrics = Metrics::new(smoothed);
assert!(smoothed_metrics.roughness() < original_metrics.roughness() * 0.8,
"Smoothing didn't reduce roughness enough: {} -> {}",
original_metrics.roughness(), smoothed_metrics.roughness());
}
#[test]
fn test_seasonal_data_preserves_patterns() {
let data: Vec<f64> = (0..100).map(|i| {
let i_f64 = i as f64;
(i_f64 * 0.314).sin() * 5.0
}).collect();
let original_metrics = Metrics::new(data.clone());
let smoothed = smooth(&data, 20);
assert!(smoothed.len() >= 10,
"Seasonal data over-smoothed: {} -> {} points",
data.len(), smoothed.len());
let smoothed_metrics = Metrics::new(smoothed);
assert!(smoothed_metrics.kurtosis() >= original_metrics.kurtosis() * 0.9,
"Smoothing reduced kurtosis too much: {} -> {}",
original_metrics.kurtosis(), smoothed_metrics.kurtosis());
}
#[test]
fn test_roughness_always_decreases() {
let datasets = vec![
(0..50).map(|i| i as f64 * 0.5).collect::<Vec<f64>>(),
(0..50).map(|i| (i as f64 * 0.1).sin() * 10.0).collect::<Vec<f64>>(),
(0..50).map(|i| ((i * 7919) % 104729) as f64 / 104729.0 * 100.0).collect::<Vec<f64>>(),
(0..50).map(|i| if i % 2 == 0 { 5.0 } else { -5.0 }).collect::<Vec<f64>>()
];
for (i, data) in datasets.iter().enumerate() {
let original_metrics = Metrics::new(data.clone());
if original_metrics.roughness() < 1e-10 {
continue;
}
let smoothed = smooth(data, 10);
if smoothed.len() < data.len() {
let smoothed_metrics = Metrics::new(smoothed);
assert!(smoothed_metrics.roughness() <= original_metrics.roughness(),
"Dataset {} - Smoothing increased roughness: {} -> {}",
i, original_metrics.roughness(), smoothed_metrics.roughness());
}
}
}
#[test]
fn test_various_resolutions() {
let data: Vec<f64> = (0..200).map(|i| {
let i_f64 = i as f64;
i_f64 * 0.1 + (i_f64 * 0.05).sin() * 10.0 + ((i * 104729) % 15485863) as f64 / 15485863.0 * 5.0
}).collect();
let resolutions = [5, 10, 20, 50, 100];
for &resolution in &resolutions {
let smoothed = smooth(&data, resolution);
assert!(smoothed.len() > 0,
"Resolution {} produced empty result", resolution);
if resolution > 10 {
let lower_res_smoothed = smooth(&data, resolution / 2);
assert!(smoothed.len() >= lower_res_smoothed.len(),
"Higher resolution ({}) produced fewer points than lower resolution ({}): {} vs {}",
resolution, resolution/2, smoothed.len(), lower_res_smoothed.len());
}
}
}
#[test]
fn test_mean_preservation() {
let data: Vec<f64> = (0..100).map(|i| i as f64 + ((i * 104729) % 15485863) as f64 / 15485863.0 * 10.0).collect();
let original_mean = Metrics::mean(&data);
let smoothed = smooth(&data, 10);
if !smoothed.is_empty() {
let smoothed_mean = Metrics::mean(&smoothed);
let percent_diff = ((smoothed_mean - original_mean) / original_mean).abs() * 100.0;
assert!(percent_diff < 5.0,
"Smoothing changed mean by {:.2}%: {} -> {}",
percent_diff, original_mean, smoothed_mean);
}
}
#[test]
fn test_extreme_resolution_values() {
let data: Vec<f64> = (0..100).map(|i| i as f64).collect();
let smoothed_low = smooth(&data, 1);
assert!(!smoothed_low.is_empty(), "Resolution=1 produced empty result");
let smoothed_high = smooth(&data, 200);
assert_eq!(smoothed_high, data,
"Resolution > data.len() should return original data");
}
#[test]
fn test_empty_and_single_element() {
let empty: Vec<f64> = vec![];
let smoothed_empty = smooth(&empty, 10);
assert!(smoothed_empty.is_empty(), "Empty input should produce empty output");
let single = vec![42.0];
let smoothed_single = smooth(&single, 10);
assert_eq!(smoothed_single, single, "Single element should remain unchanged");
}
#[test]
fn test_nan_handling() {
let mut data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
data.push(f64::NAN);
let smoothed = smooth(&data, 2);
for (i, &val) in smoothed.iter().enumerate() {
assert!(!val.is_nan(), "Smoothed result contains NaN at position {}", i);
}
}
#[test]
fn test_large_dataset_performance() {
let data: Vec<f64> = (0..10000).map(|i| (i as f64 * 0.01).sin() * 10.0 + i as f64 * 0.1).collect();
let start = std::time::Instant::now();
let smoothed = smooth(&data, 100);
let duration = start.elapsed();
assert!(duration.as_secs() < 2, "Smoothing took too long: {:?}", duration);
assert!(!smoothed.is_empty(), "Large dataset produced empty result");
}
}