use crate::glicko2::{constants::EPSILON, game::Outcome, rating::Rating};
pub(crate) fn reduce_impact(rating: &Rating, other_rating: &Rating) -> f64 {
if !rating.is_scaled || !other_rating.is_scaled {
panic!("Unscaled ratings passed to reduce impact!");
}
let phi = rating.phi.powi(2) + other_rating.phi.powi(2);
let phi_sqrt = phi.sqrt();
let pi_2 = std::f64::consts::PI.powi(2);
let denominator = 1.0 + (3.0 * phi_sqrt.powi(2)) / pi_2;
1.0 / denominator.sqrt()
}
pub(crate) fn expect_score(rating: &Rating, other_rating: &Rating, impact: f64) -> f64 {
if !rating.is_scaled || !other_rating.is_scaled {
panic!("Unscaled ratings passed to expect score!");
}
let new_impact = -impact * (rating.mu - other_rating.mu);
1.0 / (1.0 + new_impact.exp())
}
fn determine_sigma(rating: &Rating, difference: &f64, variance: &f64) -> f64 {
let phi = rating.phi;
let diff_squared = difference.powi(2);
let alpha = rating.sigma.powi(2).ln();
let optimality_criterion = |x: f64| -> f64 {
let tmp = phi.powi(2) + variance + x.exp();
let tmp_2 = 2.0 * tmp.powi(2);
let a = x.exp() * (diff_squared - tmp) / tmp_2;
let b = (x - alpha) / rating.tuning.tau.powi(2);
a - b
};
let mut a = alpha;
let mut b = if diff_squared > (phi.powi(2) + variance) {
(diff_squared - phi.powi(2) - variance).ln()
} else {
let mut k = 1.0;
while optimality_criterion(alpha - k * rating.tuning.tau) < 0.0 {
k += 1.0;
}
alpha - k * rating.tuning.tau
};
let mut f_a = optimality_criterion(a);
let mut f_b = optimality_criterion(b);
while (b - a).abs() > EPSILON {
let c = a + (a - b) * f_a / (f_b - f_a);
let f_c = optimality_criterion(c);
if f_c * f_b < 0.0 {
a = b;
f_a = f_b;
} else {
f_a /= 2.0;
}
b = c;
f_b = f_c;
}
1.0f64.exp().powf(a / 2.0)
}
pub fn rate(rating: &mut Rating, outcomes: &mut [(Outcome, &mut Rating)]) {
rating.scale_down();
let mut variance_inv = 0.0;
let mut difference = 0.0;
for (score, other_rating) in outcomes {
other_rating.scale_down();
let impact = reduce_impact(rating, other_rating);
let expected = expect_score(rating, other_rating, impact);
let expected_inv = expected * (1.0 - expected);
variance_inv += impact.powi(2) * expected_inv;
difference += impact * (score.val() - expected);
other_rating.scale_up();
}
difference /= variance_inv.max(0.0001);
let variance = 1.0 / variance_inv;
let sigma = determine_sigma(rating, &difference, &variance);
let phi_star = (rating.phi.powi(2) + sigma.powi(2)).sqrt();
let phi = 1.0 / ((1.0 / phi_star).powi(2) + (1.0 / variance)).sqrt();
let mu = (rating.mu + phi).powi(2) * (difference / variance);
rating.mu = mu;
rating.phi = phi;
rating.sigma = sigma;
rating.scale_up(); }