Compare commits

..
1 Commits
Author SHA1 Message Date
swaits e639ee28c7 create 1.1 branch 2005-05-12 18:25:57 +00:00
4 changed files with 64 additions and 72 deletions

No files matched your search

+2 -2
View File
@@ -33,7 +33,7 @@
class Glicko2_impl class Glicko2_impl
{ {
public: public:
// constructors // constructors
Glicko2_impl(); Glicko2_impl();
@@ -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));
} }
+2 -2
View File
@@ -47,7 +47,7 @@ class Glicko2_impl;
*/ */
class Glicko2 class Glicko2
{ {
public: public:
@@ -230,7 +230,7 @@ class Glicko2
private: private:
/** /**
* Private Implementation. * Private Implementation.
+57 -65
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby -w #!/usr/bin/env ruby
# #
# #
@@ -41,7 +41,7 @@ class Glicko2
protected protected
# Glicko2 reprensentation (Glicko2 uses different scales from Glicko) # Glicko2 reprensentation (Glicko2 uses different scales from Glicko)
attr_reader :g2rating, :g2deviation, :g2volatility attr_accessor :g2rating, :g2deviation, :g2volatility
# a nested Class to hold a single result # a nested Class to hold a single result
Result = Struct.new(:opponent, :result) Result = Struct.new(:opponent, :result)
@@ -52,13 +52,11 @@ class Glicko2
LOSS = 0.0 LOSS = 0.0
DRAW = 0.5 DRAW = 0.5
# system constant, determines delta volatility over time; should be [0.3,1.2]
DVOL = 0.3
# Constructor with rating, rating deviation, and volatility optionally # Constructor with rating, rating deviation, and volatility optionally
# specified. If nothing specified, nitializes to a rating of 1500, a # specified. If nothing specified, nitializes to a rating of 1500, a
# rating deviation of 350, and a volatility of 0.06. # rating deviation of 350, and a volatility of 0.06.
def initialize(rating = 1500.0, deviation = 350.0, volatility = 0.06) def initialize(rating = 1500.0, deviation = 350.0, volatility = 0.06)
@dvolatility = 0.3
self.rating = rating self.rating = rating
self.deviation = deviation self.deviation = deviation
self.volatility = volatility self.volatility = volatility
@@ -69,12 +67,7 @@ class Glicko2
# Comparison operator # Comparison operator
def <=>(other) def <=>(other)
p_win(other) <=> 0.5 self.rating <=> other.rating
end
# get the probability of beating opponent
def p_win(opponent)
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))))))
end end
# Get the current rating. This rating is a Glicko rating, not a Glicko2 rating. # Get the current rating. This rating is a Glicko rating, not a Glicko2 rating.
@@ -115,14 +108,14 @@ class Glicko2
# and/or add_draw(). This method is called automatically whenever update() # and/or add_draw(). This method is called automatically whenever update()
# is called. # is called.
def clear_results def clear_results
@opponents = Array.new @opponents = []
@results = Array.new @results = []
end end
# Add a result to this rating. Note that no calculation is performed until # Add a result to this rating. Note that no calculation is performed until
# update() is called. # update() is called.
def add_result(opponent,result) def add_result(opponent,result)
@results << Result.new(opponent.clone, result) @results.push( Result.new(opponent.clone, result) )
end end
# Add a win 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
@@ -143,82 +136,81 @@ class Glicko2
add_result(opponent,Glicko2::DRAW) add_result(opponent,Glicko2::DRAW)
end end
# util func 'g' # Update rating based on current results list, and clear results.
def update
# util func
def Glicko2.g(deviation) def Glicko2.g(deviation)
1.0 / (Math.sqrt(1.0 + 3.0 * deviation ** 2.0 / (Math::PI ** 2.0))) 1.0 / (Math.sqrt(1.0 + 3.0 * deviation ** 2.0 / (Math::PI ** 2.0)))
end end
# util func 'E' # util func
def Glicko2.E(rating, rating_opponent, deviation_opponent) def Glicko2.E(rating, rating_opponent, deviation_opponent)
1.0 / (1.0 + Math.exp(-Glicko2.g(deviation_opponent)*(rating - rating_opponent))); 1.0 / (1.0 + Math.exp(-Glicko2.g(deviation_opponent)*(rating - rating_opponent)));
end end
# Update rating based on current results list, and clear results. # bail if no opponents set
def update
# Note that if a player does not compete during the rating period, then
# only Step 6 applies.
if @results.empty? if @results.empty?
# In this case, the player's rating and volatility parameters remain the return nil
# same, but the RD increases according to:
@g2deviation = Math.sqrt(@g2deviation**2.0 + @g2volatility**2.0)
return
end end
# Step 1. Determine a rating and RD for each player at the onset of the # compute variance
# 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 #variance = 0.0
# scale. #@results.inject(0.0) do |variance,r|
# ... (ratings already stored in G2 format) # 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
# Step 3. Compute the quantity v. This is the estimated variance of the variance = 0.0
# team's/player's rating based only on game outcomes. @results.each do |r|
variance = 1.0 / @results.inject(0.0) do |sum,r|
g_i = Glicko2.g(r.opponent.g2deviation) g_i = Glicko2.g(r.opponent.g2deviation)
e_i = Glicko2.E(@g2rating, r.opponent.g2rating, 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) variance += g_i ** 2.0 * e_i * (1.0 - e_i)
end end
variance = 1.0 / variance
# Step 4. Compute the quantity delta, the estimated improvement in rating # compute delta
# by comparing the pre-period rating to the performance rating based only delta = 0.0
# on game outcomes. @results.each do |r|
delta = variance * @results.inject(0.0) do |sum,r| delta += Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation))
sum + Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation))
end end
delta *= variance
# Step 5. Determine the new value of the volatility. # determine new volatility
a = Math.log(@g2volatility**2.0) new_volatility = 0.0
x0, x = 0.0, a a = Math.log( @g2volatility**2.0 )
while ((x0 - x).abs > 0.0000001) x = 0.0
x0 = x x_new = a
d = @g2deviation**2.0 + variance + Math.exp(x0) while ( (x - x_new).abs > 0.0000001 )
h1 = -(x0 - a)/(DVOL**2.0) - 0.5*Math.exp(x0)/d + 0.5*Math.exp(x0)*(delta/d)*(delta/d) x = x_new
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) d = @g2deviation**2.0 + variance + Math.exp(x)
x = x0 - h1/h2 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 end
new_volatility = Math.exp(x / 2.0) new_volatility = Math.exp(x_new / 2.0)
# Step 6. Update the rating deviation to the new pre-rating period value. # update the rating deviation to the new pre-rating period value
pre_deviation = Math.sqrt(@g2deviation**2.0 + new_volatility**2.0) pre_deviation = Math.sqrt( @g2deviation**2.0 + new_volatility**2.0 )
# Step 7. Update the rating and RD to the new values. # update the rating and deviation
new_deviation = 1.0 / (Math.sqrt(1.0/(pre_deviation**2.0) + 1.0 / variance)) 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| new_rating = 0.0
sum + Glicko2.g(r.opponent.g2deviation) * (r.result - Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation)) @results.each do |r|
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
# Step 8. Convert ratings and RD's back to original scale. new_rating += @g2rating
@g2deviation = new_deviation
@g2volatility = new_volatility
@g2rating = new_rating
# wipe our result lists # wipe our result lists
clear_results clear_results
end
# copy new values
@g2deviation = new_deviation
@g2volatility = new_volatility
@g2rating = new_rating
end
end end
+3 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby -w #!/usr/bin/env ruby
require_relative 'glicko2' require 'glicko2'
require 'test/unit' require 'test/unit'
class TestGlicko2 < Test::Unit::TestCase class TestGlicko2 < Test::Unit::TestCase
@@ -26,7 +26,7 @@ class TestGlicko2 < Test::Unit::TestCase
a.update() a.update()
assert_in_delta(1464.05,a.rating,0.01) assert_in_delta(1464.05,a.rating,0.01)
assert_in_delta(151.875,a.deviation,0.01) assert_in_delta(151.516,a.deviation,0.01)
end end