glicko2 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/lib/glicko2.rb +1 -0
- data/lib/glicko2/normal_distribution.rb +58 -0
- data/lib/glicko2/player.rb +14 -15
- data/lib/glicko2/version.rb +1 -1
- data/spec/normal_distribution_spec.rb +69 -0
- data/spec/player_spec.rb +2 -2
- metadata +13 -13
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3264135c077789c8ce7d6f49a216c3ac50dafeb4
|
4
|
+
data.tar.gz: c621d811f861685e08a85c60d5252e4868231842
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8d34d1bab836561f0fefb5f58807387af31d7d21adfd31344b547b8d804f75ba16c8ffd5a45d6982a620aca49c9e8374fb5ed93744b9050dea158536c4be0b61
|
7
|
+
data.tar.gz: 27fbeaf711881f18500a5277f2a9571d71bad500976949f73e873c0196cd29347723b382597c8d54e5961ff2751128f6591babb088a1efa60bcdb542da65468a
|
data/.gitignore
CHANGED
data/lib/glicko2.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Glicko2
|
2
|
+
# Glicko ratings are represented with a rating and rating deviation. For this
|
3
|
+
# gem it is assumed that ratings are normally distributed where rating and
|
4
|
+
# rating deviation correspond to mean and standard deviation.
|
5
|
+
class NormalDistribution
|
6
|
+
attr_reader :mean, :standard_deviation
|
7
|
+
alias_method :sd, :standard_deviation
|
8
|
+
|
9
|
+
def initialize(mean, standard_deviation)
|
10
|
+
@mean = mean
|
11
|
+
@standard_deviation = standard_deviation
|
12
|
+
end
|
13
|
+
|
14
|
+
# Calculate the distribution variance
|
15
|
+
#
|
16
|
+
# @return [Numeric]
|
17
|
+
def variance
|
18
|
+
standard_deviation ** 2.0
|
19
|
+
end
|
20
|
+
|
21
|
+
# Calculate the sum
|
22
|
+
#
|
23
|
+
# @param [NormalDistribution] other
|
24
|
+
# @return [NormalDistribution]
|
25
|
+
def +(other)
|
26
|
+
self.class.new(mean + other.mean, Math.sqrt(variance + other.variance))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Calculate the difference
|
30
|
+
#
|
31
|
+
# @param [NormalDistribution] other
|
32
|
+
# @return [NormalDistribution]
|
33
|
+
def -(other)
|
34
|
+
self.class.new(mean - other.mean, Math.sqrt(variance + other.variance))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Calculate the probability density at `x`
|
38
|
+
#
|
39
|
+
# @param [Numeric] x
|
40
|
+
# @return [Numeric]
|
41
|
+
def pdf(x)
|
42
|
+
1.0 / (sd * Math.sqrt(2.0 * Math::PI)) *
|
43
|
+
Math.exp(-(x - mean) ** 2.0 / 2.0 * variance)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Calculate the cumulative distribution at `x`
|
47
|
+
#
|
48
|
+
# @param [Numeric] x
|
49
|
+
# @return [Numeric]
|
50
|
+
def cdf(x)
|
51
|
+
0.5 * (1.0 + Math.erf((x - mean) / (sd * Math.sqrt(2.0))))
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#<NormalDistribution mean=#{mean}, sd=#{sd}>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/glicko2/player.rb
CHANGED
@@ -22,11 +22,11 @@ module Glicko2
|
|
22
22
|
#
|
23
23
|
# puts player_seed
|
24
24
|
#
|
25
|
-
class Player
|
25
|
+
class Player < NormalDistribution
|
26
26
|
TOLERANCE = 0.0000001
|
27
27
|
MIN_SD = DEFAULT_GLICKO_RATING_DEVIATION / GLICKO_GRADIENT
|
28
28
|
|
29
|
-
attr_reader :
|
29
|
+
attr_reader :volatility, :obj
|
30
30
|
|
31
31
|
# Create a {Player} from a seed object, converting from Glicko
|
32
32
|
# ratings to Glicko2.
|
@@ -43,8 +43,7 @@ module Glicko2
|
|
43
43
|
# @param [Numeric] volatility player volatility
|
44
44
|
# @param [#rating,#rating_deviation,#volatility] obj seed values object
|
45
45
|
def initialize(mean, sd, volatility, obj=nil, config=DEFAULT_CONFIG)
|
46
|
-
|
47
|
-
@sd = sd
|
46
|
+
super(mean, sd)
|
48
47
|
@volatility = volatility
|
49
48
|
@obj = obj
|
50
49
|
@config = config
|
@@ -55,7 +54,7 @@ module Glicko2
|
|
55
54
|
#
|
56
55
|
# @return [Numeric]
|
57
56
|
def g
|
58
|
-
@g ||= 1 / Math.sqrt(1 + 3 *
|
57
|
+
@g ||= 1 / Math.sqrt(1 + 3 * variance / Math::PI ** 2)
|
59
58
|
end
|
60
59
|
|
61
60
|
# Calculate `E(mu, mu_j, phi_j)` as defined in the Glicko2 paper
|
@@ -71,7 +70,7 @@ module Glicko2
|
|
71
70
|
#
|
72
71
|
# @param [Array<Player>] others other participating players.
|
73
72
|
# @return [Numeric]
|
74
|
-
def
|
73
|
+
def estimated_variance(others)
|
75
74
|
return 0.0 if others.length < 1
|
76
75
|
others.reduce(0) do |v, other|
|
77
76
|
e_other = e(other)
|
@@ -89,14 +88,14 @@ module Glicko2
|
|
89
88
|
def delta(others, scores)
|
90
89
|
others.zip(scores).reduce(0) do |d, (other, score)|
|
91
90
|
d + other.g * (score - e(other))
|
92
|
-
end *
|
91
|
+
end * estimated_variance(others)
|
93
92
|
end
|
94
93
|
|
95
94
|
# Calculate `f(x)` as defined in the Glicko2 paper
|
96
95
|
#
|
97
96
|
# @param [Numeric] x
|
98
97
|
# @param [Numeric] d the result of calculating {#delta}
|
99
|
-
# @param [Numeric] v the result of calculating {#
|
98
|
+
# @param [Numeric] v the result of calculating {#estimated_variance}
|
100
99
|
# @return [Numeric]
|
101
100
|
def f(x, d, v)
|
102
101
|
f_part1(x, d, v) - f_part2(x)
|
@@ -105,12 +104,12 @@ module Glicko2
|
|
105
104
|
# Calculate the new value of the volatility
|
106
105
|
#
|
107
106
|
# @param [Numeric] d the result of calculating {#delta}
|
108
|
-
# @param [Numeric] v the result of calculating {#
|
107
|
+
# @param [Numeric] v the result of calculating {#estimated_variance}
|
109
108
|
# @return [Numeric]
|
110
109
|
def volatility1(d, v)
|
111
110
|
a = Math::log(volatility ** 2)
|
112
|
-
if d >
|
113
|
-
b = Math.log(d -
|
111
|
+
if d > variance + v
|
112
|
+
b = Math.log(d - variance - v)
|
114
113
|
else
|
115
114
|
k = 1
|
116
115
|
k += 1 while f(a - k * @config[:volatility_change], d, v) < 0
|
@@ -162,15 +161,15 @@ module Glicko2
|
|
162
161
|
private
|
163
162
|
|
164
163
|
def generate_next_without_games
|
165
|
-
sd_pre = [Math.sqrt(
|
164
|
+
sd_pre = [Math.sqrt(variance + volatility ** 2), MIN_SD].min
|
166
165
|
self.class.new(mean, sd_pre, volatility, obj)
|
167
166
|
end
|
168
167
|
|
169
168
|
def generate_next_with_games(others, scores)
|
170
|
-
_v =
|
169
|
+
_v = estimated_variance(others)
|
171
170
|
_d = delta(others, scores)
|
172
171
|
_volatility = volatility1(_d, _v)
|
173
|
-
sd_pre = Math.sqrt(
|
172
|
+
sd_pre = [Math.sqrt(variance + volatility ** 2), MIN_SD].min
|
174
173
|
_sd = 1 / Math.sqrt(1 / sd_pre ** 2 + 1 / _v)
|
175
174
|
_mean = mean + _sd ** 2 * others.zip(scores).reduce(0) {
|
176
175
|
|x, (other, score)| x + other.g * (score - e(other))
|
@@ -180,7 +179,7 @@ module Glicko2
|
|
180
179
|
|
181
180
|
def f_part1(x, d, v)
|
182
181
|
exp_x = Math.exp(x)
|
183
|
-
sd_sq =
|
182
|
+
sd_sq = variance
|
184
183
|
(exp_x * (d ** 2 - sd_sq - v - exp_x)) / (2 * (sd_sq + v + exp_x) ** 2)
|
185
184
|
end
|
186
185
|
|
data/lib/glicko2/version.rb
CHANGED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe Glicko2::NormalDistribution do
|
4
|
+
describe "#variance" do
|
5
|
+
it "must return the square of the standard deviation" do
|
6
|
+
Glicko2::NormalDistribution.new(1.0, 1.0).variance.must_equal 1.0 ** 2.0
|
7
|
+
Glicko2::NormalDistribution.new(1.0, 2.0).variance.must_equal 2.0 ** 2.0
|
8
|
+
Glicko2::NormalDistribution.new(1.0, 10.0).variance.must_equal 10.0 ** 2.0
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#+" do
|
13
|
+
let(:dist1) { Glicko2::NormalDistribution.new(10.0, 0.5) }
|
14
|
+
let(:dist2) { Glicko2::NormalDistribution.new(5.0, 1.0) }
|
15
|
+
|
16
|
+
it "must sum the means" do
|
17
|
+
(dist1 + dist2).mean.must_equal 15.0
|
18
|
+
end
|
19
|
+
|
20
|
+
it "must sqrt the sum of the variances" do
|
21
|
+
(dist1 + dist2).sd.must_equal Math.sqrt(0.5 ** 2.0 + 1.0 ** 2.0)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#-" do
|
26
|
+
let(:dist1) { Glicko2::NormalDistribution.new(10.0, 0.5) }
|
27
|
+
let(:dist2) { Glicko2::NormalDistribution.new(5.0, 1.0) }
|
28
|
+
|
29
|
+
it "must subtract the means" do
|
30
|
+
(dist1 - dist2).mean.must_equal 5.0
|
31
|
+
end
|
32
|
+
|
33
|
+
it "must sqrt the sum of the variances" do
|
34
|
+
(dist1 - dist2).sd.must_equal Math.sqrt(0.5 ** 2.0 + 1.0 ** 2.0)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#pdf" do
|
39
|
+
describe "standard normal" do
|
40
|
+
let(:dist) { Glicko2::NormalDistribution.new(0.0, 1.0) }
|
41
|
+
|
42
|
+
it "must calculate PDF at x" do
|
43
|
+
dist.pdf(-5.0).must_be_close_to 0.00000149, 0.00000001
|
44
|
+
dist.pdf(-2.5).must_be_close_to 0.01752830, 0.00000001
|
45
|
+
dist.pdf(-1.0).must_be_close_to 0.24197072, 0.00000001
|
46
|
+
dist.pdf(0.0).must_be_close_to 0.39894228, 0.00000001
|
47
|
+
dist.pdf(1.0).must_be_close_to 0.24197072, 0.00000001
|
48
|
+
dist.pdf(2.5).must_be_close_to 0.01752830, 0.00000001
|
49
|
+
dist.pdf(5.0).must_be_close_to 0.00000149, 0.00000001
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#cdf" do
|
55
|
+
describe "standard normal" do
|
56
|
+
let(:dist) { Glicko2::NormalDistribution.new(0.0, 1.0) }
|
57
|
+
|
58
|
+
it "must calculate CDF at x" do
|
59
|
+
dist.cdf(-5.0).must_be_close_to 0.00000029, 0.00000001
|
60
|
+
dist.cdf(-2.5).must_be_close_to 0.00620967, 0.00000001
|
61
|
+
dist.cdf(-1.0).must_be_close_to 0.15865525, 0.00000001
|
62
|
+
dist.cdf(0.0).must_be_close_to 0.50000000, 0.00000001
|
63
|
+
dist.cdf(1.0).must_be_close_to 0.84134475, 0.00000001
|
64
|
+
dist.cdf(2.5).must_be_close_to 0.99379033, 0.00000001
|
65
|
+
dist.cdf(5.0).must_be_close_to 0.99999971, 0.00000001
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/player_spec.rb
CHANGED
@@ -66,9 +66,9 @@ describe Glicko2::Player do
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
describe "#
|
69
|
+
describe "#estimated_variance" do
|
70
70
|
it "must be close to example" do
|
71
|
-
@player.
|
71
|
+
@player.estimated_variance(@others).must_be_close_to 1.7785
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
metadata
CHANGED
@@ -1,30 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: glicko2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- James Fargher
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-03-04 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: minitest
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
description: Implementation of Glicko2 ratings
|
@@ -41,39 +38,42 @@ files:
|
|
41
38
|
- Rakefile
|
42
39
|
- glicko2.gemspec
|
43
40
|
- lib/glicko2.rb
|
41
|
+
- lib/glicko2/normal_distribution.rb
|
44
42
|
- lib/glicko2/player.rb
|
45
43
|
- lib/glicko2/rating_period.rb
|
46
44
|
- lib/glicko2/version.rb
|
47
45
|
- spec/minitest_helper.rb
|
46
|
+
- spec/normal_distribution_spec.rb
|
48
47
|
- spec/player_spec.rb
|
49
48
|
- spec/rating_period_spec.rb
|
50
49
|
- spec/util_spec.rb
|
51
50
|
homepage: https://github.com/proglottis/glicko2
|
52
51
|
licenses: []
|
52
|
+
metadata: {}
|
53
53
|
post_install_message:
|
54
54
|
rdoc_options: []
|
55
55
|
require_paths:
|
56
56
|
- lib
|
57
57
|
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
58
|
requirements:
|
60
|
-
- -
|
59
|
+
- - '>='
|
61
60
|
- !ruby/object:Gem::Version
|
62
61
|
version: '0'
|
63
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
-
none: false
|
65
63
|
requirements:
|
66
|
-
- -
|
64
|
+
- - '>='
|
67
65
|
- !ruby/object:Gem::Version
|
68
66
|
version: '0'
|
69
67
|
requirements: []
|
70
68
|
rubyforge_project:
|
71
|
-
rubygems_version:
|
69
|
+
rubygems_version: 2.0.0
|
72
70
|
signing_key:
|
73
|
-
specification_version:
|
71
|
+
specification_version: 4
|
74
72
|
summary: Implementation of Glicko2 ratings
|
75
73
|
test_files:
|
76
74
|
- spec/minitest_helper.rb
|
75
|
+
- spec/normal_distribution_spec.rb
|
77
76
|
- spec/player_spec.rb
|
78
77
|
- spec/rating_period_spec.rb
|
79
78
|
- spec/util_spec.rb
|
79
|
+
has_rdoc:
|