mirror of
https://github.com/furyfire/trueskill.git
synced 2025-04-19 12:24:28 +00:00
Deleted C# code (it was just there to note the fork)
This commit is contained in:
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using Moserware.Numerics;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill
|
||||
{
|
||||
internal static class DrawMargin
|
||||
{
|
||||
public static double GetDrawMarginFromDrawProbability(double drawProbability, double beta)
|
||||
{
|
||||
// Derived from TrueSkill technical report (MSR-TR-2006-80), page 6
|
||||
|
||||
// draw probability = 2 * CDF(margin/(sqrt(n1+n2)*beta)) -1
|
||||
|
||||
// implies
|
||||
//
|
||||
// margin = inversecdf((draw probability + 1)/2) * sqrt(n1+n2) * beta
|
||||
// n1 and n2 are the number of players on each team
|
||||
double margin = GaussianDistribution.InverseCumulativeTo(.5*(drawProbability + 1), 0, 1)*Math.Sqrt(1 + 1)*
|
||||
beta;
|
||||
return margin;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Numerics;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates TrueSkill using a full factor graph.
|
||||
/// </summary>
|
||||
internal class FactorGraphTrueSkillCalculator : SkillCalculator
|
||||
{
|
||||
public FactorGraphTrueSkillCalculator()
|
||||
: base(SupportedOptions.PartialPlay | SupportedOptions.PartialUpdate, TeamsRange.AtLeast(2), PlayersRange.AtLeast(1))
|
||||
{
|
||||
}
|
||||
|
||||
public override IDictionary<TPlayer, Rating> CalculateNewRatings<TPlayer>(GameInfo gameInfo,
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teams,
|
||||
params int[] teamRanks)
|
||||
{
|
||||
Guard.ArgumentNotNull(gameInfo, "gameInfo");
|
||||
ValidateTeamCountAndPlayersCountPerTeam(teams);
|
||||
|
||||
RankSorter.Sort(ref teams, ref teamRanks);
|
||||
|
||||
var factorGraph = new TrueSkillFactorGraph<TPlayer>(gameInfo, teams, teamRanks);
|
||||
factorGraph.BuildGraph();
|
||||
factorGraph.RunSchedule();
|
||||
|
||||
double probabilityOfOutcome = factorGraph.GetProbabilityOfRanking();
|
||||
|
||||
return factorGraph.GetUpdatedRatings();
|
||||
}
|
||||
|
||||
|
||||
public override double CalculateMatchQuality<TPlayer>(GameInfo gameInfo,
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teams)
|
||||
{
|
||||
// We need to create the A matrix which is the player team assigments.
|
||||
List<IDictionary<TPlayer, Rating>> teamAssignmentsList = teams.ToList();
|
||||
Matrix skillsMatrix = GetPlayerCovarianceMatrix(teamAssignmentsList);
|
||||
Vector meanVector = GetPlayerMeansVector(teamAssignmentsList);
|
||||
Matrix meanVectorTranspose = meanVector.Transpose;
|
||||
|
||||
Matrix playerTeamAssignmentsMatrix = CreatePlayerTeamAssignmentMatrix(teamAssignmentsList, meanVector.Rows);
|
||||
Matrix playerTeamAssignmentsMatrixTranspose = playerTeamAssignmentsMatrix.Transpose;
|
||||
|
||||
double betaSquared = Square(gameInfo.Beta);
|
||||
|
||||
Matrix start = meanVectorTranspose * playerTeamAssignmentsMatrix;
|
||||
Matrix aTa = (betaSquared * playerTeamAssignmentsMatrixTranspose) * playerTeamAssignmentsMatrix;
|
||||
Matrix aTSA = playerTeamAssignmentsMatrixTranspose * skillsMatrix * playerTeamAssignmentsMatrix;
|
||||
Matrix middle = aTa + aTSA;
|
||||
|
||||
Matrix middleInverse = middle.Inverse;
|
||||
|
||||
Matrix end = playerTeamAssignmentsMatrixTranspose * meanVector;
|
||||
|
||||
Matrix expPartMatrix = -0.5 * (start * middleInverse * end);
|
||||
double expPart = expPartMatrix.Determinant;
|
||||
|
||||
double sqrtPartNumerator = aTa.Determinant;
|
||||
double sqrtPartDenominator = middle.Determinant;
|
||||
double sqrtPart = sqrtPartNumerator / sqrtPartDenominator;
|
||||
|
||||
double result = Math.Exp(expPart) * Math.Sqrt(sqrtPart);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Vector GetPlayerMeansVector<TPlayer>(
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teamAssignmentsList)
|
||||
{
|
||||
// A simple vector of all the player means.
|
||||
return new Vector(GetPlayerRatingValues(teamAssignmentsList, rating => rating.Mean));
|
||||
}
|
||||
|
||||
private static Matrix GetPlayerCovarianceMatrix<TPlayer>(
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teamAssignmentsList)
|
||||
{
|
||||
// This is a square matrix whose diagonal values represent the variance (square of standard deviation) of all
|
||||
// players.
|
||||
return
|
||||
new DiagonalMatrix(GetPlayerRatingValues(teamAssignmentsList, rating => Square(rating.StandardDeviation)));
|
||||
}
|
||||
|
||||
// Helper function that gets a list of values for all player ratings
|
||||
private static IList<double> GetPlayerRatingValues<TPlayer>(
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teamAssignmentsList, Func<Rating, double> playerRatingFunction)
|
||||
{
|
||||
var playerRatingValues = new List<double>();
|
||||
|
||||
foreach (var currentTeam in teamAssignmentsList)
|
||||
{
|
||||
foreach (Rating currentRating in currentTeam.Values)
|
||||
{
|
||||
playerRatingValues.Add(playerRatingFunction(currentRating));
|
||||
}
|
||||
}
|
||||
|
||||
return playerRatingValues;
|
||||
}
|
||||
|
||||
private static Matrix CreatePlayerTeamAssignmentMatrix<TPlayer>(
|
||||
IList<IDictionary<TPlayer, Rating>> teamAssignmentsList, int totalPlayers)
|
||||
{
|
||||
// The team assignment matrix is often referred to as the "A" matrix. It's a matrix whose rows represent the players
|
||||
// and the columns represent teams. At Matrix[row, column] represents that player[row] is on team[col]
|
||||
// Positive values represent an assignment and a negative value means that we subtract the value of the next
|
||||
// team since we're dealing with pairs. This means that this matrix always has teams - 1 columns.
|
||||
// The only other tricky thing is that values represent the play percentage.
|
||||
|
||||
// For example, consider a 3 team game where team1 is just player1, team 2 is player 2 and player 3, and
|
||||
// team3 is just player 4. Furthermore, player 2 and player 3 on team 2 played 25% and 75% of the time
|
||||
// (e.g. partial play), the A matrix would be:
|
||||
|
||||
// A = this 4x2 matrix:
|
||||
// | 1.00 0.00 |
|
||||
// | -0.25 0.25 |
|
||||
// | -0.75 0.75 |
|
||||
// | 0.00 -1.00 |
|
||||
|
||||
var playerAssignments = new List<IEnumerable<double>>();
|
||||
int totalPreviousPlayers = 0;
|
||||
|
||||
for (int i = 0; i < teamAssignmentsList.Count - 1; i++)
|
||||
{
|
||||
IDictionary<TPlayer, Rating> currentTeam = teamAssignmentsList[i];
|
||||
|
||||
// Need to add in 0's for all the previous players, since they're not
|
||||
// on this team
|
||||
var currentRowValues = new List<double>(new double[totalPreviousPlayers]);
|
||||
playerAssignments.Add(currentRowValues);
|
||||
|
||||
foreach (var currentRating in currentTeam)
|
||||
{
|
||||
currentRowValues.Add(PartialPlay.GetPartialPlayPercentage(currentRating.Key));
|
||||
// indicates the player is on the team
|
||||
totalPreviousPlayers++;
|
||||
}
|
||||
|
||||
IDictionary<TPlayer, Rating> nextTeam = teamAssignmentsList[i + 1];
|
||||
foreach (var nextTeamPlayerPair in nextTeam)
|
||||
{
|
||||
// Add a -1 * playing time to represent the difference
|
||||
currentRowValues.Add(-1 * PartialPlay.GetPartialPlayPercentage(nextTeamPlayerPair.Key));
|
||||
}
|
||||
}
|
||||
|
||||
var playerTeamAssignmentsMatrix = new Matrix(totalPlayers, teamAssignmentsList.Count - 1, playerAssignments);
|
||||
|
||||
return playerTeamAssignmentsMatrix;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Factors
|
||||
{
|
||||
public abstract class GaussianFactor : Factor<GaussianDistribution>
|
||||
{
|
||||
protected GaussianFactor(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// Sends the factor-graph message with and returns the log-normalization constant
|
||||
protected override double SendMessage(Message<GaussianDistribution> message,
|
||||
Variable<GaussianDistribution> variable)
|
||||
{
|
||||
GaussianDistribution marginal = variable.Value;
|
||||
GaussianDistribution messageValue = message.Value;
|
||||
double logZ = GaussianDistribution.LogProductNormalization(marginal, messageValue);
|
||||
variable.Value = marginal*messageValue;
|
||||
return logZ;
|
||||
}
|
||||
|
||||
public override Message<GaussianDistribution> CreateVariableToMessageBinding(
|
||||
Variable<GaussianDistribution> variable)
|
||||
{
|
||||
return CreateVariableToMessageBinding(variable,
|
||||
new Message<GaussianDistribution>(
|
||||
GaussianDistribution.FromPrecisionMean(0, 0),
|
||||
"message from {0} to {1}", this, variable));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Factors
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor representing a team difference that has exceeded the draw margin.
|
||||
/// </summary>
|
||||
/// <remarks>See the accompanying math paper for more details.</remarks>
|
||||
public class GaussianGreaterThanFactor : GaussianFactor
|
||||
{
|
||||
private readonly double _Epsilon;
|
||||
|
||||
public GaussianGreaterThanFactor(double epsilon, Variable<GaussianDistribution> variable)
|
||||
: base(String.Format("{0} > {1:0.000}", variable, epsilon))
|
||||
{
|
||||
_Epsilon = epsilon;
|
||||
CreateVariableToMessageBinding(variable);
|
||||
}
|
||||
|
||||
public override double LogNormalization
|
||||
{
|
||||
get
|
||||
{
|
||||
GaussianDistribution marginal = Variables[0].Value;
|
||||
GaussianDistribution message = Messages[0].Value;
|
||||
GaussianDistribution messageFromVariable = marginal/message;
|
||||
return -GaussianDistribution.LogProductNormalization(messageFromVariable, message)
|
||||
+
|
||||
Math.Log(
|
||||
GaussianDistribution.CumulativeTo((messageFromVariable.Mean - _Epsilon)/
|
||||
messageFromVariable.StandardDeviation));
|
||||
}
|
||||
}
|
||||
|
||||
protected override double UpdateMessage(Message<GaussianDistribution> message,
|
||||
Variable<GaussianDistribution> variable)
|
||||
{
|
||||
GaussianDistribution oldMarginal = variable.Value.Clone();
|
||||
GaussianDistribution oldMessage = message.Value.Clone();
|
||||
GaussianDistribution messageFromVar = oldMarginal/oldMessage;
|
||||
|
||||
double c = messageFromVar.Precision;
|
||||
double d = messageFromVar.PrecisionMean;
|
||||
|
||||
double sqrtC = Math.Sqrt(c);
|
||||
|
||||
double dOnSqrtC = d/sqrtC;
|
||||
|
||||
double epsilsonTimesSqrtC = _Epsilon*sqrtC;
|
||||
d = messageFromVar.PrecisionMean;
|
||||
|
||||
double denom = 1.0 - TruncatedGaussianCorrectionFunctions.WExceedsMargin(dOnSqrtC, epsilsonTimesSqrtC);
|
||||
|
||||
double newPrecision = c/denom;
|
||||
double newPrecisionMean = (d +
|
||||
sqrtC*
|
||||
TruncatedGaussianCorrectionFunctions.VExceedsMargin(dOnSqrtC, epsilsonTimesSqrtC))/
|
||||
denom;
|
||||
|
||||
GaussianDistribution newMarginal = GaussianDistribution.FromPrecisionMean(newPrecisionMean, newPrecision);
|
||||
|
||||
GaussianDistribution newMessage = oldMessage*newMarginal/oldMarginal;
|
||||
|
||||
/// Update the message and marginal
|
||||
message.Value = newMessage;
|
||||
|
||||
variable.Value = newMarginal;
|
||||
|
||||
/// Return the difference in the new marginal
|
||||
return newMarginal - oldMarginal;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Factors
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects two variables and adds uncertainty.
|
||||
/// </summary>
|
||||
/// <remarks>See the accompanying math paper for more details.</remarks>
|
||||
public class GaussianLikelihoodFactor : GaussianFactor
|
||||
{
|
||||
private readonly double _Precision;
|
||||
|
||||
public GaussianLikelihoodFactor(double betaSquared, Variable<GaussianDistribution> variable1,
|
||||
Variable<GaussianDistribution> variable2)
|
||||
: base(String.Format("Likelihood of {0} going to {1}", variable2, variable1))
|
||||
{
|
||||
_Precision = 1.0/betaSquared;
|
||||
CreateVariableToMessageBinding(variable1);
|
||||
CreateVariableToMessageBinding(variable2);
|
||||
}
|
||||
|
||||
public override double LogNormalization
|
||||
{
|
||||
get { return GaussianDistribution.LogRatioNormalization(Variables[0].Value, Messages[0].Value); }
|
||||
}
|
||||
|
||||
private double UpdateHelper(Message<GaussianDistribution> message1, Message<GaussianDistribution> message2,
|
||||
Variable<GaussianDistribution> variable1, Variable<GaussianDistribution> variable2)
|
||||
{
|
||||
GaussianDistribution message1Value = message1.Value.Clone();
|
||||
GaussianDistribution message2Value = message2.Value.Clone();
|
||||
|
||||
GaussianDistribution marginal1 = variable1.Value.Clone();
|
||||
GaussianDistribution marginal2 = variable2.Value.Clone();
|
||||
|
||||
double a = _Precision/(_Precision + marginal2.Precision - message2Value.Precision);
|
||||
|
||||
GaussianDistribution newMessage = GaussianDistribution.FromPrecisionMean(
|
||||
a*(marginal2.PrecisionMean - message2Value.PrecisionMean),
|
||||
a*(marginal2.Precision - message2Value.Precision));
|
||||
|
||||
GaussianDistribution oldMarginalWithoutMessage = marginal1/message1Value;
|
||||
|
||||
GaussianDistribution newMarginal = oldMarginalWithoutMessage*newMessage;
|
||||
|
||||
/// Update the message and marginal
|
||||
|
||||
message1.Value = newMessage;
|
||||
variable1.Value = newMarginal;
|
||||
|
||||
/// Return the difference in the new marginal
|
||||
return newMarginal - marginal1;
|
||||
}
|
||||
|
||||
public override double UpdateMessage(int messageIndex)
|
||||
{
|
||||
switch (messageIndex)
|
||||
{
|
||||
case 0:
|
||||
return UpdateHelper(Messages[0], Messages[1],
|
||||
Variables[0], Variables[1]);
|
||||
case 1:
|
||||
return UpdateHelper(Messages[1], Messages[0],
|
||||
Variables[1], Variables[0]);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Factors
|
||||
{
|
||||
/// <summary>
|
||||
/// Supplies the factor graph with prior information.
|
||||
/// </summary>
|
||||
/// <remarks>See the accompanying math paper for more details.</remarks>
|
||||
public class GaussianPriorFactor : GaussianFactor
|
||||
{
|
||||
private readonly GaussianDistribution _NewMessage;
|
||||
|
||||
public GaussianPriorFactor(double mean, double variance, Variable<GaussianDistribution> variable)
|
||||
: base(String.Format("Prior value going to {0}", variable))
|
||||
{
|
||||
_NewMessage = new GaussianDistribution(mean, Math.Sqrt(variance));
|
||||
CreateVariableToMessageBinding(variable,
|
||||
new Message<GaussianDistribution>(
|
||||
GaussianDistribution.FromPrecisionMean(0, 0), "message from {0} to {1}",
|
||||
this, variable));
|
||||
}
|
||||
|
||||
protected override double UpdateMessage(Message<GaussianDistribution> message,
|
||||
Variable<GaussianDistribution> variable)
|
||||
{
|
||||
GaussianDistribution oldMarginal = variable.Value.Clone();
|
||||
Message<GaussianDistribution> oldMessage = message;
|
||||
GaussianDistribution newMarginal =
|
||||
GaussianDistribution.FromPrecisionMean(
|
||||
oldMarginal.PrecisionMean + _NewMessage.PrecisionMean - oldMessage.Value.PrecisionMean,
|
||||
oldMarginal.Precision + _NewMessage.Precision - oldMessage.Value.Precision);
|
||||
variable.Value = newMarginal;
|
||||
message.Value = _NewMessage;
|
||||
return oldMarginal - newMarginal;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,251 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Factors
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor that sums together multiple Gaussians.
|
||||
/// </summary>
|
||||
/// <remarks>See the accompanying math paper for more details.</remarks>
|
||||
public class GaussianWeightedSumFactor : GaussianFactor
|
||||
{
|
||||
private readonly List<int[]> _VariableIndexOrdersForWeights = new List<int[]>();
|
||||
|
||||
// This following is used for convenience, for example, the first entry is [0, 1, 2]
|
||||
// corresponding to v[0] = a1*v[1] + a2*v[2]
|
||||
private readonly double[][] _Weights;
|
||||
private readonly double[][] _WeightsSquared;
|
||||
|
||||
public GaussianWeightedSumFactor(Variable<GaussianDistribution> sumVariable,
|
||||
Variable<GaussianDistribution>[] variablesToSum)
|
||||
: this(sumVariable,
|
||||
variablesToSum,
|
||||
variablesToSum.Select(v => 1.0).ToArray()) // By default, set the weight to 1.0
|
||||
{
|
||||
}
|
||||
|
||||
public GaussianWeightedSumFactor(Variable<GaussianDistribution> sumVariable,
|
||||
Variable<GaussianDistribution>[] variablesToSum, double[] variableWeights)
|
||||
: base(CreateName(sumVariable, variablesToSum, variableWeights))
|
||||
{
|
||||
_Weights = new double[variableWeights.Length + 1][];
|
||||
_WeightsSquared = new double[_Weights.Length][];
|
||||
|
||||
// The first weights are a straightforward copy
|
||||
// v_0 = a_1*v_1 + a_2*v_2 + ... + a_n * v_n
|
||||
_Weights[0] = new double[variableWeights.Length];
|
||||
Array.Copy(variableWeights, _Weights[0], variableWeights.Length);
|
||||
_WeightsSquared[0] = _Weights[0].Select(w => w*w).ToArray();
|
||||
|
||||
// 0..n-1
|
||||
_VariableIndexOrdersForWeights.Add(Enumerable.Range(0, 1 + variablesToSum.Length).ToArray());
|
||||
|
||||
|
||||
// The rest move the variables around and divide out the constant.
|
||||
// For example:
|
||||
// v_1 = (-a_2 / a_1) * v_2 + (-a3/a1) * v_3 + ... + (1.0 / a_1) * v_0
|
||||
// By convention, we'll put the v_0 term at the end
|
||||
|
||||
for (int weightsIndex = 1; weightsIndex < _Weights.Length; weightsIndex++)
|
||||
{
|
||||
var currentWeights = new double[variableWeights.Length];
|
||||
_Weights[weightsIndex] = currentWeights;
|
||||
|
||||
var variableIndices = new int[variableWeights.Length + 1];
|
||||
variableIndices[0] = weightsIndex;
|
||||
|
||||
var currentWeightsSquared = new double[variableWeights.Length];
|
||||
_WeightsSquared[weightsIndex] = currentWeightsSquared;
|
||||
|
||||
// keep a single variable to keep track of where we are in the array.
|
||||
// This is helpful since we skip over one of the spots
|
||||
int currentDestinationWeightIndex = 0;
|
||||
|
||||
for (int currentWeightSourceIndex = 0;
|
||||
currentWeightSourceIndex < variableWeights.Length;
|
||||
currentWeightSourceIndex++)
|
||||
{
|
||||
if (currentWeightSourceIndex == (weightsIndex - 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double currentWeight = (-variableWeights[currentWeightSourceIndex]/variableWeights[weightsIndex - 1]);
|
||||
|
||||
if (variableWeights[weightsIndex - 1] == 0)
|
||||
{
|
||||
// HACK: Getting around division by zero
|
||||
currentWeight = 0;
|
||||
}
|
||||
|
||||
currentWeights[currentDestinationWeightIndex] = currentWeight;
|
||||
currentWeightsSquared[currentDestinationWeightIndex] = currentWeight*currentWeight;
|
||||
|
||||
variableIndices[currentDestinationWeightIndex + 1] = currentWeightSourceIndex + 1;
|
||||
currentDestinationWeightIndex++;
|
||||
}
|
||||
|
||||
// And the final one
|
||||
double finalWeight = 1.0/variableWeights[weightsIndex - 1];
|
||||
|
||||
if (variableWeights[weightsIndex - 1] == 0)
|
||||
{
|
||||
// HACK: Getting around division by zero
|
||||
finalWeight = 0;
|
||||
}
|
||||
currentWeights[currentDestinationWeightIndex] = finalWeight;
|
||||
currentWeightsSquared[currentDestinationWeightIndex] = finalWeight*finalWeight;
|
||||
variableIndices[variableIndices.Length - 1] = 0;
|
||||
_VariableIndexOrdersForWeights.Add(variableIndices);
|
||||
}
|
||||
|
||||
CreateVariableToMessageBinding(sumVariable);
|
||||
|
||||
foreach (var currentVariable in variablesToSum)
|
||||
{
|
||||
CreateVariableToMessageBinding(currentVariable);
|
||||
}
|
||||
}
|
||||
|
||||
public override double LogNormalization
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadOnlyCollection<Variable<GaussianDistribution>> vars = Variables;
|
||||
ReadOnlyCollection<Message<GaussianDistribution>> messages = Messages;
|
||||
|
||||
double result = 0.0;
|
||||
|
||||
// We start at 1 since offset 0 has the sum
|
||||
for (int i = 1; i < vars.Count; i++)
|
||||
{
|
||||
result += GaussianDistribution.LogRatioNormalization(vars[i].Value, messages[i].Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private double UpdateHelper(double[] weights, double[] weightsSquared,
|
||||
IList<Message<GaussianDistribution>> messages,
|
||||
IList<Variable<GaussianDistribution>> variables)
|
||||
{
|
||||
// Potentially look at http://mathworld.wolfram.com/NormalSumDistribution.html for clues as
|
||||
// to what it's doing
|
||||
|
||||
GaussianDistribution message0 = messages[0].Value.Clone();
|
||||
GaussianDistribution marginal0 = variables[0].Value.Clone();
|
||||
|
||||
// The math works out so that 1/newPrecision = sum of a_i^2 /marginalsWithoutMessages[i]
|
||||
double inverseOfNewPrecisionSum = 0.0;
|
||||
double anotherInverseOfNewPrecisionSum = 0.0;
|
||||
double weightedMeanSum = 0.0;
|
||||
double anotherWeightedMeanSum = 0.0;
|
||||
|
||||
for (int i = 0; i < weightsSquared.Length; i++)
|
||||
{
|
||||
// These flow directly from the paper
|
||||
|
||||
inverseOfNewPrecisionSum += weightsSquared[i]/
|
||||
(variables[i + 1].Value.Precision - messages[i + 1].Value.Precision);
|
||||
|
||||
GaussianDistribution diff = (variables[i + 1].Value/messages[i + 1].Value);
|
||||
anotherInverseOfNewPrecisionSum += weightsSquared[i]/diff.Precision;
|
||||
|
||||
weightedMeanSum += weights[i]
|
||||
*
|
||||
(variables[i + 1].Value.PrecisionMean - messages[i + 1].Value.PrecisionMean)
|
||||
/
|
||||
(variables[i + 1].Value.Precision - messages[i + 1].Value.Precision);
|
||||
|
||||
anotherWeightedMeanSum += weights[i]*diff.PrecisionMean/diff.Precision;
|
||||
}
|
||||
|
||||
double newPrecision = 1.0/inverseOfNewPrecisionSum;
|
||||
double anotherNewPrecision = 1.0/anotherInverseOfNewPrecisionSum;
|
||||
|
||||
double newPrecisionMean = newPrecision*weightedMeanSum;
|
||||
double anotherNewPrecisionMean = anotherNewPrecision*anotherWeightedMeanSum;
|
||||
|
||||
GaussianDistribution newMessage = GaussianDistribution.FromPrecisionMean(newPrecisionMean, newPrecision);
|
||||
GaussianDistribution oldMarginalWithoutMessage = marginal0/message0;
|
||||
|
||||
GaussianDistribution newMarginal = oldMarginalWithoutMessage*newMessage;
|
||||
|
||||
/// Update the message and marginal
|
||||
|
||||
messages[0].Value = newMessage;
|
||||
variables[0].Value = newMarginal;
|
||||
|
||||
/// Return the difference in the new marginal
|
||||
return newMarginal - marginal0;
|
||||
}
|
||||
|
||||
public override double UpdateMessage(int messageIndex)
|
||||
{
|
||||
ReadOnlyCollection<Message<GaussianDistribution>> allMessages = Messages;
|
||||
ReadOnlyCollection<Variable<GaussianDistribution>> allVariables = Variables;
|
||||
|
||||
Guard.ArgumentIsValidIndex(messageIndex, allMessages.Count, "messageIndex");
|
||||
|
||||
var updatedMessages = new List<Message<GaussianDistribution>>();
|
||||
var updatedVariables = new List<Variable<GaussianDistribution>>();
|
||||
|
||||
int[] indicesToUse = _VariableIndexOrdersForWeights[messageIndex];
|
||||
|
||||
// The tricky part here is that we have to put the messages and variables in the same
|
||||
// order as the weights. Thankfully, the weights and messages share the same index numbers,
|
||||
// so we just need to make sure they're consistent
|
||||
for (int i = 0; i < allMessages.Count; i++)
|
||||
{
|
||||
updatedMessages.Add(allMessages[indicesToUse[i]]);
|
||||
updatedVariables.Add(allVariables[indicesToUse[i]]);
|
||||
}
|
||||
|
||||
return UpdateHelper(_Weights[messageIndex], _WeightsSquared[messageIndex], updatedMessages, updatedVariables);
|
||||
}
|
||||
|
||||
private static string CreateName(Variable<GaussianDistribution> sumVariable,
|
||||
IList<Variable<GaussianDistribution>> variablesToSum, double[] weights)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(sumVariable.ToString());
|
||||
sb.Append(" = ");
|
||||
for (int i = 0; i < variablesToSum.Count; i++)
|
||||
{
|
||||
bool isFirst = (i == 0);
|
||||
|
||||
if (isFirst && (weights[i] < 0))
|
||||
{
|
||||
sb.Append("-");
|
||||
}
|
||||
|
||||
sb.Append(Math.Abs(weights[i]).ToString("0.00"));
|
||||
sb.Append("*[");
|
||||
sb.Append(variablesToSum[i]);
|
||||
sb.Append("]");
|
||||
|
||||
bool isLast = (i == variablesToSum.Count - 1);
|
||||
|
||||
if (!isLast)
|
||||
{
|
||||
if (weights[i + 1] >= 0)
|
||||
{
|
||||
sb.Append(" + ");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(" - ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Factors
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor representing a team difference that has not exceeded the draw margin.
|
||||
/// </summary>
|
||||
/// <remarks>See the accompanying math paper for more details.</remarks>
|
||||
public class GaussianWithinFactor : GaussianFactor
|
||||
{
|
||||
private readonly double _Epsilon;
|
||||
|
||||
public GaussianWithinFactor(double epsilon, Variable<GaussianDistribution> variable)
|
||||
: base(String.Format("{0} <= {1:0.000}", variable, epsilon))
|
||||
{
|
||||
_Epsilon = epsilon;
|
||||
CreateVariableToMessageBinding(variable);
|
||||
}
|
||||
|
||||
public override double LogNormalization
|
||||
{
|
||||
get
|
||||
{
|
||||
GaussianDistribution marginal = Variables[0].Value;
|
||||
GaussianDistribution message = Messages[0].Value;
|
||||
GaussianDistribution messageFromVariable = marginal/message;
|
||||
double mean = messageFromVariable.Mean;
|
||||
double std = messageFromVariable.StandardDeviation;
|
||||
double z = GaussianDistribution.CumulativeTo((_Epsilon - mean)/std)
|
||||
-
|
||||
GaussianDistribution.CumulativeTo((-_Epsilon - mean)/std);
|
||||
|
||||
return -GaussianDistribution.LogProductNormalization(messageFromVariable, message) + Math.Log(z);
|
||||
}
|
||||
}
|
||||
|
||||
protected override double UpdateMessage(Message<GaussianDistribution> message,
|
||||
Variable<GaussianDistribution> variable)
|
||||
{
|
||||
GaussianDistribution oldMarginal = variable.Value.Clone();
|
||||
GaussianDistribution oldMessage = message.Value.Clone();
|
||||
GaussianDistribution messageFromVariable = oldMarginal/oldMessage;
|
||||
|
||||
double c = messageFromVariable.Precision;
|
||||
double d = messageFromVariable.PrecisionMean;
|
||||
|
||||
double sqrtC = Math.Sqrt(c);
|
||||
double dOnSqrtC = d/sqrtC;
|
||||
|
||||
double epsilonTimesSqrtC = _Epsilon*sqrtC;
|
||||
d = messageFromVariable.PrecisionMean;
|
||||
|
||||
double denominator = 1.0 - TruncatedGaussianCorrectionFunctions.WWithinMargin(dOnSqrtC, epsilonTimesSqrtC);
|
||||
double newPrecision = c/denominator;
|
||||
double newPrecisionMean = (d +
|
||||
sqrtC*
|
||||
TruncatedGaussianCorrectionFunctions.VWithinMargin(dOnSqrtC, epsilonTimesSqrtC))/
|
||||
denominator;
|
||||
|
||||
GaussianDistribution newMarginal = GaussianDistribution.FromPrecisionMean(newPrecisionMean, newPrecision);
|
||||
GaussianDistribution newMessage = oldMessage*newMarginal/oldMarginal;
|
||||
|
||||
/// Update the message and marginal
|
||||
message.Value = newMessage;
|
||||
variable.Value = newMarginal;
|
||||
|
||||
/// Return the difference in the new marginal
|
||||
return newMarginal - oldMarginal;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
using Moserware.Skills.TrueSkill.Factors;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Layers
|
||||
{
|
||||
// The whole purpose of this is to do a loop on the bottom
|
||||
internal class IteratedTeamDifferencesInnerLayer<TPlayer> :
|
||||
TrueSkillFactorGraphLayer
|
||||
<TPlayer, Variable<GaussianDistribution>, GaussianWeightedSumFactor, Variable<GaussianDistribution>>
|
||||
{
|
||||
private readonly TeamDifferencesComparisonLayer<TPlayer> _TeamDifferencesComparisonLayer;
|
||||
|
||||
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer<TPlayer>
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer;
|
||||
|
||||
public IteratedTeamDifferencesInnerLayer(TrueSkillFactorGraph<TPlayer> parentGraph,
|
||||
TeamPerformancesToTeamPerformanceDifferencesLayer<TPlayer>
|
||||
teamPerformancesToPerformanceDifferences,
|
||||
TeamDifferencesComparisonLayer<TPlayer> teamDifferencesComparisonLayer)
|
||||
: base(parentGraph)
|
||||
{
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer = teamPerformancesToPerformanceDifferences;
|
||||
_TeamDifferencesComparisonLayer = teamDifferencesComparisonLayer;
|
||||
}
|
||||
|
||||
public override IEnumerable<Factor<GaussianDistribution>> UntypedFactors
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.UntypedFactors.Concat(
|
||||
_TeamDifferencesComparisonLayer.UntypedFactors);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BuildLayer()
|
||||
{
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.SetRawInputVariablesGroups(InputVariablesGroups);
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.BuildLayer();
|
||||
|
||||
_TeamDifferencesComparisonLayer.SetRawInputVariablesGroups(
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.GetRawOutputVariablesGroups());
|
||||
_TeamDifferencesComparisonLayer.BuildLayer();
|
||||
}
|
||||
|
||||
public override Schedule<GaussianDistribution> CreatePriorSchedule()
|
||||
{
|
||||
Schedule<GaussianDistribution> loop = null;
|
||||
|
||||
switch (InputVariablesGroups.Count)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
throw new InvalidOperationException();
|
||||
case 2:
|
||||
loop = CreateTwoTeamInnerPriorLoopSchedule();
|
||||
break;
|
||||
default:
|
||||
loop = CreateMultipleTeamInnerPriorLoopSchedule();
|
||||
break;
|
||||
}
|
||||
|
||||
// When dealing with differences, there are always (n-1) differences, so add in the 1
|
||||
int totalTeamDifferences = _TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors.Count;
|
||||
int totalTeams = totalTeamDifferences + 1;
|
||||
|
||||
var innerSchedule = new ScheduleSequence<GaussianDistribution>(
|
||||
"inner schedule",
|
||||
new[]
|
||||
{
|
||||
loop,
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
"teamPerformanceToPerformanceDifferenceFactors[0] @ 1",
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors[0], 1),
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
String.Format("teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = {0} - 1] @ 2",
|
||||
totalTeamDifferences),
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors[totalTeamDifferences - 1], 2)
|
||||
}
|
||||
);
|
||||
|
||||
return innerSchedule;
|
||||
}
|
||||
|
||||
private Schedule<GaussianDistribution> CreateTwoTeamInnerPriorLoopSchedule()
|
||||
{
|
||||
return ScheduleSequence(
|
||||
new[]
|
||||
{
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
"send team perf to perf differences",
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors[0],
|
||||
0),
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
"send to greater than or within factor",
|
||||
_TeamDifferencesComparisonLayer.LocalFactors[0],
|
||||
0)
|
||||
},
|
||||
"loop of just two teams inner sequence");
|
||||
}
|
||||
|
||||
private Schedule<GaussianDistribution> CreateMultipleTeamInnerPriorLoopSchedule()
|
||||
{
|
||||
int totalTeamDifferences = _TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors.Count;
|
||||
|
||||
var forwardScheduleList = new List<Schedule<GaussianDistribution>>();
|
||||
|
||||
for (int i = 0; i < totalTeamDifferences - 1; i++)
|
||||
{
|
||||
Schedule<GaussianDistribution> currentForwardSchedulePiece =
|
||||
ScheduleSequence(
|
||||
new Schedule<GaussianDistribution>[]
|
||||
{
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
String.Format("team perf to perf diff {0}",
|
||||
i),
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors[i], 0),
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
String.Format("greater than or within result factor {0}",
|
||||
i),
|
||||
_TeamDifferencesComparisonLayer.LocalFactors[i],
|
||||
0),
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
String.Format("team perf to perf diff factors [{0}], 2",
|
||||
i),
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors[i], 2)
|
||||
}, "current forward schedule piece {0}", i);
|
||||
|
||||
forwardScheduleList.Add(currentForwardSchedulePiece);
|
||||
}
|
||||
|
||||
var forwardSchedule =
|
||||
new ScheduleSequence<GaussianDistribution>(
|
||||
"forward schedule",
|
||||
forwardScheduleList);
|
||||
|
||||
var backwardScheduleList = new List<Schedule<GaussianDistribution>>();
|
||||
|
||||
for (int i = 0; i < totalTeamDifferences - 1; i++)
|
||||
{
|
||||
var currentBackwardSchedulePiece = new ScheduleSequence<GaussianDistribution>(
|
||||
"current backward schedule piece",
|
||||
new Schedule<GaussianDistribution>[]
|
||||
{
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
String.Format("teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - {0}] @ 0",
|
||||
i),
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors[
|
||||
totalTeamDifferences - 1 - i], 0),
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
String.Format("greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - {0}] @ 0",
|
||||
i),
|
||||
_TeamDifferencesComparisonLayer.LocalFactors[totalTeamDifferences - 1 - i], 0),
|
||||
new ScheduleStep<GaussianDistribution>(
|
||||
String.Format("teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - {0}] @ 1",
|
||||
i),
|
||||
_TeamPerformancesToTeamPerformanceDifferencesLayer.LocalFactors[
|
||||
totalTeamDifferences - 1 - i], 1)
|
||||
}
|
||||
);
|
||||
backwardScheduleList.Add(currentBackwardSchedulePiece);
|
||||
}
|
||||
|
||||
var backwardSchedule =
|
||||
new ScheduleSequence<GaussianDistribution>(
|
||||
"backward schedule",
|
||||
backwardScheduleList);
|
||||
|
||||
var forwardBackwardScheduleToLoop =
|
||||
new ScheduleSequence<GaussianDistribution>(
|
||||
"forward Backward Schedule To Loop",
|
||||
new Schedule<GaussianDistribution>[]
|
||||
{
|
||||
forwardSchedule, backwardSchedule
|
||||
});
|
||||
|
||||
const double initialMaxDelta = 0.0001;
|
||||
|
||||
var loop = new ScheduleLoop<GaussianDistribution>(
|
||||
String.Format("loop with max delta of {0}",
|
||||
initialMaxDelta),
|
||||
forwardBackwardScheduleToLoop,
|
||||
initialMaxDelta);
|
||||
|
||||
return loop;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
using Moserware.Skills.TrueSkill.Factors;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Layers
|
||||
{
|
||||
internal class PlayerPerformancesToTeamPerformancesLayer<TPlayer> :
|
||||
TrueSkillFactorGraphLayer
|
||||
<TPlayer, KeyedVariable<TPlayer, GaussianDistribution>, GaussianWeightedSumFactor,
|
||||
Variable<GaussianDistribution>>
|
||||
{
|
||||
public PlayerPerformancesToTeamPerformancesLayer(TrueSkillFactorGraph<TPlayer> parentGraph)
|
||||
: base(parentGraph)
|
||||
{
|
||||
}
|
||||
|
||||
public override void BuildLayer()
|
||||
{
|
||||
foreach (var currentTeam in InputVariablesGroups)
|
||||
{
|
||||
Variable<GaussianDistribution> teamPerformance = CreateOutputVariable(currentTeam);
|
||||
AddLayerFactor(CreatePlayerToTeamSumFactor(currentTeam, teamPerformance));
|
||||
|
||||
// REVIEW: Does it make sense to have groups of one?
|
||||
OutputVariablesGroups.Add(new[] {teamPerformance});
|
||||
}
|
||||
}
|
||||
|
||||
public override Schedule<GaussianDistribution> CreatePriorSchedule()
|
||||
{
|
||||
return ScheduleSequence(
|
||||
from weightedSumFactor in LocalFactors
|
||||
select new ScheduleStep<GaussianDistribution>("Perf to Team Perf Step", weightedSumFactor, 0),
|
||||
"all player perf to team perf schedule");
|
||||
}
|
||||
|
||||
protected GaussianWeightedSumFactor CreatePlayerToTeamSumFactor(
|
||||
IList<KeyedVariable<TPlayer, GaussianDistribution>> teamMembers, Variable<GaussianDistribution> sumVariable)
|
||||
{
|
||||
return new GaussianWeightedSumFactor(sumVariable, teamMembers.ToArray(),
|
||||
teamMembers.Select(v => PartialPlay.GetPartialPlayPercentage(v.Key)).
|
||||
ToArray());
|
||||
}
|
||||
|
||||
public override Schedule<GaussianDistribution> CreatePosteriorSchedule()
|
||||
{
|
||||
return ScheduleSequence(from currentFactor in LocalFactors
|
||||
from currentIteration in
|
||||
Enumerable.Range(1, currentFactor.NumberOfMessages - 1)
|
||||
select new ScheduleStep<GaussianDistribution>(
|
||||
"team sum perf @" + currentIteration,
|
||||
currentFactor,
|
||||
currentIteration),
|
||||
"all of the team's sum iterations");
|
||||
}
|
||||
|
||||
private Variable<GaussianDistribution> CreateOutputVariable(
|
||||
IList<KeyedVariable<TPlayer, GaussianDistribution>> team)
|
||||
{
|
||||
string teamMemberNames = String.Join(", ", team.Select(teamMember => teamMember.Key.ToString()).ToArray());
|
||||
|
||||
return ParentFactorGraph.VariableFactory.CreateBasicVariable("Team[{0}]'s performance", teamMemberNames);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
using Moserware.Skills.TrueSkill.Factors;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Layers
|
||||
{
|
||||
// We intentionally have no Posterior schedule since the only purpose here is to
|
||||
internal class PlayerPriorValuesToSkillsLayer<TPlayer> :
|
||||
TrueSkillFactorGraphLayer
|
||||
<TPlayer, DefaultVariable<GaussianDistribution>, GaussianPriorFactor,
|
||||
KeyedVariable<TPlayer, GaussianDistribution>>
|
||||
{
|
||||
private readonly IEnumerable<IDictionary<TPlayer, Rating>> _Teams;
|
||||
|
||||
public PlayerPriorValuesToSkillsLayer(TrueSkillFactorGraph<TPlayer> parentGraph,
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teams)
|
||||
: base(parentGraph)
|
||||
{
|
||||
_Teams = teams;
|
||||
}
|
||||
|
||||
public override void BuildLayer()
|
||||
{
|
||||
foreach (var currentTeam in _Teams)
|
||||
{
|
||||
var currentTeamSkills = new List<KeyedVariable<TPlayer, GaussianDistribution>>();
|
||||
|
||||
foreach (var currentTeamPlayer in currentTeam)
|
||||
{
|
||||
KeyedVariable<TPlayer, GaussianDistribution> playerSkill =
|
||||
CreateSkillOutputVariable(currentTeamPlayer.Key);
|
||||
AddLayerFactor(CreatePriorFactor(currentTeamPlayer.Key, currentTeamPlayer.Value, playerSkill));
|
||||
currentTeamSkills.Add(playerSkill);
|
||||
}
|
||||
|
||||
OutputVariablesGroups.Add(currentTeamSkills);
|
||||
}
|
||||
}
|
||||
|
||||
public override Schedule<GaussianDistribution> CreatePriorSchedule()
|
||||
{
|
||||
return ScheduleSequence(
|
||||
from prior in LocalFactors
|
||||
select new ScheduleStep<GaussianDistribution>("Prior to Skill Step", prior, 0),
|
||||
"All priors");
|
||||
}
|
||||
|
||||
private GaussianPriorFactor CreatePriorFactor(TPlayer player, Rating priorRating,
|
||||
Variable<GaussianDistribution> skillsVariable)
|
||||
{
|
||||
return new GaussianPriorFactor(priorRating.Mean,
|
||||
Square(priorRating.StandardDeviation) +
|
||||
Square(ParentFactorGraph.GameInfo.DynamicsFactor), skillsVariable);
|
||||
}
|
||||
|
||||
private KeyedVariable<TPlayer, GaussianDistribution> CreateSkillOutputVariable(TPlayer key)
|
||||
{
|
||||
return ParentFactorGraph.VariableFactory.CreateKeyedVariable(key, "{0}'s skill", key);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
using Moserware.Skills.TrueSkill.Factors;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Layers
|
||||
{
|
||||
internal class PlayerSkillsToPerformancesLayer<TPlayer> :
|
||||
TrueSkillFactorGraphLayer
|
||||
<TPlayer, KeyedVariable<TPlayer, GaussianDistribution>, GaussianLikelihoodFactor,
|
||||
KeyedVariable<TPlayer, GaussianDistribution>>
|
||||
{
|
||||
public PlayerSkillsToPerformancesLayer(TrueSkillFactorGraph<TPlayer> parentGraph)
|
||||
: base(parentGraph)
|
||||
{
|
||||
}
|
||||
|
||||
public override void BuildLayer()
|
||||
{
|
||||
foreach (var currentTeam in InputVariablesGroups)
|
||||
{
|
||||
var currentTeamPlayerPerformances = new List<KeyedVariable<TPlayer, GaussianDistribution>>();
|
||||
|
||||
foreach (var playerSkillVariable in currentTeam)
|
||||
{
|
||||
KeyedVariable<TPlayer, GaussianDistribution> playerPerformance =
|
||||
CreateOutputVariable(playerSkillVariable.Key);
|
||||
AddLayerFactor(CreateLikelihood(playerSkillVariable, playerPerformance));
|
||||
currentTeamPlayerPerformances.Add(playerPerformance);
|
||||
}
|
||||
|
||||
OutputVariablesGroups.Add(currentTeamPlayerPerformances);
|
||||
}
|
||||
}
|
||||
|
||||
private GaussianLikelihoodFactor CreateLikelihood(KeyedVariable<TPlayer, GaussianDistribution> playerSkill,
|
||||
KeyedVariable<TPlayer, GaussianDistribution> playerPerformance)
|
||||
{
|
||||
return new GaussianLikelihoodFactor(Square(ParentFactorGraph.GameInfo.Beta), playerPerformance, playerSkill);
|
||||
}
|
||||
|
||||
private KeyedVariable<TPlayer, GaussianDistribution> CreateOutputVariable(TPlayer key)
|
||||
{
|
||||
return ParentFactorGraph.VariableFactory.CreateKeyedVariable(key, "{0}'s performance", key);
|
||||
}
|
||||
|
||||
public override Schedule<GaussianDistribution> CreatePriorSchedule()
|
||||
{
|
||||
return ScheduleSequence(
|
||||
from likelihood in LocalFactors
|
||||
select new ScheduleStep<GaussianDistribution>("Skill to Perf step", likelihood, 0),
|
||||
"All skill to performance sending");
|
||||
}
|
||||
|
||||
public override Schedule<GaussianDistribution> CreatePosteriorSchedule()
|
||||
{
|
||||
return ScheduleSequence(
|
||||
from likelihood in LocalFactors
|
||||
select new ScheduleStep<GaussianDistribution>("name", likelihood, 1),
|
||||
"All skill to performance sending");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
using Moserware.Skills.TrueSkill.Factors;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Layers
|
||||
{
|
||||
internal class TeamDifferencesComparisonLayer<TPlayer> :
|
||||
TrueSkillFactorGraphLayer
|
||||
<TPlayer, Variable<GaussianDistribution>, GaussianFactor, DefaultVariable<GaussianDistribution>>
|
||||
{
|
||||
private readonly double _Epsilon;
|
||||
private readonly int[] _TeamRanks;
|
||||
|
||||
public TeamDifferencesComparisonLayer(TrueSkillFactorGraph<TPlayer> parentGraph, int[] teamRanks)
|
||||
: base(parentGraph)
|
||||
{
|
||||
_TeamRanks = teamRanks;
|
||||
GameInfo gameInfo = ParentFactorGraph.GameInfo;
|
||||
_Epsilon = DrawMargin.GetDrawMarginFromDrawProbability(gameInfo.DrawProbability, gameInfo.Beta);
|
||||
}
|
||||
|
||||
public override void BuildLayer()
|
||||
{
|
||||
for (int i = 0; i < InputVariablesGroups.Count; i++)
|
||||
{
|
||||
bool isDraw = (_TeamRanks[i] == _TeamRanks[i + 1]);
|
||||
Variable<GaussianDistribution> teamDifference = InputVariablesGroups[i][0];
|
||||
|
||||
GaussianFactor factor =
|
||||
isDraw
|
||||
? (GaussianFactor) new GaussianWithinFactor(_Epsilon, teamDifference)
|
||||
: new GaussianGreaterThanFactor(_Epsilon, teamDifference);
|
||||
|
||||
AddLayerFactor(factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
using Moserware.Skills.TrueSkill.Factors;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Layers
|
||||
{
|
||||
internal class TeamPerformancesToTeamPerformanceDifferencesLayer<TPlayer> :
|
||||
TrueSkillFactorGraphLayer
|
||||
<TPlayer, Variable<GaussianDistribution>, GaussianWeightedSumFactor, Variable<GaussianDistribution>>
|
||||
{
|
||||
public TeamPerformancesToTeamPerformanceDifferencesLayer(TrueSkillFactorGraph<TPlayer> parentGraph)
|
||||
: base(parentGraph)
|
||||
{
|
||||
}
|
||||
|
||||
public override void BuildLayer()
|
||||
{
|
||||
for (int i = 0; i < InputVariablesGroups.Count - 1; i++)
|
||||
{
|
||||
Variable<GaussianDistribution> strongerTeam = InputVariablesGroups[i][0];
|
||||
Variable<GaussianDistribution> weakerTeam = InputVariablesGroups[i + 1][0];
|
||||
|
||||
Variable<GaussianDistribution> currentDifference = CreateOutputVariable();
|
||||
AddLayerFactor(CreateTeamPerformanceToDifferenceFactor(strongerTeam, weakerTeam, currentDifference));
|
||||
|
||||
// REVIEW: Does it make sense to have groups of one?
|
||||
OutputVariablesGroups.Add(new[] {currentDifference});
|
||||
}
|
||||
}
|
||||
|
||||
private GaussianWeightedSumFactor CreateTeamPerformanceToDifferenceFactor(
|
||||
Variable<GaussianDistribution> strongerTeam, Variable<GaussianDistribution> weakerTeam,
|
||||
Variable<GaussianDistribution> output)
|
||||
{
|
||||
return new GaussianWeightedSumFactor(output, new[] {strongerTeam, weakerTeam}, new[] {1.0, -1.0});
|
||||
}
|
||||
|
||||
private Variable<GaussianDistribution> CreateOutputVariable()
|
||||
{
|
||||
return ParentFactorGraph.VariableFactory.CreateBasicVariable("Team performance difference");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill.Layers
|
||||
{
|
||||
internal abstract class TrueSkillFactorGraphLayer<TPlayer, TInputVariable, TFactor, TOutputVariable>
|
||||
:
|
||||
FactorGraphLayer
|
||||
<TrueSkillFactorGraph<TPlayer>, GaussianDistribution, Variable<GaussianDistribution>, TInputVariable,
|
||||
TFactor, TOutputVariable>
|
||||
where TInputVariable : Variable<GaussianDistribution>
|
||||
where TFactor : Factor<GaussianDistribution>
|
||||
where TOutputVariable : Variable<GaussianDistribution>
|
||||
{
|
||||
public TrueSkillFactorGraphLayer(TrueSkillFactorGraph<TPlayer> parentGraph)
|
||||
: base(parentGraph)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Numerics;
|
||||
using Moserware.Skills.FactorGraphs;
|
||||
using Moserware.Skills.TrueSkill.Layers;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill
|
||||
{
|
||||
public class TrueSkillFactorGraph<TPlayer> :
|
||||
FactorGraph<TrueSkillFactorGraph<TPlayer>, GaussianDistribution, Variable<GaussianDistribution>>
|
||||
{
|
||||
private readonly List<FactorGraphLayerBase<GaussianDistribution>> _Layers;
|
||||
private readonly PlayerPriorValuesToSkillsLayer<TPlayer> _PriorLayer;
|
||||
|
||||
public TrueSkillFactorGraph(GameInfo gameInfo, IEnumerable<IDictionary<TPlayer, Rating>> teams, int[] teamRanks)
|
||||
{
|
||||
_PriorLayer = new PlayerPriorValuesToSkillsLayer<TPlayer>(this, teams);
|
||||
GameInfo = gameInfo;
|
||||
VariableFactory =
|
||||
new VariableFactory<GaussianDistribution>(() => GaussianDistribution.FromPrecisionMean(0, 0));
|
||||
|
||||
_Layers = new List<FactorGraphLayerBase<GaussianDistribution>>
|
||||
{
|
||||
_PriorLayer,
|
||||
new PlayerSkillsToPerformancesLayer<TPlayer>(this),
|
||||
new PlayerPerformancesToTeamPerformancesLayer<TPlayer>(this),
|
||||
new IteratedTeamDifferencesInnerLayer<TPlayer>(
|
||||
this,
|
||||
new TeamPerformancesToTeamPerformanceDifferencesLayer<TPlayer>(this),
|
||||
new TeamDifferencesComparisonLayer<TPlayer>(this, teamRanks))
|
||||
};
|
||||
}
|
||||
|
||||
public GameInfo GameInfo { get; private set; }
|
||||
|
||||
public void BuildGraph()
|
||||
{
|
||||
object lastOutput = null;
|
||||
|
||||
foreach (var currentLayer in _Layers)
|
||||
{
|
||||
if (lastOutput != null)
|
||||
{
|
||||
currentLayer.SetRawInputVariablesGroups(lastOutput);
|
||||
}
|
||||
|
||||
currentLayer.BuildLayer();
|
||||
|
||||
lastOutput = currentLayer.GetRawOutputVariablesGroups();
|
||||
}
|
||||
}
|
||||
|
||||
public void RunSchedule()
|
||||
{
|
||||
Schedule<GaussianDistribution> fullSchedule = CreateFullSchedule();
|
||||
double fullScheduleDelta = fullSchedule.Visit();
|
||||
}
|
||||
|
||||
public double GetProbabilityOfRanking()
|
||||
{
|
||||
var factorList = new FactorList<GaussianDistribution>();
|
||||
|
||||
foreach (var currentLayer in _Layers)
|
||||
{
|
||||
foreach (var currentFactor in currentLayer.UntypedFactors)
|
||||
{
|
||||
factorList.AddFactor(currentFactor);
|
||||
}
|
||||
}
|
||||
|
||||
double logZ = factorList.LogNormalization;
|
||||
return Math.Exp(logZ);
|
||||
}
|
||||
|
||||
private Schedule<GaussianDistribution> CreateFullSchedule()
|
||||
{
|
||||
var fullSchedule = new List<Schedule<GaussianDistribution>>();
|
||||
|
||||
foreach (var currentLayer in _Layers)
|
||||
{
|
||||
Schedule<GaussianDistribution> currentPriorSchedule = currentLayer.CreatePriorSchedule();
|
||||
if (currentPriorSchedule != null)
|
||||
{
|
||||
fullSchedule.Add(currentPriorSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
// Casting to IEnumerable to get the LINQ Reverse()
|
||||
IEnumerable<FactorGraphLayerBase<GaussianDistribution>> allLayers = _Layers;
|
||||
|
||||
foreach (var currentLayer in allLayers.Reverse())
|
||||
{
|
||||
Schedule<GaussianDistribution> currentPosteriorSchedule = currentLayer.CreatePosteriorSchedule();
|
||||
if (currentPosteriorSchedule != null)
|
||||
{
|
||||
fullSchedule.Add(currentPosteriorSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
return new ScheduleSequence<GaussianDistribution>("Full schedule", fullSchedule);
|
||||
}
|
||||
|
||||
public IDictionary<TPlayer, Rating> GetUpdatedRatings()
|
||||
{
|
||||
var result = new Dictionary<TPlayer, Rating>();
|
||||
foreach (var currentTeam in _PriorLayer.OutputVariablesGroups)
|
||||
{
|
||||
foreach (var currentPlayer in currentTeam)
|
||||
{
|
||||
result[currentPlayer.Key] = new Rating(currentPlayer.Value.Mean,
|
||||
currentPlayer.Value.StandardDeviation);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
using System;
|
||||
using Moserware.Numerics;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill
|
||||
{
|
||||
internal static class TruncatedGaussianCorrectionFunctions
|
||||
{
|
||||
// These functions from the bottom of page 4 of the TrueSkill paper.
|
||||
|
||||
/// <summary>
|
||||
/// The "V" function where the team performance difference is greater than the draw margin.
|
||||
/// </summary>
|
||||
/// <remarks>In the reference F# implementation, this is referred to as "the additive
|
||||
/// correction of a single-sided truncated Gaussian with unit variance."</remarks>
|
||||
/// <param name="teamPerformanceDifference"></param>
|
||||
/// <param name="drawMargin">In the paper, it's referred to as just "ε".</param>
|
||||
/// <returns></returns>
|
||||
public static double VExceedsMargin(double teamPerformanceDifference, double drawMargin, double c)
|
||||
{
|
||||
return VExceedsMargin(teamPerformanceDifference/c, drawMargin/c);
|
||||
//return GaussianDistribution.At((teamPerformanceDifference - drawMargin) / c) / GaussianDistribution.CumulativeTo((teamPerformanceDifference - drawMargin) / c);
|
||||
}
|
||||
|
||||
public static double VExceedsMargin(double teamPerformanceDifference, double drawMargin)
|
||||
{
|
||||
double denominator = GaussianDistribution.CumulativeTo(teamPerformanceDifference - drawMargin);
|
||||
|
||||
if (denominator < 2.222758749e-162)
|
||||
{
|
||||
return -teamPerformanceDifference + drawMargin;
|
||||
}
|
||||
|
||||
return GaussianDistribution.At(teamPerformanceDifference - drawMargin)/denominator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "W" function where the team performance difference is greater than the draw margin.
|
||||
/// </summary>
|
||||
/// <remarks>In the reference F# implementation, this is referred to as "the multiplicative
|
||||
/// correction of a single-sided truncated Gaussian with unit variance."</remarks>
|
||||
/// <param name="teamPerformanceDifference"></param>
|
||||
/// <param name="drawMargin"></param>
|
||||
/// <param name="c"></param>
|
||||
/// <returns></returns>
|
||||
public static double WExceedsMargin(double teamPerformanceDifference, double drawMargin, double c)
|
||||
{
|
||||
return WExceedsMargin(teamPerformanceDifference/c, drawMargin/c);
|
||||
//var vWin = VExceedsMargin(teamPerformanceDifference, drawMargin, c);
|
||||
//return vWin * (vWin + (teamPerformanceDifference - drawMargin) / c);
|
||||
}
|
||||
|
||||
public static double WExceedsMargin(double teamPerformanceDifference, double drawMargin)
|
||||
{
|
||||
double denominator = GaussianDistribution.CumulativeTo(teamPerformanceDifference - drawMargin);
|
||||
|
||||
if (denominator < 2.222758749e-162)
|
||||
{
|
||||
if (teamPerformanceDifference < 0.0)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double vWin = VExceedsMargin(teamPerformanceDifference, drawMargin);
|
||||
return vWin*(vWin + teamPerformanceDifference - drawMargin);
|
||||
}
|
||||
|
||||
// the additive correction of a double-sided truncated Gaussian with unit variance
|
||||
public static double VWithinMargin(double teamPerformanceDifference, double drawMargin, double c)
|
||||
{
|
||||
return VWithinMargin(teamPerformanceDifference/c, drawMargin/c);
|
||||
//var teamPerformanceDifferenceAbsoluteValue = Math.Abs(teamPerformanceDifference);
|
||||
//return (GaussianDistribution.At((-drawMargin - teamPerformanceDifferenceAbsoluteValue) / c) - GaussianDistribution.At((drawMargin - teamPerformanceDifferenceAbsoluteValue) / c))
|
||||
// /
|
||||
// (GaussianDistribution.CumulativeTo((drawMargin - teamPerformanceDifferenceAbsoluteValue) / c) - GaussianDistribution.CumulativeTo((-drawMargin - teamPerformanceDifferenceAbsoluteValue) / c));
|
||||
}
|
||||
|
||||
// My original:
|
||||
//public static double VWithinMargin(double teamPerformanceDifference, double drawMargin)
|
||||
//{
|
||||
// var teamPerformanceDifferenceAbsoluteValue = Math.Abs(teamPerformanceDifference);
|
||||
// return (GaussianDistribution.At(-drawMargin - teamPerformanceDifferenceAbsoluteValue) - GaussianDistribution.At(drawMargin - teamPerformanceDifferenceAbsoluteValue))
|
||||
// /
|
||||
// (GaussianDistribution.CumulativeTo(drawMargin - teamPerformanceDifferenceAbsoluteValue) - GaussianDistribution.CumulativeTo(-drawMargin - teamPerformanceDifferenceAbsoluteValue));
|
||||
//}
|
||||
|
||||
// from F#:
|
||||
public static double VWithinMargin(double teamPerformanceDifference, double drawMargin)
|
||||
{
|
||||
double teamPerformanceDifferenceAbsoluteValue = Math.Abs(teamPerformanceDifference);
|
||||
double denominator =
|
||||
GaussianDistribution.CumulativeTo(drawMargin - teamPerformanceDifferenceAbsoluteValue) -
|
||||
GaussianDistribution.CumulativeTo(-drawMargin - teamPerformanceDifferenceAbsoluteValue);
|
||||
if (denominator < 2.222758749e-162)
|
||||
{
|
||||
if (teamPerformanceDifference < 0.0)
|
||||
{
|
||||
return -teamPerformanceDifference - drawMargin;
|
||||
}
|
||||
|
||||
return -teamPerformanceDifference + drawMargin;
|
||||
}
|
||||
|
||||
double numerator = GaussianDistribution.At(-drawMargin - teamPerformanceDifferenceAbsoluteValue) -
|
||||
GaussianDistribution.At(drawMargin - teamPerformanceDifferenceAbsoluteValue);
|
||||
|
||||
if (teamPerformanceDifference < 0.0)
|
||||
{
|
||||
return -numerator/denominator;
|
||||
}
|
||||
|
||||
return numerator/denominator;
|
||||
}
|
||||
|
||||
// the multiplicative correction of a double-sided truncated Gaussian with unit variance
|
||||
public static double WWithinMargin(double teamPerformanceDifference, double drawMargin, double c)
|
||||
{
|
||||
return WWithinMargin(teamPerformanceDifference/c, drawMargin/c);
|
||||
//var teamPerformanceDifferenceAbsoluteValue = Math.Abs(teamPerformanceDifference);
|
||||
//var vDraw = VWithinMargin(teamPerformanceDifferenceAbsoluteValue, drawMargin, c);
|
||||
|
||||
//return (vDraw * vDraw)
|
||||
// +
|
||||
// (
|
||||
// (
|
||||
// (
|
||||
// ((drawMargin - teamPerformanceDifferenceAbsoluteValue) / c)
|
||||
// *
|
||||
// GaussianDistribution.At((drawMargin - teamPerformanceDifferenceAbsoluteValue) / c)
|
||||
// )
|
||||
// +
|
||||
// (
|
||||
// ((drawMargin + teamPerformanceDifferenceAbsoluteValue) / c)
|
||||
// *
|
||||
// GaussianDistribution.At((drawMargin + teamPerformanceDifferenceAbsoluteValue) / c)
|
||||
// )
|
||||
// )
|
||||
// /
|
||||
// (
|
||||
// GaussianDistribution.CumulativeTo((drawMargin - teamPerformanceDifferenceAbsoluteValue) / c)
|
||||
// -
|
||||
// GaussianDistribution.CumulativeTo((-drawMargin - teamPerformanceDifferenceAbsoluteValue) / c)
|
||||
// )
|
||||
// );
|
||||
}
|
||||
|
||||
// My original:
|
||||
//public static double WWithinMargin(double teamPerformanceDifference, double drawMargin)
|
||||
//{
|
||||
// var teamPerformanceDifferenceAbsoluteValue = Math.Abs(teamPerformanceDifference);
|
||||
// var vDraw = VWithinMargin(teamPerformanceDifferenceAbsoluteValue, drawMargin);
|
||||
// return (vDraw * vDraw)
|
||||
// +
|
||||
// (
|
||||
// ((drawMargin - teamPerformanceDifferenceAbsoluteValue) * GaussianDistribution.At(drawMargin - teamPerformanceDifferenceAbsoluteValue) + (drawMargin + teamPerformanceDifferenceAbsoluteValue) * GaussianDistribution.At(drawMargin + teamPerformanceDifferenceAbsoluteValue))
|
||||
// /
|
||||
// (GaussianDistribution.CumulativeTo(drawMargin - teamPerformanceDifferenceAbsoluteValue) - GaussianDistribution.CumulativeTo(-drawMargin - teamPerformanceDifferenceAbsoluteValue))
|
||||
// );
|
||||
//}
|
||||
|
||||
// From F#:
|
||||
public static double WWithinMargin(double teamPerformanceDifference, double drawMargin)
|
||||
{
|
||||
double teamPerformanceDifferenceAbsoluteValue = Math.Abs(teamPerformanceDifference);
|
||||
double denominator = GaussianDistribution.CumulativeTo(drawMargin - teamPerformanceDifferenceAbsoluteValue)
|
||||
-
|
||||
GaussianDistribution.CumulativeTo(-drawMargin - teamPerformanceDifferenceAbsoluteValue);
|
||||
|
||||
if (denominator < 2.222758749e-162)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
double vt = VWithinMargin(teamPerformanceDifferenceAbsoluteValue, drawMargin);
|
||||
|
||||
return vt*vt +
|
||||
(
|
||||
(drawMargin - teamPerformanceDifferenceAbsoluteValue)
|
||||
*
|
||||
GaussianDistribution.At(
|
||||
drawMargin - teamPerformanceDifferenceAbsoluteValue)
|
||||
- (-drawMargin - teamPerformanceDifferenceAbsoluteValue)
|
||||
*
|
||||
GaussianDistribution.At(-drawMargin - teamPerformanceDifferenceAbsoluteValue))/denominator;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Skills.Numerics;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the new ratings for only two players.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When you only have two players, a lot of the math simplifies. The main purpose of this class
|
||||
/// is to show the bare minimum of what a TrueSkill implementation should have.
|
||||
/// </remarks>
|
||||
public class TwoPlayerTrueSkillCalculator : SkillCalculator
|
||||
{
|
||||
public TwoPlayerTrueSkillCalculator()
|
||||
: base(SupportedOptions.None, Range<TeamsRange>.Exactly(2), Range<PlayersRange>.Exactly(1))
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IDictionary<TPlayer, Rating> CalculateNewRatings<TPlayer>(GameInfo gameInfo,
|
||||
IEnumerable
|
||||
<IDictionary<TPlayer, Rating>>
|
||||
teams, params int[] teamRanks)
|
||||
{
|
||||
// Basic argument checking
|
||||
Guard.ArgumentNotNull(gameInfo, "gameInfo");
|
||||
ValidateTeamCountAndPlayersCountPerTeam(teams);
|
||||
|
||||
// Make sure things are in order
|
||||
RankSorter.Sort(ref teams, ref teamRanks);
|
||||
|
||||
// Get the teams as a list to make it easier to index
|
||||
List<IDictionary<TPlayer, Rating>> teamList = teams.ToList();
|
||||
|
||||
// Since we verified that each team has one player, we know the player is the first one
|
||||
IDictionary<TPlayer, Rating> winningTeam = teamList[0];
|
||||
TPlayer winner = winningTeam.Keys.First();
|
||||
Rating winnerPreviousRating = winningTeam[winner];
|
||||
|
||||
IDictionary<TPlayer, Rating> losingTeam = teamList[1];
|
||||
TPlayer loser = losingTeam.Keys.First();
|
||||
Rating loserPreviousRating = losingTeam[loser];
|
||||
|
||||
bool wasDraw = (teamRanks[0] == teamRanks[1]);
|
||||
|
||||
var results = new Dictionary<TPlayer, Rating>();
|
||||
results[winner] = CalculateNewRating(gameInfo, winnerPreviousRating, loserPreviousRating,
|
||||
wasDraw ? PairwiseComparison.Draw : PairwiseComparison.Win);
|
||||
results[loser] = CalculateNewRating(gameInfo, loserPreviousRating, winnerPreviousRating,
|
||||
wasDraw ? PairwiseComparison.Draw : PairwiseComparison.Lose);
|
||||
|
||||
// And we're done!
|
||||
return results;
|
||||
}
|
||||
|
||||
private static Rating CalculateNewRating(GameInfo gameInfo, Rating selfRating, Rating opponentRating,
|
||||
PairwiseComparison comparison)
|
||||
{
|
||||
double drawMargin = DrawMargin.GetDrawMarginFromDrawProbability(gameInfo.DrawProbability, gameInfo.Beta);
|
||||
|
||||
double c =
|
||||
Math.Sqrt(
|
||||
Square(selfRating.StandardDeviation)
|
||||
+
|
||||
Square(opponentRating.StandardDeviation)
|
||||
+
|
||||
2*Square(gameInfo.Beta));
|
||||
|
||||
double winningMean = selfRating.Mean;
|
||||
double losingMean = opponentRating.Mean;
|
||||
|
||||
switch (comparison)
|
||||
{
|
||||
case PairwiseComparison.Win:
|
||||
case PairwiseComparison.Draw:
|
||||
// NOP
|
||||
break;
|
||||
case PairwiseComparison.Lose:
|
||||
winningMean = opponentRating.Mean;
|
||||
losingMean = selfRating.Mean;
|
||||
break;
|
||||
}
|
||||
|
||||
double meanDelta = winningMean - losingMean;
|
||||
|
||||
double v;
|
||||
double w;
|
||||
double rankMultiplier;
|
||||
|
||||
if (comparison != PairwiseComparison.Draw)
|
||||
{
|
||||
// non-draw case
|
||||
v = TruncatedGaussianCorrectionFunctions.VExceedsMargin(meanDelta, drawMargin, c);
|
||||
w = TruncatedGaussianCorrectionFunctions.WExceedsMargin(meanDelta, drawMargin, c);
|
||||
rankMultiplier = (int) comparison;
|
||||
}
|
||||
else
|
||||
{
|
||||
v = TruncatedGaussianCorrectionFunctions.VWithinMargin(meanDelta, drawMargin, c);
|
||||
w = TruncatedGaussianCorrectionFunctions.WWithinMargin(meanDelta, drawMargin, c);
|
||||
rankMultiplier = 1;
|
||||
}
|
||||
|
||||
double meanMultiplier = (Square(selfRating.StandardDeviation) + Square(gameInfo.DynamicsFactor))/c;
|
||||
|
||||
double varianceWithDynamics = Square(selfRating.StandardDeviation) + Square(gameInfo.DynamicsFactor);
|
||||
double stdDevMultiplier = varianceWithDynamics/Square(c);
|
||||
|
||||
double newMean = selfRating.Mean + (rankMultiplier*meanMultiplier*v);
|
||||
double newStdDev = Math.Sqrt(varianceWithDynamics*(1 - w*stdDevMultiplier));
|
||||
|
||||
return new Rating(newMean, newStdDev);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override double CalculateMatchQuality<TPlayer>(GameInfo gameInfo,
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teams)
|
||||
{
|
||||
Guard.ArgumentNotNull(gameInfo, "gameInfo");
|
||||
ValidateTeamCountAndPlayersCountPerTeam(teams);
|
||||
|
||||
Rating player1Rating = teams.First().Values.First();
|
||||
Rating player2Rating = teams.Last().Values.First();
|
||||
|
||||
// We just use equation 4.1 found on page 8 of the TrueSkill 2006 paper:
|
||||
double betaSquared = Square(gameInfo.Beta);
|
||||
double player1SigmaSquared = Square(player1Rating.StandardDeviation);
|
||||
double player2SigmaSquared = Square(player2Rating.StandardDeviation);
|
||||
|
||||
// This is the square root part of the equation:
|
||||
double sqrtPart =
|
||||
Math.Sqrt(
|
||||
(2*betaSquared)
|
||||
/
|
||||
(2*betaSquared + player1SigmaSquared + player2SigmaSquared));
|
||||
|
||||
// This is the exponent part of the equation:
|
||||
double expPart =
|
||||
Math.Exp(
|
||||
(-1*Square(player1Rating.Mean - player2Rating.Mean))
|
||||
/
|
||||
(2*(2*betaSquared + player1SigmaSquared + player2SigmaSquared)));
|
||||
|
||||
return sqrtPart*expPart;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moserware.Skills.Numerics;
|
||||
|
||||
namespace Moserware.Skills.TrueSkill
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates new ratings for only two teams where each team has 1 or more players.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When you only have two teams, the math is still simple: no factor graphs are used yet.
|
||||
/// </remarks>
|
||||
public class TwoTeamTrueSkillCalculator : SkillCalculator
|
||||
{
|
||||
public TwoTeamTrueSkillCalculator()
|
||||
: base(SupportedOptions.None, Range<TeamsRange>.Exactly(2), Range<PlayersRange>.AtLeast(1))
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IDictionary<TPlayer, Rating> CalculateNewRatings<TPlayer>(GameInfo gameInfo,
|
||||
IEnumerable
|
||||
<IDictionary<TPlayer, Rating>>
|
||||
teams, params int[] teamRanks)
|
||||
{
|
||||
Guard.ArgumentNotNull(gameInfo, "gameInfo");
|
||||
ValidateTeamCountAndPlayersCountPerTeam(teams);
|
||||
|
||||
RankSorter.Sort(ref teams, ref teamRanks);
|
||||
|
||||
IDictionary<TPlayer, Rating> team1 = teams.First();
|
||||
IDictionary<TPlayer, Rating> team2 = teams.Last();
|
||||
|
||||
bool wasDraw = (teamRanks[0] == teamRanks[1]);
|
||||
|
||||
var results = new Dictionary<TPlayer, Rating>();
|
||||
|
||||
UpdatePlayerRatings(gameInfo,
|
||||
results,
|
||||
team1,
|
||||
team2,
|
||||
wasDraw ? PairwiseComparison.Draw : PairwiseComparison.Win);
|
||||
|
||||
UpdatePlayerRatings(gameInfo,
|
||||
results,
|
||||
team2,
|
||||
team1,
|
||||
wasDraw ? PairwiseComparison.Draw : PairwiseComparison.Lose);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void UpdatePlayerRatings<TPlayer>(GameInfo gameInfo,
|
||||
IDictionary<TPlayer, Rating> newPlayerRatings,
|
||||
IDictionary<TPlayer, Rating> selfTeam,
|
||||
IDictionary<TPlayer, Rating> otherTeam,
|
||||
PairwiseComparison selfToOtherTeamComparison)
|
||||
{
|
||||
double drawMargin = DrawMargin.GetDrawMarginFromDrawProbability(gameInfo.DrawProbability, gameInfo.Beta);
|
||||
double betaSquared = Square(gameInfo.Beta);
|
||||
double tauSquared = Square(gameInfo.DynamicsFactor);
|
||||
|
||||
int totalPlayers = selfTeam.Count() + otherTeam.Count();
|
||||
|
||||
double selfMeanSum = selfTeam.Values.Sum(r => r.Mean);
|
||||
double otherTeamMeanSum = otherTeam.Values.Sum(r => r.Mean);
|
||||
|
||||
double c = Math.Sqrt(selfTeam.Values.Sum(r => Square(r.StandardDeviation))
|
||||
+
|
||||
otherTeam.Values.Sum(r => Square(r.StandardDeviation))
|
||||
+
|
||||
totalPlayers*betaSquared);
|
||||
|
||||
double winningMean = selfMeanSum;
|
||||
double losingMean = otherTeamMeanSum;
|
||||
|
||||
switch (selfToOtherTeamComparison)
|
||||
{
|
||||
case PairwiseComparison.Win:
|
||||
case PairwiseComparison.Draw:
|
||||
// NOP
|
||||
break;
|
||||
case PairwiseComparison.Lose:
|
||||
winningMean = otherTeamMeanSum;
|
||||
losingMean = selfMeanSum;
|
||||
break;
|
||||
}
|
||||
|
||||
double meanDelta = winningMean - losingMean;
|
||||
|
||||
double v;
|
||||
double w;
|
||||
double rankMultiplier;
|
||||
|
||||
if (selfToOtherTeamComparison != PairwiseComparison.Draw)
|
||||
{
|
||||
// non-draw case
|
||||
v = TruncatedGaussianCorrectionFunctions.VExceedsMargin(meanDelta, drawMargin, c);
|
||||
w = TruncatedGaussianCorrectionFunctions.WExceedsMargin(meanDelta, drawMargin, c);
|
||||
rankMultiplier = (int) selfToOtherTeamComparison;
|
||||
}
|
||||
else
|
||||
{
|
||||
// assume draw
|
||||
v = TruncatedGaussianCorrectionFunctions.VWithinMargin(meanDelta, drawMargin, c);
|
||||
w = TruncatedGaussianCorrectionFunctions.WWithinMargin(meanDelta, drawMargin, c);
|
||||
rankMultiplier = 1;
|
||||
}
|
||||
|
||||
foreach (var teamPlayerRatingPair in selfTeam)
|
||||
{
|
||||
Rating previousPlayerRating = teamPlayerRatingPair.Value;
|
||||
|
||||
double meanMultiplier = (Square(previousPlayerRating.StandardDeviation) + tauSquared)/c;
|
||||
double stdDevMultiplier = (Square(previousPlayerRating.StandardDeviation) + tauSquared)/Square(c);
|
||||
|
||||
double playerMeanDelta = (rankMultiplier*meanMultiplier*v);
|
||||
double newMean = previousPlayerRating.Mean + playerMeanDelta;
|
||||
|
||||
double newStdDev =
|
||||
Math.Sqrt((Square(previousPlayerRating.StandardDeviation) + tauSquared)*(1 - w*stdDevMultiplier));
|
||||
|
||||
newPlayerRatings[teamPlayerRatingPair.Key] = new Rating(newMean, newStdDev);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override double CalculateMatchQuality<TPlayer>(GameInfo gameInfo,
|
||||
IEnumerable<IDictionary<TPlayer, Rating>> teams)
|
||||
{
|
||||
Guard.ArgumentNotNull(gameInfo, "gameInfo");
|
||||
ValidateTeamCountAndPlayersCountPerTeam(teams);
|
||||
|
||||
// We've verified that there's just two teams
|
||||
ICollection<Rating> team1 = teams.First().Values;
|
||||
int team1Count = team1.Count();
|
||||
|
||||
ICollection<Rating> team2 = teams.Last().Values;
|
||||
int team2Count = team2.Count();
|
||||
|
||||
int totalPlayers = team1Count + team2Count;
|
||||
|
||||
double betaSquared = Square(gameInfo.Beta);
|
||||
|
||||
double team1MeanSum = team1.Sum(r => r.Mean);
|
||||
double team1StdDevSquared = team1.Sum(r => Square(r.StandardDeviation));
|
||||
|
||||
double team2MeanSum = team2.Sum(r => r.Mean);
|
||||
double team2SigmaSquared = team2.Sum(r => Square(r.StandardDeviation));
|
||||
|
||||
// This comes from equation 4.1 in the TrueSkill paper on page 8
|
||||
// The equation was broken up into the part under the square root sign and
|
||||
// the exponential part to make the code easier to read.
|
||||
|
||||
double sqrtPart
|
||||
= Math.Sqrt(
|
||||
(totalPlayers*betaSquared)
|
||||
/
|
||||
(totalPlayers*betaSquared + team1StdDevSquared + team2SigmaSquared)
|
||||
);
|
||||
|
||||
double expPart
|
||||
= Math.Exp(
|
||||
(-1*Square(team1MeanSum - team2MeanSum))
|
||||
/
|
||||
(2*(totalPlayers*betaSquared + team1StdDevSquared + team2SigmaSquared))
|
||||
);
|
||||
|
||||
return expPart*sqrtPart;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user