#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{trueskill::TrueSkillRating, MultiTeamOutcome, Outcomes};
use std::cmp::Ordering;
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WengLinRating {
pub rating: f64,
pub uncertainty: f64,
}
impl WengLinRating {
#[must_use]
pub fn new() -> Self {
Self {
rating: 25.0,
uncertainty: 25.0 / 3.0,
}
}
}
impl Default for WengLinRating {
fn default() -> Self {
Self::new()
}
}
impl From<(f64, f64)> for WengLinRating {
fn from((r, u): (f64, f64)) -> Self {
Self {
rating: r,
uncertainty: u,
}
}
}
impl From<TrueSkillRating> for WengLinRating {
fn from(t: TrueSkillRating) -> Self {
Self {
rating: t.rating,
uncertainty: t.uncertainty,
}
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WengLinConfig {
pub beta: f64,
pub uncertainty_tolerance: f64,
}
impl WengLinConfig {
#[must_use]
pub fn new() -> Self {
Self {
beta: 25.0 / 6.0,
uncertainty_tolerance: 0.000_001,
}
}
}
impl Default for WengLinConfig {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn weng_lin(
player_one: &WengLinRating,
player_two: &WengLinRating,
outcome: &Outcomes,
config: &WengLinConfig,
) -> (WengLinRating, WengLinRating) {
let c = 2.0f64
.mul_add(
config.beta.powi(2),
player_one
.uncertainty
.mul_add(player_one.uncertainty, player_two.uncertainty.powi(2)),
)
.sqrt();
let (p1, p2) = p_value(player_one.rating, player_two.rating, c);
let outcome1 = outcome.to_chess_points();
let outcome2 = 1.0 - outcome1;
let new_rating1 = new_rating(player_one.rating, player_one.uncertainty, c, p1, outcome1);
let new_rating2 = new_rating(player_two.rating, player_two.uncertainty, c, p2, outcome2);
let new_uncertainty1 =
new_uncertainty(player_one.uncertainty, c, p1, config.uncertainty_tolerance);
let new_uncertainty2 =
new_uncertainty(player_two.uncertainty, c, p2, config.uncertainty_tolerance);
(
WengLinRating {
rating: new_rating1,
uncertainty: new_uncertainty1,
},
WengLinRating {
rating: new_rating2,
uncertainty: new_uncertainty2,
},
)
}
#[must_use]
pub fn weng_lin_rating_period(
player: &WengLinRating,
results: &[(WengLinRating, Outcomes)],
config: &WengLinConfig,
) -> WengLinRating {
let mut player_rating = player.rating;
let mut player_uncertainty = player.uncertainty;
for (opponent, result) in results {
let c = 2.0f64
.mul_add(
config.beta.powi(2),
player_uncertainty.mul_add(player_uncertainty, opponent.uncertainty.powi(2)),
)
.sqrt();
let (p, _) = p_value(player_rating, opponent.rating, c);
let outcome = result.to_chess_points();
player_rating = new_rating(player_rating, player_uncertainty, c, p, outcome);
player_uncertainty =
new_uncertainty(player_uncertainty, c, p, config.uncertainty_tolerance);
}
WengLinRating {
rating: player_rating,
uncertainty: player_uncertainty,
}
}
#[must_use]
pub fn weng_lin_two_teams(
team_one: &[WengLinRating],
team_two: &[WengLinRating],
outcome: &Outcomes,
config: &WengLinConfig,
) -> (Vec<WengLinRating>, Vec<WengLinRating>) {
if team_one.is_empty() || team_two.is_empty() {
return (team_one.to_vec(), team_two.to_vec());
}
let team_one_rating: f64 = team_one.iter().map(|p| p.rating).sum();
let team_two_rating: f64 = team_two.iter().map(|p| p.rating).sum();
let team_one_uncertainty_sq: f64 = team_one.iter().map(|p| p.uncertainty.powi(2)).sum();
let team_two_uncertainty_sq: f64 = team_two.iter().map(|p| p.uncertainty.powi(2)).sum();
let c = 2.0f64
.mul_add(
config.beta.powi(2),
team_one_uncertainty_sq + team_two_uncertainty_sq,
)
.sqrt();
let (p1, p2) = p_value(team_one_rating, team_two_rating, c);
let outcome1 = outcome.to_chess_points();
let outcome2 = 1.0 - outcome1;
let team_one_small_delta = small_delta(team_one_uncertainty_sq, c, p1, outcome1);
let team_two_small_delta = small_delta(team_two_uncertainty_sq, c, p2, outcome2);
let team_one_eta = eta(
team_one_uncertainty_sq,
c,
p1,
gamma(team_one_uncertainty_sq, c),
);
let team_two_eta = eta(
team_two_uncertainty_sq,
c,
p2,
gamma(team_two_uncertainty_sq, c),
);
let mut new_team_one = Vec::new();
let mut new_team_two = Vec::new();
for player in team_one {
let player_uncertainty_sq = player.uncertainty.powi(2);
let new_rating = new_rating_teams(
player.rating,
player_uncertainty_sq,
team_one_uncertainty_sq,
team_one_small_delta,
);
let new_uncertainty = new_uncertainty_teams(
player_uncertainty_sq,
team_one_uncertainty_sq,
config.uncertainty_tolerance,
team_one_eta,
);
new_team_one.push(WengLinRating {
rating: new_rating,
uncertainty: new_uncertainty,
});
}
for player in team_two {
let player_uncertainty_sq = player.uncertainty.powi(2);
let new_rating = new_rating_teams(
player.rating,
player_uncertainty_sq,
team_two_uncertainty_sq,
team_two_small_delta,
);
let new_uncertainty = new_uncertainty_teams(
player_uncertainty_sq,
team_two_uncertainty_sq,
config.uncertainty_tolerance,
team_two_eta,
);
new_team_two.push(WengLinRating {
rating: new_rating,
uncertainty: new_uncertainty,
});
}
(new_team_one, new_team_two)
}
#[must_use]
pub fn weng_lin_multi_team(
teams_and_ranks: &[(&[WengLinRating], MultiTeamOutcome)],
config: &WengLinConfig,
) -> Vec<Vec<WengLinRating>> {
if teams_and_ranks.is_empty() {
return Vec::new();
}
for (team, _) in teams_and_ranks {
if team.is_empty() {
return teams_and_ranks
.iter()
.map(|(team, _)| team.to_vec())
.collect();
}
}
let mut teams_ratings = Vec::with_capacity(teams_and_ranks.len());
let mut teams_uncertainties_sq = Vec::with_capacity(teams_and_ranks.len());
for (team, _) in teams_and_ranks {
let team_rating: f64 = team.iter().map(|p| p.rating).sum();
let team_uncertainty_sq: f64 = team.iter().map(|p| p.uncertainty.powi(2)).sum();
teams_ratings.push(team_rating);
teams_uncertainties_sq.push(team_uncertainty_sq);
}
let mut new_teams = Vec::with_capacity(teams_and_ranks.len());
for (i, (team_one, rank_one)) in teams_and_ranks.iter().enumerate() {
let mut omega = 0.0;
let mut large_delta = 0.0;
for (q, (_, rank_two)) in teams_and_ranks.iter().enumerate() {
if i == q {
continue;
}
let c = 2.0f64
.mul_add(
config.beta.powi(2),
teams_uncertainties_sq[i] + teams_uncertainties_sq[q],
)
.sqrt();
let (p, _) = p_value(teams_ratings[i], teams_ratings[q], c);
let score = match rank_two.cmp(rank_one) {
Ordering::Greater => 1.0,
Ordering::Equal => 0.5,
Ordering::Less => 0.0,
};
let small_delta = small_delta(teams_uncertainties_sq[i], c, p, score);
let eta = eta(
teams_uncertainties_sq[i],
c,
p,
gamma(teams_uncertainties_sq[i], c),
);
omega += small_delta;
large_delta += eta;
}
let mut new_team = Vec::with_capacity(team_one.len());
for player in *team_one {
let player_uncertainty_sq = player.uncertainty.powi(2);
let new_rating = new_rating_teams(
player.rating,
player_uncertainty_sq,
teams_uncertainties_sq[i],
omega,
);
let new_uncertainty = new_uncertainty_teams(
player_uncertainty_sq,
teams_uncertainties_sq[i],
config.uncertainty_tolerance,
large_delta,
);
new_team.push(WengLinRating {
rating: new_rating,
uncertainty: new_uncertainty,
});
}
new_teams.push(new_team);
}
new_teams
}
#[must_use]
pub fn expected_score(
player_one: &WengLinRating,
player_two: &WengLinRating,
config: &WengLinConfig,
) -> (f64, f64) {
let c = 2.0f64
.mul_add(
config.beta.powi(2),
player_one
.uncertainty
.mul_add(player_one.uncertainty, player_two.uncertainty.powi(2)),
)
.sqrt();
p_value(player_one.rating, player_two.rating, c)
}
#[must_use]
pub fn expected_score_two_teams(
team_one: &[WengLinRating],
team_two: &[WengLinRating],
config: &WengLinConfig,
) -> (f64, f64) {
let team_one_rating: f64 = team_one.iter().map(|p| p.rating).sum();
let team_two_rating: f64 = team_two.iter().map(|p| p.rating).sum();
let team_one_uncertainty_sq: f64 = team_one.iter().map(|p| p.uncertainty.powi(2)).sum();
let team_two_uncertainty_sq: f64 = team_two.iter().map(|p| p.uncertainty.powi(2)).sum();
let c = 2.0f64
.mul_add(
config.beta.powi(2),
team_one_uncertainty_sq + team_two_uncertainty_sq,
)
.sqrt();
p_value(team_one_rating, team_two_rating, c)
}
#[must_use]
pub fn expected_score_multi_team(teams: &[&[WengLinRating]], config: &WengLinConfig) -> Vec<f64> {
let mut ratings = Vec::with_capacity(teams.len());
for team in teams {
let team_rating: f64 = team.iter().map(|p| p.rating).sum();
ratings.push(team_rating);
}
let mut uncertainties_sq = Vec::with_capacity(teams.len());
for team in teams {
let team_uncertainty_sq: f64 = team.iter().map(|p| p.uncertainty.powi(2)).sum();
uncertainties_sq.push(team_uncertainty_sq);
}
let c = 2.0f64
.mul_add(config.beta.powi(2), uncertainties_sq.iter().sum::<f64>())
.sqrt();
let mut exps = Vec::with_capacity(ratings.len());
let mut sum = 0.0;
for rating in ratings {
let e = (rating / c).exp();
exps.push(e);
sum += e;
}
for exp in &mut exps {
*exp /= sum;
}
exps
}
fn p_value(rating_one: f64, rating_two: f64, c_value: f64) -> (f64, f64) {
let e1 = (rating_one / c_value).exp();
let e2 = (rating_two / c_value).exp();
let exp_one = e1 / (e1 + e2);
let exp_two = 1.0 - exp_one;
(exp_one, exp_two)
}
fn small_delta(team_uncertainty_sq: f64, c_value: f64, p_value: f64, score: f64) -> f64 {
(team_uncertainty_sq / c_value) * (score - p_value)
}
fn gamma(team_uncertainty_sq: f64, c_value: f64) -> f64 {
team_uncertainty_sq.sqrt() / c_value
}
fn eta(team_uncertainty_sq: f64, c_value: f64, p_value: f64, gamma: f64) -> f64 {
gamma * team_uncertainty_sq / c_value.powi(2) * p_value * (1.0 - p_value)
}
fn new_rating(
player_rating: f64,
player_uncertainty: f64,
c_value: f64,
p_value: f64,
score: f64,
) -> f64 {
(player_uncertainty.powi(2) / c_value).mul_add(score - p_value, player_rating)
}
fn new_uncertainty(
player_uncertainty: f64,
c_value: f64,
p_value: f64,
uncertainty_tolerance: f64,
) -> f64 {
let eta = (player_uncertainty / c_value).powi(3) * p_value * (1.0 - p_value);
(player_uncertainty.powi(2) * (1.0 - eta).max(uncertainty_tolerance)).sqrt()
}
fn new_rating_teams(
player_rating: f64,
player_uncertainty_sq: f64,
team_uncertainty_sq: f64,
omega: f64,
) -> f64 {
(player_uncertainty_sq / team_uncertainty_sq).mul_add(omega, player_rating)
}
fn new_uncertainty_teams(
player_uncertainty_sq: f64,
team_uncertainty_sq: f64,
uncertainty_tolerance: f64,
large_delta: f64,
) -> f64 {
let new_player_uncertainty_sq = (1.0
- ((player_uncertainty_sq / team_uncertainty_sq) * large_delta))
.max(uncertainty_tolerance);
(player_uncertainty_sq * new_player_uncertainty_sq).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weng() {
let p1 = WengLinRating::new();
let p2 = WengLinRating::new();
let (t1, t2) = weng_lin(&p1, &p2, &Outcomes::WIN, &WengLinConfig::new());
assert!((t1.rating - 27.635_231_383_473_65).abs() < f64::EPSILON);
assert!((t1.uncertainty - 8.065_506_316_323_548).abs() < f64::EPSILON);
assert!((t2.rating - 22.364_768_616_526_35).abs() < f64::EPSILON);
assert!((t2.uncertainty - 8.065_506_316_323_548).abs() < f64::EPSILON);
let p1 = WengLinRating {
rating: 42.0,
uncertainty: 1.3,
};
let p2 = WengLinRating::new();
let (t1, t2) = weng_lin(&p1, &p2, &Outcomes::WIN, &WengLinConfig::new());
assert!((t1.rating - 42.026_412_401_802_894).abs() < f64::EPSILON);
assert!((t1.uncertainty - 1.299_823_053_277_078_3).abs() < f64::EPSILON);
assert!((t2.rating - 23.914_677_769_440_46).abs() < f64::EPSILON);
assert!((t2.uncertainty - 8.029_022_445_649_298).abs() < f64::EPSILON);
let (t1, t2) = weng_lin(&p1, &p2, &Outcomes::LOSS, &WengLinConfig::new());
assert!((t1.rating - 41.862_153_998_286_94).abs() < f64::EPSILON);
assert!((t1.uncertainty - 1.299_823_053_277_078_3).abs() < f64::EPSILON);
assert!((t2.rating - 30.664_283_436_598_35).abs() < f64::EPSILON);
assert!((t2.uncertainty - 8.029_022_445_649_298).abs() < f64::EPSILON);
let (t1, t2) = weng_lin(&p1, &p2, &Outcomes::DRAW, &WengLinConfig::new());
assert!((t1.rating - 41.944_283_200_044_92).abs() < f64::EPSILON);
assert!((t1.uncertainty - 1.299_823_053_277_078_3).abs() < f64::EPSILON);
assert!((t2.rating - 27.289_480_603_019_403).abs() < f64::EPSILON);
assert!((t2.uncertainty - 8.029_022_445_649_298).abs() < f64::EPSILON);
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_weng_two_teams() {
let t1 = vec![
WengLinRating::new(),
WengLinRating {
rating: 30.0,
uncertainty: 1.2,
},
WengLinRating {
rating: 21.0,
uncertainty: 6.5,
},
];
let t2 = vec![
WengLinRating::default(),
WengLinRating {
rating: 41.0,
uncertainty: 1.4,
},
WengLinRating {
rating: 19.2,
uncertainty: 4.3,
},
];
let (nt1, nt2) = weng_lin_two_teams(&t1, &t2, &Outcomes::WIN, &WengLinConfig::new());
assert!((nt1[0].rating - 27.904_443_970_057_24).abs() < f64::EPSILON);
assert!((nt1[1].rating - 30.060_226_550_163_108).abs() < f64::EPSILON);
assert!((nt1[2].rating - 22.767_063_711_382_825).abs() < f64::EPSILON);
assert!((nt2[0].rating - 22.095_556_029_942_76).abs() < f64::EPSILON);
assert!((nt2[1].rating - 40.918_024_973_389_1).abs() < f64::EPSILON);
assert!((nt2[2].rating - 18.426_674_366_308_44).abs() < f64::EPSILON);
assert!((nt1[0].uncertainty - 8.138_803_466_450_47).abs() < f64::EPSILON);
assert!((nt1[1].uncertainty - 1.199_425_779_255_630_7).abs() < f64::EPSILON);
assert!((nt1[2].uncertainty - 6.408_113_466_768_933).abs() < f64::EPSILON);
assert!((nt2[0].uncertainty - 8.160_155_338_979_159).abs() < f64::EPSILON);
assert!((nt2[1].uncertainty - 1.399_187_149_975_365_4).abs() < f64::EPSILON);
assert!((nt2[2].uncertainty - 4.276_389_807_576_043).abs() < f64::EPSILON);
let (nt1, nt2) = weng_lin_two_teams(&t1, &t2, &Outcomes::DRAW, &WengLinConfig::new());
assert!((nt1[0].rating - 25.652_558_832_338_293).abs() < f64::EPSILON);
assert!((nt1[1].rating - 30.013_531_459_947_366).abs() < f64::EPSILON);
assert!((nt1[2].rating - 21.397_016_793_594_62).abs() < f64::EPSILON);
assert!((nt2[0].rating - 24.347_441_167_661_707).abs() < f64::EPSILON);
assert!((nt2[1].rating - 40.981_582_179_516_08).abs() < f64::EPSILON);
assert!((nt2[2].rating - 19.026_252_295_536_935).abs() < f64::EPSILON);
assert!((nt1[0].uncertainty - 8.138_803_466_450_47).abs() < f64::EPSILON);
let (nt1, nt2) = weng_lin_two_teams(&t1, &t2, &Outcomes::LOSS, &WengLinConfig::default());
assert!((nt1[0].rating - 23.400_673_694_619_35).abs() < f64::EPSILON);
assert!((nt1[1].rating - 29.966_836_369_731_627).abs() < f64::EPSILON);
assert!((nt1[2].rating - 20.026_969_875_806_41).abs() < f64::EPSILON);
assert!((nt2[0].rating - 26.599_326_305_380_65).abs() < f64::EPSILON);
assert!((nt2[1].rating - 41.045_139_385_643_06).abs() < f64::EPSILON);
assert!((nt2[2].rating - 19.625_830_224_765_43).abs() < f64::EPSILON);
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_weng_multi_team_two() {
let t1 = vec![
WengLinRating::new(),
WengLinRating {
rating: 30.0,
uncertainty: 1.2,
},
WengLinRating {
rating: 21.0,
uncertainty: 6.5,
},
];
let t2 = vec![
WengLinRating::default(),
WengLinRating {
rating: 41.0,
uncertainty: 1.4,
},
WengLinRating {
rating: 19.2,
uncertainty: 4.3,
},
];
let game = vec![
(&t1[..], MultiTeamOutcome::new(1)),
(&t2[..], MultiTeamOutcome::new(2)),
];
let results = weng_lin_multi_team(&game, &WengLinConfig::new());
assert_eq!(results.len(), 2);
let nt1 = &results[0];
let nt2 = &results[1];
assert!((nt1[0].rating - 27.904_443_970_057_24).abs() < f64::EPSILON);
assert!((nt1[1].rating - 30.060_226_550_163_108).abs() < f64::EPSILON);
assert!((nt1[2].rating - 22.767_063_711_382_825).abs() < f64::EPSILON);
assert!((nt2[0].rating - 22.095_556_029_942_76).abs() < f64::EPSILON);
assert!((nt2[1].rating - 40.918_024_973_389_1).abs() < f64::EPSILON);
assert!((nt2[2].rating - 18.426_674_366_308_44).abs() < f64::EPSILON);
assert!((nt1[0].uncertainty - 8.138_803_466_450_47).abs() < f64::EPSILON);
assert!((nt1[1].uncertainty - 1.199_425_779_255_630_7).abs() < f64::EPSILON);
assert!((nt1[2].uncertainty - 6.408_113_466_768_933).abs() < f64::EPSILON);
assert!((nt2[0].uncertainty - 8.160_155_338_979_159).abs() < f64::EPSILON);
assert!((nt2[1].uncertainty - 1.399_187_149_975_365_4).abs() < f64::EPSILON);
assert!((nt2[2].uncertainty - 4.276_389_807_576_043).abs() < f64::EPSILON);
let game = vec![
(&t1[..], MultiTeamOutcome::new(1)),
(&t2[..], MultiTeamOutcome::new(1)),
];
let results = weng_lin_multi_team(&game, &WengLinConfig::new());
assert_eq!(results.len(), 2);
let nt1 = &results[0];
let nt2 = &results[1];
assert!((nt1[0].rating - 25.652_558_832_338_293).abs() < f64::EPSILON);
assert!((nt1[1].rating - 30.013_531_459_947_366).abs() < f64::EPSILON);
assert!((nt1[2].rating - 21.397_016_793_594_62).abs() < f64::EPSILON);
assert!((nt2[0].rating - 24.347_441_167_661_707).abs() < f64::EPSILON);
assert!((nt2[1].rating - 40.981_582_179_516_08).abs() < f64::EPSILON);
assert!((nt2[2].rating - 19.026_252_295_536_935).abs() < f64::EPSILON);
assert!((nt1[0].uncertainty - 8.138_803_466_450_47).abs() < f64::EPSILON);
let game = vec![
(&t1[..], MultiTeamOutcome::new(2)),
(&t2[..], MultiTeamOutcome::new(1)),
];
let results = weng_lin_multi_team(&game, &WengLinConfig::new());
assert_eq!(results.len(), 2);
let nt1 = &results[0];
let nt2 = &results[1];
assert!((nt1[0].rating - 23.400_673_694_619_35).abs() < f64::EPSILON);
assert!((nt1[1].rating - 29.966_836_369_731_627).abs() < f64::EPSILON);
assert!((nt1[2].rating - 20.026_969_875_806_41).abs() < f64::EPSILON);
assert!((nt2[0].rating - 26.599_326_305_380_65).abs() < f64::EPSILON);
assert!((nt2[1].rating - 41.045_139_385_643_06).abs() < f64::EPSILON);
assert!((nt2[2].rating - 19.625_830_224_765_43).abs() < f64::EPSILON);
}
#[test]
fn test_empty_team() {
let t1 = vec![WengLinRating::new()];
let t2 = Vec::new();
let (nt1, nt2) = weng_lin_two_teams(&t1, &t2, &Outcomes::DRAW, &WengLinConfig::new());
assert_eq!(t1, nt1);
assert_eq!(t2, nt2);
let game = vec![
(&t1[..], MultiTeamOutcome::new(1)),
(&t2[..], MultiTeamOutcome::new(1)),
];
let results = weng_lin_multi_team(&game, &WengLinConfig::new());
assert_eq!(results.len(), 2);
assert_eq!(results[0], t1);
assert_eq!(results[1], t2);
let result = weng_lin_multi_team(&[], &WengLinConfig::new());
assert_eq!(result.len(), 0);
}
#[test]
#[allow(clippy::similar_names)]
fn test_1v1_team() {
let player_one = WengLinRating {
rating: 20.0,
uncertainty: 3.2,
};
let player_two = WengLinRating {
rating: 22.2,
uncertainty: 4.1,
};
let config = WengLinConfig::new();
let outcome = Outcomes::WIN;
let (op1, op2) = weng_lin(&player_one, &player_two, &outcome, &config);
let (tp1, tp2) = weng_lin_two_teams(&[player_one], &[player_two], &outcome, &config);
assert_eq!(op1, tp1[0]);
assert_eq!(op2, tp2[0]);
}
#[test]
fn test_expected() {
let p1 = WengLinRating::new();
let p2 = WengLinRating::new();
let (exp1, exp2) = expected_score(&p1, &p2, &WengLinConfig::new());
assert!((exp1 - exp2).abs() < f64::EPSILON);
let p1 = WengLinRating {
rating: 42.0,
uncertainty: 2.1,
};
let p2 = WengLinRating {
rating: 31.0,
uncertainty: 1.2,
};
let (exp1, exp2) = expected_score(&p1, &p2, &WengLinConfig::new());
assert!((exp1 + exp2 - 1.0).abs() < f64::EPSILON);
assert!((exp1 - 0.849_021_123_412_260_5).abs() < f64::EPSILON);
assert!((exp2 - 0.150_978_876_587_739_42).abs() < f64::EPSILON);
}
#[test]
fn test_expected_teams() {
let p1 = vec![WengLinRating::new()];
let p2 = vec![WengLinRating::new()];
let (exp1, exp2) = expected_score_two_teams(&p1, &p2, &WengLinConfig::new());
assert!((exp1 - exp2).abs() < f64::EPSILON);
let mut p1 = vec![WengLinRating {
rating: 42.0,
uncertainty: 2.1,
}];
let mut p2 = vec![WengLinRating {
rating: 31.0,
uncertainty: 1.2,
}];
let (exp1, exp2) = expected_score_two_teams(&p1, &p2, &WengLinConfig::new());
assert!((exp1 + exp2 - 1.0).abs() < f64::EPSILON);
assert!((exp1 - 0.849_021_123_412_260_5).abs() < f64::EPSILON);
assert!((exp2 - 0.150_978_876_587_739_42).abs() < f64::EPSILON);
p1.push(WengLinRating::new());
p1.push(WengLinRating {
rating: 12.0,
uncertainty: 3.2,
});
p2.push(WengLinRating {
rating: 41.0,
uncertainty: 1.2,
});
let (exp1, exp2) = expected_score_two_teams(&p1, &p2, &WengLinConfig::new());
assert!((exp1 + exp2 - 1.0).abs() < f64::EPSILON);
assert!((exp1 - 0.653_518_078_332_893_4).abs() < f64::EPSILON);
p2.push(WengLinRating::new());
let (exp1, _) = expected_score_two_teams(&p1, &p2, &WengLinConfig::new());
assert!((exp1 - 0.213_836_440_502_453_18).abs() < f64::EPSILON);
}
#[test]
fn test_expected_score_multi_teams() {
let team_one = vec![WengLinRating::new()];
let team_two = vec![WengLinRating::new()];
let team_three = vec![WengLinRating::new()];
let team_four = vec![WengLinRating::new()];
let exp = expected_score_multi_team(
&[&team_one, &team_two, &team_three, &team_four],
&WengLinConfig::new(),
);
assert_eq!(exp.len(), 4);
assert!((exp.iter().sum::<f64>() - 1.0).abs() < f64::EPSILON);
assert!((exp[0] - 0.25).abs() < f64::EPSILON);
assert!((exp[1] - 0.25).abs() < f64::EPSILON);
assert!((exp[2] - 0.25).abs() < f64::EPSILON);
assert!((exp[3] - 0.25).abs() < f64::EPSILON);
let team_one = vec![WengLinRating {
rating: 42.0,
uncertainty: 2.1,
}];
let team_two = vec![WengLinRating {
rating: 31.0,
uncertainty: 1.2,
}];
let exp = expected_score_multi_team(&[&team_one, &team_two], &WengLinConfig::new());
assert!((exp[0] + exp[1] - 1.0).abs() < f64::EPSILON);
assert!((exp[0] - 0.849_021_123_412_260_5).abs() < f64::EPSILON);
assert!((exp[1] - 0.150_978_876_587_739_42).abs() < f64::EPSILON);
}
#[test]
fn test_rating_period() {
let player = WengLinRating::new();
let opponent_one = WengLinRating::new();
let opponent_two = WengLinRating {
rating: 12.0,
uncertainty: 4.2,
};
let (normal_player, _) = weng_lin(
&player,
&opponent_one,
&Outcomes::WIN,
&WengLinConfig::new(),
);
let (normal_player, _) = weng_lin(
&normal_player,
&opponent_two,
&Outcomes::DRAW,
&WengLinConfig::new(),
);
let (normal_player, _) = weng_lin(
&normal_player,
&opponent_two,
&Outcomes::LOSS,
&WengLinConfig::new(),
);
let rating_player = weng_lin_rating_period(
&player,
&[
(opponent_one, Outcomes::WIN),
(opponent_two, Outcomes::DRAW),
(opponent_two, Outcomes::LOSS),
],
&WengLinConfig::new(),
);
assert!((normal_player.rating - rating_player.rating).abs() < f64::EPSILON);
assert!((normal_player.uncertainty - rating_player.uncertainty).abs() < f64::EPSILON);
}
#[test]
fn trueskill_conversion() {
let weng_lin_player = WengLinRating::new();
let trueskill_player = TrueSkillRating::from(weng_lin_player);
assert_eq!(trueskill_player, TrueSkillRating::new());
let other_weng_lin_player = WengLinRating::from(TrueSkillRating {
rating: 35.0,
uncertainty: 4.0,
});
assert!((other_weng_lin_player.rating - 35.0).abs() < f64::EPSILON);
}
#[test]
#[allow(clippy::clone_on_copy)]
fn test_misc_stuff() {
let player_one = WengLinRating::new();
let config = WengLinConfig::new();
assert_eq!(player_one, player_one.clone());
assert!((config.beta - config.clone().beta).abs() < f64::EPSILON);
assert!(!format!("{:?}", player_one).is_empty());
assert!(!format!("{:?}", config).is_empty());
assert_eq!(player_one, WengLinRating::from((25.0, 25.0 / 3.0)));
}
}