//! Determinism tests: identical posteriors across RAYON_NUM_THREADS //! values. Only compiled with the `rayon` feature. #![cfg(feature = "rayon")] use smallvec::smallvec; use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team}; /// Build a deterministic workload using a simple LCG (no external rand crate). fn build_and_converge(seed: u64) -> Vec<(i64, trueskill_tt::Gaussian)> { let mut h = History::::builder_with_key() .mu(25.0) .sigma(25.0 / 3.0) .beta(25.0 / 6.0) .drift(ConstantDrift(25.0 / 300.0)) .convergence(ConvergenceOptions { max_iter: 30, epsilon: 1e-6, }) .build(); // LCG for deterministic pseudo-random ints. let mut rng = seed; let mut next = || { rng = rng .wrapping_mul(6364136223846793005) .wrapping_add(1442695040888963407); rng }; let mut events: Vec> = Vec::with_capacity(200); for ev_i in 0..200 { let a = (next() % 40) as usize; let mut b = (next() % 40) as usize; while b == a { b = (next() % 40) as usize; } // ~10 events per slice so color groups have material parallelism. events.push(Event { time: (ev_i as i64 / 10) + 1, teams: smallvec![ Team::with_members([Member::new(format!("p{a}"))]), Team::with_members([Member::new(format!("p{b}"))]), ], outcome: Outcome::winner((next() % 2) as u32, 2), }); } h.add_events(events).unwrap(); h.converge().unwrap(); // Sample one competitor's curve for the comparison. h.learning_curve("p0") } #[test] fn posteriors_identical_across_thread_counts() { let sizes = [1usize, 2, 4, 8]; let mut results: Vec> = Vec::new(); for &n in &sizes { let pool = rayon::ThreadPoolBuilder::new() .num_threads(n) .build() .expect("rayon pool build"); let curve = pool.install(|| build_and_converge(42)); results.push(curve); } let reference = &results[0]; for (i, curve) in results.iter().enumerate().skip(1) { assert_eq!( curve.len(), reference.len(), "curve length differs at {n} threads", n = sizes[i], ); for (j, (&(t_ref, g_ref), &(t, g))) in reference.iter().zip(curve.iter()).enumerate() { assert_eq!( t_ref, t, "time point {j} differs at {n} threads: ref={t_ref} vs got={t}", n = sizes[i], ); assert_eq!( g_ref.mu().to_bits(), g.mu().to_bits(), "mu bits differ at {n} threads, time {t}: ref={ref_mu} got={got_mu}", n = sizes[i], ref_mu = g_ref.mu(), got_mu = g.mu(), ); assert_eq!( g_ref.sigma().to_bits(), g.sigma().to_bits(), "sigma bits differ at {n} threads, time {t}: ref={ref_sigma} got={got_sigma}", n = sizes[i], ref_sigma = g_ref.sigma(), got_sigma = g.sigma(), ); } } }