trueskill 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/CHANGELOG +0 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +17 -0
  6. data/Rakefile +46 -0
  7. data/VERSION +1 -0
  8. data/lib/saulabs/gauss.rb +3 -0
  9. data/lib/saulabs/gauss/distribution.rb +125 -0
  10. data/lib/saulabs/gauss/truncated_correction.rb +62 -0
  11. data/lib/saulabs/trueskill.rb +7 -0
  12. data/lib/saulabs/trueskill/factor_graph.rb +75 -0
  13. data/lib/saulabs/trueskill/factors/base.rb +50 -0
  14. data/lib/saulabs/trueskill/factors/greater_than.rb +45 -0
  15. data/lib/saulabs/trueskill/factors/likelihood.rb +45 -0
  16. data/lib/saulabs/trueskill/factors/prior.rb +35 -0
  17. data/lib/saulabs/trueskill/factors/weighted_sum.rb +80 -0
  18. data/lib/saulabs/trueskill/factors/within.rb +45 -0
  19. data/lib/saulabs/trueskill/layers/base.rb +32 -0
  20. data/lib/saulabs/trueskill/layers/iterated_team_performances.rb +72 -0
  21. data/lib/saulabs/trueskill/layers/performances_to_team_performances.rb +31 -0
  22. data/lib/saulabs/trueskill/layers/prior_to_skills.rb +32 -0
  23. data/lib/saulabs/trueskill/layers/skills_to_performances.rb +31 -0
  24. data/lib/saulabs/trueskill/layers/team_difference_comparision.rb +27 -0
  25. data/lib/saulabs/trueskill/layers/team_performance_differences.rb +22 -0
  26. data/lib/saulabs/trueskill/rating.rb +18 -0
  27. data/lib/saulabs/trueskill/schedules/base.rb +15 -0
  28. data/lib/saulabs/trueskill/schedules/loop.rb +26 -0
  29. data/lib/saulabs/trueskill/schedules/sequence.rb +23 -0
  30. data/lib/saulabs/trueskill/schedules/step.rb +20 -0
  31. data/spec/saulabs/gauss/distribution_spec.rb +162 -0
  32. data/spec/saulabs/gauss/truncated_correction_spec.rb +41 -0
  33. data/spec/saulabs/trueskill/factor_graph_spec.rb +29 -0
  34. data/spec/saulabs/trueskill/factors/greater_than_spec.rb +26 -0
  35. data/spec/saulabs/trueskill/factors/likelihood_spec.rb +32 -0
  36. data/spec/saulabs/trueskill/factors/prior_spec.rb +26 -0
  37. data/spec/saulabs/trueskill/factors/weighted_sum_spec.rb +67 -0
  38. data/spec/saulabs/trueskill/factors/within_spec.rb +26 -0
  39. data/spec/saulabs/trueskill/layers/prior_to_skills_spec.rb +39 -0
  40. data/spec/saulabs/trueskill/schedules_spec.rb +14 -0
  41. data/spec/spec.opts +1 -0
  42. data/spec/spec_helper.rb +29 -0
  43. data/trueskill.gemspec +99 -0
  44. metadata +143 -0
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Lars Kuhnt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ = trueskill
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Lars Kuhnt. See LICENSE for details.
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "trueskill"
8
+ gem.summary = %Q{A ruby library for the trueskill rating system}
9
+ gem.description = %Q{A ruby library for the trueskill rating system}
10
+ gem.email = "lars.kuhnt@gmail.com"
11
+ gem.homepage = "http://github.com/saulabs/trueskill"
12
+ gem.authors = ["Lars Kuhnt"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_dependency('narray', '>= 0.5.9.7')
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "trueskill #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,3 @@
1
+ Dir.glob("#{File.dirname(__FILE__)}/gauss/**/*.rb").each do |src|
2
+ require src
3
+ end
@@ -0,0 +1,125 @@
1
+ module Saulabs
2
+ module Gauss
3
+ class Distribution
4
+
5
+ SQRT2 = Math.sqrt(2).freeze
6
+ INV_SQRT_2PI = (1 / Math.sqrt(2 * Math::PI)).freeze
7
+ LOG_SQRT_2PI = Math.log(Math.sqrt(2 * Math::PI)).freeze
8
+
9
+ # gaussian normal distribution values
10
+ attr_accessor :mean, :deviation, :variance, :precision, :precision_mean
11
+
12
+ def initialize(mean = 0.0, deviation = 0.0)
13
+ mean = 0.0 unless mean.to_f.finite?
14
+ deviation = 0.0 unless deviation.to_f.finite?
15
+ @mean = mean
16
+ @deviation = deviation
17
+ @variance = deviation * deviation
18
+ @precision = deviation == 0.0 ? 0.0 : 1 / @variance.to_f
19
+ @precision_mean = @precision * mean
20
+ end
21
+
22
+ class << self
23
+
24
+ def standard
25
+ @@standard ||= Distribution.new(0.0, 1.0)
26
+ @@standard
27
+ end
28
+
29
+ def with_deviation(mean, deviation)
30
+ Distribution.new(mean, deviation)
31
+ end
32
+
33
+ def with_variance(mean, variance)
34
+ Distribution.new(mean, Math.sqrt(variance))
35
+ end
36
+
37
+ def with_precision(mean, precision)
38
+ Distribution.new(mean / precision, Math.sqrt(1 / precision))
39
+ end
40
+
41
+ def absolute_difference(x, y)
42
+ [(x.precision_mean - y.precision_mean).abs, Math.sqrt((x.precision - y.precision).abs)].max
43
+ end
44
+
45
+ def log_product_normalization(x, y)
46
+ return 0.0 if x.precision == 0.0 || y.precision == 0.0
47
+ variance_sum = x.variance + y.variance
48
+ mean_diff = x.mean - y.mean
49
+ -LOG_SQRT_2PI - (Math.log(variance_sum) / 2.0) - (mean_diff**2 / (2.0 * variance_sum))
50
+ end
51
+
52
+ def log_ratio_normalization(x, y)
53
+ return 0.0 if x.precision == 0.0 || y.precision == 0.0
54
+ variance_diff = y.variance - x.variance
55
+ return 0.0 if variance_diff == 0.0
56
+ mean_diff = x.mean - y.mean
57
+ Math.log(y.variance) + LOG_SQRT_2PI - (Math.log(variance_diff) / 2.0) + (mean_diff**2 / (2.0 * variance_diff))
58
+ end
59
+
60
+ # Computes the cummulative Gaussian distribution at a specified point of interest
61
+ def cumulative_distribution_function(x)
62
+ 0.5 * (1 + Math.erf(x / SQRT2))
63
+ end
64
+ alias_method :cdf, :cumulative_distribution_function
65
+
66
+ # Computes the Gaussian density at a specified point of interest
67
+ def probability_density_function(x)
68
+ INV_SQRT_2PI * Math.exp(-0.5 * (x**2))
69
+ end
70
+ alias_method :pdf, :probability_density_function
71
+
72
+ # The inverse of the cummulative Gaussian distribution function
73
+ def quantile_function(x)
74
+ -SQRT2 * Math.erfc(2.0 * x)
75
+ end
76
+ alias_method :inv_cdf, :quantile_function
77
+
78
+ end
79
+
80
+ def value_at(x)
81
+ exp = -(x - @mean)**2.0 / (2.0 * @variance)
82
+ (1.0/@deviation) * INV_SQRT_2PI * Math.exp(exp)
83
+ end
84
+
85
+ # copy values from other distribution
86
+ def replace(other)
87
+ @precision = other.precision
88
+ @precision_mean = other.precision_mean
89
+ @mean = other.mean
90
+ @deviation = other.deviation
91
+ @variance = other.variance
92
+ end
93
+
94
+ def *(other)
95
+ Distribution.with_precision(self.precision_mean + other.precision_mean, self.precision + other.precision)
96
+ end
97
+
98
+ def /(other)
99
+ Distribution.with_precision(self.precision_mean - other.precision_mean, self.precision - other.precision)
100
+ end
101
+
102
+ # absolute difference
103
+ def -(other)
104
+ Distribution.absolute_difference(self, other)
105
+ end
106
+
107
+ def +(other)
108
+
109
+ end
110
+
111
+ def ==(other)
112
+ self.mean == other.mean && self.variance == other.variance
113
+ end
114
+
115
+ def equals(other)
116
+ self == other
117
+ end
118
+
119
+ def to_s
120
+ "[μ=#{'%.4f' % mean}, σ=#{'%.4f' % deviation}]"
121
+ end
122
+
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,62 @@
1
+ module Saulabs
2
+ module Gauss
3
+ class TruncatedCorrection
4
+
5
+ class << self
6
+
7
+ def w_within_margin(perf_diff, draw_margin)
8
+ abs_diff = perf_diff.abs
9
+ denom = Distribution.cdf(draw_margin - abs_diff) - Distribution.cdf(-draw_margin - abs_diff)
10
+ return 1.0 if denom < 2.2e-162
11
+ vt = v_within_margin(abs_diff, draw_margin)
12
+ return vt**2 + (
13
+ (draw_margin - abs_diff) *
14
+ Gauss::Distribution.standard.value_at(draw_margin - abs_diff) -
15
+ (-draw_margin - abs_diff) *
16
+ Gauss::Distribution.standard.value_at(-draw_margin - abs_diff)
17
+ ) / denom
18
+ end
19
+
20
+ def v_within_margin(perf_diff, draw_margin)
21
+ abs_diff = perf_diff.abs
22
+ denom = Distribution.cdf(draw_margin - abs_diff) - Distribution.cdf(-draw_margin - abs_diff)
23
+ if denom < 2.2e-162
24
+ return perf_diff < 0 ? -perf_diff - draw_margin : -perf_diff + draw_margin
25
+ end
26
+ num = Gauss::Distribution.standard.value_at(-draw_margin - abs_diff) -
27
+ Gauss::Distribution.standard.value_at(draw_margin - abs_diff)
28
+ perf_diff < 0 ? -num/denom : num/denom
29
+ end
30
+
31
+ def w_exceeds_margin(perf_diff, draw_margin)
32
+ denom = Distribution.cdf(perf_diff - draw_margin)
33
+ if denom < 2.2e-162
34
+ return perf_diff < 0.0 ? 1.0 : 0.0
35
+ else
36
+ v = v_exceeds_margin(perf_diff, draw_margin)
37
+ return v * (v + perf_diff - draw_margin)
38
+ end
39
+ end
40
+
41
+ def v_exceeds_margin(perf_diff, draw_margin)
42
+ denom = Distribution.cdf(perf_diff - draw_margin)
43
+ denom < 2.2e-162 ? -perf_diff + draw_margin : Gauss::Distribution.standard.value_at(perf_diff - draw_margin)/denom
44
+ end
45
+
46
+ def exceeds_margin(perf_diff, draw_margin)
47
+ abs_diff = perf_diff.abs
48
+ denom = Distribution.cdf(draw_margin - abs_diff) - Distribution.cdf(-draw_margin - abs_diff)
49
+ if denom < 2.2e-162
50
+ return 1.0
51
+ else
52
+ v = v_exceeds_margin(abs_diff, draw_margin)
53
+ return v**2 +
54
+ ((draw_margin - abs_diff) * Gauss::Distribution.standard.value_at(draw_margin - abs_diff) -
55
+ (-draw_margin - abs_diff) * Gauss::Distribution.standard.value_at(-draw_margin - abs_diff)) / denom
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,7 @@
1
+ require 'pp'
2
+
3
+ require "#{File.dirname(__FILE__)}/gauss.rb"
4
+
5
+ Dir.glob("#{File.dirname(__FILE__)}/trueskill/**/*.rb").each do |src|
6
+ require src
7
+ end
@@ -0,0 +1,75 @@
1
+ module Saulabs
2
+ module TrueSkill
3
+
4
+ class FactorGraph
5
+
6
+ attr_reader :teams, :beta, :beta_squared, :draw_probability, :epsilon, :layers
7
+
8
+ # teams: 2 dimensional array of ratings
9
+ def initialize(teams, ranks, options = {})
10
+ @teams = teams
11
+ @ranks = ranks
12
+ @beta = options[:beta] || 25/6.0
13
+ @draw_probability = options[:draw_probability] || 0.1
14
+ @beta_squared = @beta**2
15
+ @epsilon = -Math.sqrt(2.0 * @beta_squared) * Gauss::Distribution.inv_cdf((1.0 - @draw_probability) / 2.0)
16
+
17
+ @prior_layer = Layers::PriorToSkills.new(self, @teams)
18
+ @layers = [
19
+ @prior_layer,
20
+ Layers::SkillsToPerformances.new(self),
21
+ Layers::PerformancesToTeamPerformances.new(self),
22
+ Layers::IteratedTeamPerformances.new(self,
23
+ Layers::TeamPerformanceDifferences.new(self),
24
+ Layers::TeamDifferenceComparision.new(self, ranks)
25
+ )
26
+ ]
27
+ end
28
+
29
+ def draw_margin
30
+ Gauss::Distribution.inv_cdf(0.5*(@draw_probability + 1)) * Math.sqrt(1 + 1) * @beta
31
+ end
32
+
33
+ def evaluate
34
+ build_layers
35
+ run_schedule
36
+ [ranking_probability, updated_skills]
37
+ end
38
+
39
+ private
40
+
41
+ def ranking_probability
42
+ # factor_list = []
43
+ # sum_log_z, sum_log_s = 0.0
44
+ # @layers.each do |layer|
45
+ # layer.factors.each do |factor|
46
+ # factor.reset_marginals
47
+ # factor.messages.each_index { |i| sum_log_z += factor.send_message_at(i) }
48
+ # sum_log_s += factor.log_normalization
49
+ # end
50
+ # end
51
+ # Math.exp(sum_log_z + sum_log_s)
52
+ end
53
+
54
+ def updated_skills
55
+ @prior_layer.output
56
+ end
57
+
58
+ def build_layers
59
+ output = nil
60
+ @layers.each do |layer|
61
+ layer.input = output
62
+ layer.build
63
+ output = layer.output
64
+ end
65
+ end
66
+
67
+ def run_schedule
68
+ schedules = @layers.map(&:prior_schedule) + @layers.reverse.map(&:posterior_schedule)
69
+ Schedules::Sequence.new(schedules.compact).visit
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,50 @@
1
+ module Saulabs
2
+ module TrueSkill
3
+ module Factors
4
+
5
+ class Base
6
+
7
+ def initialize
8
+ @messages = []
9
+ @bindings = {}
10
+ @variables = []
11
+ @priors = []
12
+ end
13
+
14
+ def update_message_at(index)
15
+ raise "Abstract method Factors::Base#update_message_at(index) called"
16
+ end
17
+
18
+ def message_count
19
+ @messages.size
20
+ end
21
+
22
+ def log_normalization
23
+ raise "Abstract method Factors::Base#log_normalization called"
24
+ end
25
+
26
+ def reset_marginals
27
+ @bindings.values.each { |var| var.replace(Gauss::Distribution.new) }
28
+ end
29
+
30
+ def send_message_at(idx)
31
+ message = @messages[idx]
32
+ variable = @variables[idx]
33
+ log_z = Gauss::Distribution.log_product_normalization(message, variable)
34
+ variable.replace(message * variable)
35
+ return log_z
36
+ end
37
+
38
+ def bind(variable)
39
+ message = Gauss::Distribution.new
40
+ @messages << message
41
+ @bindings[message] = variable
42
+ @variables << variable
43
+ return message
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+ end