diff --git a/src/batch.rs b/src/batch.rs index 80eb979..434e31a 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet}; use crate::{Game, Gaussian, Player, N_INF}; +#[derive(Clone)] pub struct Skill { pub forward: Gaussian, pub backward: Gaussian, @@ -20,6 +21,7 @@ impl Default for Skill { } } +#[derive(Clone, Copy, Debug)] pub struct Agent { pub player: Player, pub message: Gaussian, @@ -44,16 +46,19 @@ impl Agent { } } +#[derive(Clone)] pub struct Item { name: String, likelihood: Gaussian, } +#[derive(Clone)] pub struct Team { items: Vec, output: u16, } +#[derive(Clone)] pub struct Event { teams: Vec, evidence: f64, @@ -86,8 +91,9 @@ fn compute_elapsed(last_time: f64, actual_time: f64) -> f64 { } } +#[derive(Clone)] pub struct Batch { - skills: HashMap, + pub skills: HashMap, events: Vec, time: f64, agents: HashMap, @@ -170,7 +176,7 @@ impl Batch { self.iteration(from); } - fn posterior(&self, agent: &str) -> Gaussian { + pub fn posterior(&self, agent: &str) -> Gaussian { let skill = &self.skills[agent]; skill.likelihood * skill.backward * skill.forward @@ -190,7 +196,7 @@ impl Batch { Player::new(g, r.beta, r.gamma, N_INF) } - fn within_priors(&self, event: usize) -> Vec> { + pub fn within_priors(&self, event: usize) -> Vec> { self.events[event] .teams .iter() @@ -262,8 +268,15 @@ impl Batch { step = dict_diff(old, self.posteriors()) i += 1 return i - def forward_prior_out(self, agent): - return self.skills[agent].forward * self.skills[agent].likelihood + */ + + pub fn forward_prior_out(&self, agent: &str) -> Gaussian { + let skill = &self.skills[agent]; + + skill.forward * skill.likelihood + } + + /* def backward_prior_out(self, agent): N = self.skills[agent].likelihood*self.skills[agent].backward return N.forget(self.agents[agent].player.gamma, self.skills[agent].elapsed) diff --git a/src/game.rs b/src/game.rs index 229a2e7..53866c7 100644 --- a/src/game.rs +++ b/src/game.rs @@ -73,7 +73,7 @@ impl Game { } let r = &self.result; - let o = sortperm(r); + let o = utils::sortperm(r); let t = (0..self.teams.len()) .map(|e| TeamVariable { @@ -272,23 +272,12 @@ impl Game { } } -fn sortperm(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() -} - #[cfg(test)] mod tests { use crate::{Gaussian, Player, GAMMA, N_INF}; use super::*; - #[test] - fn test_sortperm() { - assert_eq!(sortperm(&[0, 1, 2, 0]), vec![2, 1, 0, 3]); - } - #[test] fn test_1vs1() { let t_a = Player::new( diff --git a/src/history.rs b/src/history.rs index 721ac64..3a55a2b 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,5 +1,203 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{utils, Agent, Batch, Gaussian, Player, N_INF}; + pub struct History { + size: usize, + batches: Vec, + agents: HashMap, mu: f64, sigma: f64, gamma: f64, + p_draw: f64, + time: bool, +} + +impl History { + pub fn new( + composition: Vec>>, + results: Vec>, + times: Vec, + priors: HashMap, + mu: f64, + beta: f64, + sigma: f64, + gamma: f64, + p_draw: f64, + ) -> Self { + let this_agent = composition + .iter() + .flat_map(|teams| teams.iter()) + .flat_map(|team| team.iter()) + .cloned() + .collect::>(); + + let agents = this_agent + .into_iter() + .map(|a| { + let player = priors + .get(a) + .cloned() + .unwrap_or_else(|| Player::new(Gaussian::new(mu, sigma), beta, gamma, N_INF)); + + ( + a.to_string(), + Agent { + player, + message: N_INF, + last_time: f64::NEG_INFINITY, + }, + ) + }) + .collect::>(); + + println!("{:#?}", agents); + + let mut this = Self { + size: composition.len(), + batches: Vec::new(), + agents, + mu, + sigma, + gamma, + p_draw, + time: !times.is_empty(), + }; + + this.trueskill(composition, results, times); + + this + } + + fn trueskill( + &mut self, + composition: Vec>>, + results: Vec>, + times: Vec, + ) { + let o = { + let mut o = utils::sortperm(×); + o.reverse(); + o + }; + + let o = o; + let mut i = 0; + + while i < self.size { + let mut j = i + 1; + let t = times[o[i]]; + + while j < self.size && times[o[j]] == t { + j += 1; + } + + let composition = (i..j) + .map(|e| composition[o[e]].clone()) + .collect::>(); + + let results = (i..j).map(|e| results[o[e]].clone()).collect::>(); + + let b = Batch::new( + composition, + results, + t as f64, + self.agents.clone(), + self.p_draw, + ); + + self.batches.push(b.clone()); + + for a in b.skills.keys() { + let agent = self.agents.get_mut(a).unwrap(); + + agent.last_time = t as f64; + agent.message = b.forward_prior_out(a); + } + + i = j; + } + } + + fn iteration(&self) { + todo!() + } + + fn convergence(&self) { + let epsilon = 1e-6; + let iterations = 30; + let verbose = true; + + todo!() + } + + fn learning_curves(&self) { + todo!() + } + + fn log_evidence(&self) { + todo!() + } +} + +#[cfg(test)] +mod tests { + use approx::assert_ulps_eq; + + use crate::{Game, BETA, GAMMA, MU, P_DRAW, SIGMA}; + + use super::*; + + #[test] + fn test_init() { + let composition = vec![ + 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 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, + ); + + priors.insert(k.to_string(), player); + } + + let h = History::new( + composition, + results, + vec![1, 2, 3], + priors, + MU, + BETA, + SIGMA, + GAMMA, + P_DRAW, + ); + + 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); + + 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(); + + assert_ulps_eq!(observed, expected, epsilon = 0.000001); + + let observed = h.batches[1].posterior("a"); + let p = Game::new(h.batches[1].within_priors(0), vec![0, 1], 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); + } } diff --git a/src/lib.rs b/src/lib.rs index 7e29920..ac6c4f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,3 +12,14 @@ pub use game::*; pub use gaussian::*; pub use history::*; pub use player::*; + +pub 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; + +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); diff --git a/src/player.rs b/src/player.rs index 43ab8a3..c0d1e8a 100644 --- a/src/player.rs +++ b/src/player.rs @@ -2,7 +2,7 @@ use std::fmt; use crate::{Gaussian, BETA, GAMMA, N_INF}; -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct Player { pub prior: Gaussian, pub beta: f64, diff --git a/src/utils.rs b/src/utils.rs index cdf7562..dda82a1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use std::cmp::Reverse; use std::f64::consts::{FRAC_1_SQRT_2, FRAC_2_SQRT_PI, SQRT_2}; use crate::Gaussian; @@ -133,6 +134,12 @@ pub(crate) fn compute_margin(p_draw: f64, sd: f64) -> f64 { ppf(0.5 - p_draw / 2.0, 0.0, sd).abs() } +pub(crate) fn sortperm(xs: &[T]) -> Vec { + let mut x = xs.iter().enumerate().collect::>(); + x.sort_unstable_by_key(|(_, x)| Reverse(*x)); + x.into_iter().map(|(i, _)| i).collect() +} + #[cfg(test)] mod tests { use crate::{Gaussian, N01}; @@ -206,4 +213,9 @@ mod tests { (0.39009949143595435, 1.034397855300721) ); } + + #[test] + fn test_sortperm() { + assert_eq!(sortperm(&[0, 1, 2, 0]), vec![2, 1, 0, 3]); + } }