Search Apps Documentation Source Content File Folder Download Copy Actions Download

glicko2.gno

4.81 Kb · 165 lines
  1package glicko2
  2
  3import (
  4	"math"
  5)
  6
  7// http://www.glicko.net/glicko/glicko2.pdf
  8
  9const (
 10	// Step 1.
 11	// Determine a rating and RD for each player at the onset of the rating period. The
 12	// system constant, τ , which constrains the change in volatility over time, needs to be
 13	// set prior to application of the system. Reasonable choices are between 0.3 and 1.2,
 14	// though the system should be tested to decide which value results in greatest predictive
 15	// accuracy. Smaller values of τ prevent the volatility measures from changing by large
 16	// amounts, which in turn prevent enormous changes in ratings based on very improbable
 17	// results. If the application of Glicko-2 is expected to involve extremely improbable
 18	// collections of game outcomes, then τ should be set to a small value, even as small as,
 19	// say, τ = 0.2.
 20	GlickoTau = 0.5
 21
 22	GlickoInitialRating     = 1500
 23	GlickoInitialRD         = 350
 24	GlickoInitialVolatility = 0.06
 25
 26	glickoScaleFactor = 173.7178
 27)
 28
 29type PlayerRating struct {
 30	ID address
 31
 32	Rating           float64
 33	RatingDeviation  float64
 34	RatingVolatility float64
 35
 36	// working values, these are referred to as μ, φ and σ in the paper.
 37	wr, wrd, wrv float64
 38}
 39
 40func NewPlayerRating(addr address) *PlayerRating {
 41	return &PlayerRating{
 42		ID:               addr,
 43		Rating:           GlickoInitialRating,
 44		RatingDeviation:  GlickoInitialRD,
 45		RatingVolatility: GlickoInitialVolatility,
 46	}
 47}
 48
 49// RatingScore is the outcome of a game between two players.
 50type RatingScore struct {
 51	White, Black address
 52	Score        float64 // 0 = black win, 0.5 = draw, 1 = white win
 53}
 54
 55func getRatingScore(scores []RatingScore, player, opponent address) float64 {
 56	for _, score := range scores {
 57		if score.White == player && score.Black == opponent {
 58			return score.Score
 59		}
 60		if score.Black == player && score.White == opponent {
 61			return 1 - score.Score
 62		}
 63	}
 64	return -1
 65}
 66
 67func UpdateRatings(source []*PlayerRating, scores []RatingScore) {
 68	// step 2: assign working wr/wrd/wrv
 69	for _, player := range source {
 70		player.wr = (player.Rating - GlickoInitialRating) / glickoScaleFactor
 71		player.wrd = player.RatingDeviation / glickoScaleFactor
 72		player.wrv = player.RatingVolatility
 73	}
 74
 75	for _, player := range source {
 76		// step 3: compute the quantity v. This is the estimated variance of the team’s/player’s
 77		// rating based only on game outcomes
 78		// step 4: compute the quantity ∆, the estimated improvement in rating
 79		v, delta, competed := glickoVDelta(player, source, scores)
 80
 81		newRDPart := math.Sqrt(player.wrd*player.wrd + player.wrv*player.wrv)
 82		if !competed {
 83			// fast path for players who have not competed:
 84			// update only rating deviation
 85			player.RatingDeviation = newRDPart
 86			player.wr, player.wrd, player.wrv = 0, 0, 0
 87			continue
 88		}
 89
 90		// step 5: determine the new value, σ′, of the volatility.
 91		player.wrv = glickoVolatility(player, delta, v)
 92		// step 6 and 7
 93		player.wrd = 1 / math.Sqrt(1/v+1/(newRDPart*newRDPart))
 94		player.wr = player.wr + player.wrd*player.wrd*(delta/v)
 95
 96		// step 8
 97		player.Rating = player.wr*glickoScaleFactor + 1500
 98		player.RatingDeviation = player.wrd * glickoScaleFactor
 99		player.RatingVolatility = player.wrv
100		player.wrv, player.wrd, player.wr = 0, 0, 0
101	}
102}
103
104func glickoVDelta(player *PlayerRating, source []*PlayerRating, scores []RatingScore) (v, delta float64, competed bool) {
105	var deltaPartial float64
106	for _, opponent := range source {
107		if opponent.ID == player.ID {
108			continue
109		}
110		score := getRatingScore(scores, player.ID, opponent.ID)
111		if score == -1 { // not found
112			continue
113		}
114		competed = true
115
116		// Step 3
117		gTheta := 1 / math.Sqrt(1+(3*opponent.wrd*opponent.wrd)/(math.Pi*math.Pi))
118		eMu := 1 / (1 + math.Exp(-gTheta*(player.wr-opponent.wr)))
119		v += gTheta * gTheta * eMu * (1 - eMu)
120
121		// step 4
122		deltaPartial += gTheta * (score - eMu)
123	}
124	v = 1 / v
125	return v, v * deltaPartial, competed
126}
127
128const glickoVolatilityEps = 0.000001
129
130// Step 5
131func glickoVolatility(player *PlayerRating, delta, v float64) float64 {
132	aInit := math.Log(player.wrv * player.wrv)
133	A := aInit
134	var B float64
135	rdp2 := player.wrd * player.wrd
136	if delta*delta > rdp2+v {
137		B = math.Log(delta*delta - rdp2 - v)
138	} else {
139		k := float64(1)
140		for ; glickoVolatilityF(A-k*GlickoTau, aInit, rdp2, delta, v) < 0; k++ {
141		}
142		B = A - k*GlickoTau
143	}
144	fA, fB := glickoVolatilityF(A, aInit, rdp2, delta, v), glickoVolatilityF(B, aInit, rdp2, delta, v)
145	for math.Abs(B-A) > glickoVolatilityEps {
146		C := A + ((A-B)*fA)/(fB-fA)
147		fC := glickoVolatilityF(C, aInit, rdp2, delta, v)
148		if fC*fB <= 0 {
149			A, fA = B, fB
150		} else {
151			fA = fA / 2
152		}
153		B, fB = C, fC
154	}
155	return math.Exp(A / 2)
156}
157
158// rdp2: player.rd, power 2
159func glickoVolatilityF(x, a, rdp2, delta, v float64) float64 {
160	expX := math.Exp(x)
161	return (expX*(delta*delta-rdp2-v-expX))/(2*pow2(rdp2+v+expX)) -
162		(x-a)/(GlickoTau*GlickoTau)
163}
164
165func pow2(f float64) float64 { return f * f }