Skip to content

Commit

Permalink
Ratings - account for first player advantage (WIP) lichess-org#6818
Browse files Browse the repository at this point in the history
  • Loading branch information
ddugovic committed Mar 22, 2021
1 parent f48daa8 commit d2e2344
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 24 deletions.
44 changes: 42 additions & 2 deletions modules/rating/src/main/java/glicko2/Rating.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
public class Rating {

private double advantage;
private double rating;
private double ratingDeviation;
private double volatility;
Expand All @@ -31,17 +32,36 @@ public class Rating {
private double workingVolatility;

public Rating(double initRating, double initRatingDeviation, double initVolatility, int nbResults) {
this(initRating, initRatingDeviation, initVolatility, nbResults, null);
this(0.0d, initRating, initRatingDeviation, initVolatility, nbResults, null);
}

public Rating(double initRating, double initRatingDeviation, double initVolatility, int nbResults, DateTime lastRatingPeriodEndDate) {
this(0.0d, initRating, initRatingDeviation, initVolatility, nbResults, lastRatingPeriodEndDate);
}

public Rating(double advantage, double initRating, double initRatingDeviation, double initVolatility, int nbResults, DateTime lastRatingPeriodEndDate) {
this.advantage = advantage;
this.rating = initRating;
this.ratingDeviation = initRatingDeviation;
this.volatility = initVolatility;
this.numberOfResults = nbResults;
this.lastRatingPeriodEndDate = lastRatingPeriodEndDate;
}

public Rating setAdvantage(double advantage) {
this.advantage = advantage;
return this;
}

/**
* Return the skill advantage (first-player handicap) value.
*
* @return double
*/
public double getAdvantage() {
return this.advantage;
}

/**
* Return the average skill value of the player.
*
Expand All @@ -55,6 +75,16 @@ public void setRating(double rating) {
this.rating = rating;
}

/**
* Return the average skill value of the player scaled down
* to the scale used by the algorithm's internal workings.
*
* @return double
*/
public double getGlicko2RatingWithAdvantage() {
return RatingCalculator.convertRatingToGlicko2Scale(this.rating + advantage);
}

/**
* Return the average skill value of the player scaled down
* to the scale used by the algorithm's internal workings.
Expand All @@ -70,7 +100,7 @@ public double getGlicko2Rating() {
*
* @param double
*/
public void setGlicko2Rating(double rating) {
private void setGlicko2Rating(double rating) {
this.rating = RatingCalculator.convertRatingToOriginalGlickoScale(rating);
}

Expand Down Expand Up @@ -159,4 +189,14 @@ public void setWorkingRating(double workingRating) {
public void setWorkingRatingDeviation(double workingRatingDeviation) {
this.workingRatingDeviation = workingRatingDeviation;
}


// note that the newly calculated rating values are stored in a "working" area in the Rating object
// this avoids us attempting to calculate subsequent participants' ratings against a moving target
public void updateWorkingRatingAndResults(double workingRatingIncrement, double workingRatingDeviation, int results) {
setWorkingRating( getGlicko2Rating() + workingRatingIncrement );
setWorkingRatingDeviation( workingRatingDeviation );
incrementNumberOfResults( results);
}
}
}
34 changes: 15 additions & 19 deletions modules/rating/src/main/java/glicko2/RatingCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ public void updateRatings(RatingPeriodResults results, boolean skipDeviationIncr
// if a player does not compete during the rating period, then only Step 6 applies.
// the player's rating and volatility parameters remain the same but deviation increases

player.setWorkingRating(player.getGlicko2Rating());
player.setWorkingRatingDeviation(calculateNewRD(player.getGlicko2RatingDeviation(), player.getVolatility(), elapsedRatingPeriods));
player.setWorkingVolatility(player.getVolatility());
double newSigma = player.getVolatility();
double newPhi = calculateNewRD( player.getGlicko2RatingDeviation(), player.getVolatility(), elapsedRatingPeriods );
player.updateWorkingRatingAndResults( 0.0d, newPhi, 0 );
player.setWorkingVolatility( newSigma );
}
}

Expand Down Expand Up @@ -191,23 +192,18 @@ private void calculateNewRating(Rating player, List<Result> results, double elap
throw new RuntimeException("Convergence fail");
}

double newSigma = Math.exp( A/2.0 );

player.setWorkingVolatility(newSigma);
double newSigma = Math.exp( A / 2.0 );
player.setWorkingVolatility( newSigma );

// Step 6
double phiStar = calculateNewRD( phi, newSigma, elapsedRatingPeriods );

// Step 7
double newPhi = 1.0 / Math.sqrt(( 1.0 / Math.pow(phiStar, 2) ) + ( 1.0 / v ));
double muIncrement = Math.pow( newPhi, 2 ) * outcomeBasedRating( player, results );

// note that the newly calculated rating values are stored in a "working" area in the Rating object
// this avoids us attempting to calculate subsequent participants' ratings against a moving target
player.setWorkingRating(
player.getGlicko2Rating()
+ ( Math.pow(newPhi, 2) * outcomeBasedRating(player, results)));
player.setWorkingRatingDeviation(newPhi);
player.incrementNumberOfResults(results.size());
// Step 8
player.updateWorkingRatingAndResults( muIncrement, newPhi, results.size() );
}

private double f(double x, double delta, double phi, double v, double a, double tau) {
Expand Down Expand Up @@ -254,11 +250,11 @@ private double v(Rating player, List<Result> results) {
for ( Result result: results ) {
v = v + (
( Math.pow( g(result.getOpponent(player).getGlicko2RatingDeviation()), 2) )
* E(player.getGlicko2Rating(),
result.getOpponent(player).getGlicko2Rating(),
* E(player.getGlicko2RatingWithAdvantage(),
result.getOpponent(player).getGlicko2RatingWithAdvantage(),
result.getOpponent(player).getGlicko2RatingDeviation())
* ( 1.0 - E(player.getGlicko2Rating(),
result.getOpponent(player).getGlicko2Rating(),
* ( 1.0 - E(player.getGlicko2RatingWithAdvantage(),
result.getOpponent(player).getGlicko2RatingWithAdvantage(),
result.getOpponent(player).getGlicko2RatingDeviation())
));
}
Expand Down Expand Up @@ -293,8 +289,8 @@ private double outcomeBasedRating(Rating player, List<Result> results) {
outcomeBasedRating = outcomeBasedRating
+ ( g(result.getOpponent(player).getGlicko2RatingDeviation())
* ( result.getScore(player) - E(
player.getGlicko2Rating(),
result.getOpponent(player).getGlicko2Rating(),
player.getGlicko2RatingWithAdvantage(),
result.getOpponent(player).getGlicko2RatingWithAdvantage(),
result.getOpponent(player).getGlicko2RatingDeviation() ))
);
}
Expand Down
6 changes: 3 additions & 3 deletions modules/round/src/main/PerfsUpdater.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ final class PerfsUpdater(
}
val results = new RatingPeriodResults()
result match {
case Glicko.Result.Draw => results.addDraw(white, black)
case Glicko.Result.Win => results.addResult(white, black)
case Glicko.Result.Loss => results.addResult(black, white)
case Glicko.Result.Draw => results.addDraw(white.setAdvantage(5), black.setAdvantage(-5))
case Glicko.Result.Win => results.addResult(white.setAdvantage(5), black.setAdvantage(-5))
case Glicko.Result.Loss => results.addResult(black.setAdvantage(-5), white.setAdvantage(5))
}
try {
Glicko.system.updateRatings(results, true)
Expand Down

0 comments on commit d2e2344

Please sign in to comment.