Added a TrueSkill struct that holds default config.

This commit is contained in:
2018-10-26 15:06:17 +02:00
parent 1ae7feb2cf
commit f16cc50901

View File

@@ -25,20 +25,29 @@ pub const MU: f64 = 25.0;
pub const SIGMA: f64 = MU / 3.0; pub const SIGMA: f64 = MU / 3.0;
/// Default distance that guarantees about 76% chance of winning. /// Default distance that guarantees about 76% chance of winning.
const BETA: f64 = SIGMA / 2.0; pub const BETA: f64 = SIGMA / 2.0;
/// Default dynamic factor. /// Default dynamic factor.
const TAU: f64 = SIGMA / 100.0; pub const TAU: f64 = SIGMA / 100.0;
/// Default draw probability of the game. /// Default draw probability of the game.
const DRAW_PROBABILITY: f64 = 0.10; pub const DRAW_PROBABILITY: f64 = 0.10;
/// A basis to check reliability of the result. /// A basis to check reliability of the result.
const DELTA: f64 = 0.0001; pub const DELTA: f64 = 0.0001;
pub trait Rateable { pub trait Rateable {
fn mu(&self) -> f64; fn mu(&self) -> f64;
fn sigma(&self) -> f64; fn sigma(&self) -> f64;
fn skill(&self) -> f64 {
self.mu() - 3.0 * self.sigma()
}
}
pub trait RateableMut {
fn mu_mut(&mut self) -> &mut f64;
fn sigma_mut(&mut self) -> &mut f64;
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -63,13 +72,13 @@ impl Rateable for Rating {
} }
} }
impl Rateable for Gaussian { impl RateableMut for Rating {
fn mu(&self) -> f64 { fn mu_mut(&mut self) -> &mut f64 {
self.mu() &mut self.mu
} }
fn sigma(&self) -> f64 { fn sigma_mut(&mut self) -> &mut f64 {
self.sigma() &mut self.sigma
} }
} }
@@ -77,236 +86,262 @@ fn draw_margin(p: f64, beta: f64, total_players: f64) -> f64 {
math::icdf((p + 1.0) / 2.0) * total_players.sqrt() * beta math::icdf((p + 1.0) / 2.0) * total_players.sqrt() * beta
} }
pub fn rate<R>(ratings: &[(R, u16)], ranks: &[u16], min_delta: f64) -> Vec<Rating> pub struct TrueSkill {
where mu: f64,
R: Rateable, sigma: f64,
{ beta: f64,
// TODO Validate rating_groups is orderded in teams. tau: f64,
// TODO Validate that teams are orderd after rank. draw_probability: f64,
delta: f64,
}
let tau_sqr = TAU.powi(2); impl TrueSkill {
let beta_sqr = BETA.powi(2); pub fn rate<R>(&self, ratings: &[(R, u16)], ranks: &[u16], min_delta: f64) -> Vec<Rating>
where
R: Rateable,
{
// TODO Validate ratings is orderded in teams.
// TODO Validate that teams are orderd after rank.
let mut variable_arena = VariableArena::new(); let tau_sqr = self.tau.powi(2);
let beta_sqr = self.beta.powi(2);
let rating_count = ratings.len(); let mut variable_arena = VariableArena::new();
let team_count = ranks.len();
let rating_vars = (0..rating_count) let rating_count = ratings.len();
.map(|_| variable_arena.create()) let team_count = ranks.len();
.collect::<Vec<_>>();
let perf_vars = ratings
.iter()
.map(|(_, team)| (variable_arena.create(), *team))
.collect::<Vec<_>>();
let team_perf_vars = (0..team_count) let rating_vars = (0..rating_count)
.map(|_| variable_arena.create()) .map(|_| variable_arena.create())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let team_diff_vars = (0..team_count - 1)
.map(|_| variable_arena.create())
.collect::<Vec<_>>();
let mut factor_id = 0; let perf_vars = ratings
.iter()
.map(|(_, team)| (variable_arena.create(), *team))
.collect::<Vec<_>>();
let rating_layer = rating_vars let team_perf_vars = (0..team_count)
.iter() .map(|_| variable_arena.create())
.zip(ratings.iter().map(|(rating, _)| rating)) .collect::<Vec<_>>();
.map(|(rating_var, rating)| {
let gaussian =
Gaussian::from_mu_sigma(rating.mu(), (rating.sigma().powi(2) + tau_sqr).sqrt());
factor_id += 1; let team_diff_vars = (0..team_count - 1)
.map(|_| variable_arena.create())
.collect::<Vec<_>>();
PriorFactor::new(&mut variable_arena, factor_id, *rating_var, gaussian) let mut factor_id = 0;
})
.collect::<Vec<_>>();
let perf_layer = rating_vars let rating_layer = rating_vars
.iter() .iter()
.zip(perf_vars.iter().map(|(variable, _)| variable)) .zip(ratings.iter().map(|(rating, _)| rating))
.map(|(rating_var, perf)| { .map(|(rating_var, rating)| {
factor_id += 1; let gaussian =
Gaussian::from_mu_sigma(rating.mu(), (rating.sigma().powi(2) + tau_sqr).sqrt());
LikelihoodFactor::new(&mut variable_arena, factor_id, *rating_var, *perf, beta_sqr) factor_id += 1;
})
.collect::<Vec<_>>();
let team_perf_layer = team_perf_vars PriorFactor::new(&mut variable_arena, factor_id, *rating_var, gaussian)
.iter() })
.enumerate() .collect::<Vec<_>>();
.map(|(i, variable)| {
factor_id += 1;
let team = perf_vars let perf_layer = rating_vars
.iter() .iter()
.filter(|(_, team)| *team as usize == i) .zip(perf_vars.iter().map(|(variable, _)| variable))
.map(|(variable, _)| *variable) .map(|(rating_var, perf)| {
.collect::<Vec<_>>(); factor_id += 1;
let team_count = team.len(); LikelihoodFactor::new(&mut variable_arena, factor_id, *rating_var, *perf, beta_sqr)
})
.collect::<Vec<_>>();
SumFactor::new( let team_perf_layer = team_perf_vars
&mut variable_arena, .iter()
factor_id, .enumerate()
*variable, .map(|(i, variable)| {
team, factor_id += 1;
vec![1.0; team_count],
)
})
.collect::<Vec<_>>();
let team_diff_layer = team_diff_vars let team = perf_vars
.iter() .iter()
.zip(team_perf_vars.windows(2)) .filter(|(_, team)| *team as usize == i)
.map(|(variable, teams)| { .map(|(variable, _)| *variable)
factor_id += 1; .collect::<Vec<_>>();
SumFactor::new( let team_count = team.len();
&mut variable_arena,
factor_id,
*variable,
teams.to_vec(),
vec![1.0, -1.0],
)
})
.collect::<Vec<_>>();
let trunc_layer = team_diff_vars SumFactor::new(
.iter() &mut variable_arena,
.enumerate() factor_id,
.map(|(i, variable)| { *variable,
factor_id += 1; team,
vec![1.0; team_count],
)
})
.collect::<Vec<_>>();
let player_count = perf_vars let team_diff_layer = team_diff_vars
.iter() .iter()
.filter(|(_, team)| *team as usize == i || *team as usize == i + 1) .zip(team_perf_vars.windows(2))
.count(); .map(|(variable, teams)| {
factor_id += 1;
TruncateFactor::new( SumFactor::new(
&mut variable_arena, &mut variable_arena,
factor_id, factor_id,
*variable, *variable,
draw_margin(DRAW_PROBABILITY, BETA, player_count as f64), teams.to_vec(),
ranks[i] == ranks[i + 1], vec![1.0, -1.0],
) )
}) })
.collect::<Vec<_>>();; .collect::<Vec<_>>();
for factor in &rating_layer { let trunc_layer = team_diff_vars
factor.start(&mut variable_arena); .iter()
} .enumerate()
.map(|(i, variable)| {
factor_id += 1;
for factor in &perf_layer { let player_count = perf_vars
factor.update_value(&mut variable_arena); .iter()
} .filter(|(_, team)| *team as usize == i || *team as usize == i + 1)
.count();
for factor in &team_perf_layer { TruncateFactor::new(
factor.update_sum(&mut variable_arena); &mut variable_arena,
} factor_id,
*variable,
draw_margin(self.draw_probability, self.beta, player_count as f64),
ranks[i] == ranks[i + 1],
)
})
.collect::<Vec<_>>();;
for _ in 0..10 { for factor in &rating_layer {
let mut delta = 0.0; factor.start(&mut variable_arena);
}
for factor in &team_diff_layer { for factor in &perf_layer {
factor.update_value(&mut variable_arena);
}
for factor in &team_perf_layer {
factor.update_sum(&mut variable_arena); factor.update_sum(&mut variable_arena);
} }
for factor in &trunc_layer { for _ in 0..10 {
let d = factor.update(&mut variable_arena); let mut delta = 0.0;
if d > delta { for factor in &team_diff_layer {
delta = d; factor.update_sum(&mut variable_arena);
}
for factor in &trunc_layer {
let d = factor.update(&mut variable_arena);
if d > delta {
delta = d;
}
}
for factor in &team_diff_layer {
factor.update_term(&mut variable_arena, 0);
factor.update_term(&mut variable_arena, 1);
}
if delta < min_delta {
break;
} }
} }
for factor in &team_diff_layer { for factor in &team_perf_layer {
factor.update_term(&mut variable_arena, 0); factor.update_all_terms(&mut variable_arena);
factor.update_term(&mut variable_arena, 1);
} }
if delta < min_delta { for factor in &perf_layer {
break; factor.update_mean(&mut variable_arena);
} }
rating_vars
.iter()
.map(|variable| variable_arena.get(*variable).unwrap().get_value())
.map(|value| Rating {
mu: value.mu(),
sigma: value.sigma(),
})
.collect::<Vec<_>>()
} }
for factor in &team_perf_layer { pub fn quality<R>(&self, rating_groups: &[&[R]]) -> f64
factor.update_all_terms(&mut variable_arena); where
} R: Rateable,
{
let flatten_ratings = rating_groups
.iter()
.flat_map(|group| group.iter())
.collect::<Vec<_>>();
for factor in &perf_layer { let flatten_weights = vec![1.0; flatten_ratings.len()].into_boxed_slice();
factor.update_mean(&mut variable_arena);
}
rating_vars let length = flatten_ratings.len();
.iter()
.map(|variable| variable_arena.get(*variable).unwrap().get_value()) let mut mean_matrix = Matrix::new(length, 1);
.map(|value| Rating {
mu: value.mu(), for (i, rating) in flatten_ratings.iter().enumerate() {
sigma: value.sigma(), mean_matrix[(i, 0)] = rating.mu();
}) }
.collect::<Vec<_>>()
let mut variance_matrix = Matrix::new(length, length);
for (i, rating) in flatten_ratings.iter().enumerate() {
variance_matrix[(i, i)] = rating.sigma().powi(2);
}
let mut rotated_a_matrix = Matrix::new(rating_groups.len() - 1, length);
let mut t = 0;
let mut x = 0;
for (row, group) in rating_groups.windows(2).enumerate() {
let current = group[0];
let next = group[1];
for n in t..t + current.len() {
rotated_a_matrix[(row, n)] = flatten_weights[n];
t += 1;
x += 1;
}
for n in x..x + next.len() {
rotated_a_matrix[(row, n)] = -flatten_weights[n];
x += 1;
}
}
let a_matrix = rotated_a_matrix.transpose();
let _ata = self.beta.powi(2) * &rotated_a_matrix * &a_matrix;
let _atsa = &rotated_a_matrix * &variance_matrix * &a_matrix;
let start = mean_matrix.transpose() * &a_matrix;
let middle = &_ata + &_atsa;
let end = &rotated_a_matrix * &mean_matrix;
let e_arg = (-0.5 * &start * &middle.inverse() * &end).determinant();
let s_arg = _ata.determinant() / middle.determinant();
e_arg.exp() * s_arg.sqrt()
}
} }
pub fn quality<R>(rating_groups: &[&[R]]) -> f64 impl Default for TrueSkill {
where fn default() -> TrueSkill {
R: Rateable, TrueSkill {
{ mu: MU,
let flatten_ratings = rating_groups sigma: SIGMA,
.iter() beta: BETA,
.flat_map(|group| group.iter()) tau: TAU,
.collect::<Vec<_>>(); draw_probability: DRAW_PROBABILITY,
delta: DELTA,
let flatten_weights = vec![1.0; flatten_ratings.len()].into_boxed_slice();
let length = flatten_ratings.len();
let mut mean_matrix = Matrix::new(length, 1);
for (i, rating) in flatten_ratings.iter().enumerate() {
mean_matrix[(i, 0)] = rating.mu();
}
let mut variance_matrix = Matrix::new(length, length);
for (i, rating) in flatten_ratings.iter().enumerate() {
variance_matrix[(i, i)] = rating.sigma().powi(2);
}
let mut rotated_a_matrix = Matrix::new(rating_groups.len() - 1, length);
let mut t = 0;
let mut x = 0;
for (row, group) in rating_groups.windows(2).enumerate() {
let current = group[0];
let next = group[1];
for n in t..t + current.len() {
rotated_a_matrix[(row, n)] = flatten_weights[n];
t += 1;
x += 1;
}
for n in x..x + next.len() {
rotated_a_matrix[(row, n)] = -flatten_weights[n];
x += 1;
} }
} }
let a_matrix = rotated_a_matrix.transpose();
let _ata = BETA.powi(2) * &rotated_a_matrix * &a_matrix;
let _atsa = &rotated_a_matrix * &variance_matrix * &a_matrix;
let start = mean_matrix.transpose() * &a_matrix;
let middle = &_ata + &_atsa;
let end = &rotated_a_matrix * &mean_matrix;
let e_arg = (-0.5 * &start * &middle.inverse() * &end).determinant();
let s_arg = _ata.determinant() / middle.determinant();
e_arg.exp() * s_arg.sqrt()
} }
#[cfg(test)] #[cfg(test)]
@@ -360,11 +395,13 @@ mod tests {
#[test] #[test]
fn test_quality_1vs1() { fn test_quality_1vs1() {
let ts = TrueSkill::default();
let alice = Rating::new(MU, SIGMA); let alice = Rating::new(MU, SIGMA);
let bob = Rating::new(MU, SIGMA); let bob = Rating::new(MU, SIGMA);
assert_relative_eq!( assert_relative_eq!(
quality(&[&[alice], &[bob]]), ts.quality(&[&[alice], &[bob]]),
0.4472135954999579, 0.4472135954999579,
epsilon = EPSILON epsilon = EPSILON
); );
@@ -372,10 +409,9 @@ mod tests {
#[test] #[test]
fn test_rate_4_free_for_all() { fn test_rate_4_free_for_all() {
let alice = Rating::new(MU, SIGMA); let ts = TrueSkill::default();
let bob = Rating::new(MU, SIGMA);
let chris = Rating::new(MU, SIGMA); let (ratings, ranks) = generate_free_for_all(4);
let darren = Rating::new(MU, SIGMA);
let expected_ratings = vec![ let expected_ratings = vec![
Rating::new(33.20668089876779, 6.34810941351329), Rating::new(33.20668089876779, 6.34810941351329),
@@ -384,11 +420,7 @@ mod tests {
Rating::new(16.79331910018712, 6.34810938603116), Rating::new(16.79331910018712, 6.34810938603116),
]; ];
let ratings = rate( let ratings = ts.rate(ratings.as_ref(), ranks.as_ref(), DELTA);
&[(alice, 0), (bob, 1), (chris, 2), (darren, 3)],
&[0, 1, 2, 3],
DELTA,
);
for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) { for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) {
assert_relative_eq!(rating, expected, epsilon = EPSILON); assert_relative_eq!(rating, expected, epsilon = EPSILON);
@@ -398,6 +430,7 @@ mod tests {
#[test] #[test]
fn test_rate_8_free_for_all() { fn test_rate_8_free_for_all() {
// Example from http://research.microsoft.com/en-us/projects/trueskill/details.aspx // Example from http://research.microsoft.com/en-us/projects/trueskill/details.aspx
let ts = TrueSkill::default();
let (ratings, ranks) = generate_free_for_all(8); let (ratings, ranks) = generate_free_for_all(8);
@@ -412,7 +445,7 @@ mod tests {
Rating::new(13.22891063797913, 5.74928289201801), Rating::new(13.22891063797913, 5.74928289201801),
]; ];
let ratings = rate(ratings.as_ref(), ranks.as_ref(), DELTA); let ratings = ts.rate(ratings.as_ref(), ranks.as_ref(), DELTA);
for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) { for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) {
assert_relative_eq!(rating, expected, epsilon = EPSILON); assert_relative_eq!(rating, expected, epsilon = EPSILON);
@@ -421,6 +454,8 @@ mod tests {
#[test] #[test]
fn test_rate_8_free_for_all_draw() { fn test_rate_8_free_for_all_draw() {
let ts = TrueSkill::default();
let (ratings, ranks) = generate_free_for_all_draw(8); let (ratings, ranks) = generate_free_for_all_draw(8);
let expected_ratings = vec![ let expected_ratings = vec![
@@ -434,7 +469,7 @@ mod tests {
Rating::new(25.00000000000000, 4.59217372356178), Rating::new(25.00000000000000, 4.59217372356178),
]; ];
let ratings = rate(ratings.as_ref(), ranks.as_ref(), DELTA); let ratings = ts.rate(ratings.as_ref(), ranks.as_ref(), DELTA);
for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) { for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) {
assert_relative_eq!(rating, expected, epsilon = EPSILON); assert_relative_eq!(rating, expected, epsilon = EPSILON);
@@ -443,6 +478,8 @@ mod tests {
#[test] #[test]
fn test_rate_16_free_for_all() { fn test_rate_16_free_for_all() {
let ts = TrueSkill::default();
let (ratings, ranks) = generate_free_for_all(16); let (ratings, ranks) = generate_free_for_all(16);
let expected_ratings = vec![ let expected_ratings = vec![
@@ -464,7 +501,7 @@ mod tests {
Rating::new(9.461957485668828, 5.27776869816230), Rating::new(9.461957485668828, 5.27776869816230),
]; ];
let ratings = rate(ratings.as_ref(), ranks.as_ref(), DELTA); let ratings = ts.rate(ratings.as_ref(), ranks.as_ref(), DELTA);
for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) { for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) {
assert_relative_eq!(rating, expected, epsilon = EPSILON); assert_relative_eq!(rating, expected, epsilon = EPSILON);
@@ -473,6 +510,8 @@ mod tests {
#[test] #[test]
fn test_rate_1vs1_draw() { fn test_rate_1vs1_draw() {
let ts = TrueSkill::default();
let alice = Rating::new(MU, SIGMA); let alice = Rating::new(MU, SIGMA);
let bob = Rating::new(MU, SIGMA); let bob = Rating::new(MU, SIGMA);
@@ -481,7 +520,7 @@ mod tests {
Rating::new(25.00000000000000, 6.45751568324505), Rating::new(25.00000000000000, 6.45751568324505),
]; ];
let ratings = rate(&[(alice, 0), (bob, 1)], &[0, 0], DELTA); let ratings = ts.rate(&[(alice, 0), (bob, 1)], &[0, 0], DELTA);
for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) { for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) {
assert_relative_eq!(rating, expected, epsilon = EPSILON); assert_relative_eq!(rating, expected, epsilon = EPSILON);
@@ -490,6 +529,8 @@ mod tests {
#[test] #[test]
fn test_rate_2vs2() { fn test_rate_2vs2() {
let ts = TrueSkill::default();
let alice = Rating::new(MU, SIGMA); let alice = Rating::new(MU, SIGMA);
let bob = Rating::new(MU, SIGMA); let bob = Rating::new(MU, SIGMA);
let chris = Rating::new(MU, SIGMA); let chris = Rating::new(MU, SIGMA);
@@ -502,7 +543,7 @@ mod tests {
Rating::new(21.89167760093095, 7.77436345109384), Rating::new(21.89167760093095, 7.77436345109384),
]; ];
let ratings = rate( let ratings = ts.rate(
&[(alice, 0), (bob, 0), (chris, 1), (darren, 1)], &[(alice, 0), (bob, 0), (chris, 1), (darren, 1)],
&[0, 1], &[0, 1],
DELTA, DELTA,
@@ -515,6 +556,8 @@ mod tests {
#[test] #[test]
fn test_rate_4vs4() { fn test_rate_4vs4() {
let ts = TrueSkill::default();
let alice = Rating::new(MU, SIGMA); let alice = Rating::new(MU, SIGMA);
let bob = Rating::new(MU, SIGMA); let bob = Rating::new(MU, SIGMA);
let chris = Rating::new(MU, SIGMA); let chris = Rating::new(MU, SIGMA);
@@ -535,7 +578,7 @@ mod tests {
Rating::new(22.80208415350423, 8.05891171184399), Rating::new(22.80208415350423, 8.05891171184399),
]; ];
let ratings = rate( let ratings = ts.rate(
&[ &[
(alice, 0), (alice, 0),
(bob, 0), (bob, 0),
@@ -557,6 +600,8 @@ mod tests {
#[test] #[test]
fn test_rate_sublee_trueskill_issue_3_case_1() { fn test_rate_sublee_trueskill_issue_3_case_1() {
let ts = TrueSkill::default();
let ratings = vec![ let ratings = vec![
(Rating::new(42.234, 3.728), 0), (Rating::new(42.234, 3.728), 0),
(Rating::new(43.290, 3.842), 0), (Rating::new(43.290, 3.842), 0),
@@ -595,7 +640,7 @@ mod tests {
Rating::new(16.54109749124066, 0.50668947816812), Rating::new(16.54109749124066, 0.50668947816812),
]; ];
let ratings = rate(ratings.as_ref(), &[0, 1], DELTA); let ratings = ts.rate(ratings.as_ref(), &[0, 1], DELTA);
for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) { for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) {
assert_relative_eq!(rating, expected, epsilon = EPSILON); assert_relative_eq!(rating, expected, epsilon = EPSILON);
@@ -604,7 +649,7 @@ mod tests {
#[test] #[test]
fn test_rate_sublee_trueskill_issue_3_case_2() { fn test_rate_sublee_trueskill_issue_3_case_2() {
let _ = env_logger::try_init(); let ts = TrueSkill::default();
let ratings = vec![ let ratings = vec![
(Rating::new(25.000, 0.500), 0), (Rating::new(25.000, 0.500), 0),
@@ -644,7 +689,7 @@ mod tests {
Rating::new(43.29000000000000, 3.84290364756188), Rating::new(43.29000000000000, 3.84290364756188),
]; ];
let ratings = rate(ratings.as_ref(), &[0, 1], DELTA); let ratings = ts.rate(ratings.as_ref(), &[0, 1], DELTA);
for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) { for (rating, expected) in ratings.iter().zip(expected_ratings.iter()) {
assert_relative_eq!(rating, expected, epsilon = EPSILON); assert_relative_eq!(rating, expected, epsilon = EPSILON);