#[cfg(test)]
mod tests {
use atelier_quant::poisson::estimation;
use atelier_quant::poisson::PoissonProcess;
#[test]
fn test_validate_events_empty() {
assert!(estimation::validate_events(&[]).is_err());
}
#[test]
fn test_validate_events_single() {
assert!(estimation::validate_events(&[1.0]).is_err());
}
#[test]
fn test_validate_events_nan() {
assert!(estimation::validate_events(&[1.0, f64::NAN, 3.0]).is_err());
}
#[test]
fn test_validate_events_non_increasing() {
assert!(estimation::validate_events(&[1.0, 3.0, 2.0]).is_err());
}
#[test]
fn test_validate_events_ok() {
assert!(estimation::validate_events(&[0.1, 0.5, 1.0, 2.0]).is_ok());
}
#[test]
fn test_log_likelihood_known_value() {
let events = vec![0.0, 1.0, 2.0, 3.0];
let ll = estimation::log_likelihood(1.0, &events);
assert!(
(ll - (-3.0)).abs() < 1e-12,
"Expected -3.0, got {}",
ll
);
}
#[test]
fn test_log_likelihood_negative_lambda() {
let events = vec![0.0, 1.0, 2.0];
assert_eq!(estimation::log_likelihood(-1.0, &events), f64::NEG_INFINITY);
}
#[test]
fn test_log_likelihood_too_few_events() {
assert_eq!(estimation::log_likelihood(1.0, &[0.5]), f64::NEG_INFINITY);
}
#[test]
fn test_log_likelihood_maximised_at_mle() {
let events = vec![0.0, 0.5, 1.2, 1.8, 3.0, 4.5, 5.0];
let config = estimation::PoissonEstimationConfig;
let result = estimation::estimate_poisson_mle(&events, &config).unwrap();
let ll_mle = result.log_likelihood;
let delta = 0.01;
let ll_above = estimation::log_likelihood(result.lambda + delta, &events);
let ll_below = estimation::log_likelihood(result.lambda - delta, &events);
assert!(
ll_mle >= ll_above,
"ℓ(λ̂) = {} should be >= ℓ(λ̂+δ) = {}",
ll_mle,
ll_above
);
assert!(
ll_mle >= ll_below,
"ℓ(λ̂) = {} should be >= ℓ(λ̂-δ) = {}",
ll_mle,
ll_below
);
}
#[test]
fn test_mle_closed_form_exact() {
let events: Vec<f64> = (0..10).map(|i| i as f64).collect(); let config = estimation::PoissonEstimationConfig;
let result = estimation::estimate_poisson_mle(&events, &config).unwrap();
assert!(
(result.lambda - 1.0).abs() < 1e-12,
"Expected λ̂ = 1.0, got {}",
result.lambda
);
assert_eq!(result.iterations, 1);
assert!(result.converged);
}
#[test]
fn test_mle_roundtrip_recovery() {
let true_lambda = 5.0;
let pp = PoissonProcess::new(true_lambda).unwrap();
let events = pp.generate_values(0.0, 500);
let config = estimation::PoissonEstimationConfig;
let result = estimation::estimate_poisson_mle(&events, &config).unwrap();
let rel_err = ((result.lambda - true_lambda) / true_lambda).abs();
assert!(
rel_err < 0.30,
"λ̂ = {:.4} should be near {:.1} (rel_err = {:.2})",
result.lambda,
true_lambda,
rel_err
);
}
#[test]
fn test_aic_bic_formula() {
let events = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let config = estimation::PoissonEstimationConfig;
let result = estimation::estimate_poisson_mle(&events, &config).unwrap();
let k = 1.0_f64;
let n_f = events.len() as f64;
let expected_aic = 2.0 * k - 2.0 * result.log_likelihood;
let expected_bic = k * n_f.ln() - 2.0 * result.log_likelihood;
assert!(
(result.aic - expected_aic).abs() < 1e-12,
"AIC mismatch: {} vs {}",
result.aic,
expected_aic
);
assert!(
(result.bic - expected_bic).abs() < 1e-12,
"BIC mismatch: {} vs {}",
result.bic,
expected_bic
);
}
#[test]
fn test_compensator_linear() {
let events = vec![0.0, 1.0, 2.0, 3.0];
let lambda = 2.5;
assert!((estimation::compensator(lambda, &events, 0.0)).abs() < 1e-12);
assert!((estimation::compensator(lambda, &events, 2.0) - 5.0).abs() < 1e-12);
assert!((estimation::compensator(lambda, &events, 3.0) - 7.5).abs() < 1e-12);
}
#[test]
fn test_compensator_monotonic() {
let events = vec![0.0, 1.0, 2.0, 3.0];
let lambda = 1.5;
let mut prev = 0.0;
for t in [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] {
let c = estimation::compensator(lambda, &events, t);
assert!(c >= prev, "Compensator must be monotonically increasing");
prev = c;
}
}
#[test]
fn test_time_rescaling_residuals_positive() {
let pp = PoissonProcess::new(3.0).unwrap();
let events = pp.generate_values(0.0, 100);
let residuals = estimation::time_rescaling_residuals(3.0, &events);
assert_eq!(residuals.len(), 99);
for &r in &residuals {
assert!(r > 0.0, "Residuals must be positive, got {}", r);
}
}
#[test]
fn test_time_rescaling_residuals_mean_near_one() {
let true_lambda = 4.0;
let pp = PoissonProcess::new(true_lambda).unwrap();
let events = pp.generate_values(0.0, 500);
let residuals = estimation::time_rescaling_residuals(true_lambda, &events);
let mean = residuals.iter().sum::<f64>() / residuals.len() as f64;
assert!(
(mean - 1.0).abs() < 0.50,
"Mean residual {:.4} should be near 1.0",
mean
);
}
#[test]
fn test_time_rescaling_residuals_exact() {
let events = vec![0.0, 1.0, 2.0, 3.0];
let residuals = estimation::time_rescaling_residuals(2.0, &events);
assert_eq!(residuals.len(), 3);
for &r in &residuals {
assert!(
(r - 2.0).abs() < 1e-12,
"Expected residual 2.0, got {}",
r
);
}
}
}