#rating #elo #glicko #glicko-2

instant-glicko-2

Implementation of the Glicko-2 rating system that allows for instant feedback after games, not just once a rating period closes

2 unstable releases

0.2.0 Nov 12, 2023
0.1.0 Mar 31, 2022

#437 in Math

MIT license

96KB
1.5K SLoC

Instant Glicko-2

This crate provides an implementation of the Glicko-2 rating system. Due to the concept of rating periods, Glicko-2 has the problem that rankings cannot easily be updated instantly after a match concludes.

This implementation aims to solve that problem by allowing fractional rating periods, so that ratings can be updated directly after every game, and not just once a rating period closes. This draws inspiration from the rating system implementation for open-source chess website Lichess, as well as two blogpost (1, 2) by Ryan Juckett on skill ratings for INVERSUS Deluxe.

For more on the implementation, I wrote something here.

Documentation for the crate can be found on Docs.rs.

To use this as a dependency, add the following line to your Cargo.toml dependencies:

instant-glicko-2 = "0.1.0"

Examples

Example calculation from Glickman's paper using algorithm:

use instant_glicko_2::{Parameters, PublicRating, IntoWithParameters};
use instant_glicko_2::algorithm::{self, PublicGame};

let parameters = Parameters::default().with_volatility_change(0.5);

// Create our player's rating
let mut player = PublicRating::new(1500.0, 200.0, 0.06);

// Create our opponents
// Their volatility is not specified in the paper and it doesn't matter in the calculation,
// so we're just using the default starting volatility.
let opponent_a = PublicRating::new(1400.0, 30.0, parameters.start_rating().volatility());
let opponent_b = PublicRating::new(1550.0, 100.0, parameters.start_rating().volatility());
let opponent_c = PublicRating::new(1700.0, 300.0, parameters.start_rating().volatility());

// Create match results for our player
let results = [
    // Wins first game (score 1.0)
    PublicGame::new(opponent_a, 1.0).into_with_parameters(parameters),
    // Loses second game (score 0.0)
    PublicGame::new(opponent_b, 0.0).into_with_parameters(parameters),
    // Loses third game (score 0.0)
    PublicGame::new(opponent_c, 0.0).into_with_parameters(parameters),
];

// Update rating after rating period
let new_rating: PublicRating = algorithm::rate_games_untimed(player.into_with_parameters(parameters), &results, 1.0, parameters).into_with_parameters(parameters);

// The rating after the rating period are very close to the results from the paper
assert!((new_rating.rating() - 1464.06).abs() < 0.01);
assert!((new_rating.deviation() - 151.52).abs() < 0.01);
assert!((new_rating.volatility() - 0.05999).abs() < 0.0001);

Different example using RatingEngine:

use std::time::Duration;
use instant_glicko_2::{Parameters, PublicRating};
use instant_glicko_2::engine::{MatchResult, RatingEngine};

let parameters = Parameters::default();

// Create a RatingEngine with a one day rating period duration
// The first rating period starts instantly
let mut engine = RatingEngine::start_new(
    Duration::from_secs(60 * 60 * 24),
    Parameters::default(),
);

// Register two players
// The first player is relatively strong
let player_1_rating_old = PublicRating::new(1700.0, 300.0, 0.06);
let player_1 = engine.register_player(player_1_rating_old).0;

// The second player hasn't played any games
let player_2_rating_old = parameters.start_rating();
let player_2 = engine.register_player(player_2_rating_old).0;

// They play and player_2 wins
engine.register_result(
    player_1,
    player_2,
    &MatchResult::Loss,
);

// Print the new ratings
// Type signatures are needed because we could also work with the internal InternalRating
// That skips one step of calculation,
// but the rating values are not as pretty and not comparable to the original Glicko ratings
let player_1_rating_new: PublicRating = engine.player_rating(player_1).0;
println!("Player 1 old rating: {player_1_rating_old:?}, new rating: {player_1_rating_new:?}");
let player_2_rating_new: PublicRating = engine.player_rating(player_2).0;
println!("Player 2 old rating: {player_2_rating_old:?}, new rating: {player_2_rating_new:?}");

// Loser's rating goes down, winner's rating goes up
assert!(player_1_rating_old.rating() > player_1_rating_new.rating());
assert!(player_2_rating_old.rating() < player_2_rating_new.rating());

Dependencies

~170KB