Files
trueskill-tt/src/game.rs
2022-06-27 11:49:47 +02:00

552 lines
18 KiB
Rust

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<Vec<Player>>,
result: Vec<f64>,
weights: Vec<Vec<f64>>,
p_draw: f64,
pub(crate) likelihoods: Vec<Vec<Gaussian>>,
pub(crate) evidence: f64,
}
impl Game {
pub fn new(
teams: Vec<Vec<Player>>,
mut result: Vec<f64>,
mut weights: Vec<Vec<f64>>,
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::<Vec<_>>();
}
if weights.is_empty() {
weights = teams
.iter()
.map(|team| vec![1.0; team.len()])
.collect::<Vec<_>>();
}
let mut this = Self {
teams,
result,
weights,
p_draw,
likelihoods: Vec::new(),
evidence: 0.0,
};
this.likelihoods();
this
}
fn likelihoods(&mut self) -> &Vec<Vec<Gaussian>> {
let m_t_ft = self.likelihood_teams();
self.likelihoods = self
.teams
.iter()
.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::<Vec<_>>()
})
.collect::<Vec<_>>();
&self.likelihoods
}
fn likelihood_teams(&mut self) -> Vec<Gaussian> {
let o = sort_perm(&self.result, true);
let mut t = o
.iter()
.map(|&e| {
let performance = self.teams[e]
.iter()
.zip(self.weights[e].iter())
.fold(N00, |p, (player, &weight)| {
p + (player.performance() * weight)
});
TeamMessage {
prior: performance,
likelihood_lose: N_INF,
likelihood_win: N_INF,
likelihood_draw: N_INF,
}
})
.collect::<Vec<_>>();
let mut d = t
.windows(2)
.map(|w| DiffMessage {
prior: w[0].prior - w[1].prior,
likelihood: N_INF,
})
.collect::<Vec<_>>();
let tie = o
.windows(2)
.map(|e| self.result[e[0]] == self.result[e[1]])
.collect::<Vec<_>>();
let margin = if self.p_draw == 0.0 {
vec![0.0; o.len() - 1]
} else {
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::<Vec<_>>()
};
self.evidence = 1.0;
let mut step = (f64::INFINITY, f64::INFINITY);
let mut iter = 0;
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 iter == 0 {
self.evidence *= evidence(&d, &margin, &tie, e);
}
d[e].likelihood = approx(d[e].prior, margin[e], tie[e]) / d[e].prior;
let likelihood_lose = t[e].posterior_win() - d[e].likelihood;
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 iter == 0 && e == d.len() - 1 {
self.evidence *= evidence(&d, &margin, &tie, e);
}
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;
step = tuple_max(step, t[e].likelihood_win.delta(likelihood_win));
t[e].likelihood_win = likelihood_win;
}
iter += 1;
}
if d.len() == 1 {
self.evidence = evidence(&d, &margin, &tie, 0);
d[0].prior = t[0].posterior_win() - t[1].posterior_lose();
d[0].likelihood = approx(d[0].prior, margin[0], tie[0]) / d[0].prior;
}
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_end].likelihood_lose = t[t_end - 1].posterior_win() - d[d_end].likelihood;
o.iter().map(|&e| t[e].likelihood()).collect::<Vec<_>>()
}
pub fn posteriors(&self) -> Vec<Vec<Gaussian>> {
self.likelihoods
.iter()
.zip(self.teams.iter())
.map(|(l, t)| {
l.iter()
.zip(t.iter())
.map(|(&l, p)| l * p.prior)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
}
}
#[cfg(test)]
mod tests {
use ::approx::assert_ulps_eq;
use crate::{Gaussian, Player, GAMMA, N_INF};
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);
let t_b = 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![0.0, 1.0], vec![], 0.0);
let p = g.posteriors();
let a = p[0][0];
let b = p[1][0];
assert_ulps_eq!(a, Gaussian::new(20.794779, 7.194481), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(29.205220, 7.194481), epsilon = 1e-6);
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.0, 1.0], vec![], 0.0);
let p = g.posteriors();
let a = p[0][0];
let b = p[1][0];
assert_ulps_eq!(a, Gaussian::new(28.896475, 0.996604), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(32.189211, 6.062063), epsilon = 1e-6);
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.0, 1.0], vec![], 0.0);
assert_eq!(g.likelihoods[0][0], N_INF);
assert_eq!(g.likelihoods[1][0], N_INF);
}
#[test]
fn test_1vs1vs1() {
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(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_ulps_eq!(a, Gaussian::new(25.000000, 6.238469), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(31.311358, 6.698818), epsilon = 1e-6);
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_ulps_eq!(a, Gaussian::new(31.311358, 6.698818), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(25.000000, 6.238469), epsilon = 1e-6);
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_ulps_eq!(a, Gaussian::new(24.999999, 6.092561), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(33.379314, 6.483575), epsilon = 1e-6);
assert_ulps_eq!(c, Gaussian::new(16.620685, 6.483575), epsilon = 1e-6);
}
#[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);
let t_b = 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![0.0, 0.0], vec![], 0.25);
let p = g.posteriors();
let a = p[0][0];
let b = p[1][0];
assert_ulps_eq!(a, Gaussian::new(24.999999, 6.469480), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(24.999999, 6.469480), epsilon = 1e-6);
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 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_ulps_eq!(a, Gaussian::new(25.736001, 2.709956), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(28.672888, 1.916471), epsilon = 1e-6);
}
#[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);
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, 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_ulps_eq!(a, Gaussian::new(24.999999, 5.729068), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(25.000000, 5.707423), epsilon = 1e-6);
assert_ulps_eq!(c, Gaussian::new(24.999999, 5.729068), epsilon = 1e-6);
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, 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_ulps_eq!(a, Gaussian::new(25.488507, 2.638208), epsilon = 1e-6);
assert_ulps_eq!(b, Gaussian::new(25.510671, 2.628751), epsilon = 1e-6);
assert_ulps_eq!(c, Gaussian::new(28.555920, 1.885689), epsilon = 1e-6);
}
#[test]
fn test_2vs1vs2_mixed() {
let t_a = vec![
Player::new(Gaussian::new(12.0, 3.0), 25.0 / 6.0, 25.0 / 300.0),
Player::new(Gaussian::new(18.0, 3.0), 25.0 / 6.0, 25.0 / 300.0),
];
let t_b = vec![Player::new(
Gaussian::new(30.0, 3.0),
25.0 / 6.0,
25.0 / 300.0,
)];
let t_c = vec![
Player::new(Gaussian::new(14.0, 3.0), 25.0 / 6.0, 25.0 / 300.0),
Player::new(Gaussian::new(16., 3.0), 25.0 / 6.0, 25.0 / 300.0),
];
let g = Game::new(vec![t_a, t_b, t_c], vec![1.0, 0.0, 0.0], vec![], 0.25);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(13.051, 2.864), epsilon = 1e-3);
assert_ulps_eq!(p[0][1], Gaussian::new(19.051, 2.864), epsilon = 1e-3);
assert_ulps_eq!(p[1][0], Gaussian::new(29.292, 2.764), epsilon = 1e-3);
assert_ulps_eq!(p[2][0], Gaussian::new(13.658, 2.813), epsilon = 1e-3);
assert_ulps_eq!(p[2][1], Gaussian::new(15.658, 2.813), epsilon = 1e-3);
}
#[test]
fn test_1vs1_weighted() {
let w_a = vec![1.0];
let w_b = vec![2.0];
let t_a = vec![Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
0.0,
)];
let t_b = vec![Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
0.0,
)];
let g = Game::new(vec![t_a.clone(), t_b.clone()], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(30.625173, 7.765472), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::new(13.749653, 5.733840), epsilon = 1e-6);
let w_a = vec![1.0];
let w_b = vec![0.7];
let g = Game::new(vec![t_a.clone(), t_b.clone()], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(27.630080, 7.206676), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::new(23.158943, 7.801628), epsilon = 1e-6);
let w_a = vec![1.6];
let w_b = vec![0.7];
let g = Game::new(vec![t_a, t_b], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(26.142438, 7.573088), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::new(24.500183, 8.193278), epsilon = 1e-6);
let w_a = vec![1.0];
let w_b = vec![0.0];
let t_a = vec![Player::new(Gaussian::new(2.0, 6.0), 1.0, 0.0)];
let t_b = vec![Player::new(Gaussian::new(2.0, 6.0), 1.0, 0.0)];
let g = Game::new(vec![t_a, t_b], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(5.557067, 4.052826), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::new(2.000000, 6.000000), epsilon = 1e-6);
let w_a = vec![1.0];
let w_b = vec![-1.0];
let t_a = vec![Player::new(Gaussian::new(2.0, 6.0), 1.0, 0.0)];
let t_b = vec![Player::new(Gaussian::new(2.0, 6.0), 1.0, 0.0)];
let g = Game::new(vec![t_a, t_b], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], p[1][0], epsilon = 1e-6);
}
#[test]
fn test_2vs2_weighted() {
let t_a = vec![
Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 0.0),
Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 0.0),
];
let w_a = vec![0.4, 0.8];
let t_b = vec![
Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 0.0),
Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 0.0),
];
let w_b = vec![0.9, 0.6];
let g = Game::new(vec![t_a.clone(), t_b.clone()], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(27.539023, 8.129639), epsilon = 1e-6);
assert_ulps_eq!(p[0][1], Gaussian::new(30.078046, 7.485372), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::new(19.287197, 7.243465), epsilon = 1e-6);
assert_ulps_eq!(p[1][1], Gaussian::new(21.191465, 7.867608), epsilon = 1e-6);
let w_a = vec![1.3, 1.5];
let w_b = vec![0.7, 0.4];
let g = Game::new(vec![t_a.clone(), t_b.clone()], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(25.190190, 8.220511), epsilon = 1e-6);
assert_ulps_eq!(p[0][1], Gaussian::new(25.219450, 8.182783), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::new(24.897589, 8.300779), epsilon = 1e-6);
assert_ulps_eq!(p[1][1], Gaussian::new(24.941479, 8.322717), epsilon = 1e-6);
let w_a = vec![1.6, 0.2];
let w_b = vec![0.7, 2.4];
let g = Game::new(vec![t_a.clone(), t_b.clone()], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], Gaussian::new(31.674697, 7.501180), epsilon = 1e-6);
assert_ulps_eq!(p[0][1], Gaussian::new(25.834337, 8.320970), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::new(22.079819, 8.180607), epsilon = 1e-6);
assert_ulps_eq!(p[1][1], Gaussian::new(14.987953, 6.308469), epsilon = 1e-6);
let g = Game::new(
vec![
t_a.clone(),
vec![Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
0.0,
)],
],
vec![],
vec![],
0.0,
);
let post_2vs1 = g.posteriors();
let w_a = vec![1.0, 1.0];
let w_b = vec![1.0, 0.0];
let g = Game::new(vec![t_a, t_b.clone()], vec![], vec![w_a, w_b], 0.0);
let p = g.posteriors();
assert_ulps_eq!(p[0][0], post_2vs1[0][0], epsilon = 1e-6);
assert_ulps_eq!(p[0][1], post_2vs1[0][1], epsilon = 1e-6);
assert_ulps_eq!(p[1][0], post_2vs1[1][0], epsilon = 1e-6);
assert_ulps_eq!(p[1][1], t_b[1].prior, epsilon = 1e-6);
}
}