diff --git a/benches/batch.rs b/benches/batch.rs index 1562402..7bc0bc0 100644 --- a/benches/batch.rs +++ b/benches/batch.rs @@ -11,7 +11,7 @@ fn criterion_benchmark(criterion: &mut Criterion) { let b = index_map.get_or_create("b"); let c = index_map.get_or_create("c"); - let mut agents: CompetitorStore = CompetitorStore::new(); + let mut agents: CompetitorStore = CompetitorStore::new(); for agent in [a, b, c] { agents.insert( diff --git a/src/competitor.rs b/src/competitor.rs index f2f270b..78b44a5 100644 --- a/src/competitor.rs +++ b/src/competitor.rs @@ -3,6 +3,7 @@ use crate::{ drift::{ConstantDrift, Drift}, gaussian::Gaussian, rating::Rating, + time::Time, }; /// Per-history, temporal state for someone competing. @@ -10,41 +11,61 @@ use crate::{ /// Renamed from `Agent` in T2; the former `.player` field is now /// `.rating` to match the `Player → Rating` rename. #[derive(Debug)] -pub struct Competitor { - pub rating: Rating, +pub struct Competitor = ConstantDrift> { + pub rating: Rating, pub message: Gaussian, - pub last_time: i64, + pub last_time: Option, } -impl Competitor { - pub(crate) fn receive(&self, elapsed: i64) -> Gaussian { +impl> Competitor { + /// Compute the message received at time `now`, with drift accumulated + /// from `self.last_time` (if any) to `now`. + pub(crate) fn receive(&self, now: &T) -> Gaussian { + if self.message != N_INF { + let elapsed_variance = match &self.last_time { + Some(last) => self.rating.drift.variance_delta(last, now), + None => 0.0, + }; + self.message.forget(elapsed_variance) + } else { + self.rating.prior + } + } + + /// Compute the message using a pre-cached elapsed count (in `Time::elapsed_to` units). + /// + /// Used in convergence sweeps where the elapsed was cached at slice-construction time + /// and should not be recomputed from `last_time` (which may have shifted). + pub(crate) fn receive_for_elapsed(&self, elapsed: i64) -> Gaussian { if self.message != N_INF { self.message - .forget(self.rating.drift.variance_delta(elapsed)) + .forget(self.rating.drift.variance_for_elapsed(elapsed)) } else { self.rating.prior } } } -impl Default for Competitor { +impl Default for Competitor { fn default() -> Self { Self { rating: Rating::default(), message: N_INF, - last_time: i64::MIN, + last_time: None, } } } -pub(crate) fn clean<'a, D: Drift + 'a, C: Iterator>>( - competitors: C, - last_time: bool, -) { +pub(crate) fn clean<'a, T, D, C>(competitors: C, last_time: bool) +where + T: Time + 'a, + D: Drift + 'a, + C: Iterator>, +{ for c in competitors { c.message = N_INF; if last_time { - c.last_time = i64::MIN; + c.last_time = None; } } } diff --git a/src/drift.rs b/src/drift.rs index 5c7107e..57e684a 100644 --- a/src/drift.rs +++ b/src/drift.rs @@ -1,14 +1,36 @@ use std::fmt::Debug; -pub trait Drift: Copy + Debug { - fn variance_delta(&self, elapsed: i64) -> f64; +use crate::time::Time; + +/// Governs how much a competitor's skill can drift between two time points. +/// +/// Generic over `T: Time` so seasonal or calendar-aware drift is expressible +/// without going through `i64`. +pub trait Drift: Copy + Debug { + /// Variance added to the skill prior for elapsed time `from -> to`. + /// + /// Called with `from <= to`; returning zero means no drift accumulates. + fn variance_delta(&self, from: &T, to: &T) -> f64; + + /// Variance added for a pre-computed elapsed count (in the same units as + /// `T::elapsed_to`). Used where the elapsed is already cached as `i64`. + fn variance_for_elapsed(&self, elapsed: i64) -> f64; } +/// Simple constant-per-unit-time drift. +/// +/// For `Time = i64`: variance added is `(to - from) * gamma^2`. +/// For `Time = Untimed`: elapsed is always 0, so drift is always 0. #[derive(Clone, Copy, Debug)] pub struct ConstantDrift(pub f64); -impl Drift for ConstantDrift { - fn variance_delta(&self, elapsed: i64) -> f64 { - elapsed as f64 * self.0 * self.0 +impl Drift for ConstantDrift { + fn variance_delta(&self, from: &T, to: &T) -> f64 { + let elapsed = from.elapsed_to(to).max(0) as f64; + elapsed * self.0 * self.0 + } + + fn variance_for_elapsed(&self, elapsed: i64) -> f64 { + elapsed.max(0) as f64 * self.0 * self.0 } } diff --git a/src/game.rs b/src/game.rs index 9f76d9c..30f0889 100644 --- a/src/game.rs +++ b/src/game.rs @@ -8,12 +8,13 @@ use crate::{ factor::{Factor, trunc::TruncFactor}, gaussian::Gaussian, rating::Rating, + time::Time, tuple_gt, tuple_max, }; #[derive(Debug)] -pub struct Game<'a, D: Drift> { - teams: Vec>>, +pub struct Game<'a, T: Time = i64, D: Drift = crate::drift::ConstantDrift> { + teams: Vec>>, result: &'a [f64], weights: &'a [Vec], p_draw: f64, @@ -21,9 +22,9 @@ pub struct Game<'a, D: Drift> { pub(crate) evidence: f64, } -impl<'a, D: Drift> Game<'a, D> { +impl<'a, T: Time, D: Drift> Game<'a, T, D> { pub fn new( - teams: Vec>>, + teams: Vec>>, result: &'a [f64], weights: &'a [Vec], p_draw: f64, @@ -227,14 +228,16 @@ mod tests { use super::*; use crate::{ConstantDrift, GAMMA, Gaussian, N_INF, Rating, arena::ScratchArena}; + type R = Rating; + #[test] fn test_1vs1() { - let t_a = Rating::new( + let t_a = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ); - let t_b = Rating::new( + let t_b = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), @@ -256,12 +259,12 @@ mod tests { assert_ulps_eq!(a, Gaussian::from_ms(20.794779, 7.194481), epsilon = 1e-6); assert_ulps_eq!(b, Gaussian::from_ms(29.205220, 7.194481), epsilon = 1e-6); - let t_a = Rating::new( + let t_a = R::new( Gaussian::from_ms(29.0, 1.0), 25.0 / 6.0, ConstantDrift(GAMMA), ); - let t_b = Rating::new( + let t_b = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(GAMMA), @@ -283,8 +286,8 @@ mod tests { assert_ulps_eq!(a, Gaussian::from_ms(28.896475, 0.996604), epsilon = 1e-6); assert_ulps_eq!(b, Gaussian::from_ms(32.189211, 6.062063), epsilon = 1e-6); - let t_a = Rating::new(Gaussian::from_ms(1.139, 0.531), 1.0, ConstantDrift(0.2125)); - let t_b = Rating::new(Gaussian::from_ms(15.568, 0.51), 1.0, ConstantDrift(0.2125)); + let t_a = R::new(Gaussian::from_ms(1.139, 0.531), 1.0, ConstantDrift(0.2125)); + let t_b = R::new(Gaussian::from_ms(15.568, 0.51), 1.0, ConstantDrift(0.2125)); let w = [vec![1.0], vec![1.0]]; let g = Game::new( @@ -302,17 +305,17 @@ mod tests { #[test] fn test_1vs1vs1() { let teams = vec![ - vec![Rating::new( + vec![R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), )], - vec![Rating::new( + vec![R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), )], - vec![Rating::new( + vec![R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), @@ -367,12 +370,12 @@ mod tests { #[test] fn test_1vs1_draw() { - let t_a = Rating::new( + let t_a = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ); - let t_b = Rating::new( + let t_b = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), @@ -394,12 +397,12 @@ mod tests { assert_ulps_eq!(a, Gaussian::from_ms(24.999999, 6.469480), epsilon = 1e-6); assert_ulps_eq!(b, Gaussian::from_ms(24.999999, 6.469480), epsilon = 1e-6); - let t_a = Rating::new( + let t_a = R::new( Gaussian::from_ms(25.0, 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ); - let t_b = Rating::new( + let t_b = R::new( Gaussian::from_ms(29.0, 2.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), @@ -424,17 +427,17 @@ mod tests { #[test] fn test_1vs1vs1_draw() { - let t_a = Rating::new( + let t_a = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ); - let t_b = Rating::new( + let t_b = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ); - let t_c = Rating::new( + let t_c = R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), @@ -460,17 +463,17 @@ mod tests { assert_ulps_eq!(b, Gaussian::from_ms(25.0, 5.707424), epsilon = 1e-6); assert_ulps_eq!(c, Gaussian::from_ms(25.0, 5.729069), epsilon = 1e-6); - let t_a = Rating::new( + let t_a = R::new( Gaussian::from_ms(25.0, 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ); - let t_b = Rating::new( + let t_b = R::new( Gaussian::from_ms(25.0, 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ); - let t_c = Rating::new( + let t_c = R::new( Gaussian::from_ms(29.0, 2.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), @@ -498,29 +501,29 @@ mod tests { #[test] fn test_2vs1vs2_mixed() { let t_a = vec![ - Rating::new( + R::new( Gaussian::from_ms(12.0, 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ), - Rating::new( + R::new( Gaussian::from_ms(18.0, 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ), ]; - let t_b = vec![Rating::new( + let t_b = vec![R::new( Gaussian::from_ms(30.0, 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), )]; let t_c = vec![ - Rating::new( + R::new( Gaussian::from_ms(14.0, 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ), - Rating::new( + R::new( Gaussian::from_ms(16., 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), @@ -549,12 +552,12 @@ mod tests { let w_a = vec![1.0]; let w_b = vec![2.0]; - let t_a = vec![Rating::new( + let t_a = vec![R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(0.0), )]; - let t_b = vec![Rating::new( + let t_b = vec![R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(0.0), @@ -632,16 +635,8 @@ mod tests { let w_a = vec![1.0]; let w_b = vec![0.0]; - let t_a = vec![Rating::new( - Gaussian::from_ms(2.0, 6.0), - 1.0, - ConstantDrift(0.0), - )]; - let t_b = vec![Rating::new( - Gaussian::from_ms(2.0, 6.0), - 1.0, - ConstantDrift(0.0), - )]; + let t_a = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))]; + let t_b = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))]; let w = [w_a, w_b]; let g = Game::new( @@ -667,16 +662,8 @@ mod tests { let w_a = vec![1.0]; let w_b = vec![-1.0]; - let t_a = vec![Rating::new( - Gaussian::from_ms(2.0, 6.0), - 1.0, - ConstantDrift(0.0), - )]; - let t_b = vec![Rating::new( - Gaussian::from_ms(2.0, 6.0), - 1.0, - ConstantDrift(0.0), - )]; + let t_a = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))]; + let t_b = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))]; let w = [w_a, w_b]; let g = Game::new( @@ -694,12 +681,12 @@ mod tests { #[test] fn test_2vs2_weighted() { let t_a = vec![ - Rating::new( + R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(0.0), ), - Rating::new( + R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(0.0), @@ -708,12 +695,12 @@ mod tests { let w_a = vec![0.4, 0.8]; let t_b = vec![ - Rating::new( + R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(0.0), ), - Rating::new( + R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(0.0), @@ -824,7 +811,7 @@ mod tests { let g = Game::new( vec![ t_a.clone(), - vec![Rating::new( + vec![R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(0.0), diff --git a/src/history.rs b/src/history.rs index 851b11f..9238e55 100644 --- a/src/history.rs +++ b/src/history.rs @@ -8,12 +8,13 @@ use crate::{ rating::Rating, sort_time, storage::CompetitorStore, + time::Time, time_slice::{self, TimeSlice}, tuple_gt, tuple_max, }; #[derive(Clone)] -pub struct HistoryBuilder { +pub struct HistoryBuilder = ConstantDrift> { time: bool, mu: f64, sigma: f64, @@ -21,9 +22,10 @@ pub struct HistoryBuilder { drift: D, p_draw: f64, online: bool, + _time: std::marker::PhantomData, } -impl HistoryBuilder { +impl> HistoryBuilder { pub fn time(mut self, time: bool) -> Self { self.time = time; self @@ -44,7 +46,7 @@ impl HistoryBuilder { self } - pub fn drift(self, drift: D2) -> HistoryBuilder { + pub fn drift>(self, drift: D2) -> HistoryBuilder { HistoryBuilder { drift, time: self.time, @@ -53,6 +55,7 @@ impl HistoryBuilder { beta: self.beta, p_draw: self.p_draw, online: self.online, + _time: std::marker::PhantomData, } } @@ -66,7 +69,7 @@ impl HistoryBuilder { self } - pub fn build(self) -> History { + pub fn build(self) -> History { History { size: 0, time_slices: Vec::new(), @@ -82,14 +85,14 @@ impl HistoryBuilder { } } -impl HistoryBuilder { +impl HistoryBuilder { pub fn gamma(mut self, gamma: f64) -> Self { self.drift = ConstantDrift(gamma); self } } -impl Default for HistoryBuilder { +impl Default for HistoryBuilder { fn default() -> Self { Self { time: true, @@ -99,14 +102,15 @@ impl Default for HistoryBuilder { drift: ConstantDrift(GAMMA), p_draw: P_DRAW, online: false, + _time: std::marker::PhantomData, } } } -pub struct History { +pub struct History = ConstantDrift> { size: usize, - pub(crate) time_slices: Vec, - agents: CompetitorStore, + pub(crate) time_slices: Vec>, + pub(crate) agents: CompetitorStore, time: bool, mu: f64, sigma: f64, @@ -116,7 +120,7 @@ pub struct History { online: bool, } -impl Default for History { +impl Default for History { fn default() -> Self { Self { size: 0, @@ -133,13 +137,13 @@ impl Default for History { } } -impl History { - pub fn builder() -> HistoryBuilder { +impl History { + pub fn builder() -> HistoryBuilder { HistoryBuilder::default() } } -impl History { +impl> History { fn iteration(&mut self) -> (f64, f64) { let mut step = (0.0, 0.0); @@ -226,8 +230,8 @@ impl History { (step, i) } - pub fn learning_curves(&self) -> HashMap> { - let mut data: HashMap> = HashMap::new(); + pub fn learning_curves(&self) -> HashMap> { + let mut data: HashMap> = HashMap::new(); for b in &self.time_slices { for (agent, skill) in b.skills.iter() { @@ -250,7 +254,9 @@ impl History { .map(|ts| ts.log_evidence(self.online, targets, forward, &self.agents)) .sum() } +} +impl> History { pub fn add_events( &mut self, composition: Vec>>, @@ -267,7 +273,7 @@ impl History { results: Vec>, times: Vec, weights: Vec>>, - mut priors: HashMap>, + mut priors: HashMap>, ) { assert!(times.is_empty() || self.time, "length(times)>0 but !h.time"); assert!( @@ -310,7 +316,7 @@ impl History { ) }), message: N_INF, - last_time: i64::MIN, + last_time: None, }, ); } @@ -343,21 +349,30 @@ impl History { time_slice.new_forward_info(&self.agents); } - // TODO: Is it faster to iterate over agents in batch instead? 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, - time_slice.time, + self.agents[*agent_idx].last_time.as_ref(), + &time_slice.time, ); let agent = self.agents.get_mut(*agent_idx).unwrap(); - agent.last_time = if self.time { time_slice.time } else { i64::MAX }; + agent.last_time = Some(time_slice.time); agent.message = time_slice.forward_prior_out(agent_idx); } } + if !self.time { + let slice_time = time_slice.time; + for agent_idx in &this_agent { + let c = self.agents.get_mut(*agent_idx).unwrap(); + if c.last_time.is_some() { + c.last_time = Some(slice_time); + } + } + } + k += 1; } @@ -384,11 +399,11 @@ impl History { for agent_idx in time_slice.skills.keys() { let agent = self.agents.get_mut(agent_idx).unwrap(); - agent.last_time = if self.time { t } else { i64::MAX }; + agent.last_time = Some(t); agent.message = time_slice.forward_prior_out(&agent_idx); } } else { - let mut time_slice: TimeSlice = TimeSlice::new(t, self.p_draw); + let mut time_slice = TimeSlice::new(t, self.p_draw); time_slice.add_events(composition, results, weights, &self.agents); self.time_slices.insert(k, time_slice); @@ -398,10 +413,19 @@ impl History { for agent_idx in time_slice.skills.keys() { let agent = self.agents.get_mut(agent_idx).unwrap(); - agent.last_time = if self.time { t } else { i64::MAX }; + agent.last_time = Some(t); agent.message = time_slice.forward_prior_out(&agent_idx); } + if !self.time { + for agent_idx in &this_agent { + let c = self.agents.get_mut(*agent_idx).unwrap(); + if c.last_time.is_some() { + c.last_time = Some(t); + } + } + } + k += 1; } @@ -413,17 +437,16 @@ impl History { time_slice.new_forward_info(&self.agents); - // TODO: Is it faster to iterate over agents in batch instead? 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, - time_slice.time, + self.agents[*agent_idx].last_time.as_ref(), + &time_slice.time, ); let agent = self.agents.get_mut(*agent_idx).unwrap(); - agent.last_time = if self.time { time_slice.time } else { i64::MAX }; + agent.last_time = Some(time_slice.time); agent.message = time_slice.forward_prior_out(agent_idx); } } diff --git a/src/rating.rs b/src/rating.rs index 25fe13e..3530e24 100644 --- a/src/rating.rs +++ b/src/rating.rs @@ -1,7 +1,10 @@ +use std::marker::PhantomData; + use crate::{ BETA, GAMMA, drift::{ConstantDrift, Drift}, gaussian::Gaussian, + time::Time, }; /// Static rating configuration: prior skill, performance noise `beta`, drift. @@ -9,15 +12,21 @@ use crate::{ /// Renamed from `Player` in T2; `Rating` better describes the data /// (a configuration) vs. a person (who's a `Competitor` with state). #[derive(Clone, Copy, Debug)] -pub struct Rating { +pub struct Rating = ConstantDrift> { pub(crate) prior: Gaussian, pub(crate) beta: f64, pub(crate) drift: D, + pub(crate) _time: PhantomData, } -impl Rating { +impl> Rating { pub fn new(prior: Gaussian, beta: f64, drift: D) -> Self { - Self { prior, beta, drift } + Self { + prior, + beta, + drift, + _time: PhantomData, + } } pub(crate) fn performance(&self) -> Gaussian { @@ -25,12 +34,13 @@ impl Rating { } } -impl Default for Rating { +impl Default for Rating { fn default() -> Self { Self { prior: Gaussian::default(), beta: BETA, drift: ConstantDrift(GAMMA), + _time: PhantomData, } } } diff --git a/src/storage/competitor_store.rs b/src/storage/competitor_store.rs index b8f392f..25f72aa 100644 --- a/src/storage/competitor_store.rs +++ b/src/storage/competitor_store.rs @@ -1,17 +1,17 @@ -use crate::{Index, competitor::Competitor, drift::Drift}; +use crate::{Index, competitor::Competitor, drift::Drift, time::Time}; /// Dense Vec-backed store for competitor state in History. /// /// Indexed directly by Index.0, eliminating HashMap hashing in the -/// forward/backward sweep. Uses `Vec>>` so slots can be +/// forward/backward sweep. Uses `Vec>>` so slots can be /// absent without an explicit present mask. #[derive(Debug)] -pub struct CompetitorStore { - competitors: Vec>>, +pub struct CompetitorStore = crate::drift::ConstantDrift> { + competitors: Vec>>, n_present: usize, } -impl Default for CompetitorStore { +impl> Default for CompetitorStore { fn default() -> Self { Self { competitors: Vec::new(), @@ -20,7 +20,7 @@ impl Default for CompetitorStore { } } -impl CompetitorStore { +impl> CompetitorStore { pub fn new() -> Self { Self::default() } @@ -31,7 +31,7 @@ impl CompetitorStore { } } - pub fn insert(&mut self, idx: Index, competitor: Competitor) { + pub fn insert(&mut self, idx: Index, competitor: Competitor) { self.ensure_capacity(idx.0); if self.competitors[idx.0].is_none() { self.n_present += 1; @@ -39,11 +39,11 @@ impl CompetitorStore { self.competitors[idx.0] = Some(competitor); } - pub fn get(&self, idx: Index) -> Option<&Competitor> { + pub fn get(&self, idx: Index) -> Option<&Competitor> { self.competitors.get(idx.0).and_then(|slot| slot.as_ref()) } - pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor> { + pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor> { self.competitors .get_mut(idx.0) .and_then(|slot| slot.as_mut()) @@ -61,34 +61,34 @@ impl CompetitorStore { self.n_present == 0 } - pub fn iter(&self) -> impl Iterator)> { + pub fn iter(&self) -> impl Iterator)> { self.competitors .iter() .enumerate() .filter_map(|(i, slot)| slot.as_ref().map(|a| (Index(i), a))) } - pub fn iter_mut(&mut self) -> impl Iterator)> { + pub fn iter_mut(&mut self) -> impl Iterator)> { self.competitors .iter_mut() .enumerate() .filter_map(|(i, slot)| slot.as_mut().map(|a| (Index(i), a))) } - pub fn values_mut(&mut self) -> impl Iterator> { + pub fn values_mut(&mut self) -> impl Iterator> { self.competitors.iter_mut().filter_map(|s| s.as_mut()) } } -impl std::ops::Index for CompetitorStore { - type Output = Competitor; - fn index(&self, idx: Index) -> &Competitor { +impl> std::ops::Index for CompetitorStore { + type Output = Competitor; + fn index(&self, idx: Index) -> &Competitor { self.get(idx).expect("competitor not found at index") } } -impl std::ops::IndexMut for CompetitorStore { - fn index_mut(&mut self, idx: Index) -> &mut Competitor { +impl> std::ops::IndexMut for CompetitorStore { + fn index_mut(&mut self, idx: Index) -> &mut Competitor { self.get_mut(idx).expect("competitor not found at index") } } @@ -100,7 +100,7 @@ mod tests { #[test] fn insert_then_get() { - let mut store: CompetitorStore = CompetitorStore::new(); + let mut store: CompetitorStore = CompetitorStore::new(); let idx = Index(7); store.insert(idx, Competitor::default()); assert!(store.contains(idx)); @@ -110,7 +110,7 @@ mod tests { #[test] fn iter_in_index_order() { - let mut store: CompetitorStore = CompetitorStore::new(); + let mut store: CompetitorStore = CompetitorStore::new(); store.insert(Index(2), Competitor::default()); store.insert(Index(0), Competitor::default()); store.insert(Index(5), Competitor::default()); @@ -120,7 +120,7 @@ mod tests { #[test] fn index_operator_works() { - let mut store: CompetitorStore = CompetitorStore::new(); + let mut store: CompetitorStore = CompetitorStore::new(); store.insert(Index(3), Competitor::default()); let _ = &store[Index(3)]; } diff --git a/src/time_slice.rs b/src/time_slice.rs index 6f1ed1f..162398a 100644 --- a/src/time_slice.rs +++ b/src/time_slice.rs @@ -12,6 +12,7 @@ use crate::{ gaussian::Gaussian, rating::Rating, storage::{CompetitorStore, SkillStore}, + time::Time, tuple_gt, tuple_max, }; @@ -49,13 +50,13 @@ struct Item { } impl Item { - fn within_prior( + fn within_prior>( &self, online: bool, forward: bool, skills: &SkillStore, - agents: &CompetitorStore, - ) -> Rating { + agents: &CompetitorStore, + ) -> Rating { let r = &agents[self.agent].rating; let skill = skills.get(self.agent).unwrap(); @@ -90,13 +91,13 @@ impl Event { .collect::>() } - pub(crate) fn within_priors( + pub(crate) fn within_priors>( &self, online: bool, forward: bool, skills: &SkillStore, - agents: &CompetitorStore, - ) -> Vec>> { + agents: &CompetitorStore, + ) -> Vec>> { self.teams .iter() .map(|team| { @@ -110,16 +111,16 @@ impl Event { } #[derive(Debug)] -pub struct TimeSlice { +pub struct TimeSlice { pub(crate) events: Vec, pub(crate) skills: SkillStore, - pub(crate) time: i64, + pub(crate) time: T, p_draw: f64, arena: ScratchArena, } -impl TimeSlice { - pub fn new(time: i64, p_draw: f64) -> Self { +impl TimeSlice { + pub fn new(time: T, p_draw: f64) -> Self { Self { events: Vec::new(), skills: SkillStore::new(), @@ -129,12 +130,12 @@ impl TimeSlice { } } - pub fn add_events( + pub fn add_events>( &mut self, composition: Vec>>, results: Vec>, weights: Vec>>, - agents: &CompetitorStore, + agents: &CompetitorStore, ) { let mut unique = Vec::with_capacity(10); @@ -149,16 +150,16 @@ impl TimeSlice { }); for idx in this_agent { - let elapsed = compute_elapsed(agents[*idx].last_time, self.time); + let elapsed = compute_elapsed(agents[*idx].last_time.as_ref(), &self.time); if let Some(skill) = self.skills.get_mut(*idx) { skill.elapsed = elapsed; - skill.forward = agents[*idx].receive(elapsed); + skill.forward = agents[*idx].receive(&self.time); } else { self.skills.insert( *idx, Skill { - forward: agents[*idx].receive(elapsed), + forward: agents[*idx].receive(&self.time), elapsed, ..Default::default() }, @@ -220,7 +221,7 @@ impl TimeSlice { .collect::>() } - pub fn iteration(&mut self, from: usize, agents: &CompetitorStore) { + pub fn iteration>(&mut self, from: usize, agents: &CompetitorStore) { for event in self.events.iter_mut().skip(from) { let teams = event.within_priors(false, false, &self.skills, agents); let result = event.outputs(); @@ -241,7 +242,7 @@ impl TimeSlice { } #[allow(dead_code)] - pub(crate) fn convergence(&mut self, agents: &CompetitorStore) -> usize { + pub(crate) fn convergence>(&mut self, agents: &CompetitorStore) -> usize { let epsilon = 1e-6; let iterations = 20; @@ -270,36 +271,41 @@ impl TimeSlice { skill.forward * skill.likelihood } - pub(crate) fn backward_prior_out( + pub(crate) fn backward_prior_out>( &self, agent: &Index, - agents: &CompetitorStore, + agents: &CompetitorStore, ) -> Gaussian { let skill = self.skills.get(*agent).unwrap(); let n = skill.likelihood * skill.backward; - n.forget(agents[*agent].rating.drift.variance_delta(skill.elapsed)) + n.forget( + agents[*agent] + .rating + .drift + .variance_for_elapsed(skill.elapsed), + ) } - pub(crate) fn new_backward_info(&mut self, agents: &CompetitorStore) { + pub(crate) fn new_backward_info>(&mut self, agents: &CompetitorStore) { for (agent, skill) in self.skills.iter_mut() { skill.backward = agents[agent].message; } self.iteration(0, agents); } - pub(crate) fn new_forward_info(&mut self, agents: &CompetitorStore) { + pub(crate) fn new_forward_info>(&mut self, agents: &CompetitorStore) { for (agent, skill) in self.skills.iter_mut() { - skill.forward = agents[agent].receive(skill.elapsed); + skill.forward = agents[agent].receive_for_elapsed(skill.elapsed); } self.iteration(0, agents); } - pub(crate) fn log_evidence( + pub(crate) fn log_evidence>( &self, online: bool, targets: &[Index], forward: bool, - agents: &CompetitorStore, + agents: &CompetitorStore, ) -> f64 { // log_evidence is infrequent; a local arena avoids needing &mut self. let mut arena = ScratchArena::new(); @@ -388,14 +394,8 @@ impl TimeSlice { } } -pub(crate) fn compute_elapsed(last_time: i64, actual_time: i64) -> i64 { - if last_time == i64::MIN { - 0 - } else if last_time == i64::MAX { - 1 - } else { - actual_time - last_time - } +pub(crate) fn compute_elapsed(last: Option<&T>, current: &T) -> i64 { + last.map(|l| l.elapsed_to(current).max(0)).unwrap_or(0) } #[cfg(test)] @@ -419,7 +419,7 @@ 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 agents: CompetitorStore = CompetitorStore::new(); for agent in [a, b, c, d, e, f] { agents.insert( @@ -435,7 +435,7 @@ mod tests { ); } - let mut time_slice = TimeSlice::new(0, 0.0); + let mut time_slice = TimeSlice::new(0i64, 0.0); time_slice.add_events( vec![ @@ -495,7 +495,7 @@ 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 agents: CompetitorStore = CompetitorStore::new(); for agent in [a, b, c, d, e, f] { agents.insert( @@ -511,7 +511,7 @@ mod tests { ); } - let mut time_slice = TimeSlice::new(0, 0.0); + let mut time_slice = TimeSlice::new(0i64, 0.0); time_slice.add_events( vec![ @@ -574,7 +574,7 @@ 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 agents: CompetitorStore = CompetitorStore::new(); for agent in [a, b, c, d, e, f] { agents.insert( @@ -590,7 +590,7 @@ mod tests { ); } - let mut time_slice = TimeSlice::new(0, 0.0); + let mut time_slice = TimeSlice::new(0i64, 0.0); time_slice.add_events( vec![