use libspot_rs::{SpotConfig, SpotDetector, SpotStatus};
struct CRand {
seed: u32,
}
impl CRand {
fn new(seed: u32) -> Self {
Self { seed }
}
fn next(&mut self) -> u32 {
self.seed = self.seed.wrapping_mul(1103515245).wrapping_add(12345);
(self.seed / 65536) % 32768
}
fn rexp(&mut self) -> f64 {
let u = self.next() as f64 / 32767.0;
if u <= 0.0 || u >= 1.0 {
return 1.0; }
-u.ln()
}
}
#[test]
fn test_pure_rust_exact_c_behavior_1m_samples() {
println!("Running pure Rust SPOT implementation test (1M samples)...");
let config = SpotConfig {
q: 0.0001, low_tail: false, discard_anomalies: true, level: 0.998, max_excess: 200, };
let mut detector = SpotDetector::new(config).unwrap();
let n = 20000;
let mut initial_data = Vec::with_capacity(n);
let mut rng = CRand::new(1);
for _ in 0..n {
initial_data.push(rng.rexp());
}
detector.fit(&initial_data).unwrap();
let mut anomaly = 0;
let mut excess = 0;
let mut normal = 0;
for _ in 0..1_000_000 {
let x = rng.rexp();
match detector.step(x).unwrap() {
SpotStatus::Normal => normal += 1,
SpotStatus::Excess => excess += 1,
SpotStatus::Anomaly => anomaly += 1,
}
}
let z = detector.anomaly_threshold();
let t = detector.excess_threshold();
println!("Pure Rust Results (1M samples):");
println!("ANOMALY={} EXCESS={} NORMAL={}", anomaly, excess, normal);
println!("Z={:.6} T={:.6}", z, t);
assert_eq!(anomaly + excess + normal, 1_000_000);
assert!(z.is_finite(), "Anomaly threshold should be finite");
assert!(t.is_finite(), "Excess threshold should be finite");
assert!(
normal > 900_000,
"Should have mostly normal classifications, got {}",
normal
);
assert!(excess > 0, "Should have some excess classifications");
assert!(anomaly < 2000, "Anomaly count seems too high: {}", anomaly);
assert!(excess < 50000, "Excess count seems too high: {}", excess);
println!("✓ Pure Rust SPOT implementation produces valid results!");
}
#[test]
fn test_pure_rust_matches_expected_c_pattern() {
let config = SpotConfig {
q: 0.0001,
low_tail: false,
discard_anomalies: true,
level: 0.998,
max_excess: 200,
};
let mut detector = SpotDetector::new(config).unwrap();
let n = 20000;
let mut initial_data = Vec::with_capacity(n);
let mut rng = CRand::new(1);
for _ in 0..n {
initial_data.push(rng.rexp());
}
detector.fit(&initial_data).unwrap();
let mut anomaly = 0;
let mut excess = 0;
let mut normal = 0;
for _ in 0..10_000 {
let x = rng.rexp();
match detector.step(x).unwrap() {
SpotStatus::Normal => normal += 1,
SpotStatus::Excess => excess += 1,
SpotStatus::Anomaly => anomaly += 1,
}
}
println!("Pure Rust Results (10K samples):");
println!("ANOMALY={} EXCESS={} NORMAL={}", anomaly, excess, normal);
println!(
"Z={:.6} T={:.6}",
detector.anomaly_threshold(),
detector.excess_threshold()
);
assert_eq!(anomaly + excess + normal, 10_000);
assert!(normal > 9_500, "Should have mostly normal classifications");
assert!(!detector.anomaly_threshold().is_nan());
assert!(!detector.excess_threshold().is_nan());
println!("✓ Pure Rust implementation behaves correctly on smaller dataset!");
}