openskill 0.1.0 → 1.0.1
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 +4 -4
- data/README.md +37 -14
- data/lib/openskill/models/bradley_terry_full.rb +616 -0
- data/lib/openskill/models/bradley_terry_part.rb +606 -0
- data/lib/openskill/models/common.rb +69 -0
- data/lib/openskill/models/plackett_luce.rb +26 -26
- data/lib/openskill/models/thurstone_mosteller_full.rb +595 -0
- data/lib/openskill/models/thurstone_mosteller_part.rb +610 -0
- data/lib/openskill/statistics/normal.rb +1 -1
- data/lib/openskill/version.rb +1 -1
- data/lib/openskill.rb +8 -4
- metadata +30 -12
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../statistics/normal"
|
|
5
|
+
require_relative "common"
|
|
6
6
|
|
|
7
7
|
module OpenSkill
|
|
8
8
|
module Models
|
|
@@ -71,8 +71,8 @@ module OpenSkill
|
|
|
71
71
|
# @raise [ArgumentError] if rating_array is invalid
|
|
72
72
|
def load_rating(rating_array, name: nil)
|
|
73
73
|
raise ArgumentError, "Rating must be an Array, got #{rating_array.class}" unless rating_array.is_a?(Array)
|
|
74
|
-
raise ArgumentError,
|
|
75
|
-
raise ArgumentError,
|
|
74
|
+
raise ArgumentError, "Rating array must have exactly 2 elements" unless rating_array.size == 2
|
|
75
|
+
raise ArgumentError, "Rating values must be numeric" unless rating_array.all? { |v| v.is_a?(Numeric) }
|
|
76
76
|
|
|
77
77
|
Rating.new(mu: rating_array[0], sigma: rating_array[1], name: name)
|
|
78
78
|
end
|
|
@@ -93,7 +93,7 @@ module OpenSkill
|
|
|
93
93
|
validate_weights!(teams, weights) if weights
|
|
94
94
|
|
|
95
95
|
# Can't have both ranks and scores
|
|
96
|
-
raise ArgumentError,
|
|
96
|
+
raise ArgumentError, "Cannot provide both ranks and scores" if ranks && scores
|
|
97
97
|
|
|
98
98
|
# Deep copy teams to avoid mutating input
|
|
99
99
|
original_teams = teams
|
|
@@ -300,25 +300,25 @@ module OpenSkill
|
|
|
300
300
|
end
|
|
301
301
|
|
|
302
302
|
def <(other)
|
|
303
|
-
raise ArgumentError,
|
|
303
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
304
304
|
|
|
305
305
|
ordinal < other.ordinal
|
|
306
306
|
end
|
|
307
307
|
|
|
308
308
|
def >(other)
|
|
309
|
-
raise ArgumentError,
|
|
309
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
310
310
|
|
|
311
311
|
ordinal > other.ordinal
|
|
312
312
|
end
|
|
313
313
|
|
|
314
314
|
def <=(other)
|
|
315
|
-
raise ArgumentError,
|
|
315
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
316
316
|
|
|
317
317
|
ordinal <= other.ordinal
|
|
318
318
|
end
|
|
319
319
|
|
|
320
320
|
def >=(other)
|
|
321
|
-
raise ArgumentError,
|
|
321
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
322
322
|
|
|
323
323
|
ordinal >= other.ordinal
|
|
324
324
|
end
|
|
@@ -377,8 +377,8 @@ module OpenSkill
|
|
|
377
377
|
|
|
378
378
|
# Validate teams structure
|
|
379
379
|
def validate_teams!(teams)
|
|
380
|
-
raise ArgumentError,
|
|
381
|
-
raise ArgumentError,
|
|
380
|
+
raise ArgumentError, "Teams must be an Array" unless teams.is_a?(Array)
|
|
381
|
+
raise ArgumentError, "Must have at least 2 teams" if teams.size < 2
|
|
382
382
|
|
|
383
383
|
teams.each_with_index do |team, idx|
|
|
384
384
|
raise ArgumentError, "Team #{idx} must be an Array" unless team.is_a?(Array)
|
|
@@ -393,7 +393,7 @@ module OpenSkill
|
|
|
393
393
|
# Validate ranks
|
|
394
394
|
def validate_ranks!(teams, ranks)
|
|
395
395
|
raise ArgumentError, "Ranks must be an Array, got #{ranks.class}" unless ranks.is_a?(Array)
|
|
396
|
-
raise ArgumentError,
|
|
396
|
+
raise ArgumentError, "Ranks must have same length as teams" if ranks.size != teams.size
|
|
397
397
|
|
|
398
398
|
ranks.each do |rank|
|
|
399
399
|
raise ArgumentError, "All ranks must be numeric, got #{rank.class}" unless rank.is_a?(Numeric)
|
|
@@ -403,7 +403,7 @@ module OpenSkill
|
|
|
403
403
|
# Validate scores
|
|
404
404
|
def validate_scores!(teams, scores)
|
|
405
405
|
raise ArgumentError, "Scores must be an Array, got #{scores.class}" unless scores.is_a?(Array)
|
|
406
|
-
raise ArgumentError,
|
|
406
|
+
raise ArgumentError, "Scores must have same length as teams" if scores.size != teams.size
|
|
407
407
|
|
|
408
408
|
scores.each do |score|
|
|
409
409
|
raise ArgumentError, "All scores must be numeric, got #{score.class}" unless score.is_a?(Numeric)
|
|
@@ -413,7 +413,7 @@ module OpenSkill
|
|
|
413
413
|
# Validate weights
|
|
414
414
|
def validate_weights!(teams, weights)
|
|
415
415
|
raise ArgumentError, "Weights must be an Array, got #{weights.class}" unless weights.is_a?(Array)
|
|
416
|
-
raise ArgumentError,
|
|
416
|
+
raise ArgumentError, "Weights must have same length as teams" if weights.size != teams.size
|
|
417
417
|
|
|
418
418
|
weights.each_with_index do |team_weights, idx|
|
|
419
419
|
raise ArgumentError, "Weights for team #{idx} must be an Array" unless team_weights.is_a?(Array)
|
|
@@ -481,7 +481,7 @@ module OpenSkill
|
|
|
481
481
|
comparison_count += 1
|
|
482
482
|
end
|
|
483
483
|
|
|
484
|
-
adjusted_mu += comparison_count > 0 ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
484
|
+
adjusted_mu += (comparison_count > 0) ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
485
485
|
end
|
|
486
486
|
|
|
487
487
|
summed = Math.exp(adjusted_mu / c_value)
|
|
@@ -516,11 +516,11 @@ module OpenSkill
|
|
|
516
516
|
|
|
517
517
|
sorted_team.each do |player|
|
|
518
518
|
balance_weight = if @balance
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
519
|
+
ordinal_diff = max_ordinal - player.ordinal
|
|
520
|
+
1 + (ordinal_diff / (max_ordinal + @kappa))
|
|
521
|
+
else
|
|
522
|
+
1.0
|
|
523
|
+
end
|
|
524
524
|
|
|
525
525
|
mu_sum += player.mu * balance_weight
|
|
526
526
|
sigma_squared_sum += (player.sigma * balance_weight)**2
|
|
@@ -540,10 +540,10 @@ module OpenSkill
|
|
|
540
540
|
return [] if game.empty?
|
|
541
541
|
|
|
542
542
|
team_scores = if ranks
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
543
|
+
ranks.each_with_index.map { |rank, idx| rank || idx }
|
|
544
|
+
else
|
|
545
|
+
game.each_index.to_a
|
|
546
|
+
end
|
|
547
547
|
|
|
548
548
|
sorted_scores = team_scores.sort
|
|
549
549
|
rank_map = {}
|
|
@@ -603,7 +603,7 @@ module OpenSkill
|
|
|
603
603
|
comparison_count += 1
|
|
604
604
|
end
|
|
605
605
|
|
|
606
|
-
adjusted_mu_i += comparison_count > 0 ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
606
|
+
adjusted_mu_i += (comparison_count > 0) ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
607
607
|
end
|
|
608
608
|
|
|
609
609
|
i_mu_over_c = Math.exp(adjusted_mu_i / c_value)
|