Added a TrueSkill struct that holds default config.
This commit is contained in:
125
src/lib.rs
125
src/lib.rs
@@ -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,15 +86,25 @@ 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,
|
||||||
|
sigma: f64,
|
||||||
|
beta: f64,
|
||||||
|
tau: f64,
|
||||||
|
draw_probability: f64,
|
||||||
|
delta: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrueSkill {
|
||||||
|
pub fn rate<R>(&self, ratings: &[(R, u16)], ranks: &[u16], min_delta: f64) -> Vec<Rating>
|
||||||
|
where
|
||||||
R: Rateable,
|
R: Rateable,
|
||||||
{
|
{
|
||||||
// TODO Validate rating_groups is orderded in teams.
|
// TODO Validate ratings is orderded in teams.
|
||||||
// TODO Validate that teams are orderd after rank.
|
// TODO Validate that teams are orderd after rank.
|
||||||
|
|
||||||
let tau_sqr = TAU.powi(2);
|
let tau_sqr = self.tau.powi(2);
|
||||||
let beta_sqr = BETA.powi(2);
|
let beta_sqr = self.beta.powi(2);
|
||||||
|
|
||||||
let mut variable_arena = VariableArena::new();
|
let mut variable_arena = VariableArena::new();
|
||||||
|
|
||||||
@@ -95,6 +114,7 @@ where
|
|||||||
let rating_vars = (0..rating_count)
|
let rating_vars = (0..rating_count)
|
||||||
.map(|_| variable_arena.create())
|
.map(|_| variable_arena.create())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let perf_vars = ratings
|
let perf_vars = ratings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, team)| (variable_arena.create(), *team))
|
.map(|(_, team)| (variable_arena.create(), *team))
|
||||||
@@ -103,6 +123,7 @@ where
|
|||||||
let team_perf_vars = (0..team_count)
|
let team_perf_vars = (0..team_count)
|
||||||
.map(|_| variable_arena.create())
|
.map(|_| variable_arena.create())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let team_diff_vars = (0..team_count - 1)
|
let team_diff_vars = (0..team_count - 1)
|
||||||
.map(|_| variable_arena.create())
|
.map(|_| variable_arena.create())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -187,7 +208,7 @@ where
|
|||||||
&mut variable_arena,
|
&mut variable_arena,
|
||||||
factor_id,
|
factor_id,
|
||||||
*variable,
|
*variable,
|
||||||
draw_margin(DRAW_PROBABILITY, BETA, player_count as f64),
|
draw_margin(self.draw_probability, self.beta, player_count as f64),
|
||||||
ranks[i] == ranks[i + 1],
|
ranks[i] == ranks[i + 1],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -246,12 +267,12 @@ where
|
|||||||
sigma: value.sigma(),
|
sigma: value.sigma(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn quality<R>(rating_groups: &[&[R]]) -> f64
|
pub fn quality<R>(&self, rating_groups: &[&[R]]) -> f64
|
||||||
where
|
where
|
||||||
R: Rateable,
|
R: Rateable,
|
||||||
{
|
{
|
||||||
let flatten_ratings = rating_groups
|
let flatten_ratings = rating_groups
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|group| group.iter())
|
.flat_map(|group| group.iter())
|
||||||
@@ -296,7 +317,7 @@ where
|
|||||||
|
|
||||||
let a_matrix = rotated_a_matrix.transpose();
|
let a_matrix = rotated_a_matrix.transpose();
|
||||||
|
|
||||||
let _ata = BETA.powi(2) * &rotated_a_matrix * &a_matrix;
|
let _ata = self.beta.powi(2) * &rotated_a_matrix * &a_matrix;
|
||||||
let _atsa = &rotated_a_matrix * &variance_matrix * &a_matrix;
|
let _atsa = &rotated_a_matrix * &variance_matrix * &a_matrix;
|
||||||
|
|
||||||
let start = mean_matrix.transpose() * &a_matrix;
|
let start = mean_matrix.transpose() * &a_matrix;
|
||||||
@@ -307,6 +328,20 @@ where
|
|||||||
let s_arg = _ata.determinant() / middle.determinant();
|
let s_arg = _ata.determinant() / middle.determinant();
|
||||||
|
|
||||||
e_arg.exp() * s_arg.sqrt()
|
e_arg.exp() * s_arg.sqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TrueSkill {
|
||||||
|
fn default() -> TrueSkill {
|
||||||
|
TrueSkill {
|
||||||
|
mu: MU,
|
||||||
|
sigma: SIGMA,
|
||||||
|
beta: BETA,
|
||||||
|
tau: TAU,
|
||||||
|
draw_probability: DRAW_PROBABILITY,
|
||||||
|
delta: DELTA,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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);
|
||||||
|
|||||||
Reference in New Issue
Block a user