Deleted C# code (it was just there to note the fork)

This commit is contained in:
Jeff Moser 2010-10-09 10:42:00 -04:00
parent 294db1d233
commit 110463ad49
86 changed files with 9 additions and 6764 deletions

20
.gitignore vendored

@ -1,20 +0,0 @@
cmake_install.cmake
CMakeCache.txt
CMakeFiles
Makefile
Moserware.Skills.dll
TestResult.xml
UnitTests.dll
bin
deploy
deploy/*
obj
*.suo
*.cache
*.tmp
_Resharper.*
*.resharper
*.resharper.user

@ -1,79 +0,0 @@
cmake_minimum_required(VERSION 2.6)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules)
enable_language(CSharp)
csharp_add_library(Moserware.Skills
Skills/Elo/DuellingEloCalculator.cs
Skills/Elo/EloRating.cs
Skills/Elo/FideEloCalculator.cs
Skills/Elo/FideKFactor.cs
Skills/Elo/GaussianEloCalculator.cs
Skills/Elo/GaussianKFactor.cs
Skills/Elo/KFactor.cs
Skills/Elo/TwoPlayerEloCalculator.cs
Skills/FactorGraphs/Factor.cs
Skills/FactorGraphs/FactorGraph.cs
Skills/FactorGraphs/FactorGraphLayer.cs
Skills/FactorGraphs/FactorList.cs
Skills/FactorGraphs/Message.cs
Skills/FactorGraphs/Schedule.cs
Skills/FactorGraphs/Variable.cs
Skills/FactorGraphs/VariableFactory.cs
Skills/GameInfo.cs
Skills/Guard.cs
Skills/ISupportPartialPlay.cs
Skills/ISupportPartialUpdate.cs
Skills/Numerics/GaussianDistribution.cs
Skills/Numerics/Matrix.cs
Skills/Numerics/Range.cs
Skills/PairwiseComparison.cs
Skills/PartialPlay.cs
Skills/Player.cs
Skills/PlayersRange.cs
Skills/Properties/AssemblyInfo.cs
Skills/RankSorter.cs
Skills/Rating.cs
Skills/SkillCalculator.cs
Skills/Team.cs
Skills/TeamsRange.cs
Skills/TrueSkill/DrawMargin.cs
Skills/TrueSkill/FactorGraphTrueSkillCalculator.cs
Skills/TrueSkill/TrueSkillFactorGraph.cs
Skills/TrueSkill/TruncatedGaussianCorrectionFunctions.cs
Skills/TrueSkill/TwoTeamTrueSkillCalculator.cs
Skills/TrueSkill/TwoPlayerTrueSkillCalculator.cs
Skills/TrueSkill/Factors/GaussianFactor.cs
Skills/TrueSkill/Factors/GaussianGreaterThanFactor.cs
Skills/TrueSkill/Factors/GaussianLikelihoodFactor.cs
Skills/TrueSkill/Factors/GaussianPriorFactor.cs
Skills/TrueSkill/Factors/GaussianWeightedSumFactor.cs
Skills/TrueSkill/Factors/GaussianWithinFactor.cs
Skills/TrueSkill/Layers/IteratedTeamDifferencesInnerLayer.cs
Skills/TrueSkill/Layers/PlayerPerformancesToTeamPerformancesLayer.cs
Skills/TrueSkill/Layers/PlayerPriorValuesToSkillsLayer.cs
Skills/TrueSkill/Layers/PlayerSkillsToPerformancesLayer.cs
Skills/TrueSkill/Layers/TeamDifferencesComparisonLayer.cs
Skills/TrueSkill/Layers/TeamPerformancesToTeamPerformanceDifferencesLayer.cs
Skills/TrueSkill/Layers/TrueSkillFactorGraphLayer.cs
Skills/TrueSkillCalculator.cs
)
# find_program(NUNIT_CONSOLE nunit-console)
csharp_add_library(UnitTests
UnitTests/Elo/GaussianEloCalculatorTest.cs
UnitTests/Elo/DuellingEloTest.cs
UnitTests/Elo/EloAssert.cs
UnitTests/Elo/FideEloCalculatorTest.cs
UnitTests/Numerics/MatrixTests.cs
UnitTests/Numerics/GaussianDistributionTests.cs
UnitTests/Properties/AssemblyInfo.cs
UnitTests/RankSorterTest.cs
UnitTests/TrueSkill/DrawMarginTest.cs
UnitTests/TrueSkill/FactorGraphTrueSkillCalculatorTests.cs
UnitTests/TrueSkill/TrueSkillCalculatorTests.cs
UnitTests/TrueSkill/TwoPlayerTrueSkillCalculatorTest.cs
UnitTests/TrueSkill/TwoTeamTrueSkillCalculatorTest.cs
REFERENCES Moserware.Skills nunit.framework
)
add_dependencies(UnitTests Moserware.Skills)

9
README Normal file

@ -0,0 +1,9 @@
This is a PHP port of the Moserware.Skills project that's available at
http://github.com/moserware/Skills
For more details on how the algorithm works, see
http://www.moserware.com/2010/03/computing-your-skill.html
For details on how to use this project, see the accompanying unit tests with this project

@ -1,4 +0,0 @@
The code for the TrueSkill calculator is in the "Skills" folder and its unit
tests are in the "UnitTests" folder.
For more details, see the "README" file in each of those folders.

@ -1,26 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Skills", "Skills\Skills.csproj", "{15AD1345-984C-48ED-AF9A-2EAB44E5AA2B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{6F80946D-AC8B-4063-8588-96841C18BF0A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{15AD1345-984C-48ED-AF9A-2EAB44E5AA2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15AD1345-984C-48ED-AF9A-2EAB44E5AA2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15AD1345-984C-48ED-AF9A-2EAB44E5AA2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15AD1345-984C-48ED-AF9A-2EAB44E5AA2B}.Release|Any CPU.Build.0 = Release|Any CPU
{6F80946D-AC8B-4063-8588-96841C18BF0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F80946D-AC8B-4063-8588-96841C18BF0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F80946D-AC8B-4063-8588-96841C18BF0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F80946D-AC8B-4063-8588-96841C18BF0A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

@ -1,138 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Moserware.Skills.Elo
{
public class DuellingEloCalculator : SkillCalculator
{
private readonly TwoPlayerEloCalculator _TwoPlayerEloCalc;
public DuellingEloCalculator(TwoPlayerEloCalculator twoPlayerEloCalculator)
: base(SupportedOptions.None, TeamsRange.AtLeast(2), PlayersRange.AtLeast(1))
{
_TwoPlayerEloCalc = twoPlayerEloCalculator;
}
public override IDictionary<TPlayer, Rating> CalculateNewRatings<TPlayer>(GameInfo gameInfo, IEnumerable<IDictionary<TPlayer, Rating>> teams, params int[] teamRanks)
{
// On page 6 of the TrueSkill paper, the authors write:
// "When we had to process a team game or a game with more than two teams we used
// the so-called *duelling* heuristic: For each player, compute the Δ's in comparison
// to all other players based on the team outcome of the player and every other player and
// perform an update with the average of the Δ's."
// This implements that algorithm.
ValidateTeamCountAndPlayersCountPerTeam(teams);
RankSorter.Sort(ref teams, ref teamRanks);
var teamsList = teams.ToList();
var deltas = new Dictionary<TPlayer, IDictionary<TPlayer, double>>();
for(int ixCurrentTeam = 0; ixCurrentTeam < teamsList.Count; ixCurrentTeam++)
{
for(int ixOtherTeam = 0; ixOtherTeam < teamsList.Count; ixOtherTeam++)
{
if(ixOtherTeam == ixCurrentTeam)
{
// Shouldn't duel against ourself ;)
continue;
}
var currentTeam = teamsList[ixCurrentTeam];
var otherTeam = teamsList[ixOtherTeam];
// Remember that bigger numbers mean worse rank (e.g. other-current is what we want)
var comparison = (PairwiseComparison) Math.Sign(teamRanks[ixOtherTeam] - teamRanks[ixCurrentTeam]);
foreach(var currentTeamPlayerRatingPair in currentTeam)
{
foreach(var otherTeamPlayerRatingPair in otherTeam)
{
UpdateDuels<TPlayer>(gameInfo, deltas,
currentTeamPlayerRatingPair.Key, currentTeamPlayerRatingPair.Value,
otherTeamPlayerRatingPair.Key, otherTeamPlayerRatingPair.Value,
comparison);
}
}
}
}
var result = new Dictionary<TPlayer, Rating>();
foreach(var currentTeam in teamsList)
{
foreach(var currentTeamPlayerPair in currentTeam)
{
var currentPlayerAverageDuellingDelta = deltas[currentTeamPlayerPair.Key].Values.Average();
result[currentTeamPlayerPair.Key] = new EloRating(currentTeamPlayerPair.Value.Mean + currentPlayerAverageDuellingDelta);
}
}
return result;
}
private void UpdateDuels<TPlayer>(GameInfo gameInfo,
IDictionary<TPlayer, IDictionary<TPlayer, double>> duels,
TPlayer player1, Rating player1Rating,
TPlayer player2, Rating player2Rating,
PairwiseComparison weakToStrongComparison)
{
var duelOutcomes = _TwoPlayerEloCalc.CalculateNewRatings(gameInfo,
Teams.Concat(
new Team<TPlayer>(player1, player1Rating),
new Team<TPlayer>(player2, player2Rating)),
(weakToStrongComparison == PairwiseComparison.Win) ? new int[] { 1, 2 }
: (weakToStrongComparison == PairwiseComparison.Lose) ? new int[] { 2, 1 }
: new int[] { 1, 1});
UpdateDuelInfo(duels, player1, player1Rating, duelOutcomes[player1], player2);
UpdateDuelInfo(duels, player2, player2Rating, duelOutcomes[player2], player1);
}
private static void UpdateDuelInfo<TPlayer>(IDictionary<TPlayer, IDictionary<TPlayer, double>> duels,
TPlayer self, Rating selfBeforeRating, Rating selfAfterRating,
TPlayer opponent )
{
IDictionary<TPlayer, double> selfToOpponentDuelDeltas;
if(!duels.TryGetValue(self, out selfToOpponentDuelDeltas))
{
selfToOpponentDuelDeltas = new Dictionary<TPlayer, double>();
duels[self] = selfToOpponentDuelDeltas;
}
selfToOpponentDuelDeltas[opponent] = selfAfterRating.Mean - selfBeforeRating.Mean;
}
public override double CalculateMatchQuality<TPlayer>(GameInfo gameInfo, IEnumerable<IDictionary<TPlayer, Rating>> teams)
{
// HACK! Need a better algorithm, this is just to have something there and it isn't good
double minQuality = 1.0;
var teamList = teams.ToList();
for(int ixCurrentTeam = 0; ixCurrentTeam < teamList.Count; ixCurrentTeam++)
{
EloRating currentTeamAverageRating = new EloRating(teamList[ixCurrentTeam].Values.Average(r => r.Mean));
var currentTeam = new Team(new Player(ixCurrentTeam), currentTeamAverageRating);
for(int ixOtherTeam = ixCurrentTeam + 1; ixOtherTeam < teamList.Count; ixOtherTeam++)
{
EloRating otherTeamAverageRating = new EloRating(teamList[ixOtherTeam].Values.Average(r => r.Mean));
var otherTeam = new Team(new Player(ixOtherTeam), otherTeamAverageRating);
minQuality = Math.Min(minQuality,
_TwoPlayerEloCalc.CalculateMatchQuality(gameInfo,
Teams.Concat(currentTeam, otherTeam)));
}
}
return minQuality;
}
}
}

@ -1,14 +0,0 @@

namespace Moserware.Skills.Elo
{
/// <summary>
/// An Elo rating represented by a single number (mean).
/// </summary>
public class EloRating : Rating
{
public EloRating(double rating)
: base(rating, 0)
{
}
}
}

@ -1,31 +0,0 @@
using System;
namespace Moserware.Skills.Elo
{
// Including ELO's scheme as a simple comparison.
// See http://en.wikipedia.org/wiki/Elo_rating_system#Theory
// for more details
public class FideEloCalculator : TwoPlayerEloCalculator
{
public FideEloCalculator()
: this(new FideKFactor())
{
}
public FideEloCalculator(FideKFactor kFactor)
: base(kFactor)
{
}
protected override double GetPlayerWinProbability(GameInfo gameInfo, double playerRating, double opponentRating)
{
double ratingDifference = opponentRating - playerRating;
return 1.0
/
(
1.0 + Math.Pow(10.0, ratingDifference / (2 * gameInfo.Beta))
);
}
}
}

@ -1,36 +0,0 @@

namespace Moserware.Skills.Elo
{
// see http://ratings.fide.com/calculator_rtd.phtml for details
public class FideKFactor : KFactor
{
public FideKFactor()
{
}
public override double GetValueForRating(double rating)
{
if (rating < 2400)
{
return 15;
}
return 10;
}
/// <summary>
/// Indicates someone who has played less than 30 games.
/// </summary>
public class Provisional : FideKFactor
{
public Provisional()
{
}
public override double GetValueForRating(double rating)
{
return 25;
}
}
}
}

@ -1,27 +0,0 @@
using System;
using Moserware.Numerics;
namespace Moserware.Skills.Elo
{
public class GaussianEloCalculator : TwoPlayerEloCalculator
{
// From the paper
private static readonly KFactor StableKFactor = new KFactor(24);
public GaussianEloCalculator()
: base(StableKFactor)
{
}
protected override double GetPlayerWinProbability(GameInfo gameInfo, double playerRating, double opponentRating)
{
double ratingDifference = playerRating - opponentRating;
// See equation 1.1 in the TrueSkill paper
return GaussianDistribution.CumulativeTo(
ratingDifference
/
(Math.Sqrt(2) * gameInfo.Beta));
}
}
}

@ -1,20 +0,0 @@
using System;
namespace Moserware.Skills.Elo
{
public class GaussianKFactor : KFactor
{
// From paper
const double StableDynamicsKFactor = 24.0;
public GaussianKFactor()
: base(StableDynamicsKFactor)
{
}
public GaussianKFactor(GameInfo gameInfo, double latestGameWeightingFactor)
: base(latestGameWeightingFactor * gameInfo.Beta * Math.Sqrt(Math.PI))
{
}
}
}

@ -1,22 +0,0 @@

namespace Moserware.Skills.Elo
{
public class KFactor
{
private double _Value;
protected KFactor()
{
}
public KFactor(double exactKFactor)
{
_Value = exactKFactor;
}
public virtual double GetValueForRating(double rating)
{
return _Value;
}
}
}

@ -1,79 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Moserware.Skills.Elo
{
public abstract class TwoPlayerEloCalculator : SkillCalculator
{
protected readonly KFactor _KFactor;
protected TwoPlayerEloCalculator(KFactor kFactor)
: base(SupportedOptions.None, TeamsRange.Exactly(2), PlayersRange.Exactly(1))
{
_KFactor = kFactor;
}
public override IDictionary<TPlayer, Rating> CalculateNewRatings<TPlayer>(GameInfo gameInfo, IEnumerable<IDictionary<TPlayer, Rating>> teams, params int[] teamRanks)
{
ValidateTeamCountAndPlayersCountPerTeam(teams);
RankSorter.Sort(ref teams, ref teamRanks);
var result = new Dictionary<TPlayer, Rating>();
bool isDraw = (teamRanks[0] == teamRanks[1]);
var player1 = teams.First().First();
var player2 = teams.Last().First();
var player1Rating = player1.Value.Mean;
var player2Rating = player2.Value.Mean;
result[player1.Key] = CalculateNewRating(gameInfo, player1Rating, player2Rating, isDraw ? PairwiseComparison.Draw : PairwiseComparison.Win);
result[player2.Key] = CalculateNewRating(gameInfo, player2Rating, player1Rating, isDraw ? PairwiseComparison.Draw : PairwiseComparison.Lose);
return result;
}
protected virtual EloRating CalculateNewRating(GameInfo gameInfo, double selfRating, double opponentRating, PairwiseComparison selfToOpponentComparison)
{
double expectedProbability = GetPlayerWinProbability(gameInfo, selfRating, opponentRating);
double actualProbability = GetScoreFromComparison(selfToOpponentComparison);
double k = _KFactor.GetValueForRating(selfRating);
double ratingChange = k * (actualProbability - expectedProbability);
double newRating = selfRating + ratingChange;
return new EloRating(newRating);
}
private static double GetScoreFromComparison(PairwiseComparison comparison)
{
switch (comparison)
{
case PairwiseComparison.Win:
return 1;
case PairwiseComparison.Draw:
return 0.5;
case PairwiseComparison.Lose:
return 0;
default:
throw new NotSupportedException();
}
}
protected abstract double GetPlayerWinProbability(GameInfo gameInfo, double playerRating, double opponentRating);
public override double CalculateMatchQuality<TPlayer>(GameInfo gameInfo, IEnumerable<IDictionary<TPlayer, Rating>> teams)
{
ValidateTeamCountAndPlayersCountPerTeam(teams);
double player1Rating = teams.First().First().Value.Mean;
double player2Rating = teams.Last().First().Value.Mean;
double ratingDifference = player1Rating - player2Rating;
// The TrueSkill paper mentions that they used s1 - s2 (rating difference) to
// determine match quality. I convert that to a percentage as a delta from 50%
// using the cumulative density function of the specific curve being used
double deltaFrom50Percent = Math.Abs(GetPlayerWinProbability(gameInfo, player1Rating, player2Rating) - 0.5);
return (0.5 - deltaFrom50Percent) / 0.5;
}
}
}

@ -1,94 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Moserware.Skills.FactorGraphs
{
public abstract class Factor<TValue>
{
private readonly List<Message<TValue>> _Messages = new List<Message<TValue>>();
private readonly Dictionary<Message<TValue>, Variable<TValue>> _MessageToVariableBinding =
new Dictionary<Message<TValue>, Variable<TValue>>();
private readonly string _Name;
private readonly List<Variable<TValue>> _Variables = new List<Variable<TValue>>();
protected Factor(string name)
{
_Name = "Factor[" + name + "]";
}
/// Returns the log-normalization constant of that factor
public virtual double LogNormalization
{
get { return 0; }
}
/// Returns the number of messages that the factor has
public int NumberOfMessages
{
get { return _Messages.Count; }
}
protected ReadOnlyCollection<Variable<TValue>> Variables
{
get { return _Variables.AsReadOnly(); }
}
protected ReadOnlyCollection<Message<TValue>> Messages
{
get { return _Messages.AsReadOnly(); }
}
/// Update the message and marginal of the i-th variable that the factor is connected to
public virtual double UpdateMessage(int messageIndex)
{
Guard.ArgumentIsValidIndex(messageIndex, _Messages.Count, "messageIndex");
return UpdateMessage(_Messages[messageIndex], _MessageToVariableBinding[_Messages[messageIndex]]);
}
protected virtual double UpdateMessage(Message<TValue> message, Variable<TValue> variable)
{
throw new NotImplementedException();
}
/// Resets the marginal of the variables a factor is connected to
public virtual void ResetMarginals()
{
foreach (var currentVariable in _MessageToVariableBinding.Values)
{
currentVariable.ResetToPrior();
}
}
/// Sends the ith message to the marginal and returns the log-normalization constant
public virtual double SendMessage(int messageIndex)
{
Guard.ArgumentIsValidIndex(messageIndex, _Messages.Count, "messageIndex");
Message<TValue> message = _Messages[messageIndex];
Variable<TValue> variable = _MessageToVariableBinding[message];
return SendMessage(message, variable);
}
protected abstract double SendMessage(Message<TValue> message, Variable<TValue> variable);
public abstract Message<TValue> CreateVariableToMessageBinding(Variable<TValue> variable);
protected Message<TValue> CreateVariableToMessageBinding(Variable<TValue> variable, Message<TValue> message)
{
int index = _Messages.Count;
_Messages.Add(message);
_MessageToVariableBinding[message] = variable;
_Variables.Add(variable);
return message;
}
public override string ToString()
{
return _Name ?? base.ToString();
}
}
}

@ -1,9 +0,0 @@
namespace Moserware.Skills.FactorGraphs
{
public class FactorGraph<TSelf, TValue, TVariable>
where TSelf : FactorGraph<TSelf, TValue, TVariable>
where TVariable : Variable<TValue>
{
public VariableFactory<TValue> VariableFactory { get; protected set; }
}
}

@ -1,108 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Moserware.Skills.FactorGraphs
{
public abstract class FactorGraphLayerBase<TValue>
{
public abstract IEnumerable<Factor<TValue>> UntypedFactors { get; }
public abstract void BuildLayer();
public virtual Schedule<TValue> CreatePriorSchedule()
{
return null;
}
public virtual Schedule<TValue> CreatePosteriorSchedule()
{
return null;
}
// HACK
public abstract void SetRawInputVariablesGroups(object value);
public abstract object GetRawOutputVariablesGroups();
}
public abstract class FactorGraphLayer<TParentGraph, TValue, TBaseVariable, TInputVariable, TFactor, TOutputVariable>
: FactorGraphLayerBase<TValue>
where TParentGraph : FactorGraph<TParentGraph, TValue, TBaseVariable>
where TBaseVariable : Variable<TValue>
where TInputVariable : TBaseVariable
where TFactor : Factor<TValue>
where TOutputVariable : TBaseVariable
{
private readonly List<TFactor> _LocalFactors = new List<TFactor>();
private readonly List<IList<TOutputVariable>> _OutputVariablesGroups = new List<IList<TOutputVariable>>();
private IList<IList<TInputVariable>> _InputVariablesGroups = new List<IList<TInputVariable>>();
protected FactorGraphLayer(TParentGraph parentGraph)
{
ParentFactorGraph = parentGraph;
}
protected IList<IList<TInputVariable>> InputVariablesGroups
{
get { return _InputVariablesGroups; }
}
// HACK
public TParentGraph ParentFactorGraph { get; private set; }
public IList<IList<TOutputVariable>> OutputVariablesGroups
{
get { return _OutputVariablesGroups; }
}
public IList<TFactor> LocalFactors
{
get { return _LocalFactors; }
}
public override IEnumerable<Factor<TValue>> UntypedFactors
{
get { return _LocalFactors.Cast<Factor<TValue>>(); }
}
public override void SetRawInputVariablesGroups(object value)
{
var newList = value as IList<IList<TInputVariable>>;
if (newList == null)
{
// TODO: message
throw new ArgumentException();
}
_InputVariablesGroups = newList;
}
public override object GetRawOutputVariablesGroups()
{
return _OutputVariablesGroups;
}
protected Schedule<TValue> ScheduleSequence<TSchedule>(
IEnumerable<TSchedule> itemsToSequence,
string nameFormat,
params object[] args)
where TSchedule : Schedule<TValue>
{
string formattedName = String.Format(nameFormat, args);
return new ScheduleSequence<TValue, TSchedule>(formattedName, itemsToSequence);
}
protected void AddLayerFactor(TFactor factor)
{
_LocalFactors.Add(factor);
}
// Helper utility
protected double Square(double x)
{
return x*x;
}
}
}

@ -1,47 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace Moserware.Skills.FactorGraphs
{
/// <summary>
/// Helper class for computing the factor graph's normalization constant.
/// </summary>
public class FactorList<TValue>
{
private readonly List<Factor<TValue>> _List = new List<Factor<TValue>>();
public double LogNormalization
{
get
{
_List.ForEach(f => f.ResetMarginals());
double sumLogZ = 0.0;
for (int i = 0; i < _List.Count; i++)
{
Factor<TValue> f = _List[i];
for (int j = 0; j < f.NumberOfMessages; j++)
{
sumLogZ += f.SendMessage(j);
}
}
double sumLogS = _List.Aggregate(0.0, (acc, fac) => acc + fac.LogNormalization);
return sumLogZ + sumLogS;
}
}
public int Count
{
get { return _List.Count; }
}
public Factor<TValue> AddFactor(Factor<TValue> factor)
{
_List.Add(factor);
return factor;
}
}
}

@ -1,30 +0,0 @@
using System;
namespace Moserware.Skills.FactorGraphs
{
public class Message<T>
{
private readonly string _NameFormat;
private readonly object[] _NameFormatArgs;
public Message()
: this(default(T), null, null)
{
}
public Message(T value, string nameFormat, params object[] args)
{
_NameFormat = nameFormat;
_NameFormatArgs = args;
Value = value;
}
public T Value { get; set; }
public override string ToString()
{
return (_NameFormat == null) ? base.ToString() : String.Format(_NameFormat, _NameFormatArgs);
}
}
}

@ -1,104 +0,0 @@
using System;
using System.Collections.Generic;
namespace Moserware.Skills.FactorGraphs
{
public abstract class Schedule<T>
{
private readonly string _Name;
protected Schedule(string name)
{
_Name = name;
}
public abstract double Visit(int depth, int maxDepth);
public double Visit()
{
return Visit(-1, 0);
}
public override string ToString()
{
return _Name;
}
}
public class ScheduleStep<T> : Schedule<T>
{
private readonly Factor<T> _Factor;
private readonly int _Index;
public ScheduleStep(string name, Factor<T> factor, int index)
: base(name)
{
_Factor = factor;
_Index = index;
}
public override double Visit(int depth, int maxDepth)
{
double delta = _Factor.UpdateMessage(_Index);
return delta;
}
}
public class ScheduleSequence<TValue> : ScheduleSequence<TValue, Schedule<TValue>>
{
public ScheduleSequence(string name, IEnumerable<Schedule<TValue>> schedules)
: base(name, schedules)
{
}
}
public class ScheduleSequence<TValue, TSchedule> : Schedule<TValue>
where TSchedule : Schedule<TValue>
{
private readonly IEnumerable<TSchedule> _Schedules;
public ScheduleSequence(string name, IEnumerable<TSchedule> schedules)
: base(name)
{
_Schedules = schedules;
}
public override double Visit(int depth, int maxDepth)
{
double maxDelta = 0;
foreach (TSchedule currentSchedule in _Schedules)
{
maxDelta = Math.Max(currentSchedule.Visit(depth + 1, maxDepth), maxDelta);
}
return maxDelta;
}
}
public class ScheduleLoop<T> : Schedule<T>
{
private readonly double _MaxDelta;
private readonly Schedule<T> _ScheduleToLoop;
public ScheduleLoop(string name, Schedule<T> scheduleToLoop, double maxDelta)
: base(name)
{
_ScheduleToLoop = scheduleToLoop;
_MaxDelta = maxDelta;
}
public override double Visit(int depth, int maxDepth)
{
int totalIterations = 1;
double delta = _ScheduleToLoop.Visit(depth + 1, maxDepth);
while (delta > _MaxDelta)
{
delta = _ScheduleToLoop.Visit(depth + 1, maxDepth);
totalIterations++;
}
return delta;
}
}
}

@ -1,54 +0,0 @@
using System;
namespace Moserware.Skills.FactorGraphs
{
public class Variable<TValue>
{
private readonly string _Name;
private readonly TValue _Prior;
public Variable(string name, TValue prior)
{
_Name = "Variable[" + name + "]";
_Prior = prior;
ResetToPrior();
}
public virtual TValue Value { get; set; }
public void ResetToPrior()
{
Value = _Prior;
}
public override string ToString()
{
return _Name;
}
}
public class DefaultVariable<TValue> : Variable<TValue>
{
public DefaultVariable()
: base("Default", default(TValue))
{
}
public override TValue Value
{
get { return default(TValue); }
set { throw new NotSupportedException(); }
}
}
public class KeyedVariable<TKey, TValue> : Variable<TValue>
{
public KeyedVariable(TKey key, string name, TValue prior)
: base(name, prior)
{
Key = key;
}
public TKey Key { get; private set; }
}
}

@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
namespace Moserware.Skills.FactorGraphs
{
public class VariableFactory<TValue>
{
// using a Func<TValue> to encourage fresh copies in case it's overwritten
private readonly Func<TValue> _VariablePriorInitializer;
public VariableFactory(Func<TValue> variablePriorInitializer)
{
_VariablePriorInitializer = variablePriorInitializer;
}
public Variable<TValue> CreateBasicVariable(string nameFormat, params object[] args)
{
var newVar = new Variable<TValue>(
String.Format(nameFormat, args),
_VariablePriorInitializer());
return newVar;
}
public KeyedVariable<TKey, TValue> CreateKeyedVariable<TKey>(TKey key, string nameFormat, params object[] args)
{
var newVar = new KeyedVariable<TKey, TValue>(
key,
String.Format(nameFormat, args),
_VariablePriorInitializer());
return newVar;
}
}
}

@ -1,49 +0,0 @@
namespace Moserware.Skills
{
/// <summary>
/// Parameters about the game for calculating the TrueSkill.
/// </summary>
public class GameInfo
{
private const double DefaultBeta = DefaultInitialMean/6.0;
private const double DefaultDrawProbability = 0.10;
private const double DefaultDynamicsFactor = DefaultInitialMean/300.0;
private const double DefaultInitialMean = 25.0;
private const double DefaultInitialStandardDeviation = DefaultInitialMean/3.0;
public GameInfo(double initialMean, double initialStandardDeviation, double beta, double dynamicFactor,
double drawProbability)
{
InitialMean = initialMean;
InitialStandardDeviation = initialStandardDeviation;
Beta = beta;
DynamicsFactor = dynamicFactor;
DrawProbability = drawProbability;
}
public double InitialMean { get; set; }
public double InitialStandardDeviation { get; set; }
public double Beta { get; set; }
public double DynamicsFactor { get; set; }
public double DrawProbability { get; set; }
public Rating DefaultRating
{
get { return new Rating(InitialMean, InitialStandardDeviation); }
}
public static GameInfo DefaultGameInfo
{
get
{
// We return a fresh copy since we have public setters that can mutate state
return new GameInfo(DefaultInitialMean,
DefaultInitialStandardDeviation,
DefaultBeta,
DefaultDynamicsFactor,
DefaultDrawProbability);
}
}
}
}

@ -1,36 +0,0 @@
using System;
namespace Moserware.Skills
{
/// <summary>
/// Verifies argument contracts.
/// </summary>
/// <remarks>These are used until .NET 4.0 ships with Contracts. For more information,
/// see http://www.moserware.com/2008/01/borrowing-ideas-from-3-interesting.html</remarks>
internal static class Guard
{
public static void ArgumentNotNull(object value, string parameterName)
{
if (value == null)
{
throw new ArgumentNullException(parameterName);
}
}
public static void ArgumentIsValidIndex(int index, int count, string parameterName)
{
if ((index < 0) || (index >= count))
{
throw new ArgumentOutOfRangeException(parameterName);
}
}
public static void ArgumentInRangeInclusive(double value, double min, double max, string parameterName)
{
if ((value < min) || (value > max))
{
throw new ArgumentOutOfRangeException(parameterName);
}
}
}
}

@ -1,13 +0,0 @@
namespace Moserware.Skills
{
/// <summary>
/// Indicates support for allowing partial play (where a player only plays a part of the time).
/// </summary>
public interface ISupportPartialPlay
{
/// <summary>
/// Indicates the percent of the time the player should be weighted where 0.0 indicates the player didn't play and 1.0 indicates the player played 100% of the time.
/// </summary>
double PartialPlayPercentage { get; }
}
}

@ -1,10 +0,0 @@
namespace Moserware.Skills
{
public interface ISupportPartialUpdate
{
/// <summary>
/// Indicated how much of a skill update a player should receive where 0.0 represents no update and 1.0 represents 100% of the update.
/// </summary>
double PartialUpdatePercentage { get; }
}
}

@ -1,37 +0,0 @@
The core ideas used in this Moserware.Skills project were described in
"TrueSkill (TM): A Bayesian Skill Rating System" available at
http://research.microsoft.com/apps/pubs/default.aspx?id=67956
The authors of the above paper have asked for a link to that article
as attribution in derived works.
Some concepts were based on sample F# code that was written by Ralf Herbrich
Copyright (c) 2007, 2008 Microsoft Research Ltd, available at
http://blogs.technet.com/apg/archive/2008/06/16/trueskill-in-f.aspx
All the C# code in this Moserware.Skills project is
Copyright (c) 2010 Jeff Moser <jeff@moserware.com>
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY JEFF MOSER ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JEFF MOSER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Jeff Moser.

@ -1,240 +0,0 @@
using System;
namespace Moserware.Numerics
{
public class GaussianDistribution
{
// Intentionally, we're not going to derive related things, but set them all at once
// to get around some NaN issues
private GaussianDistribution()
{
}
public GaussianDistribution(double mean, double standardDeviation)
{
Mean = mean;
StandardDeviation = standardDeviation;
Variance = Square(StandardDeviation);
Precision = 1.0/Variance;
PrecisionMean = Precision*Mean;
}
public double Mean { get; private set; }
public double StandardDeviation { get; private set; }
// Precision and PrecisionMean are used because they make multiplying and dividing simpler
// (the the accompanying math paper for more details)
public double Precision { get; private set; }
public double PrecisionMean { get; private set; }
private double Variance { get; set; }
public double NormalizationConstant
{
get
{
// Great derivation of this is at http://www.astro.psu.edu/~mce/A451_2/A451/downloads/notes0.pdf
return 1.0/(Math.Sqrt(2*Math.PI)*StandardDeviation);
}
}
public GaussianDistribution Clone()
{
var result = new GaussianDistribution();
result.Mean = Mean;
result.StandardDeviation = StandardDeviation;
result.Variance = Variance;
result.Precision = Precision;
result.PrecisionMean = PrecisionMean;
return result;
}
public static GaussianDistribution FromPrecisionMean(double precisionMean, double precision)
{
var gaussianDistribution = new GaussianDistribution();
gaussianDistribution.Precision = precision;
gaussianDistribution.PrecisionMean = precisionMean;
gaussianDistribution.Variance = 1.0/precision;
gaussianDistribution.StandardDeviation = Math.Sqrt(gaussianDistribution.Variance);
gaussianDistribution.Mean = gaussianDistribution.PrecisionMean/gaussianDistribution.Precision;
return gaussianDistribution;
}
// Although we could use equations from // For details, see http://www.tina-vision.net/tina-knoppix/tina-memo/2003-003.pdf
// for multiplication, the precision mean ones are easier to write :)
public static GaussianDistribution operator *(GaussianDistribution left, GaussianDistribution right)
{
return FromPrecisionMean(left.PrecisionMean + right.PrecisionMean, left.Precision + right.Precision);
}
/// Computes the absolute difference between two Gaussians
public static double AbsoluteDifference(GaussianDistribution left, GaussianDistribution right)
{
return Math.Max(
Math.Abs(left.PrecisionMean - right.PrecisionMean),
Math.Sqrt(Math.Abs(left.Precision - right.Precision)));
}
/// Computes the absolute difference between two Gaussians
public static double operator -(GaussianDistribution left, GaussianDistribution right)
{
return AbsoluteDifference(left, right);
}
public static double LogProductNormalization(GaussianDistribution left, GaussianDistribution right)
{
if ((left.Precision == 0) || (right.Precision == 0))
{
return 0;
}
double varianceSum = left.Variance + right.Variance;
double meanDifference = left.Mean - right.Mean;
double logSqrt2Pi = Math.Log(Math.Sqrt(2*Math.PI));
return -logSqrt2Pi - (Math.Log(varianceSum)/2.0) - (Square(meanDifference)/(2.0*varianceSum));
}
public static GaussianDistribution operator /(GaussianDistribution numerator, GaussianDistribution denominator)
{
return FromPrecisionMean(numerator.PrecisionMean - denominator.PrecisionMean,
numerator.Precision - denominator.Precision);
}
public static double LogRatioNormalization(GaussianDistribution numerator, GaussianDistribution denominator)
{
if ((numerator.Precision == 0) || (denominator.Precision == 0))
{
return 0;
}
double varianceDifference = denominator.Variance - numerator.Variance;
double meanDifference = numerator.Mean - denominator.Mean;
double logSqrt2Pi = Math.Log(Math.Sqrt(2*Math.PI));
return Math.Log(denominator.Variance) + logSqrt2Pi - Math.Log(varianceDifference)/2.0 +
Square(meanDifference)/(2*varianceDifference);
}
private static double Square(double x)
{
return x*x;
}
public static double At(double x)
{
return At(x, 0, 1);
}
public static double At(double x, double mean, double standardDeviation)
{
// See http://mathworld.wolfram.com/NormalDistribution.html
// 1 -(x-mean)^2 / (2*stdDev^2)
// P(x) = ------------------- * e
// stdDev * sqrt(2*pi)
double multiplier = 1.0/(standardDeviation*Math.Sqrt(2*Math.PI));
double expPart = Math.Exp((-1.0*Math.Pow(x - mean, 2.0))/(2*(standardDeviation*standardDeviation)));
double result = multiplier*expPart;
return result;
}
public static double CumulativeTo(double x, double mean, double standardDeviation)
{
double invsqrt2 = -0.707106781186547524400844362104;
double result = ErrorFunctionCumulativeTo(invsqrt2*x);
return 0.5*result;
}
public static double CumulativeTo(double x)
{
return CumulativeTo(x, 0, 1);
}
private static double ErrorFunctionCumulativeTo(double x)
{
// Derived from page 265 of Numerical Recipes 3rd Edition
double z = Math.Abs(x);
double t = 2.0/(2.0 + z);
double ty = 4*t - 2;
double[] coefficients = {
-1.3026537197817094, 6.4196979235649026e-1,
1.9476473204185836e-2, -9.561514786808631e-3, -9.46595344482036e-4,
3.66839497852761e-4, 4.2523324806907e-5, -2.0278578112534e-5,
-1.624290004647e-6, 1.303655835580e-6, 1.5626441722e-8, -8.5238095915e-8,
6.529054439e-9, 5.059343495e-9, -9.91364156e-10, -2.27365122e-10,
9.6467911e-11, 2.394038e-12, -6.886027e-12, 8.94487e-13, 3.13092e-13,
-1.12708e-13, 3.81e-16, 7.106e-15, -1.523e-15, -9.4e-17, 1.21e-16, -2.8e-17
};
int ncof = coefficients.Length;
double d = 0.0;
double dd = 0.0;
for (int j = ncof - 1; j > 0; j--)
{
double tmp = d;
d = ty*d - dd + coefficients[j];
dd = tmp;
}
double ans = t*Math.Exp(-z*z + 0.5*(coefficients[0] + ty*d) - dd);
return x >= 0.0 ? ans : (2.0 - ans);
}
private static double InverseErrorFunctionCumulativeTo(double p)
{
// From page 265 of numerical recipes
if (p >= 2.0)
{
return -100;
}
if (p <= 0.0)
{
return 100;
}
double pp = (p < 1.0) ? p : 2 - p;
double t = Math.Sqrt(-2*Math.Log(pp/2.0)); // Initial guess
double x = -0.70711*((2.30753 + t*0.27061)/(1.0 + t*(0.99229 + t*0.04481)) - t);
for (int j = 0; j < 2; j++)
{
double err = ErrorFunctionCumulativeTo(x) - pp;
x += err/(1.12837916709551257*Math.Exp(-(x*x)) - x*err); // Halley
}
return p < 1.0 ? x : -x;
}
public static double InverseCumulativeTo(double x, double mean, double standardDeviation)
{
// From numerical recipes, page 320
return mean - Math.Sqrt(2)*standardDeviation*InverseErrorFunctionCumulativeTo(2*x);
}
public static double InverseCumulativeTo(double x)
{
return InverseCumulativeTo(x, 0, 1);
}
public override string ToString()
{
// Debug help
return String.Format("μ={0:0.0000}, σ={1:0.0000}",
Mean,
StandardDeviation);
}
}
}

@ -1,524 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Moserware.Numerics
{
/// <summary>
/// Represents an MxN matrix with double precision values.
/// </summary>
internal class Matrix
{
// Anything smaller than this will be assumed to be rounding error in terms of equality matching
private const int FractionalDigitsToRoundTo = 10;
private static readonly double ErrorTolerance = Math.Pow(0.1, FractionalDigitsToRoundTo); // e.g. 1/10^10
protected double[][] _MatrixRowValues;
// Note: some properties like Determinant, Inverse, etc are properties instead
// of methods to make the syntax look nicer even though this sort of goes against
// Framework Design Guidelines that properties should be "cheap" since it could take
// a long time to compute these properties if the matrices are "big."
protected Matrix()
{
}
public Matrix(int rows, int columns, params double[] allRowValues)
{
Rows = rows;
Columns = columns;
_MatrixRowValues = new double[rows][];
int currentIndex = 0;
for (int currentRow = 0; currentRow < Rows; currentRow++)
{
_MatrixRowValues[currentRow] = new double[Columns];
for (int currentColumn = 0; currentColumn < Columns; currentColumn++)
{
if ((allRowValues != null) && (currentIndex < allRowValues.Length))
{
_MatrixRowValues[currentRow][currentColumn] = allRowValues[currentIndex++];
}
}
}
}
public Matrix(double[][] rowValues)
{
if (!rowValues.All(row => row.Length == rowValues[0].Length))
{
throw new ArgumentException("All rows must be the same length!");
}
Rows = rowValues.Length;
Columns = rowValues[0].Length;
_MatrixRowValues = rowValues;
}
protected Matrix(int rows, int columns, double[][] matrixRowValues)
{
Rows = rows;
Columns = columns;
_MatrixRowValues = matrixRowValues;
}
public Matrix(int rows, int columns, IEnumerable<IEnumerable<double>> columnValues)
: this(rows, columns)
{
int columnIndex = 0;
foreach (var currentColumn in columnValues)
{
int rowIndex = 0;
foreach (double currentColumnValue in currentColumn)
{
_MatrixRowValues[rowIndex++][columnIndex] = currentColumnValue;
}
columnIndex++;
}
}
public int Rows { get; protected set; }
public int Columns { get; protected set; }
public double this[int row, int column]
{
get { return _MatrixRowValues[row][column]; }
}
public Matrix Transpose
{
get
{
// Just flip everything
var transposeMatrix = new double[Columns][];
for (int currentRowTransposeMatrix = 0;
currentRowTransposeMatrix < Columns;
currentRowTransposeMatrix++)
{
var transposeMatrixCurrentRowColumnValues = new double[Rows];
transposeMatrix[currentRowTransposeMatrix] = transposeMatrixCurrentRowColumnValues;
for (int currentColumnTransposeMatrix = 0;
currentColumnTransposeMatrix < Rows;
currentColumnTransposeMatrix++)
{
transposeMatrixCurrentRowColumnValues[currentColumnTransposeMatrix] =
_MatrixRowValues[currentColumnTransposeMatrix][currentRowTransposeMatrix];
}
}
return new Matrix(Columns, Rows, transposeMatrix);
}
}
private bool IsSquare
{
get { return (Rows == Columns) && Rows > 0; }
}
public double Determinant
{
get
{
// Basic argument checking
if (!IsSquare)
{
throw new NotSupportedException("Matrix must be square!");
}
if (Rows == 1)
{
// Really happy path :)
return _MatrixRowValues[0][0];
}
if (Rows == 2)
{
// Happy path!
// Given:
// | a b |
// | c d |
// The determinant is ad - bc
double a = _MatrixRowValues[0][0];
double b = _MatrixRowValues[0][1];
double c = _MatrixRowValues[1][0];
double d = _MatrixRowValues[1][1];
return a*d - b*c;
}
// I use the Laplace expansion here since it's straightforward to implement.
// It's O(n^2) and my implementation is especially poor performing, but the
// core idea is there. Perhaps I should replace it with a better algorithm
// later.
// See http://en.wikipedia.org/wiki/Laplace_expansion for details
double result = 0.0;
// I expand along the first row
for (int currentColumn = 0; currentColumn < Columns; currentColumn++)
{
double firstRowColValue = _MatrixRowValues[0][currentColumn];
double cofactor = GetCofactor(0, currentColumn);
double itemToAdd = firstRowColValue*cofactor;
result += itemToAdd;
}
return result;
}
}
public Matrix Adjugate
{
get
{
if (!IsSquare)
{
throw new ArgumentException("Matrix must be square!");
}
// See http://en.wikipedia.org/wiki/Adjugate_matrix
if (Rows == 2)
{
// Happy path!
// Adjugate of:
// | a b |
// | c d |
// is
// | d -b |
// | -c a |
double a = _MatrixRowValues[0][0];
double b = _MatrixRowValues[0][1];
double c = _MatrixRowValues[1][0];
double d = _MatrixRowValues[1][1];
return new SquareMatrix(d, -b,
-c, a);
}
// The idea is that it's the transpose of the cofactors
var result = new double[Columns][];
for (int currentColumn = 0; currentColumn < Columns; currentColumn++)
{
result[currentColumn] = new double[Rows];
for (int currentRow = 0; currentRow < Rows; currentRow++)
{
result[currentColumn][currentRow] = GetCofactor(currentRow, currentColumn);
}
}
return new Matrix(result);
}
}
public Matrix Inverse
{
get
{
if ((Rows == 1) && (Columns == 1))
{
return new SquareMatrix(1.0/_MatrixRowValues[0][0]);
}
// Take the simple approach:
// http://en.wikipedia.org/wiki/Cramer%27s_rule#Finding_inverse_matrix
return (1.0/Determinant)*Adjugate;
}
}
public static Matrix operator *(double scalarValue, Matrix matrix)
{
int rows = matrix.Rows;
int columns = matrix.Columns;
var newValues = new double[rows][];
for (int currentRow = 0; currentRow < rows; currentRow++)
{
var newRowColumnValues = new double[columns];
newValues[currentRow] = newRowColumnValues;
for (int currentColumn = 0; currentColumn < columns; currentColumn++)
{
newRowColumnValues[currentColumn] = scalarValue*matrix._MatrixRowValues[currentRow][currentColumn];
}
}
return new Matrix(rows, columns, newValues);
}
public static Matrix operator +(Matrix left, Matrix right)
{
if ((left.Rows != right.Rows) || (left.Columns != right.Columns))
{
throw new ArgumentException("Matrices must be of the same size");
}
// simple addition of each item
var resultMatrix = new double[left.Rows][];
for (int currentRow = 0; currentRow < left.Rows; currentRow++)
{
var rowColumnValues = new double[right.Columns];
resultMatrix[currentRow] = rowColumnValues;
for (int currentColumn = 0; currentColumn < right.Columns; currentColumn++)
{
rowColumnValues[currentColumn] = left._MatrixRowValues[currentRow][currentColumn]
+
right._MatrixRowValues[currentRow][currentColumn];
}
}
return new Matrix(left.Rows, right.Columns, resultMatrix);
}
public static Matrix operator *(Matrix left, Matrix right)
{
// Just your standard matrix multiplication.
// See http://en.wikipedia.org/wiki/Matrix_multiplication for details
if (left.Columns != right.Rows)
{
throw new ArgumentException("The width of the left matrix must match the height of the right matrix",
"right");
}
int resultRows = left.Rows;
int resultColumns = right.Columns;
var resultMatrix = new double[resultRows][];
for (int currentRow = 0; currentRow < resultRows; currentRow++)
{
resultMatrix[currentRow] = new double[resultColumns];
for (int currentColumn = 0; currentColumn < resultColumns; currentColumn++)
{
double productValue = 0;
for (int vectorIndex = 0; vectorIndex < left.Columns; vectorIndex++)
{
double leftValue = left._MatrixRowValues[currentRow][vectorIndex];
double rightValue = right._MatrixRowValues[vectorIndex][currentColumn];
double vectorIndexProduct = leftValue*rightValue;
productValue += vectorIndexProduct;
}
resultMatrix[currentRow][currentColumn] = productValue;
}
}
return new Matrix(resultRows, resultColumns, resultMatrix);
}
private Matrix GetMinorMatrix(int rowToRemove, int columnToRemove)
{
// See http://en.wikipedia.org/wiki/Minor_(linear_algebra)
// I'm going to use a horribly naïve algorithm... because I can :)
var result = new double[Rows - 1][];
int resultRow = 0;
for (int currentRow = 0; currentRow < Rows; currentRow++)
{
if (currentRow == rowToRemove)
{
continue;
}
result[resultRow] = new double[Columns - 1];
int resultColumn = 0;
for (int currentColumn = 0; currentColumn < Columns; currentColumn++)
{
if (currentColumn == columnToRemove)
{
continue;
}
result[resultRow][resultColumn] = _MatrixRowValues[currentRow][currentColumn];
resultColumn++;
}
resultRow++;
}
return new Matrix(Rows - 1, Columns - 1, result);
}
private double GetCofactor(int rowToRemove, int columnToRemove)
{
// See http://en.wikipedia.org/wiki/Cofactor_(linear_algebra) for details
// REVIEW: should things be reversed since I'm 0 indexed?
int sum = rowToRemove + columnToRemove;
bool isEven = (sum%2 == 0);
if (isEven)
{
return GetMinorMatrix(rowToRemove, columnToRemove).Determinant;
}
else
{
return -1.0*GetMinorMatrix(rowToRemove, columnToRemove).Determinant;
}
}
// Equality stuff
// See http://msdn.microsoft.com/en-us/library/ms173147.aspx
public static bool operator ==(Matrix a, Matrix b)
{
// If both are null, or both are same instance, return true.
if (ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object) a == null) || ((object) b == null))
{
return false;
}
if ((a.Rows != b.Rows) || (a.Columns != b.Columns))
{
return false;
}
for (int currentRow = 0; currentRow < a.Rows; currentRow++)
{
for (int currentColumn = 0; currentColumn < a.Columns; currentColumn++)
{
double delta =
Math.Abs(a._MatrixRowValues[currentRow][currentColumn] -
b._MatrixRowValues[currentRow][currentColumn]);
if (delta > ErrorTolerance)
{
return false;
}
}
}
return true;
}
public static bool operator !=(Matrix a, Matrix b)
{
return !(a == b);
}
public override int GetHashCode()
{
double result = Rows;
result += 2*Columns;
unchecked
{
for (int currentRow = 0; currentRow < Rows; currentRow++)
{
bool eventRow = (currentRow%2) == 0;
double multiplier = eventRow ? 1.0 : 2.0;
for (int currentColumn = 0; currentColumn < Columns; currentColumn++)
{
double cellValue = _MatrixRowValues[currentRow][currentColumn];
double roundedValue = Math.Round(cellValue, FractionalDigitsToRoundTo);
result += multiplier*roundedValue;
}
}
}
// Ok, now convert that double to an int
byte[] resultBytes = BitConverter.GetBytes(result);
var finalBytes = new byte[4];
for (int i = 0; i < 4; i++)
{
finalBytes[i] = (byte) (resultBytes[i] ^ resultBytes[i + 4]);
}
int hashCode = BitConverter.ToInt32(finalBytes, 0);
return hashCode;
}
public override bool Equals(object obj)
{
var other = obj as Matrix;
if (other == null)
{
return base.Equals(obj);
}
return this == other;
}
}
internal class DiagonalMatrix : Matrix
{
public DiagonalMatrix(IList<double> diagonalValues)
: base(diagonalValues.Count, diagonalValues.Count)
{
for (int i = 0; i < diagonalValues.Count; i++)
{
_MatrixRowValues[i][i] = diagonalValues[i];
}
}
}
internal class Vector : Matrix
{
public Vector(IList<double> vectorValues)
: base(vectorValues.Count, 1, new IEnumerable<double>[] {vectorValues})
{
}
}
internal class SquareMatrix : Matrix
{
public SquareMatrix(params double[] allValues)
{
Rows = (int) Math.Sqrt(allValues.Length);
Columns = Rows;
int allValuesIndex = 0;
_MatrixRowValues = new double[Rows][];
for (int currentRow = 0; currentRow < Rows; currentRow++)
{
var currentRowValues = new double[Columns];
_MatrixRowValues[currentRow] = currentRowValues;
for (int currentColumn = 0; currentColumn < Columns; currentColumn++)
{
currentRowValues[currentColumn] = allValues[allValuesIndex++];
}
}
}
}
internal class IdentityMatrix : DiagonalMatrix
{
public IdentityMatrix(int rows)
: base(CreateDiagonal(rows))
{
}
private static double[] CreateDiagonal(int rows)
{
var result = new double[rows];
for (int i = 0; i < rows; i++)
{
result[i] = 1.0;
}
return result;
}
}
}

@ -1,49 +0,0 @@
using System;
namespace Moserware.Skills.Numerics
{
// The whole purpose of this class is to make the code for the SkillCalculator(s)
// look a little cleaner
public abstract class Range<T> where T : Range<T>, new()
{
private static readonly T _Instance = new T();
protected Range(int min, int max)
{
if (min > max)
{
throw new ArgumentOutOfRangeException();
}
Min = min;
Max = max;
}
public int Min { get; private set; }
public int Max { get; private set; }
protected abstract T Create(int min, int max);
// REVIEW: It's probably bad form to have access statics via a derived class, but the syntax looks better :-)
public static T Inclusive(int min, int max)
{
return _Instance.Create(min, max);
}
public static T Exactly(int value)
{
return _Instance.Create(value, value);
}
public static T AtLeast(int minimumValue)
{
return _Instance.Create(minimumValue, int.MaxValue);
}
public bool IsInRange(int value)
{
return (Min <= value) && (value <= Max);
}
}
}

@ -1,15 +0,0 @@
namespace Moserware.Skills
{
/// <summary>
/// Represents a comparison between two players.
/// </summary>
/// <remarks>
/// The actual values for the enum were chosen so that the also correspond to the multiplier for updates to means.
/// </remarks>
public enum PairwiseComparison
{
Win = 1,
Draw = 0,
Lose = -1
}
}

@ -1,26 +0,0 @@
namespace Moserware.Skills
{
internal static class PartialPlay
{
public static double GetPartialPlayPercentage(object player)
{
// If the player doesn't support the interface, assume 1.0 == 100%
var partialPlay = player as ISupportPartialPlay;
if (partialPlay == null)
{
return 1.0;
}
double partialPlayPercentage = partialPlay.PartialPlayPercentage;
// HACK to get around bug near 0
const double smallestPercentage = 0.0001;
if (partialPlayPercentage < smallestPercentage)
{
partialPlayPercentage = smallestPercentage;
}
return partialPlayPercentage;
}
}
}

@ -1,129 +0,0 @@
namespace Moserware.Skills
{
/// <summary>
/// Represents a player who has a <see cref="Rating"/>.
/// </summary>
public class Player<T> : ISupportPartialPlay, ISupportPartialUpdate
{
private const double DefaultPartialPlayPercentage = 1.0; // = 100% play time
private const double DefaultPartialUpdatePercentage = 1.0; // = receive 100% update
private readonly T _Id;
private readonly double _PartialPlayPercentage;
private readonly double _PartialUpdatePercentage;
/// <summary>
/// Constructs a player.
/// </summary>
/// <param name="id">The identifier for the player, such as a name.</param>
public Player(T id)
: this(id, DefaultPartialPlayPercentage, DefaultPartialUpdatePercentage)
{
}
/// <summary>
/// Constructs a player.
/// </summary>
/// <param name="id">The identifier for the player, such as a name.</param>
/// <param name="partialPlayPercentage">The weight percentage to give this player when calculating a new rank.</param>
public Player(T id, double partialPlayPercentage)
: this(id, partialPlayPercentage, DefaultPartialUpdatePercentage)
{
}
/// <summary>
/// Constructs a player.
/// </summary>
/// <param name="id">The identifier for the player, such as a name.</param>
/// <param name="partialPlayPercentage">The weight percentage to give this player when calculating a new rank.</param>
/// <param name="partialUpdatePercentage">/// Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.</param>
public Player(T id, double partialPlayPercentage, double partialUpdatePercentage)
{
// If they don't want to give a player an id, that's ok...
Guard.ArgumentInRangeInclusive(partialPlayPercentage, 0, 1.0, "partialPlayPercentage");
Guard.ArgumentInRangeInclusive(partialUpdatePercentage, 0, 1.0, "partialUpdatePercentage");
_Id = id;
_PartialPlayPercentage = partialPlayPercentage;
_PartialUpdatePercentage = partialUpdatePercentage;
}
/// <summary>
/// The identifier for the player, such as a name.
/// </summary>
public T Id
{
get { return _Id; }
}
#region ISupportPartialPlay Members
/// <summary>
/// Indicates the percent of the time the player should be weighted where 0.0 indicates the player didn't play and 1.0 indicates the player played 100% of the time.
/// </summary>
public double PartialPlayPercentage
{
get { return _PartialPlayPercentage; }
}
#endregion
#region ISupportPartialUpdate Members
/// <summary>
/// Indicated how much of a skill update a player should receive where 0.0 represents no update and 1.0 represents 100% of the update.
/// </summary>
public double PartialUpdatePercentage
{
get { return _PartialUpdatePercentage; }
}
#endregion
public override string ToString()
{
if (Id != null)
{
return Id.ToString();
}
return base.ToString();
}
}
/// <summary>
/// Represents a player who has a <see cref="Rating"/>.
/// </summary>
public class Player : Player<object>
{
/// <summary>
/// Constructs a player.
/// </summary>
/// <param name="id">The identifier for the player, such as a name.</param>
public Player(object id)
: base(id)
{
}
/// <summary>
/// Constructs a player.
/// </summary>
/// <param name="id">The identifier for the player, such as a name.</param>
/// <param name="partialPlayPercentage">The weight percentage to give this player when calculating a new rank.</param>
/// <param name="partialUpdatePercentage">Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.</param>
public Player(object id, double partialPlayPercentage)
: base(id, partialPlayPercentage)
{
}
/// <summary>
/// Constructs a player.
/// </summary>
/// <param name="id">The identifier for the player, such as a name.</param>
/// <param name="partialPlayPercentage">The weight percentage to give this player when calculating a new rank.</param>
/// <param name="partialUpdatePercentage">Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.</param>
public Player(object id, double partialPlayPercentage, double partialUpdatePercentage)
: base(id, partialPlayPercentage, partialUpdatePercentage)
{
}
}
}

@ -1,22 +0,0 @@
using Moserware.Skills.Numerics;
namespace Moserware.Skills
{
public class PlayersRange : Range<PlayersRange>
{
public PlayersRange()
: base(int.MinValue, int.MinValue)
{
}
private PlayersRange(int min, int max)
: base(min, max)
{
}
protected override PlayersRange Create(int min, int max)
{
return new PlayersRange(min, max);
}
}
}

@ -1,41 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Moserware.Skills")]
[assembly: AssemblyDescription("Implementation of the TrueSkill algorithm.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jeff Moser")]
[assembly: AssemblyProduct("TrueSkill Calculator")]
[assembly: AssemblyCopyright("Copyright © Jeff Moser 2010")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4326f9ed-f234-42ed-bee0-84f7757ab28f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("UnitTests")]

@ -1,58 +0,0 @@
Hi there!
Thanks for downloading this code and opening up this file. The goal of this
project is to provide an annotated reference implementation of Microsoft's
TrueSkill algorithm.
I describe the philosophy and the buildup of the math involved in my blog post
"Computing Your Skill" available at moserware.com.
In addition, there is a math paper that goes along with the blog post that explains
most of the more technical concepts.
This project isn't intended to win performance tests, it's meant to be read
and understood. If you see ways to improve its clarity, please submit a patch.
If you just want to use the TrueSkill algorithm, simply use the TrueSkillCalculator
class and enjoy. If you need examples, please look in the UnitTests\TrueSkill folder.
If you want to understand the inner workings of the algorithm and implement it
yourself, look in the Skills\TrueSkill folder. There are three separate
implementations of the algorithm in increasing levels of difficulty:
1. TwoPlayerTrueSkillCalculator.cs is the easiest to follow and implement. It uses
the simple equations directly from the TrueSkill website.
2. TwoTeamTrueSkillCalculator.cs is slightly more complicated than the two player
version and supports two teams that have at least one player each. It extends
the equations on the website and incorporates some things implied in the paper.
3. FactorGraphTrueSkillCalculator.cs is a wholly different animal than the first two
and it is at least an order of magnitude more complex. It implements the complete
TrueSkill algorithm and builds up a "factor graph" composed of several layers.
Each layer is composed of "factors", "variables", and "messages" between the two.
Work happens on the factor graph according to a "schedule" which can either be
a single step (e.g. sending a message from a factor to a variable) or a sequence of
steps (e.g. everything that happens in a "layer") or a loop where the schedule runs
until values start to stabilize (e.g. the bottom layer is approximated and runs until
it converges)
TrueSkill is more general than the popular Elo algorithm. As a comparison, I implemented
the Elo algorithm using the both the bell curve (Gaussian) and curve that the FIDE chess
league uses (logistic curve). I specifically implemented them in a way to show how the
only difference among these Elo implementations is the curve. I also implemented the
"duelling" Elo calculator as implied in the paper.
Everything else was implemented to support these classes. Note that a "player" can be an
arbitrary class. However, if that player class supports the "ISupportPartialPlay" or
"ISupportPartialUpdate" interfaces, you can add these extra parameters. The only calculator
that uses this info is the factor graph implementation. See those files for more details.
I use this code personally to rank around 45 people, so it's important that it's accurate.
Please let me know if you find errors. Bug fix patches are strongly encouraged! Also, feel
free to fork the project for different language implementations.
I'd love to hear from you via comments on the "Computing Your Skill" blog post.
Have fun and enjoy!
Jeff Moser <jeff@moserware.com>

@ -1,76 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace Moserware.Skills
{
/// <summary>
/// Helper class to sort ranks in non-decreasing order.
/// </summary>
internal static class RankSorter
{
/// <summary>
/// Performs an in-place sort of the <paramref name="items"/> in according to the <paramref name="ranks"/> in non-decreasing order.
/// </summary>
/// <typeparam name="T">The types of items to sort.</typeparam>
/// <param name="items">The items to sort according to the order specified by <paramref name="ranks"/>.</param>
/// <param name="ranks">The ranks for each item where 1 is first place.</param>
public static void Sort<T>(ref IEnumerable<T> teams, ref int[] teamRanks)
{
Guard.ArgumentNotNull(teams, "teams");
Guard.ArgumentNotNull(teamRanks, "teamRanks");
int lastObserverdRank = 0;
bool needToSort = false;
foreach (int currentRank in teamRanks)
{
// We're expecting ranks to go up (e.g. 1, 2, 2, 3, ...)
// If it goes down, then we've got to sort it.
if (currentRank < lastObserverdRank)
{
needToSort = true;
break;
}
lastObserverdRank = currentRank;
}
if (!needToSort)
{
// Don't bother doing more work, it's already in a good order
return;
}
// Get the existing items as an indexable list.
List<T> itemsInList = teams.ToList();
// item -> rank
var itemToRank = new Dictionary<T, int>();
for (int i = 0; i < itemsInList.Count; i++)
{
T currentItem = itemsInList[i];
int currentItemRank = teamRanks[i];
itemToRank[currentItem] = currentItemRank;
}
// Now we need a place for our results...
var sortedItems = new T[teamRanks.Length];
var sortedRanks = new int[teamRanks.Length];
// where are we in the result?
int currentIndex = 0;
// Let LINQ-to-Objects to the actual sorting
foreach (var sortedKeyValuePair in itemToRank.OrderBy(pair => pair.Value))
{
sortedItems[currentIndex] = sortedKeyValuePair.Key;
sortedRanks[currentIndex++] = sortedKeyValuePair.Value;
}
// And we're done
teams = sortedItems;
teamRanks = sortedRanks;
}
}
}

@ -1,94 +0,0 @@
using System;
using Moserware.Numerics;
namespace Moserware.Skills
{
/// <summary>
/// Container for a player's rating.
/// </summary>
public class Rating
{
private const int ConservativeStandardDeviationMultiplier = 3;
private readonly double _ConservativeStandardDeviationMultiplier;
private readonly double _Mean;
private readonly double _StandardDeviation;
/// <summary>
/// Constructs a rating.
/// </summary>
/// <param name="mean">The statistical mean value of the rating (also known as μ).</param>
/// <param name="standardDeviation">The standard deviation of the rating (also known as σ).</param>
public Rating(double mean, double standardDeviation)
: this(mean, standardDeviation, ConservativeStandardDeviationMultiplier)
{
}
/// <summary>
/// Constructs a rating.
/// </summary>
/// <param name="mean">The statistical mean value of the rating (also known as μ).</param>
/// <param name="standardDeviation">The standard deviation (the spread) of the rating (also known as σ).</param>
/// <param name="conservativeStandardDeviationMultiplier">The number of <paramref name="standardDeviation"/>s to subtract from the <paramref name="mean"/> to achieve a conservative rating.</param>
public Rating(double mean, double standardDeviation, double conservativeStandardDeviationMultiplier)
{
_Mean = mean;
_StandardDeviation = standardDeviation;
_ConservativeStandardDeviationMultiplier = conservativeStandardDeviationMultiplier;
}
/// <summary>
/// The statistical mean value of the rating (also known as μ).
/// </summary>
public double Mean
{
get { return _Mean; }
}
/// <summary>
/// The standard deviation (the spread) of the rating. This is also known as σ.
/// </summary>
public double StandardDeviation
{
get { return _StandardDeviation; }
}
/// <summary>
/// A conservative estimate of skill based on the mean and standard deviation.
/// </summary>
public double ConservativeRating
{
get { return _Mean - ConservativeStandardDeviationMultiplier*_StandardDeviation; }
}
public static Rating GetPartialUpdate(Rating prior, Rating fullPosterior, double updatePercentage)
{
var priorGaussian = new GaussianDistribution(prior.Mean, prior.StandardDeviation);
var posteriorGaussian = new GaussianDistribution(fullPosterior.Mean, fullPosterior.StandardDeviation);
// From a clarification email from Ralf Herbrich:
// "the idea is to compute a linear interpolation between the prior and posterior skills of each player
// ... in the canonical space of parameters"
double precisionDifference = posteriorGaussian.Precision - priorGaussian.Precision;
double partialPrecisionDifference = updatePercentage*precisionDifference;
double precisionMeanDifference = posteriorGaussian.PrecisionMean - priorGaussian.PrecisionMean;
double partialPrecisionMeanDifference = updatePercentage*precisionMeanDifference;
GaussianDistribution partialPosteriorGaussion = GaussianDistribution.FromPrecisionMean(
priorGaussian.PrecisionMean + partialPrecisionMeanDifference,
priorGaussian.Precision + partialPrecisionDifference);
return new Rating(partialPosteriorGaussion.Mean, partialPosteriorGaussion.StandardDeviation,
prior._ConservativeStandardDeviationMultiplier);
}
public override string ToString()
{
// As a debug helper, display a localized rating:
return String.Format(
"μ={0:0.0000}, σ={1:0.0000}",
Mean, StandardDeviation);
}
}
}

@ -1,93 +0,0 @@
using System;
using System.Collections.Generic;
namespace Moserware.Skills
{
/// <summary>
/// Base class for all skill calculator implementations.
/// </summary>
public abstract class SkillCalculator
{
[Flags]
public enum SupportedOptions
{
None = 0x00,
PartialPlay = 0x01,
PartialUpdate = 0x02,
}
private readonly SupportedOptions _SupportedOptions;
private readonly PlayersRange _PlayersPerTeamAllowed;
private readonly TeamsRange _TotalTeamsAllowed;
protected SkillCalculator(SupportedOptions supportedOptions, TeamsRange totalTeamsAllowed, PlayersRange playerPerTeamAllowed)
{
_SupportedOptions = supportedOptions;
_TotalTeamsAllowed = totalTeamsAllowed;
_PlayersPerTeamAllowed = playerPerTeamAllowed;
}
/// <summary>
/// Calculates new ratings based on the prior ratings and team ranks.
/// </summary>
/// <typeparam name="TPlayer">The underlying type of the player.</typeparam>
/// <param name="gameInfo">Parameters for the game.</param>
/// <param name="teams">A mapping of team players and their ratings.</param>
/// <param name="teamRanks">The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2)</param>
/// <returns>All the players and their new ratings.</returns>
public abstract IDictionary<TPlayer, Rating> CalculateNewRatings<TPlayer>(GameInfo gameInfo,
IEnumerable
<IDictionary<TPlayer, Rating>>
teams,
params int[] teamRanks);
/// <summary>
/// Calculates the match quality as the likelihood of all teams drawing.
/// </summary>
/// <typeparam name="TPlayer">The underlying type of the player.</typeparam>
/// <param name="gameInfo">Parameters for the game.</param>
/// <param name="teams">A mapping of team players and their ratings.</param>
/// <returns>The quality of the match between the teams as a percentage (0% = bad, 100% = well matched).</returns>
public abstract double CalculateMatchQuality<TPlayer>(GameInfo gameInfo,
IEnumerable<IDictionary<TPlayer, Rating>> teams);
public bool IsSupported(SupportedOptions option)
{
return (_SupportedOptions & option) == option;
}
/// <summary>
/// Helper function to square the <paramref name="value"/>.
/// </summary>
/// <returns><param name="value"/> * <param name="value"/></returns>
protected static double Square(double value)
{
return value*value;
}
protected void ValidateTeamCountAndPlayersCountPerTeam<TPlayer>(IEnumerable<IDictionary<TPlayer, Rating>> teams)
{
ValidateTeamCountAndPlayersCountPerTeam(teams, _TotalTeamsAllowed, _PlayersPerTeamAllowed);
}
private static void ValidateTeamCountAndPlayersCountPerTeam<TPlayer>(
IEnumerable<IDictionary<TPlayer, Rating>> teams, TeamsRange totalTeams, PlayersRange playersPerTeam)
{
Guard.ArgumentNotNull(teams, "teams");
int countOfTeams = 0;
foreach (var currentTeam in teams)
{
if (!playersPerTeam.IsInRange(currentTeam.Count))
{
throw new ArgumentException();
}
countOfTeams++;
}
if (!totalTeams.IsInRange(countOfTeams))
{
throw new ArgumentException();
}
}
}
}

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{15AD1345-984C-48ED-AF9A-2EAB44E5AA2B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Moserware.Skills</RootNamespace>
<AssemblyName>Moserware.Skills</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<StartupObject>
</StartupObject>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Elo\DuellingEloCalculator.cs" />
<Compile Include="Elo\GaussianKFactor.cs" />
<Compile Include="Elo\TwoPlayerEloCalculator.cs" />
<Compile Include="Elo\EloRating.cs" />
<Compile Include="Elo\FideKFactor.cs" />
<Compile Include="Elo\GaussianEloCalculator.cs" />
<Compile Include="Elo\KFactor.cs" />
<Compile Include="Numerics\Range.cs" />
<Compile Include="PlayersRange.cs" />
<Compile Include="TeamsRange.cs" />
<Compile Include="TrueSkill\DrawMargin.cs" />
<Compile Include="FactorGraphs\FactorGraph.cs" />
<Compile Include="FactorGraphs\FactorGraphLayer.cs" />
<Compile Include="FactorGraphs\FactorList.cs" />
<Compile Include="FactorGraphs\Message.cs" />
<Compile Include="FactorGraphs\Schedule.cs" />
<Compile Include="FactorGraphs\VariableFactory.cs" />
<Compile Include="TrueSkill\Factors\GaussianFactor.cs" />
<Compile Include="TrueSkill\Factors\GaussianGreaterThanFactor.cs" />
<Compile Include="TrueSkill\Factors\GaussianLikelihoodFactor.cs" />
<Compile Include="TrueSkill\Factors\GaussianPriorFactor.cs" />
<Compile Include="TrueSkill\Factors\GaussianWeightedSumFactor.cs" />
<Compile Include="TrueSkill\Factors\GaussianWithinFactor.cs" />
<Compile Include="Elo\FideEloCalculator.cs" />
<Compile Include="FactorGraphs\Factor.cs" />
<Compile Include="FactorGraphs\Variable.cs" />
<Compile Include="TrueSkill\FactorGraphTrueSkillCalculator.cs" />
<Compile Include="GameInfo.cs" />
<Compile Include="Numerics\GaussianDistribution.cs" />
<Compile Include="Guard.cs" />
<Compile Include="PartialPlay.cs" />
<Compile Include="SkillCalculator.cs" />
<Compile Include="ISupportPartialPlay.cs" />
<Compile Include="ISupportPartialUpdate.cs" />
<Compile Include="Numerics\Matrix.cs" />
<Compile Include="TrueSkillCalculator.cs" />
<Compile Include="TrueSkill\Layers\IteratedTeamDifferencesInnerLayer.cs" />
<Compile Include="TrueSkill\Layers\PlayerPerformancesToTeamPerformancesLayer.cs" />
<Compile Include="TrueSkill\Layers\PlayerPriorValuesToSkillsLayer.cs" />
<Compile Include="TrueSkill\Layers\PlayerSkillsToPerformancesLayer.cs" />
<Compile Include="TrueSkill\Layers\TeamDifferencesComparisonLayer.cs" />
<Compile Include="TrueSkill\Layers\TeamPerformancesToTeamPerformanceDifferencesLayer.cs" />
<Compile Include="TrueSkill\Layers\TrueSkillFactorGraphLayer.cs" />
<Compile Include="TrueSkill\TrueSkillFactorGraph.cs" />
<Compile Include="TrueSkill\TruncatedGaussianCorrectionFunctions.cs" />
<Compile Include="PairwiseComparison.cs" />
<Compile Include="Player.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rating.cs" />
<Compile Include="Team.cs" />
<Compile Include="RankSorter.cs" />
<Compile Include="TrueSkill\TwoPlayerTrueSkillCalculator.cs" />
<Compile Include="TrueSkill\TwoTeamTrueSkillCalculator.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="License.txt" />
<Content Include="README.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Binary file not shown.

@ -1,90 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace Moserware.Skills
{
/// <summary>
/// Helper class for working with a single team.
/// </summary>
public class Team<TPlayer>
{
private readonly Dictionary<TPlayer, Rating> _PlayerRatings = new Dictionary<TPlayer, Rating>();
/// <summary>
/// Constructs a new team.
/// </summary>
public Team()
{
}
/// <summary>
/// Constructs a <see cref="Team"/> and populates it with the specified <paramref name="player"/>.
/// </summary>
/// <param name="player">The player to add.</param>
/// <param name="rating">The rating of the <paramref name="player"/>.</param>
public Team(TPlayer player, Rating rating)
{
AddPlayer(player, rating);
}
/// <summary>
/// Adds the <paramref name="player"/> to the team.
/// </summary>
/// <param name="player">The player to add.</param>
/// <param name="rating">The rating of the <paramref name="player"/>.</param>
/// <returns>The instance of the team (for chaining convenience).</returns>
public Team<TPlayer> AddPlayer(TPlayer player, Rating rating)
{
_PlayerRatings[player] = rating;
return this;
}
/// <summary>
/// Returns the <see cref="Team"/> as a simple dictionary.
/// </summary>
/// <returns>The <see cref="Team"/> as a simple dictionary.</returns>
public IDictionary<TPlayer, Rating> AsDictionary()
{
return _PlayerRatings;
}
}
/// <summary>
/// Helper class for working with a single team.
/// </summary>
public class Team : Team<Player>
{
/// <summary>
/// Constructs a new team.
/// </summary>
public Team()
{
}
/// <summary>
/// Constructs a <see cref="Team"/> and populates it with the specified <paramref name="player"/>.
/// </summary>
/// <param name="player">The player to add.</param>
/// <param name="rating">The rating of the <paramref name="player"/>.</param>
public Team(Player player, Rating rating)
: base(player, rating)
{
}
}
/// <summary>
/// Helper class for working with multiple teams.
/// </summary>
public static class Teams
{
/// <summary>
/// Concatenates multiple teams into a list of teams.
/// </summary>
/// <param name="teams">The teams to concatenate together.</param>
/// <returns>A sequence of teams.</returns>
public static IEnumerable<IDictionary<TPlayer, Rating>> Concat<TPlayer>(params Team<TPlayer>[] teams)
{
return teams.Select(t => t.AsDictionary());
}
}
}

@ -1,22 +0,0 @@
using Moserware.Skills.Numerics;
namespace Moserware.Skills
{
public class TeamsRange : Range<TeamsRange>
{
public TeamsRange()
: base(int.MinValue, int.MinValue)
{
}
private TeamsRange(int min, int max)
: base(min, max)
{
}
protected override TeamsRange Create(int min, int max)
{
return new TeamsRange(min, max);
}
}
}

@ -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;
}
}
}

@ -1,45 +0,0 @@
using System.Collections.Generic;
using Moserware.Skills.TrueSkill;
namespace Moserware.Skills
{
/// <summary>
/// Calculates a TrueSkill rating using <see cref="FactorGraphTrueSkillCalculator"/>.
/// </summary>
public static class TrueSkillCalculator
{
// Keep a singleton around
private static readonly SkillCalculator _Calculator
= new FactorGraphTrueSkillCalculator();
/// <summary>
/// Calculates new ratings based on the prior ratings and team ranks.
/// </summary>
/// <param name="gameInfo">Parameters for the game.</param>
/// <param name="teams">A mapping of team players and their ratings.</param>
/// <param name="teamRanks">The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2)</param>
/// <returns>All the players and their new ratings.</returns>
public static IDictionary<TPlayer, Rating> CalculateNewRatings<TPlayer>(GameInfo gameInfo,
IEnumerable
<IDictionary<TPlayer, Rating>> teams,
params int[] teamRanks)
{
// Just punt the work to the full implementation
return _Calculator.CalculateNewRatings(gameInfo, teams, teamRanks);
}
/// <summary>
/// Calculates the match quality as the likelihood of all teams drawing.
/// </summary>
/// <typeparam name="TPlayer">The underlying type of the player.</typeparam>
/// <param name="gameInfo">Parameters for the game.</param>
/// <param name="teams">A mapping of team players and their ratings.</param>
/// <returns>The match quality as a percentage (between 0.0 and 1.0).</returns>
public static double CalculateMatchQuality<TPlayer>(GameInfo gameInfo,
IEnumerable<IDictionary<TPlayer, Rating>> teams)
{
// Just punt the work to the full implementation
return _Calculator.CalculateMatchQuality(gameInfo, teams);
}
}
}

@ -1,51 +0,0 @@
using Moserware.Skills;
using Moserware.Skills.Elo;
using NUnit.Framework;
namespace UnitTests.Elo
{
[TestFixture]
public class DuellingEloTest
{
private const double ErrorTolerance = 0.1;
[Test]
public void TwoOnTwoDuellingTest()
{
var calculator = new DuellingEloCalculator(new GaussianEloCalculator());
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating)
.AddPlayer(player2, gameInfo.DefaultRating);
var player3 = new Player(3);
var player4 = new Player(4);
var team2 = new Team()
.AddPlayer(player3, gameInfo.DefaultRating)
.AddPlayer(player4, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// TODO: Verify?
AssertRating(37, newRatingsWinLose[player1]);
AssertRating(37, newRatingsWinLose[player2]);
AssertRating(13, newRatingsWinLose[player3]);
AssertRating(13, newRatingsWinLose[player4]);
var quality = calculator.CalculateMatchQuality(gameInfo, teams);
Assert.AreEqual(1.0, quality, 0.001);
}
private static void AssertRating(double expected, Rating actual)
{
Assert.AreEqual(expected, actual.Mean, ErrorTolerance);
}
}
}

@ -1,37 +0,0 @@
using Moserware.Skills;
using Moserware.Skills.Elo;
using NUnit.Framework;
namespace UnitTests.Elo
{
internal static class EloAssert
{
private const double ErrorTolerance = 0.1;
public static void AssertChessRating(TwoPlayerEloCalculator calculator,
double player1BeforeRating,
double player2BeforeRating,
PairwiseComparison player1Result,
double player1AfterRating,
double player2AfterRating)
{
var player1 = new Player(1);
var player2 = new Player(2);
var teams = Teams.Concat(
new Team(player1, new EloRating(player1BeforeRating)),
new Team(player2, new EloRating(player2BeforeRating)));
var chessGameInfo = new GameInfo(1200, 0, 200, 0, 0);
var result = calculator.CalculateNewRatings(chessGameInfo, teams,
(player1Result == PairwiseComparison.Win) ? new[] { 1, 2 } :
(player1Result == PairwiseComparison.Lose) ? new[] { 2, 1 } :
new[] { 1, 1 });
Assert.AreEqual(player1AfterRating, result[player1].Mean, ErrorTolerance);
Assert.AreEqual(player2AfterRating, result[player2].Mean, ErrorTolerance);
}
}
}

@ -1,36 +0,0 @@
using Moserware.Skills;
using Moserware.Skills.Elo;
using NUnit.Framework;
namespace UnitTests.Elo
{
[TestFixture]
public class FideEloCalculatorTest
{
[Test]
public void FideProvisionalEloCalculatorTests()
{
// verified against http://ratings.fide.com/calculator_rtd.phtml
var calc = new FideEloCalculator(new FideKFactor.Provisional());
EloAssert.AssertChessRating(calc, 1200, 1500, PairwiseComparison.Win, 1221.25, 1478.75);
EloAssert.AssertChessRating(calc, 1200, 1500, PairwiseComparison.Draw, 1208.75, 1491.25);
EloAssert.AssertChessRating(calc, 1200, 1500, PairwiseComparison.Lose, 1196.25, 1503.75);
}
[Test]
public void FideNonProvisionalEloCalculatorTests()
{
// verified against http://ratings.fide.com/calculator_rtd.phtml
var calc = new FideEloCalculator();
EloAssert.AssertChessRating(calc, 1200, 1200, PairwiseComparison.Win, 1207.5, 1192.5);
EloAssert.AssertChessRating(calc, 1200, 1200, PairwiseComparison.Draw, 1200, 1200);
EloAssert.AssertChessRating(calc, 1200, 1200, PairwiseComparison.Lose, 1192.5, 1207.5);
EloAssert.AssertChessRating(calc, 2600, 2500, PairwiseComparison.Win, 2603.6, 2496.4);
EloAssert.AssertChessRating(calc, 2600, 2500, PairwiseComparison.Draw, 2598.6, 2501.4);
EloAssert.AssertChessRating(calc, 2600, 2500, PairwiseComparison.Lose, 2593.6, 2506.4);
}
}
}

@ -1,26 +0,0 @@
using Moserware.Skills;
using Moserware.Skills.Elo;
using NUnit.Framework;
namespace UnitTests.Elo
{
[TestFixture]
public class GaussianEloCalculatorTest
{
[Test]
public void GaussianEloCalculatorTests()
{
const double defaultKFactor = 24.0;
var calc = new GaussianEloCalculator();
EloAssert.AssertChessRating(calc, 1200, 1200, PairwiseComparison.Win, 1212, 1188);
EloAssert.AssertChessRating(calc, 1200, 1200, PairwiseComparison.Draw, 1200, 1200);
EloAssert.AssertChessRating(calc, 1200, 1200, PairwiseComparison.Lose, 1188, 1212);
// verified using TrueSkill paper equation
EloAssert.AssertChessRating(calc, 1200, 1000, PairwiseComparison.Win, 1200 + ((1 - 0.76024993890652326884) * defaultKFactor), 1000 - (1 - 0.76024993890652326884) * defaultKFactor);
EloAssert.AssertChessRating(calc, 1200, 1000, PairwiseComparison.Draw, 1200 - (0.76024993890652326884 - 0.5) * defaultKFactor, 1000 + (0.76024993890652326884 - 0.5) * defaultKFactor);
EloAssert.AssertChessRating(calc, 1200, 1000, PairwiseComparison.Lose, 1200 - 0.76024993890652326884 * defaultKFactor, 1000 + 0.76024993890652326884 * defaultKFactor);
}
}
}

@ -1,110 +0,0 @@
using System;
using Moserware.Numerics;
using NUnit.Framework;
namespace UnitTests.Numerics
{
[TestFixture]
public class GaussianDistributionTests
{
private const double ErrorTolerance = 0.000001;
[Test]
public void CumulativeToTests()
{
// Verified with WolframAlpha
// (e.g. http://www.wolframalpha.com/input/?i=CDF%5BNormalDistribution%5B0%2C1%5D%2C+0.5%5D )
Assert.AreEqual(0.691462, GaussianDistribution.CumulativeTo(0.5), ErrorTolerance);
}
[Test]
public void AtTests()
{
// Verified with WolframAlpha
// (e.g. http://www.wolframalpha.com/input/?i=PDF%5BNormalDistribution%5B0%2C1%5D%2C+0.5%5D )
Assert.AreEqual(0.352065, GaussianDistribution.At(0.5), ErrorTolerance);
}
[Test]
public void MultiplicationTests()
{
// I verified this against the formula at http://www.tina-vision.net/tina-knoppix/tina-memo/2003-003.pdf
var standardNormal = new GaussianDistribution(0, 1);
var shiftedGaussian = new GaussianDistribution(2, 3);
var product = standardNormal * shiftedGaussian;
Assert.AreEqual(0.2, product.Mean, ErrorTolerance);
Assert.AreEqual(3.0 / Math.Sqrt(10), product.StandardDeviation, ErrorTolerance);
var m4s5 = new GaussianDistribution(4, 5);
var m6s7 = new GaussianDistribution(6, 7);
var product2 = m4s5 * m6s7;
Func<double, double> square = x => x*x;
var expectedMean = (4 * square(7) + 6 * square(5)) / (square(5) + square(7));
Assert.AreEqual(expectedMean, product2.Mean, ErrorTolerance);
var expectedSigma = Math.Sqrt(((square(5) * square(7)) / (square(5) + square(7))));
Assert.AreEqual(expectedSigma, product2.StandardDeviation, ErrorTolerance);
}
[Test]
public void DivisionTests()
{
// Since the multiplication was worked out by hand, we use the same numbers but work backwards
var product = new GaussianDistribution(0.2, 3.0 / Math.Sqrt(10));
var standardNormal = new GaussianDistribution(0, 1);
var productDividedByStandardNormal = product / standardNormal;
Assert.AreEqual(2.0, productDividedByStandardNormal.Mean, ErrorTolerance);
Assert.AreEqual(3.0, productDividedByStandardNormal.StandardDeviation, ErrorTolerance);
Func<double, double> square = x => x * x;
var product2 = new GaussianDistribution((4 * square(7) + 6 * square(5)) / (square(5) + square(7)), Math.Sqrt(((square(5) * square(7)) / (square(5) + square(7)))));
var m4s5 = new GaussianDistribution(4,5);
var product2DividedByM4S5 = product2 / m4s5;
Assert.AreEqual(6.0, product2DividedByM4S5.Mean, ErrorTolerance);
Assert.AreEqual(7.0, product2DividedByM4S5.StandardDeviation, ErrorTolerance);
}
[Test]
public void LogProductNormalizationTests()
{
// Verified with Ralf Herbrich's F# implementation
var standardNormal = new GaussianDistribution(0, 1);
var lpn = GaussianDistribution.LogProductNormalization(standardNormal, standardNormal);
Assert.AreEqual(-1.2655121234846454, lpn, ErrorTolerance);
var m1s2 = new GaussianDistribution(1, 2);
var m3s4 = new GaussianDistribution(3, 4);
var lpn2 = GaussianDistribution.LogProductNormalization(m1s2, m3s4);
Assert.AreEqual(-2.5168046699816684, lpn2, ErrorTolerance);
}
[Test]
public void LogRatioNormalizationTests()
{
// Verified with Ralf Herbrich's F# implementation
var m1s2 = new GaussianDistribution(1, 2);
var m3s4 = new GaussianDistribution(3, 4);
var lrn = GaussianDistribution.LogRatioNormalization(m1s2, m3s4);
Assert.AreEqual(2.6157405972171204, lrn, ErrorTolerance);
}
[Test]
public void AbsoluteDifferenceTests()
{
// Verified with Ralf Herbrich's F# implementation
var standardNormal = new GaussianDistribution(0, 1);
var absDiff = GaussianDistribution.AbsoluteDifference(standardNormal, standardNormal);
Assert.AreEqual(0.0, absDiff, ErrorTolerance);
var m1s2 = new GaussianDistribution(1, 2);
var m3s4 = new GaussianDistribution(3, 4);
var absDiff2 = GaussianDistribution.AbsoluteDifference(m1s2, m3s4);
Assert.AreEqual(0.4330127018922193, absDiff2, ErrorTolerance);
}
}
}

@ -1,196 +0,0 @@
using Moserware.Numerics;
using NUnit.Framework;
namespace UnitTests.Numerics
{
[TestFixture]
public class MatrixTests
{
[Test]
public void TwoByTwoDeterminantTests()
{
var a = new SquareMatrix(1, 2,
3, 4);
Assert.AreEqual(-2, a.Determinant);
var b = new SquareMatrix(3, 4,
5, 6);
Assert.AreEqual(-2, b.Determinant);
var c = new SquareMatrix(1, 1,
1, 1);
Assert.AreEqual(0, c.Determinant);
var d = new SquareMatrix(12, 15,
17, 21);
Assert.AreEqual(12 * 21 - 15 * 17, d.Determinant);
}
[Test]
public void ThreeByThreeDeterminantTests()
{
var a = new SquareMatrix(1, 2, 3,
4, 5, 6,
7, 8, 9);
Assert.AreEqual(0, a.Determinant);
var π = new SquareMatrix(3, 1, 4,
1, 5, 9,
2, 6, 5);
// Verified against http://www.wolframalpha.com/input/?i=determinant+%7B%7B3%2C1%2C4%7D%2C%7B1%2C5%2C9%7D%2C%7B2%2C6%2C5%7D%7D
Assert.AreEqual(-90, π.Determinant);
}
[Test]
public void FourByFourDeterminantTests()
{
var a = new SquareMatrix( 1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16);
Assert.AreEqual(0, a.Determinant);
var π = new SquareMatrix(3, 1, 4, 1,
5, 9, 2, 6,
5, 3, 5, 8,
9, 7, 9, 3);
// Verified against http://www.wolframalpha.com/input/?i=determinant+%7B+%7B3%2C1%2C4%2C1%7D%2C+%7B5%2C9%2C2%2C6%7D%2C+%7B5%2C3%2C5%2C8%7D%2C+%7B9%2C7%2C9%2C3%7D%7D
Assert.AreEqual(98, π.Determinant);
}
[Test]
public void EightByEightDeterminantTests()
{
var a = new SquareMatrix( 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 32, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64);
Assert.AreEqual(0, a.Determinant);
var π = new SquareMatrix(3, 1, 4, 1, 5, 9, 2, 6,
5, 3, 5, 8, 9, 7, 9, 3,
2, 3, 8, 4, 6, 2, 6, 4,
3, 3, 8, 3, 2, 7, 9, 5,
0, 2, 8, 8, 4, 1, 9, 7,
1, 6, 9, 3, 9, 9, 3, 7,
5, 1, 0, 5, 8, 2, 0, 9,
7, 4, 9, 4, 4, 5, 9, 2);
// Verified against http://www.wolframalpha.com/input/?i=det+%7B%7B3%2C1%2C4%2C1%2C5%2C9%2C2%2C6%7D%2C%7B5%2C3%2C5%2C8%2C9%2C7%2C9%2C3%7D%2C%7B2%2C3%2C8%2C4%2C6%2C2%2C6%2C4%7D%2C%7B3%2C3%2C8%2C3%2C2%2C7%2C9%2C5%7D%2C%7B0%2C2%2C8%2C8%2C4%2C1%2C9%2C7%7D%2C%7B1%2C6%2C9%2C3%2C9%2C9%2C3%2C7%7D%2C%7B5%2C1%2C0%2C5%2C8%2C2%2C0%2C9%7D%2C%7B7%2C4%2C9%2C4%2C4%2C5%2C9%2C2%7D%7D
Assert.AreEqual(1378143, π.Determinant);
}
[Test]
public void EqualsTest()
{
var a = new SquareMatrix(1, 2,
3, 4);
var b = new SquareMatrix(1, 2,
3, 4);
Assert.IsTrue(a == b);
Assert.AreEqual(a, b);
var c = new Matrix(2, 3,
1, 2, 3,
4, 5, 6);
var d = new Matrix(2, 3,
1, 2, 3,
4, 5, 6);
Assert.IsTrue(c == d);
Assert.AreEqual(c, d);
var e = new Matrix(3, 2,
1, 4,
2, 5,
3, 6);
var f = e.Transpose;
Assert.IsTrue(d == f);
Assert.AreEqual(d, f);
Assert.AreEqual(d.GetHashCode(), f.GetHashCode());
// Test rounding (thanks to nsp on GitHub for finding this case)
var g = new SquareMatrix(1, 2.00000000000001,
3, 4);
var h = new SquareMatrix(1, 2,
3, 4);
Assert.IsTrue(g == h);
Assert.AreEqual(g, h);
Assert.AreEqual(g.GetHashCode(), h.GetHashCode());
}
[Test]
public void AdjugateTests()
{
// From Wikipedia: http://en.wikipedia.org/wiki/Adjugate_matrix
var a = new SquareMatrix(1, 2,
3, 4);
var b = new SquareMatrix( 4, -2,
-3, 1);
Assert.AreEqual(b, a.Adjugate);
var c = new SquareMatrix(-3, 2, -5,
-1, 0, -2,
3, -4, 1);
var d = new SquareMatrix(-8, 18, -4,
-5, 12, -1,
4, -6, 2);
Assert.AreEqual(d, c.Adjugate);
}
[Test]
public void InverseTests()
{
// see http://www.mathwords.com/i/inverse_of_a_matrix.htm
var a = new SquareMatrix(4, 3,
3, 2);
var b = new SquareMatrix(-2, 3,
3, -4);
var aInverse = a.Inverse;
Assert.AreEqual(b, aInverse);
var identity2x2 = new IdentityMatrix(2);
var aaInverse = a * aInverse;
Assert.IsTrue(identity2x2 == aaInverse);
var c = new SquareMatrix(1, 2, 3,
0, 4, 5,
1, 0, 6);
var cInverse = c.Inverse;
var d = (1.0 / 22) * new SquareMatrix(24, -12, -2,
5, 3, -5,
-4, 2, 4);
Assert.IsTrue(d == cInverse);
var identity3x3 = new IdentityMatrix(3);
var ccInverse = c * cInverse;
Assert.IsTrue(identity3x3 == ccInverse);
}
}
}

@ -1,35 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("UnitTests")]
[assembly: AssemblyDescription("Unit tests for Moserware.Skills")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jeff Moser")]
[assembly: AssemblyProduct("UnitTests")]
[assembly: AssemblyCopyright("Copyright © Jeff Moser 2010")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ddd7d430-f9c0-45c8-9576-70418d766e1f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

@ -1,23 +0,0 @@
These tests were written using NUnit 2.5.2 that is available for download
at:
http://sourceforge.net/projects/nunit/files/NUnit%20Version%202/NUnit-2.5.2.9222.msi/download
If you have a different version or setup, you'll need to update the path under
the UnitTests project properties by right clicking on UnitTests and then
click "properties" and then click the "debug" tab. The "start external program"
points to the NUnit test runner.
I did it this way so you didn't need more than the express version of
Visual Studio to run. If you have a fancy test runner already, feel
free to use that.
Additionally, it should be easy to update the tests to your tool
of choice.
Finally, realize that these tests test *all* of the calculators
implementations. For that reason, they create a new instance of
a particular calculator. If you're using this code in your application,
you can just use the convenience helper class of "TrueSkillCalculator"
that has static methods. If you do that, you won't have to worry
about creating your own instances.

@ -1,34 +0,0 @@
using System.Collections.Generic;
using Moserware.Skills;
using NUnit.Framework;
namespace UnitTests
{
[TestFixture]
public class RankSorterTest
{
[Test]
public void SortAlreadySortedTest()
{
IEnumerable<string> people = new[] { "One", "Two", "Three" };
int[] ranks = new[] { 1, 2, 3 };
RankSorter.Sort(ref people, ref ranks);
CollectionAssert.AreEqual(new[] { "One", "Two", "Three" }, people);
CollectionAssert.AreEqual(new[] { 1, 2, 3 }, ranks);
}
[Test]
public void SortUnsortedTest()
{
IEnumerable<string> people = new[] { "Five", "Two1", "Two2", "One", "Four" };
int[] ranks = new[] { 5, 2, 2, 1, 4 };
RankSorter.Sort(ref people, ref ranks);
CollectionAssert.AreEqual(new[] { "One", "Two1", "Two2", "Four", "Five" }, people);
CollectionAssert.AreEqual(new[] { 1, 2, 2, 4, 5 }, ranks);
}
}
}

@ -1,27 +0,0 @@
using Moserware.Skills.TrueSkill;
using NUnit.Framework;
namespace UnitTests.TrueSkill
{
[TestFixture]
public class DrawMarginTest
{
private const double ErrorTolerance = .000001;
[Test]
public void GetDrawMarginFromDrawProbabilityTest()
{
double beta = 25.0 / 6.0;
// The expected values were compared against Ralf Herbrich's implementation in F#
AssertDrawMargin(0.10, beta, 0.74046637542690541);
AssertDrawMargin(0.25, beta, 1.87760059883033);
AssertDrawMargin(0.33, beta, 2.5111010132487492);
}
private static void AssertDrawMargin(double drawProbability, double beta, double expected)
{
double actual = DrawMargin.GetDrawMarginFromDrawProbability(drawProbability, beta);
Assert.AreEqual(expected, actual, ErrorTolerance);
}
}
}

@ -1,22 +0,0 @@
using Moserware.Skills.TrueSkill;
using NUnit.Framework;
namespace UnitTests.TrueSkill
{
[TestFixture]
public class FactorGraphTrueSkillCalculatorTests
{
[Test]
public void FullFactorGraphCalculatorTests()
{
var calculator = new FactorGraphTrueSkillCalculator();
// We can test all classes
TrueSkillCalculatorTests.TestAllTwoPlayerScenarios(calculator);
TrueSkillCalculatorTests.TestAllTwoTeamScenarios(calculator);
TrueSkillCalculatorTests.TestAllMultipleTeamScenarios(calculator);
TrueSkillCalculatorTests.TestPartialPlayScenarios(calculator);
}
}
}

@ -1,987 +0,0 @@
using Moserware.Skills;
using NUnit.Framework;
namespace UnitTests.TrueSkill
{
public static class TrueSkillCalculatorTests
{
private const double ErrorTolerance = 0.085;
// These are the roll-up ones
public static void TestAllTwoPlayerScenarios(SkillCalculator calculator)
{
TwoPlayerTestNotDrawn(calculator);
TwoPlayerTestDrawn(calculator);
OneOnOneMassiveUpsetDrawTest(calculator);
TwoPlayerChessTestNotDrawn(calculator);
}
public static void TestAllTwoTeamScenarios(SkillCalculator calculator)
{
OneOnTwoSimpleTest(calculator);
OneOnTwoDrawTest(calculator);
OneOnTwoSomewhatBalanced(calculator);
OneOnThreeDrawTest(calculator);
OneOnThreeSimpleTest(calculator);
OneOnSevenSimpleTest(calculator);
TwoOnTwoSimpleTest(calculator);
TwoOnTwoUnbalancedDrawTest(calculator);
TwoOnTwoDrawTest(calculator);
TwoOnTwoUpsetTest(calculator);
ThreeOnTwoTests(calculator);
FourOnFourSimpleTest(calculator);
}
public static void TestAllMultipleTeamScenarios(SkillCalculator calculator)
{
ThreeTeamsOfOneNotDrawn(calculator);
ThreeTeamsOfOneDrawn(calculator);
FourTeamsOfOneNotDrawn(calculator);
FiveTeamsOfOneNotDrawn(calculator);
EightTeamsOfOneDrawn(calculator);
EightTeamsOfOneUpset(calculator);
SixteenTeamsOfOneNotDrawn(calculator);
TwoOnFourOnTwoWinDraw(calculator);
}
public static void TestPartialPlayScenarios(SkillCalculator calculator)
{
OneOnTwoBalancedPartialPlay(calculator);
}
//------------------- Actual Tests ---------------------------
// If you see more than 3 digits of precision in the decimal point, then the expected values calculated from
// F# RalfH's implementation with the same input. It didn't support teams, so team values all came from the
// online calculator at http://atom.research.microsoft.com/trueskill/rankcalculator.aspx
//
// All match quality expected values came from the online calculator
// In both cases, there may be some discrepancy after the first decimal point. I think this is due to my implementation
// using slightly higher precision in GaussianDistribution.
//------------------------------------------------------------------------------
// Two Player Tests
//------------------------------------------------------------------------------
private static void TwoPlayerTestNotDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
var player1NewRating = newRatings[player1];
AssertRating(29.39583201999924, 7.171475587326186, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(20.60416798000076, 7.171475587326186, player2NewRating);
AssertMatchQuality(0.447, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void TwoPlayerTestDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 1);
var player1NewRating = newRatings[player1];
AssertRating(25.0, 6.4575196623173081, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(25.0, 6.4575196623173081, player2NewRating);
AssertMatchQuality(0.447, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void TwoPlayerChessTestNotDrawn(SkillCalculator calculator)
{
// Inspired by a real bug :-)
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = new GameInfo(1200.0, 1200.0 / 3.0, 200.0, 1200.0 / 300.0, 0.03);
var team1 = new Team(player1, new Rating(1301.0007, 42.9232));
var team2 = new Team(player2, new Rating(1188.7560, 42.5570));
var newRatings = calculator.CalculateNewRatings(gameInfo, Teams.Concat(team1, team2), 1, 2);
var player1NewRating = newRatings[player1];
AssertRating(1304.7820836053318, 42.843513887848658, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(1185.0383099003536, 42.485604606897752, player2NewRating);
}
private static void OneOnOneMassiveUpsetDrawTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating);
var player2 = new Player(2);
var team2 = new Team()
.AddPlayer(player2, new Rating(50, 12.5));
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 1);
// Winners
AssertRating(31.662, 7.137, newRatingsWinLose[player1]);
// Losers
AssertRating(35.010, 7.910, newRatingsWinLose[player2]);
AssertMatchQuality(0.110, calculator.CalculateMatchQuality(gameInfo, teams));
}
//------------------------------------------------------------------------------
// Two Team Tests
//------------------------------------------------------------------------------
private static void TwoOnTwoSimpleTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating)
.AddPlayer(player2, gameInfo.DefaultRating);
var player3 = new Player(3);
var player4 = new Player(4);
var team2 = new Team()
.AddPlayer(player3, gameInfo.DefaultRating)
.AddPlayer(player4, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(28.108, 7.774, newRatingsWinLose[player1]);
AssertRating(28.108, 7.774, newRatingsWinLose[player2]);
// Losers
AssertRating(21.892, 7.774, newRatingsWinLose[player3]);
AssertRating(21.892, 7.774, newRatingsWinLose[player4]);
AssertMatchQuality(0.447, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void TwoOnTwoDrawTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating)
.AddPlayer(player2, gameInfo.DefaultRating);
var player3 = new Player(3);
var player4 = new Player(4);
var team2 = new Team()
.AddPlayer(player3, gameInfo.DefaultRating)
.AddPlayer(player4, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 1);
// Winners
AssertRating(25, 7.455, newRatingsWinLose[player1]);
AssertRating(25, 7.455, newRatingsWinLose[player2]);
// Losers
AssertRating(25, 7.455, newRatingsWinLose[player3]);
AssertRating(25, 7.455, newRatingsWinLose[player4]);
AssertMatchQuality(0.447, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void TwoOnTwoUnbalancedDrawTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, new Rating(15, 8))
.AddPlayer(player2, new Rating(20, 6));
var player3 = new Player(3);
var player4 = new Player(4);
var team2 = new Team()
.AddPlayer(player3, new Rating(25, 4))
.AddPlayer(player4, new Rating(30, 3));
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 1);
// Winners
AssertRating(21.570, 6.556, newRatingsWinLose[player1]);
AssertRating(23.696, 5.418, newRatingsWinLose[player2]);
// Losers
AssertRating(23.357, 3.833, newRatingsWinLose[player3]);
AssertRating(29.075, 2.931, newRatingsWinLose[player4]);
AssertMatchQuality(0.214, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void TwoOnTwoUpsetTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, new Rating(20, 8))
.AddPlayer(player2, new Rating(25, 6));
var player3 = new Player(3);
var player4 = new Player(4);
var team2 = new Team()
.AddPlayer(player3, new Rating(35, 7))
.AddPlayer(player4, new Rating(40, 5));
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(29.698, 7.008, newRatingsWinLose[player1]);
AssertRating(30.455, 5.594, newRatingsWinLose[player2]);
// Losers
AssertRating(27.575, 6.346, newRatingsWinLose[player3]);
AssertRating(36.211, 4.768, newRatingsWinLose[player4]);
AssertMatchQuality(0.084, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void FourOnFourSimpleTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating)
.AddPlayer(player2, gameInfo.DefaultRating)
.AddPlayer(player3, gameInfo.DefaultRating)
.AddPlayer(player4, gameInfo.DefaultRating);
var player5 = new Player(5);
var player6 = new Player(6);
var player7 = new Player(7);
var player8 = new Player(8);
var team2 = new Team()
.AddPlayer(player5, gameInfo.DefaultRating)
.AddPlayer(player6, gameInfo.DefaultRating)
.AddPlayer(player7, gameInfo.DefaultRating)
.AddPlayer(player8, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(27.198, 8.059, newRatingsWinLose[player1]);
AssertRating(27.198, 8.059, newRatingsWinLose[player2]);
AssertRating(27.198, 8.059, newRatingsWinLose[player3]);
AssertRating(27.198, 8.059, newRatingsWinLose[player4]);
// Losers
AssertRating(22.802, 8.059, newRatingsWinLose[player5]);
AssertRating(22.802, 8.059, newRatingsWinLose[player6]);
AssertRating(22.802, 8.059, newRatingsWinLose[player7]);
AssertRating(22.802, 8.059, newRatingsWinLose[player8]);
AssertMatchQuality(0.447, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void OneOnTwoSimpleTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating);
var player2 = new Player(2);
var player3 = new Player(3);
var team2 = new Team()
.AddPlayer(player2, gameInfo.DefaultRating)
.AddPlayer(player3, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(33.730, 7.317, newRatingsWinLose[player1]);
// Losers
AssertRating(16.270, 7.317, newRatingsWinLose[player2]);
AssertRating(16.270, 7.317, newRatingsWinLose[player3]);
AssertMatchQuality(0.135, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void OneOnTwoSomewhatBalanced(SkillCalculator calculator)
{
var player1 = new Player(1);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, new Rating(40, 6));
var player2 = new Player(2);
var player3 = new Player(3);
var team2 = new Team()
.AddPlayer(player2, new Rating(20, 7))
.AddPlayer(player3, new Rating(25, 8));
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(42.744, 5.602, newRatingsWinLose[player1]);
// Losers
AssertRating(16.266, 6.359, newRatingsWinLose[player2]);
AssertRating(20.123, 7.028, newRatingsWinLose[player3]);
AssertMatchQuality(0.478, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void OneOnThreeSimpleTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var team2 = new Team()
.AddPlayer(player2, gameInfo.DefaultRating)
.AddPlayer(player3, gameInfo.DefaultRating)
.AddPlayer(player4, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(36.337, 7.527, newRatingsWinLose[player1]);
// Losers
AssertRating(13.663, 7.527, newRatingsWinLose[player2]);
AssertRating(13.663, 7.527, newRatingsWinLose[player3]);
AssertRating(13.663, 7.527, newRatingsWinLose[player4]);
AssertMatchQuality(0.012, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void OneOnTwoDrawTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating);
var player2 = new Player(2);
var player3 = new Player(3);
var team2 = new Team()
.AddPlayer(player2, gameInfo.DefaultRating)
.AddPlayer(player3, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 1);
// Winners
AssertRating(31.660, 7.138, newRatingsWinLose[player1]);
// Losers
AssertRating(18.340, 7.138, newRatingsWinLose[player2]);
AssertRating(18.340, 7.138, newRatingsWinLose[player3]);
AssertMatchQuality(0.135, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void OneOnThreeDrawTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var team2 = new Team()
.AddPlayer(player2, gameInfo.DefaultRating)
.AddPlayer(player3, gameInfo.DefaultRating)
.AddPlayer(player4, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 1);
// Winners
AssertRating(34.990, 7.455, newRatingsWinLose[player1]);
// Losers
AssertRating(15.010, 7.455, newRatingsWinLose[player2]);
AssertRating(15.010, 7.455, newRatingsWinLose[player3]);
AssertRating(15.010, 7.455, newRatingsWinLose[player4]);
AssertMatchQuality(0.012, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void OneOnSevenSimpleTest(SkillCalculator calculator)
{
var player1 = new Player(1);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, gameInfo.DefaultRating);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var player5 = new Player(5);
var player6 = new Player(6);
var player7 = new Player(7);
var player8 = new Player(8);
var team2 = new Team()
.AddPlayer(player2, gameInfo.DefaultRating)
.AddPlayer(player3, gameInfo.DefaultRating)
.AddPlayer(player4, gameInfo.DefaultRating)
.AddPlayer(player5, gameInfo.DefaultRating)
.AddPlayer(player6, gameInfo.DefaultRating)
.AddPlayer(player7, gameInfo.DefaultRating)
.AddPlayer(player8, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(40.582, 7.917, newRatingsWinLose[player1]);
// Losers
AssertRating(9.418, 7.917, newRatingsWinLose[player2]);
AssertRating(9.418, 7.917, newRatingsWinLose[player3]);
AssertRating(9.418, 7.917, newRatingsWinLose[player4]);
AssertRating(9.418, 7.917, newRatingsWinLose[player5]);
AssertRating(9.418, 7.917, newRatingsWinLose[player6]);
AssertRating(9.418, 7.917, newRatingsWinLose[player7]);
AssertRating(9.418, 7.917, newRatingsWinLose[player8]);
AssertMatchQuality(0.000, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void ThreeOnTwoTests(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var team1 = new Team()
.AddPlayer(player1, new Rating(28, 7))
.AddPlayer(player2, new Rating(27, 6))
.AddPlayer(player3, new Rating(26, 5));
var player4 = new Player(4);
var player5 = new Player(5);
var team2 = new Team()
.AddPlayer(player4, new Rating(30, 4))
.AddPlayer(player5, new Rating(31, 3));
var gameInfo = GameInfo.DefaultGameInfo;
var teams = Teams.Concat(team1, team2);
var newRatingsWinLoseExpected = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
// Winners
AssertRating(28.658, 6.770, newRatingsWinLoseExpected[player1]);
AssertRating(27.484, 5.856, newRatingsWinLoseExpected[player2]);
AssertRating(26.336, 4.917, newRatingsWinLoseExpected[player3]);
// Losers
AssertRating(29.785, 3.958, newRatingsWinLoseExpected[player4]);
AssertRating(30.879, 2.983, newRatingsWinLoseExpected[player5]);
var newRatingsWinLoseUpset = calculator.CalculateNewRatings(gameInfo, Teams.Concat(team1, team2), 2, 1);
// Winners
AssertRating(32.012, 3.877, newRatingsWinLoseUpset[player4]);
AssertRating(32.132, 2.949, newRatingsWinLoseUpset[player5]);
// Losers
AssertRating(21.840, 6.314, newRatingsWinLoseUpset[player1]);
AssertRating(22.474, 5.575, newRatingsWinLoseUpset[player2]);
AssertRating(22.857, 4.757, newRatingsWinLoseUpset[player3]);
AssertMatchQuality(0.254, calculator.CalculateMatchQuality(gameInfo, teams));
}
//------------------------------------------------------------------------------
// Multiple Teams Tests
//------------------------------------------------------------------------------
private static void TwoOnFourOnTwoWinDraw(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team()
.AddPlayer(player1, new Rating(40,4))
.AddPlayer(player2, new Rating(45,3));
var player3 = new Player(3);
var player4 = new Player(4);
var player5 = new Player(5);
var player6 = new Player(6);
var team2 = new Team()
.AddPlayer(player3, new Rating(20, 7))
.AddPlayer(player4, new Rating(19, 6))
.AddPlayer(player5, new Rating(30, 9))
.AddPlayer(player6, new Rating(10, 4));
var player7 = new Player(7);
var player8 = new Player(8);
var team3 = new Team()
.AddPlayer(player7, new Rating(50,5))
.AddPlayer(player8, new Rating(30,2));
var teams = Teams.Concat(team1, team2, team3);
var newRatingsWinLose = calculator.CalculateNewRatings(gameInfo, teams, 1, 2, 2);
// Winners
AssertRating(40.877, 3.840, newRatingsWinLose[player1]);
AssertRating(45.493, 2.934, newRatingsWinLose[player2]);
AssertRating(19.609, 6.396, newRatingsWinLose[player3]);
AssertRating(18.712, 5.625, newRatingsWinLose[player4]);
AssertRating(29.353, 7.673, newRatingsWinLose[player5]);
AssertRating(9.872, 3.891, newRatingsWinLose[player6]);
AssertRating(48.830, 4.590, newRatingsWinLose[player7]);
AssertRating(29.813, 1.976, newRatingsWinLose[player8]);
AssertMatchQuality(0.367, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void ThreeTeamsOfOneNotDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var team3 = new Team(player3, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2, team3);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 2, 3);
var player1NewRating = newRatings[player1];
AssertRating(31.675352419172107, 6.6559853776206905, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(25.000000000003912, 6.2078966412243233, player2NewRating);
var player3NewRating = newRatings[player3];
AssertRating(18.324647580823971, 6.6559853776218318, player3NewRating);
AssertMatchQuality(0.200, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void ThreeTeamsOfOneDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var team3 = new Team(player3, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2, team3);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 1, 1);
var player1NewRating = newRatings[player1];
AssertRating(25.000, 5.698, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(25.000, 5.695, player2NewRating);
var player3NewRating = newRatings[player3];
AssertRating(25.000, 5.698, player3NewRating);
AssertMatchQuality(0.200, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void FourTeamsOfOneNotDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var team3 = new Team(player3, gameInfo.DefaultRating);
var team4 = new Team(player4, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2, team3, team4);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 2, 3, 4);
var player1NewRating = newRatings[player1];
AssertRating(33.206680965631264, 6.3481091698077057, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(27.401454693843323, 5.7871629348447584, player2NewRating);
var player3NewRating = newRatings[player3];
AssertRating(22.598545306188374, 5.7871629348413451, player3NewRating);
var player4NewRating = newRatings[player4];
AssertRating(16.793319034361271, 6.3481091698144967, player4NewRating);
AssertMatchQuality(0.089, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void FiveTeamsOfOneNotDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var player5 = new Player(5);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var team3 = new Team(player3, gameInfo.DefaultRating);
var team4 = new Team(player4, gameInfo.DefaultRating);
var team5 = new Team(player5, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2, team3, team4, team5);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 2, 3, 4, 5);
var player1NewRating = newRatings[player1];
AssertRating(34.363135705841188, 6.1361528798112692, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(29.058448805636779, 5.5358352402833413, player2NewRating);
var player3NewRating = newRatings[player3];
AssertRating(25.000000000031758, 5.4200805474429847, player3NewRating);
var player4NewRating = newRatings[player4];
AssertRating(20.941551194426314, 5.5358352402709672, player4NewRating);
var player5NewRating = newRatings[player5];
AssertRating(15.636864294158848, 6.136152879829349, player5NewRating);
AssertMatchQuality(0.040, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void EightTeamsOfOneDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var player5 = new Player(5);
var player6 = new Player(6);
var player7 = new Player(7);
var player8 = new Player(8);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var team3 = new Team(player3, gameInfo.DefaultRating);
var team4 = new Team(player4, gameInfo.DefaultRating);
var team5 = new Team(player5, gameInfo.DefaultRating);
var team6 = new Team(player6, gameInfo.DefaultRating);
var team7 = new Team(player7, gameInfo.DefaultRating);
var team8 = new Team(player8, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2, team3, team4, team5, team6, team7, team8);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 1, 1, 1, 1, 1, 1, 1);
var player1NewRating = newRatings[player1];
AssertRating(25.000, 4.592, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(25.000, 4.583, player2NewRating);
var player3NewRating = newRatings[player3];
AssertRating(25.000, 4.576, player3NewRating);
var player4NewRating = newRatings[player4];
AssertRating(25.000, 4.573, player4NewRating);
var player5NewRating = newRatings[player5];
AssertRating(25.000, 4.573, player5NewRating);
var player6NewRating = newRatings[player6];
AssertRating(25.000, 4.576, player6NewRating);
var player7NewRating = newRatings[player7];
AssertRating(25.000, 4.583, player7NewRating);
var player8NewRating = newRatings[player8];
AssertRating(25.000, 4.592, player8NewRating);
AssertMatchQuality(0.004, calculator.CalculateMatchQuality(gameInfo, teams));
}
private static void EightTeamsOfOneUpset(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var player5 = new Player(5);
var player6 = new Player(6);
var player7 = new Player(7);
var player8 = new Player(8);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, new Rating(10, 8));
var team2 = new Team(player2, new Rating(15, 7));
var team3 = new Team(player3, new Rating(20, 6));
var team4 = new Team(player4, new Rating(25, 5));
var team5 = new Team(player5, new Rating(30, 4));
var team6 = new Team(player6, new Rating(35, 3));
var team7 = new Team(player7, new Rating(40, 2));
var team8 = new Team(player8, new Rating(45, 1));
var teams = Teams.Concat(team1, team2, team3, team4, team5, team6, team7, team8);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 2, 3, 4, 5, 6, 7, 8);
var player1NewRating = newRatings[player1];
AssertRating(35.135, 4.506, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(32.585, 4.037, player2NewRating);
var player3NewRating = newRatings[player3];
AssertRating(31.329, 3.756, player3NewRating);
var player4NewRating = newRatings[player4];
AssertRating(30.984, 3.453, player4NewRating);
var player5NewRating = newRatings[player5];
AssertRating(31.751, 3.064, player5NewRating);
var player6NewRating = newRatings[player6];
AssertRating(34.051, 2.541, player6NewRating);
var player7NewRating = newRatings[player7];
AssertRating(38.263, 1.849, player7NewRating);
var player8NewRating = newRatings[player8];
AssertRating(44.118, 0.983, player8NewRating);
AssertMatchQuality(0.000, calculator.CalculateMatchQuality(gameInfo,teams));
}
private static void SixteenTeamsOfOneNotDrawn(SkillCalculator calculator)
{
var player1 = new Player(1);
var player2 = new Player(2);
var player3 = new Player(3);
var player4 = new Player(4);
var player5 = new Player(5);
var player6 = new Player(6);
var player7 = new Player(7);
var player8 = new Player(8);
var player9 = new Player(9);
var player10 = new Player(10);
var player11 = new Player(11);
var player12 = new Player(12);
var player13 = new Player(13);
var player14 = new Player(14);
var player15 = new Player(15);
var player16 = new Player(16);
var gameInfo = GameInfo.DefaultGameInfo;
var team1 = new Team(player1, gameInfo.DefaultRating);
var team2 = new Team(player2, gameInfo.DefaultRating);
var team3 = new Team(player3, gameInfo.DefaultRating);
var team4 = new Team(player4, gameInfo.DefaultRating);
var team5 = new Team(player5, gameInfo.DefaultRating);
var team6 = new Team(player6, gameInfo.DefaultRating);
var team7 = new Team(player7, gameInfo.DefaultRating);
var team8 = new Team(player8, gameInfo.DefaultRating);
var team9 = new Team(player9, gameInfo.DefaultRating);
var team10 = new Team(player10, gameInfo.DefaultRating);
var team11 = new Team(player11, gameInfo.DefaultRating);
var team12 = new Team(player12, gameInfo.DefaultRating);
var team13 = new Team(player13, gameInfo.DefaultRating);
var team14 = new Team(player14, gameInfo.DefaultRating);
var team15 = new Team(player15, gameInfo.DefaultRating);
var team16 = new Team(player16, gameInfo.DefaultRating);
var newRatings =
calculator.CalculateNewRatings(
gameInfo,
Teams.Concat(
team1, team2, team3, team4, team5,
team6, team7, team8, team9, team10,
team11, team12, team13, team14, team15,
team16),
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
var player1NewRating = newRatings[player1];
AssertRating(40.53945776946920, 5.27581643889050, player1NewRating);
var player2NewRating = newRatings[player2];
AssertRating(36.80951229454210, 4.71121217610266, player2NewRating);
var player3NewRating = newRatings[player3];
AssertRating(34.34726355544460, 4.52440328139991, player3NewRating);
var player4NewRating = newRatings[player4];
AssertRating(32.33614722608720, 4.43258628279632, player4NewRating);
var player5NewRating = newRatings[player5];
AssertRating(30.55048814671730, 4.38010805034365, player5NewRating);
var player6NewRating = newRatings[player6];
AssertRating(28.89277312234790, 4.34859291776483, player6NewRating);
var player7NewRating = newRatings[player7];
AssertRating(27.30952161972210, 4.33037679041216, player7NewRating);
var player8NewRating = newRatings[player8];
AssertRating(25.76571046519540, 4.32197078088701, player8NewRating);
var player9NewRating = newRatings[player9];
AssertRating(24.23428953480470, 4.32197078088703, player9NewRating);
var player10NewRating = newRatings[player10];
AssertRating(22.69047838027800, 4.33037679041219, player10NewRating);
var player11NewRating = newRatings[player11];
AssertRating(21.10722687765220, 4.34859291776488, player11NewRating);
var player12NewRating = newRatings[player12];
AssertRating(19.44951185328290, 4.38010805034375, player12NewRating);
var player13NewRating = newRatings[player13];
AssertRating(17.66385277391300, 4.43258628279643, player13NewRating);
var player14NewRating = newRatings[player14];
AssertRating(15.65273644455550, 4.52440328139996, player14NewRating);
var player15NewRating = newRatings[player15];
AssertRating(13.19048770545810, 4.71121217610273, player15NewRating);
var player16NewRating = newRatings[player16];
AssertRating(9.46054223053080, 5.27581643889032, player16NewRating);
}
//------------------------------------------------------------------------------
// Partial Play Tests
//------------------------------------------------------------------------------
private static void OneOnTwoBalancedPartialPlay(SkillCalculator calculator)
{
var gameInfo = GameInfo.DefaultGameInfo;
var p1 = new Player(1);
var team1 = new Team(p1, gameInfo.DefaultRating);
var p2 = new Player(2, 0.0);
var p3 = new Player(3, 1.00);
var team2 = new Team()
.AddPlayer(p2, gameInfo.DefaultRating)
.AddPlayer(p3, gameInfo.DefaultRating);
var teams = Teams.Concat(team1, team2);
var newRatings = calculator.CalculateNewRatings(gameInfo, teams, 1, 2);
var matchQuality = calculator.CalculateMatchQuality(gameInfo, teams);
}
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
private static void AssertRating(double expectedMean, double expectedStandardDeviation, Rating actual)
{
Assert.AreEqual(expectedMean, actual.Mean, ErrorTolerance);
Assert.AreEqual(expectedStandardDeviation, actual.StandardDeviation, ErrorTolerance);
}
private static void AssertMatchQuality(double expectedMatchQuality, double actualMatchQuality)
{
Assert.AreEqual(expectedMatchQuality, actualMatchQuality, 0.0005);
}
}
}

@ -1,20 +0,0 @@
using Moserware.Skills.TrueSkill;
using NUnit.Framework;
namespace UnitTests.TrueSkill
{
[TestFixture]
public class TwoPlayerTrueSkillCalculatorTest
{
[Test]
public void TwoPlayerTrueSkillCalculatorTests()
{
var calculator = new TwoPlayerTrueSkillCalculator();
// We only support two players
TrueSkillCalculatorTests.TestAllTwoPlayerScenarios(calculator);
// TODO: Assert failures for larger teams
}
}
}

@ -1,19 +0,0 @@
using Moserware.Skills.TrueSkill;
using NUnit.Framework;
namespace UnitTests.TrueSkill
{
[TestFixture]
public class TwoTeamTrueSkillCalculatorTest
{
[Test]
public void TwoTeamTrueSkillCalculatorTests()
{
var calculator = new TwoTeamTrueSkillCalculator();
// This calculator supports up to two teams with many players each
TrueSkillCalculatorTests.TestAllTwoPlayerScenarios(calculator);
TrueSkillCalculatorTests.TestAllTwoTeamScenarios(calculator);
}
}
}

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{6F80946D-AC8B-4063-8588-96841C18BF0A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UnitTests</RootNamespace>
<AssemblyName>UnitTests</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=2.5.2.9222, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL" />
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Elo\DuellingEloTest.cs" />
<Compile Include="Elo\EloAssert.cs" />
<Compile Include="Elo\GaussianEloCalculatorTest.cs" />
<Compile Include="TrueSkill\DrawMarginTest.cs" />
<Compile Include="TrueSkill\FactorGraphTrueSkillCalculatorTests.cs" />
<Compile Include="Elo\FideEloCalculatorTest.cs" />
<Compile Include="Numerics\GaussianDistributionTests.cs" />
<Compile Include="Numerics\MatrixTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RankSorterTest.cs" />
<Compile Include="TrueSkill\TrueSkillCalculatorTests.cs" />
<Compile Include="TrueSkill\TwoPlayerTrueSkillCalculatorTest.cs" />
<Compile Include="TrueSkill\TwoTeamTrueSkillCalculatorTest.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Skills\Skills.csproj">
<Project>{15AD1345-984C-48ED-AF9A-2EAB44E5AA2B}</Project>
<Name>Skills</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="README.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -1,9 +0,0 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<StartAction>Program</StartAction>
<StartProgram>C:\Program Files (x86)\NUnit 2.5.2\bin\net-2.0\nunit.exe</StartProgram>
<StartWorkingDirectory>
</StartWorkingDirectory>
<StartArguments>UnitTests.dll</StartArguments>
</PropertyGroup>
</Project>

@ -1,7 +0,0 @@
set(CMAKE_CSharp_COMPILER "@CMAKE_CSharp_COMPILER@")
set(CMAKE_CSharp_COMPILER_LOADED @CMAKE_CSharp_COMPILER_LOADED@)
set(GAC_DIR @GAC_DIR@)
set(_csc_default_lib_path @_csc_default_lib_path@ CACHE INTERNAL "")
set(CMAKE_CSharp_COMPILER_ENV_VAR "CSC")

@ -1,321 +0,0 @@
# copyright (c) 2007, 2009 Arno Rehn arno@arnorehn.de
# copyright (c) 2008 Helio castro helio@kde.org
#
# Redistribution and use is allowed according to the terms of the GPL license.
# This file adds support for the C# language to cmake.
#
# It adds the following functions:
#
# csharp_add_executable (<name> <source files> [UNSAFE] [WINEXE] [REFERENCES <references>]
# [COMPILE_FLAGS <flags to be passed to the compiler>]
# [COMPILE_DEFINITIONS <additional definitions>] )
#
# csharp_add_library (<name> <source files> [UNSAFE] [REFERENCES <references>]
# [COMPILE_FLAGS <flags to be passed to the compiler>]
# [COMPILE_DEFINITIONS <additional definitions>] )
#
# install_assembly (<target name> DESTINATION <assembly destination directory>
# [PACKAGE <package name>] )
# The assembly destination directory is only used if we compile with Visual C# and thus can't use gacutil.
# If a package is specified and a file called <target>.pc.cmake exists in the current source directory,
# this function will configure the template file. All occurences of @assembly@ will be replaced with
# the path to the assembly. The resulting <target>.pc file will be installed to
# <CMAKE_INSTALL_PREFIX>/lib/pkgconfig/ . If you want to have a different basename for the template file,
# set the 'pkg-config_template_basename' property of the target with set_property.
#
# Example:
# ------------------------------
# cmake code:
# ------------------------------
# csharp_add_library(foo foo.cs)
# install_assembly(foo DESTINATION lib)
#
# ------------------------------
# contents of foo.pc.cmake file:
# ------------------------------
# Name: Foo
# Description: Foo library
# Version: 1.0
# Libs: -r:@assembly@
# ----- support macros -----
macro(GET_LIBRARY_OUTPUT_DIR var)
if (NOT LIBRARY_OUTPUT_PATH)
set(${var} ${CMAKE_CURRENT_BINARY_DIR})
else (NOT LIBRARY_OUTPUT_PATH)
set(${var} ${LIBRARY_OUTPUT_PATH})
endif (NOT LIBRARY_OUTPUT_PATH)
endmacro(GET_LIBRARY_OUTPUT_DIR)
macro(GET_EXECUTABLE_OUTPUT_DIR var)
if (NOT EXECUTABLE_OUTPUT_PATH)
set(${var} ${CMAKE_CURRENT_BINARY_DIR})
else (NOT EXECUTABLE_OUTPUT_PATH)
set(${var} ${EXECUTABLE_OUTPUT_PATH})
endif (NOT EXECUTABLE_OUTPUT_PATH)
endmacro(GET_EXECUTABLE_OUTPUT_DIR)
# This does just not always work... why?!
# macro(MAKE_PROPER_FILE_LIST var)
# foreach(file ${ARGN})
# if (IS_ABSOLUTE "${file}")
# file(GLOB globbed "${file}")
# else (IS_ABSOLUTE "${file}")
# file(GLOB globbed "${CMAKE_CURRENT_SOURCE_DIR}/${file}")
# endif (IS_ABSOLUTE "${file}")
#
# foreach (glob ${globbed})
# file(TO_NATIVE_PATH "${glob}" native)
# list(APPEND proper_file_list "${native}")
# endforeach (glob ${globbed})
# endforeach(file ${ARGN})
# endmacro(MAKE_PROPER_FILE_LIST)
# ----- actual functions -----
# ----- add an executable -----
function(csharp_add_executable target)
set(current "s")
set(dotnet_target "exe")
foreach (arg ${ARGN})
file(TO_NATIVE_PATH ${arg} native_path)
if (arg STREQUAL "UNSAFE")
set (unsafe "/unsafe")
elseif (arg STREQUAL "WINEXE")
set (dotnet_target "winexe")
elseif (arg STREQUAL "REFERENCES")
set (current "r")
elseif (arg STREQUAL "COMPILE_FLAGS")
set (current "flags")
elseif (arg STREQUAL "COMPILE_DEFINITIONS")
set (current "defs")
else (arg STREQUAL "UNSAFE")
if (current STREQUAL "s")
# source file
list(APPEND sources ${native_path})
elseif (current STREQUAL "r")
# reference
if (TARGET ${arg})
# this is an existing target - get the target assembly
get_property(prop TARGET ${arg} PROPERTY _assembly)
list(APPEND references "/r:${prop}")
list(APPEND deps ${arg})
else (TARGET ${arg})
# something different (e.g. assembly name in the gac)
list(APPEND references "/r:${native_path}")
endif (TARGET ${arg})
elseif (current STREQUAL "flags")
list(APPEND _csc_opts "${arg}")
elseif (current STREQUAL "defs")
list(APPEND _csc_opts "/define:${arg}")
endif (current STREQUAL "s")
endif (arg STREQUAL "UNSAFE")
endforeach (arg ${ARGN})
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND _csc_opts "/define:DEBUG")
list(APPEND _csc_opts "/debug")
endif (CMAKE_BUILD_TYPE STREQUAL "Debug")
get_executable_output_dir(outdir)
if (NOT IS_ABSOLUTE "${outdir}")
message(FATAL_ERROR "Directory \"${outdir}\" is not an absolute path!")
endif (NOT IS_ABSOLUTE "${outdir}")
file(RELATIVE_PATH relative_path "${CMAKE_BINARY_DIR}" "${outdir}/${target}.exe")
file(TO_NATIVE_PATH "${outdir}/${target}" native_target)
# inlined - this doesn't work as a macro :(
foreach(file ${sources})
file(TO_CMAKE_PATH "${file}" cmake_file)
if (IS_ABSOLUTE "${cmake_file}")
file(GLOB globbed "${cmake_file}")
else (IS_ABSOLUTE "${cmake_file}")
file(GLOB globbed "${CMAKE_CURRENT_SOURCE_DIR}/${cmake_file}")
endif (IS_ABSOLUTE "${cmake_file}")
foreach (glob ${globbed})
file(TO_CMAKE_PATH "${glob}" cmake_path)
list(APPEND cmake_file_list "${cmake_path}")
endforeach (glob ${globbed})
if (NOT globbed)
list(APPEND cmake_file_list "${cmake_file}")
endif (NOT globbed)
list(APPEND compiler_file_list ${file})
endforeach(file ${sources})
get_directory_property(compile_definitions COMPILE_DEFINITIONS)
foreach (def ${compile_definitions})
# macros with values aren't supported by C#
if (NOT def MATCHES ".*=.*")
list(APPEND _csc_opts "/define:${def}")
endif (NOT def MATCHES ".*=.*")
endforeach (def ${compile_definitions})
get_directory_property(link_dirs LINK_DIRECTORIES)
foreach (dir ${link_dirs})
list(APPEND _csc_opts "/lib:${dir}")
endforeach (dir ${link_dirs})
add_custom_command(OUTPUT "${outdir}/${target}.stubexe"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${outdir}" # create the output dir
COMMAND "${CMAKE_CSharp_COMPILER}" /nologo /target:${dotnet_target} "/out:${native_target}.exe" # build the executable
${_csc_opts} ${unsafe} ${references} ${compiler_file_list}
COMMAND "${CMAKE_COMMAND}" -E touch "${outdir}/${target}.stubexe" # create the stub so that DEPENDS will work
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" # working directory is the source directory, so we don't have to care about relative paths
DEPENDS ${cmake_file_list}
COMMENT "Building ${relative_path}" VERBATIM) # nice comment
add_custom_target(${target} ALL DEPENDS "${outdir}/${target}.stubexe" SOURCES ${cmake_file_list}) # create the actual target
if (deps)
add_dependencies(${target} ${deps})
endif(deps)
endfunction(csharp_add_executable)
# ----- add a library -----
function(csharp_add_library target)
set(current "s")
foreach (arg ${ARGN})
file(TO_NATIVE_PATH ${arg} native_path)
if (arg STREQUAL "UNSAFE")
set (unsafe "/unsafe")
elseif (arg STREQUAL "REFERENCES")
set (current "r")
elseif (arg STREQUAL "COMPILE_FLAGS")
set (current "flags")
elseif (arg STREQUAL "COMPILE_DEFINITIONS")
set (current "defs")
else (arg STREQUAL "UNSAFE")
if (current STREQUAL "s")
# source file
list(APPEND sources ${native_path})
elseif (current STREQUAL "r")
# reference
if (TARGET ${arg})
# this is an existing target - get the target assembly
get_property(prop TARGET ${arg} PROPERTY _assembly)
list(APPEND references "/r:${prop}")
list(APPEND deps ${arg})
else (TARGET ${arg})
# something different (e.g. assembly name in the gac)
list(APPEND references "/r:${native_path}")
endif (TARGET ${arg})
elseif (current STREQUAL "flags")
list(APPEND _csc_opts "${arg}")
elseif (current STREQUAL "defs")
list(APPEND _csc_opts "/define:${arg}")
endif (current STREQUAL "s")
endif (arg STREQUAL "UNSAFE")
endforeach (arg ${ARGN})
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND _csc_opts "/define:DEBUG")
list(APPEND _csc_opts "/debug")
endif (CMAKE_BUILD_TYPE STREQUAL "Debug")
get_library_output_dir(outdir)
if (NOT IS_ABSOLUTE "${outdir}")
message(FATAL_ERROR "Directory \"${outdir}\" is not an absolute path!")
endif (NOT IS_ABSOLUTE "${outdir}")
file(RELATIVE_PATH relative_path "${CMAKE_BINARY_DIR}" "${outdir}/${target}.dll")
file(TO_NATIVE_PATH "${outdir}/${target}" native_target)
# inlined - this doesn't work as a macro :(
foreach(file ${sources})
file(TO_CMAKE_PATH "${file}" cmake_file)
if (IS_ABSOLUTE "${cmake_file}")
file(GLOB globbed "${cmake_file}")
else (IS_ABSOLUTE "${cmake_file}")
file(GLOB globbed "${CMAKE_CURRENT_SOURCE_DIR}/${cmake_file}")
endif (IS_ABSOLUTE "${cmake_file}")
foreach (glob ${globbed})
file(TO_CMAKE_PATH "${glob}" cmake_path)
list(APPEND cmake_file_list "${cmake_path}")
endforeach (glob ${globbed})
if (NOT globbed)
list(APPEND cmake_file_list "${cmake_file}")
endif (NOT globbed)
list(APPEND compiler_file_list ${file})
endforeach(file ${sources})
# message("CMake File List for target ${target}: ${cmake_file_list}")
get_directory_property(compile_definitions COMPILE_DEFINITIONS)
foreach (def ${compile_definitions})
# macros with values aren't supported by C#
if (NOT def MATCHES ".*=.*")
list(APPEND _csc_opts "/define:${def}")
endif (NOT def MATCHES ".*=.*")
endforeach (def ${compile_definitions})
get_directory_property(link_dirs LINK_DIRECTORIES)
foreach (dir ${link_dirs})
list(APPEND _csc_opts "/lib:${dir}")
endforeach (dir ${link_dirs})
add_custom_command(OUTPUT "${outdir}/${target}.dll"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${outdir}" # create the output dir
COMMAND "${CMAKE_CSharp_COMPILER}" /nologo /target:library "/out:${native_target}.dll" # build the executable
${_csc_opts} ${unsafe} ${references} ${compiler_file_list}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" # working directory is the source directory, so we don't have to care about relative paths
DEPENDS ${cmake_file_list}
COMMENT "Building ${relative_path}" VERBATIM) # nice comment
add_custom_target(${target} ALL DEPENDS "${outdir}/${target}.dll" SOURCES ${cmake_file_list}) # create the actual target
set_property(TARGET ${target} PROPERTY _assembly "${native_target}.dll")
if (deps)
add_dependencies(${target} ${deps})
endif(deps)
endfunction(csharp_add_library)
# ----- install a library assembly -----
function(install_assembly target DESTINATION destination_dir)
# retrieve the absolute path of the generated assembly
get_property(filename TARGET ${target} PROPERTY _assembly)
get_property(pc_file TARGET ${target} PROPERTY pkg-config_template_basename)
if (NOT pc_file)
set (pc_file ${target})
endif (NOT pc_file)
if (NOT filename)
message(FATAL_ERROR "Couldn't retrieve the assembly filename for target ${target}! Are you sure the target is a .NET library assembly?")
endif (NOT filename)
if (NOT MONO_FOUND)
install(FILES "${filename}" DESTINATION ${destination_dir})
if (EXISTS "${filename}.config")
install(FILES "${filename}.config" DESTINATION ${destination_dir})
endif (EXISTS "${filename}.config")
return()
endif (NOT MONO_FOUND)
if (ARGV3 STREQUAL "PACKAGE" AND ARGV4)
set (package_option "-package ${ARGV4}")
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${pc_file}.pc.cmake")
set(assembly "${GAC_DIR}/${ARGV4}/${target}.dll")
configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${pc_file}.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${pc_file}.pc")
if (NOT LIB_INSTALL_DIR)
set (LIB_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/lib)
endif (NOT LIB_INSTALL_DIR)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${pc_file}.pc" DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
endif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${pc_file}.pc.cmake")
endif (ARGV3 STREQUAL "PACKAGE" AND ARGV4)
# So we have the mono runtime and we can use gacutil (it has the -root option, which the MS version doesn't have).
install(CODE "execute_process(COMMAND ${GACUTIL_EXECUTABLE} -i ${filename} ${package_option} -root ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac)")
file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac/mono)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac/mono)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac/mono/ DESTINATION ${GAC_DIR} )
endfunction(install_assembly)
set(CMAKE_CSharp_INFORMATION_LOADED 1)

@ -1,85 +0,0 @@
# copyright (c) 2007, 2009 Arno Rehn arno@arnorehn.de
# copyright (c) 2008 Helio castro helio@kde.org
#
# Redistribution and use is allowed according to the terms of the GPL license.
# determine the compiler to use for C# programs
# NOTE, a generator may set CMAKE_CSharp_COMPILER before
# loading this file to force a compiler.
if(NOT CMAKE_CSharp_COMPILER)
# prefer the environment variable CSC
if($ENV{CSC} MATCHES ".+")
if (EXISTS $ENV{CSC})
message(STATUS "Found compiler set in environment variable CSC: $ENV{CSC}.")
set(CMAKE_CSharp_COMPILER $ENV{CSC})
else (EXISTS $ENV{CSC})
message(SEND_ERROR "Could not find compiler set in environment variable CSC:\n$ENV{CSC}.")
endif (EXISTS $ENV{CSC})
endif($ENV{CSC} MATCHES ".+")
# if no compiler has been specified yet, then look for one
if (NOT CMAKE_CSharp_COMPILER)
find_package(Mono)
set (CMAKE_CSharp_COMPILER "${GMCS_EXECUTABLE}")
# still not found, try csc.exe
if (NOT CMAKE_CSharp_COMPILER)
get_filename_component(dotnet_path "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\.NETFramework;InstallRoot]" PATH)
find_program(CMAKE_CSharp_COMPILER NAMES csc PATHS "${dotnet_path}/Framework/v2.0.50727")
file(TO_NATIVE_PATH "${dotnet_path}/Framework/v2.0.50727" native_path)
message(STATUS "Looking for csc: ${CMAKE_CSharp_COMPILER}")
# give up
if (NOT CMAKE_CSharp_COMPILER)
message (STATUS "Couldn't find a valid C# compiler. Set either CMake_CSharp_COMPILER or the CSC environment variable to a valid path.")
endif (NOT CMAKE_CSharp_COMPILER)
endif (NOT CMAKE_CSharp_COMPILER)
endif (NOT CMAKE_CSharp_COMPILER)
endif(NOT CMAKE_CSharp_COMPILER)
# now try to find the gac location
if (CMAKE_CSharp_COMPILER AND NOT GAC_DIR AND MONO_FOUND)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_search_module(MONO_CECIL mono-cecil)
if(MONO_CECIL_FOUND)
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} mono-cecil --variable=assemblies_dir OUTPUT_VARIABLE GAC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
endif(MONO_CECIL_FOUND)
pkg_search_module(CECIL cecil)
if(CECIL_FOUND)
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} cecil --variable=assemblies_dir OUTPUT_VARIABLE GAC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
endif(CECIL_FOUND)
if (NOT GAC_DIR)
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} mono --variable=libdir OUTPUT_VARIABLE MONO_LIB_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
if (MONO_LIB_DIR)
set (GAC_DIR "${MONO_LIB_DIR}/mono")
message (STATUS "Could not find cecil, guessing GAC dir from mono prefix: ${GAC_DIR}")
endif (MONO_LIB_DIR)
endif (NOT GAC_DIR)
endif (PKG_CONFIG_FOUND)
if (NOT GAC_DIR)
set (GAC_DIR "/usr/lib/mono")
message(STATUS "Could not find cecil or mono. Using default GAC dir: ${GAC_DIR}")
endif (NOT GAC_DIR)
endif (CMAKE_CSharp_COMPILER AND NOT GAC_DIR AND MONO_FOUND)
# Create a cache entry so the user can modify this.
set(GAC_DIR "${GAC_DIR}" CACHE PATH "Location of the GAC")
message(STATUS "Using GAC dir: ${GAC_DIR}")
mark_as_advanced(CMAKE_CSharp_COMPILER)
if (CMAKE_CSharp_COMPILER)
set (CMAKE_CSharp_COMPILER_LOADED 1)
endif (CMAKE_CSharp_COMPILER)
# configure variables set in this file for fast reload later on
configure_file(${CMAKE_SOURCE_DIR}/cmake/modules/CMakeCSharpCompiler.cmake.in
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeCSharpCompiler.cmake IMMEDIATE @ONLY)
set(CMAKE_CSharp_COMPILER_ENV_VAR "CSC")

@ -1,36 +0,0 @@
# - Try to find the mono, mcs, gmcs and gacutil
#
# defines
#
# MONO_FOUND - system has mono, mcs, gmcs and gacutil
# MONO_PATH - where to find 'mono'
# GMCS_PATH - where to find 'gmcs'
# GACUTIL_PATH - where to find 'gacutil'
#
# copyright (c) 2007 Arno Rehn arno@arnorehn.de
#
# Redistribution and use is allowed according to the terms of the GPL license.
FIND_PROGRAM (MONO_EXECUTABLE mono)
FIND_PROGRAM (GMCS_EXECUTABLE gmcs)
FIND_PROGRAM (GACUTIL_EXECUTABLE gacutil)
SET (MONO_FOUND FALSE CACHE INTERNAL "")
IF (MONO_EXECUTABLE AND GMCS_EXECUTABLE AND GACUTIL_EXECUTABLE)
SET (MONO_FOUND TRUE CACHE INTERNAL "")
ENDIF (MONO_EXECUTABLE AND GMCS_EXECUTABLE AND GACUTIL_EXECUTABLE)
IF (NOT Mono_FIND_QUIETLY)
MESSAGE(STATUS "Path of mono: ${MONO_EXECUTABLE}")
MESSAGE(STATUS "Path of gmcs: ${GMCS_EXECUTABLE}")
MESSAGE(STATUS "Path of gacutil: ${GACUTIL_EXECUTABLE}")
ENDIF (NOT Mono_FIND_QUIETLY)
IF (NOT MONO_FOUND)
IF (Mono_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find one or more of the following programs: mono, gmcs, gacutil")
ENDIF (Mono_FIND_REQUIRED)
ENDIF (NOT MONO_FOUND)
MARK_AS_ADVANCED(MONO_EXECUTABLE GMCS_EXECUTABLE GACUTIL_EXECUTABLE)