diff --git a/src/error.rs b/src/error.rs index 6f4de66..f1f04f2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -73,7 +73,7 @@ pub enum InferenceError { /// `epsilon`. /// /// A fit that stops short is wrong by a little, which is the worst - /// available failure: every rating is finite, the ordering looks sensible, + /// available failure: every posterior is finite, the ordering looks sensible, /// and nothing in the numbers says they were still moving. Reported rather /// than returned as a flag on an `Ok`, because a flag has to be checked /// and `let _ = h.converge()` is the natural way not to. diff --git a/src/game.rs b/src/game.rs index d946cfb..b0d7e67 100644 --- a/src/game.rs +++ b/src/game.rs @@ -284,7 +284,9 @@ impl<'a, T: Time, D: Drift> Game<'a, T, D> { self.teams[t] .iter() .zip(self.weights[t].iter()) - .fold(N00, |p, (player, &w)| p + (player.performance() * w)) + .fold(N00, |p, (competitor, &w)| { + p + (competitor.performance() * w) + }) })); let n_diffs = n_teams.saturating_sub(1); @@ -361,18 +363,18 @@ impl<'a, T: Time, D: Drift> Game<'a, T, D> { .iter() .zip(self.weights.iter()) .enumerate() - .map(|(orig_i, (players, weights))| { + .map(|(orig_i, (competitors, weights))| { let si = arena.inv_buf[orig_i]; let m = arena.lhood_win[si] * arena.lhood_lose[si]; // Already folded into `team_prior` at the top of the chain, // indexed by sorted position. let performance = arena.team_prior[si]; - players + competitors .iter() .zip(weights.iter()) - .map(|(player, &w)| { - ((m - performance.exclude(player.performance() * w)) * (1.0 / w)) - .forget(player.beta.powi(2)) + .map(|(competitor, &w)| { + ((m - performance.exclude(competitor.performance() * w)) * (1.0 / w)) + .forget(competitor.beta.powi(2)) }) .collect::>() }) @@ -577,7 +579,7 @@ impl> Game<'_, T, D> { )) } - /// Convenience wrapper over [`Game::ranked`] for two single-player teams. + /// Convenience wrapper over [`Game::ranked`] for two single-competitor teams. /// /// # Errors /// @@ -597,14 +599,14 @@ impl> Game<'_, T, D> { /// # Errors /// - /// Wraps each player in a one-member team and delegates to + /// Wraps each competitor in a one-member team and delegates to /// [`Game::ranked`], so it returns the same errors. pub fn free_for_all( - players: &[&Rating], + competitors: &[&Rating], outcome: crate::Outcome, options: &GameOptions, ) -> Result, crate::InferenceError> { - let teams: Vec>> = players.iter().map(|p| vec![**p]).collect(); + let teams: Vec>> = competitors.iter().map(|p| vec![**p]).collect(); let team_refs: Vec<&[Rating]> = teams.iter().map(|t| t.as_slice()).collect(); Self::ranked(&team_refs, outcome, options) } @@ -1428,8 +1430,8 @@ mod tests { #[test] fn run_chain_honours_max_iter_in_convergence_options() { - let players: Vec = (0..4).map(|_| R::default()).collect(); - let teams: Vec> = players.iter().map(|p| vec![*p]).collect(); + let competitors: Vec = (0..4).map(|_| R::default()).collect(); + let teams: Vec> = competitors.iter().map(|p| vec![*p]).collect(); let result = vec![3.0, 2.0, 1.0, 0.0]; let weights = vec![vec![1.0]; 4]; @@ -1476,8 +1478,8 @@ mod tests { #[test] fn run_chain_with_damping_converges_to_same_posterior() { - let players: Vec = (0..4).map(|_| R::default()).collect(); - let teams: Vec> = players.iter().map(|p| vec![*p]).collect(); + let competitors: Vec = (0..4).map(|_| R::default()).collect(); + let teams: Vec> = competitors.iter().map(|p| vec![*p]).collect(); let result = vec![3.0, 2.0, 1.0, 0.0]; let weights = vec![vec![1.0]; 4]; diff --git a/src/history.rs b/src/history.rs index 1040842..cd2e2bc 100644 --- a/src/history.rs +++ b/src/history.rs @@ -268,7 +268,7 @@ impl, O: Observer, K: Eq + Hash + Clone> HistoryBuilder< History { size: 0, time_slices: Vec::new(), - agents: CompetitorStore::new(), + competitors: CompetitorStore::new(), keys: KeyTable::new(), mu: self.mu, sigma: self.sigma, @@ -391,7 +391,7 @@ pub struct History< > { size: usize, pub(crate) time_slices: Vec>, - pub(crate) agents: CompetitorStore, + pub(crate) competitors: CompetitorStore, keys: KeyTable, mu: f64, sigma: f64, @@ -470,17 +470,18 @@ impl, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History> = HashMap::new(); for (time, step) in self.filtered_pass() { - for (agent, posterior) in step.posteriors { - if let Some(key) = self.keys.key(agent).cloned() { + for (competitor, posterior) in step.posteriors { + if let Some(key) = self.keys.key(competitor).cloned() { data.entry(key).or_default().push((time, posterior)); } } @@ -804,7 +807,7 @@ impl, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History` in and demand // `K: Sync` from every caller, which the key type need not satisfy. - let agents = &self.agents; + let competitors = &self.competitors; #[cfg(feature = "rayon")] { @@ -831,7 +834,7 @@ impl, O: Observer, K: Eq + Hash + Clone> History = self .time_slices .par_iter() - .map(|ts| ts.log_evidence(targets, forward, agents)) + .map(|ts| ts.log_evidence(targets, forward, competitors)) .collect(); per_slice.into_iter().sum() } @@ -839,7 +842,7 @@ impl, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History { let row = n; n += 1; - first_rows.push((row, agent)); + first_rows.push((row, competitor)); row } Some(&prev) => { @@ -1143,16 +1146,17 @@ impl, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History = priors.keys().copied().collect(); conflict_scan.sort_unstable(); - for agent in &conflict_scan { - let batch = priors[agent]; - let held = self.declared.get(agent).copied().unwrap_or_default(); + for competitor in &conflict_scan { + let batch = priors[competitor]; + let held = self.declared.get(competitor).copied().unwrap_or_default(); if let (Some(existing), Some(new)) = (held.prior, batch.prior) { if existing != new { return Err(InferenceError::ConflictingCompetitorConfig { - competitor: agent.get(), + competitor: competitor.get(), field: "prior", }); } @@ -2021,15 +2025,15 @@ impl, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History, O: Observer, K: Eq + Hash + Clone> History 0 { - time_slice.new_forward_info(&self.agents); + time_slice.new_forward_info(&self.competitors); } for agent_idx in &this_agent { if let Some(skill) = time_slice.skills.get_mut(*agent_idx) { skill.elapsed = time_slice::compute_elapsed( - self.agents[*agent_idx].last_time.as_ref(), + self.competitors[*agent_idx].last_time.as_ref(), &time_slice.time, ); - let agent = self.agents.get_mut(*agent_idx).unwrap(); + let competitor = self.competitors.get_mut(*agent_idx).unwrap(); - agent.last_time = Some(time_slice.time); - agent.message = Some(time_slice.forward_prior_out(agent_idx)); + competitor.last_time = Some(time_slice.time); + competitor.message = Some(time_slice.forward_prior_out(agent_idx)); } } @@ -2184,29 +2188,41 @@ impl, O: Observer, K: Eq + Hash + Clone> History k && self.time_slices[k].time == t { let time_slice = &mut self.time_slices[k]; - time_slice.add_events(composition, results, weights, kinds_chunk, &self.agents); + time_slice.add_events( + composition, + results, + weights, + kinds_chunk, + &self.competitors, + ); for agent_idx in time_slice.skills.keys() { - let agent = self.agents.get_mut(agent_idx).unwrap(); + let competitor = self.competitors.get_mut(agent_idx).unwrap(); - agent.last_time = Some(t); - agent.message = Some(time_slice.forward_prior_out(&agent_idx)); + competitor.last_time = Some(t); + competitor.message = Some(time_slice.forward_prior_out(&agent_idx)); } k += 1; } else { 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.competitors, + ); self.time_slices.insert(k, time_slice); let time_slice = &self.time_slices[k]; for agent_idx in time_slice.skills.keys() { - let agent = self.agents.get_mut(agent_idx).unwrap(); + let competitor = self.competitors.get_mut(agent_idx).unwrap(); - agent.last_time = Some(t); - agent.message = Some(time_slice.forward_prior_out(&agent_idx)); + competitor.last_time = Some(t); + competitor.message = Some(time_slice.forward_prior_out(&agent_idx)); } k += 1; @@ -2218,19 +2234,19 @@ impl, O: Observer, K: Eq + Hash + Clone> History k { let time_slice = &mut self.time_slices[k]; - time_slice.new_forward_info(&self.agents); + time_slice.new_forward_info(&self.competitors); for agent_idx in &this_agent { if let Some(skill) = time_slice.skills.get_mut(*agent_idx) { skill.elapsed = time_slice::compute_elapsed( - self.agents[*agent_idx].last_time.as_ref(), + self.competitors[*agent_idx].last_time.as_ref(), &time_slice.time, ); - let agent = self.agents.get_mut(*agent_idx).unwrap(); + let competitor = self.competitors.get_mut(*agent_idx).unwrap(); - agent.last_time = Some(time_slice.time); - agent.message = Some(time_slice.forward_prior_out(agent_idx)); + competitor.last_time = Some(time_slice.time); + competitor.message = Some(time_slice.forward_prior_out(agent_idx)); } } @@ -2606,9 +2622,9 @@ impl, O: Observer, K: Eq + Hash + Clone> Joint<'_, T, D, if slice.time > time { break; } - for (agent, _) in slice.appearances() { - if let Some(row) = self.at_slice.get(&(agent, slice_idx)) { - as_of.insert(agent, (*row, slice_idx)); + for (competitor, _) in slice.appearances() { + if let Some(row) = self.at_slice.get(&(competitor, slice_idx)) { + as_of.insert(competitor, (*row, slice_idx)); } } } @@ -2657,7 +2673,7 @@ impl, O: Observer, K: Eq + Hash + Clone> Joint<'_, T, D, .keys .get(*key) .map_or(self.history.beta, |index| { - self.history.agents[index].rating.beta + self.history.competitors[index].rating.beta }); noise += beta * beta; } @@ -2812,7 +2828,11 @@ mod tests { let w = [vec![1.0], vec![1.0]]; let p = Game::ranked_with_arena( - h.time_slices[1].events[0].within_priors(false, &h.time_slices[1].skills, &h.agents), + h.time_slices[1].events[0].within_priors( + false, + &h.time_slices[1].skills, + &h.competitors, + ), &[0.0, 1.0], &w, P_DRAW, @@ -3731,7 +3751,7 @@ mod tests { let mut max_diff: f64 = 0.0; for (key, capped_pts) in curves_capped.iter() { - let full_pts = curves_full.get(key).expect("agent missing in full"); + let full_pts = curves_full.get(key).expect("competitor missing in full"); for (capped, full) in capped_pts.iter().zip(full_pts.iter()) { max_diff = max_diff.max((capped.1.mu() - full.1.mu()).abs()); max_diff = max_diff.max((capped.1.sigma() - full.1.sigma()).abs()); @@ -3778,7 +3798,7 @@ mod tests { let mut max_diff: f64 = 0.0; for (key, u_pts) in curves_u.iter() { - let d_pts = curves_d.get(key).expect("agent missing in damped"); + let d_pts = curves_d.get(key).expect("competitor missing in damped"); for (u, d) in u_pts.iter().zip(d_pts.iter()) { max_diff = max_diff.max((u.1.mu() - d.1.mu()).abs()); max_diff = max_diff.max((u.1.sigma() - d.1.sigma()).abs()); @@ -3824,10 +3844,10 @@ mod tests { let curves_a = h_a.learning_curves(); let curves_b = h_b.learning_curves(); for (key, a_pts) in curves_a.iter() { - let b_pts = curves_b.get(key).expect("agent missing in path B"); + let b_pts = curves_b.get(key).expect("competitor missing in path B"); for (a, b) in a_pts.iter().zip(b_pts.iter()) { - assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}"); - assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}"); + assert_eq!(a.1.pi(), b.1.pi(), "mismatch at competitor {key:?}"); + assert_eq!(a.1.tau(), b.1.tau(), "mismatch at competitor {key:?}"); } } } @@ -3866,10 +3886,10 @@ mod tests { let curves_a = h_a.learning_curves(); let curves_b = h_b.learning_curves(); for (key, a_pts) in curves_a.iter() { - let b_pts = curves_b.get(key).expect("agent missing in path B"); + let b_pts = curves_b.get(key).expect("competitor missing in path B"); for (a, b) in a_pts.iter().zip(b_pts.iter()) { - assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}"); - assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}"); + assert_eq!(a.1.pi(), b.1.pi(), "mismatch at competitor {key:?}"); + assert_eq!(a.1.tau(), b.1.tau(), "mismatch at competitor {key:?}"); } } @@ -3889,7 +3909,7 @@ mod tests { let curves_c = h_c.learning_curves(); let mut max_diff: f64 = 0.0; for (key, a_pts) in curves_a.iter() { - let c_pts = curves_c.get(key).expect("agent missing in path C"); + let c_pts = curves_c.get(key).expect("competitor missing in path C"); for (a, c) in a_pts.iter().zip(c_pts.iter()) { max_diff = max_diff.max((a.1.mu() - c.1.mu()).abs()); max_diff = max_diff.max((a.1.sigma() - c.1.sigma()).abs()); @@ -3931,10 +3951,10 @@ mod tests { let curves_a = h_a.learning_curves(); let curves_b = h_b.learning_curves(); for (key, a_pts) in curves_a.iter() { - let b_pts = curves_b.get(key).expect("agent missing"); + let b_pts = curves_b.get(key).expect("competitor missing"); for (a, b) in a_pts.iter().zip(b_pts.iter()) { - assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}"); - assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}"); + assert_eq!(a.1.pi(), b.1.pi(), "mismatch at competitor {key:?}"); + assert_eq!(a.1.tau(), b.1.tau(), "mismatch at competitor {key:?}"); } } } diff --git a/src/lib.rs b/src/lib.rs index 4f37b3d..91b1585 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -746,14 +746,14 @@ pub(crate) fn sort_time(xs: &[T], reverse: bool) -> Vec { x.into_iter().map(|(i, _)| i).collect() } -/// Calculates the match quality of the given rating groups. A result is the draw probability in the association +/// Calculates the match quality of the given teams. A result is the draw probability in the association /// /// Supports any number of groups. Values range roughly `[0, 1]`; 1 means a /// perfectly balanced match. /// /// # Panics /// -/// Panics if fewer than two rating groups are supplied, or if any group is +/// Panics if fewer than two teams are supplied, or if any group is /// empty — match quality is a property of a contest between at least two /// non-empty sides. /// @@ -764,18 +764,18 @@ pub(crate) fn sort_time(xs: &[T], reverse: bool) -> Vec { /// converted, because the input has no meaningful answer rather than an /// awkward one. #[must_use] -pub fn quality(rating_groups: &[&[Gaussian]], beta: f64) -> f64 { +pub fn quality(teams: &[&[Gaussian]], beta: f64) -> f64 { assert!( - rating_groups.len() >= 2, - "quality() requires at least 2 rating groups, got {}", - rating_groups.len() + teams.len() >= 2, + "quality() requires at least 2 teams, got {}", + teams.len() ); assert!( - rating_groups.iter().all(|group| !group.is_empty()), - "quality() requires every rating group to be non-empty" + teams.iter().all(|group| !group.is_empty()), + "quality() requires every team to be non-empty" ); - let flatten_ratings = rating_groups + let flatten_ratings = teams .iter() .flat_map(|group| group.iter()) .collect::>(); @@ -796,14 +796,14 @@ pub fn quality(rating_groups: &[&[Gaussian]], beta: f64) -> f64 { variance_matrix[(i, i)] = rating.sigma().powi(2); } - let mut rotated_a_matrix = Matrix::new(rating_groups.len() - 1, length); + let mut rotated_a_matrix = Matrix::new(teams.len() - 1, length); // Row `row` contrasts group `row` (+weight) against group `row + 1` // (-weight). `t` is the column where the current group's players start; // the negative block begins immediately after it. let mut t = 0; - for (row, group) in rating_groups.windows(2).enumerate() { + for (row, group) in teams.windows(2).enumerate() { let current = group[0]; let next = group[1]; diff --git a/src/time_slice.rs b/src/time_slice.rs index 6518ac1..681eb5d 100644 --- a/src/time_slice.rs +++ b/src/time_slice.rs @@ -50,12 +50,12 @@ pub enum EventKind { #[derive(Clone, Debug)] struct Item { - agent: Index, + competitor: Index, /// This competitor's slot in the owning slice's `SkillStore`, resolved /// once at ingestion. /// /// The convergence loop reaches skills through this rather than through - /// `agent`, which is what keeps `HashMap` hashing out of the hot path now + /// `competitor`, which is what keeps `HashMap` hashing out of the hot path now /// that the store is compact rather than indexed by the global `Index`. slot: u32, likelihood: Gaussian, @@ -66,9 +66,9 @@ impl Item { &self, forward: bool, skills: &SkillStore, - agents: &CompetitorStore, + competitors: &CompetitorStore, ) -> Rating { - let r = &agents[self.agent].rating; + let r = &competitors[self.competitor].rating; let skill = skills.at(self.slot); if forward { @@ -98,7 +98,7 @@ impl Event { pub(crate) fn iter_agents(&self) -> impl Iterator + '_ { self.teams .iter() - .flat_map(|t| t.items.iter().map(|it| it.agent)) + .flat_map(|t| t.items.iter().map(|it| it.competitor)) } fn outputs(&self) -> Vec { @@ -112,14 +112,14 @@ impl Event { &self, forward: bool, skills: &SkillStore, - agents: &CompetitorStore, + competitors: &CompetitorStore, ) -> Vec>> { self.teams .iter() .map(|team| { team.items .iter() - .map(|item| item.within_prior(forward, skills, agents)) + .map(|item| item.within_prior(forward, skills, competitors)) .collect::>() }) .collect::>() @@ -133,12 +133,12 @@ impl Event { fn compute>( &self, skills: &SkillStore, - agents: &CompetitorStore, + competitors: &CompetitorStore, p_draw: f64, convergence: crate::ConvergenceOptions, arena: &mut ScratchArena, ) -> EventUpdate { - let teams = self.within_priors(false, skills, agents); + let teams = self.within_priors(false, skills, competitors); let result = self.outputs(); let g = match self.kind { EventKind::Ranked => { @@ -179,12 +179,12 @@ impl Event { fn iteration_direct>( &mut self, skills: &mut SkillStore, - agents: &CompetitorStore, + competitors: &CompetitorStore, p_draw: f64, convergence: crate::ConvergenceOptions, arena: &mut ScratchArena, ) { - let update = self.compute(skills, agents, p_draw, convergence, arena); + let update = self.compute(skills, competitors, p_draw, convergence, arena); self.apply(skills, update); } } @@ -288,7 +288,7 @@ impl TimeSlice { results: Option>>, weights: Option>>>, kinds: Vec, - agents: &CompetitorStore, + competitors: &CompetitorStore, ) { let mut unique = Vec::with_capacity(10); @@ -303,9 +303,9 @@ impl TimeSlice { }); for idx in this_agent { - let elapsed = compute_elapsed(agents[*idx].last_time.as_ref(), &self.time); + let elapsed = compute_elapsed(competitors[*idx].last_time.as_ref(), &self.time); - let forward = agents[*idx].receive(&self.time); + let forward = competitors[*idx].receive(&self.time); if let Some(skill) = self.skills.get_mut(*idx) { skill.elapsed = elapsed; @@ -332,12 +332,12 @@ impl TimeSlice { .map(|(t, team)| { let items = team .iter() - .map(|&agent| Item { - agent, + .map(|&competitor| Item { + competitor, // Every participant was inserted into `skills` // just above, so the slot always resolves. slot: skills - .slot_of(agent) + .slot_of(competitor) .expect("participant must be present in the slice store"), likelihood: N_INF, }) @@ -376,7 +376,7 @@ impl TimeSlice { self.color_groups_dirty = true; - self.iteration(from, agents); + self.iteration(from, competitors); } pub(crate) fn posteriors(&self) -> HashMap { @@ -393,7 +393,7 @@ impl TimeSlice { /// Panics if an event references a competitor with no entry in this /// slice's skill store. `add_events` inserts one for every participant, so /// this cannot happen for slices built through the public API. - pub fn iteration>(&mut self, from: usize, agents: &CompetitorStore) { + pub fn iteration>(&mut self, from: usize, competitors: &CompetitorStore) { if from == 0 && self.color_groups_dirty { self.recompute_color_groups(); } @@ -401,7 +401,7 @@ impl TimeSlice { if from > 0 || self.color_groups.is_empty() { // Initial pass (add_events) or no color groups yet: simple sequential sweep. for event in self.events.iter_mut().skip(from) { - let teams = event.within_priors(false, &self.skills, agents); + let teams = event.within_priors(false, &self.skills, competitors); let result = event.outputs(); let g = match event.kind { @@ -436,14 +436,14 @@ impl TimeSlice { event.log_evidence = g.log_evidence; } } else { - self.sweep_color_groups(agents); + self.sweep_color_groups(competitors); } } /// Full event sweep using the color-group partition. Colors are processed /// sequentially; within each color the inner loop is parallel under rayon. /// - /// Events in one color group touch disjoint agent sets, so none of them + /// Events in one color group touch disjoint competitor sets, so none of them /// can observe another's writes. That makes the sweep separable: inference /// runs concurrently over shared `&self.skills`, and the resulting updates /// are folded in afterwards in index order. Splitting it this way needs no @@ -451,7 +451,7 @@ impl TimeSlice { /// across thread counts because the apply order does not depend on which /// worker finished first. #[cfg(feature = "rayon")] - fn sweep_color_groups>(&mut self, agents: &CompetitorStore) { + fn sweep_color_groups>(&mut self, competitors: &CompetitorStore) { use rayon::prelude::*; thread_local! { @@ -483,7 +483,7 @@ impl TimeSlice { let mut arena = cell.borrow_mut(); arena.reset(); - ev.compute(skills, agents, p_draw, convergence, &mut arena) + ev.compute(skills, competitors, p_draw, convergence, &mut arena) }) }) .collect(); @@ -495,7 +495,7 @@ impl TimeSlice { for ev in &mut self.events[range] { ev.iteration_direct( &mut self.skills, - agents, + competitors, p_draw, self.convergence, &mut self.arena, @@ -509,7 +509,7 @@ impl TimeSlice { /// Events within each color group are updated inline — no EventOutput allocation — /// matching the T2 performance profile. #[cfg(not(feature = "rayon"))] - fn sweep_color_groups>(&mut self, agents: &CompetitorStore) { + fn sweep_color_groups>(&mut self, competitors: &CompetitorStore) { for color_idx in 0..self.color_groups.groups.len() { if self.color_groups.groups[color_idx].is_empty() { continue; @@ -523,7 +523,7 @@ impl TimeSlice { for ev in &mut self.events[range] { ev.iteration_direct( &mut self.skills, - agents, + competitors, p_draw, self.convergence, &mut self.arena, @@ -544,7 +544,7 @@ impl TimeSlice { /// schedule default. pub(crate) fn iterate_to_convergence>( &mut self, - agents: &CompetitorStore, + competitors: &CompetitorStore, ) -> usize { use crate::{tuple_gt, tuple_max}; @@ -557,7 +557,7 @@ impl TimeSlice { while tuple_gt(step, epsilon) && i < max_iter { let old = self.posteriors(); - self.iteration(0, agents); + self.iteration(0, competitors); let new = self.posteriors(); @@ -575,37 +575,37 @@ impl TimeSlice { i } - pub(crate) fn forward_prior_out(&self, agent: &Index) -> Gaussian { - let skill = self.skills.get(*agent).unwrap(); + pub(crate) fn forward_prior_out(&self, competitor: &Index) -> Gaussian { + let skill = self.skills.get(*competitor).unwrap(); skill.forward * skill.likelihood } pub(crate) fn backward_prior_out>( &self, - agent: &Index, - agents: &CompetitorStore, + competitor: &Index, + competitors: &CompetitorStore, ) -> Gaussian { - let skill = self.skills.get(*agent).unwrap(); + let skill = self.skills.get(*competitor).unwrap(); let n = skill.likelihood * skill.backward; n.forget( - agents[*agent] + competitors[*competitor] .rating .drift_variance_for_elapsed(skill.elapsed), ) } - pub(crate) fn new_backward_info>(&mut self, agents: &CompetitorStore) { - for (agent, skill) in self.skills.iter_mut() { - skill.backward = agents[agent].message.unwrap_or(N_INF); + pub(crate) fn new_backward_info>(&mut self, competitors: &CompetitorStore) { + for (competitor, skill) in self.skills.iter_mut() { + skill.backward = competitors[competitor].message.unwrap_or(N_INF); } - self.iteration(0, agents); + self.iteration(0, competitors); } - pub(crate) fn new_forward_info>(&mut self, agents: &CompetitorStore) { - for (agent, skill) in self.skills.iter_mut() { - skill.forward = agents[agent].receive_for_elapsed(skill.elapsed); + pub(crate) fn new_forward_info>(&mut self, competitors: &CompetitorStore) { + for (competitor, skill) in self.skills.iter_mut() { + skill.forward = competitors[competitor].receive_for_elapsed(skill.elapsed); } - self.iteration(0, agents); + self.iteration(0, competitors); } /// Run this slice's events on forward (filtering) information alone. @@ -618,7 +618,7 @@ impl TimeSlice { pub(crate) fn filtered_step>( &self, incoming: &HashMap, - agents: &CompetitorStore, + competitors: &CompetitorStore, ) -> FilteredStep { let mut scratch = TimeSlice { events: self.events.clone(), @@ -641,16 +641,16 @@ impl TimeSlice { event.log_evidence = 0.0; } - for (agent, skill) in self.skills.iter() { - let rating = &agents[agent].rating; + for (competitor, skill) in self.skills.iter() { + let rating = &competitors[competitor].rating; - let forward = match incoming.get(&agent) { + let forward = match incoming.get(&competitor) { Some(message) => message.forget(rating.drift_variance_for_elapsed(skill.elapsed)), None => rating.prior, }; let slot = scratch.skills.insert( - agent, + competitor, Skill { forward, backward: N_INF, @@ -666,19 +666,19 @@ impl TimeSlice { // than leave it to be rediscovered after it breaks. debug_assert_eq!( Some(slot), - self.skills.slot_of(agent), - "scratch slot must match the real slice's slot for {agent:?}" + self.skills.slot_of(competitor), + "scratch slot must match the real slice's slot for {competitor:?}" ); } - scratch.iterate_to_convergence(agents); + scratch.iterate_to_convergence(competitors); FilteredStep { log_evidence: scratch.events.iter().map(|event| event.log_evidence).sum(), posteriors: scratch .skills .iter() - .map(|(agent, skill)| (agent, skill.posterior())) + .map(|(competitor, skill)| (competitor, skill.posterior())) .collect(), } } @@ -687,7 +687,7 @@ impl TimeSlice { &self, targets: &[Index], forward: bool, - agents: &CompetitorStore, + competitors: &CompetitorStore, ) -> f64 { // Hashed once rather than scanned per player per event, so a // `log_evidence_for` with many keys is not quadratic. @@ -696,7 +696,7 @@ impl TimeSlice { let mut arena = ScratchArena::new(); let run_event = |event: &Event, arena: &mut ScratchArena| -> f64 { - let teams = event.within_priors(forward, &self.skills, agents); + let teams = event.within_priors(forward, &self.skills, competitors); let result = event.outputs(); match event.kind { EventKind::Ranked => { @@ -741,7 +741,7 @@ impl TimeSlice { .teams .iter() .flat_map(|team| &team.items) - .any(|item| target_set.contains(&item.agent)) + .any(|item| target_set.contains(&item.competitor)) }) .map(|event| run_event(event, &mut arena)) .sum() @@ -753,7 +753,7 @@ impl TimeSlice { .teams .iter() .flat_map(|team| &team.items) - .any(|item| target_set.contains(&item.agent)) + .any(|item| target_set.contains(&item.competitor)) }) .map(|event| event.log_evidence) .sum() @@ -769,7 +769,12 @@ impl TimeSlice { event .teams .iter() - .map(|team| team.items.iter().map(|item| item.agent).collect::>()) + .map(|team| { + team.items + .iter() + .map(|item| item.competitor) + .collect::>() + }) .collect::>() }) .collect::>() @@ -831,7 +836,7 @@ impl TimeSlice { /// approximations that inference does not retain. pub(crate) fn scored_contrasts>( &self, - agents: &CompetitorStore, + competitors: &CompetitorStore, ) -> Vec<(Vec<(Index, f64)>, f64)> { let mut out = Vec::new(); @@ -857,8 +862,8 @@ impl TimeSlice { for (team, sign) in [(hi, 1.0), (lo, -1.0)] { for (m, item) in event.teams[team].items.iter().enumerate() { let w = event.weights[team][m]; - noise += w * w * agents[item.agent].rating.beta.powi(2); - contrast.push((item.agent, sign * w)); + noise += w * w * competitors[item.competitor].rating.beta.powi(2); + contrast.push((item.competitor, sign * w)); } } @@ -906,11 +911,11 @@ mod tests { let e = index_map.get_or_create("e"); let f = index_map.get_or_create("f"); - let mut agents: CompetitorStore = CompetitorStore::new(); + let mut competitors: CompetitorStore = CompetitorStore::new(); - for agent in [a, b, c, d, e, f] { - agents.insert( - agent, + for competitor in [a, b, c, d, e, f] { + competitors.insert( + competitor, Competitor { rating: Rating::new( Gaussian::from_ms(25.0, 25.0 / 3.0), @@ -933,7 +938,7 @@ mod tests { Some(vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]]), None, vec![EventKind::Ranked; 3], - &agents, + &competitors, ); let post = time_slice.posteriors(); @@ -969,7 +974,7 @@ mod tests { epsilon = 1e-6 ); - assert_eq!(time_slice.iterate_to_convergence(&agents), 1); + assert_eq!(time_slice.iterate_to_convergence(&competitors), 1); } #[test] @@ -983,11 +988,11 @@ mod tests { let e = index_map.get_or_create("e"); let f = index_map.get_or_create("f"); - let mut agents: CompetitorStore = CompetitorStore::new(); + let mut competitors: CompetitorStore = CompetitorStore::new(); - for agent in [a, b, c, d, e, f] { - agents.insert( - agent, + for competitor in [a, b, c, d, e, f] { + competitors.insert( + competitor, Competitor { rating: Rating::new( Gaussian::from_ms(25.0, 25.0 / 3.0), @@ -1010,7 +1015,7 @@ mod tests { Some(vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]]), None, vec![EventKind::Ranked; 3], - &agents, + &competitors, ); let post = time_slice.posteriors(); @@ -1031,7 +1036,7 @@ mod tests { epsilon = 1e-6 ); - assert!(time_slice.iterate_to_convergence(&agents) > 1); + assert!(time_slice.iterate_to_convergence(&competitors) > 1); let post = time_slice.posteriors(); @@ -1063,11 +1068,11 @@ mod tests { let e = index_map.get_or_create("e"); let f = index_map.get_or_create("f"); - let mut agents: CompetitorStore = CompetitorStore::new(); + let mut competitors: CompetitorStore = CompetitorStore::new(); - for agent in [a, b, c, d, e, f] { - agents.insert( - agent, + for competitor in [a, b, c, d, e, f] { + competitors.insert( + competitor, Competitor { rating: Rating::new( Gaussian::from_ms(25.0, 25.0 / 3.0), @@ -1090,10 +1095,10 @@ mod tests { Some(vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]]), None, vec![EventKind::Ranked; 3], - &agents, + &competitors, ); - time_slice.iterate_to_convergence(&agents); + time_slice.iterate_to_convergence(&competitors); let post = time_slice.posteriors(); @@ -1122,12 +1127,12 @@ mod tests { Some(vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]]), None, vec![EventKind::Ranked; 3], - &agents, + &competitors, ); assert_eq!(time_slice.events.len(), 6); - time_slice.iterate_to_convergence(&agents); + time_slice.iterate_to_convergence(&competitors); let post = time_slice.posteriors(); @@ -1166,11 +1171,11 @@ mod tests { let c = index_map.get_or_create("c"); let d = index_map.get_or_create("d"); - let mut agents: CompetitorStore = CompetitorStore::new(); + let mut competitors: CompetitorStore = CompetitorStore::new(); - for agent in [a, b, c, d] { - agents.insert( - agent, + for competitor in [a, b, c, d] { + competitors.insert( + competitor, Competitor { rating: Rating::new( Gaussian::from_ms(25.0, 25.0 / 3.0), @@ -1193,7 +1198,7 @@ mod tests { Some(vec![vec![1.0, 0.0], vec![1.0, 0.0], vec![1.0, 0.0]]), None, vec![EventKind::Ranked; 3], - &agents, + &competitors, ); assert_eq!(ts.color_groups.n_colors(), 2); @@ -1204,14 +1209,14 @@ mod tests { assert_eq!(ts.color_groups.color_range(1), 2..3); // Events at positions 0 and 1 (color 0) must be disjoint — verify by - // checking that the agent sets of self.events[0] and self.events[1] do - // not include the agent at self.events[2]. + // checking that the competitor sets of self.events[0] and self.events[1] do + // not include the competitor at self.events[2]. let agents_in_ev2: Vec = ts.events[2].iter_agents().collect(); let agents_in_ev0: Vec = ts.events[0].iter_agents().collect(); let agents_in_ev1: Vec = ts.events[1].iter_agents().collect(); // ev0 and ev1 must be disjoint from each other (color-0 invariant). assert!(agents_in_ev0.iter().all(|ag| !agents_in_ev1.contains(ag))); - // ev2 must share an agent with ev0 or ev1 (it needed its own color). + // ev2 must share an competitor with ev0 or ev1 (it needed its own color). let ev2_overlaps_ev0 = agents_in_ev2.iter().any(|ag| agents_in_ev0.contains(ag)); let ev2_overlaps_ev1 = agents_in_ev2.iter().any(|ag| agents_in_ev1.contains(ag)); assert!(ev2_overlaps_ev0 || ev2_overlaps_ev1); diff --git a/tests/quality.rs b/tests/quality.rs index cbc15fe..15b76a6 100644 --- a/tests/quality.rs +++ b/tests/quality.rs @@ -1,4 +1,4 @@ -//! `quality()` beyond two rating groups. +//! `quality()` beyond two teams. //! //! The historical golden (two equal singletons) is asserted in //! `src/lib.rs::tests::test_quality`. These cover the N-group generalisation, @@ -82,14 +82,14 @@ fn uneven_group_sizes_work() { } #[test] -#[should_panic(expected = "at least 2 rating groups")] +#[should_panic(expected = "at least 2 teams")] fn single_group_panics_with_clear_message() { let r = rating(25.0, 3.0); let _ = quality(&[&[r]], BETA); } #[test] -#[should_panic(expected = "at least 2 rating groups")] +#[should_panic(expected = "at least 2 teams")] fn zero_groups_panics_with_clear_message() { let _ = quality(&[], BETA); }