diff --git a/cpp/glicko2.cpp b/cpp/glicko2.cpp index 5f1e431..9be4739 100644 --- a/cpp/glicko2.cpp +++ b/cpp/glicko2.cpp @@ -33,33 +33,33 @@ class Glicko2_impl { -public: + public: - // constructors - Glicko2_impl(); - Glicko2_impl(const Glicko2_impl& rhs); + // constructors + Glicko2_impl(); + Glicko2_impl(const Glicko2_impl& rhs); - // copy assignment - Glicko2_impl& operator=(const Glicko2_impl& rhs); + // copy assignment + Glicko2_impl& operator=(const Glicko2_impl& rhs); - // destructor - virtual ~Glicko2_impl(); + // destructor + virtual ~Glicko2_impl(); - // system constants - static const double dvolatility; + // system constants + static const double dvolatility; - // rating data - double rating; - double deviation; - double volatility; + // rating data + double rating; + double deviation; + double volatility; - // result data (copy of each opponent and results, 0.0, 0.5, or 1.0) - std::vector opponents; - std::vector results; + // result data (copy of each opponent and results, 0.0, 0.5, or 1.0) + std::vector opponents; + std::vector results; - // utility functions - double g(const double& deviation); - double E(const double& rating, const double& rating_opponent, const double& deviation_opponent); + // utility functions + double g(const double& deviation); + 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) { - #define PI_SQUARED (9.86960440108935861883) +#define PI_SQUARED (9.86960440108935861883) return 1.0 / (sqrt(1.0 + 3.0 * deviation * deviation / PI_SQUARED)); } diff --git a/cpp/glicko2.h b/cpp/glicko2.h index 889fc77..4aeca8c 100644 --- a/cpp/glicko2.h +++ b/cpp/glicko2.h @@ -47,195 +47,195 @@ class Glicko2_impl; */ 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 - * of 350, and a volatility of 0.06. - */ - 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); + /** + * Copy assignment operator. + * + * @param rhs Object to copy. + * + * @return Reference to self after copy assignment. + */ + Glicko2& operator=(const Glicko2& rhs); - /** - * Copy assignment operator. - * - * @param rhs Object to copy. - * - * @return Reference to self after copy assignment. - */ - Glicko2& operator=(const Glicko2& rhs); + /** + * Destructor. + */ + virtual ~Glicko2(); - /** - * Destructor. - */ - virtual ~Glicko2(); + /** + * Less than comparison operator. + * + * @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. - * - * @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); + /** + * Get the current rating. This rating is a Glicko rating, not a Glicko2 rating. + * For details, please see http://www.glicko.com/ + * + * @return + */ + double GetRating() const; + + /** + * 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. - * For details, please see http://www.glicko.com/ - * - * @return - */ - double GetRating() const; + /** + * Set Glicko rating. Internally this is converted to a Glicko2 rating. + * For details, please see http://www.glicko.com/ + * + * @param rating Rating. + */ + void SetRating(double rating); - /** - * 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; + /** + * 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); - /** - * Get the current rating volatility. - * - * @return - */ - double GetVolatility() const; + /** + * Set rating volatility. + * + * @param volatility Rating volatility. + */ + void SetVolatility(double volatility); - /** - * Set Glicko rating. Internally this is converted to a Glicko2 rating. - * For details, please see http://www.glicko.com/ - * - * @param rating Rating. - */ - 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(), + * and/or AddDraw(). This method is called automatically whenever Update() + * is called. + */ + void ClearResults(); - /** - * Clear all results previously added via AddResult(), AddWin(), AddLoss(), - * and/or AddDraw(). This method is called automatically whenever Update() - * is called. - */ - void ClearResults(); + /** + * Add a result to this rating. Note that no calculation is performed until + * Update() is called. + * + * @param opponent Other player in contest. + * @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 - * Update() is called. - * - * @param opponent Other player in contest. - * @param result WIN, LOSS, or DRAW; from the point of view of this player. - */ - void AddResult(const Glicko2& opponent, RESULT result); + /** + * Add a win result to this rating. Note that no calculation is performed until + * Update() is called. + * + * @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); - /** - * Add a win result to this rating. Note that no calculation is performed until - * Update() is called. - * - * @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); + /** + * Update rating based on current results list, and clear results. + */ + void Update(); - /** - * Update rating based on current results list, and clear results. - */ - void Update(); + private: - - -private: - - /** - * Private Implementation. - */ - Glicko2_impl* pimpl; + /** + * Private Implementation. + */ + Glicko2_impl* pimpl; }; diff --git a/py/glicko2.py b/py/glicko2.py index b47061d..06f3af7 100644 --- a/py/glicko2.py +++ b/py/glicko2.py @@ -30,14 +30,14 @@ 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/ """ @@ -229,12 +229,12 @@ if __name__ == "__main__": 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() diff --git a/rb/glicko2.rb b/rb/glicko2.rb index 80bcf21..009ba86 100755 --- a/rb/glicko2.rb +++ b/rb/glicko2.rb @@ -38,179 +38,179 @@ # class Glicko2 - protected + protected - # Glicko2 reprensentation (Glicko2 uses different scales from Glicko) - attr_accessor :g2rating, :g2deviation, :g2volatility - - # a nested Class to hold a single result - Result = Struct.new(:opponent, :result) + # Glicko2 reprensentation (Glicko2 uses different scales from Glicko) + attr_accessor :g2rating, :g2deviation, :g2volatility - public + # a nested Class to hold a single result + Result = Struct.new(:opponent, :result) - WIN = 1.0 - 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 + public - include Comparable + WIN = 1.0 + LOSS = 0.0 + DRAW = 0.5 - # Comparison operator - def <=>(other) - self.rating <=> other.rating - end + # 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 - # Get the current rating. This rating is a Glicko rating, not a Glicko2 rating. - # For details, please see http://www.glicko.com/ - def rating - (@g2rating * 173.7178) + 1500.0 - end + include Comparable - # Set Glicko rating. Internally this is converted to a Glicko2 rating. - # For details, please see http://www.glicko.com/ - def rating=(r) - @g2rating = (r - 1500.0) / 173.7178 - end + # Comparison operator + def <=>(other) + self.rating <=> other.rating + end - # Get the current rating deviation. This is a Glicko rating deviation, not a - # Glicko-2 RD. For details, please see http://www.glicko.com/ - def deviation - @g2deviation * 173.7178 - end + # Get the current rating. This rating is a Glicko rating, not a Glicko2 rating. + # For details, please see http://www.glicko.com/ + def rating + (@g2rating * 173.7178) + 1500.0 + end - # Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD. - # For details, please see http://www.glicko.com/ - def deviation=(d) - @g2deviation = d / 173.7178 - end + # Set Glicko rating. Internally this is converted to a Glicko2 rating. + # For details, please see http://www.glicko.com/ + def rating=(r) + @g2rating = (r - 1500.0) / 173.7178 + end - # Get the current rating volatility. - def volatility - @g2volatility - end + # Get the current rating deviation. This is a Glicko rating deviation, not a + # Glicko-2 RD. For details, please see http://www.glicko.com/ + def deviation + @g2deviation * 173.7178 + end - # Set rating volatility. - def volatility=(v) - @g2volatility = v - end + # Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD. + # For details, please see http://www.glicko.com/ + def deviation=(d) + @g2deviation = d / 173.7178 + end - # Clear all results previously added via add_result(), add_win(), add_loss(), - # and/or add_draw(). This method is called automatically whenever update() - # is called. - def clear_results - @opponents = [] - @results = [] - end + # Get the current rating volatility. + def volatility + @g2volatility + end - # Add a result to this rating. Note that no calculation is performed until - # update() is called. - def add_result(opponent,result) - @results.push( Result.new(opponent.clone, result) ) - end + # Set rating volatility. + def volatility=(v) + @g2volatility = v + end - # Add a win result to this rating. Note that no calculation is performed until - # update() is called. - def add_win(opponent) - add_result(opponent,Glicko2::WIN) - end + # Clear all results previously added via add_result(), add_win(), add_loss(), + # and/or add_draw(). This method is called automatically whenever update() + # is called. + def clear_results + @opponents = [] + @results = [] + end - # Add a loss result to this rating. Note that no calculation is performed until - # update() is called. - def add_loss(opponent) - add_result(opponent,Glicko2::LOSS) - end + # Add a result to this rating. Note that no calculation is performed until + # update() is called. + def add_result(opponent,result) + @results.push( Result.new(opponent.clone, result) ) + end - # Add a draw result to this rating. Note that no calculation is performed until - # update() is called. - def add_draw(opponent) - add_result(opponent,Glicko2::DRAW) - end - - # Update rating based on current results list, and clear results. - 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 + # Add a win result to this rating. Note that no calculation is performed until + # update() is called. + def add_win(opponent) + add_result(opponent,Glicko2::WIN) + end - # compute variance + # Add a loss result to this rating. Note that no calculation is performed until + # update() is called. + def add_loss(opponent) + add_result(opponent,Glicko2::LOSS) + end - #variance = 0.0 - #@results.inject(0.0) do |variance,r| - # g_i = Glicko2.g(r.opponent.g2deviation) - # e_i = Glicko2.E(@g2rating,r.opponent.g2rating,r.opponent.g2deviation) - # g_i ** 2.0 * e_i * (1.0 - e_i) - #end + # Add a draw result to this rating. Note that no calculation is performed until + # update() is called. + def add_draw(opponent) + add_result(opponent,Glicko2::DRAW) + end - variance = 0.0 - @results.each do |r| - g_i = Glicko2.g(r.opponent.g2deviation) - e_i = Glicko2.E(@g2rating,r.opponent.g2rating,r.opponent.g2deviation) - variance += g_i ** 2.0 * e_i * (1.0 - e_i) - 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 rating based on current results list, and clear results. + def update - # update the rating and deviation - new_deviation = 1.0 / (Math.sqrt( 1.0/(pre_deviation**2.0) + 1.0 / variance)) - new_rating = 0.0 - @results.each do |r| - new_rating += Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation)) - end - new_rating = new_rating * new_deviation**2.0 - new_rating += @g2rating + # util func + def Glicko2.g(deviation) + 1.0 / (Math.sqrt(1.0 + 3.0 * deviation ** 2.0 / (Math::PI ** 2.0))) + end - # wipe our result lists - clear_results + # util func + def Glicko2.E(rating, rating_opponent, deviation_opponent) + 1.0 / (1.0 + Math.exp(-Glicko2.g(deviation_opponent)*(rating - rating_opponent))); + end - # copy new values - @g2deviation = new_deviation - @g2volatility = new_volatility - @g2rating = new_rating - end + # bail if no opponents set + if @results.empty? + return nil + end + + # compute variance + + #variance = 0.0 + #@results.inject(0.0) do |variance,r| + # g_i = Glicko2.g(r.opponent.g2deviation) + # e_i = Glicko2.E(@g2rating,r.opponent.g2rating,r.opponent.g2deviation) + # g_i ** 2.0 * e_i * (1.0 - e_i) + #end + + variance = 0.0 + @results.each do |r| + g_i = Glicko2.g(r.opponent.g2deviation) + e_i = Glicko2.E(@g2rating,r.opponent.g2rating,r.opponent.g2deviation) + variance += g_i ** 2.0 * e_i * (1.0 - e_i) + 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 + new_deviation = 1.0 / (Math.sqrt( 1.0/(pre_deviation**2.0) + 1.0 / variance)) + new_rating = 0.0 + @results.each do |r| + new_rating += Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation)) + end + new_rating = new_rating * new_deviation**2.0 + new_rating += @g2rating + + # wipe our result lists + clear_results + + # copy new values + @g2deviation = new_deviation + @g2volatility = new_volatility + @g2rating = new_rating + end end diff --git a/rb/test_glicko2.rb b/rb/test_glicko2.rb index e104ca6..a2da9ca 100644 --- a/rb/test_glicko2.rb +++ b/rb/test_glicko2.rb @@ -5,30 +5,30 @@ require 'test/unit' class TestGlicko2 < Test::Unit::TestCase - def test_simple - a = Glicko2.new(1500.0, 200.0, 0.06) - b = Glicko2.new(1400.0, 30.0, 0.06) - c = Glicko2.new(1550.0, 100.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) + def test_simple + a = Glicko2.new(1500.0, 200.0, 0.06) + b = Glicko2.new(1400.0, 30.0, 0.06) + c = Glicko2.new(1550.0, 100.0, 0.06) + d = Glicko2.new(1700.0, 300.0, 0.06) - a.update() - - assert_in_delta(1464.05,a.rating,0.01) - assert_in_delta(151.516,a.deviation,0.01) + 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() + + assert_in_delta(1464.05,a.rating,0.01) + assert_in_delta(151.516,a.deviation,0.01) + + end - end - end