From dc10504b80df458f4e26b52338c6f50679cb8cac Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Sat, 18 Jun 2022 22:27:38 +0200 Subject: [PATCH] Port from julia version instead --- Cargo.toml | 2 + README.md | 4 +- examples/atp.rs | 6 + src/agent.rs | 37 +++ src/batch.rs | 500 ++++++++++++++++++++------------------ src/game.rs | 633 ++++++++++++++++++++---------------------------- src/gaussian.rs | 117 +++++---- src/history.rs | 437 +++++++++++++++++---------------- src/lib.rs | 214 ++++++++++++++-- src/message.rs | 73 ++++-- src/player.rs | 41 ++-- src/utils.rs | 248 ------------------- src/variable.rs | 42 ---- 13 files changed, 1141 insertions(+), 1213 deletions(-) create mode 100644 src/agent.rs delete mode 100644 src/utils.rs delete mode 100644 src/variable.rs diff --git a/Cargo.toml b/Cargo.toml index 4194002..53f6483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ name = "trueskill-tt" version = "0.1.0" edition = "2021" +[dependencies] + [dev-dependencies] approx = "0.5.1" time = { version = "0.3.9", features = ["parsing"] } diff --git a/README.md b/README.md index 7ddea97..51331c8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,6 @@ Rust port of [TrueSkillThroughTime.py](https://github.com/glandfried/TrueSkillTh ## Todo -- [ ] Add examples (use same TrueSkillThroughTime.py) -- [x] Change `time` to always be f64 +- [ ] Change time from u64 to i64 so we can use i64::MIN in `batch::compute_elapsed()` +- [ ] Add examples (use same TrueSkillThroughTime.(py|jl)) - [ ] Add Observer (see [argmin](https://docs.rs/argmin/latest/argmin/core/trait.Observe.html) for inspiration) diff --git a/examples/atp.rs b/examples/atp.rs index 730e45f..9389a95 100644 --- a/examples/atp.rs +++ b/examples/atp.rs @@ -1,3 +1,4 @@ +/* use std::collections::HashMap; use time::Date; @@ -193,3 +194,8 @@ mod csv { } } } +*/ + +fn main() { + // +} diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 0000000..22edda7 --- /dev/null +++ b/src/agent.rs @@ -0,0 +1,37 @@ +use crate::{gaussian::Gaussian, player::Player, N_INF}; + +pub(crate) struct Agent { + pub(crate) player: Player, + pub(crate) message: Gaussian, + pub(crate) last_time: u64, +} + +impl Agent { + pub(crate) fn receive(&self, elapsed: u64) -> Gaussian { + if self.message != N_INF { + self.message.forget(self.player.gamma, elapsed) + } else { + self.player.prior + } + } +} + +impl Default for Agent { + fn default() -> Self { + Self { + player: Player::default(), + message: N_INF, + last_time: u64::MIN, + } + } +} + +pub(crate) fn clean<'a, A: Iterator>(agents: A, last_time: bool) { + for a in agents { + a.message = N_INF; + + if last_time { + a.last_time = 0; + } + } +} diff --git a/src/batch.rs b/src/batch.rs index 6372b93..38fb1bb 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -1,13 +1,15 @@ use std::collections::{HashMap, HashSet}; -use crate::{Game, Gaussian, Player, PlayerIndex, N_INF}; +use crate::{ + agent::Agent, game::Game, gaussian::Gaussian, player::Player, tuple_gt, tuple_max, N_INF, +}; -#[derive(Clone, Debug)] -pub struct Skill { - pub forward: Gaussian, - pub backward: Gaussian, - pub likelihood: Gaussian, - pub elapsed: f64, +pub(crate) struct Skill { + pub(crate) forward: Gaussian, + backward: Gaussian, + likelihood: Gaussian, + pub(crate) elapsed: u64, + pub(crate) online: Gaussian, } impl Default for Skill { @@ -16,67 +18,30 @@ impl Default for Skill { forward: N_INF, backward: N_INF, likelihood: N_INF, - elapsed: 0.0, + elapsed: 0, + online: N_INF, } } } -#[derive(Clone, Copy, Debug)] -pub struct Agent { - pub player: Player, - pub message: Gaussian, - pub last_time: f64, -} - -impl Agent { - pub fn new(player: Player, message: Gaussian, last_time: f64) -> Self { - Self { - player, - message, - last_time, - } - } - - #[inline] - pub fn receive(&self, elapsed: f64) -> Gaussian { - if self.message != N_INF { - self.message.forget(self.player.gamma, elapsed) - } else { - self.player.prior - } - } -} - -#[derive(Clone, Debug)] -pub struct Item { - index: PlayerIndex, +struct Item { + agent: String, likelihood: Gaussian, } -#[derive(Clone, Debug)] -pub struct Team { +struct Team { items: Vec, - output: u16, + output: f64, } -#[derive(Clone, Debug)] -pub struct Event { +struct Event { teams: Vec, - pub evidence: f64, + evidence: f64, + weights: Vec>, } impl Event { - /* - pub fn names(&self) -> Vec<&str> { - self.teams - .iter() - .flat_map(|team| team.items.iter()) - .map(|item| item.name.as_str()) - .collect::>() - } - */ - - pub fn result(&self) -> Vec { + fn outputs(&self) -> Vec { self.teams .iter() .map(|team| team.output) @@ -84,75 +49,126 @@ impl Event { } } -fn compute_elapsed(last_time: f64, actual_time: f64) -> f64 { - if last_time == f64::NEG_INFINITY { - 0.0 - } else if last_time == f64::INFINITY { - 1.0 - } else { - actual_time - last_time - } -} - -#[derive(Clone, Debug)] -pub struct Batch { - pub skills: HashMap, - pub events: Vec, - pub time: f64, +pub(crate) struct Batch { + events: Vec, + pub(crate) skills: HashMap, + pub(crate) time: u64, p_draw: f64, } -/* -fn test(inp: S) where S: AsRef<[I]>, I: AsRef<[u8]> { - for a in inp.as_ref().iter() { - for b in a.as_ref().iter() { - println!("{}", b); - } - } -} -*/ - impl Batch { - pub fn new( - composition: Vec>>, - results: Vec>, - time: f64, - agents: &mut HashMap, + pub(crate) fn new( + composition: Vec>>, + results: Vec>, + weights: Vec>>, + time: u64, p_draw: f64, + agents: &mut HashMap, ) -> Self { + assert!( + results.is_empty() || results.len() == composition.len(), + "TODO: Add a comment here" + ); + assert!( + weights.is_empty() || weights.len() == composition.len(), + "TODO: Add a comment here" + ); + + let this_agent = composition + .iter() + .flatten() + .flatten() + .cloned() + .collect::>(); + + let elapsed = this_agent + .iter() + .map(|&a| (a, compute_elapsed(agents[a].last_time, time))) + .collect::>(); + + let skills = this_agent + .iter() + .map(|&a| { + ( + a.to_string(), + Skill { + forward: agents[a].receive(elapsed[a]), + elapsed: elapsed[a], + ..Default::default() + }, + ) + }) + .collect::>(); + + let events = (0..composition.len()) + .map(|e| { + let teams = (0..composition[e].len()) + .map(|t| { + let items = (0..composition[e][t].len()) + .map(|a| Item { + agent: composition[e][t][a].to_string(), + likelihood: N_INF, + }) + .collect::>(); + + Team { + items, + output: if results.is_empty() { + (composition[e].len() - (t + 1)) as f64 + } else { + results[e][t] + }, + } + }) + .collect::>(); + + Event { + teams, + evidence: 0.0, + weights: if weights.is_empty() { + Vec::new() + } else { + weights[e].clone() + }, + } + }) + .collect::>(); + let mut this = Self { - skills: HashMap::new(), - events: Vec::new(), time, + events, + skills, p_draw, }; - this.add_events(composition, results, agents); + this.iteration(0, agents); this } - pub fn add_events( + pub(crate) fn add_events( &mut self, - composition: Vec>>, - results: Vec>, - agents: &mut HashMap, + composition: Vec>>, + results: Vec>, + weights: Vec>>, + agents: &mut HashMap, ) { let this_agent = composition .iter() .flatten() .flatten() + .cloned() .collect::>(); for a in this_agent { let elapsed = compute_elapsed(agents[a].last_time, self.time); if let Some(skill) = self.skills.get_mut(a) { - skill.forward = agents[a].receive(elapsed); skill.elapsed = elapsed; + skill.forward = agents[a].receive(elapsed); } else { self.skills.insert( - *a, + a.to_string(), Skill { forward: agents[a].receive(elapsed), elapsed, @@ -169,14 +185,18 @@ impl Batch { .map(|t| { let items = (0..composition[e][t].len()) .map(|a| Item { - index: composition[e][t][a], + agent: composition[e][t][a].to_string(), likelihood: N_INF, }) .collect::>(); Team { items, - output: results[e][t], + output: if results.is_empty() { + (composition[e].len() - (t + 1)) as f64 + } else { + results[e][t] + }, } }) .collect::>(); @@ -184,6 +204,11 @@ impl Batch { let event = Event { teams, evidence: 0.0, + weights: if weights.is_empty() { + Vec::new() + } else { + weights[e].clone() + }, }; self.events.push(event); @@ -192,33 +217,45 @@ impl Batch { self.iteration(from, agents); } - #[inline] - pub fn posterior(&self, agent: &PlayerIndex) -> Gaussian { + pub(crate) fn posterior(&self, agent: &str) -> Gaussian { let skill = &self.skills[agent]; skill.likelihood * skill.backward * skill.forward } - #[inline] - pub fn posteriors(&self) -> HashMap { + pub(crate) fn posteriors(&self) -> HashMap { self.skills .keys() - .map(|a| (*a, self.posterior(a))) + .map(|a| (a.to_string(), self.posterior(a))) .collect::>() } - #[inline] - fn within_prior(&self, item: &Item, agents: &mut HashMap) -> Player { - let r = &agents[&item.index].player; - let g = self.posterior(&item.index) / item.likelihood; + fn within_prior( + &self, + item: &Item, + online: bool, + forward: bool, + agents: &mut HashMap, + ) -> Player { + let r = &agents[&item.agent].player; - Player::new(g, r.beta, r.gamma, N_INF) + if online { + Player::new(self.skills[&item.agent].online, r.beta, r.gamma) + } else if forward { + Player::new(self.skills[&item.agent].forward, r.beta, r.gamma) + } else { + let wp = self.posterior(&item.agent) / item.likelihood; + + Player::new(wp, r.beta, r.gamma) + } } - pub fn within_priors( + pub(crate) fn within_priors( &self, event: usize, - agents: &mut HashMap, + online: bool, + forward: bool, + agents: &mut HashMap, ) -> Vec> { self.events[event] .teams @@ -226,23 +263,23 @@ impl Batch { .map(|team| { team.items .iter() - .map(|item| self.within_prior(item, agents)) + .map(|item| self.within_prior(item, online, forward, agents)) .collect::>() }) .collect::>() } - fn iteration(&mut self, from: usize, agents: &mut HashMap) { + pub(crate) fn iteration(&mut self, from: usize, agents: &mut HashMap) { for e in from..self.events.len() { - let teams = self.within_priors(e, agents); - let result = self.events[e].result(); + let teams = self.within_priors(e, false, false, agents); + let result = self.events[e].outputs(); - let g = Game::new(teams, result, self.p_draw); + let g = Game::new(teams, result, self.events[e].weights.clone(), self.p_draw); for (t, team) in self.events[e].teams.iter_mut().enumerate() { for (i, item) in team.items.iter_mut().enumerate() { - self.skills.get_mut(&item.index).unwrap().likelihood = - (self.skills[&item.index].likelihood / item.likelihood) + self.skills.get_mut(&item.agent).unwrap().likelihood = + (self.skills[&item.agent].likelihood / item.likelihood) * g.likelihoods[t][i]; item.likelihood = g.likelihoods[t][i]; @@ -253,27 +290,22 @@ impl Batch { } } - pub fn convergence(&mut self, agents: &mut HashMap) -> usize { + pub(crate) fn convergence(&mut self, agents: &mut HashMap) -> usize { let epsilon = 1e-6; let iterations = 20; let mut step = (f64::INFINITY, f64::INFINITY); let mut i = 0; - while (step.0 > epsilon || step.1 > epsilon) && i < iterations { + while tuple_gt(step, epsilon) && i < iterations { let old = self.posteriors(); self.iteration(0, agents); let new = self.posteriors(); - step = old.iter().fold((0.0, 0.0), |(o_l, o_r), (a, old)| { - let (n_l, n_r) = old.delta(new[a]); - - ( - if n_l > o_l { n_l } else { o_l }, - if n_r > o_r { n_r } else { o_r }, - ) + step = old.iter().fold((0.0, 0.0), |step, (a, old)| { + tuple_max(step, old.delta(new[a])) }); i += 1; @@ -282,18 +314,16 @@ impl Batch { i } - #[inline] - pub fn forward_prior_out(&self, agent: &PlayerIndex) -> Gaussian { + pub(crate) fn forward_prior_out(&self, agent: &str) -> Gaussian { let skill = &self.skills[agent]; skill.forward * skill.likelihood } - #[inline] - pub fn backward_prior_out( + pub(crate) fn backward_prior_out( &self, - agent: &PlayerIndex, - agents: &mut HashMap, + agent: &str, + agents: &mut HashMap, ) -> Gaussian { let skill = &self.skills[agent]; let n = skill.likelihood * skill.backward; @@ -301,8 +331,7 @@ impl Batch { n.forget(agents[agent].player.gamma, skill.elapsed) } - #[inline] - pub fn new_backward_info(&mut self, agents: &mut HashMap) { + pub(crate) fn new_backward_info(&mut self, agents: &mut HashMap) { for (agent, skill) in self.skills.iter_mut() { skill.backward = agents[agent].message; } @@ -310,8 +339,7 @@ impl Batch { self.iteration(0, agents); } - #[inline] - pub fn new_forward_info(&mut self, agents: &mut HashMap) { + pub(crate) fn new_forward_info(&mut self, agents: &mut HashMap) { for (agent, skill) in self.skills.iter_mut() { skill.forward = agents[agent].receive(skill.elapsed); } @@ -320,60 +348,69 @@ impl Batch { } } +fn compute_elapsed(last_time: u64, actual_time: u64) -> u64 { + if last_time == u64::MIN { + 0 + } else if last_time == u64::MAX { + 1 + } else { + actual_time - last_time + } +} #[cfg(test)] mod tests { use approx::assert_ulps_eq; + use crate::{agent::Agent, player::Player}; + use super::*; #[test] fn test_one_event_each() { let mut agents = HashMap::new(); - let a = PlayerIndex::new(0); - let b = PlayerIndex::new(1); - let c = PlayerIndex::new(2); - let d = PlayerIndex::new(3); - let e = PlayerIndex::new(4); - let f = PlayerIndex::new(5); - - for k in [a, b, c, d, e, f] { - let agent = Agent::new( - Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ), - N_INF, - f64::NEG_INFINITY, + for agent in ["a", "b", "c", "d", "e", "f"] { + agents.insert( + agent.to_string(), + Agent { + player: Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0), + ..Default::default() + }, ); - - agents.insert(k, agent); } let mut batch = Batch::new( vec![ - vec![vec![a], vec![b]], - vec![vec![c], vec![d]], - vec![vec![e], vec![f]], + vec![vec!["a"], vec!["b"]], + vec![vec!["c"], vec!["d"]], + vec![vec!["e"], vec!["f"]], ], - vec![vec![1, 0], vec![0, 1], vec![1, 0]], + vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]], + vec![], + 0, 0.0, &mut agents, - 0.0, ); let post = batch.posteriors(); - assert_eq!(post[&a].mu(), 29.205220743876975); - assert_eq!(post[&a].sigma(), 7.194481422570443); + assert_ulps_eq!(post["a"].mu, 29.205220743876975, epsilon = 0.000001); + assert_ulps_eq!(post["a"].sigma, 7.194481422570443, epsilon = 0.000001); - assert_eq!(post[&b].mu(), 20.79477925612302); - assert_eq!(post[&b].sigma(), 7.194481422570443); + assert_ulps_eq!(post["b"].mu, 20.79477925612302, epsilon = 0.000001); + assert_ulps_eq!(post["b"].sigma, 7.194481422570443, epsilon = 0.000001); - assert_eq!(post[&c].mu(), 20.79477925612302); - assert_eq!(post[&c].sigma(), 7.194481422570443); + assert_ulps_eq!(post["c"].mu, 20.79477925612302, epsilon = 0.000001); + assert_ulps_eq!(post["c"].sigma, 7.194481422570443, epsilon = 0.000001); + + assert_ulps_eq!(post["d"].mu, 29.205220743876975, epsilon = 0.000001); + assert_ulps_eq!(post["d"].sigma, 7.194481422570443, epsilon = 0.000001); + + assert_ulps_eq!(post["e"].mu, 29.205220743876975, epsilon = 0.000001); + assert_ulps_eq!(post["e"].sigma, 7.194481422570443, epsilon = 0.000001); + + assert_ulps_eq!(post["f"].mu, 20.79477925612302, epsilon = 0.000001); + assert_ulps_eq!(post["f"].sigma, 7.194481422570443, epsilon = 0.000001); assert_eq!(batch.convergence(&mut agents), 1); } @@ -382,123 +419,102 @@ mod tests { fn test_same_strength() { let mut agents = HashMap::new(); - let a = PlayerIndex::new(0); - let b = PlayerIndex::new(1); - let c = PlayerIndex::new(2); - let d = PlayerIndex::new(3); - let e = PlayerIndex::new(4); - let f = PlayerIndex::new(5); - - for k in [a, b, c, d, e, f] { - let agent = Agent::new( - Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ), - N_INF, - f64::NEG_INFINITY, + for agent in ["a", "b", "c", "d", "e", "f"] { + agents.insert( + agent.to_string(), + Agent { + player: Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0), + ..Default::default() + }, ); - - agents.insert(k, agent); } let mut batch = Batch::new( vec![ - vec![vec![a], vec![b]], - vec![vec![a], vec![c]], - vec![vec![b], vec![c]], + vec![vec!["a"], vec!["b"]], + vec![vec!["a"], vec!["c"]], + vec![vec!["b"], vec!["c"]], ], - vec![vec![1, 0], vec![0, 1], vec![1, 0]], - 2.0, - &mut agents, + vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]], + vec![], + 0, 0.0, + &mut agents, ); let post = batch.posteriors(); - assert_eq!(post[&a].mu(), 24.96097857478182); - assert_eq!(post[&a].sigma(), 6.298544763358269); + assert_ulps_eq!(post["a"].mu, 24.96097857478182, epsilon = 0.000001); + assert_ulps_eq!(post["a"].sigma, 6.298544763358269, epsilon = 0.000001); - assert_eq!(post[&b].mu(), 27.095590669107086); - assert_eq!(post[&b].sigma(), 6.010330439043099); + assert_ulps_eq!(post["b"].mu, 27.095590669107086, epsilon = 0.000001); + assert_ulps_eq!(post["b"].sigma, 6.010330439043099, epsilon = 0.000001); - assert_eq!(post[&c].mu(), 24.88968178743119); - assert_eq!(post[&c].sigma(), 5.866311348102562); + assert_ulps_eq!(post["c"].mu, 24.88968178743119, epsilon = 0.000001); + assert_ulps_eq!(post["c"].sigma, 5.866311348102562, epsilon = 0.000001); assert!(batch.convergence(&mut agents) > 1); let post = batch.posteriors(); - assert_ulps_eq!(post[&a].mu(), 25.000000, epsilon = 0.000001); - assert_ulps_eq!(post[&a].sigma(), 5.4192120, epsilon = 0.000001); + assert_ulps_eq!(post["a"].mu, 25.000000, epsilon = 0.000001); + assert_ulps_eq!(post["a"].sigma, 5.4192120, epsilon = 0.000001); - assert_ulps_eq!(post[&b].mu(), 25.000000, epsilon = 0.000001); - assert_ulps_eq!(post[&b].sigma(), 5.4192120, epsilon = 0.000001); + assert_ulps_eq!(post["b"].mu, 25.000000, epsilon = 0.000001); + assert_ulps_eq!(post["b"].sigma, 5.4192120, epsilon = 0.000001); - assert_ulps_eq!(post[&c].mu(), 25.000000, epsilon = 0.000001); - assert_ulps_eq!(post[&c].sigma(), 5.4192120, epsilon = 0.000001); + assert_ulps_eq!(post["c"].mu, 25.000000, epsilon = 0.000001); + assert_ulps_eq!(post["c"].sigma, 5.4192120, epsilon = 0.000001); } #[test] fn test_add_events() { let mut agents = HashMap::new(); - let a = PlayerIndex::new(0); - let b = PlayerIndex::new(1); - let c = PlayerIndex::new(2); - let d = PlayerIndex::new(3); - let e = PlayerIndex::new(4); - let f = PlayerIndex::new(5); - - for k in [a, b, c, d, e, f] { - let agent = Agent::new( - Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ), - N_INF, - f64::NEG_INFINITY, + for agent in ["a", "b", "c", "d", "e", "f"] { + agents.insert( + agent.to_string(), + Agent { + player: Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0), + ..Default::default() + }, ); - - agents.insert(k, agent); } let mut batch = Batch::new( vec![ - vec![vec![a], vec![b]], - vec![vec![a], vec![c]], - vec![vec![b], vec![c]], + vec![vec!["a"], vec!["b"]], + vec![vec!["a"], vec!["c"]], + vec![vec!["b"], vec!["c"]], ], - vec![vec![1, 0], vec![0, 1], vec![1, 0]], + vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]], + vec![], + 0, 0.0, &mut agents, - 0.0, ); batch.convergence(&mut agents); - let p = batch.posteriors(); + let post = batch.posteriors(); - assert_ulps_eq!(p[&a].mu(), 25.000000, epsilon = 0.000001); - assert_ulps_eq!(p[&a].sigma(), 5.4192120, epsilon = 0.000001); + assert_ulps_eq!(post["a"].mu, 25.000000, epsilon = 0.000001); + assert_ulps_eq!(post["a"].sigma, 5.4192120, epsilon = 0.000001); - assert_ulps_eq!(p[&b].mu(), 25.000000, epsilon = 0.000001); - assert_ulps_eq!(p[&b].sigma(), 5.4192120, epsilon = 0.000001); + assert_ulps_eq!(post["b"].mu, 25.000000, epsilon = 0.000001); + assert_ulps_eq!(post["b"].sigma, 5.4192120, epsilon = 0.000001); - assert_ulps_eq!(p[&c].mu(), 25.000000, epsilon = 0.000001); - assert_ulps_eq!(p[&c].sigma(), 5.4192120, epsilon = 0.000001); + assert_ulps_eq!(post["c"].mu, 25.000000, epsilon = 0.000001); + assert_ulps_eq!(post["c"].sigma, 5.4192120, epsilon = 0.000001); batch.add_events( vec![ - vec![vec![a], vec![b]], - vec![vec![a], vec![c]], - vec![vec![b], vec![c]], + vec![vec!["a"], vec!["b"]], + vec![vec!["a"], vec!["c"]], + vec![vec!["b"], vec!["c"]], ], - vec![vec![1, 0], vec![0, 1], vec![1, 0]], + vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]], + vec![], &mut agents, ); @@ -506,15 +522,15 @@ mod tests { batch.convergence(&mut agents); - let p = batch.posteriors(); + let post = batch.posteriors(); - assert_ulps_eq!(p[&a].mu(), 25.00000315330858, epsilon = 0.000001); - assert_ulps_eq!(p[&a].sigma(), 3.880150268080797, epsilon = 0.000001); + assert_ulps_eq!(post["a"].mu, 25.00000315330858, epsilon = 0.000001); + assert_ulps_eq!(post["a"].sigma, 3.880150268080797, epsilon = 0.000001); - assert_ulps_eq!(p[&b].mu(), 25.00000315330858, epsilon = 0.000001); - assert_ulps_eq!(p[&b].sigma(), 3.880150268080797, epsilon = 0.000001); + assert_ulps_eq!(post["b"].mu, 25.00000315330858, epsilon = 0.000001); + assert_ulps_eq!(post["b"].sigma, 3.880150268080797, epsilon = 0.000001); - assert_ulps_eq!(p[&c].mu(), 25.00000315330858, epsilon = 0.000001); - assert_ulps_eq!(p[&c].sigma(), 3.880150268080797, epsilon = 0.000001); + assert_ulps_eq!(post["c"].mu, 25.00000315330858, epsilon = 0.000001); + assert_ulps_eq!(post["c"].sigma, 3.880150268080797, epsilon = 0.000001); } } diff --git a/src/game.rs b/src/game.rs index 08dc887..58db44c 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,270 +1,222 @@ -use std::collections::HashSet; - -use crate::{message::DiffMessages, utils, variable::TeamVariable, Gaussian, Player, N00}; +use crate::{ + approx, compute_margin, evidence, + gaussian::Gaussian, + message::{DiffMessage, TeamMessage}, + player::Player, + sort_perm, tuple_gt, tuple_max, N00, N_INF, +}; pub struct Game { teams: Vec>, - result: Vec, + result: Vec, + weights: Vec>, p_draw: f64, - pub likelihoods: Vec>, - pub evidence: f64, + pub(crate) likelihoods: Vec>, + pub(crate) evidence: f64, } impl Game { - pub fn new(teams: Vec>, result: Vec, p_draw: f64) -> Self { - if !result.is_empty() { - debug_assert!( - teams.len() == result.len(), - "len(result) and (len(teams) != len(result))" - ); + pub fn new( + teams: Vec>, + mut result: Vec, + mut weights: Vec>, + p_draw: f64, + ) -> Self { + assert!( + (result.is_empty() || result.len() == teams.len()), + "result.must be empty or the same length as teams" + ); + + assert!( + (weights.is_empty() || weights.len() == teams.len()), + "weights.must be empty or the same length as teams" + ); + + assert!( + weights.is_empty() + || weights + .iter() + .zip(teams.iter()) + .all(|(w, t)| w.len() == t.len()), + "weights.must be empty or has the same dimensions as teams" + ); + + assert!( + (0.0..1.0).contains(&p_draw), + "draw probability.must be >= 0.0 and < 1.0" + ); + + assert!( + p_draw > 0.0 || { + let mut r = result.clone(); + r.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap()); + r.windows(2).all(|w| w[0] != w[1]) + }, + "draw.must be > 0.0 if there is teams with draw" + ); + + if result.is_empty() { + result = (0..teams.len()).rev().map(|i| i as f64).collect::>(); } - debug_assert!(p_draw >= 0.0 && p_draw < 1.0, "0.0 <= p_draw < 1.0"); - - if p_draw == 0.0 { - debug_assert!( - result.iter().collect::>().len() == result.len(), - "(p_draw == 0.0) and (len(result) > 0) and (len(set(result)) != len(result))" - ); + if weights.is_empty() { + weights = teams + .iter() + .map(|team| vec![1.0; team.len()]) + .collect::>(); } let mut this = Self { teams, result, + weights, p_draw, likelihoods: Vec::new(), evidence: 0.0, }; - this.compute_likelihoods(); + this.likelihoods(); this } - fn performance(&self, index: usize) -> Gaussian { - self.teams[index] + fn likelihoods(&mut self) -> &Vec> { + let m_t_ft = self.likelihood_teams(); + + self.likelihoods = self + .teams .iter() - .fold(N00, |sum, p| sum + p.performance()) + .zip(self.weights.iter()) + .zip(m_t_ft) + .map(|((p, w), m)| { + let performance = p.iter().zip(w.iter()).fold(N00, |p, (player, &weight)| { + p + (player.performance() * weight) + }); + + p.iter() + .zip(w.iter()) + .map(|(p, &w)| { + ((m - performance.exclude(p.performance() * w)) * (1.0 / w)) + .forget(p.beta, 1) + }) + .collect::>() + }) + .collect::>(); + + &self.likelihoods } - fn partial_evidence(&mut self, d: &[DiffMessages], margin: &[f64], tie: &[bool], e: usize) { - let mu = d[e].prior.mu(); - let sigma = d[e].prior.sigma(); + fn likelihood_teams(&mut self) -> Vec { + let o = sort_perm(&self.result, true); - if tie[e] { - self.evidence *= utils::cdf(margin[e], mu, sigma) - utils::cdf(-margin[e], mu, sigma) - } else { - self.evidence *= 1.0 - utils::cdf(margin[e], mu, sigma); - } - } - - fn graphical_model( - &mut self, - ) -> ( - Vec, - Vec, - Vec, - Vec, - Vec, - ) { - if self.result.is_empty() { - self.result = (0..self.teams.len() as u16).rev().collect::>(); - } - - let r = &self.result; - let o = utils::sort_perm(r); - - let t = (0..self.teams.len()) - .map(|e| TeamVariable { - prior: self.teams[o[e]] + let mut t = o + .iter() + .map(|&e| { + let performance = self.teams[e] .iter() - .fold(N00, |sum, p| sum + p.performance()), - ..Default::default() - }) - .collect::>(); + .zip(self.weights[e].iter()) + .fold(N00, |p, (player, &weight)| { + p + (player.performance() * weight) + }); - let d = t - .windows(2) - .map(|window| DiffMessages { - prior: window[0].prior - window[1].prior, - ..Default::default() - }) - .collect::>(); - - let tie = (0..d.len()) - .map(|e| r[o[e]] == r[o[e + 1]]) - .collect::>(); - - let margin = (0..d.len()) - .map(|e| { - if self.p_draw == 0.0 { - 0.0 - } else { - let a: f64 = self.teams[o[e]].iter().map(|a| a.beta.powi(2)).sum(); - let b: f64 = self.teams[o[e + 1]].iter().map(|a| a.beta.powi(2)).sum(); - - utils::compute_margin(self.p_draw, (a + b).sqrt()) + TeamMessage { + prior: performance, + likelihood_lose: N_INF, + likelihood_win: N_INF, + likelihood_draw: N_INF, } }) .collect::>(); - self.evidence = 1.0; + let mut d = t + .windows(2) + .map(|w| DiffMessage { + prior: w[0].prior - w[1].prior, + likelihood: N_INF, + }) + .collect::>(); - (o, t, d, tie, margin) - } + let tie = o + .windows(2) + .map(|e| self.result[e[0]] == self.result[e[1]]) + .collect::>(); - fn likelihood_analytical(&mut self) -> Vec> { - let (o, t, d, tie, margin) = self.graphical_model(); - - self.partial_evidence(&d, &margin, &tie, 0); - - let d = d[0].prior; - let (mu_trunc, sigma_trunc) = utils::trunc(d.mu(), d.sigma(), margin[0], tie[0]); - - let (delta_div, theta_div_pow2) = if d.sigma() == sigma_trunc { - ( - d.sigma().powi(2) * mu_trunc - sigma_trunc.powi(2) * d.mu(), - f64::INFINITY, - ) + let margin = if self.p_draw == 0.0 { + vec![0.0; o.len() - 1] } else { - ( - (d.sigma().powi(2) * mu_trunc - sigma_trunc.powi(2) * d.mu()) - / (d.sigma().powi(2) - sigma_trunc.powi(2)), - (sigma_trunc.powi(2) * d.sigma().powi(2)) - / (d.sigma().powi(2) - sigma_trunc.powi(2)), - ) + o.windows(2) + .map(|w| { + if self.p_draw == 0.0 { + 0.0 + } else { + let a: f64 = self.teams[w[0]].iter().map(|a| a.beta.powi(2)).sum(); + let b: f64 = self.teams[w[1]].iter().map(|a| a.beta.powi(2)).sum(); + + compute_margin(self.p_draw, (a + b).sqrt()) + } + }) + .collect::>() }; - let mut res = Vec::new(); - - for i in 0..t.len() { - let mut team = Vec::new(); - - for j in 0..self.teams[o[i]].len() { - // - let mu = if d.sigma() == sigma_trunc { - 0.0 - } else { - self.teams[o[i]][j].prior.mu() - + (delta_div - d.mu()) * (-1.0f64).powi(if i == 1 { 1 } else { 0 }) - }; - - let sigma_analitico = (theta_div_pow2 + d.sigma().powi(2) - - self.teams[o[i]][j].prior.sigma().powi(2)) - .sqrt(); - - team.push(Gaussian::new(mu, sigma_analitico)); - } - - res.push(team); - } - - if o[0] >= o[1] { - res.swap(0, 1); - } - - res - } - - fn likelihood_teams(&mut self) -> Vec { - let (o, mut t, mut d, tie, margin) = self.graphical_model(); - let mut step = (f64::INFINITY, f64::INFINITY); - let mut i = 0; + let mut iter = 0; - while ((step.0 > 1e-6) || (step.1 > 1e-6)) && i < 10 { + while tuple_gt(step, 1e-6) && iter < 10 { step = (0.0, 0.0); for e in 0..d.len() - 1 { d[e].prior = t[e].posterior_win() - t[e + 1].posterior_lose(); - if i == 0 { - let mu = d[e].prior.mu(); - let sigma = d[e].prior.sigma(); - - if tie[e] { - self.evidence *= - utils::cdf(margin[e], mu, sigma) - utils::cdf(-margin[e], mu, sigma) - } else { - self.evidence *= 1.0 - utils::cdf(margin[e], mu, sigma); - } + if iter == 0 { + self.evidence *= evidence(&d, &margin, &tie, e); } - d[e].likelihood = utils::approx(d[e].prior, margin[e], tie[e]) / d[e].prior; - + d[e].likelihood = approx(d[e].prior, margin[e], tie[e]) / d[e].prior; let likelihood_lose = t[e].posterior_win() - d[e].likelihood; - let delta = t[e + 1].likelihood_lose.delta(likelihood_lose); - - step = ( - if step.0 > delta.0 { step.0 } else { delta.0 }, - if step.1 > delta.1 { step.1 } else { delta.1 }, - ); - + step = tuple_max(step, t[e + 1].likelihood_lose.delta(likelihood_lose)); t[e + 1].likelihood_lose = likelihood_lose; } for e in (1..d.len()).rev() { d[e].prior = t[e].posterior_win() - t[e + 1].posterior_lose(); - if i == 0 && e == d.len() - 1 { - self.partial_evidence(&d, &margin, &tie, e); + if iter == 0 && e == d.len() - 1 { + self.evidence *= evidence(&d, &margin, &tie, e); } - d[e].likelihood = utils::approx(d[e].prior, margin[e], tie[e]) / d[e].prior; - + d[e].likelihood = approx(d[e].prior, margin[e], tie[e]) / d[e].prior; let likelihood_win = t[e + 1].posterior_lose() + d[e].likelihood; - let delta = t[e].likelihood_win.delta(likelihood_win); - - step = ( - if step.0 > delta.0 { step.0 } else { delta.0 }, - if step.1 > delta.1 { step.1 } else { delta.1 }, - ); - + step = tuple_max(step, t[e].likelihood_win.delta(likelihood_win)); t[e].likelihood_win = likelihood_win; } - i += 1; + iter += 1; } if d.len() == 1 { - self.partial_evidence(&d, &margin, &tie, 0); + self.evidence *= evidence(&d, &margin, &tie, 0); d[0].prior = t[0].posterior_win() - t[1].posterior_lose(); - d[0].likelihood = utils::approx(d[0].prior, margin[0], tie[0]) / d[0].prior; + d[0].likelihood = approx(d[0].prior, margin[0], tie[0]) / d[0].prior; } - let t_e = t.len(); - let d_e = d.len(); + let t_end = t.len() - 1; + let d_end = d.len() - 1; t[0].likelihood_win = t[1].posterior_lose() + d[0].likelihood; - t[t_e - 1].likelihood_lose = t[t_e - 2].posterior_win() - d[d_e - 1].likelihood; + t[t_end].likelihood_lose = t[t_end - 1].posterior_win() - d[d_end].likelihood; - (0..t.len()) - .map(|e| t[o[e]].likelihood()) - .collect::>() - } - - fn compute_likelihoods(&mut self) { - if self.teams.len() > 2 { - let m_t_ft = self.likelihood_teams(); - - self.likelihoods = (0..self.teams.len()) - .map(|e| { - (0..self.teams[e].len()) - .map(|i| m_t_ft[e] - self.performance(e).exclude(self.teams[e][i].prior)) - .collect::>() - }) - .collect::>(); - } else { - self.likelihoods = self.likelihood_analytical(); - } + o.iter().map(|&e| t[e].likelihood()).collect::>() } pub fn posteriors(&self) -> Vec> { - (0..self.teams.len()) - .map(|e| { - (0..self.teams[e].len()) - .map(|i| self.likelihoods[e][i] * self.teams[e][i].prior) + self.likelihoods + .iter() + .zip(self.teams.iter()) + .map(|(l, t)| { + l.iter() + .zip(t.iter()) + .map(|(&l, p)| l * p.prior) .collect::>() }) .collect::>() @@ -273,256 +225,183 @@ impl Game { #[cfg(test)] mod tests { - use crate::{Gaussian, Player, GAMMA, N_INF}; + use crate::{Gaussian, Player, GAMMA}; use super::*; #[test] fn test_1vs1() { - let t_a = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); + let t_a = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); + let t_b = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); - let t_b = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - - let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0, 1], 0.0); + let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0.0, 1.0], vec![], 0.0); let p = g.posteriors(); let a = p[0][0]; let b = p[1][0]; - assert_eq!(a.mu(), 20.79477925612302); - assert_eq!(b.mu(), 29.205220743876975); - assert_eq!(a.sigma(), 7.194481422570443); + assert_eq!(a.mu, 20.79477925612302); + assert_eq!(b.mu, 29.205220743876975); + assert_eq!(a.sigma, 7.194481422570443); - let t_a = Player::new(Gaussian::new(29.0, 1.0), 25.0 / 6.0, GAMMA, N_INF); - let t_b = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, GAMMA, N_INF); + let t_a = Player::new(Gaussian::new(29.0, 1.0), 25.0 / 6.0, GAMMA); + let t_b = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, GAMMA); - let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0, 1], 0.0); + let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0.0, 1.0], vec![], 0.0); let p = g.posteriors(); let a = p[0][0]; let b = p[1][0]; - assert_eq!(a.mu(), 28.896475351225412); - assert_eq!(a.sigma(), 0.9966043313004235); - assert_eq!(b.mu(), 32.18921172045737); - assert_eq!(b.sigma(), 6.062063735879715); + assert_eq!(a.mu, 28.896475351225412); + assert_eq!(a.sigma, 0.9966043313004235); + assert_eq!(b.mu, 32.18921172045737); + assert_eq!(b.sigma, 6.062063735879715); - let t_a = Player::new(Gaussian::new(1.139, 0.531), 1.0, 0.2125, N_INF); - let t_b = Player::new(Gaussian::new(15.568, 0.51), 1.0, 0.2125, N_INF); + let t_a = Player::new(Gaussian::new(1.139, 0.531), 1.0, 0.2125); + let t_b = Player::new(Gaussian::new(15.568, 0.51), 1.0, 0.2125); - let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0, 1], 0.0); + let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0.0, 1.0], vec![], 0.0); - assert_eq!(g.likelihoods[0][0].sigma(), f64::INFINITY); - assert_eq!(g.likelihoods[1][0].sigma(), f64::INFINITY); - assert_eq!(g.likelihoods[0][0].mu(), 0.0); + assert_eq!(g.likelihoods[0][0].sigma, f64::INFINITY); + assert_eq!(g.likelihoods[1][0].sigma, f64::INFINITY); + assert_eq!(g.likelihoods[0][0].mu, 0.0); } #[test] fn test_1vs1vs1() { - let t_a = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_b = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_c = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); + let teams = vec![ + vec![Player::new( + Gaussian::new(25.0, 25.0 / 3.0), + 25.0 / 6.0, + 25.0 / 300.0, + )], + vec![Player::new( + Gaussian::new(25.0, 25.0 / 3.0), + 25.0 / 6.0, + 25.0 / 300.0, + )], + vec![Player::new( + Gaussian::new(25.0, 25.0 / 3.0), + 25.0 / 6.0, + 25.0 / 300.0, + )], + ]; - let g = Game::new(vec![vec![t_a], vec![t_b], vec![t_c]], vec![1, 2, 0], 0.0); + let g = Game::new(teams.clone(), vec![1.0, 2.0, 0.0], vec![], 0.0); + let p = g.posteriors(); + + let a = p[0][0]; + let b = p[1][0]; + + assert_eq!(a.mu, 25.00000000000592); + assert_eq!(a.sigma, 6.238469796269066); + assert_eq!(b.mu, 31.31135822129149); + assert_eq!(b.sigma, 6.69881865477675); + + let g = Game::new(teams.clone(), vec![], vec![], 0.0); + let p = g.posteriors(); + + let a = p[0][0]; + let b = p[1][0]; + + assert_eq!(a.mu, 31.31135822129149); + assert_eq!(a.sigma, 6.69881865477675); + assert_eq!(b.mu, 25.00000000000592); + assert_eq!(b.sigma, 6.238469796269066); + + let g = Game::new(teams, vec![1.0, 2.0, 0.0], vec![], 0.5); let p = g.posteriors(); let a = p[0][0]; let b = p[1][0]; let c = p[2][0]; - assert_eq!(a.mu(), 25.00000000000592); - assert_eq!(a.sigma(), 6.238469796269066); - assert_eq!(b.mu(), 31.31135822129149); - assert_eq!(b.sigma(), 6.69881865477675); - assert_eq!(c.mu(), 18.688641778702593); - assert_eq!(c.sigma(), 6.698818654778007); - - let t_a = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_b = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_c = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - - let g = Game::new(vec![vec![t_a], vec![t_b], vec![t_c]], vec![2, 1, 0], 0.0); - let p = g.posteriors(); - - let a = p[0][0]; - let b = p[1][0]; - let c = p[2][0]; - - assert_eq!(a.mu(), 31.31135822129149); - assert_eq!(a.sigma(), 6.69881865477675); - assert_eq!(b.mu(), 25.00000000000592); - assert_eq!(b.sigma(), 6.238469796269066); - assert_eq!(c.mu(), 18.688641778702593); - assert_eq!(c.sigma(), 6.698818654778007); - - let t_a = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_b = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_c = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - - let g = Game::new(vec![vec![t_a], vec![t_b], vec![t_c]], vec![1, 2, 0], 0.5); - let p = g.posteriors(); - - let a = p[0][0]; - let b = p[1][0]; - let c = p[2][0]; - - assert_eq!(a.mu(), 24.999999999511545); - assert_eq!(a.sigma(), 6.092561128305945); - assert_eq!(b.mu(), 33.37931495595287); - assert_eq!(b.sigma(), 6.483575782278924); - assert_eq!(c.mu(), 16.62068504453558); - assert_eq!(c.sigma(), 6.483575782198122); + assert_eq!(a.mu, 24.999999999511545); + assert_eq!(a.sigma, 6.092561128305945); + assert_eq!(b.mu, 33.37931495595287); + assert_eq!(b.sigma, 6.483575782278924); + assert_eq!(c.mu, 16.62068504453558); + assert_eq!(c.sigma, 6.483575782198122); } #[test] fn test_1vs1_draw() { - let t_a = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); + let t_a = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); + let t_b = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); - let t_b = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - - let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0, 0], 0.25); + let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0.0, 0.0], vec![], 0.25); let p = g.posteriors(); let a = p[0][0]; let b = p[1][0]; - assert_eq!(a.mu(), 25.0); - assert_eq!(a.sigma(), 6.469480769842277); - assert_eq!(b.mu(), 25.0); - assert_eq!(b.sigma(), 6.469480769842277); + assert_eq!(a.mu, 24.999999999999996); + assert_eq!(a.sigma, 6.469480769842277); + assert_eq!(b.mu, 24.999999999999996); + assert_eq!(b.sigma, 6.469480769842277); - let t_a = Player::new(Gaussian::new(25.0, 3.0), 25.0 / 6.0, 25.0 / 300.0, N_INF); + let t_a = Player::new(Gaussian::new(25.0, 3.0), 25.0 / 6.0, 25.0 / 300.0); + let t_b = Player::new(Gaussian::new(29.0, 2.0), 25.0 / 6.0, 25.0 / 300.0); - let t_b = Player::new(Gaussian::new(29.0, 2.0), 25.0 / 6.0, 25.0 / 300.0, N_INF); - - let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0, 0], 0.25); + let g = Game::new(vec![vec![t_a], vec![t_b]], vec![0.0, 0.0], vec![], 0.25); let p = g.posteriors(); let a = p[0][0]; let b = p[1][0]; - assert_eq!(a.mu(), 25.736001810566616); - assert_eq!(a.sigma(), 2.709956162204711); - assert_eq!(b.mu(), 28.67288808419261); - assert_eq!(b.sigma(), 1.9164711604544398); + assert_eq!(a.mu, 25.73600181056662); + assert_eq!(a.sigma, 2.709956162204711); + assert_eq!(b.mu, 28.67288808419261); + assert_eq!(b.sigma, 1.9164711604544398); } #[test] fn test_1vs1vs1_draw() { - let t_a = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_b = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); - let t_c = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); + let t_a = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); + let t_b = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); + let t_c = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); - let g = Game::new(vec![vec![t_a], vec![t_b], vec![t_c]], vec![0, 0, 0], 0.25); + let g = Game::new( + vec![vec![t_a], vec![t_b], vec![t_c]], + vec![0.0, 0.0, 0.0], + vec![], + 0.25, + ); let p = g.posteriors(); let a = p[0][0]; let b = p[1][0]; let c = p[2][0]; - assert_eq!(a.mu(), 24.999999999999996); - assert_eq!(a.sigma(), 5.729068664890827); - assert_eq!(b.mu(), 25.000000000000004); - assert_eq!(b.sigma(), 5.707423522433266); - assert_eq!(c.mu(), 24.999999999999996); - assert_eq!(c.sigma(), 5.729068664890825); + assert_eq!(a.mu, 24.999999999999996); + assert_eq!(a.sigma, 5.729068664890827); + assert_eq!(b.mu, 25.000000000000004); + assert_eq!(b.sigma, 5.707423522433266); + assert_eq!(c.mu, 24.999999999999996); + assert_eq!(c.sigma, 5.729068664890825); - let t_a = Player::new(Gaussian::new(25.0, 3.0), 25.0 / 6.0, 25.0 / 300.0, N_INF); - let t_b = Player::new(Gaussian::new(25.0, 3.0), 25.0 / 6.0, 25.0 / 300.0, N_INF); - let t_c = Player::new(Gaussian::new(29.0, 2.0), 25.0 / 6.0, 25.0 / 300.0, N_INF); + let t_a = Player::new(Gaussian::new(25.0, 3.0), 25.0 / 6.0, 25.0 / 300.0); + let t_b = Player::new(Gaussian::new(25.0, 3.0), 25.0 / 6.0, 25.0 / 300.0); + let t_c = Player::new(Gaussian::new(29.0, 2.0), 25.0 / 6.0, 25.0 / 300.0); - let g = Game::new(vec![vec![t_a], vec![t_b], vec![t_c]], vec![0, 0, 0], 0.25); + let g = Game::new( + vec![vec![t_a], vec![t_b], vec![t_c]], + vec![0.0, 0.0, 0.0], + vec![], + 0.25, + ); let p = g.posteriors(); let a = p[0][0]; let b = p[1][0]; let c = p[2][0]; - assert_eq!(a.mu(), 25.48850755025261); - assert_eq!(a.sigma(), 2.638208444298423); - assert_eq!(b.mu(), 25.51067170990121); - assert_eq!(b.sigma(), 2.6287517663583633); - assert_eq!(c.mu(), 28.555920328820523); - assert_eq!(c.sigma(), 1.8856891308577184); + assert_eq!(a.mu, 25.48850755025261); + assert_eq!(a.sigma, 2.6382084442984226); + assert_eq!(b.mu, 25.510671709901217); + assert_eq!(b.sigma, 2.6287517663583633); + assert_eq!(c.mu, 28.555920328820527); + assert_eq!(c.sigma, 1.8856891308577184); } } diff --git a/src/gaussian.rs b/src/gaussian.rs index d9e00b8..f03182f 100644 --- a/src/gaussian.rs +++ b/src/gaussian.rs @@ -1,62 +1,58 @@ use std::ops; -use crate::{utils, MU, SIGMA}; +use crate::{MU, N_INF, SIGMA}; #[derive(Clone, Copy, PartialEq, Debug)] pub struct Gaussian { - mu: f64, - sigma: f64, + pub(crate) mu: f64, + pub(crate) sigma: f64, } impl Gaussian { - #[inline] - pub const fn new(mu: f64, sigma: f64) -> Self { + pub fn new(mu: f64, sigma: f64) -> Self { + debug_assert!(sigma >= 0.0, "sigma must be equal or larger than 0.0"); + Gaussian { mu, sigma } } - #[inline] - pub fn mu(&self) -> f64 { - self.mu + fn pi(&self) -> f64 { + if self.sigma > 0.0 { + self.sigma.powi(-2) + } else { + f64::INFINITY + } } - #[inline] - pub fn sigma(&self) -> f64 { - self.sigma + fn tau(&self) -> f64 { + if self.sigma > 0.0 { + self.mu * self.pi() + } else { + f64::INFINITY + } } - #[inline] - pub fn tau(&self) -> f64 { - self.mu * self.pi() + pub(crate) fn delta(&self, m: Gaussian) -> (f64, f64) { + ((self.mu - m.mu).abs(), (self.sigma - m.sigma).abs()) } - #[inline] - pub fn pi(&self) -> f64 { - self.sigma.powi(-2) + pub(crate) fn exclude(&self, m: Gaussian) -> Self { + Self { + mu: self.mu - m.mu, + sigma: (self.sigma.powi(2) - m.sigma.powi(2)).sqrt(), + } } - #[inline] - pub fn forget(&self, gamma: f64, t: f64) -> Self { - Self::new(self.mu, (self.sigma().powi(2) + t * gamma.powi(2)).sqrt()) - } - - #[inline] - pub fn delta(&self, m: Gaussian) -> (f64, f64) { - ((self.mu() - m.mu()).abs(), (self.sigma() - m.sigma()).abs()) - } - - #[inline] - pub fn exclude(&self, m: Gaussian) -> Self { - Self::new( - self.mu() - m.mu(), - (self.sigma().powi(2) - m.sigma().powi(2)).sqrt(), - ) + pub(crate) fn forget(&self, gamma: f64, t: u64) -> Self { + Self { + mu: self.mu, + sigma: (self.sigma.powi(2) + t as f64 * gamma.powi(2)).sqrt(), + } } } impl Default for Gaussian { - #[inline] fn default() -> Self { - Gaussian { + Self { mu: MU, sigma: SIGMA, } @@ -66,7 +62,6 @@ impl Default for Gaussian { impl ops::Add for Gaussian { type Output = Gaussian; - #[inline] fn add(self, rhs: Gaussian) -> Self::Output { Gaussian { mu: self.mu + rhs.mu, @@ -78,7 +73,6 @@ impl ops::Add for Gaussian { impl ops::Sub for Gaussian { type Output = Gaussian; - #[inline] fn sub(self, rhs: Gaussian) -> Self::Output { Gaussian { mu: self.mu - rhs.mu, @@ -90,25 +84,66 @@ impl ops::Sub for Gaussian { impl ops::Mul for Gaussian { type Output = Gaussian; - #[inline] fn mul(self, rhs: Gaussian) -> Self::Output { - let (mu, sigma) = utils::mu_sigma(self.tau() + rhs.tau(), self.pi() + rhs.pi()); + let (mu, sigma) = if self.sigma == 0.0 || rhs.sigma == 0.0 { + let mu = self.mu / (self.sigma.powi(2) / rhs.sigma.powi(2) + 1.0) + + rhs.mu / (rhs.sigma.powi(2) / self.sigma.powi(2) + 1.0); + + let sigma = (1.0 / ((1.0 / self.sigma.powi(2)) + (1.0 / rhs.sigma.powi(2)))).sqrt(); + + (mu, sigma) + } else { + mu_sigma(self.tau() + rhs.tau(), self.pi() + rhs.pi()) + }; Gaussian { mu, sigma } } } +impl ops::Mul for Gaussian { + type Output = Gaussian; + + fn mul(self, rhs: f64) -> Self::Output { + if rhs.is_finite() { + Self { + mu: self.mu * rhs, + sigma: self.sigma * rhs, + } + } else { + N_INF + } + } +} + impl ops::Div for Gaussian { type Output = Gaussian; - #[inline] fn div(self, rhs: Gaussian) -> Self::Output { - let (mu, sigma) = utils::mu_sigma(self.tau() - rhs.tau(), self.pi() - rhs.pi()); + let (mu, sigma) = if self.sigma == 0.0 || rhs.sigma == 0.0 { + let mu = self.mu / (1.0 - self.sigma.powi(2) / rhs.sigma.powi(2)) + + rhs.mu / (rhs.sigma.powi(2) / self.sigma.powi(2) - 1.0); + + let sigma = (1.0 / ((1.0 / self.sigma.powi(2)) - (1.0 / rhs.sigma.powi(2)))).sqrt(); + + (mu, sigma) + } else { + mu_sigma(self.tau() - rhs.tau(), self.pi() - rhs.pi()) + }; Gaussian { mu, sigma } } } +fn mu_sigma(tau: f64, pi: f64) -> (f64, f64) { + if pi > 0.0 { + (tau / pi, (1.0 / pi).sqrt()) + } else if (pi + 1e-5) < 0.0 { + panic!("precision should be greater than 0"); + } else { + (0.0, f64::INFINITY) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/history.rs b/src/history.rs index 632b420..5d814c9 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,54 +1,76 @@ use std::collections::{HashMap, HashSet}; -use crate::{utils, Agent, Batch, Gaussian, Player, PlayerIndex, N_INF}; +use crate::{ + agent::{self, Agent}, + batch::Batch, + gaussian::Gaussian, + player::Player, + sort_time, tuple_gt, tuple_max, +}; pub struct History { size: usize, batches: Vec, - agents: HashMap, - // mu: f64, - // sigma: f64, - // beta: f64, - // gamma: f64, - p_draw: f64, + agents: HashMap, time: bool, - pub epsilon: f64, - pub iterations: usize, - pub verbose: bool, + mu: f64, + sigma: f64, + beta: f64, + gamma: f64, + p_draw: f64, + online: bool, + weights: Vec>>, + epsilon: f64, + iterations: usize, } impl History { pub fn new( - composition: &[Vec>], - results: &[Vec], - times: &[f64], - priors: HashMap, + composition: Vec>>, + results: Vec>, + times: Vec, + weights: Vec>>, + priors: HashMap, mu: f64, sigma: f64, beta: f64, gamma: f64, p_draw: f64, + online: bool, ) -> Self { + assert!( + results.is_empty() || results.len() == composition.len(), + "TODO: Add a comment here" + ); + assert!( + times.is_empty() || times.len() == composition.len(), + "TODO: Add a comment here" + ); + assert!( + weights.is_empty() || weights.len() == composition.len(), + "TODO: Add a comment here" + ); + let this_agent = composition .iter() .flatten() .flatten() + .cloned() .collect::>(); let agents = this_agent .into_iter() - .map(|a| { + .map(|agent| { let player = priors - .get(a) + .get(agent) .cloned() - .unwrap_or_else(|| Player::new(Gaussian::new(mu, sigma), beta, gamma, N_INF)); + .unwrap_or_else(|| Player::new(Gaussian::new(mu, sigma), beta, gamma)); ( - *a, + agent.to_string(), Agent { player, - message: N_INF, - last_time: f64::NEG_INFINITY, + ..Default::default() }, ) }) @@ -58,41 +80,45 @@ impl History { size: composition.len(), batches: Vec::new(), agents, - p_draw, time: !times.is_empty(), - epsilon: 1e-6, - iterations: 30, - verbose: true, + mu, + sigma, + beta, + gamma, + p_draw, + online, + weights: weights.clone(), + epsilon: 0.0, + iterations: 10, }; - this.trueskill(composition, results, times); + this.trueskill(composition, results, times, weights, online); this } fn trueskill( &mut self, - composition: &[Vec>], - results: &[Vec], - times: &[f64], + composition: Vec>>, + results: Vec>, + times: Vec, + weights: Vec>>, + online: bool, ) { let o = if self.time { - utils::sort_time(times) + sort_time(×, false) } else { (0..composition.len()).collect::>() }; let mut i = 0; + let mut last = 0.0; while i < self.size { let mut j = i + 1; - let time = if self.time { - times[o[i]] - } else { - i as f64 + 1.0 - }; + let t = if self.time { times[o[i]] } else { i as u64 + 1 }; - while self.time && j < self.size && times[o[j]] == time { + while self.time && j < self.size && times[o[j]] == t { j += 1; } @@ -100,30 +126,67 @@ impl History { .map(|e| composition[o[e]].clone()) .collect::>(); - let results = (i..j).map(|e| results[o[e]].clone()).collect::>(); + let results = if results.is_empty() { + Vec::new() + } else { + (i..j).map(|e| results[o[e]].clone()).collect::>() + }; - let b = Batch::new(composition, results, time, &mut self.agents, self.p_draw); + let weights = if weights.is_empty() { + Vec::new() + } else { + (i..j).map(|e| weights[o[e]].clone()).collect::>() + }; + + let b = Batch::new( + composition, + results, + weights, + t, + self.p_draw, + &mut self.agents, + ); self.batches.push(b); let idx = self.batches.len() - 1; + + if online { + let new = 100.0 * (i as f64 / self.size as f64); + + if new != last { + println!("{:.02}%", new); + last = new; + } + + for skill in self.batches[idx].skills.values_mut() { + skill.online = skill.forward; + } + + self.convergence(self.iterations, self.epsilon, false); + } + let b = &mut self.batches[idx]; for a in b.skills.keys() { let agent = self.agents.get_mut(a).unwrap(); - agent.last_time = if self.time { time } else { f64::INFINITY }; + agent.last_time = if self.time { t } else { u64::MAX }; agent.message = b.forward_prior_out(a); } i = j; } + + if online { + println!("100.00%"); + } } fn iteration(&mut self) -> (f64, f64) { let mut step = (0.0, 0.0); - clean(self.agents.values_mut(), false); + agent::clean(self.agents.values_mut(), false); for j in (0..self.batches.len() - 1).rev() { for agent in self.batches[j + 1].skills.keys() { @@ -142,7 +205,7 @@ impl History { .fold(step, |step, (a, old)| tuple_max(step, old.delta(new[a]))); } - clean(self.agents.values_mut(), false); + agent::clean(self.agents.values_mut(), false); for j in 1..self.batches.len() { for agent in self.batches[j - 1].skills.keys() { @@ -164,7 +227,7 @@ impl History { if self.batches.len() == 1 { let old = self.batches[0].posteriors(); - self.batches[0].convergence(&mut self.agents); + self.batches[0].iteration(0, &mut self.agents); let new = self.batches[0].posteriors(); @@ -176,12 +239,17 @@ impl History { step } - pub fn convergence(&mut self) -> ((f64, f64), usize) { + pub fn convergence( + &mut self, + iterations: usize, + epsilon: f64, + verbose: bool, + ) -> ((f64, f64), usize) { let mut step = (f64::INFINITY, f64::INFINITY); let mut i = 0; - while (step.0 > self.epsilon || step.1 > self.epsilon) && i < self.iterations { - if self.verbose { + while tuple_gt(step, epsilon) && i < iterations { + if verbose { print!("Iteration = {}", i); } @@ -189,20 +257,20 @@ impl History { i += 1; - if self.verbose { + if verbose { println!(", step = {:?}", step); } } - if self.verbose { + if verbose { println!("End"); } (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.batches { for agent in b.skills.keys() { @@ -211,266 +279,234 @@ impl History { if let Some(entry) = data.get_mut(agent) { entry.push(point); } else { - data.insert(*agent, vec![point]); + data.insert(agent.to_string(), vec![point]); } } } data } - - pub fn log_evidence(&self) -> f64 { - self.batches - .iter() - .flat_map(|batch| batch.events.iter()) - .map(|event| event.evidence.ln()) - .sum() - } -} - -fn clean<'a, A: Iterator>(agents: A, last_time: bool) { - for a in agents { - a.message = N_INF; - - if last_time { - a.last_time = f64::NEG_INFINITY; - } - } -} - -fn tuple_max(a: (f64, f64), b: (f64, f64)) -> (f64, f64) { - ( - if a.0 > b.0 { a.0 } else { b.0 }, - if a.1 > b.1 { a.1 } else { b.1 }, - ) } #[cfg(test)] mod tests { use approx::assert_ulps_eq; - use crate::{Game, BETA, GAMMA, MU, P_DRAW, SIGMA}; + use crate::{Game, Player, BETA, EPSILON, GAMMA, ITERATIONS, MU, P_DRAW, SIGMA}; use super::*; #[test] fn test_init() { - let a = PlayerIndex::new(0); - let b = PlayerIndex::new(1); - let c = PlayerIndex::new(2); - let composition = vec![ - vec![vec![a], vec![b]], - vec![vec![a], vec![c]], - vec![vec![b], vec![c]], + vec![vec!["a"], vec!["b"]], + vec![vec!["a"], vec!["c"]], + vec![vec!["b"], vec!["c"]], ]; - let results = vec![vec![1, 0], vec![0, 1], vec![1, 0]]; + let results = vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]]; let mut priors = HashMap::new(); - for k in [a, b, c] { - let player = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 0.15 * 25.0 / 3.0, - N_INF, + for agent in ["a", "b", "c"] { + priors.insert( + agent.to_string(), + Player::new( + Gaussian::new(25.0, 25.0 / 3.0), + 25.0 / 6.0, + 0.15 * 25.0 / 3.0, + ), ); - - priors.insert(k, player); } let mut h = History::new( - &composition, - &results, - &[1.0, 2.0, 3.0], + composition, + results, + vec![1, 2, 3], + vec![], priors, MU, - BETA, SIGMA, + BETA, GAMMA, P_DRAW, + false, ); let p0 = h.batches[0].posteriors(); - assert_ulps_eq!(p0[&a].mu(), 29.205220743876975, epsilon = 0.000001); - assert_ulps_eq!(p0[&a].sigma(), 7.194481422570443, epsilon = 0.000001); + assert_ulps_eq!(p0["a"].mu, 29.205220743876975, epsilon = 0.000001); + assert_ulps_eq!(p0["a"].sigma, 7.194481422570443, epsilon = 0.000001); - let observed = h.batches[1].skills[&a].forward.sigma(); + let observed = h.batches[1].skills["a"].forward.sigma; let gamma: f64 = 0.15 * 25.0 / 3.0; - let expected = (gamma.powi(2) + h.batches[0].posterior(&a).sigma().powi(2)).sqrt(); + let expected = (gamma.powi(2) + h.batches[0].posterior("a").sigma.powi(2)).sqrt(); assert_ulps_eq!(observed, expected, epsilon = 0.000001); - let observed = h.batches[1].posterior(&a); + let observed = h.batches[1].posterior("a"); let p = Game::new( - h.batches[1].within_priors(0, &mut h.agents), - vec![0, 1], + h.batches[1].within_priors(0, false, false, &mut h.agents), + vec![0.0, 1.0], + vec![], P_DRAW, ) .posteriors(); let expected = p[0][0]; - assert_ulps_eq!(observed.mu(), expected.mu(), epsilon = 0.000001); - assert_ulps_eq!(observed.sigma(), expected.sigma(), epsilon = 0.000001); + assert_ulps_eq!(observed.mu, expected.mu, epsilon = 0.000001); + assert_ulps_eq!(observed.sigma, expected.sigma, epsilon = 0.000001); } #[test] fn test_one_batch() { - let a = PlayerIndex::new(0); - let b = PlayerIndex::new(1); - let c = PlayerIndex::new(2); - let composition = vec![ - vec![vec![a], vec![b]], - vec![vec![b], vec![c]], - vec![vec![c], vec![a]], + vec![vec!["a"], vec!["b"]], + vec![vec!["b"], vec!["c"]], + vec![vec!["c"], vec!["a"]], ]; - let results = vec![vec![1, 0], vec![1, 0], vec![1, 0]]; - let times = vec![1.0, 1.0, 1.0]; + let results = vec![vec![1.0, 0.0], vec![1.0, 0.0], vec![1.0, 0.0]]; + let times = vec![1, 1, 1]; let mut priors = HashMap::new(); - for k in [a, b, c] { + for k in ["a", "b", "c"] { let player = Player::new( Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 0.15 * 25.0 / 3.0, - N_INF, ); - priors.insert(k, player); + priors.insert(k.to_string(), player); } let mut h1 = History::new( - &composition, - &results, - ×, + composition, + results, + times, + vec![], priors, MU, - BETA, SIGMA, + BETA, GAMMA, P_DRAW, + false, ); assert_ulps_eq!( - h1.batches[0].posterior(&a).mu(), + h1.batches[0].posterior("a").mu, 22.904409330892914, epsilon = 0.000001 ); assert_ulps_eq!( - h1.batches[0].posterior(&a).sigma(), + h1.batches[0].posterior("a").sigma, 6.0103304390431, epsilon = 0.000001 ); assert_ulps_eq!( - h1.batches[0].posterior(&c).mu(), + h1.batches[0].posterior("c").mu, 25.110318212568806, epsilon = 0.000001 ); assert_ulps_eq!( - h1.batches[0].posterior(&c).sigma(), + h1.batches[0].posterior("c").sigma, 5.866311348102563, epsilon = 0.000001 ); - let (_step, _i) = h1.convergence(); + let (_step, _i) = h1.convergence(ITERATIONS, EPSILON, false); assert_ulps_eq!( - h1.batches[0].posterior(&a).mu(), + h1.batches[0].posterior("a").mu, 25.00000000, epsilon = 0.000001 ); assert_ulps_eq!( - h1.batches[0].posterior(&a).sigma(), + h1.batches[0].posterior("a").sigma, 5.41921200, epsilon = 0.000001 ); assert_ulps_eq!( - h1.batches[0].posterior(&c).mu(), + h1.batches[0].posterior("c").mu, 25.00000000, epsilon = 0.000001 ); assert_ulps_eq!( - h1.batches[0].posterior(&c).sigma(), + h1.batches[0].posterior("c").sigma, 5.41921200, epsilon = 0.000001 ); let composition = vec![ - vec![vec![a], vec![b]], - vec![vec![b], vec![c]], - vec![vec![c], vec![a]], + vec![vec!["a"], vec!["b"]], + vec![vec!["b"], vec!["c"]], + vec![vec!["c"], vec!["a"]], ]; - let results = vec![vec![1, 0], vec![1, 0], vec![1, 0]]; - let times = vec![1.0, 2.0, 3.0]; + let results = vec![vec![1.0, 0.0], vec![1.0, 0.0], vec![1.0, 0.0]]; + let times = vec![1, 2, 3]; let mut priors = HashMap::new(); - for k in [a, b, c] { - let player = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); + for k in ["a", "b", "c"] { + let player = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); - priors.insert(k, player); + priors.insert(k.to_string(), player); } let mut h2 = History::new( - &composition, - &results, - ×, + composition, + results, + times, + vec![], priors, MU, - BETA, SIGMA, + BETA, GAMMA, P_DRAW, + false, ); assert_ulps_eq!( - h2.batches[2].posterior(&a).mu(), + h2.batches[2].posterior("a").mu, 22.90352227792141, epsilon = 0.000001 ); assert_ulps_eq!( - h2.batches[2].posterior(&a).sigma(), + h2.batches[2].posterior("a").sigma, 6.011017301320632, epsilon = 0.000001 ); assert_ulps_eq!( - h2.batches[2].posterior(&c).mu(), + h2.batches[2].posterior("c").mu, 25.110702468366718, epsilon = 0.000001 ); assert_ulps_eq!( - h2.batches[2].posterior(&c).sigma(), + h2.batches[2].posterior("c").sigma, 5.866811597660157, epsilon = 0.000001 ); - let (_step, _i) = h2.convergence(); + let (_step, _i) = h2.convergence(ITERATIONS, EPSILON, false); assert_ulps_eq!( - h2.batches[2].posterior(&a).mu(), + h2.batches[2].posterior("a").mu, 24.99866831022851, epsilon = 0.000001 ); assert_ulps_eq!( - h2.batches[2].posterior(&a).sigma(), + h2.batches[2].posterior("a").sigma, 5.420053708148435, epsilon = 0.000001 ); assert_ulps_eq!( - h2.batches[2].posterior(&c).mu(), + h2.batches[2].posterior("c").mu, 25.000532179593538, epsilon = 0.000001 ); assert_ulps_eq!( - h2.batches[2].posterior(&c).sigma(), + h2.batches[2].posterior("c").sigma, 5.419827012784138, epsilon = 0.000001 ); @@ -478,70 +514,63 @@ mod tests { #[test] fn test_learning_curves() { - let a = PlayerIndex::new(0); - let b = PlayerIndex::new(1); - let c = PlayerIndex::new(2); - let composition = vec![ - vec![vec![a], vec![b]], - vec![vec![b], vec![c]], - vec![vec![c], vec![a]], + vec![vec!["a"], vec!["b"]], + vec![vec!["b"], vec!["c"]], + vec![vec!["c"], vec!["a"]], ]; - let results = vec![vec![1, 0], vec![1, 0], vec![1, 0]]; - let times = vec![5.0, 6.0, 7.0]; + let results = vec![vec![1.0, 0.0], vec![1.0, 0.0], vec![1.0, 0.0]]; + let times = vec![5, 6, 7]; let mut priors = HashMap::new(); - for k in [a, b, c] { - let player = Player::new( - Gaussian::new(25.0, 25.0 / 3.0), - 25.0 / 6.0, - 25.0 / 300.0, - N_INF, - ); + for k in ["a", "b", "c"] { + let player = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0); - priors.insert(k, player); + priors.insert(k.to_string(), player); } let mut h = History::new( - &composition, - &results, - ×, + composition, + results, + times, + vec![], priors, MU, - BETA, SIGMA, + BETA, GAMMA, P_DRAW, + false, ); - h.convergence(); + h.convergence(ITERATIONS, EPSILON, false); let lc = h.learning_curves(); - let aj_e = lc[&a].len(); - let cj_e = lc[&c].len(); + let aj_e = lc["a"].len(); + let cj_e = lc["c"].len(); - assert_eq!(lc[&a][0].0, 5.0); - assert_eq!(lc[&a][aj_e - 1].0, 7.0); + assert_eq!(lc["a"][0].0, 5); + assert_eq!(lc["a"][aj_e - 1].0, 7); assert_ulps_eq!( - lc[&a][aj_e - 1].1.mu(), + lc["a"][aj_e - 1].1.mu, 24.99866831022851, epsilon = 0.000001 ); assert_ulps_eq!( - lc[&a][aj_e - 1].1.sigma(), + lc["a"][aj_e - 1].1.sigma, 5.420053708148435, epsilon = 0.000001 ); assert_ulps_eq!( - lc[&c][cj_e - 1].1.mu(), + lc["c"][cj_e - 1].1.mu, 25.000532179593538, epsilon = 0.000001 ); assert_ulps_eq!( - lc[&c][cj_e - 1].1.sigma(), + lc["c"][cj_e - 1].1.sigma, 5.419827012784138, epsilon = 0.000001 ); @@ -549,61 +578,59 @@ mod tests { #[test] fn test_env_ttt() { - let a = PlayerIndex::new(0); - let b = PlayerIndex::new(1); - let c = PlayerIndex::new(2); - let composition = vec![ - vec![vec![a], vec![b]], - vec![vec![a], vec![c]], - vec![vec![b], vec![c]], + vec![vec!["a"], vec!["b"]], + vec![vec!["a"], vec!["c"]], + vec![vec!["b"], vec!["c"]], ]; - let results = vec![vec![1, 0], vec![0, 1], vec![1, 0]]; + let results = vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]]; let mut h = History::new( - &composition, - &results, - &[], + composition, + results, + vec![], + vec![], HashMap::new(), 25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0, 0.0, + false, ); - let (_step, _i) = h.convergence(); + let (_step, _i) = h.convergence(ITERATIONS, EPSILON, false); - assert_eq!(h.batches[2].skills[&b].elapsed, 1.0); - assert_eq!(h.batches[2].skills[&c].elapsed, 1.0); + assert_eq!(h.batches[2].skills["b"].elapsed, 1); + assert_eq!(h.batches[2].skills["c"].elapsed, 1); assert_ulps_eq!( - h.batches[0].posterior(&a).mu(), + h.batches[0].posterior("a").mu, 25.0002673, epsilon = 0.000001 ); assert_ulps_eq!( - h.batches[0].posterior(&a).sigma(), + h.batches[0].posterior("a").sigma, 5.41938162, epsilon = 0.000001 ); assert_ulps_eq!( - h.batches[0].posterior(&b).mu(), + h.batches[0].posterior("b").mu, 24.999465, epsilon = 0.000001 ); assert_ulps_eq!( - h.batches[0].posterior(&b).sigma(), + h.batches[0].posterior("b").sigma, 5.419425831, epsilon = 0.000001 ); assert_ulps_eq!( - h.batches[2].posterior(&b).mu(), + h.batches[2].posterior("b").mu, 25.00053219, epsilon = 0.000001 ); assert_ulps_eq!( - h.batches[2].posterior(&b).sigma(), + h.batches[2].posterior("b").sigma, 5.419696790, epsilon = 0.000001 ); diff --git a/src/lib.rs b/src/lib.rs index ac6c4f1..aa427f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,211 @@ +use std::cmp::Reverse; +use std::f64::consts::{FRAC_1_SQRT_2, FRAC_2_SQRT_PI, SQRT_2}; + +mod agent; mod batch; mod game; mod gaussian; mod history; mod message; mod player; -mod utils; -mod variable; -pub use batch::*; -pub use game::*; -pub use gaussian::*; -pub use history::*; -pub use player::*; +use gaussian::Gaussian; +use message::DiffMessage; -pub const BETA: f64 = 1.0; +pub use game::Game; +pub use history::History; +pub use player::Player; + +const BETA: f64 = 1.0; pub const MU: f64 = 0.0; pub const SIGMA: f64 = BETA * 6.0; -pub const GAMMA: f64 = BETA * 0.03; -pub const P_DRAW: f64 = 0.0; +const GAMMA: f64 = BETA * 0.03; +const P_DRAW: f64 = 0.0; +pub const EPSILON: f64 = 1e-6; +pub const ITERATIONS: usize = 30; -pub const N01: Gaussian = Gaussian::new(0.0, 1.0); -pub const N00: Gaussian = Gaussian::new(0.0, 0.0); -pub const N_INF: Gaussian = Gaussian::new(0.0, f64::INFINITY); -pub const N_MS: Gaussian = Gaussian::new(MU, SIGMA); +const SQRT_TAU: f64 = 2.5066282746310002; + +const N01: Gaussian = Gaussian { + mu: 0.0, + sigma: 1.0, +}; +pub(crate) const N00: Gaussian = Gaussian { + mu: 0.0, + sigma: 0.0, +}; +pub(crate) const N_INF: Gaussian = Gaussian { + mu: 0.0, + sigma: f64::INFINITY, +}; + +fn erfc(x: f64) -> f64 { + let z = x.abs(); + let t = 1.0 / (1.0 + z / 2.0); + + let a = -0.82215223 + t * 0.17087277; + let b = 1.48851587 + t * a; + let c = -1.13520398 + t * b; + let d = 0.27886807 + t * c; + let e = -0.18628806 + t * d; + let f = 0.09678418 + t * e; + let g = 0.37409196 + t * f; + let h = 1.00002368 + t * g; + + let r = t * (-z * z - 1.26551223 + t * h).exp(); + + if x >= 0.0 { + r + } else { + 2.0 - r + } +} + +fn erfc_inv(mut y: f64) -> f64 { + if y >= 2.0 { + return f64::NEG_INFINITY; + } + + debug_assert!(y >= 0.0, "y must be nonnegative"); + + if y == 0.0 { + return f64::INFINITY; + } + + if y >= 1.0 { + y = 2.0 - y; + } + + let t = (-2.0 * (y / 2.0).ln()).sqrt(); + + let mut x = FRAC_1_SQRT_2 * ((2.30753 + t * 0.27061) / (1.0 + t * (0.99229 + t * 0.04481)) - t); + + for _ in 0..3 { + let err = erfc(x) - y; + + x += err / (FRAC_2_SQRT_PI * (-(x.powi(2))).exp() - x * err) + } + + if y < 1.0 { + x + } else { + -x + } +} + +fn ppf(p: f64, mu: f64, sigma: f64) -> f64 { + mu - sigma * SQRT_2 * erfc_inv(2.0 * p) +} + +fn compute_margin(p_draw: f64, sd: f64) -> f64 { + ppf(0.5 - p_draw / 2.0, 0.0, sd).abs() +} + +fn cdf(x: f64, mu: f64, sigma: f64) -> f64 { + let z = -(x - mu) / (sigma * SQRT_2); + + 0.5 * erfc(z) +} + +fn pdf(x: f64, mu: f64, sigma: f64) -> f64 { + let normalizer = (SQRT_TAU * sigma).powi(-1); + let functional = (-((x - mu).powi(2)) / (2.0 * sigma.powi(2))).exp(); + + normalizer * functional +} + +fn v_w(mu: f64, sigma: f64, margin: f64, tie: bool) -> (f64, f64) { + if !tie { + let alpha = (margin - mu) / sigma; + + let v = pdf(-alpha, 0.0, 1.0) / cdf(-alpha, 0.0, 1.0); + let w = v * (v + (-alpha)); + + (v, w) + } else { + let alpha = (-margin - mu) / sigma; + let beta = (margin - mu) / sigma; + + let v = (pdf(alpha, 0.0, 1.0) - pdf(beta, 0.0, 1.0)) + / (cdf(beta, 0.0, 1.0) - cdf(alpha, 0.0, 1.0)); + let u = (alpha * pdf(alpha, 0.0, 1.0) - beta * pdf(beta, 0.0, 1.0)) + / (cdf(beta, 0.0, 1.0) - cdf(alpha, 0.0, 1.0)); + let w = -(u - v.powi(2)); + + (v, w) + } +} + +fn trunc(mu: f64, sigma: f64, margin: f64, tie: bool) -> (f64, f64) { + let (v, w) = v_w(mu, sigma, margin, tie); + + let mu_trunc = mu + sigma * v; + let sigma_trunc = sigma * (1.0 - w).sqrt(); + + (mu_trunc, sigma_trunc) +} + +pub(crate) fn approx(n: Gaussian, margin: f64, tie: bool) -> Gaussian { + let (mu, sigma) = trunc(n.mu, n.sigma, margin, tie); + + Gaussian { mu, sigma } +} + +pub(crate) fn tuple_max(v1: (f64, f64), v2: (f64, f64)) -> (f64, f64) { + ( + if v1.0 > v2.0 { v1.0 } else { v2.0 }, + if v1.1 > v2.1 { v1.1 } else { v2.1 }, + ) +} + +pub(crate) fn tuple_gt(t: (f64, f64), e: f64) -> bool { + t.0 > e || t.1 > e +} + +pub(crate) fn sort_perm(x: &[f64], reverse: bool) -> Vec { + let mut v = x.iter().enumerate().collect::>(); + + if reverse { + v.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap()); + } else { + v.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()); + } + + v.into_iter().map(|(i, _)| i).collect() +} + +pub(crate) fn sort_time(xs: &[u64], reverse: bool) -> Vec { + let mut x = xs.iter().enumerate().collect::>(); + + if reverse { + x.sort_by_key(|(_, &x)| Reverse(x)); + } else { + x.sort_by_key(|(_, &x)| x); + } + + x.into_iter().map(|(i, _)| i).collect() +} + +pub(crate) fn evidence(d: &[DiffMessage], margin: &[f64], tie: &[bool], e: usize) -> f64 { + if tie[e] { + cdf(margin[e], d[e].prior.mu, d[e].prior.sigma) + - cdf(-margin[e], d[e].prior.mu, d[e].prior.sigma) + } else { + 1.0 - cdf(margin[e], d[e].prior.mu, d[e].prior.sigma) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sort_perm() { + assert_eq!(sort_perm(&[0.0, 1.0, 2.0, 0.0], true), vec![2, 1, 0, 3]); + } + + #[test] + fn test_sort_time() { + assert_eq!(sort_time(&[0, 1, 2, 0], true), vec![2, 1, 0, 3]); + } +} diff --git a/src/message.rs b/src/message.rs index 6187b2b..bb49b4c 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,23 +1,62 @@ -use crate::{Gaussian, N_INF}; +use crate::gaussian::Gaussian; -#[derive(Debug)] -pub struct DiffMessages { - pub prior: Gaussian, - pub likelihood: Gaussian, +pub(crate) struct TeamMessage { + pub(crate) prior: Gaussian, + pub(crate) likelihood_lose: Gaussian, + pub(crate) likelihood_win: Gaussian, + pub(crate) likelihood_draw: Gaussian, } -impl DiffMessages { - #[inline] - pub fn p(&self) -> Gaussian { +impl TeamMessage { + pub(crate) fn p(&self) -> Gaussian { + self.prior * self.likelihood_lose * self.likelihood_win * self.likelihood_draw + } + + pub(crate) fn posterior_win(&self) -> Gaussian { + self.prior * self.likelihood_lose * self.likelihood_draw + } + + pub(crate) fn posterior_lose(&self) -> Gaussian { + self.prior * self.likelihood_win * self.likelihood_draw + } + + pub(crate) fn likelihood(&self) -> Gaussian { + self.likelihood_win * self.likelihood_lose * self.likelihood_draw + } +} + +pub(crate) struct DrawMessage { + pub(crate) prior: Gaussian, + pub(crate) prior_team: Gaussian, + pub(crate) likelihood_lose: Gaussian, + pub(crate) likelihood_win: Gaussian, +} + +impl DrawMessage { + pub(crate) fn p(&self) -> Gaussian { + self.prior_team * self.likelihood_lose * self.likelihood_win + } + + pub(crate) fn posterior_win(&self) -> Gaussian { + self.prior_team * self.likelihood_lose + } + + pub(crate) fn posterior_lose(&self) -> Gaussian { + self.prior_team * self.likelihood_win + } + + pub(crate) fn likelihood(&self) -> Gaussian { + self.likelihood_win * self.likelihood_lose + } +} + +pub(crate) struct DiffMessage { + pub(crate) prior: Gaussian, + pub(crate) likelihood: Gaussian, +} + +impl DiffMessage { + pub(crate) fn p(&self) -> Gaussian { self.prior * self.likelihood } } - -impl Default for DiffMessages { - fn default() -> Self { - Self { - prior: N_INF, - likelihood: N_INF, - } - } -} diff --git a/src/player.rs b/src/player.rs index 6c96ea7..3bfc8e9 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,44 +1,35 @@ -use crate::{Gaussian, BETA, GAMMA, N_INF}; - -#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub struct PlayerIndex(usize); - -impl PlayerIndex { - pub fn new(index: usize) -> Self { - Self(index) - } -} +use crate::{gaussian::Gaussian, BETA, GAMMA, N_INF}; #[derive(Clone, Copy, Debug)] pub struct Player { - pub prior: Gaussian, - pub beta: f64, - pub gamma: f64, - pub draw: Gaussian, + pub(crate) prior: Gaussian, + pub(crate) beta: f64, + pub(crate) gamma: f64, + pub(crate) draw: Gaussian, } impl Player { - pub fn new(prior: Gaussian, beta: f64, gamma: f64, draw: Gaussian) -> Self { - Player { + pub fn new(prior: Gaussian, beta: f64, gamma: f64) -> Self { + Self { prior, beta, gamma, - draw, + draw: N_INF, } } -} -impl Player { - pub fn performance(&self) -> Gaussian { - Gaussian::new( - self.prior.mu(), - (self.prior.sigma().powi(2) + self.beta.powi(2)).sqrt(), - ) + pub(crate) fn performance(&self) -> Gaussian { + self.prior.forget(self.beta, 1) } } impl Default for Player { fn default() -> Self { - Player::new(Gaussian::default(), BETA, GAMMA, N_INF) + Self { + prior: Gaussian::default(), + beta: BETA, + gamma: GAMMA, + draw: N_INF, + } } } diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index e980d46..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::cmp::Reverse; -use std::f64::consts::{FRAC_1_SQRT_2, FRAC_2_SQRT_PI, SQRT_2}; - -use crate::Gaussian; - -const SQRT_TAU: f64 = 2.5066282746310002; - -#[inline] -fn erfc(x: f64) -> f64 { - let z = x.abs(); - let t = 1.0 / (1.0 + z / 2.0); - - let a = -0.82215223 + t * 0.17087277; - let b = 1.48851587 + t * a; - let c = -1.13520398 + t * b; - let d = 0.27886807 + t * c; - let e = -0.18628806 + t * d; - let f = 0.09678418 + t * e; - let g = 0.37409196 + t * f; - let h = 1.00002368 + t * g; - - let r = t * (-z * z - 1.26551223 + t * h).exp(); - - if x >= 0.0 { - r - } else { - 2.0 - r - } -} - -#[inline] -fn erfc_inv(mut y: f64) -> f64 { - if y >= 2.0 { - return f64::NEG_INFINITY; - } - - debug_assert!(y >= 0.0, "argument must be nonnegative"); - - if y == 0.0 { - return f64::INFINITY; - } - - if y >= 1.0 { - y = 2.0 - y; - } - - let t = (-2.0 * (y / 2.0).ln()).sqrt(); - - let mut x = FRAC_1_SQRT_2 * ((2.30753 + t * 0.27061) / (1.0 + t * (0.99229 + t * 0.04481)) - t); - - for _ in 0..3 { - let err = erfc(x) - y; - - x += err / (FRAC_2_SQRT_PI * (-(x.powi(2))).exp() - x * err) - } - - if y < 1.0 { - x - } else { - -x - } -} - -#[inline] -fn ppf(p: f64, mu: f64, sigma: f64) -> f64 { - mu - sigma * SQRT_2 * erfc_inv(2.0 * p) -} - -#[inline] -pub(crate) fn mu_sigma(tau: f64, pi: f64) -> (f64, f64) { - if pi > 0.0 { - return (tau / pi, (1.0 / pi).sqrt()); - } - - if pi + 1e-5 < 0.0 { - panic!("pi should be greater than 0, got: {}", pi + 1e-5); - } - - (0.0, f64::INFINITY) -} - -#[inline] -pub(crate) fn cdf(x: f64, mu: f64, sigma: f64) -> f64 { - let z = -(x - mu) / (sigma * SQRT_2); - - 0.5 * erfc(z) -} - -#[inline] -fn pdf(x: f64, mu: f64, sigma: f64) -> f64 { - let normalizer = (SQRT_TAU * sigma).powi(-1); - let functional = (-((x - mu).powi(2)) / (2.0 * sigma.powi(2))).exp(); - - normalizer * functional -} - -/* -def ppf(p, mu, sigma): - return mu - sigma * sqrt2 * erfcinv(2 * p) -*/ - -#[inline] -fn v_w(mu: f64, sigma: f64, margin: f64, tie: bool) -> (f64, f64) { - if !tie { - let alpha = (margin - mu) / sigma; - - let v = pdf(-alpha, 0.0, 1.0) / cdf(-alpha, 0.0, 1.0); - let w = v * (v + (-alpha)); - - (v, w) - } else { - let alpha = (-margin - mu) / sigma; - let beta = (margin - mu) / sigma; - - let v = (pdf(alpha, 0.0, 1.0) - pdf(beta, 0.0, 1.0)) - / (cdf(beta, 0.0, 1.0) - cdf(alpha, 0.0, 1.0)); - let u = (alpha * pdf(alpha, 0.0, 1.0) - beta * pdf(beta, 0.0, 1.0)) - / (cdf(beta, 0.0, 1.0) - cdf(alpha, 0.0, 1.0)); - let w = -(u - v.powi(2)); - - (v, w) - } -} - -#[inline] -pub(crate) fn trunc(mu: f64, sigma: f64, margin: f64, tie: bool) -> (f64, f64) { - let (v, w) = v_w(mu, sigma, margin, tie); - - let mu_trunc = mu + sigma * v; - let sigma_trunc = sigma * (1.0 - w).sqrt(); - - (mu_trunc, sigma_trunc) -} - -#[inline] -pub(crate) fn approx(n: Gaussian, margin: f64, tie: bool) -> Gaussian { - let (mu, sigma) = trunc(n.mu(), n.sigma(), margin, tie); - - Gaussian::new(mu, sigma) -} - -#[inline] -pub(crate) fn compute_margin(p_draw: f64, sd: f64) -> f64 { - ppf(0.5 - p_draw / 2.0, 0.0, sd).abs() -} - -#[inline] -pub(crate) fn sort_perm(xs: &[u16]) -> Vec { - let mut x = xs.iter().enumerate().collect::>(); - - x.sort_unstable_by_key(|(_, x)| Reverse(*x)); - - x.into_iter().map(|(i, _)| i).collect() -} - -#[inline] -pub(crate) fn sort_time(xs: &[f64]) -> Vec { - let mut x = xs.iter().enumerate().collect::>(); - - x.sort_unstable_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()); - - x.into_iter().map(|(i, _)| i).collect() -} - -#[cfg(test)] -mod tests { - use crate::{Gaussian, N01}; - - use super::*; - - #[test] - fn test_ppf() { - assert_eq!(ppf(0.3, N01.mu(), N01.sigma()), -0.5244004458961101); - - let n23 = Gaussian::new(2.0, 3.0); - assert_eq!(ppf(0.3, n23.mu(), n23.sigma()), 0.4267986623116695); - } - - #[test] - fn test_cdf() { - assert_eq!(cdf(0.3, N01.mu(), N01.sigma()), 0.6179114097962345); - - let n23 = Gaussian::new(2.0, 3.0); - assert_eq!(cdf(0.3, n23.mu(), n23.sigma()), 0.28547031198297773); - } - - #[test] - fn test_pdf() { - assert_eq!(pdf(0.3, N01.mu(), N01.sigma()), 0.38138781546052414); - - let n23 = Gaussian::new(2.0, 3.0); - assert_eq!(pdf(0.3, n23.mu(), n23.sigma()), 0.11325579143491937); - } - - #[test] - fn test_compute_margin() { - assert_eq!( - compute_margin(0.25, 2.0f64.sqrt() * (25.0 / 6.0)), - 1.8776005988640154 - ); - assert_eq!( - compute_margin(0.25, 3.0f64.sqrt() * (25.0 / 6.0)), - 2.2995817039804787 - ); - assert_eq!( - compute_margin(0.0, 3.0f64.sqrt() * (25.0 / 6.0)), - 2.71348758713328e-7 - ); - assert_eq!( - compute_margin(1.0, 3.0f64.sqrt() * (25.0 / 6.0)), - f64::INFINITY - ); - } - - #[test] - fn test_trunc() { - let g = Gaussian::new(0.0, 1.0); - - assert_eq!( - trunc(g.mu(), g.sigma(), 0.0, false), - (0.7978845368663289, 0.6028103066716792) - ); - - let g = Gaussian::new(0.0, SQRT_2 * (25.0 / 6.0)); - - assert_eq!( - trunc(g.mu(), g.sigma(), 1.8776005988, true), - (0.0, 1.0767055018086311) - ); - - let g = Gaussian::new(12.0, SQRT_2 * (25.0 / 6.0)); - - assert_eq!( - trunc(g.mu(), g.sigma(), 1.8776005988, true), - (0.39009949143595435, 1.034397855300721) - ); - } - - #[test] - fn test_sortperm() { - assert_eq!(sort_perm(&[0, 1, 2, 0]), vec![2, 1, 0, 3]); - } - - #[test] - fn test_sort_time() { - assert_eq!(sort_time(&[1.0, 1.0, 1.0]), vec![0, 1, 2]); - } -} diff --git a/src/variable.rs b/src/variable.rs deleted file mode 100644 index 8eabb83..0000000 --- a/src/variable.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{Gaussian, N_INF}; - -#[derive(Debug)] -pub struct TeamVariable { - pub prior: Gaussian, - pub likelihood_lose: Gaussian, - pub likelihood_win: Gaussian, - pub likelihood_draw: Gaussian, -} - -impl TeamVariable { - #[inline] - pub fn p(&self) -> Gaussian { - self.prior * self.likelihood_lose * self.likelihood_win * self.likelihood_draw - } - - #[inline] - pub fn posterior_win(&self) -> Gaussian { - self.prior * self.likelihood_lose * self.likelihood_draw - } - - #[inline] - pub fn posterior_lose(&self) -> Gaussian { - self.prior * self.likelihood_win * self.likelihood_draw - } - - #[inline] - pub fn likelihood(&self) -> Gaussian { - self.likelihood_win * self.likelihood_lose * self.likelihood_draw - } -} - -impl Default for TeamVariable { - fn default() -> Self { - Self { - prior: N_INF, - likelihood_lose: N_INF, - likelihood_win: N_INF, - likelihood_draw: N_INF, - } - } -}