use super::*;
struct Lcg(u64);
impl Lcg {
fn next_f32(&mut self) -> f32 {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
((self.0 >> 33) as f32) / ((1u64 << 31) as f32) - 0.5
}
}
#[test]
fn iid_chain_has_ess_near_n() {
let mut rng = Lcg(42);
let x: Vec<f32> = (0..4000).map(|_| rng.next_f32()).collect();
let e = ess(&x);
assert!(
e > 0.6 * x.len() as f32,
"iid chain should keep most of its draws, got ess={e} of {}",
x.len()
);
}
#[test]
fn autocorrelated_chain_has_ess_well_below_n() {
let mut rng = Lcg(7);
let rho = 0.9f32;
let mut v = 0.0f32;
let mut x = Vec::with_capacity(4000);
for _ in 0..4000 {
v = rho * v + rng.next_f32();
x.push(v);
}
let e = ess(&x);
let n = x.len() as f32;
assert!(
e < n / 5.0,
"a strongly autocorrelated chain must be discounted, got ess={e} of {n}"
);
assert!(e >= 1.0, "ess stays positive, got {e}");
}
#[test]
fn constant_chain_does_not_divide_by_zero() {
let e = ess(&vec![1.0f32; 500]);
assert_eq!(e, 500.0);
let short = ess(&[1.0, 2.0]);
assert_eq!(short, 2.0, "too short to estimate ⇒ n");
}
#[test]
fn mcse_is_nonzero_at_p_zero() {
let se = mcse_proportion(0.0, 1000.0);
assert!(se > 0.0, "p=0 must still carry MC error, got {se}");
assert!(
se < 0.002,
"…but it should be small at ess=1000, got {se}" );
let se_border = mcse_proportion(0.1, 1000.0);
assert!(
se_border > 0.008 && se_border < 0.011,
"lfsr=0.1 at ess=1000 ⇒ mcse≈0.0095, got {se_border}"
);
assert!(mcse_proportion(0.1, 100.0) > se_border);
}
#[test]
fn a_stationary_chain_has_rhat_near_one() {
use rand::rngs::SmallRng;
use rand::SeedableRng;
use rand_distr::{Distribution, StandardNormal};
let mut rng = SmallRng::seed_from_u64(11);
let x: Vec<f32> = (0..800)
.map(|_| {
let g: f64 = StandardNormal.sample(&mut rng);
g as f32
})
.collect();
let r = split_rhat(&x);
assert!(
(r - 1.0).abs() < 0.05,
"iid draws should give R̂ ≈ 1, got {r}"
);
}
#[test]
fn a_drifting_chain_is_caught() {
let x: Vec<f32> = (0..800).map(|i| i as f32 * 0.01).collect();
let r = split_rhat(&x);
assert!(
r > 1.1,
"a linear drift must not pass as converged, got R̂ {r}"
);
}
#[test]
fn a_constant_chain_reports_one_not_a_division_by_zero() {
let x = vec![0.7f32; 400];
let r = split_rhat(&x);
assert_eq!(r, 1.0, "a pinned chain should read 1.0, got {r}");
}
#[test]
fn constant_but_disagreeing_segments_are_not_converged() {
let mut x = vec![0.0f32; 200];
x.extend(vec![5.0f32; 200]);
assert!(
split_rhat(&x) > 1.1,
"two constant halves at different levels are not converged"
);
}
#[test]
fn a_chain_too_short_to_split_makes_no_claim() {
assert_eq!(split_rhat(&[1.0, 2.0, 3.0]), 1.0);
assert_eq!(split_rhat(&[]), 1.0);
}