//! Cost of the joint posterior: factorising versus querying. //! //! The split is the whole point of `History::joint`. Factorising is `O(n^3)` in //! the history's appearances and depends only on the fit; a query is `O(n^2)` //! and depends only on the question. `posterior_of_one_shot` pays both every //! time, `joint_query` pays only the second. use criterion::{Criterion, criterion_group, criterion_main}; use smallvec::smallvec; use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team}; /// 30 slices of 8 duels: 480 appearances over 100 competitors. fn fitted() -> History { let mut h: History = History::builder() .key_type::() .mu(0.0) .sigma(6.0) .beta(1.0) .score_sigma(2.0) .drift(ConstantDrift::new(0.05)) // `max_iter: 30` was here, and this fixture needs more: `converge` // reported `NotConverged { iterations: 30, final_step: (4.5e-4, 0.0) }` // once it stopped returning short fits silently. The benchmark measures // the factorisation, whose cost depends on the fit's *shape* rather // than its exactness — but measuring it on an unconverged fit is still // measuring something nobody would run. .convergence(ConvergenceOptions { max_iter: trueskill_tt::ITERATIONS, epsilon: 1e-10, alpha: 1.0, }) .build(); let mut events: Vec> = Vec::new(); let mut k = 0usize; for t in 0..30i64 { for _ in 0..8 { k += 1; events.push(Event { time: t, teams: smallvec![ Team::with_members([Member::new(format!("p{}", k % 100))]), Team::with_members([Member::new(format!("p{}", (k + 37) % 100))]), ], outcome: Outcome::scores([ (k as f64 * 0.3).sin().abs() * 20.0, (k as f64 * 0.3).cos().abs() * 20.0, ]), }); } } h.add_events(events).unwrap(); let _ = h.converge().unwrap(); h } fn bench_joint(c: &mut Criterion) { let h = fitted(); let a = "p0".to_string(); let b = "p1".to_string(); let terms = [(&a, 1.0), (&b, -1.0)]; c.bench_function("joint_factorise_480_appearances", |bencher| { bencher.iter(|| std::hint::black_box(h.joint().unwrap().variables())); }); // Factorise-and-query, the cost the deleted `History::posterior_of` // wrapper paid on every call. Kept as the baseline the cached query below // is measured against. c.bench_function("posterior_of_one_shot_480_appearances", |bencher| { bencher.iter(|| std::hint::black_box(h.joint().unwrap().posterior_of(&terms).unwrap())); }); let joint = h.joint().unwrap(); c.bench_function("joint_query_480_appearances", |bencher| { bencher.iter(|| std::hint::black_box(joint.posterior_of(&terms).unwrap())); }); } criterion_group!(benches, bench_joint); criterion_main!(benches);