riix.models.glicko

Glicko

  1"""Glicko"""
  2import math
  3import numpy as np
  4from riix.core.base import OnlineRatingSystem
  5from riix.utils.math_utils import sigmoid, sigmoid_scalar
  6from riix.utils.constants import PI2, Q, Q2, Q2_3
  7
  8
  9class Glicko(OnlineRatingSystem):
 10    """
 11    Implements the original Glicko rating system, designed by Mark Glickman.
 12
 13    This rating system is an improvement over the Elo rating system, introducing the concept of rating
 14    deviation and volatility to better account for the uncertainty in a player's true strength.
 15    """
 16
 17    rating_dim = 2
 18
 19    def __init__(
 20        self,
 21        competitors: list,
 22        initial_rating: float = 1500.0,
 23        initial_rating_dev: float = 350.0,
 24        c: float = 63.2,
 25        dtype=np.float64,
 26        update_method='online',
 27        # update_method='batched',
 28        do_weird_prob=False,
 29    ):
 30        """
 31        Initializes the Glicko rating system with the given parameters.
 32
 33        Parameters:
 34            competitors (list): A list of competitors to be rated within the system.
 35            initial_rating (float, optional): The initial Glicko rating for new competitors. Defaults to 1500.0.
 36            initial_rating_dev (float, optional): The initial rating deviation for new competitors. Defaults to 350.0.
 37            c (float, optional): Constant used to adjust the rate of change of the rating deviation. Defaults to 63.2.
 38            dtype (data-type, optional): The desired data-type for the ratings and deviations arrays. Defaults to np.float64.
 39            update_method (str, optional): Method used for updating ratings ('online' or another specified method). Defaults to 'online'.
 40            do_weird_prob (bool, optional): If set to True, applies an alternative probability calculation. Defaults to False.
 41        """
 42        super().__init__(competitors)
 43        self.initial_rating_dev = initial_rating_dev
 44        self.c2 = c**2.0
 45        self.ratings = np.zeros(shape=self.num_competitors, dtype=dtype) + initial_rating
 46        self.rating_devs = np.zeros(shape=self.num_competitors, dtype=dtype) + initial_rating_dev
 47        self.has_played = np.zeros(shape=self.num_competitors, dtype=np.bool_)
 48        self.prev_time_step = -1
 49        self.do_weird_prob = do_weird_prob
 50
 51        if update_method == 'batched':
 52            self.update = self.batched_update
 53        elif update_method == 'online':
 54            self.update = self.online_update
 55        else:
 56            raise ValueError(f'update_method must be one of online or batched, got {update_method}')
 57
 58    @staticmethod
 59    def g_vector(rating_dev):
 60        """
 61        Calculates the g function as part of the Glicko rating system.
 62
 63        This function is used to scale the rating deviation, affecting the impact of a game outcome
 64        as a function of the opponent's rating volatility.
 65
 66        Parameters:
 67            rating_dev (float): The rating deviation of an opponent.
 68
 69        Returns:
 70            float: The calculated g function result, used to scale the expected score between players.
 71        """
 72        return 1.0 / np.sqrt(1.0 + (Q2_3 * np.square(rating_dev)) / PI2)
 73
 74    @staticmethod
 75    def g_scalar(rating_dev):
 76        return 1.0 / math.sqrt(1.0 + (Q2_3 * (rating_dev) ** 2.0) / PI2)
 77
 78    # TODO should Glicko probs incorporate the dev increase?
 79    def predict(self, matchups: np.ndarray, time_step: int = None, set_cache: bool = False):
 80        """generate predictions"""
 81        ratings_1 = self.ratings[matchups[:, 0]]
 82        ratings_2 = self.ratings[matchups[:, 1]]
 83        rating_diffs = ratings_1 - ratings_2
 84        if self.do_weird_prob:
 85            # not sure why they do it this way but it seems to work better than the "real" way
 86            # https://github.com/McLeopold/PythonSkills/blob/95559262fbeaabc39cc5d698b93a6e43dc9b5e64/skills/glicko.py#L181
 87            probs = 1.0 / (1.0 + np.power(10, -rating_diffs / (2.0 * self.initial_rating_dev)))
 88        else:
 89            rating_devs_1 = self.rating_devs[matchups[:, 0]]
 90            rating_devs_2 = self.rating_devs[matchups[:, 1]]
 91            combined_dev = self.g_vector(np.sqrt(np.square(rating_devs_1) + np.square(rating_devs_2)))
 92            probs = sigmoid(Q * combined_dev * rating_diffs)
 93        return probs
 94
 95    def get_pre_match_ratings(self, matchups: np.ndarray, **kwargs):
 96        means = self.ratings[matchups]
 97        devs = self.rating_devs[matchups]
 98        ratings = np.concatenate((means[..., None], devs[..., None]), axis=2).reshape(means.shape[0], -1)
 99        return ratings
100
101    def increase_rating_dev(self, time_step, matchups):
102        """called once per period to model the increase in variance over time"""
103        active_in_period = np.unique(matchups)
104        self.has_played[active_in_period] = True
105        time_delta = time_step - self.prev_time_step
106        self.rating_devs[self.has_played] = np.minimum(
107            np.sqrt(np.square(self.rating_devs[self.has_played]) + (time_delta * self.c2)), self.initial_rating_dev
108        )
109        self.prev_time_step = time_step
110        return active_in_period
111
112    def batched_update(self, matchups, outcomes, time_step, use_cache=False, **kwargs):
113        """apply one update based on all of the results of the rating period"""
114        active_in_period = self.increase_rating_dev(time_step, matchups)
115        masks = np.equal(matchups[:, :, None], active_in_period[None, :])  # N x 2 x active
116
117        ratings = self.ratings[matchups]
118        rating_diffs = ratings[:, 0] - ratings[:, 1]
119        g_rating_devs = self.g_vector(self.rating_devs[matchups])
120        probs_1 = sigmoid(Q * g_rating_devs[:, 1] * rating_diffs)
121        probs_2 = sigmoid(-1.0 * (Q * g_rating_devs[:, 0] * rating_diffs))
122
123        tmp = np.stack([probs_1 * (1.0 - probs_1), probs_2 * (1.0 - probs_2)]).T * np.square(g_rating_devs)[:, [1, 0]]
124        d2 = 1.0 / ((tmp[:, :, None] * masks).sum(axis=(0, 1)) * Q2)
125
126        outcomes = np.hstack([outcomes[:, None], 1.0 - outcomes[:, None]])
127        probs = np.hstack([probs_1[:, None], probs_2[:, None]])
128
129        r_num = Q * ((g_rating_devs[:, [1, 0]] * (outcomes - probs))[:, :, None] * masks).sum(axis=(0, 1))
130        r_denom = (1.0 / np.square(self.rating_devs[active_in_period])) + (1.0 / d2)
131
132        self.ratings[active_in_period] += r_num / r_denom
133        self.rating_devs[active_in_period] = np.sqrt(1.0 / r_denom)
134
135    def online_update(self, matchups, outcomes, time_step, **kwargs):
136        """treat the matchups in the rating period as if they were sequential"""
137        self.increase_rating_dev(time_step, matchups)
138        for idx in range(matchups.shape[0]):
139            comp_1, comp_2 = matchups[idx]
140            rating_diff = self.ratings[comp_1] - self.ratings[comp_2]
141            g_rating_devs = self.g_vector(self.rating_devs[matchups[idx]])
142            g_rating_devs_2 = np.square(g_rating_devs)
143            prob_1 = sigmoid_scalar(Q * g_rating_devs[1] * rating_diff)
144            prob_2 = sigmoid_scalar(-Q * g_rating_devs[0] * rating_diff)
145            d2_1 = 1.0 / (Q2 * prob_1 * (1.0 - prob_1) * g_rating_devs_2[1])
146            d2_2 = 1.0 / (Q2 * prob_2 * (1.0 - prob_2) * g_rating_devs_2[0])
147            r1_num = Q * g_rating_devs[1] * (outcomes[idx] - prob_1)
148            r2_num = Q * g_rating_devs[0] * (1.0 - outcomes[idx] - prob_2)
149            r1_denom = (1.0 / (self.rating_devs[comp_1] ** 2.0)) + (1.0 / d2_1)
150            r2_denom = (1.0 / (self.rating_devs[comp_2] ** 2.0)) + (1.0 / d2_2)
151
152            self.ratings[comp_1] += r1_num / r1_denom
153            self.ratings[comp_2] += r2_num / r2_denom
154            self.rating_devs[comp_1] = 1.0 / math.sqrt(r1_denom)
155            self.rating_devs[comp_2] = 1.0 / math.sqrt(r2_denom)
156
157    def print_leaderboard(self, num_places):
158        sort_array = self.ratings - (3.0 * self.rating_devs)
159        sorted_idxs = np.argsort(-sort_array)[:num_places]
160        max_len = min(np.max([len(comp) for comp in self.competitors] + [10]), 25)
161        print(f'{"competitor": <{max_len}}\t{"rating - (3*dev)"}\t')
162        for p_idx in range(num_places):
163            comp_idx = sorted_idxs[p_idx]
164            print(f'{self.competitors[comp_idx]: <{max_len}}\t{sort_array[comp_idx]:.6f}')
class Glicko(riix.core.base.OnlineRatingSystem):
 10class Glicko(OnlineRatingSystem):
 11    """
 12    Implements the original Glicko rating system, designed by Mark Glickman.
 13
 14    This rating system is an improvement over the Elo rating system, introducing the concept of rating
 15    deviation and volatility to better account for the uncertainty in a player's true strength.
 16    """
 17
 18    rating_dim = 2
 19
 20    def __init__(
 21        self,
 22        competitors: list,
 23        initial_rating: float = 1500.0,
 24        initial_rating_dev: float = 350.0,
 25        c: float = 63.2,
 26        dtype=np.float64,
 27        update_method='online',
 28        # update_method='batched',
 29        do_weird_prob=False,
 30    ):
 31        """
 32        Initializes the Glicko rating system with the given parameters.
 33
 34        Parameters:
 35            competitors (list): A list of competitors to be rated within the system.
 36            initial_rating (float, optional): The initial Glicko rating for new competitors. Defaults to 1500.0.
 37            initial_rating_dev (float, optional): The initial rating deviation for new competitors. Defaults to 350.0.
 38            c (float, optional): Constant used to adjust the rate of change of the rating deviation. Defaults to 63.2.
 39            dtype (data-type, optional): The desired data-type for the ratings and deviations arrays. Defaults to np.float64.
 40            update_method (str, optional): Method used for updating ratings ('online' or another specified method). Defaults to 'online'.
 41            do_weird_prob (bool, optional): If set to True, applies an alternative probability calculation. Defaults to False.
 42        """
 43        super().__init__(competitors)
 44        self.initial_rating_dev = initial_rating_dev
 45        self.c2 = c**2.0
 46        self.ratings = np.zeros(shape=self.num_competitors, dtype=dtype) + initial_rating
 47        self.rating_devs = np.zeros(shape=self.num_competitors, dtype=dtype) + initial_rating_dev
 48        self.has_played = np.zeros(shape=self.num_competitors, dtype=np.bool_)
 49        self.prev_time_step = -1
 50        self.do_weird_prob = do_weird_prob
 51
 52        if update_method == 'batched':
 53            self.update = self.batched_update
 54        elif update_method == 'online':
 55            self.update = self.online_update
 56        else:
 57            raise ValueError(f'update_method must be one of online or batched, got {update_method}')
 58
 59    @staticmethod
 60    def g_vector(rating_dev):
 61        """
 62        Calculates the g function as part of the Glicko rating system.
 63
 64        This function is used to scale the rating deviation, affecting the impact of a game outcome
 65        as a function of the opponent's rating volatility.
 66
 67        Parameters:
 68            rating_dev (float): The rating deviation of an opponent.
 69
 70        Returns:
 71            float: The calculated g function result, used to scale the expected score between players.
 72        """
 73        return 1.0 / np.sqrt(1.0 + (Q2_3 * np.square(rating_dev)) / PI2)
 74
 75    @staticmethod
 76    def g_scalar(rating_dev):
 77        return 1.0 / math.sqrt(1.0 + (Q2_3 * (rating_dev) ** 2.0) / PI2)
 78
 79    # TODO should Glicko probs incorporate the dev increase?
 80    def predict(self, matchups: np.ndarray, time_step: int = None, set_cache: bool = False):
 81        """generate predictions"""
 82        ratings_1 = self.ratings[matchups[:, 0]]
 83        ratings_2 = self.ratings[matchups[:, 1]]
 84        rating_diffs = ratings_1 - ratings_2
 85        if self.do_weird_prob:
 86            # not sure why they do it this way but it seems to work better than the "real" way
 87            # https://github.com/McLeopold/PythonSkills/blob/95559262fbeaabc39cc5d698b93a6e43dc9b5e64/skills/glicko.py#L181
 88            probs = 1.0 / (1.0 + np.power(10, -rating_diffs / (2.0 * self.initial_rating_dev)))
 89        else:
 90            rating_devs_1 = self.rating_devs[matchups[:, 0]]
 91            rating_devs_2 = self.rating_devs[matchups[:, 1]]
 92            combined_dev = self.g_vector(np.sqrt(np.square(rating_devs_1) + np.square(rating_devs_2)))
 93            probs = sigmoid(Q * combined_dev * rating_diffs)
 94        return probs
 95
 96    def get_pre_match_ratings(self, matchups: np.ndarray, **kwargs):
 97        means = self.ratings[matchups]
 98        devs = self.rating_devs[matchups]
 99        ratings = np.concatenate((means[..., None], devs[..., None]), axis=2).reshape(means.shape[0], -1)
100        return ratings
101
102    def increase_rating_dev(self, time_step, matchups):
103        """called once per period to model the increase in variance over time"""
104        active_in_period = np.unique(matchups)
105        self.has_played[active_in_period] = True
106        time_delta = time_step - self.prev_time_step
107        self.rating_devs[self.has_played] = np.minimum(
108            np.sqrt(np.square(self.rating_devs[self.has_played]) + (time_delta * self.c2)), self.initial_rating_dev
109        )
110        self.prev_time_step = time_step
111        return active_in_period
112
113    def batched_update(self, matchups, outcomes, time_step, use_cache=False, **kwargs):
114        """apply one update based on all of the results of the rating period"""
115        active_in_period = self.increase_rating_dev(time_step, matchups)
116        masks = np.equal(matchups[:, :, None], active_in_period[None, :])  # N x 2 x active
117
118        ratings = self.ratings[matchups]
119        rating_diffs = ratings[:, 0] - ratings[:, 1]
120        g_rating_devs = self.g_vector(self.rating_devs[matchups])
121        probs_1 = sigmoid(Q * g_rating_devs[:, 1] * rating_diffs)
122        probs_2 = sigmoid(-1.0 * (Q * g_rating_devs[:, 0] * rating_diffs))
123
124        tmp = np.stack([probs_1 * (1.0 - probs_1), probs_2 * (1.0 - probs_2)]).T * np.square(g_rating_devs)[:, [1, 0]]
125        d2 = 1.0 / ((tmp[:, :, None] * masks).sum(axis=(0, 1)) * Q2)
126
127        outcomes = np.hstack([outcomes[:, None], 1.0 - outcomes[:, None]])
128        probs = np.hstack([probs_1[:, None], probs_2[:, None]])
129
130        r_num = Q * ((g_rating_devs[:, [1, 0]] * (outcomes - probs))[:, :, None] * masks).sum(axis=(0, 1))
131        r_denom = (1.0 / np.square(self.rating_devs[active_in_period])) + (1.0 / d2)
132
133        self.ratings[active_in_period] += r_num / r_denom
134        self.rating_devs[active_in_period] = np.sqrt(1.0 / r_denom)
135
136    def online_update(self, matchups, outcomes, time_step, **kwargs):
137        """treat the matchups in the rating period as if they were sequential"""
138        self.increase_rating_dev(time_step, matchups)
139        for idx in range(matchups.shape[0]):
140            comp_1, comp_2 = matchups[idx]
141            rating_diff = self.ratings[comp_1] - self.ratings[comp_2]
142            g_rating_devs = self.g_vector(self.rating_devs[matchups[idx]])
143            g_rating_devs_2 = np.square(g_rating_devs)
144            prob_1 = sigmoid_scalar(Q * g_rating_devs[1] * rating_diff)
145            prob_2 = sigmoid_scalar(-Q * g_rating_devs[0] * rating_diff)
146            d2_1 = 1.0 / (Q2 * prob_1 * (1.0 - prob_1) * g_rating_devs_2[1])
147            d2_2 = 1.0 / (Q2 * prob_2 * (1.0 - prob_2) * g_rating_devs_2[0])
148            r1_num = Q * g_rating_devs[1] * (outcomes[idx] - prob_1)
149            r2_num = Q * g_rating_devs[0] * (1.0 - outcomes[idx] - prob_2)
150            r1_denom = (1.0 / (self.rating_devs[comp_1] ** 2.0)) + (1.0 / d2_1)
151            r2_denom = (1.0 / (self.rating_devs[comp_2] ** 2.0)) + (1.0 / d2_2)
152
153            self.ratings[comp_1] += r1_num / r1_denom
154            self.ratings[comp_2] += r2_num / r2_denom
155            self.rating_devs[comp_1] = 1.0 / math.sqrt(r1_denom)
156            self.rating_devs[comp_2] = 1.0 / math.sqrt(r2_denom)
157
158    def print_leaderboard(self, num_places):
159        sort_array = self.ratings - (3.0 * self.rating_devs)
160        sorted_idxs = np.argsort(-sort_array)[:num_places]
161        max_len = min(np.max([len(comp) for comp in self.competitors] + [10]), 25)
162        print(f'{"competitor": <{max_len}}\t{"rating - (3*dev)"}\t')
163        for p_idx in range(num_places):
164            comp_idx = sorted_idxs[p_idx]
165            print(f'{self.competitors[comp_idx]: <{max_len}}\t{sort_array[comp_idx]:.6f}')

Implements the original Glicko rating system, designed by Mark Glickman.

This rating system is an improvement over the Elo rating system, introducing the concept of rating deviation and volatility to better account for the uncertainty in a player's true strength.

Glicko( competitors: list, initial_rating: float = 1500.0, initial_rating_dev: float = 350.0, c: float = 63.2, dtype=<class 'numpy.float64'>, update_method='online', do_weird_prob=False)
20    def __init__(
21        self,
22        competitors: list,
23        initial_rating: float = 1500.0,
24        initial_rating_dev: float = 350.0,
25        c: float = 63.2,
26        dtype=np.float64,
27        update_method='online',
28        # update_method='batched',
29        do_weird_prob=False,
30    ):
31        """
32        Initializes the Glicko rating system with the given parameters.
33
34        Parameters:
35            competitors (list): A list of competitors to be rated within the system.
36            initial_rating (float, optional): The initial Glicko rating for new competitors. Defaults to 1500.0.
37            initial_rating_dev (float, optional): The initial rating deviation for new competitors. Defaults to 350.0.
38            c (float, optional): Constant used to adjust the rate of change of the rating deviation. Defaults to 63.2.
39            dtype (data-type, optional): The desired data-type for the ratings and deviations arrays. Defaults to np.float64.
40            update_method (str, optional): Method used for updating ratings ('online' or another specified method). Defaults to 'online'.
41            do_weird_prob (bool, optional): If set to True, applies an alternative probability calculation. Defaults to False.
42        """
43        super().__init__(competitors)
44        self.initial_rating_dev = initial_rating_dev
45        self.c2 = c**2.0
46        self.ratings = np.zeros(shape=self.num_competitors, dtype=dtype) + initial_rating
47        self.rating_devs = np.zeros(shape=self.num_competitors, dtype=dtype) + initial_rating_dev
48        self.has_played = np.zeros(shape=self.num_competitors, dtype=np.bool_)
49        self.prev_time_step = -1
50        self.do_weird_prob = do_weird_prob
51
52        if update_method == 'batched':
53            self.update = self.batched_update
54        elif update_method == 'online':
55            self.update = self.online_update
56        else:
57            raise ValueError(f'update_method must be one of online or batched, got {update_method}')

Initializes the Glicko rating system with the given parameters.

Arguments:
  • competitors (list): A list of competitors to be rated within the system.
  • initial_rating (float, optional): The initial Glicko rating for new competitors. Defaults to 1500.0.
  • initial_rating_dev (float, optional): The initial rating deviation for new competitors. Defaults to 350.0.
  • c (float, optional): Constant used to adjust the rate of change of the rating deviation. Defaults to 63.2.
  • dtype (data-type, optional): The desired data-type for the ratings and deviations arrays. Defaults to np.float64.
  • update_method (str, optional): Method used for updating ratings ('online' or another specified method). Defaults to 'online'.
  • do_weird_prob (bool, optional): If set to True, applies an alternative probability calculation. Defaults to False.
rating_dim = 2
initial_rating_dev
c2
ratings
rating_devs
has_played
prev_time_step
do_weird_prob
@staticmethod
def g_vector(rating_dev):
59    @staticmethod
60    def g_vector(rating_dev):
61        """
62        Calculates the g function as part of the Glicko rating system.
63
64        This function is used to scale the rating deviation, affecting the impact of a game outcome
65        as a function of the opponent's rating volatility.
66
67        Parameters:
68            rating_dev (float): The rating deviation of an opponent.
69
70        Returns:
71            float: The calculated g function result, used to scale the expected score between players.
72        """
73        return 1.0 / np.sqrt(1.0 + (Q2_3 * np.square(rating_dev)) / PI2)

Calculates the g function as part of the Glicko rating system.

This function is used to scale the rating deviation, affecting the impact of a game outcome as a function of the opponent's rating volatility.

Arguments:
  • rating_dev (float): The rating deviation of an opponent.
Returns:

float: The calculated g function result, used to scale the expected score between players.

@staticmethod
def g_scalar(rating_dev):
75    @staticmethod
76    def g_scalar(rating_dev):
77        return 1.0 / math.sqrt(1.0 + (Q2_3 * (rating_dev) ** 2.0) / PI2)
def predict( self, matchups: numpy.ndarray, time_step: int = None, set_cache: bool = False):
80    def predict(self, matchups: np.ndarray, time_step: int = None, set_cache: bool = False):
81        """generate predictions"""
82        ratings_1 = self.ratings[matchups[:, 0]]
83        ratings_2 = self.ratings[matchups[:, 1]]
84        rating_diffs = ratings_1 - ratings_2
85        if self.do_weird_prob:
86            # not sure why they do it this way but it seems to work better than the "real" way
87            # https://github.com/McLeopold/PythonSkills/blob/95559262fbeaabc39cc5d698b93a6e43dc9b5e64/skills/glicko.py#L181
88            probs = 1.0 / (1.0 + np.power(10, -rating_diffs / (2.0 * self.initial_rating_dev)))
89        else:
90            rating_devs_1 = self.rating_devs[matchups[:, 0]]
91            rating_devs_2 = self.rating_devs[matchups[:, 1]]
92            combined_dev = self.g_vector(np.sqrt(np.square(rating_devs_1) + np.square(rating_devs_2)))
93            probs = sigmoid(Q * combined_dev * rating_diffs)
94        return probs

generate predictions

def get_pre_match_ratings(self, matchups: numpy.ndarray, **kwargs):
 96    def get_pre_match_ratings(self, matchups: np.ndarray, **kwargs):
 97        means = self.ratings[matchups]
 98        devs = self.rating_devs[matchups]
 99        ratings = np.concatenate((means[..., None], devs[..., None]), axis=2).reshape(means.shape[0], -1)
100        return ratings

Returns the ratings for competitors at the timestep of the matchups Useful when using pre-match ratings as features in downstream ML pipelines

Arguments:
  • matchups (np.ndarray of shape (n,2)): competitor indices
  • time_step (optional int)
Returns:

np.ndarray of shape (n,2): ratings for specified competitors

def increase_rating_dev(self, time_step, matchups):
102    def increase_rating_dev(self, time_step, matchups):
103        """called once per period to model the increase in variance over time"""
104        active_in_period = np.unique(matchups)
105        self.has_played[active_in_period] = True
106        time_delta = time_step - self.prev_time_step
107        self.rating_devs[self.has_played] = np.minimum(
108            np.sqrt(np.square(self.rating_devs[self.has_played]) + (time_delta * self.c2)), self.initial_rating_dev
109        )
110        self.prev_time_step = time_step
111        return active_in_period

called once per period to model the increase in variance over time

def batched_update(self, matchups, outcomes, time_step, use_cache=False, **kwargs):
113    def batched_update(self, matchups, outcomes, time_step, use_cache=False, **kwargs):
114        """apply one update based on all of the results of the rating period"""
115        active_in_period = self.increase_rating_dev(time_step, matchups)
116        masks = np.equal(matchups[:, :, None], active_in_period[None, :])  # N x 2 x active
117
118        ratings = self.ratings[matchups]
119        rating_diffs = ratings[:, 0] - ratings[:, 1]
120        g_rating_devs = self.g_vector(self.rating_devs[matchups])
121        probs_1 = sigmoid(Q * g_rating_devs[:, 1] * rating_diffs)
122        probs_2 = sigmoid(-1.0 * (Q * g_rating_devs[:, 0] * rating_diffs))
123
124        tmp = np.stack([probs_1 * (1.0 - probs_1), probs_2 * (1.0 - probs_2)]).T * np.square(g_rating_devs)[:, [1, 0]]
125        d2 = 1.0 / ((tmp[:, :, None] * masks).sum(axis=(0, 1)) * Q2)
126
127        outcomes = np.hstack([outcomes[:, None], 1.0 - outcomes[:, None]])
128        probs = np.hstack([probs_1[:, None], probs_2[:, None]])
129
130        r_num = Q * ((g_rating_devs[:, [1, 0]] * (outcomes - probs))[:, :, None] * masks).sum(axis=(0, 1))
131        r_denom = (1.0 / np.square(self.rating_devs[active_in_period])) + (1.0 / d2)
132
133        self.ratings[active_in_period] += r_num / r_denom
134        self.rating_devs[active_in_period] = np.sqrt(1.0 / r_denom)

apply one update based on all of the results of the rating period

def online_update(self, matchups, outcomes, time_step, **kwargs):
136    def online_update(self, matchups, outcomes, time_step, **kwargs):
137        """treat the matchups in the rating period as if they were sequential"""
138        self.increase_rating_dev(time_step, matchups)
139        for idx in range(matchups.shape[0]):
140            comp_1, comp_2 = matchups[idx]
141            rating_diff = self.ratings[comp_1] - self.ratings[comp_2]
142            g_rating_devs = self.g_vector(self.rating_devs[matchups[idx]])
143            g_rating_devs_2 = np.square(g_rating_devs)
144            prob_1 = sigmoid_scalar(Q * g_rating_devs[1] * rating_diff)
145            prob_2 = sigmoid_scalar(-Q * g_rating_devs[0] * rating_diff)
146            d2_1 = 1.0 / (Q2 * prob_1 * (1.0 - prob_1) * g_rating_devs_2[1])
147            d2_2 = 1.0 / (Q2 * prob_2 * (1.0 - prob_2) * g_rating_devs_2[0])
148            r1_num = Q * g_rating_devs[1] * (outcomes[idx] - prob_1)
149            r2_num = Q * g_rating_devs[0] * (1.0 - outcomes[idx] - prob_2)
150            r1_denom = (1.0 / (self.rating_devs[comp_1] ** 2.0)) + (1.0 / d2_1)
151            r2_denom = (1.0 / (self.rating_devs[comp_2] ** 2.0)) + (1.0 / d2_2)
152
153            self.ratings[comp_1] += r1_num / r1_denom
154            self.ratings[comp_2] += r2_num / r2_denom
155            self.rating_devs[comp_1] = 1.0 / math.sqrt(r1_denom)
156            self.rating_devs[comp_2] = 1.0 / math.sqrt(r2_denom)

treat the matchups in the rating period as if they were sequential

def print_leaderboard(self, num_places):
158    def print_leaderboard(self, num_places):
159        sort_array = self.ratings - (3.0 * self.rating_devs)
160        sorted_idxs = np.argsort(-sort_array)[:num_places]
161        max_len = min(np.max([len(comp) for comp in self.competitors] + [10]), 25)
162        print(f'{"competitor": <{max_len}}\t{"rating - (3*dev)"}\t')
163        for p_idx in range(num_places):
164            comp_idx = sorted_idxs[p_idx]
165            print(f'{self.competitors[comp_idx]: <{max_len}}\t{sort_array[comp_idx]:.6f}')

Prints the leaderboard of the rating system.

Arguments:
  • num_places int: The number of top places to display on the leaderboard.