trueskill-ranked 0.9b

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.
data/lib/TrueSkill.rb ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'pp'
3
+
4
+ %w(
5
+ general
6
+ guassian
7
+ ).each do |name|
8
+ require File.expand_path(File.join(File.dirname(__FILE__), "TrueSkill", "Mathematics", "#{name}.rb"))
9
+ end
10
+
11
+ %w(
12
+ Factor
13
+ LikelihoodFactor
14
+ PriorFactor
15
+ SumFactor
16
+ TruncateFactor
17
+ Variable
18
+ ).each do |name|
19
+ require File.expand_path(File.join(File.dirname(__FILE__), "TrueSkill", "FactorGraph", "#{name}.rb"))
20
+ end
21
+
22
+
23
+
24
+
25
+ %w(
26
+ Array
27
+ core_ext
28
+ general
29
+ Rating
30
+ TrueSkill
31
+ ).each do |name|
32
+ require File.expand_path(File.join(File.dirname(__FILE__), "TrueSkill", "#{name}.rb"))
33
+ end
File without changes
@@ -0,0 +1,17 @@
1
+
2
+
3
+ class Factor
4
+ attr_accessor :vars
5
+ @vars=nil
6
+ def initialize(vars)
7
+ @vars=vars
8
+ @vars.each do |var|
9
+ var[self]=Gaussian.new
10
+ end
11
+
12
+ end
13
+
14
+ def var
15
+ @vars[0]
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+
2
+ class LikelihoodFactor < Factor
3
+ @mean=nil
4
+ @value=nil
5
+ @variance=nil
6
+ def initialize(mean_var,value_var,variance)
7
+ super([mean_var,value_var])
8
+ @mean=mean_var
9
+ @value=value_var
10
+ @variance=variance
11
+ end
12
+
13
+ def down
14
+ val=@mean
15
+ msg=val/@mean[self]
16
+ pi=1.0/@variance
17
+ a=pi/(pi+val.pi)
18
+ return @value.update_message(self,a*msg.pi,a*msg.tau)
19
+ end
20
+
21
+ def up
22
+ val=@value
23
+ msg=val/@value[self]
24
+ a=1.0/(1.0+@variance*msg.pi)
25
+ return @mean.update_message(self,a*msg.pi,a*msg.tau)
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+
2
+ require_relative 'Factor'
3
+ class PriorFactor < Factor
4
+ @val=nil
5
+ @dynamic=0
6
+ def initialize(var,val,dynamic=0)
7
+ super([var])
8
+ @val=val
9
+ @dynamic=dynamic
10
+ end
11
+
12
+ def down
13
+ sigma=Math.sqrt(@val.sigma**2 + @dynamic ** 2)
14
+ value=Gaussian.new @val.mu,sigma
15
+ var.update_value self,0,0,value
16
+ end
17
+ end
@@ -0,0 +1,65 @@
1
+
2
+ class SumFactor < Factor
3
+ @sum=nil
4
+ @terms=nil
5
+ @coeffs=nil
6
+ def initialize(sum_var,term_vars,coeffs)
7
+ super([sum_var]+term_vars)
8
+ @sum=sum_var
9
+ @terms=term_vars
10
+ @coeffs=coeffs
11
+ end
12
+
13
+ def down
14
+ vals=@terms
15
+ msgs=[]
16
+ vals.each do |var|
17
+ msgs << var[self]
18
+ end
19
+ update(@sum,vals,msgs,@coeffs)
20
+ end
21
+
22
+ def up(index=0)
23
+ coeff=@coeffs[index]
24
+ x=0
25
+ coeffs=[]
26
+ @coeffs.each do |c|
27
+ if x!=index
28
+ coeffs << -c/coeff
29
+ end
30
+ x+=1
31
+ end
32
+ coeffs.insert(index,1.0/coeff)
33
+ vals=@terms.dup
34
+ vals[index]=@sum
35
+ msgs=[]
36
+ vals.each do |var|
37
+ msgs << var[self]
38
+ end
39
+ return update(@terms[index],vals,msgs,coeffs)
40
+ end
41
+
42
+ def update(var,vals,msgs,coeffs)
43
+ size=coeffs.length-1
44
+ divs=[]
45
+ (0..size).each do |x|
46
+ vl=vals[x]
47
+ ms=msgs[x]
48
+ divs << vl/ms
49
+ end
50
+ pisum=[]
51
+ (0..size).each do |x|
52
+ pisum << coeffs[x]**2/divs[x].pi
53
+ end
54
+ pi=1.0/pisum.inject{|sum,x| sum + x }
55
+
56
+ tausum=[]
57
+ (0..size).each do |x|
58
+ tausum << coeffs[x]*divs[x].mu
59
+ end
60
+ tau=pi*tausum.inject{|sum,x| sum + x }
61
+ return var.update_message(self,pi,tau)
62
+ end
63
+
64
+ end
65
+
@@ -0,0 +1,28 @@
1
+ class TruncateFactor < Factor
2
+ @v_func=nil
3
+ @w_func=nil
4
+ @draw_margin=nil
5
+
6
+ def initialize(var,v_func,w_func,draw_margin)
7
+ super([var])
8
+ @v_func=v_func
9
+ @w_func=w_func
10
+ @draw_margin=draw_margin
11
+ end
12
+
13
+
14
+ def up()
15
+ val=var
16
+ msg=var[self]
17
+ div=val/msg
18
+ sqrt_pi=Math.sqrt(div.pi)
19
+
20
+ v=@v_func.call(div.tau/sqrt_pi,@draw_margin*sqrt_pi)
21
+ w=@w_func.call(div.tau/sqrt_pi,@draw_margin*sqrt_pi)
22
+
23
+ denom=(1.0-w)
24
+ pi=div.pi/denom
25
+ tau=(div.tau+sqrt_pi*v)/denom
26
+ return var.update_value(self,pi,tau)
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+
2
+ class Variable < Gaussian
3
+
4
+
5
+ @delta=nil
6
+ def initialize()
7
+ super()
8
+ @messages={}
9
+ end
10
+
11
+ def set(val)
12
+ @delta=delta(val)
13
+ @pi=val.pi
14
+ @tau=val.tau
15
+ end
16
+ def delta(other)
17
+ return [(@tau-other.tau).abs,Math.sqrt((@pi-other.pi).abs)].max
18
+ end
19
+
20
+ def update_message(factor,pi=0,tau=0,message=nil)
21
+ if message.nil?
22
+ message=Gaussian.new(nil,nil,pi,tau)
23
+ end
24
+ old_message=self[factor]
25
+ self[factor]=message
26
+ return set(self/old_message*message)
27
+ end
28
+
29
+ def update_value(factor,pi=0,tau=0,value=nil)
30
+ if value.nil?
31
+ value=Gaussian.new(nil,nil,pi,tau)
32
+ end
33
+ old_message=self[factor]
34
+ self[factor]=value*old_message/self
35
+ return set(value)
36
+ end
37
+
38
+ def [](y)
39
+ @messages[y]
40
+ end
41
+
42
+ def []=(y,value)
43
+ @messages[y]=value
44
+ end
45
+
46
+ end
47
+
@@ -0,0 +1,24 @@
1
+
2
+
3
+ def ierfcc(p)
4
+ return -100 if p >= 2.0
5
+ return 100 if p <= 0.0
6
+ pp = p < 1.0 ? p : 2 - p
7
+ t = Math.sqrt(-2*Math.log(pp/2.0)) # Initial guess
8
+ x = -0.70711*((2.30753 + t*0.27061)/(1.0 + t*(0.99229 + t*0.04481)) - t)
9
+ [0,1].each do |j|
10
+ err = Math.erfc(x) - pp
11
+ x += err/(1.12837916709551257*Math.exp(-(x*x)) - x*err)
12
+ end
13
+ p < 1.0 ? x : -x
14
+ end
15
+
16
+ def cdf(x,mu=0,sigma=1)
17
+ return 0.5*Math.erfc(-(x-mu)/(sigma*Math.sqrt(2)))
18
+ end
19
+ def pdf(x,mu=0,sigma=1)
20
+ return (1/Math.sqrt(2*Math::PI)*sigma.abs)*Math.exp(-(((x-mu)/sigma.abs)**2/2))
21
+ end
22
+ def ppf(x,mu=0,sigma=1)
23
+ return mu-sigma*Math.sqrt(2)*ierfcc(2*x)
24
+ end
@@ -0,0 +1,38 @@
1
+ class Gaussian
2
+ attr_accessor :pi,:tau,:mu,:sigma
3
+ @pi=nil
4
+ @tau=nil
5
+ def initialize(mu=nil,sigma=nil,pi=0,tau=0)
6
+ if not mu.nil?
7
+ if sigma.nil? or sigma==0.0
8
+ raise "a variance(sigma^2) should be greater than 0"
9
+ end
10
+ pi=sigma**-2
11
+ tau=pi*mu
12
+ end
13
+ @pi=pi
14
+ @tau=tau
15
+ end
16
+
17
+ def mu
18
+ @tau/@pi
19
+ end
20
+
21
+ def sigma
22
+ if @pi.nil? or @pi==0
23
+ return 1.0/0
24
+ end
25
+ return Math.sqrt(1/@pi)
26
+ end
27
+ def *(y)
28
+ pi=@pi+y.pi
29
+ tau=@tau+y.tau
30
+ return Gaussian.new(nil,nil,pi,tau)
31
+ end
32
+ def /(y)
33
+ pi=@pi-y.pi
34
+ tau=@tau-y.tau
35
+ return Gaussian.new(nil,nil,pi,tau)
36
+ end
37
+ end
38
+
@@ -0,0 +1,23 @@
1
+
2
+ class Rating < Gaussian
3
+ attr_accessor :exposure
4
+ def initialize(mu=nil,sigma=nil)
5
+ if mu.kind_of?(Array)
6
+ mu,sigma=mu
7
+ end
8
+ if mu.nil?
9
+ mu=g().mu
10
+ end
11
+ if sigma.nil?
12
+ sigma=g().sigma
13
+ end
14
+ super(mu,sigma)
15
+ end
16
+
17
+ def exposure
18
+ return mu-3*sigma
19
+ end
20
+ def to_s
21
+ return "[mu="+mu.to_s+",sigma="+sigma.to_s+"]"
22
+ end
23
+ end
@@ -0,0 +1,235 @@
1
+
2
+ class TrueSkillObject
3
+
4
+ attr_accessor :mu,:sigma,:beta,:tau,:draw_probability
5
+
6
+ @mu=nil
7
+ @sigma=nil
8
+ @beta=nil
9
+ @tau=nil
10
+ @draw_probability=nil
11
+
12
+ def initialize(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU,draw_probability=DRAW_PROBABILITY)
13
+ @mu=mu
14
+ @sigma=sigma
15
+ @beta=beta
16
+ @tau=tau
17
+ @draw_probability=draw_probability
18
+ end
19
+
20
+
21
+ def Rating(mu=nil,sigma=nil)
22
+ if mu.nil?
23
+ mu=@mu
24
+ end
25
+ if sigma.nil?
26
+ sigma=@sigma
27
+ end
28
+ return Rating.new mu,sigma
29
+ end
30
+
31
+ def make_as_global
32
+ return setup(nil,nil,nil,nil,nil,self)
33
+ end
34
+ def validate_rating_groups(rating_groups)
35
+ rating_groups.each do |group|
36
+ if group.is_a? Rating
37
+ group=[group,]
38
+ end
39
+ if group.length==0
40
+ raise "each group must contain multiple ratings"
41
+ end
42
+ end
43
+ if rating_groups.length<2
44
+ raise "need multiple rating groups"
45
+ end
46
+ return rating_groups
47
+ end
48
+ def build_factor_graph(rating_groups,ranks)
49
+ ratings=rating_groups.flatten
50
+ size=ratings.length
51
+ group_size=rating_groups.length
52
+ rating_vars=[]
53
+ size.times { rating_vars << Variable.new}
54
+
55
+ perf_vars=[]
56
+ size.times { perf_vars << Variable.new}
57
+
58
+ teamperf_vars=[]
59
+ group_size.times { teamperf_vars << Variable.new}
60
+
61
+ teamdiff_vars=[]
62
+ (group_size-1).times { teamdiff_vars << Variable.new}
63
+
64
+ team_sizes=_team_sizes(rating_groups)
65
+ get_perf_vars_by_team=lambda do |team|
66
+ if team > 0
67
+ start=team_sizes[team-1]
68
+ else
69
+ start=0
70
+ end
71
+ endv=team_sizes[team]
72
+ return perf_vars[start,endv]
73
+ end
74
+
75
+ build_rating_layer=lambda do
76
+ Enumerator.new do |yielder|
77
+
78
+ rating_vars.zip(ratings).each do |rating_var,rating|
79
+ yielder.yield(PriorFactor.new(rating_var,rating,@tau))
80
+ end
81
+ end
82
+ end
83
+ build_perf_layer=lambda do
84
+ Enumerator.new do |yielder|
85
+
86
+ rating_vars.zip(perf_vars).each do |rating_var,perf_var|
87
+ yielder.yield(LikelihoodFactor.new(rating_var,perf_var,@beta**2))
88
+ end
89
+ end
90
+ end
91
+ build_teamperf_layer=lambda do
92
+ Enumerator.new do |yielder|
93
+ teamperf_vars.each_with_index do |teamperf_var,team|
94
+ child_perf_vars = get_perf_vars_by_team.call(team)
95
+ yielder.yield(SumFactor.new(teamperf_var,child_perf_vars,[1]*child_perf_vars.length))
96
+ end
97
+ end
98
+ end
99
+ build_teamdiff_layer=lambda do
100
+ Enumerator.new do |yielder|
101
+ teamdiff_vars.each_with_index do |teamdiff_var,team|
102
+ yielder.yield(SumFactor.new(teamdiff_var,teamperf_vars[team,team=2],[+1,-1]))
103
+ end
104
+ end
105
+ end
106
+ build_trunc_layer=lambda do
107
+ Enumerator.new do |yielder|
108
+ teamdiff_vars.each_with_index do |teamdiff_var,x|
109
+ size=0
110
+ rating_groups[x,x+2].each do |group|
111
+ size+=group.length
112
+ end
113
+ draw_margin=calc_draw_margin(@draw_probability,@beta,size)
114
+
115
+ if ranks[x]==ranks[x+1]
116
+ v_func,w_func=$v_drawFunc,$w_drawFunc
117
+ else
118
+ v_func,w_func=$vFunc,$wFunc
119
+ end
120
+ yielder.yield(TruncateFactor.new(teamdiff_var,v_func,w_func,draw_margin))
121
+ end
122
+ end
123
+ end
124
+
125
+ return Array(build_rating_layer.call),Array(build_perf_layer.call),Array(build_teamperf_layer.call),Array(build_teamdiff_layer.call),Array(build_trunc_layer.call)
126
+ end
127
+
128
+ def run_schedule(rating_layer, perf_layer, teamperf_layer,teamdiff_layer, trunc_layer, min_delta=DELTA)
129
+ [rating_layer,perf_layer,teamperf_layer].flatten.each do |f|
130
+ f.down
131
+ end
132
+ teamdiff_len=teamdiff_layer.length
133
+ (10).times do |x|
134
+ if teamdiff_len==1
135
+ teamdiff_layer[0].down
136
+ delta=trunc_layer[0].up
137
+ else
138
+ delta=0
139
+ (teamdiff_len-1).times do |x2|
140
+ teamdiff_layer[x2].down
141
+ delta=[delta,trunc_layer[x].up].max
142
+ teamdiff_layer[x].up(1)
143
+ end
144
+ (teamdiff_len-1).step(0,-1) do |x2|
145
+ teamdiff_layer[x2].down
146
+ delta=[delta,trunc_layer[x].up].max
147
+ teamdiff_layer[x].up(0)
148
+ end
149
+ end
150
+ if delta<=min_delta
151
+ break
152
+ end
153
+ end
154
+ teamdiff_layer.first.up(0)
155
+ teamdiff_layer.last.up(1)
156
+ teamperf_layer.each do |f|
157
+ (f.vars.length-1).times do |x|
158
+ f.up(x)
159
+ end
160
+ end
161
+ perf_layer.each do |f|
162
+ f.up
163
+ end
164
+ end
165
+ def transform_ratings(rating_groups, ranks=nil, min_delta=DELTA)
166
+ rating_groups=validate_rating_groups(rating_groups)
167
+ group_size=rating_groups.length
168
+ if ranks.nil?
169
+ ranks=Array(0..group_size-1)
170
+ end
171
+ sorting=ranks.zip(rating_groups).each_with_index.to_a.map {|x| x.reverse}.sort { |x,y| x[1][0] <=> y[1][0]}
172
+ sorted_groups=[]
173
+ sorting.each do |x,g|
174
+ sorted_groups << g[1]
175
+ end
176
+ sorted_ranks=ranks.sort()
177
+ unsorting_hint=[]
178
+ sorting.each do |x,g|
179
+ unsorting_hint << x
180
+ end
181
+ layers=build_factor_graph(sorted_groups,sorted_ranks)
182
+ run_schedule(layers[0],layers[1],layers[2],layers[3],layers[4])
183
+ rating_layer,team_sizes=layers[0],_team_sizes(sorted_groups)
184
+ transformed_groups=[]
185
+ ([0]+team_sizes.take(team_sizes.size - 1)).zip(team_sizes).each do |start,ending|
186
+ group=[]
187
+ rating_layer[start,ending].each do |f|
188
+ group << Rating.new(f.var.mu,f.var.sigma)
189
+ end
190
+ transformed_groups << Array(group)
191
+ end
192
+ unsorting=unsorting_hint.zip(transformed_groups).sort { |x,y| x[0]<=>y[0]}
193
+ output=[]
194
+ unsorting.each do |x,g|
195
+ output << g
196
+ end
197
+ return output
198
+ end
199
+ #def match_quality(rating_groups)
200
+ #rating_groups=validate_rating_groups(rating_groups)
201
+ #ratings=rating_groups.flatten
202
+ #length=ratings.length
203
+ #mean_rows=[]
204
+ #ratings.each {|r| mean_rows << [r.mu] }
205
+ #mean_matrix=Matrix[*mean_rows]
206
+ #variance_rows=[]
207
+ #ratings.each {|r| variance_rows << r.sigma**2 }
208
+ #variance_matrix=Matrix.diagonal *variance_rows
209
+ #rotated_a_matrix=lambda do
210
+ # mat=[]
211
+ # t=0
212
+ # rating_groups.clip.zip(rating_groups.drop(1)).each_with_index do |y,r|
213
+ # rline=[]
214
+ # cur=y[0]
215
+ # nex=y[1]
216
+ # z=0
217
+ # (t..t+cur.length).each do |x|
218
+ # rline << 1
219
+ # t+=1
220
+ # z=x
221
+ # end
222
+ # z+=1
223
+ # (z..z+nex.length).each do |x|
224
+ # rline << -1
225
+ # end
226
+ # mat << rline
227
+ # end
228
+ # mat
229
+ #end
230
+ #rotated_a_matrix=Matrix[rotated_a_matrix.call]
231
+ #a_matrix = rotated_a_matrix.transpose()
232
+ #_ata = (@beta ** 2) * rotated_a_matrix * a_matrix
233
+ #pp _ata
234
+ #end
235
+ end
File without changes
@@ -0,0 +1,71 @@
1
+
2
+ MU=25.0
3
+ SIGMA=MU/3
4
+ BETA=SIGMA/2
5
+ TAU=SIGMA/100
6
+ DRAW_PROBABILITY=0.10
7
+ DELTA=0.001
8
+
9
+
10
+ $vFunc=Proc.new do |diff,draw_margin|
11
+ x=diff-draw_margin
12
+ pdf(x)/cdf(x)
13
+ end
14
+
15
+ $wFunc=Proc.new do |diff,draw_margin|
16
+ x=diff-draw_margin
17
+ v=$vFunc.call(diff,draw_margin)
18
+ v*(v+x)
19
+ end
20
+
21
+ $v_drawFunc=Proc.new do |diff,draw_margin|
22
+ abs_diff=diff.abs
23
+ a=draw_margin-abs_diff
24
+ b=-draw_margin-abs_diff
25
+ denom=cdf(a)-cdf(b)
26
+ numer=pdf(b)-pdf(a)
27
+ numer/denom*((diff<0)?-1:1)
28
+ end
29
+ $w_drawFunc=Proc.new do |diff,draw_margin|
30
+ abs_diff=diff.abs
31
+ a=draw_margin-abs_diff
32
+ b=-draw_margin-abs_diff
33
+ denom=cdf(a)-cdf(b)
34
+ v=$v_drawFunc.call(abs_diff,draw_margin)
35
+ (v**2)+(a*pdf(a)-b*pdf(b))/denom
36
+ end
37
+
38
+ def calc_draw_probability(draw_margin,beta,size)
39
+ return 2*cdf(draw_margin*(Math.sqrt(size)*beta))-1
40
+ end
41
+
42
+ def calc_draw_margin(draw_probability, beta, size)
43
+ return ppf((draw_probability+1)/2.0)*Math.sqrt(size)*beta
44
+ end
45
+
46
+ def _team_sizes(rating_groups)
47
+ team_sizes=[0]
48
+ rating_groups.each do |group|
49
+ team_sizes << group.length+team_sizes.last
50
+ end
51
+ team_sizes.delete_at(0)
52
+ return team_sizes
53
+ end
54
+ $global=[]
55
+
56
+ def g()
57
+ if $global.length==0
58
+ setup()
59
+ end
60
+ return $global[0]
61
+ end
62
+ def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU,draw_probability=DRAW_PROBABILITY, env=nil)
63
+ $global.pop
64
+ if env.nil?
65
+ $global << TrueSkillObject.new(mu, sigma, beta, tau, draw_probability)
66
+ else
67
+ $global << env
68
+ end
69
+ return g()
70
+ end
71
+
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trueskill-ranked
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9b
5
+ prerelease: 3
6
+ platform: ruby
7
+ authors:
8
+ - Katrina Swales
9
+ - Heungsub Lee
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-06-13 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: A improved TrueSkill with correct ranking order
16
+ email: kat.swales@nekokittygames.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/TrueSkill.rb
22
+ - lib/TrueSkill/Array.rb
23
+ - lib/TrueSkill/core_ext.rb
24
+ - lib/TrueSkill/Rating.rb
25
+ - lib/TrueSkill/general.rb
26
+ - lib/TrueSkill/TrueSkill.rb
27
+ - lib/TrueSkill/FactorGraph/Factor.rb
28
+ - lib/TrueSkill/FactorGraph/LikelihoodFactor.rb
29
+ - lib/TrueSkill/FactorGraph/PriorFactor.rb
30
+ - lib/TrueSkill/FactorGraph/SumFactor.rb
31
+ - lib/TrueSkill/FactorGraph/TruncateFactor.rb
32
+ - lib/TrueSkill/FactorGraph/Variable.rb
33
+ - lib/TrueSkill/Mathematics/general.rb
34
+ - lib/TrueSkill/Mathematics/guassian.rb
35
+ homepage: http://rubygems.org/gems/trueskill-ranked
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>'
51
+ - !ruby/object:Gem::Version
52
+ version: 1.3.1
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 1.8.11
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Ranked TrueSkill
59
+ test_files: []