glicko2.py 3.68 KB
Newer Older
uudlo's avatar
uudlo committed
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from math import sqrt, pi, exp, log


class Rating:
    def __init__(self, outer):
        self.outer = outer
        self.rating = outer.start_rating
        self.rd = outer.start_rd
        self.volatility = outer.start_volatility

    def __str__(self):
        return "rating: {}\nrd: {}\nvolatility: {}".format(self.rating, self.rd, self.volatility)


class ScaledRating:
    def __init__(self, rating):
        self.ratio = 173.7178
        self.rating = rating
        self.outer = rating.outer
        self.mu = (rating.rating - rating.outer.start_rating) / self.ratio
        self.phi = rating.rd / self.ratio
        self.sigma = rating.volatility

    def unscale(self):
        r = Rating(self.outer)
        r.rating = self.ratio * self.mu + self.outer.start_rating
        r.rd = self.ratio * self.phi
        r.volatility = self.sigma
        return r

    def enshure_rd(self):
        max_phi = self.outer.start_rd / self.ratio
        if self.phi > max_phi:
            self.phi = max_phi


class Glicko2:
    def create_rating(self):
        return Rating(self)

    def __init__(self, start_rating, start_rd, start_volatility, tau):
        self.start_rating = start_rating
        self.start_rd = start_rd
        self.start_volatility = start_volatility
        self.tau = tau

    @staticmethod
    def g(phi):
        return 1/(sqrt(1+(3*(phi**2))/pi**2))

    @staticmethod
    def E(mu, mu_j, phi_j):
        return 1/(1+exp(-Glicko2.g(phi_j)*(mu-mu_j)))

    def update_rating(self, rating, opponents, results):
        # step 2
        s_rating = ScaledRating(rating)
        s_opponents = [ScaledRating(r) for r in opponents]

        # check rd
        # rd is not allowed to get higher than the start_rd
        for r in [s_rating] + s_opponents:
            r.enshure_rd()

        # step 6
        if not opponents:
            s_rating.phi = sqrt(s_rating.phi ** 2 + s_rating.sigma ** 2)
            s_rating.enshure_rd()
            return s_rating.unscale()

        # step 3
        v = 0
        for r in s_opponents:
            v += (Glicko2.g(r.phi) ** 2) * Glicko2.E(s_rating.mu, r.mu, r.phi) * (1 - Glicko2.E(s_rating.mu, r.mu, r.phi))
        v = v ** (-1)

        # step 4
        delta = 0
        for (r, s) in zip(s_opponents, results):
            delta += Glicko2.g(r.phi) * (s - Glicko2.E(s_rating.mu, r.mu, r.phi))
        delta *= v

        # step 5
        a = log(s_rating.sigma**2)

        def f(x):
            return (exp(x) * (delta ** 2 - s_rating.phi ** 2 - v - exp(x))) / (2 * (s_rating.phi ** 2 + v + exp(x))) - (x - a) / (self.tau**2)

        epsilon = 0.000001

        A = a
        B = 0.0
        if delta ** 2 > (s_rating.phi ** 2 + v):
            B = log(delta ** 2 - s_rating.phi ** 2 - v)
        else:
            k = 1
            B = a - k * self.tau
            while f(B) < 0:
                k += 1
                B = a - k * self.tau

        f_A = f(A)
        f_B = f(B)
        while abs(B - A) > epsilon:
            C = A + (((A - B) * f_A) / (f_B - f_A))
            f_C = f(C)
            if f_C * f_B < 0:
                A = B
                f_A = f_B
            else:
                f_A = f_A / 2
            B = C
            f_B = f_C
        sigma_ = exp(A / 2)

        # step 6
        phi_s = sqrt(s_rating.phi ** 2 + sigma_ ** 2)

        # step 7
        phi_ = 1 / sqrt((1 / (phi_s ** 2)) + (1 / v))
        mu_ = 0
        for (r, s) in zip(s_opponents, results):
            mu_ += Glicko2.g(r.phi)*(s - Glicko2.E(s_rating.mu, r.mu, r.phi))
        mu_ *= phi_**2
        mu_ += s_rating.mu

        s_rating.sigma = sigma_
        s_rating.phi = phi_
        s_rating.mu = mu_

        # step 8
        s_rating.enshure_rd()
        return s_rating.unscale()