use crate::{ drift::{ConstantDrift, Drift}, gaussian::Gaussian, rating::Rating, time::Time, }; /// Per-history, temporal state for someone competing. /// /// The mutable half of a competitor: `Rating` holds their static /// configuration, this holds what inference learns as it sweeps. #[derive(Debug)] pub struct Competitor = ConstantDrift> { pub rating: Rating, /// The forward message carried from this competitor's last appearance, or /// `None` before they have appeared anywhere. /// /// Previously an improper `N_INF` served as the unset sentinel, which made /// "no message yet" indistinguishable from "a legitimately improper /// message" at the type level and required every reader to know the /// convention. pub message: Option, pub last_time: Option, } 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 { match self.message { Some(message) => { let elapsed_variance = match &self.last_time { Some(last) => self.rating.drift_variance_delta(last, now), None => 0.0, }; message.forget(elapsed_variance) } None => 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 { match self.message { Some(message) => message.forget(self.rating.drift_variance_for_elapsed(elapsed)), None => self.rating.prior, } } } impl Default for Competitor { fn default() -> Self { Self { rating: Rating::default(), message: None, last_time: None, } } } 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 = None; if last_time { c.last_time = None; } } }