Files
2011-04-26 18:03:21 -07:00

241 lines
7.8 KiB
Python

#
#
# Copyright (c) 2004 Stephen Waits
#
# This software is provided 'as-is', without any express or implied warranty. In
# no event will the authors be held liable for any damages arising from the use
# of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it freely,
# subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not claim
# that you wrote the original software. If you use this software in a
# product, an acknowledgment in the product documentation would be
# appreciated but is not required.
#
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
#
# 3. This notice may not be removed or altered from any source distribution.
#
#
import math
import copy
class Glicko2:
"""
Glicko-2 Rating calculator class.
This class implements the Glicko-2 rating system algorithm written by
Professor Mark E. Glickman. All rating inputs and outputs are Glicko ratings,
but internally everything is converted to and considered Glicko-2 ratings.
Glicko-2 is an improvement on the original Glicko, which was, in turn, an
improvement on the ELO system.
The Glicko-2 system is specified on http://www.glicko.com/
"""
WIN = 1.0
LOSS = 0.0
DRAW = 0.5
dvolatility = 0.3
def __init__ (self, rating = 1500.0, deviation = 350.0, volatility = 0.06):
"""
Constructor with rating, rating deviation, and volatility optionally
specified. If nothing specified, nitializes to a rating of 1500, a
rating deviation of 350, and a volatility of 0.06.
"""
# creat state vars
self.__rating = 0.0
self.__deviation = 0.0
self.__volatility = 0.0
# create empty result lists
self.__opponents = []
self.__results = []
# initialize state
self.SetRating(rating)
self.SetDeviation(deviation)
self.SetVolatility(volatility)
def __cmp__(self, other):
"""Comparison operator"""
return cmp( self.__rating, other.__rating )
def GetRating (self):
"""
Get the current rating. This rating is a Glicko rating, not a Glicko2 rating.
For details, please see http://www.glicko.com/
"""
return (self.__rating * 173.7178) + 1500.0
def GetDeviation (self):
"""
Get the current rating deviation. This is a Glicko rating deviation, not a
Glicko-2 RD. For details, please see http://www.glicko.com/
"""
return self.__deviation * 173.7178
def GetVolatility (self):
"""
Get the current rating volatility.
"""
return self.__volatility
def SetRating (self, rating):
"""
Set Glicko rating. Internally this is converted to a Glicko2 rating.
For details, please see http://www.glicko.com/
"""
self.__rating = (rating - 1500.0) / 173.7178
def SetDeviation(self, deviation):
"""
Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD.
For details, please see http://www.glicko.com/
"""
self.__deviation = deviation / 173.7178
def SetVolatility(self, volatility):
"""
Set rating volatility.
"""
self.__volatility = volatility
def ClearResults (self):
"""
Clear all results previously added via AddResult(), AddWin(), AddLoss(),
and/or AddDraw(). This method is called automatically whenever Update()
is called.
"""
self.__opponents = []
self.__results = []
def AddResult (self, opponent, result):
"""
Add a result to this rating. Note that no calculation is performed until
Update() is called.
"""
self.__opponents.append(copy.copy(opponent))
self.__results.append(copy.copy(result)) # copy probably not needed
def AddWin (self, opponent):
"""
Add a win result to this rating. Note that no calculation is performed until
Update() is called.
"""
self.AddResult(opponent, self.WIN)
def AddLoss (self, opponent):
"""
Add a loss result to this rating. Note that no calculation is performed until
Update() is called.
"""
self.AddResult(opponent, self.LOSS)
def AddDraw (self, opponent):
"""
Add a draw result to this rating. Note that no calculation is performed until
Update() is called.
"""
self.AddResult(opponent, self.DRAW)
def Update (self):
"""
Update rating based on current results list, and clear results.
"""
# util func
def g (deviation):
"""
Internal utility function.
"""
return 1.0 / (math.sqrt(1.0 + 3.0 * deviation * deviation / (math.pi * math.pi)));
# util func
def E (rating, rating_opponent, deviation_opponent):
"""
Internal utility function.
"""
return 1.0 / (1.0 + math.exp(-g(deviation_opponent)*(rating - rating_opponent)));
# bail if no opponents set
if len(self.__opponents) == 0:
return
# compute variance
variance = 0.0
for opp in self.__opponents:
g_i = g(opp.__deviation)
E_i = E(self.__rating,opp.__rating,opp.__deviation)
variance += g_i * g_i * E_i * (1.0 - E_i)
variance = 1.0 / variance
# compute delta
delta = 0.0
for i in range(len(self.__opponents)):
delta += g(self.__opponents[i].__deviation) * (self.__results[i] - E(self.__rating, self.__opponents[i].__rating, self.__opponents[i].__deviation))
delta *= variance
# determine new volatility
new_volatility = 0.0
a = math.log( math.pow(self.__volatility,2.0) )
x = 0.0
x_new = a
while ( abs(x - x_new) > 0.0000001 ):
x = x_new;
d = math.pow(self.__deviation,2.0) + variance + math.exp(x)
h1 = -(x - a)/(math.pow(self.dvolatility,2.0)) - 0.5*math.exp(x)/d + 0.5*math.exp(x)*(delta/d)*(delta/d)
h2 = -1.0/(math.pow(self.dvolatility,2.0)) - 0.5*math.exp(x)*(math.pow(self.__deviation,2.0)+variance)/(pow(d,2.0)) + 0.5*(math.pow(delta,2.0))*math.exp(x)*((math.pow(self.__deviation,2.0)) + variance - math.exp(x))/(pow(d,3.0))
x_new = x - h1/h2
new_volatility = math.exp(x_new / 2.0)
# update the rating deviation to the new pre-rating period value
pre_deviation = math.sqrt( math.pow(self.__deviation,2.0) + math.pow(new_volatility,2.0) )
# update the rating and deviation
new_deviation = 1.0 / (math.sqrt( 1.0/(pow(pre_deviation,2.0)) + 1.0 / variance))
new_rating = 0.0
for i in range(len(self.__opponents)):
new_rating += g(self.__opponents[i].__deviation) * (self.__results[i] - E(self.__rating, self.__opponents[i].__rating, self.__opponents[i].__deviation))
new_rating = new_rating * pow(new_deviation,2.0)
new_rating += self.__rating
# copy new values
self.__rating = new_rating
self.__deviation = new_deviation
self.__volatility = new_volatility
# wipe our result lists
self.ClearResults()
# test code
if __name__ == "__main__":
A = Glicko2(1500.0, 200.0, 0.06)
B = Glicko2(1400.0, 30.0, 0.06)
C = Glicko2(1550.0, 100.0, 0.06)
D = Glicko2(1700.0, 300.0, 0.06)
A.AddWin(B)
A.AddLoss(C)
A.AddResult(D,Glicko2.LOSS)
A.Update();
print "rating",A.GetRating(), "deviation",A.GetDeviation()