1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use super::util::{standard_normal_cdf, standard_normal_cdf_inv, Player, Rating, RatingSystem};
use rayon::prelude::*;
#[derive(Debug)]
pub struct TopcoderSys {
pub weight_multiplier: f64,
}
impl Default for TopcoderSys {
fn default() -> Self {
Self {
weight_multiplier: 1.,
}
}
}
impl TopcoderSys {
fn win_probability(&self, multiplier: f64, player: &Rating, foe: &Rating) -> f64 {
let z = (player.mu - foe.mu) / player.sig.hypot(foe.sig) / multiplier;
standard_normal_cdf(z)
}
}
impl RatingSystem for TopcoderSys {
fn round_update(&self, contest_weight: f64, standings: Vec<(&mut Player, usize, usize)>) {
let num_coders = standings.len() as f64;
let ave_rating = standings
.iter()
.map(|&(ref player, _, _)| player.approx_posterior.mu)
.sum::<f64>()
/ num_coders;
let c_factor = {
let mut mean_vol_sq = standings
.iter()
.map(|&(ref player, _, _)| player.approx_posterior.sig.powi(2))
.sum::<f64>()
/ num_coders;
if num_coders > 1. {
mean_vol_sq += standings
.iter()
.map(|&(ref player, _, _)| (player.approx_posterior.mu - ave_rating).powi(2))
.sum::<f64>()
/ (num_coders - 1.);
}
mean_vol_sq.sqrt()
};
let limit_weight = 1. / 0.82 - 1.;
let cap_multiplier = self.weight_multiplier * (1. + limit_weight)
/ (1. + limit_weight * self.weight_multiplier);
let new_ratings: Vec<(Rating, f64)> = standings
.par_iter()
.map(|(player, lo, hi)| {
let old_rating = player.approx_posterior.mu;
let vol_sq = player.approx_posterior.sig.powi(2);
let mut weight = contest_weight * self.weight_multiplier;
let ex_rank = standings
.iter()
.map(|&(ref foe, _, _)| {
self.win_probability(
weight,
&foe.approx_posterior,
&player.approx_posterior,
)
})
.sum::<f64>();
let ac_rank = 0.5 * (1 + lo + hi) as f64;
let ex_perf = -standard_normal_cdf_inv(ex_rank / num_coders);
let ac_perf = -standard_normal_cdf_inv(ac_rank / num_coders);
let perf_as = old_rating + c_factor * (ac_perf - ex_perf);
let num_contests = player.event_history.len() as f64;
weight *= 1. / (0.82 - 0.42 / num_contests) - 1.;
if old_rating >= 2500. {
weight *= 0.8;
} else if old_rating >= 2000. {
weight *= 0.9;
}
let mut cap = 150. + 1500. / (num_contests + 1.);
cap *= cap_multiplier;
let try_rating = (old_rating + weight * perf_as) / (1. + weight);
let new_rating = try_rating.max(old_rating - cap).min(old_rating + cap);
let new_vol =
((try_rating - old_rating).powi(2) / weight + vol_sq / (1. + weight)).sqrt();
(
Rating {
mu: new_rating,
sig: new_vol,
},
perf_as,
)
})
.collect();
standings.into_par_iter().zip(new_ratings).for_each(
|((player, _, _), (new_rating, new_perf))| {
player.update_rating(new_rating, new_perf);
},
);
}
}