Compare commits

...
16 Commits
Author SHA1 Message Date
swaits 4167b05bc0 chore: remove Sourcehut CI manifest
Migrated to git.swaits.com; Sourcehut builds no longer run.
2026-08-31 17:16:26 -06:00
swaits 51ca8a90eb add github mirror 2024-01-29 10:37:49 -07:00
swaits c5fa604d86 add LICENSE 2020-04-24 09:42:09 -07:00
Stephen Waits e470e1ec76 add README.md with description from previous Bitbucket repo 2020-04-24 08:20:32 -07:00
swaits e8a0474c8b add and update license 2013-07-30 02:15:09 -07:00
swaits 73bc8ffa23 add comments, change constant name, add ProbWin() 2013-07-30 02:12:55 -07:00
swaits f4b06ae191 merge 2013-07-30 01:54:47 -07:00
swaits a8d34856b2 first version in go 2013-07-30 01:42:45 -07:00
swaits cbe79afd1f add decay when results are empty and retab file 2013-07-30 01:42:17 -07:00
swaits 6d99d00885 cleanup
* pull more stuff into temps to clarify long equations
2011-12-19 17:39:28 -08:00
swaits 26d92ea617 misc. cleanup
* minor variable rename for clarity (x => x1)
* use temps in long calculation (rating update) for clarity
2011-12-19 17:31:58 -08:00
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
9 changed files with 692 additions and 354 deletions

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
Copyright 2013 Stephen Waits <steve@waits.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
An implementation of the Glicko2 algorithm in several languages (ruby,
python, c++).
+21 -21
View File
@@ -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<Glicko2> opponents;
std::vector<double> results;
// result data (copy of each opponent and results, 0.0, 0.5, or 1.0)
std::vector<Glicko2> opponents;
std::vector<double> 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));
}
+148 -148
View File
@@ -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;
};
+258
View File
@@ -0,0 +1,258 @@
//
//
// Copyright (c) 2013 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.
//
//
//
// Glicko-2 Rating calculator package.
//
// This package 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/
//
package glicko2
import (
"fmt"
"math"
)
// system constant, determines delta volatility over time; should be [0.3,1.2]
const kDVOL float64 = 0.3
// Glicko2 state reprensentation (Glicko2 uses different scales from Glicko)
type Glicko2 struct {
rating float64
deviation float64
volatility float64
wins [](*Glicko2)
losses [](*Glicko2)
draws [](*Glicko2)
}
// Initializer with rating, rating deviation, and volatility specified.
func NewGlicko2(rating float64, deviation float64, volatility float64) *Glicko2 {
return newGlicko2(rating, deviation, volatility)
}
// Initializer with default rating, rating deviation, and volatility.
func NewDefaultGlicko2() *Glicko2 {
return newGlicko2(1500.0, 350.0, 0.06)
}
// helper for initialization
func newGlicko2(rating float64, deviation float64, volatility float64) *Glicko2 {
g := new(Glicko2)
g.SetRating(rating)
g.SetDeviation(deviation)
g.SetVolatility(volatility)
return g
}
// get the probability of beating opponent
func (g *Glicko2) ProbWin(opponent *Glicko2) float64 {
return 1.0 / (1.0 + math.Pow(10.0, ((opponent.Rating()-g.Rating())/(400.0*math.Sqrt(1.0+0.0000100723986*((g.Deviation()*g.Deviation())+(opponent.Deviation()*opponent.Deviation())))))))
}
// Get the current rating. This rating is a Glicko rating, not a Glicko2 rating.
// For details, please see http://www.glicko.com/
func (g *Glicko2) Rating() float64 {
return ((g.rating * 173.7178) + 1500.0)
}
// Set Glicko rating. Internally this is converted to a Glicko2 rating.
// For details, please see http://www.glicko.com/
func (g *Glicko2) SetRating(rating float64) {
g.rating = (rating - 1500.0) / 173.7178
}
// Get the current rating deviation. This is a Glicko rating deviation, not a
// Glicko-2 RD. For details, please see http://www.glicko.com/
func (g *Glicko2) Deviation() float64 {
return (g.deviation * 173.7178)
}
// Set Glicko rating deviation. Internally this is converted to a Glicko-2 RD.
// For details, please see http://www.glicko.com/
func (g *Glicko2) SetDeviation(deviation float64) {
g.deviation = deviation / 173.7178
}
// Get the current rating volatility.
func (g *Glicko2) Volatility() float64 {
return g.volatility
}
// Set rating volatility.
func (g *Glicko2) SetVolatility(volatility float64) {
g.volatility = volatility
}
// helper to make a copy of a Glicko2 struct
func (g *Glicko2) duplicate() *Glicko2 {
c := *g
return &c
}
// 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.
func (g *Glicko2) ClearResults() {
g.wins = nil
g.losses = nil
g.draws = nil
}
// Add a win result to this rating. Note that no calculation is performed until
// Update() is called.
func (g *Glicko2) AddWin(loser *Glicko2) {
g.wins = append(g.wins, loser.duplicate())
}
// Add a loss result to this rating. Note that no calculation is performed until
// Update() is called.
func (g *Glicko2) AddLoss(winner *Glicko2) {
g.losses = append(g.losses, winner.duplicate())
}
// Add a draw result to this rating. Note that no calculation is performed until
// Update() is called.
func (g *Glicko2) AddDraw(drawer *Glicko2) {
g.draws = append(g.draws, drawer.duplicate())
}
// Print a Glicko2 struct
func (g *Glicko2) Print() {
fmt.Printf(" rating: %v\n", g.Rating())
fmt.Printf(" deviation: %v\n", g.Deviation())
fmt.Printf("volatility: %v\n", g.Volatility())
for _, win := range g.wins {
fmt.Printf(" *beat\n")
fmt.Printf(" rating: %v\n", win.Rating())
fmt.Printf(" deviation: %v\n", win.Deviation())
fmt.Printf(" volatility: %v\n", win.Volatility())
}
}
// util func 'g'
func calcG(deviation float64) float64 {
return 1.0 / (math.Sqrt(1.0 + 3.0*(deviation*deviation)/(math.Pi*math.Pi)))
}
// util func 'E'
func calcE(rating float64, rating_opponent float64, deviation_opponent float64) float64 {
return 1.0 / (1.0 + math.Exp(-calcG(deviation_opponent)*(rating-rating_opponent)))
}
// Update rating based on current results list, and clear results.
func (g *Glicko2) Update() {
// merge wins, losses, draws slices for convenience
results := append(append(g.wins, g.losses...), g.draws...)
// Note that if a player does not compete during the rating period, then
// only Step 6 applies.
if len(results) == 0 {
g.deviation = math.Sqrt((g.deviation * g.deviation) + (g.volatility * g.volatility))
return
}
// 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 := 0.0
for _, r := range results {
g_i := calcG(r.deviation)
e_i := calcE(g.rating, r.rating, r.deviation)
variance += (g_i * g_i) * e_i * (1.0 - e_i)
}
variance = 1.0 / variance
// 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 := 0.0
for _, r := range g.wins {
delta += calcG(r.deviation) * (1.0 - calcE(g.rating, r.rating, r.deviation))
}
for _, r := range g.losses {
delta += calcG(r.deviation) * (0.0 - calcE(g.rating, r.rating, r.deviation))
}
for _, r := range g.draws {
delta += calcG(r.deviation) * (0.5 - calcE(g.rating, r.rating, r.deviation))
}
delta *= variance
// Step 5. Determine the new value of the volatility.
new_volatility := 0.0
a := math.Log((g.volatility * g.volatility))
x := 0.0
x_new := a
for math.Abs(x-x_new) > 0.0000001 {
x = x_new
d := (g.deviation * g.deviation) + variance + math.Exp(x)
h1 := -(x-a)/(kDVOL*kDVOL) - 0.5*math.Exp(x)/d + 0.5*math.Exp(x)*(delta/d)*(delta/d)
h2 := -1.0/(kDVOL*kDVOL) - 0.5*math.Exp(x)*((g.deviation*g.deviation)+variance)/(d*d) + 0.5*(delta*delta)*math.Exp(x)*((g.deviation*g.deviation)+variance-math.Exp(x))/(d*d*d)
x_new = x - h1/h2
}
new_volatility = math.Exp(x_new / 2.0)
// Step 6. Update the rating deviation to the new pre-rating period value.
pre_deviation := math.Sqrt((g.deviation * g.deviation) + (new_volatility * new_volatility))
// Step 7. Update the rating and RD to the new values.
new_deviation := 1.0 / (math.Sqrt(1.0/(pre_deviation*pre_deviation) + 1.0/variance))
new_rating := 0.0
for _, r := range g.wins {
new_rating += calcG(r.deviation) * (1.0 - calcE(g.rating, r.rating, r.deviation))
}
for _, r := range g.losses {
new_rating += calcG(r.deviation) * (0.0 - calcE(g.rating, r.rating, r.deviation))
}
for _, r := range g.draws {
new_rating += calcG(r.deviation) * (0.5 - calcE(g.rating, r.rating, r.deviation))
}
new_rating = new_rating * (new_deviation * new_deviation)
new_rating += g.rating
// Step 8. Convert ratings and RD's back to original scale.
g.deviation = new_deviation
g.volatility = new_volatility
g.rating = new_rating
// wipe our result lists
g.ClearResults()
}
+47
View File
@@ -0,0 +1,47 @@
package glicko2
import (
"testing"
"math"
)
func inRange(want float64, have float64, delta float64) bool {
return math.Abs(want-have) <= delta
}
func TestGlicko2(t *testing.T) {
a := NewGlicko2(1500.0, 200.0, 0.06)
b := NewGlicko2(1400.0, 30.0, 0.06)
c := NewGlicko2(1550.0, 100.0, 0.06)
d := NewGlicko2(1700.0, 300.0, 0.06)
a.AddWin(b)
a.AddLoss(c)
a.AddLoss(d)
if a.Rating() != 1500.0 {
t.Error()
}
if a.Deviation() != 200.0 {
t.Error()
}
a.Update()
if !inRange(a.Rating(),1464.05,0.01) {
t.Error()
}
if !inRange(a.Deviation(),151.516,0.01) {
t.Error()
}
a.Update()
if !inRange(a.Rating(),1464.05,0.01) {
t.Error()
}
if !inRange(a.Deviation(),151.875,0.01) {
t.Error()
}
}
+6 -6
View File
@@ -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()
+166 -154
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env ruby
#!/usr/bin/env ruby -w
#
#
# Copyright (c) 2004 Stephen Waits
# Copyright (c) 2013 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
@@ -38,179 +38,191 @@
#
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_reader :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
# system constant, determines delta volatility over time; should be [0.3,1.2]
DVOL = 0.3
# 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
# 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)
self.rating = rating
self.deviation = deviation
self.volatility = volatility
clear_results
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
include Comparable
# 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
# Comparison operator
def <=>(other)
p_win(other) <=> 0.5
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
# 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
# Get the current rating volatility.
def volatility
@g2volatility
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 rating volatility.
def volatility=(v)
@g2volatility = v
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
# 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 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
# 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 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
# 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
# Get the current rating volatility.
def volatility
@g2volatility
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
# Set rating volatility.
def volatility=(v)
@g2volatility = v
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
# 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 = Array.new
@results = Array.new
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
#@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 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
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 )
# 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
# 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
# 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
# wipe our result lists
clear_results
# util func 'g'
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|
g_i = Glicko2.g(r.opponent.g2deviation)
e_i = Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation)
sum + g_i * (r.result - e_i)
end
# Step 5. Determine the new value of the volatility.
a = Math.log(@g2volatility**2.0)
x0, x1 = 0.0, a
while ((x0 - x1).abs > 0.0000001)
x0 = x1
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)
x1 = x0 - h1/h2
end
new_volatility = Math.exp(x1 / 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|
g_i = Glicko2.g(r.opponent.g2deviation)
e_i = Glicko2.E(@g2rating, r.opponent.g2rating, r.opponent.g2deviation)
sum + g_i * (r.result - e_i)
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
Regular → Executable
+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'
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.875,a.deviation,0.01)
end
end
end