lib/icu_ratings/tournament.rb in icu_ratings-1.2.2 vs lib/icu_ratings/tournament.rb in icu_ratings-1.3.0

- old
+ new

@@ -55,25 +55,35 @@ # # t.player(1).new_rating # # See ICU::RatedPlayer and ICU::RatedResult for more details. # + # The <em>rate!</em> method takes some optional arguments to control the algoritm, for example: + # + # t.rate!(max_iterations2: 30) + # + # The complete set of current options is: + # + # * <em>max_iterations1</em>: the maximum number of re-estimation iterations before the bonus calculation (default <b>30</b>) + # * <em>max_iterations2</em>: the maximum number of re-estimation iterations after the bonus calculation (default <b>1</b>) + # * <em>threshold</em>: the maximum difference allowed between re-estimated ratings in a stabe solution (default <b>0.5</b>) + # # == Error Handling # # Some of the above methods have the potential to raise RuntimeError exceptions. # In the case of _add_player_ and _add_result_, the use of invalid arguments # would cause such an error. Theoretically, the <em>rate!</em> method could also throw an # exception if the iterative algorithm it uses to estimate performance ratings # of unrated players failed to converge. However an instance of non-convergence # has yet to be observed in practice. # - # Since exception throwing is how errors are signalled, you should arrange for them - # to be caught and handled in some suitable place in your code. + # Since exception throwing is how errors are signalled, you should arrange + # for them to be caught and handled in some suitable place in your code. # class RatedTournament attr_accessor :desc - attr_reader :start, :no_bonuses + attr_reader :start, :no_bonuses, :iterations1, :iterations2 # Add a new player to the tournament. Returns the instance of ICU::RatedPlayer created. # See ICU::RatedPlayer for details. def add_player(num, args={}) raise "player with number #{num} already exists" if @player[num] @@ -95,18 +105,23 @@ p2.add_result(r2) nil end # Rate the tournament. Called after all players and results have been added. - def rate! + def rate!(opt={}) + max_iterations1 = opt[:max_iterations1] || 30 + max_iterations2 = opt[:max_iterations2] || 1 + threshold = opt[:threshold] || 0.5 players.each { |p| p.init } - performance_ratings(30) + @iterations1 = performance_ratings(max_iterations1, threshold) players.each { |p| p.rate! } if !no_bonuses && calculate_bonuses > 0 players.each { |p| p.rate! } - performance_ratings(1) + @iterations2 = performance_ratings(max_iterations2, threshold) calculate_bonuses + else + @iterations2 = 0 end end # Return an array of all players, in order of player number. def players @@ -121,11 +136,11 @@ # Set the start date. Raises exception on error. def start=(date) @start = ICU::Util.parsedate!(date) end - # Set whether there are no bonuses (false by default) + # Set whether there are no bonuses (false by default). def no_bonuses=(no_bonuses) @no_bonuses = no_bonuses ? true : false end private @@ -135,17 +150,18 @@ [:desc, :start, :no_bonuses].each { |atr| self.send("#{atr}=", opt[atr]) unless opt[atr].nil? } @player = Hash.new end # Calculate performance ratings either iteratively or with just one sweep for bonus calculations. - def performance_ratings(max) + def performance_ratings(max, thresh) stable, count = false, 0 while !stable && count < max @player.values.each { |p| p.estimate_performance } - stable = @player.values.inject(true) { |ok, p| p.update_performance && ok } + stable = @player.values.inject(true) { |ok, p| p.update_performance(thresh) && ok } count+= 1 end raise "performance rating estimation did not converge" if max > 1 && !stable + count end # Calculate bonuses for all players and return the number who got one. def calculate_bonuses @player.values.inject(0) { |t,p| t + (p.calculate_bonus ? 1 : 0) }