mirror of
				https://github.com/furyfire/trueskill.git
				synced 2025-11-04 10:12:28 +01:00 
			
		
		
		
	Start of factor graph port. Things don't work yet, but a lot of syntax updates towards PHP
This commit is contained in:
		
							
								
								
									
										168
									
								
								PHPSkills/TrueSkill/FactorGraphTrueSkillCalculator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								PHPSkills/TrueSkill/FactorGraphTrueSkillCalculator.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Calculates TrueSkill using a full factor graph.
 | 
			
		||||
/// </summary>
 | 
			
		||||
class FactorGraphTrueSkillCalculator extends SkillCalculator
 | 
			
		||||
{
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct(SkillCalculatorSupportedOptions::PARTIAL_PLAY | SkillCalculatorSupportedOptions::PARTIAL_UPDATE, TeamsRange::atLeast(2), PlayersRange::atLeast(1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function CalculateNewRatings(GameInfo $gameInfo,
 | 
			
		||||
                                        array $teams,
 | 
			
		||||
                                        array $teamRanks)
 | 
			
		||||
    {
 | 
			
		||||
        Guard::argumentNotNull($gameInfo, "gameInfo");
 | 
			
		||||
        $this->validateTeamCountAndPlayersCountPerTeam($teams);
 | 
			
		||||
 | 
			
		||||
        RankSorter::sort($teams, $teamRanks);
 | 
			
		||||
 | 
			
		||||
        $factorGraph = new TrueSkillFactorGraph($gameInfo, $teams, $teamRanks);
 | 
			
		||||
        $factorGraph->buildGraph();
 | 
			
		||||
        $factorGraph->runSchedule();
 | 
			
		||||
 | 
			
		||||
        $probabilityOfOutcome = $factorGraph->getProbabilityOfRanking();
 | 
			
		||||
 | 
			
		||||
        return $factorGraph->getUpdatedRatings();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function calculateMatchQuality(GameInfo $gameInfo,
 | 
			
		||||
                                          array $teams)
 | 
			
		||||
    {
 | 
			
		||||
        // We need to create the A matrix which is the player team assigments.
 | 
			
		||||
        $teamAssignmentsList = $teams.ToList();
 | 
			
		||||
        $skillsMatrix = $this->getPlayerCovarianceMatrix($teamAssignmentsList);
 | 
			
		||||
        $meanVector = $this->getPlayerMeansVector($teamAssignmentsList);
 | 
			
		||||
        $meanVectorTranspose = $meanVector->getTranspose();
 | 
			
		||||
 | 
			
		||||
        $playerTeamAssignmentsMatrix = $this->createPlayerTeamAssignmentMatrix($teamAssignmentsList, $meanVector->getRowCount());
 | 
			
		||||
        $playerTeamAssignmentsMatrixTranspose = $playerTeamAssignmentsMatrix->getTranspose();
 | 
			
		||||
 | 
			
		||||
        $betaSquared = square($gameInfo->getBeta());
 | 
			
		||||
 | 
			
		||||
        $start = Matrix::multiply($meanVectorTranspose, $playerTeamAssignmentsMatrix);
 | 
			
		||||
        $aTa = Matrix::multiply(
 | 
			
		||||
                        Matrix::scalarMultiply($betaSquared,
 | 
			
		||||
                                               $playerTeamAssignmentsMatrixTranspose),
 | 
			
		||||
                        $playerTeamAssignmentsMatrix);
 | 
			
		||||
 | 
			
		||||
        $aTSA = Matrix::multiply(
 | 
			
		||||
                    Matrix::multiply($playerTeamAssignmentsMatrixTranspose, $skillsMatrix),
 | 
			
		||||
                    $playerTeamAssignmentsMatrix);
 | 
			
		||||
 | 
			
		||||
        $middle = Matrix::add($aTa, $aTSA);
 | 
			
		||||
 | 
			
		||||
        $middleInverse = $middle->getInverse();
 | 
			
		||||
 | 
			
		||||
        $end = Matrix::multiply($playerTeamAssignmentsMatrixTranspose, $meanVector);
 | 
			
		||||
 | 
			
		||||
        $expPartMatrix = Matrix::scalarMultiply(-0.5, (Matrix::multiply(Matrix::multiply($start, $middleInverse), $end)));
 | 
			
		||||
        $expPart = $expPartMatrix->getDeterminant();
 | 
			
		||||
 | 
			
		||||
        $sqrtPartNumerator = $aTa->getDeterminant();
 | 
			
		||||
        $sqrtPartDenominator = $middle->getDeterminant();
 | 
			
		||||
        $sqrtPart = $sqrtPartNumerator / $sqrtPartDenominator;
 | 
			
		||||
 | 
			
		||||
        $result = exp($expPart) * sqrt($sqrtPart);
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function getPlayerMeansVector(array $teamAssignmentsList)
 | 
			
		||||
    {
 | 
			
		||||
        // A simple vector of all the player means.
 | 
			
		||||
        return new Vector($this->getPlayerRatingValues($teamAssignmentsList,
 | 
			
		||||
                                                       function($rating) 
 | 
			
		||||
                                                       { 
 | 
			
		||||
                                                            return $rating->getMean();
 | 
			
		||||
                                                       }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function getPlayerCovarianceMatrix(array $teamAssignmentsList)
 | 
			
		||||
    {
 | 
			
		||||
        // This is a square matrix whose diagonal values represent the variance (square of standard deviation) of all
 | 
			
		||||
        // players.
 | 
			
		||||
        return new DiagonalMatrix(
 | 
			
		||||
                        $this->getPlayerRatingValues($teamAssignmentsList,
 | 
			
		||||
                                                     function($rating)
 | 
			
		||||
                                                     {
 | 
			
		||||
                                                        return square($rating->getStandardDeviation());
 | 
			
		||||
                                                     }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helper function that gets a list of values for all player ratings
 | 
			
		||||
    private static function getPlayerRatingValues(array $teamAssignmentsList,
 | 
			
		||||
                                                  $playerRatingFunction)
 | 
			
		||||
    {
 | 
			
		||||
        $playerRatingValues = array();
 | 
			
		||||
 | 
			
		||||
        foreach ($teamAssignmentsList as $currentTeam)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (currentTeam.Values as $currentRating)
 | 
			
		||||
            {
 | 
			
		||||
                $playerRatingValues[] = $playerRatingFunction($currentRating);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $playerRatingValues;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function createPlayerTeamAssignmentMatrix($teamAssignmentsList, $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 |
 | 
			
		||||
 | 
			
		||||
        $playerAssignments = array();
 | 
			
		||||
        $totalPreviousPlayers = 0;
 | 
			
		||||
 | 
			
		||||
        $teamAssignmentsListCount = count($teamAssignmentsList);
 | 
			
		||||
 | 
			
		||||
        for ($i = 0; $i < $teamAssignmentsListCount - 1; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $currentTeam = $teamAssignmentsList[$i];
 | 
			
		||||
 | 
			
		||||
            // Need to add in 0's for all the previous players, since they're not
 | 
			
		||||
            // on this team
 | 
			
		||||
            $currentRowValues = array();
 | 
			
		||||
            $playerAssignments[] = &$currentRowValues;
 | 
			
		||||
 | 
			
		||||
            foreach ($currentTeam as $currentRating)
 | 
			
		||||
            {
 | 
			
		||||
                $currentRowValues[] = PartialPlay::getPartialPlayPercentage($currentRating->getKey());
 | 
			
		||||
                // indicates the player is on the team
 | 
			
		||||
                $totalPreviousPlayers++;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $nextTeam = $teamAssignmentsList[$i + 1];
 | 
			
		||||
            foreach ($nextTeam as $nextTeamPlayerPair)
 | 
			
		||||
            {
 | 
			
		||||
                // Add a -1 * playing time to represent the difference
 | 
			
		||||
                $currentRowValues[] = -1 * PartialPlay::getPartialPlayPercentage($nextTeamPlayerPair->getKey());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $playerTeamAssignmentsMatrix = new Matrix($totalPlayers, $teamAssignmentsListCount - 1, $playerAssignments);
 | 
			
		||||
 | 
			
		||||
        return $playerTeamAssignmentsMatrix;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										32
									
								
								PHPSkills/TrueSkill/Factors/GaussianFactor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								PHPSkills/TrueSkill/Factors/GaussianFactor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Factors;
 | 
			
		||||
 | 
			
		||||
abstract class GaussianFactor extends Factor
 | 
			
		||||
{
 | 
			
		||||
    protected function __construct($name)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sends the factor-graph message with and returns the log-normalization constant
 | 
			
		||||
    protected function sendMessageVariable(Message $message, Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        $marginal = $variable->getValue();
 | 
			
		||||
        $messageValue = $message->getValue();
 | 
			
		||||
        $logZ = GaussianDistribution::logProductNormalization($marginal, $messageValue);
 | 
			
		||||
        $variable->setValue($marginal*$messageValue);
 | 
			
		||||
        return $logZ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createVariableToMessageBinding(Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::createVariableToMessageBinding($variable,
 | 
			
		||||
                                                      new Message(
 | 
			
		||||
                                                          GaussianDistribution::fromPrecisionMean(0, 0),
 | 
			
		||||
                                                          "message from {0} to {1}", $this));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										73
									
								
								PHPSkills/TrueSkill/Factors/GaussianGreaterThanFactor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								PHPSkills/TrueSkill/Factors/GaussianGreaterThanFactor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
<?php
 | 
			
		||||
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>
 | 
			
		||||
class GaussianGreaterThanFactor extends GaussianFactor
 | 
			
		||||
{
 | 
			
		||||
    private $_epsilon;
 | 
			
		||||
 | 
			
		||||
    public function __construct($epsilon, Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        parent::_construct("{0} > {1:0.000}");
 | 
			
		||||
        $this->_epsilon = $epsilon;
 | 
			
		||||
        $this->createVariableToMessageBinding($variable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLogNormalization()
 | 
			
		||||
    {
 | 
			
		||||
        $vars = $this->getVariables();
 | 
			
		||||
        $marginal = $vars[0]->getValue();
 | 
			
		||||
        $messages = $this->getMessages();
 | 
			
		||||
        $message = $messages[0]->getValue();
 | 
			
		||||
        $messageFromVariable = GaussianDistribution::divide($marginal, $message);
 | 
			
		||||
        return -GaussianDistribution::logProductNormalization($messageFromVariable, $message)
 | 
			
		||||
               +
 | 
			
		||||
               log(
 | 
			
		||||
                   GaussianDistribution::cumulativeTo(($messageFromVariable->getMean() - $this->_epsilon)/
 | 
			
		||||
                                                     $messageFromVariable->getStandardDeviation()));
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function updateMessageVariable(Message $message, Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        $oldMarginal = clone $variable->getValue();
 | 
			
		||||
        $oldMessage = clone $message->getValue();
 | 
			
		||||
        $messageFromVar = GaussianDistribution::divide($oldMarginal, $oldMessage);
 | 
			
		||||
 | 
			
		||||
        $c = $messageFromVar->getPrecision();
 | 
			
		||||
        $d = $messageFromVar->getPrecisionMean();
 | 
			
		||||
 | 
			
		||||
        $sqrtC = sqrt($c);
 | 
			
		||||
 | 
			
		||||
        $dOnSqrtC = $d/$sqrtC;
 | 
			
		||||
 | 
			
		||||
        $epsilsonTimesSqrtC = $this->_epsilon*$sqrtC;
 | 
			
		||||
        $d = $messageFromVar->getPrecisionMean();
 | 
			
		||||
 | 
			
		||||
        $denom = 1.0 - TruncatedGaussianCorrectionFunctions::vExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC);
 | 
			
		||||
 | 
			
		||||
        $newPrecision = $c/$denom;
 | 
			
		||||
        $newPrecisionMean = ($d +
 | 
			
		||||
                             $sqrtC*
 | 
			
		||||
                             TruncatedGaussianCorrectionFunctions::vExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC))/
 | 
			
		||||
                             $denom;
 | 
			
		||||
 | 
			
		||||
        $newMarginal = GaussianDistribution::fromPrecisionMean($newPrecisionMean, $newPrecision);
 | 
			
		||||
 | 
			
		||||
        $newMessage = GaussianDistribution::divide(
 | 
			
		||||
                              GaussianDistribution::multiply($oldMessage, $newMarginal),
 | 
			
		||||
                              $oldMarginal);
 | 
			
		||||
 | 
			
		||||
        /// Update the message and marginal
 | 
			
		||||
        $message->setValue($newMessage);
 | 
			
		||||
 | 
			
		||||
        $variable->setValue($newMarginal);
 | 
			
		||||
 | 
			
		||||
        /// Return the difference in the new marginal
 | 
			
		||||
        return GaussianDistribution::subtract($newMarginal, $oldMarginal);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										78
									
								
								PHPSkills/TrueSkill/Factors/GaussianLikelihoodFactor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								PHPSkills/TrueSkill/Factors/GaussianLikelihoodFactor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Factors;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Connects two variables and adds uncertainty.
 | 
			
		||||
/// </summary>
 | 
			
		||||
/// <remarks>See the accompanying math paper for more details.</remarks>
 | 
			
		||||
class GaussianLikelihoodFactor extends GaussianFactor
 | 
			
		||||
{
 | 
			
		||||
    private $_precision;
 | 
			
		||||
 | 
			
		||||
    public function __construct($betaSquared, Variable $variable1, Variable $variable2)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct("Likelihood of {0} going to {1}");
 | 
			
		||||
        $this->_precision = 1.0/$betaSquared;
 | 
			
		||||
        $this->createVariableToMessageBinding($variable1);
 | 
			
		||||
        $this->createVariableToMessageBinding($variable2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLogNormalization()
 | 
			
		||||
    {
 | 
			
		||||
        $vars = $this->getVariables();
 | 
			
		||||
        $messages = $this->getMessages();
 | 
			
		||||
 | 
			
		||||
        return GaussianDistribution::logRatioNormalization(
 | 
			
		||||
                $vars[0]->getValue(),
 | 
			
		||||
                $messages[0]->getValue());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function updateHelper(Message $message1, Message $message2,
 | 
			
		||||
                                  Variable $variable1, Variable $variable2)
 | 
			
		||||
    {
 | 
			
		||||
        $message1Value = clone $message1->getValue();
 | 
			
		||||
        $message2Value = clone $message2->getValue();
 | 
			
		||||
 | 
			
		||||
        $marginal1 = clone $variable1->getValue();
 | 
			
		||||
        $marginal2 = clone $variable2->getValue();
 | 
			
		||||
 | 
			
		||||
        $a = $this->_precision/($this->_precision + $marginal2->getPrecision() - $message2Value->getPrecision());
 | 
			
		||||
 | 
			
		||||
        $newMessage = GaussianDistribution::fromPrecisionMean(
 | 
			
		||||
            $a*($marginal2->getPrecisionMean() - $message2Value->getPrecisionMean()),
 | 
			
		||||
            $a*($marginal2->getPrecision() - $message2Value->getPrecision()));
 | 
			
		||||
 | 
			
		||||
        $oldMarginalWithoutMessage = GaussianDistribution::divide($marginal1, $message1Value);
 | 
			
		||||
 | 
			
		||||
        $newMarginal = GaussianDistribution::multiply($oldMarginalWithoutMessage, $newMessage);
 | 
			
		||||
 | 
			
		||||
        /// Update the message and marginal
 | 
			
		||||
 | 
			
		||||
        $message1->setValue($newMessage);
 | 
			
		||||
        $variable1->setValue($newMarginal);
 | 
			
		||||
 | 
			
		||||
        /// Return the difference in the new marginal
 | 
			
		||||
        return GaussianDistribution::subtract($newMarginal, $marginal1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updateMessageIndex($messageIndex)
 | 
			
		||||
    {
 | 
			
		||||
        $messages = $this->getMessages();
 | 
			
		||||
        $vars = $this->getVariables();
 | 
			
		||||
 | 
			
		||||
        switch ($messageIndex)
 | 
			
		||||
        {
 | 
			
		||||
            case 0:
 | 
			
		||||
                return $this->updateHelper($messages[0], $messages[1],
 | 
			
		||||
                                           $vars[0], $vars[1]);
 | 
			
		||||
            case 1:
 | 
			
		||||
                return $this->updateHelper($messages[1], $messages[0],
 | 
			
		||||
                                           $vars[1], $vars[0]);
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Exception();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										40
									
								
								PHPSkills/TrueSkill/Factors/GaussianPriorFactor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								PHPSkills/TrueSkill/Factors/GaussianPriorFactor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Factors;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Supplies the factor graph with prior information.
 | 
			
		||||
/// </summary>
 | 
			
		||||
/// <remarks>See the accompanying math paper for more details.</remarks>
 | 
			
		||||
class GaussianPriorFactor extends GaussianFactor
 | 
			
		||||
{
 | 
			
		||||
    private $_newMessage;
 | 
			
		||||
 | 
			
		||||
    public function __construct($mean, $variance, Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct("Prior value going to {0}");
 | 
			
		||||
        $this->_newMessage = new GaussianDistribution($mean, sqrt($variance));
 | 
			
		||||
        $this->createVariableToMessageBinding($variable,
 | 
			
		||||
                                               new Message(
 | 
			
		||||
                                                   GaussianDistribution::fromPrecisionMean(0, 0),
 | 
			
		||||
                                                       "message from {0} to {1}",
 | 
			
		||||
                                                   this, variable));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function updateMessageVariable(Message $message, Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        $oldMarginal = clone $variable->getValue();
 | 
			
		||||
        $oldMessage = $message;
 | 
			
		||||
        $newMarginal =
 | 
			
		||||
            GaussianDistribution::fromPrecisionMean(
 | 
			
		||||
                $oldMarginal->getPrecisionMean() + $this->_newMessage->getPrecisionMean() - $oldMessage->getValue()->getPrecisionMean(),
 | 
			
		||||
                $oldMarginal->getPrecision() + $this->_newMessage->getPrecision() - $oldMessage->getValue()->getPrecision());
 | 
			
		||||
 | 
			
		||||
        $variable->setValue($newMarginal);
 | 
			
		||||
        $message->setValue($this->_newMessage);
 | 
			
		||||
        return GaussianDistribution::subtract($oldMarginal, $newMarginal);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										216
									
								
								PHPSkills/TrueSkill/Factors/GaussianWeightedSumFactor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								PHPSkills/TrueSkill/Factors/GaussianWeightedSumFactor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Factors;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Factor that sums together multiple Gaussians.
 | 
			
		||||
/// </summary>
 | 
			
		||||
/// <remarks>See the accompanying math paper for more details.</remarks>
 | 
			
		||||
class GaussianWeightedSumFactor extends GaussianFactor
 | 
			
		||||
{
 | 
			
		||||
    private $_variableIndexOrdersForWeights = array();
 | 
			
		||||
 | 
			
		||||
    // 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 $_weights;
 | 
			
		||||
    private $_weightsSquared;
 | 
			
		||||
 | 
			
		||||
    public function __construct(Variable $sumVariable, array $variablesToSum, array $variableWeights = null)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($this->createName($sumVariable, $variablesToSum, $variableWeights));
 | 
			
		||||
        $this->_weights = array();
 | 
			
		||||
        $this->_weightsSquared = array();
 | 
			
		||||
 | 
			
		||||
        // The first weights are a straightforward copy
 | 
			
		||||
        // v_0 = a_1*v_1 + a_2*v_2 + ... + a_n * v_n
 | 
			
		||||
        $this->_weights[0] = array();
 | 
			
		||||
 | 
			
		||||
        $variableWeightsLength = count($variableWeights);
 | 
			
		||||
 | 
			
		||||
        for($i = 0; $i < $variableWeightsLength; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $weight = $variableWeights[$i];
 | 
			
		||||
            $this->_weights[0][$i] = $weight;
 | 
			
		||||
            $this->_weightsSquared[0][i] = $weight * $weight;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $variablesToSumLength = count($variablesToSum);
 | 
			
		||||
 | 
			
		||||
        // 0..n-1
 | 
			
		||||
        for($i = 0; $i < ($variablesToSumLength + 1); $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $this->_variableIndexOrdersForWeights[] = $i;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 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
 | 
			
		||||
 | 
			
		||||
        $weightsLength = $variableWeightsLength + 1;
 | 
			
		||||
        for ($weightsIndex = 1; $weightsIndex < $weightsLength; $weightsIndex++)
 | 
			
		||||
        {            
 | 
			
		||||
            $this->_weights[$weightsIndex] = array();
 | 
			
		||||
 | 
			
		||||
            $variableIndices = array();
 | 
			
		||||
            $variableIndices[] = $weightsIndex;
 | 
			
		||||
 | 
			
		||||
            $currentWeightsSquared = array();
 | 
			
		||||
            $this->_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
 | 
			
		||||
            $currentDestinationWeightIndex = 0;
 | 
			
		||||
 | 
			
		||||
            for ($currentWeightSourceIndex = 0;
 | 
			
		||||
                 $currentWeightSourceIndex < $variableWeights.Length;
 | 
			
		||||
                 $currentWeightSourceIndex++)
 | 
			
		||||
            {
 | 
			
		||||
                if ($currentWeightSourceIndex == ($weightsIndex - 1))
 | 
			
		||||
                {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $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
 | 
			
		||||
            $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;
 | 
			
		||||
            $this->_variableIndexOrdersForWeights[] = $variableIndices;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->createVariableToMessageBinding($sumVariable);
 | 
			
		||||
 | 
			
		||||
        foreach ($variablesToSum as $currentVariable)
 | 
			
		||||
        {
 | 
			
		||||
            $this->createVariableToMessageBinding($currentVariable);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLogNormalization()
 | 
			
		||||
    {
 | 
			
		||||
        $vars = $this->getVariables();
 | 
			
		||||
        $messages = $this->getMessages();
 | 
			
		||||
 | 
			
		||||
        $result = 0.0;
 | 
			
		||||
 | 
			
		||||
        // We start at 1 since offset 0 has the sum
 | 
			
		||||
        $varCount = count($vars);
 | 
			
		||||
        for ($i = 1; $i < $varCount; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $result += GaussianDistribution::logRatioNormalization($vars[i]->getValue(), $messages[i]->getValue());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function updateHelper(array $weights, array $weightsSquared,
 | 
			
		||||
                                  array $messages,
 | 
			
		||||
                                  array $variables)
 | 
			
		||||
    {
 | 
			
		||||
        // Potentially look at http://mathworld.wolfram.com/NormalSumDistribution.html for clues as
 | 
			
		||||
        // to what it's doing
 | 
			
		||||
 | 
			
		||||
        $messages = $this->getMessages();
 | 
			
		||||
        $message0 = clone $messages[0]->getValue();
 | 
			
		||||
        $marginal0 = clone $variables[0]->getValue();
 | 
			
		||||
 | 
			
		||||
        // The math works out so that 1/newPrecision = sum of a_i^2 /marginalsWithoutMessages[i]
 | 
			
		||||
        $inverseOfNewPrecisionSum = 0.0;
 | 
			
		||||
        $anotherInverseOfNewPrecisionSum = 0.0;
 | 
			
		||||
        $weightedMeanSum = 0.0;
 | 
			
		||||
        $anotherWeightedMeanSum = 0.0;
 | 
			
		||||
 | 
			
		||||
        $weightsSquaredLength = count($weightsSquared);
 | 
			
		||||
 | 
			
		||||
        for ($i = 0; $i < $weightsSquaredLength; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            // These flow directly from the paper
 | 
			
		||||
 | 
			
		||||
            $inverseOfNewPrecisionSum += $weightsSquared[i]/
 | 
			
		||||
                                         ($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
 | 
			
		||||
 | 
			
		||||
            $diff = GaussianDistribution::divide($variables[$i + 1]->getValue(), $messages[$i + 1]->getValue());
 | 
			
		||||
            $anotherInverseOfNewPrecisionSum += $weightsSquared[i]/$diff->getPrecision();
 | 
			
		||||
 | 
			
		||||
            $weightedMeanSum += $weights[i]
 | 
			
		||||
                                *
 | 
			
		||||
                                ($variables[$i + 1]->getValue()->getPrecisionMean() - $messages[$i + 1]->getValue()->getPrecisionMean())
 | 
			
		||||
                                /
 | 
			
		||||
                                ($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
 | 
			
		||||
 | 
			
		||||
            $anotherWeightedMeanSum += $weights[$i]*$diff->getPrecisionMean()/$diff->getPrecision();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $newPrecision = 1.0/$inverseOfNewPrecisionSum;
 | 
			
		||||
        $anotherNewPrecision = 1.0/$anotherInverseOfNewPrecisionSum;
 | 
			
		||||
 | 
			
		||||
        $newPrecisionMean = $newPrecision*$weightedMeanSum;
 | 
			
		||||
        $anotherNewPrecisionMean = $anotherNewPrecision*$anotherWeightedMeanSum;
 | 
			
		||||
 | 
			
		||||
        $newMessage = GaussianDistribution::fromPrecisionMean($newPrecisionMean, $newPrecision);
 | 
			
		||||
        $oldMarginalWithoutMessage = GaussianDistribution::divide($marginal0, $message0);
 | 
			
		||||
 | 
			
		||||
        $newMarginal = GaussianDistribution::multiply($oldMarginalWithoutMessage, $newMessage);
 | 
			
		||||
 | 
			
		||||
        /// Update the message and marginal
 | 
			
		||||
 | 
			
		||||
        $messages[0]->setValue($newMessage);
 | 
			
		||||
        $variables[0]->setValue($newMarginal);
 | 
			
		||||
 | 
			
		||||
        /// Return the difference in the new marginal
 | 
			
		||||
        $finalDiff = GaussianDistribution::subtract($newMarginal, $marginal0);
 | 
			
		||||
        return $finalDiff;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updateMessageIndex($messageIndex)
 | 
			
		||||
    {
 | 
			
		||||
        $allMessages = $this->getMessages();
 | 
			
		||||
        $allVariables = $this->getVariables();
 | 
			
		||||
 | 
			
		||||
        Guard::argumentIsValidIndex($messageIndex, count($allMessages),"messageIndex");
 | 
			
		||||
 | 
			
		||||
        $updatedMessages = array();
 | 
			
		||||
        $updatedVariables = array();
 | 
			
		||||
 | 
			
		||||
        $indicesToUse = $this->_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
 | 
			
		||||
        $allMessagesCount = count($allMessages);
 | 
			
		||||
        for ($i = 0; i < $allMessagesCount; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $updatedMessages[] =$allMessages[$indicesToUse[$i]];
 | 
			
		||||
            $updatedVariables[] = $allVariables[$indicesToUse[$i]];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return updateHelper($this->_weights[$messageIndex],
 | 
			
		||||
                            $this->_weightsSquared[$messageIndex],
 | 
			
		||||
                            $updatedMessages,
 | 
			
		||||
                            $updatedVariables);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										72
									
								
								PHPSkills/TrueSkill/Factors/GaussianWithinFactor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								PHPSkills/TrueSkill/Factors/GaussianWithinFactor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
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>
 | 
			
		||||
class GaussianWithinFactor extends GaussianFactor
 | 
			
		||||
{
 | 
			
		||||
    private $_epsilon;
 | 
			
		||||
 | 
			
		||||
    public function __construct($epsilon, Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        $this->_epsilon = $epsilon;
 | 
			
		||||
        $this->createVariableToMessageBinding($variable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLogNormalization()
 | 
			
		||||
    {
 | 
			
		||||
        $variables = $this->getVariables();
 | 
			
		||||
        $marginal = $variables[0]->getValue();
 | 
			
		||||
 | 
			
		||||
        $messages = $this->getMessages();
 | 
			
		||||
        $message = $messages[0]->getValue();
 | 
			
		||||
        $messageFromVariable = GaussianDistribution::divide($marginal, $message);
 | 
			
		||||
        $mean = $messageFromVariable->getMean();
 | 
			
		||||
        $std = $messageFromVariable->getStandardDeviation();
 | 
			
		||||
        $z = GaussianDistribution::cumulativeTo(($this->_epsilon - $mean)/$std)
 | 
			
		||||
             -
 | 
			
		||||
             GaussianDistribution::cumulativeTo((-$this->_epsilon - $mean)/$std);
 | 
			
		||||
 | 
			
		||||
        return -GaussianDistribution::logProductNormalization($messageFromVariable, $message) + log($z);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function updateMessage(Message $message, Variable $variable)
 | 
			
		||||
    {
 | 
			
		||||
        $oldMarginal = clone $variable->getValue();
 | 
			
		||||
        $oldMessage = clone $message->getValue();
 | 
			
		||||
        $messageFromVariable = GaussianDistribution::divide($oldMarginal, $oldMessage);
 | 
			
		||||
 | 
			
		||||
        $c = $messageFromVariable->getPrecision();
 | 
			
		||||
        $d = $messageFromVariable->getPrecisionMean();
 | 
			
		||||
 | 
			
		||||
        $sqrtC = sqrt($c);
 | 
			
		||||
        $dOnSqrtC = $d/$sqrtC;
 | 
			
		||||
 | 
			
		||||
        $epsilonTimesSqrtC = $this->_epsilon*$sqrtC;
 | 
			
		||||
        $d = $messageFromVariable->getPrecisionMean();
 | 
			
		||||
 | 
			
		||||
        $denominator = 1.0 - TruncatedGaussianCorrectionFunctions::wWithinMargin($dOnSqrtC, $epsilonTimesSqrtC);
 | 
			
		||||
        $newPrecision = $c/$denominator;
 | 
			
		||||
        $newPrecisionMean = ($d +
 | 
			
		||||
                                   $sqrtC*
 | 
			
		||||
                                   TruncatedGaussianCorrectionFunctions::vWithinMargin($dOnSqrtC, $epsilonTimesSqrtC))/
 | 
			
		||||
                                  $denominator;
 | 
			
		||||
 | 
			
		||||
        $newMarginal = GaussianDistribution::fromPrecisionMean($newPrecisionMean, $newPrecision);
 | 
			
		||||
        $newMessage = GaussianDistribution::divide(
 | 
			
		||||
                        GaussianDistribution::multiply($oldMessage, $newMarginal),
 | 
			
		||||
                        $oldMarginal);
 | 
			
		||||
 | 
			
		||||
        /// Update the message and marginal
 | 
			
		||||
        $message->setValue($newMessage);
 | 
			
		||||
        $variable->setValue($newMarginal);
 | 
			
		||||
 | 
			
		||||
        /// Return the difference in the new marginal
 | 
			
		||||
        return GaussianDistribution::subtract($newMarginal, $oldMarginal);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										147
									
								
								PHPSkills/TrueSkill/Layers/IteratedTeamDifferencesInnerLayer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								PHPSkills/TrueSkill/Layers/IteratedTeamDifferencesInnerLayer.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Layers;
 | 
			
		||||
 | 
			
		||||
// The whole purpose of this is to do a loop on the bottom
 | 
			
		||||
class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
 | 
			
		||||
{
 | 
			
		||||
    private $_TeamDifferencesComparisonLayer;
 | 
			
		||||
    private $_TeamPerformancesToTeamPerformanceDifferencesLayer;
 | 
			
		||||
 | 
			
		||||
    public function __construct(TrueSkillFactorGraph $parentGraph,
 | 
			
		||||
                                TeamPerformancesToTeamPerformanceDifferencesLayer $teamPerformancesToPerformanceDifferences,
 | 
			
		||||
                                TeamDifferencesComparisonLayer $teamDifferencesComparisonLayer)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($parentGraph);
 | 
			
		||||
        $this->_TeamPerformancesToTeamPerformanceDifferencesLayer = $teamPerformancesToPerformanceDifferences;
 | 
			
		||||
        $this->_TeamDifferencesComparisonLayer = $teamDifferencesComparisonLayer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildLayer()
 | 
			
		||||
    {
 | 
			
		||||
        $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->setRawInputVariablesGroups($this->getInputVariablesGroups());
 | 
			
		||||
        $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->buildLayer();
 | 
			
		||||
 | 
			
		||||
        $this->_TeamDifferencesComparisonLayer->setRawInputVariablesGroups(
 | 
			
		||||
            $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getRawOutputVariablesGroups());
 | 
			
		||||
        $this->_TeamDifferencesComparisonLayer->buildLayer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createPriorSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        // BLOG about $loop
 | 
			
		||||
        switch (count($this->getInputVariablesGroups()))
 | 
			
		||||
        {
 | 
			
		||||
            case 0:
 | 
			
		||||
            case 1:
 | 
			
		||||
                throw new InvalidOperationException();
 | 
			
		||||
            case 2:
 | 
			
		||||
                $loop = $this->createTwoTeamInnerPriorLoopSchedule();
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                $loop = $this->createMultipleTeamInnerPriorLoopSchedule();
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // When dealing with differences, there are always (n-1) differences, so add in the 1
 | 
			
		||||
        $totalTeamDifferences = count($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
 | 
			
		||||
        $totalTeams = $totalTeamDifferences + 1;
 | 
			
		||||
 | 
			
		||||
        $innerSchedule = new ScheduleSequence(
 | 
			
		||||
            "inner schedule",
 | 
			
		||||
            array(
 | 
			
		||||
                    $loop,
 | 
			
		||||
                    new ScheduleStep(
 | 
			
		||||
                        "teamPerformanceToPerformanceDifferenceFactors[0] @ 1",
 | 
			
		||||
                        ($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors())[0], 1),
 | 
			
		||||
                    new ScheduleStep(
 | 
			
		||||
                        "teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = {0} - 1] @ 2",                                      
 | 
			
		||||
                        ($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors())[$totalTeamDifferences - 1], 2)
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        return innerSchedule;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createTwoTeamInnerPriorLoopSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->scheduleSequence(
 | 
			
		||||
            array(
 | 
			
		||||
                    new ScheduleStep(
 | 
			
		||||
                        "send team perf to perf differences",
 | 
			
		||||
                        ($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors())[0],
 | 
			
		||||
                        0),
 | 
			
		||||
                    new ScheduleStep(
 | 
			
		||||
                        "send to greater than or within factor",
 | 
			
		||||
                        ($this->_TeamDifferencesComparisonLayer->getLocalFactors())[0],
 | 
			
		||||
                        0)
 | 
			
		||||
                ),
 | 
			
		||||
            "loop of just two teams inner sequence");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createMultipleTeamInnerPriorLoopSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        $totalTeamDifferences = count($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
 | 
			
		||||
 | 
			
		||||
        $forwardScheduleList = array();
 | 
			
		||||
 | 
			
		||||
        for ($i = 0; $i < $totalTeamDifferences - 1; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $currentForwardSchedulePiece =
 | 
			
		||||
                $this->scheduleSequence(
 | 
			
		||||
                    array(
 | 
			
		||||
                            new ScheduleStep(
 | 
			
		||||
                                sprintf("team perf to perf diff %d", $i),
 | 
			
		||||
                                ($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors())[$i], 0),
 | 
			
		||||
                            new ScheduleStep(
 | 
			
		||||
                                sprintf("greater than or within result factor %d", $i),
 | 
			
		||||
                                ($this->_TeamDifferencesComparisonLayer->getLocalFactors())[$i], 0),
 | 
			
		||||
                            new ScheduleStep(
 | 
			
		||||
                                sprintf("team perf to perf diff factors [%d], 2", $i),
 | 
			
		||||
                                ($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors())[$i], 2)
 | 
			
		||||
                        ), sprintf("current forward schedule piece %d", $i);
 | 
			
		||||
 | 
			
		||||
            $forwardScheduleList[] = $currentForwardSchedulePiece;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $forwardSchedule = new ScheduleSequence("forward schedule", $forwardScheduleList);
 | 
			
		||||
 | 
			
		||||
        $backwardScheduleList = array();
 | 
			
		||||
 | 
			
		||||
        for ($i = 0; $i < $totalTeamDifferences - 1; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $currentBackwardSchedulePiece = new ScheduleSequence(
 | 
			
		||||
                "current backward schedule piece",
 | 
			
		||||
                array(
 | 
			
		||||
                        new ScheduleStep(
 | 
			
		||||
                            sprintf("teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 0", $i),
 | 
			
		||||
                            ($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors())[$totalTeamDifferences - 1 - $i], 0),
 | 
			
		||||
                        new ScheduleStep(
 | 
			
		||||
                            sprintf("greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - %d] @ 0", $i),
 | 
			
		||||
                            ($this->_TeamDifferencesComparisonLayer->getLocalFactors())[$totalTeamDifferences - 1 - $i], 0),
 | 
			
		||||
                        new ScheduleStep(
 | 
			
		||||
                            sprintf("teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 1", $i),
 | 
			
		||||
                            ($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors())[$totalTeamDifferences - 1 - $i], 1)
 | 
			
		||||
                );
 | 
			
		||||
            $backwardScheduleList[] = $currentBackwardSchedulePiece;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $backwardSchedule = new ScheduleSequence("backward schedule", $backwardScheduleList);
 | 
			
		||||
 | 
			
		||||
        $forwardBackwardScheduleToLoop =
 | 
			
		||||
            new ScheduleSequence(
 | 
			
		||||
                "forward Backward Schedule To Loop",
 | 
			
		||||
                array($forwardSchedule, $backwardSchedule));
 | 
			
		||||
 | 
			
		||||
        $initialMaxDelta = 0.0001;
 | 
			
		||||
 | 
			
		||||
        $loop = new ScheduleLoop(
 | 
			
		||||
            sprintf("loop with max delta of %f", $initialMaxDelta),
 | 
			
		||||
            $forwardBackwardScheduleToLoop,
 | 
			
		||||
            $initialMaxDelta);
 | 
			
		||||
 | 
			
		||||
        return $loop;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
@@ -0,0 +1,71 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Layers;
 | 
			
		||||
 | 
			
		||||
class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLayer
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(TrueSkillFactorGraph $parentGraph)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($parentGraph);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildLayer()
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->getInputVariablesGroups() as $currentTeam)
 | 
			
		||||
        {
 | 
			
		||||
            $teamPerformance = $this->createOutputVariable($currentTeam);
 | 
			
		||||
            $this->addLayerFactor($this->createPlayerToTeamSumFactor($currentTeam, $teamPerformance));
 | 
			
		||||
 | 
			
		||||
            // REVIEW: Does it make sense to have groups of one?
 | 
			
		||||
            $this->getOutputVariablesGroups() = $teamPerformance;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createPriorSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->scheduleSequence(
 | 
			
		||||
                array_map(
 | 
			
		||||
                        function($weightedSumFactor)
 | 
			
		||||
                        {
 | 
			
		||||
                            return new ScheduleStep("Perf to Team Perf Step", $weightedSumFactor, 0);
 | 
			
		||||
                        },
 | 
			
		||||
                        $this->getLocalFactors()),
 | 
			
		||||
                "all player perf to team perf schedule");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function createPlayerToTeamSumFactor($teamMembers, $sumVariable)
 | 
			
		||||
    {
 | 
			
		||||
        return new GaussianWeightedSumFactor(
 | 
			
		||||
                $sumVariable,
 | 
			
		||||
                $teamMembers,
 | 
			
		||||
                array_map(
 | 
			
		||||
                        function($v)
 | 
			
		||||
                        {
 | 
			
		||||
                            return PartialPlay::getPartialPlayPercentage($v->getKey());
 | 
			
		||||
                        },
 | 
			
		||||
                        $teamMembers));
 | 
			
		||||
                                                 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createPosteriorSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        // BLOG
 | 
			
		||||
        return $this->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 function createOutputVariable($team)
 | 
			
		||||
    {
 | 
			
		||||
        $teamMemberNames = String.Join(", ", team.Select(teamMember => teamMember.Key.ToString()).ToArray());
 | 
			
		||||
        return ParentFactorGraph.VariableFactory.CreateBasicVariable("Team[{0}]'s performance", teamMemberNames);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Layers;
 | 
			
		||||
 | 
			
		||||
// We intentionally have no Posterior schedule since the only purpose here is to
 | 
			
		||||
class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
 | 
			
		||||
{
 | 
			
		||||
    private $_teams;
 | 
			
		||||
 | 
			
		||||
    public function __construct(TrueSkillFactorGraph $parentGraph, $teams)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($parentGraph);
 | 
			
		||||
        $this->_teams = $teams;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildLayer()
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->_teams as $currentTeam)
 | 
			
		||||
        {
 | 
			
		||||
            $currentTeamSkills = array();
 | 
			
		||||
 | 
			
		||||
            foreach ($currentTeam as $currentTeamPlayer)
 | 
			
		||||
            {
 | 
			
		||||
                $playerSkill = $this->createSkillOutputVariable($currentTeamPlayer.Key);
 | 
			
		||||
                $this->addLayerFactor($this->createPriorFactor($currentTeamPlayer.Key, $currentTeamPlayer.Value, $playerSkill));
 | 
			
		||||
                $currentTeamSkills[] = $playerSkill;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            OutputVariablesGroups.Add(currentTeamSkills);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createPriorSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->scheduleSequence(
 | 
			
		||||
                array_map(
 | 
			
		||||
                        function($prior)
 | 
			
		||||
                        {
 | 
			
		||||
                            return new ScheduleStep("Prior to Skill Step", $prior, 0);
 | 
			
		||||
                        },
 | 
			
		||||
                        $this->getLocalFactors()),
 | 
			
		||||
                 "All priors");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createPriorFactor($player, $priorRating, $skillsVariable)
 | 
			
		||||
    {
 | 
			
		||||
        return new GaussianPriorFactor($priorRating->getMean(),
 | 
			
		||||
                                       square($priorRating->getStandardDeviation()) +
 | 
			
		||||
                                       square($this->getParentFactorGraph()->getGameInfo()->getDynamicsFactor()),
 | 
			
		||||
                                       $skillsVariable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createSkillOutputVariable($key)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getParentFactorGraph()->getVariableFactory()->createKeyedVariable($key, "{0}'s skill", $key);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Layers;
 | 
			
		||||
 | 
			
		||||
class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(TrueSkillFactorGraph $parentGraph)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($parentGraph);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildLayer()
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->getInputVariablesGroups() as $currentTeam)
 | 
			
		||||
        {
 | 
			
		||||
            $currentTeamPlayerPerformances = array();
 | 
			
		||||
 | 
			
		||||
            foreach ($currentTeam as $playerSkillVariable)
 | 
			
		||||
            {
 | 
			
		||||
                $playerPerformance = $this->createOutputVariable($playerSkillVariable->getKey());
 | 
			
		||||
                $this->addLayerFactor($this->createLikelihood($playerSkillVariable, $playerPerformance));
 | 
			
		||||
                $currentTeamPlayerPerformances[] = $playerPerformance;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->getOutputVariablesGroups()[] = $currentTeamPlayerPerformances;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createLikelihood($playerSkill, $playerPerformance)
 | 
			
		||||
    {
 | 
			
		||||
        return new GaussianLikelihoodFactor(square($this->getParentFactorGraph()->getGameInfo()->getBeta()), $playerPerformance, $playerSkill);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createOutputVariable($key)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getParentFactorGraph()->getVariableFactory()->createKeyedVariable($key, "{0}'s performance", $key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createPriorSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->scheduleSequence(
 | 
			
		||||
                array_map(
 | 
			
		||||
                        function($likelihood)
 | 
			
		||||
                        {
 | 
			
		||||
                            return $this->scheduleStep("Skill to Perf step", $likelihood, 0);
 | 
			
		||||
                        },
 | 
			
		||||
                        $this->getLocalFactors()),
 | 
			
		||||
                "All skill to performance sending");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createPosteriorSchedule()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->scheduleSequence(
 | 
			
		||||
                array_map(
 | 
			
		||||
                        function($likelihood)
 | 
			
		||||
                        {
 | 
			
		||||
                            return new ScheduleStep("name", $likelihood, 1);                    
 | 
			
		||||
                        },
 | 
			
		||||
                        $this->getLocalFactors()),
 | 
			
		||||
                "All skill to performance sending");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Layers;
 | 
			
		||||
 | 
			
		||||
class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
 | 
			
		||||
{
 | 
			
		||||
    private $_epsilon;
 | 
			
		||||
    private $_teamRanks;
 | 
			
		||||
 | 
			
		||||
    public function __construct(TrueSkillFactorGraph $parentGraph, array $teamRanks)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($parentGraph);
 | 
			
		||||
        $this->_teamRanks = $teamRanks;
 | 
			
		||||
        $gameInfo = $this->getParentFactorGraph()->getGameInfo();
 | 
			
		||||
        $this->_epsilon = DrawMargin::getDrawMarginFromDrawProbability($gameInfo->getDrawProbability(), $gameInfo->getBeta());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildLayer()
 | 
			
		||||
    {
 | 
			
		||||
        $inputVarGroups = $this->getInputVariablesGroups();
 | 
			
		||||
        $inputVarGroupsCount = count($inputVarGroups);
 | 
			
		||||
 | 
			
		||||
        for ($i = 0; $i < $inputVarGroupsCount; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $isDraw = ($this->_teamRanks[$i] == $this->_teamRanks[$i + 1]);
 | 
			
		||||
            $teamDifference = $inputVarGroups[$i][0];
 | 
			
		||||
 | 
			
		||||
            $factor =
 | 
			
		||||
                $isDraw
 | 
			
		||||
                    ? new GaussianWithinFactor($this->_epsilon, $teamDifference)
 | 
			
		||||
                    : new GaussianGreaterThanFactor($this->_epsilon, $teamDifference);
 | 
			
		||||
 | 
			
		||||
            $this->addLayerFactor($factor);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Layers;
 | 
			
		||||
 | 
			
		||||
class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorGraphLayer
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(TrueSkillFactorGraph $parentGraph)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($parentGraph);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildLayer()
 | 
			
		||||
    {
 | 
			
		||||
        $inputVariablesGroup = $this->getInputVariablesGroups();
 | 
			
		||||
        $inputVariablesGroupCount = count($inputVariablesGroup);
 | 
			
		||||
 | 
			
		||||
        for ($i = 0; $i < $inputVariablesGroupCount - 1; $i++)
 | 
			
		||||
        {
 | 
			
		||||
            $strongerTeam = $inputVariablesGroups[$i][0];
 | 
			
		||||
            $weakerTeam = $inputVariablesGroups[$i + 1][0];
 | 
			
		||||
 | 
			
		||||
            $currentDifference = $this->createOutputVariable();
 | 
			
		||||
            $this->addLayerFactor($this->createTeamPerformanceToDifferenceFactor($strongerTeam, $weakerTeam, currentDifference));
 | 
			
		||||
 | 
			
		||||
            // REVIEW: Does it make sense to have groups of one?
 | 
			
		||||
            $this->getOutputVariablesGroups()[] = $currentDifference;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createTeamPerformanceToDifferenceFactor(
 | 
			
		||||
        Variable $strongerTeam, Variable $weakerTeam, Variable $output)
 | 
			
		||||
    {
 | 
			
		||||
        return new GaussianWeightedSumFactor($output, array($strongerTeam, $weakerTeam), array(1.0, -1.0));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createOutputVariable()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable("Team performance difference");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										13
									
								
								PHPSkills/TrueSkill/Layers/TrueSkillFactorGraphLayer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								PHPSkills/TrueSkill/Layers/TrueSkillFactorGraphLayer.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Moserware\Skills\TrueSkill\Layers;
 | 
			
		||||
 | 
			
		||||
abstract class TrueSkillFactorGraphLayer extends FactorGraphLayer
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(TrueSkillFactorGraph $parentGraph)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($parentGraph);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
		Reference in New Issue
	
	Block a user