Module: TrueskillThroughTime
- Defined in:
- lib/TrueskillThroughTime.rb,
lib/TrueskillThroughTime/version.rb
Defined Under Namespace
Classes: Agent, Batch, DiffMessages, DrawMessages, Error, Event, Game, Gaussian, History, Item, Player, Skill, Team, TeamVariable
Constant Summary collapse
- BETA =
Configuration Constants
1.0- MU =
0.0- SIGMA =
BETA * 6
- GAMMA =
BETA * 0.03
- P_DRAW =
0.0- EPSILON =
1e-6- ITERATIONS =
30- SQRT2 =
Derived Math Constants
Math.sqrt(2)
- SQRT2PI =
Math.sqrt(2 * Math::PI)
- INF =
Float::INFINITY
- N01 =
Gaussian.new(0.0, 1.0)
- N00 =
Gaussian.new(0.0, 0.0)
- NINF =
Gaussian.new(0.0, INF)
- Nms =
Gaussian.new(MU, SIGMA)
- VERSION =
"1.0.0"
Instance Method Summary collapse
-
#approx(n, margin, tie) ⇒ Object
TODO: It seems like this should be a Guassian function.
- #cdf(x, mu = 0, sigma = 1) ⇒ Object
-
#clean!(agents, last_time = false) ⇒ Object
Agents is a dict in python and their implementation is whack.
- #compute_elapsed(last_time, actual_time) ⇒ Object
- #compute_margin(p_draw, sd) ⇒ Object
- #dict_diff(old, new) ⇒ Object
- #erfc(x) ⇒ Object
- #erfcinv(y) ⇒ Object
- #game_example(mu = MU, sigma = SIGMA, beta = BETA, p_draw = P_DRAW) ⇒ Object
- #get_composition(events) ⇒ Object
- #get_results(events) ⇒ Object
- #gr_tuple(tup, threshold) ⇒ Object
- #history_example(mu = MU, sigma = SIGMA, beta = BETA, p_draw = P_DRAW) ⇒ Object
- #liquid_example(path: "./example_data/liquid_t2_tournament_results.json") ⇒ Object
- #max_tuple(t1, t2) ⇒ Object
- #mu_sigma(tau_, pi_) ⇒ Object
- #pdf(x, mu, sigma) ⇒ Object
- #podium(xs) ⇒ Object
- #ppf(p, mu, sigma) ⇒ Object
- #sortperm(xs, reverse = false) ⇒ Object
- #tau_pi(mu, sigma) ⇒ Object
- #trunc(mu, sigma, margin, tie) ⇒ Object
- #v_w(mu, sigma, margin, tie) ⇒ Object
Instance Method Details
#approx(n, margin, tie) ⇒ Object
TODO: It seems like this should be a Guassian function
126 127 128 129 130 131 132 |
# File 'lib/TrueskillThroughTime.rb', line 126 def approx(n, margin, tie) # TODO: It seems like this should be a Guassian function mu, sigma = trunc(n.mu, n.sigma, margin, tie) # if $should_debug # puts "approx(#{[n, margin, tie]}) (n, margin, tie) = mu #{mu} sigma #{sigma}" # end Gaussian.new(mu, sigma) end |
#cdf(x, mu = 0, sigma = 1) ⇒ Object
90 91 92 |
# File 'lib/TrueskillThroughTime.rb', line 90 def cdf(x, mu=0, sigma=1) 0.5 * erfc(-(x - mu) / (sigma * SQRT2)) end |
#clean!(agents, last_time = false) ⇒ Object
Agents is a dict in python and their implementation is whack.
670 671 672 673 674 675 |
# File 'lib/TrueskillThroughTime.rb', line 670 def clean!(agents, last_time=false) agents.each_pair do |agent_label, agent| agent. = NINF agent.last_time = -INF if last_time end end |
#compute_elapsed(last_time, actual_time) ⇒ Object
736 737 738 739 740 741 742 743 744 |
# File 'lib/TrueskillThroughTime.rb', line 736 def compute_elapsed(last_time, actual_time) if last_time == INF 1 elsif last_time == -INF 0 else actual_time - last_time end end |
#compute_margin(p_draw, sd) ⇒ Object
134 135 136 |
# File 'lib/TrueskillThroughTime.rb', line 134 def compute_margin(p_draw, sd) (ppf(0.5-p_draw/2.0, 0.0, sd)).abs end |
#dict_diff(old, new) ⇒ Object
170 171 172 173 174 175 176 |
# File 'lib/TrueskillThroughTime.rb', line 170 def dict_diff(old, new) step = [0.0, 0.0] old.each_key do |a| step = max_tuple(step, old[a].delta(new[a])) # .delta is Guassian method.... end step end |
#erfc(x) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/TrueskillThroughTime.rb', line 31 def erfc(x) # """(http://bit.ly/zOLqbc)""" z = x.abs t = 1.0 / (1.0 + z / 2.0) a = -0.82215223 + t * 0.17087277; b = 1.48851587 + t * a c = -1.13520398 + t * b; d = 0.27886807 + t * c; e = -0.18628806 + t * d f = 0.09678418 + t * e; g = 0.37409196 + t * f; h = 1.00002368 + t * g r = t * Math.exp(-z * z - 1.26551223 + t * h) if x.positive? r else 2.0 - r end end |
#erfcinv(y) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/TrueskillThroughTime.rb', line 46 def erfcinv(y) raise(ArgumentError, "Argument must be non-negative numbers") if y.negative? return -INF if y > 2 return INF if y.zero? y = 2 - y if y >= 1 t = Math.sqrt(-2 * Math.log(y / 2.0)) x = -0.70711 * ((2.30753 + t * 0.27061) / (1.0 + t * (0.99229 + t * 0.04481)) - t) 3.times do err = erfc(x) - y x += err / (1.12837916709551257 * Math.exp(-(x**2)) - x * err) end y < 1 ? x : -x end |
#game_example(mu = MU, sigma = SIGMA, beta = BETA, p_draw = P_DRAW) ⇒ Object
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 |
# File 'lib/TrueskillThroughTime.rb', line 1066 def game_example(mu=MU, sigma=SIGMA, beta=BETA, p_draw=P_DRAW) a1 = Player.new(Gaussian.new(mu-500, sigma), beta=beta) a2 = Player.new(Gaussian.new(mu-500, sigma), beta=beta) a3 = Player.new(Gaussian.new(mu, sigma), beta=beta) a4 = Player.new(Gaussian.new(mu, sigma), beta=beta) a5 = Player.new(Gaussian.new(mu+5000, sigma), beta=beta) a6 = Player.new(Gaussian.new(mu+5000, sigma), beta=beta) team_a = [ a1, a2 ] team_b = [ a3, a4 ] team_c = [ a5, a6 ] teams = [team_a, team_b, team_c] result = [0.0, 1.0, 2.0] g = Game.new(teams, result, p_draw=p_draw) puts "Teams: #{g.teams}" puts "Likelihoods: #{g.likelihoods}" puts "Evidence: #{g.evidence}" puts "Posteriors: #{g.posteriors}" (0...teams.length).each do |i| puts "#{i}: #{g.performance(i)}" end end |
#get_composition(events) ⇒ Object
728 729 730 |
# File 'lib/TrueskillThroughTime.rb', line 728 def get_composition(events) events.map {|event| event.teams.map {|team| team.items.map {|item| item.name} } } end |
#get_results(events) ⇒ Object
732 733 734 |
# File 'lib/TrueskillThroughTime.rb', line 732 def get_results(events) events.map {|event| event.teams.map {|team| team.output } } end |
#gr_tuple(tup, threshold) ⇒ Object
154 155 156 |
# File 'lib/TrueskillThroughTime.rb', line 154 def gr_tuple(tup, threshold) tup.max > threshold end |
#history_example(mu = MU, sigma = SIGMA, beta = BETA, p_draw = P_DRAW) ⇒ Object
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 |
# File 'lib/TrueskillThroughTime.rb', line 1088 def history_example(mu=MU, sigma=SIGMA, beta=BETA, p_draw=P_DRAW) c1 = [["a"], ["b"]] c2 = [["b"], ["c"]] c3 = [["c"], ["a"]] composition = [c1, c2, c3] h = History.new(composition, gamma: 0.0) puts "#{h.learning_curves["a"]}" # [(1, N(mu=3.339, sigma=4.985)), (3, N(mu=-2.688, sigma=3.779))] puts "#{h.learning_curves["b"]}" # [(1, N(mu=-3.339, sigma=4.985)), (2, N(mu=0.059, sigma=4.218))] h.convergence puts "#{h.learning_curves["a"]}" # [[1, N(mu= 1.1776221018565704e-07, sigma= 2.3948083841791528)], [3, N(mu= -9.875327098012653e-08, sigma= 2.3948083506699978)]] puts "#{h.learning_curves["b"]}" # [[1, N(mu= -6.743217353120669e-08, sigma= 2.3948083803990317)], [2, N(mu= -6.888059735564812e-08, sigma= 2.394808375074641)]] puts "#{h.log_evidence}" puts "Foo" end |
#liquid_example(path: "./example_data/liquid_t2_tournament_results.json") ⇒ Object
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 |
# File 'lib/TrueskillThroughTime.rb', line 1107 def liquid_example(path:"./example_data/liquid_t2_tournament_results.json") json_data = JSON.load(File.open(path)) tournaments = {} json_data["tournament_positions"].each_pair do |k, v| # Symbolise keys and convert to dates, remove fake tournaments, remove tournaments with draws next unless v["results"].length > 2 # fake tournaments have < 3 teams results = {} had_ties = false v["results"].each_pair do |rk, rv| # numberise keys, then sort the results by them if rv.length > 1 had_ties = true break else results[rk.to_f] = rv end end unless had_ties tournaments[k] = { :results => results.sort, :day => DateTime.strptime(v["date"], "%Y-%m-%d %H:%M:%S").to_time.to_i / (60 * 60 * 24) } end end tournaments = tournaments.sort_by {|k, v| v[:day]}.to_h # Need to get to compositions, results, times: results needs to be inverse of position compositions = [] results = [] times = [] tournaments.each_pair do |tournament_name, t_data| times << t_data[:day] t_results = [] t_compositions = [] t_data[:results].each_with_index do |(position, composition), i| t_results << t_data[:results].length - i # t_compositions << composition.map {|player| [player]} t_compositions << composition.flatten end compositions << t_compositions results << t_results end h = History.new(compositions, results:results, times:times, mu:1200, sigma:400, beta:1320, gamma:19, p_draw: 0.01375) puts h.log_evidence h.convergence(epsilon: 0.01, iterations: 60, verbose: true) puts h.log_evidence puts h.learning_curves.first h.evidence.each_with_index do |ev, idx| puts "#{idx}\t#{ev}\t#{tournaments.keys[idx]}" end end |
#max_tuple(t1, t2) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/TrueskillThroughTime.rb', line 138 def max_tuple(t1, t2) # Let's emulate python nan sorting behaviour! responses = [0.0, 0.0] responses[0] = if t1[0].to_f.nan? || t2[0].to_f.nan? t1[0] else [t1[0], t2[0]].max end responses[1] = if t1[1].to_f.nan? || t2[1].to_f.nan? t1[1] else [t1[1], t2[1]].max end responses end |
#mu_sigma(tau_, pi_) ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/TrueskillThroughTime.rb', line 77 def mu_sigma(tau_, pi_) raise(ArgumentError, "Sigma must be greater than 0.") if (pi_ + 1e-5) < 0.0 if pi_ > 0.0 sigma = Math.sqrt(1.0/pi_) mu = tau_ / pi_.to_f else sigma = INF mu = 0.0 end [mu, sigma] # again tuple in original end |
#pdf(x, mu, sigma) ⇒ Object
94 95 96 97 98 |
# File 'lib/TrueskillThroughTime.rb', line 94 def pdf(x, mu, sigma) normaliser = (SQRT2PI * sigma)**-1 functional = Math.exp(-((x - mu)**2) / (2*sigma**2)) normaliser * functional end |
#podium(xs) ⇒ Object
158 159 160 |
# File 'lib/TrueskillThroughTime.rb', line 158 def podium(xs) sortperm(xs) end |
#ppf(p, mu, sigma) ⇒ Object
100 101 102 |
# File 'lib/TrueskillThroughTime.rb', line 100 def ppf(p, mu, sigma) mu - sigma * SQRT2 * erfcinv(2 * p) end |
#sortperm(xs, reverse = false) ⇒ Object
162 163 164 165 166 167 168 |
# File 'lib/TrueskillThroughTime.rb', line 162 def sortperm(xs, reverse=false) sorted_indices = xs.each_with_index .sort_by { |v, i| v } .map { |v, i| i } sorted_indices.reverse! if reverse sorted_indices end |
#tau_pi(mu, sigma) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/TrueskillThroughTime.rb', line 64 def tau_pi(mu, sigma) raise(ArgumentError, "Sigma must be greater than 0.") if (sigma + 1e-5) < 0.0 if sigma > 0.0 pi_ = sigma ** -2 tau_ = pi_ * mu else pi_ = INF tau_ = INF end [tau_, pi_] # python returns tuple end |
#trunc(mu, sigma, margin, tie) ⇒ Object
119 120 121 122 123 124 |
# File 'lib/TrueskillThroughTime.rb', line 119 def trunc(mu, sigma, margin, tie) v, w = v_w(mu, sigma, margin, tie) mu_trunc = mu + sigma * v sigma_trunc = sigma * Math.sqrt(1-w) [mu_trunc, sigma_trunc] end |
#v_w(mu, sigma, margin, tie) ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/TrueskillThroughTime.rb', line 104 def v_w(mu, sigma, margin, tie) if tie _alpha = (-margin-mu)/sigma _beta = ( margin-mu)/sigma v = (pdf(_alpha, 0, 1)-pdf(_beta, 0, 1))/(cdf(_beta, 0, 1)-cdf(_alpha, 0, 1)) u = (_alpha*pdf(_alpha, 0, 1)-_beta*pdf(_beta, 0, 1))/(cdf(_beta, 0, 1)-cdf(_alpha, 0, 1)) w = - ( u - v**2 ) else _alpha = (margin-mu)/sigma v = pdf(-_alpha, 0, 1) / cdf(-_alpha, 0, 1) w = v * (v + (-_alpha)) end [v, w] # again, a tuple end |