trueskill-ranked 0.9b

Sign up to get free protection for your applications and to get access to all the features.
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: []