glicko2 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|