refactor(time_slice): add convergence field, rename iterate_to_convergence

TimeSlice<T> gains a pub(crate) convergence: ConvergenceOptions field
set at construction. TimeSlice::new now takes it as a third parameter
(breaking change to the pub constructor, acceptable in 0.1.x).
History::add_events_with_prior passes self.convergence so the propagated
value reaches every TimeSlice. The pre-existing convergence-the-method
is renamed to iterate_to_convergence to disambiguate from the new
convergence-the-field.

The field is wired but not yet read by inference -- the three
Game::*_with_arena callsites in time_slice.rs still hardcode
ConvergenceOptions::default(). Task 2 changes that. Bit-equal because
the propagated value equals the hardcoded value end-to-end.

Also updated benches/batch.rs which has a fourth TimeSlice::new
callsite (not enumerated in the plan -- only src/ files were).
This commit is contained in:
2026-05-08 15:29:39 +02:00
parent 6e453b6845
commit 872f91797d
3 changed files with 20 additions and 14 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
use criterion::{Criterion, criterion_group, criterion_main}; use criterion::{Criterion, criterion_group, criterion_main};
use trueskill_tt::{ use trueskill_tt::{
BETA, Competitor, EventKind, GAMMA, KeyTable, MU, P_DRAW, Rating, SIGMA, TimeSlice, BETA, Competitor, ConvergenceOptions, EventKind, GAMMA, KeyTable, MU, P_DRAW, Rating, SIGMA,
drift::ConstantDrift, gaussian::Gaussian, storage::CompetitorStore, TimeSlice, drift::ConstantDrift, gaussian::Gaussian, storage::CompetitorStore,
}; };
fn criterion_benchmark(criterion: &mut Criterion) { fn criterion_benchmark(criterion: &mut Criterion) {
@@ -35,7 +35,7 @@ fn criterion_benchmark(criterion: &mut Criterion) {
let kinds = vec![EventKind::Ranked; composition.len()]; let kinds = vec![EventKind::Ranked; composition.len()];
let mut time_slice = TimeSlice::new(1, P_DRAW); let mut time_slice = TimeSlice::new(1, P_DRAW, ConvergenceOptions::default());
time_slice.add_events(composition, results, weights, kinds, &agents); time_slice.add_events(composition, results, weights, kinds, &agents);
criterion.bench_function("Batch::iteration", |b| { criterion.bench_function("Batch::iteration", |b| {
+1 -1
View File
@@ -594,7 +594,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
agent.message = time_slice.forward_prior_out(&agent_idx); agent.message = time_slice.forward_prior_out(&agent_idx);
} }
} else { } else {
let mut time_slice = TimeSlice::new(t, self.p_draw); let mut time_slice = TimeSlice::new(t, self.p_draw, self.convergence);
time_slice.add_events(composition, results, weights, kinds_chunk, &self.agents); time_slice.add_events(composition, results, weights, kinds_chunk, &self.agents);
self.time_slices.insert(k, time_slice); self.time_slices.insert(k, time_slice);
+16 -10
View File
@@ -175,17 +175,20 @@ pub struct TimeSlice<T: Time = i64> {
pub(crate) skills: SkillStore, pub(crate) skills: SkillStore,
pub(crate) time: T, pub(crate) time: T,
p_draw: f64, p_draw: f64,
#[allow(dead_code)]
pub(crate) convergence: crate::ConvergenceOptions,
arena: ScratchArena, arena: ScratchArena,
pub(crate) color_groups: ColorGroups, pub(crate) color_groups: ColorGroups,
} }
impl<T: Time> TimeSlice<T> { impl<T: Time> TimeSlice<T> {
pub fn new(time: T, p_draw: f64) -> Self { pub fn new(time: T, p_draw: f64, convergence: crate::ConvergenceOptions) -> Self {
Self { Self {
events: Vec::new(), events: Vec::new(),
skills: SkillStore::new(), skills: SkillStore::new(),
time, time,
p_draw, p_draw,
convergence,
arena: ScratchArena::new(), arena: ScratchArena::new(),
color_groups: ColorGroups::new(), color_groups: ColorGroups::new(),
} }
@@ -444,7 +447,10 @@ impl<T: Time> TimeSlice<T> {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn convergence<D: Drift<T>>(&mut self, agents: &CompetitorStore<T, D>) -> usize { pub(crate) fn iterate_to_convergence<D: Drift<T>>(
&mut self,
agents: &CompetitorStore<T, D>,
) -> usize {
let epsilon = 1e-6; let epsilon = 1e-6;
let iterations = 20; let iterations = 20;
@@ -643,7 +649,7 @@ mod tests {
); );
} }
let mut time_slice = TimeSlice::new(0i64, 0.0); let mut time_slice = TimeSlice::new(0i64, 0.0, crate::ConvergenceOptions::default());
time_slice.add_events( time_slice.add_events(
vec![ vec![
@@ -690,7 +696,7 @@ mod tests {
epsilon = 1e-6 epsilon = 1e-6
); );
assert_eq!(time_slice.convergence(&agents), 1); assert_eq!(time_slice.iterate_to_convergence(&agents), 1);
} }
#[test] #[test]
@@ -720,7 +726,7 @@ mod tests {
); );
} }
let mut time_slice = TimeSlice::new(0i64, 0.0); let mut time_slice = TimeSlice::new(0i64, 0.0, crate::ConvergenceOptions::default());
time_slice.add_events( time_slice.add_events(
vec![ vec![
@@ -752,7 +758,7 @@ mod tests {
epsilon = 1e-6 epsilon = 1e-6
); );
assert!(time_slice.convergence(&agents) > 1); assert!(time_slice.iterate_to_convergence(&agents) > 1);
let post = time_slice.posteriors(); let post = time_slice.posteriors();
@@ -800,7 +806,7 @@ mod tests {
); );
} }
let mut time_slice = TimeSlice::new(0i64, 0.0); let mut time_slice = TimeSlice::new(0i64, 0.0, crate::ConvergenceOptions::default());
time_slice.add_events( time_slice.add_events(
vec![ vec![
@@ -814,7 +820,7 @@ mod tests {
&agents, &agents,
); );
time_slice.convergence(&agents); time_slice.iterate_to_convergence(&agents);
let post = time_slice.posteriors(); let post = time_slice.posteriors();
@@ -848,7 +854,7 @@ mod tests {
assert_eq!(time_slice.events.len(), 6); assert_eq!(time_slice.events.len(), 6);
time_slice.convergence(&agents); time_slice.iterate_to_convergence(&agents);
let post = time_slice.posteriors(); let post = time_slice.posteriors();
@@ -898,7 +904,7 @@ mod tests {
); );
} }
let mut ts = TimeSlice::new(0i64, 0.0); let mut ts = TimeSlice::new(0i64, 0.0, crate::ConvergenceOptions::default());
ts.add_events( ts.add_events(
vec![ vec![