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_basic_example_behavior() {
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());
}
println!("Fitting with {} data points", initial_data.len());
println!(
"Data range: {} to {}",
initial_data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
initial_data
.iter()
.fold(f64::NEG_INFINITY, |a, &b| a.max(b))
);
let fit_result = detector.fit(&initial_data);
if let Err(e) = &fit_result {
println!("Fit error: {:?}", e);
}
fit_result.unwrap();
let mut rng = CRand::new(1);
for _ in 0..n {
rng.rexp();
}
let mut anomaly = 0;
let mut excess = 0;
let mut normal = 0;
for _ in 0..1000 {
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 after 1000 samples:");
println!("ANOMALY={} EXCESS={} NORMAL={}", anomaly, excess, normal);
println!(
"Z={:.6} T={:.6}",
detector.anomaly_threshold(),
detector.excess_threshold()
);
assert!(anomaly + excess + normal == 1000);
assert!(!detector.anomaly_threshold().is_nan());
assert!(!detector.excess_threshold().is_nan());
assert!(normal > 0, "Should have some normal classifications");
}
#[test]
#[ignore] fn test_pure_rust_basic_example_larger() {
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..100_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 after 100K samples:");
println!("ANOMALY={} EXCESS={} NORMAL={}", anomaly, excess, normal);
println!(
"Z={:.6} T={:.6}",
detector.anomaly_threshold(),
detector.excess_threshold()
);
assert_eq!(anomaly + excess + normal, 100_000);
assert!(!detector.anomaly_threshold().is_nan());
assert!(!detector.excess_threshold().is_nan());
}