Compare commits

...
5 Commits
Author SHA1 Message Date
swaits fbce23608e add p_win method and misc cleanup and refactoring
* change g2rating,etc. to attr_reader (instead of attr_accessor)
* move @dvolatility into DVOL constant
* add p_win
* make comparison operator use p_win(self,other)
* use << when adding to results array instead of .push()
* improved comments; mostly from Glicko2 document
* minor variable renaming to improve readability
* clear results array after ratings are finally updated (no functional change, just cleaner)
2011-12-19 17:13:45 -08:00
swaits 59537258f7 refactoring (some formatting, mostly ruby'ization) 2011-12-19 15:23:11 -08:00
swaits b240814147 update() now handled case correctly when a player hasn't participated in a session
* when a player has no results, deviation is slightly increased properly now
2011-12-19 15:10:38 -08:00
swaits 723952b178 formatting updates 2011-04-26 18:03:21 -07:00
convert-repo 5b64f13b89 update tags 2010-10-01 05:44:06 +00:00
5 changed files with 361 additions and 353 deletions

No files matched your search

+21 -21
View File
@@ -33,33 +33,33 @@
class Glicko2_impl class Glicko2_impl
{ {
public: public:
// constructors // constructors
Glicko2_impl(); Glicko2_impl();
Glicko2_impl(const Glicko2_impl& rhs); Glicko2_impl(const Glicko2_impl& rhs);
// copy assignment // copy assignment
Glicko2_impl& operator=(const Glicko2_impl& rhs); Glicko2_impl& operator=(const Glicko2_impl& rhs);
// destructor // destructor
virtual ~Glicko2_impl(); virtual ~Glicko2_impl();
// system constants // system constants
static const double dvolatility; static const double dvolatility;
// rating data // rating data
double rating; double rating;
double deviation; double deviation;
double volatility; double volatility;
// result data (copy of each opponent and results, 0.0, 0.5, or 1.0) // result data (copy of each opponent and results, 0.0, 0.5, or 1.0)
std::vector<Glicko2> opponents; std::vector<Glicko2> opponents;
std::vector<double> results; std::vector<double> results;
// utility functions // utility functions
double g(const double& deviation); double g(const double& deviation);
double E(const double& rating, const double& rating_opponent, const double& deviation_opponent); double E(const double& rating, const double& rating_opponent, const double& deviation_opponent);
}; };
@@ -116,7 +116,7 @@ Glicko2_impl::~Glicko2_impl()
double Glicko2_impl::g(const double& deviation) double Glicko2_impl::g(const double& deviation)
{ {
#define PI_SQUARED (9.86960440108935861883) #define PI_SQUARED (9.86960440108935861883)
return 1.0 / (sqrt(1.0 + 3.0 * deviation * deviation / PI_SQUARED)); return 1.0 / (sqrt(1.0 + 3.0 * deviation * deviation / PI_SQUARED));
} }
+148 -148
View File
@@ -47,195 +47,195 @@ class Glicko2_impl;
*/ */
class Glicko2 class Glicko2
{ {
public: public:
/**
* Enumeration of possible results.
*/
enum RESULT
{
/**
* A win result, internally 1.0.
*/
WIN,
/** /**
* A loss result, internally 0.0. * Enumeration of possible results.
*/ */
LOSS, enum RESULT
{
/**
* A win result, internally 1.0.
*/
WIN,
/**
* A loss result, internally 0.0.
*/
LOSS,
/**
* A draw result, internally 0.5.
*/
DRAW
};
/** /**
* A draw result, internally 0.5. * Default constructor. Initializes to a rating of 1500, a rating deviation
* of 350, and a volatility of 0.06.
*/ */
DRAW Glicko2();
};
/**
* Copy constructor.
*
* @param rhs Object to copy.
*/
Glicko2(const Glicko2& rhs);
/**
* Constructor with rating, rating deviation, and volatility specified.
*
* @param rating Initial rating.
* @param deviation Initial rating deviation.
* @param volatility Initial volatility.
*/
Glicko2(double rating, double deviation, double volatility);
/** /**
* Default constructor. Initializes to a rating of 1500, a rating deviation * Copy assignment operator.
* of 350, and a volatility of 0.06. *
*/ * @param rhs Object to copy.
Glicko2(); *
* @return Reference to self after copy assignment.
/** */
* Copy constructor. Glicko2& operator=(const Glicko2& rhs);
*
* @param rhs Object to copy.
*/
Glicko2(const Glicko2& rhs);
/**
* Constructor with rating, rating deviation, and volatility specified.
*
* @param rating Initial rating.
* @param deviation Initial rating deviation.
* @param volatility Initial volatility.
*/
Glicko2(double rating, double deviation, double volatility);
/** /**
* Copy assignment operator. * Destructor.
* */
* @param rhs Object to copy. virtual ~Glicko2();
*
* @return Reference to self after copy assignment.
*/
Glicko2& operator=(const Glicko2& rhs);
/** /**
* Destructor. * Less than comparison operator.
*/ *
virtual ~Glicko2(); * @param rhs Object to compare against.
*
* @return true if this object's rating is less than rhs's rating; false otherwise.
*/
bool operator< (const Glicko2& rhs);
/** /**
* Less than comparison operator. * Get the current rating. This rating is a Glicko rating, not a Glicko2 rating.
* * For details, please see http://www.glicko.com/
* @param rhs Object to compare against. *
* * @return
* @return true if this object's rating is less than rhs's rating; false otherwise. */
*/ double GetRating() const;
bool operator< (const Glicko2& rhs);
/**
* 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
*/
double GetDeviation() const;
/**
* Get the current rating volatility.
*
* @return
*/
double GetVolatility() const;
/** /**
* Get the current rating. This rating is a Glicko rating, not a Glicko2 rating. * Set Glicko rating. Internally this is converted to a Glicko2 rating.
* For details, please see http://www.glicko.com/ * For details, please see http://www.glicko.com/
* *
* @return * @param rating Rating.
*/ */
double GetRating() const; void SetRating(double rating);
/** /**
* Get the current rating deviation. This is a Glicko rating deviation, not a * Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD.
* Glicko-2 RD. For details, please see http://www.glicko.com/ * For details, please see http://www.glicko.com/
* *
* @return * @param deviation Rating deviation.
*/ */
double GetDeviation() const; void SetDeviation(double deviation);
/** /**
* Get the current rating volatility. * Set rating volatility.
* *
* @return * @param volatility Rating volatility.
*/ */
double GetVolatility() const; void SetVolatility(double volatility);
/** /**
* Set Glicko rating. Internally this is converted to a Glicko2 rating. * Clear all results previously added via AddResult(), AddWin(), AddLoss(),
* For details, please see http://www.glicko.com/ * and/or AddDraw(). This method is called automatically whenever Update()
* * is called.
* @param rating Rating. */
*/ void ClearResults();
void SetRating(double rating);
/**
* Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD.
* For details, please see http://www.glicko.com/
*
* @param deviation Rating deviation.
*/
void SetDeviation(double deviation);
/**
* Set rating volatility.
*
* @param volatility Rating volatility.
*/
void SetVolatility(double volatility);
/** /**
* Clear all results previously added via AddResult(), AddWin(), AddLoss(), * Add a result to this rating. Note that no calculation is performed until
* and/or AddDraw(). This method is called automatically whenever Update() * Update() is called.
* is called. *
*/ * @param opponent Other player in contest.
void ClearResults(); * @param result WIN, LOSS, or DRAW; from the point of view of this player.
*/
void AddResult(const Glicko2& opponent, RESULT result);
/** /**
* Add a result to this rating. Note that no calculation is performed until * Add a win result to this rating. Note that no calculation is performed until
* Update() is called. * Update() is called.
* *
* @param opponent Other player in contest. * @param opponent Other (losing) player in contest.
* @param result WIN, LOSS, or DRAW; from the point of view of this player. */
*/ void AddWin(const Glicko2& opponent);
void AddResult(const Glicko2& opponent, RESULT result);
/**
* Add a loss result to this rating. Note that no calculation is performed until
* Update() is called.
*
* @param opponent Other (winning) player in contest.
*/
void AddLoss(const Glicko2& opponent);
/**
* Add a draw result to this rating. Note that no calculation is performed until
* Update() is called.
*
* @param opponent Other (drawing) player in contest.
*/
void AddDraw(const Glicko2& opponent);
/** /**
* Add a win result to this rating. Note that no calculation is performed until * Update rating based on current results list, and clear results.
* Update() is called. */
* void Update();
* @param opponent Other (losing) player in contest.
*/
void AddWin(const Glicko2& opponent);
/**
* Add a loss result to this rating. Note that no calculation is performed until
* Update() is called.
*
* @param opponent Other (winning) player in contest.
*/
void AddLoss(const Glicko2& opponent);
/**
* Add a draw result to this rating. Note that no calculation is performed until
* Update() is called.
*
* @param opponent Other (drawing) player in contest.
*/
void AddDraw(const Glicko2& opponent);
/** private:
* Update rating based on current results list, and clear results.
*/
void Update();
/**
* Private Implementation.
private: */
Glicko2_impl* pimpl;
/**
* Private Implementation.
*/
Glicko2_impl* pimpl;
}; };
+6 -6
View File
@@ -30,14 +30,14 @@ import copy
class Glicko2: class Glicko2:
""" """
Glicko-2 Rating calculator class. Glicko-2 Rating calculator class.
This class implements the Glicko-2 rating system algorithm written by This class implements the Glicko-2 rating system algorithm written by
Professor Mark E. Glickman. All rating inputs and outputs are Glicko ratings, Professor Mark E. Glickman. All rating inputs and outputs are Glicko ratings,
but internally everything is converted to and considered Glicko-2 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 Glicko-2 is an improvement on the original Glicko, which was, in turn, an
improvement on the ELO system. improvement on the ELO system.
The Glicko-2 system is specified on http://www.glicko.com/ The Glicko-2 system is specified on http://www.glicko.com/
""" """
@@ -229,12 +229,12 @@ if __name__ == "__main__":
B = Glicko2(1400.0, 30.0, 0.06) B = Glicko2(1400.0, 30.0, 0.06)
C = Glicko2(1550.0, 100.0, 0.06) C = Glicko2(1550.0, 100.0, 0.06)
D = Glicko2(1700.0, 300.0, 0.06) D = Glicko2(1700.0, 300.0, 0.06)
A.AddWin(B) A.AddWin(B)
A.AddLoss(C) A.AddLoss(C)
A.AddResult(D,Glicko2.LOSS) A.AddResult(D,Glicko2.LOSS)
A.Update(); A.Update();
print "rating",A.GetRating(), "deviation",A.GetDeviation() print "rating",A.GetRating(), "deviation",A.GetDeviation()
+161 -153
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby -w
# #
# #
@@ -38,179 +38,187 @@
# #
class Glicko2 class Glicko2
protected protected
# Glicko2 reprensentation (Glicko2 uses different scales from Glicko) # Glicko2 reprensentation (Glicko2 uses different scales from Glicko)
attr_accessor :g2rating, :g2deviation, :g2volatility attr_reader :g2rating, :g2deviation, :g2volatility
# a nested Class to hold a single result
Result = Struct.new(:opponent, :result)
public # a nested Class to hold a single result
Result = Struct.new(:opponent, :result)
WIN = 1.0 public
LOSS = 0.0
DRAW = 0.5
# 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.
def initialize(rating = 1500.0, deviation = 350.0, volatility = 0.06)
@dvolatility = 0.3
self.rating = rating
self.deviation = deviation
self.volatility = volatility
clear_results
end
include Comparable WIN = 1.0
LOSS = 0.0
DRAW = 0.5
# Comparison operator # system constant, determines delta volatility over time; should be [0.3,1.2]
def <=>(other) DVOL = 0.3
self.rating <=> other.rating
end
# Get the current rating. This rating is a Glicko rating, not a Glicko2 rating. # Constructor with rating, rating deviation, and volatility optionally
# For details, please see http://www.glicko.com/ # specified. If nothing specified, nitializes to a rating of 1500, a
def rating # rating deviation of 350, and a volatility of 0.06.
(@g2rating * 173.7178) + 1500.0 def initialize(rating = 1500.0, deviation = 350.0, volatility = 0.06)
end self.rating = rating
self.deviation = deviation
self.volatility = volatility
clear_results
end
# Set Glicko rating. Internally this is converted to a Glicko2 rating. include Comparable
# For details, please see http://www.glicko.com/
def rating=(r)
@g2rating = (r - 1500.0) / 173.7178
end
# Get the current rating deviation. This is a Glicko rating deviation, not a # Comparison operator
# Glicko-2 RD. For details, please see http://www.glicko.com/ def <=>(other)
def deviation p_win(other) <=> 0.5
@g2deviation * 173.7178 end
end
# Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD. # get the probability of beating opponent
# For details, please see http://www.glicko.com/ def p_win(opponent)
def deviation=(d) 1.0 / (1.0 + 10.0**(((opponent.rating - self.rating) / (400.0 * Math.sqrt(1.0 + 0.0000100723986 * (self.deviation**2.0 + opponent.deviation**2.0))))))
@g2deviation = d / 173.7178 end
end
# Get the current rating volatility. # Get the current rating. This rating is a Glicko rating, not a Glicko2 rating.
def volatility # For details, please see http://www.glicko.com/
@g2volatility def rating
end (@g2rating * 173.7178) + 1500.0
end
# Set rating volatility. # Set Glicko rating. Internally this is converted to a Glicko2 rating.
def volatility=(v) # For details, please see http://www.glicko.com/
@g2volatility = v def rating=(r)
end @g2rating = (r - 1500.0) / 173.7178
end
# Clear all results previously added via add_result(), add_win(), add_loss(), # Get the current rating deviation. This is a Glicko rating deviation, not a
# and/or add_draw(). This method is called automatically whenever update() # Glicko-2 RD. For details, please see http://www.glicko.com/
# is called. def deviation
def clear_results @g2deviation * 173.7178
@opponents = [] end
@results = []
end
# Add a result to this rating. Note that no calculation is performed until # Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD.
# update() is called. # For details, please see http://www.glicko.com/
def add_result(opponent,result) def deviation=(d)
@results.push( Result.new(opponent.clone, result) ) @g2deviation = d / 173.7178
end end
# Add a win result to this rating. Note that no calculation is performed until # Get the current rating volatility.
# update() is called. def volatility
def add_win(opponent) @g2volatility
add_result(opponent,Glicko2::WIN) end
end
# Add a loss result to this rating. Note that no calculation is performed until # Set rating volatility.
# update() is called. def volatility=(v)
def add_loss(opponent) @g2volatility = v
add_result(opponent,Glicko2::LOSS) end
end
# Add a draw result to this rating. Note that no calculation is performed until # Clear all results previously added via add_result(), add_win(), add_loss(),
# update() is called. # and/or add_draw(). This method is called automatically whenever update()
def add_draw(opponent) # is called.
add_result(opponent,Glicko2::DRAW) def clear_results
end @opponents = Array.new
@results = Array.new
# Update rating based on current results list, and clear results. end
def update
# util func
def Glicko2.g(deviation)
1.0 / (Math.sqrt(1.0 + 3.0 * deviation ** 2.0 / (Math::PI ** 2.0)))
end
# util func
def Glicko2.E(rating, rating_opponent, deviation_opponent)
1.0 / (1.0 + Math.exp(-Glicko2.g(deviation_opponent)*(rating - rating_opponent)));
end
# bail if no opponents set
if @results.empty?
return nil
end
# compute variance # Add a result to this rating. Note that no calculation is performed until
# update() is called.
def add_result(opponent,result)
@results << Result.new(opponent.clone, result)
end
#variance = 0.0 # Add a win result to this rating. Note that no calculation is performed until
#@results.inject(0.0) do |variance,r| # update() is called.
# g_i = Glicko2.g(r.opponent.g2deviation) def add_win(opponent)
# e_i = Glicko2.E(@g2rating,r.opponent.g2rating,r.opponent.g2deviation) add_result(opponent,Glicko2::WIN)
# g_i ** 2.0 * e_i * (1.0 - e_i) end
#end
variance = 0.0 # Add a loss result to this rating. Note that no calculation is performed until
@results.each do |r| # update() is called.
g_i = Glicko2.g(r.opponent.g2deviation) def add_loss(opponent)
e_i = Glicko2.E(@g2rating,r.opponent.g2rating,r.opponent.g2deviation) add_result(opponent,Glicko2::LOSS)
variance += g_i ** 2.0 * e_i * (1.0 - e_i) end
end
variance = 1.0 / variance
# compute delta
delta = 0.0
@results.each do |r|
delta += Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation))
end
delta *= variance
# determine new volatility
new_volatility = 0.0
a = Math.log( @g2volatility**2.0 )
x = 0.0
x_new = a
while ( (x - x_new).abs > 0.0000001 )
x = x_new
d = @g2deviation**2.0 + variance + Math.exp(x)
h1 = -(x - a)/(@dvolatility**2.0) - 0.5*Math.exp(x)/d + 0.5*Math.exp(x)*(delta/d)*(delta/d)
h2 = -1.0/(@dvolatility**2.0) - 0.5*Math.exp(x)*(@g2deviation**2.0+variance)/(d**2.0) + 0.5*(delta**2.0)*Math.exp(x)*((@g2deviation**2.0) + variance - Math.exp(x))/(d**3.0)
x_new = x - h1/h2
end
new_volatility = Math.exp(x_new / 2.0)
# update the rating deviation to the new pre-rating period value
pre_deviation = Math.sqrt( @g2deviation**2.0 + new_volatility**2.0 )
# update the rating and deviation # Add a draw result to this rating. Note that no calculation is performed until
new_deviation = 1.0 / (Math.sqrt( 1.0/(pre_deviation**2.0) + 1.0 / variance)) # update() is called.
new_rating = 0.0 def add_draw(opponent)
@results.each do |r| add_result(opponent,Glicko2::DRAW)
new_rating += Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation)) end
end
new_rating = new_rating * new_deviation**2.0
new_rating += @g2rating
# wipe our result lists # util func 'g'
clear_results def Glicko2.g(deviation)
1.0 / (Math.sqrt(1.0 + 3.0 * deviation ** 2.0 / (Math::PI ** 2.0)))
end
# util func 'E'
def Glicko2.E(rating, rating_opponent, deviation_opponent)
1.0 / (1.0 + Math.exp(-Glicko2.g(deviation_opponent)*(rating - rating_opponent)));
end
# Update rating based on current results list, and clear results.
def update
# Note that if a player does not compete during the rating period, then
# only Step 6 applies.
if @results.empty?
# In this case, the player's rating and volatility parameters remain the
# same, but the RD increases according to:
@g2deviation = Math.sqrt(@g2deviation**2.0 + @g2volatility**2.0)
return
end
# Step 1. Determine a rating and RD for each player at the onset of the
# rating period. The system constant which constrains the change in
# volatility over time, needs to be set prior to application of the system.
# Reasonable choices are between 0.3 and 1.2, though the system should be
# tested to decide which value results in greatest predictive accuracy
# ... (ratings already stored in instance)
# Step 2. For each player, convert the ratings and RD's onto the Glicko-2
# scale.
# ... (ratings already stored in G2 format)
# Step 3. Compute the quantity v. This is the estimated variance of the
# team's/player's rating based only on game outcomes.
variance = 1.0 / @results.inject(0.0) do |sum,r|
g_i = Glicko2.g(r.opponent.g2deviation)
e_i = Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation)
sum + g_i**2.0 * e_i * (1.0 - e_i)
end
# Step 4. Compute the quantity delta, the estimated improvement in rating
# by comparing the pre-period rating to the performance rating based only
# on game outcomes.
delta = variance * @results.inject(0.0) do |sum,r|
sum + Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation))
end
# Step 5. Determine the new value of the volatility.
a = Math.log(@g2volatility**2.0)
x0, x = 0.0, a
while ((x0 - x).abs > 0.0000001)
x0 = x
d = @g2deviation**2.0 + variance + Math.exp(x0)
h1 = -(x0 - a)/(DVOL**2.0) - 0.5*Math.exp(x0)/d + 0.5*Math.exp(x0)*(delta/d)*(delta/d)
h2 = -1.0/(DVOL**2.0) - 0.5*Math.exp(x0)*(@g2deviation**2.0+variance)/(d**2.0) + 0.5*(delta**2.0)*Math.exp(x0)*((@g2deviation**2.0) + variance - Math.exp(x0))/(d**3.0)
x = x0 - h1/h2
end
new_volatility = Math.exp(x / 2.0)
# Step 6. Update the rating deviation to the new pre-rating period value.
pre_deviation = Math.sqrt(@g2deviation**2.0 + new_volatility**2.0)
# Step 7. Update the rating and RD to the new values.
new_deviation = 1.0 / (Math.sqrt(1.0/(pre_deviation**2.0) + 1.0 / variance))
new_rating = @g2rating + new_deviation**2.0 * @results.inject(0.0) do |sum,r|
sum + Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation))
end
# Step 8. Convert ratings and RD's back to original scale.
@g2deviation = new_deviation
@g2volatility = new_volatility
@g2rating = new_rating
# wipe our result lists
clear_results
end
# copy new values
@g2deviation = new_deviation
@g2volatility = new_volatility
@g2rating = new_rating
end
end end
+25 -25
View File
@@ -1,34 +1,34 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby -w
require 'glicko2' require_relative 'glicko2'
require 'test/unit' require 'test/unit'
class TestGlicko2 < Test::Unit::TestCase class TestGlicko2 < Test::Unit::TestCase
def test_simple def test_simple
a = Glicko2.new(1500.0, 200.0, 0.06) a = Glicko2.new(1500.0, 200.0, 0.06)
b = Glicko2.new(1400.0, 30.0, 0.06) b = Glicko2.new(1400.0, 30.0, 0.06)
c = Glicko2.new(1550.0, 100.0, 0.06) c = Glicko2.new(1550.0, 100.0, 0.06)
d = Glicko2.new(1700.0, 300.0, 0.06) d = Glicko2.new(1700.0, 300.0, 0.06)
a.add_win(b)
a.add_loss(c)
a.add_result(d,0.0)
assert_equal(1500.0, a.rating)
assert_equal(200.0, a.deviation)
a.update()
assert_in_delta(1464.05,a.rating,0.01)
assert_in_delta(151.516,a.deviation,0.01)
a.update() a.add_win(b)
a.add_loss(c)
assert_in_delta(1464.05,a.rating,0.01) a.add_result(d,0.0)
assert_in_delta(151.516,a.deviation,0.01)
assert_equal(1500.0, a.rating)
assert_equal(200.0, a.deviation)
a.update()
assert_in_delta(1464.05,a.rating,0.01)
assert_in_delta(151.516,a.deviation,0.01)
a.update()
assert_in_delta(1464.05,a.rating,0.01)
assert_in_delta(151.875,a.deviation,0.01)
end
end
end end