CodeBeautifier for PSR-12 standard.

This commit is contained in:
Jens True 2023-08-01 13:53:19 +00:00
parent da8125be94
commit c8c126962d
27 changed files with 307 additions and 213 deletions

@ -8,18 +8,18 @@ use Exception;
abstract class Factor implements \Stringable abstract class Factor implements \Stringable
{ {
private array $_messages = []; private array $messages = [];
private $_messageToVariableBinding; private $messageToVariableBinding;
private string $_name; private string $name;
private array $_variables = []; private array $variables = [];
protected function __construct(string $name) protected function __construct(string $name)
{ {
$this->_name = 'Factor['.$name.']'; $this->name = 'Factor[' . $name . ']';
$this->_messageToVariableBinding = new HashMap(); $this->messageToVariableBinding = new HashMap();
} }
/** /**
@ -35,17 +35,17 @@ abstract class Factor implements \Stringable
*/ */
public function getNumberOfMessages(): int public function getNumberOfMessages(): int
{ {
return count($this->_messages); return count($this->messages);
} }
protected function getVariables(): array protected function getVariables(): array
{ {
return $this->_variables; return $this->variables;
} }
protected function getMessages(): array protected function getMessages(): array
{ {
return $this->_messages; return $this->messages;
} }
/** /**
@ -57,9 +57,9 @@ abstract class Factor implements \Stringable
*/ */
public function updateMessageIndex(int $messageIndex) public function updateMessageIndex(int $messageIndex)
{ {
Guard::argumentIsValidIndex($messageIndex, count($this->_messages), 'messageIndex'); Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
$message = $this->_messages[$messageIndex]; $message = $this->messages[$messageIndex];
$variable = $this->_messageToVariableBinding->getValue($message); $variable = $this->messageToVariableBinding->getValue($message);
return $this->updateMessageVariable($message, $variable); return $this->updateMessageVariable($message, $variable);
} }
@ -74,7 +74,7 @@ abstract class Factor implements \Stringable
*/ */
public function resetMarginals() public function resetMarginals()
{ {
$allValues = $this->_messageToVariableBinding->getAllValues(); $allValues = $this->messageToVariableBinding->getAllValues();
foreach ($allValues as $currentVariable) { foreach ($allValues as $currentVariable) {
$currentVariable->resetToPrior(); $currentVariable->resetToPrior();
} }
@ -83,17 +83,17 @@ abstract class Factor implements \Stringable
/** /**
* Sends the ith message to the marginal and returns the log-normalization constant * Sends the ith message to the marginal and returns the log-normalization constant
* *
* @param $messageIndex * @param $messageIndex
* @return * @return
* *
* @throws Exception * @throws Exception
*/ */
public function sendMessageIndex($messageIndex) public function sendMessageIndex($messageIndex)
{ {
Guard::argumentIsValidIndex($messageIndex, count($this->_messages), 'messageIndex'); Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
$message = $this->_messages[$messageIndex]; $message = $this->messages[$messageIndex];
$variable = $this->_messageToVariableBinding->getValue($message); $variable = $this->messageToVariableBinding->getValue($message);
return $this->sendMessageVariable($message, $variable); return $this->sendMessageVariable($message, $variable);
} }
@ -104,15 +104,15 @@ abstract class Factor implements \Stringable
protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message
{ {
$this->_messageToVariableBinding->setValue($message, $variable); $this->messageToVariableBinding->setValue($message, $variable);
$this->_messages[] = $message; $this->messages[] = $message;
$this->_variables[] = $variable; $this->variables[] = $variable;
return $message; return $message;
} }
public function __toString(): string public function __toString(): string
{ {
return $this->_name; return $this->name;
} }
} }

@ -4,15 +4,15 @@ namespace DNW\Skills\FactorGraphs;
class FactorGraph class FactorGraph
{ {
private $_variableFactory; private $variableFactory;
public function getVariableFactory() public function getVariableFactory()
{ {
return $this->_variableFactory; return $this->variableFactory;
} }
public function setVariableFactory(VariableFactory $factory) public function setVariableFactory(VariableFactory $factory)
{ {
$this->_variableFactory = $factory; $this->variableFactory = $factory;
} }
} }

@ -4,33 +4,33 @@ namespace DNW\Skills\FactorGraphs;
class Variable implements \Stringable class Variable implements \Stringable
{ {
private string $_name; private string $name;
private mixed $_value; private mixed $value;
public function __construct(string $name, private mixed $_prior) public function __construct(string $name, private mixed $prior)
{ {
$this->_name = 'Variable['.$name.']'; $this->name = 'Variable[' . $name . ']';
$this->resetToPrior(); $this->resetToPrior();
} }
public function getValue(): mixed public function getValue(): mixed
{ {
return $this->_value; return $this->value;
} }
public function setValue(mixed $value): void public function setValue(mixed $value): void
{ {
$this->_value = $value; $this->value = $value;
} }
public function resetToPrior(): void public function resetToPrior(): void
{ {
$this->_value = $this->_prior; $this->value = $this->prior;
} }
public function __toString(): string public function __toString(): string
{ {
return $this->_name; return $this->name;
} }
} }

@ -5,15 +5,15 @@ namespace DNW\Skills\Numerics;
/** /**
* Basic math functions. * Basic math functions.
* *
* @author Jeff Moser <jeff@moserware.com> * @author Jeff Moser <jeff@moserware.com>
* @copyright 2010 Jeff Moser * @copyright 2010 Jeff Moser
*/ */
class BasicMath class BasicMath
{ {
/** /**
* Squares the input (x^2 = x * x) * Squares the input (x^2 = x * x)
* *
* @param number $x Value to square (x) * @param number $x Value to square (x)
* @return number The squared value (x^2) * @return number The squared value (x^2)
*/ */
public static function square($x): float|int public static function square($x): float|int
@ -24,8 +24,8 @@ class BasicMath
/** /**
* Sums the items in $itemsToSum * Sums the items in $itemsToSum
* *
* @param array $itemsToSum The items to sum, * @param array $itemsToSum The items to sum,
* @param callable $callback The function to apply to each array element before summing. * @param callable $callback The function to apply to each array element before summing.
* @return number The sum. * @return number The sum.
*/ */
public static function sum(array $itemsToSum, \Closure $callback): float|int public static function sum(array $itemsToSum, \Closure $callback): float|int

@ -5,8 +5,8 @@ namespace DNW\Skills\Numerics;
/** /**
* Computes Gaussian (bell curve) values. * Computes Gaussian (bell curve) values.
* *
* @author Jeff Moser <jeff@moserware.com> * @author Jeff Moser <jeff@moserware.com>
* @copyright 2010 Jeff Moser * @copyright 2010 Jeff Moser
*/ */
class GaussianDistribution implements \Stringable class GaussianDistribution implements \Stringable
{ {

@ -70,12 +70,16 @@ class Matrix
$transposeMatrix = []; $transposeMatrix = [];
$rowMatrixData = $this->_matrixRowData; $rowMatrixData = $this->_matrixRowData;
for ($currentRowTransposeMatrix = 0; for (
$currentRowTransposeMatrix = 0;
$currentRowTransposeMatrix < $this->_columnCount; $currentRowTransposeMatrix < $this->_columnCount;
$currentRowTransposeMatrix++) { $currentRowTransposeMatrix++
for ($currentColumnTransposeMatrix = 0; ) {
for (
$currentColumnTransposeMatrix = 0;
$currentColumnTransposeMatrix < $this->_rowCount; $currentColumnTransposeMatrix < $this->_rowCount;
$currentColumnTransposeMatrix++) { $currentColumnTransposeMatrix++
) {
$transposeMatrix[$currentRowTransposeMatrix][$currentColumnTransposeMatrix] = $transposeMatrix[$currentRowTransposeMatrix][$currentColumnTransposeMatrix] =
$rowMatrixData[$currentColumnTransposeMatrix][$currentRowTransposeMatrix]; $rowMatrixData[$currentColumnTransposeMatrix][$currentRowTransposeMatrix];
} }
@ -155,8 +159,12 @@ class Matrix
$c = $this->_matrixRowData[1][0]; $c = $this->_matrixRowData[1][0];
$d = $this->_matrixRowData[1][1]; $d = $this->_matrixRowData[1][1];
return new SquareMatrix($d, -$b, return new SquareMatrix(
-$c, $a); $d,
-$b,
-$c,
$a
);
} }
// The idea is that it's the transpose of the cofactors // The idea is that it's the transpose of the cofactors
@ -204,8 +212,8 @@ class Matrix
{ {
if ( if (
($left->getRowCount() != $right->getRowCount()) ($left->getRowCount() != $right->getRowCount())
||
($left->getColumnCount() != $right->getColumnCount()) || ($left->getColumnCount() != $right->getColumnCount())
) { ) {
throw new Exception('Matrices must be of the same size'); throw new Exception('Matrices must be of the same size');
} }
@ -318,8 +326,10 @@ class Matrix
for ($currentRow = 0; $currentRow < $this->_rowCount; $currentRow++) { for ($currentRow = 0; $currentRow < $this->_rowCount; $currentRow++) {
for ($currentColumn = 0; $currentColumn < $this->_columnCount; $currentColumn++) { for ($currentColumn = 0; $currentColumn < $this->_columnCount; $currentColumn++) {
$delta = $delta =
abs($this->_matrixRowData[$currentRow][$currentColumn] - abs(
$otherMatrix->getValue($currentRow, $currentColumn)); $this->_matrixRowData[$currentRow][$currentColumn] -
$otherMatrix->getValue($currentRow, $currentColumn)
);
if ($delta > self::ERROR_TOLERANCE) { if ($delta > self::ERROR_TOLERANCE) {
return false; return false;

@ -18,14 +18,15 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate, \Stringable
/** /**
* Constructs a player. * Constructs a player.
* *
* @param mixed $_Id The identifier for the player, such as a name. * @param mixed $_Id The identifier for the player, such as a name.
* @param number $partialPlayPercentage The weight percentage to give this player when calculating a new rank. * @param number $partialPlayPercentage The weight percentage to give this player when calculating a new rank.
* @param number $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 number $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.
*/ */
public function __construct(private $_Id, public function __construct(
$partialPlayPercentage = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE, private $_Id,
$partialUpdatePercentage = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE) $partialPlayPercentage = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE,
{ $partialUpdatePercentage = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE
) {
// If they don't want to give a player an id, that's ok... // If they don't want to give a player an id, that's ok...
Guard::argumentInRangeInclusive($partialPlayPercentage, 0.0, 1.0, 'partialPlayPercentage'); Guard::argumentInRangeInclusive($partialPlayPercentage, 0.0, 1.0, 'partialPlayPercentage');
Guard::argumentInRangeInclusive($partialUpdatePercentage, 0, 1.0, 'partialUpdatePercentage'); Guard::argumentInRangeInclusive($partialUpdatePercentage, 0, 1.0, 'partialUpdatePercentage');

@ -10,8 +10,8 @@ class RankSorter
/** /**
* Performs an in-place sort of the items in according to the ranks in non-decreasing order. * Performs an in-place sort of the items in according to the ranks in non-decreasing order.
* *
* @param array $teams The items to sort according to the order specified by ranks. * @param array $teams The items to sort according to the order specified by ranks.
* @param array $teamRanks The ranks for each item where 1 is first place. * @param array $teamRanks The ranks for each item where 1 is first place.
* @return array * @return array
*/ */
public static function sort(array &$teams, array &$teamRanks) public static function sort(array &$teams, array &$teamRanks)

@ -12,9 +12,9 @@ class Rating implements \Stringable
/** /**
* Constructs a rating. * Constructs a rating.
* *
* @param float $_mean The statistical mean value of the rating (also known as mu). * @param float $_mean The statistical mean value of the rating (also known as mu).
* @param float $_standardDeviation The standard deviation of the rating (also known as s). * @param float $_standardDeviation The standard deviation of the rating (also known as s).
* @param float|int $_conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating. * @param float|int $_conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating.
*/ */
public function __construct(private $_mean, private $_standardDeviation, private $_conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER) public function __construct(private $_mean, private $_standardDeviation, private $_conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER)
{ {
@ -47,7 +47,7 @@ class Rating implements \Stringable
public function getPartialUpdate(Rating $prior, Rating $fullPosterior, $updatePercentage) public function getPartialUpdate(Rating $prior, Rating $fullPosterior, $updatePercentage)
{ {
$priorGaussian = new GaussianDistribution($prior->getMean(), $prior->getStandardDeviation()); $priorGaussian = new GaussianDistribution($prior->getMean(), $prior->getStandardDeviation());
$posteriorGaussian = new GaussianDistribution($fullPosterior->getMean(), $fullPosterior.getStandardDeviation()); $posteriorGaussian = new GaussianDistribution($fullPosterior->getMean(), $fullPosterior . getStandardDeviation());
// From a clarification email from Ralf Herbrich: // From a clarification email from Ralf Herbrich:
// "the idea is to compute a linear interpolation between the prior and posterior skills of each player // "the idea is to compute a linear interpolation between the prior and posterior skills of each player
@ -56,7 +56,7 @@ class Rating implements \Stringable
$precisionDifference = $posteriorGaussian->getPrecision() - $priorGaussian->getPrecision(); $precisionDifference = $posteriorGaussian->getPrecision() - $priorGaussian->getPrecision();
$partialPrecisionDifference = $updatePercentage * $precisionDifference; $partialPrecisionDifference = $updatePercentage * $precisionDifference;
$precisionMeanDifference = $posteriorGaussian->getPrecisionMean() - $priorGaussian.getPrecisionMean(); $precisionMeanDifference = $posteriorGaussian->getPrecisionMean() - $priorGaussian . getPrecisionMean();
$partialPrecisionMeanDifference = $updatePercentage * $precisionMeanDifference; $partialPrecisionMeanDifference = $updatePercentage * $precisionMeanDifference;
$partialPosteriorGaussion = GaussianDistribution::fromPrecisionMean( $partialPosteriorGaussion = GaussianDistribution::fromPrecisionMean(

@ -19,21 +19,22 @@ abstract class SkillCalculator
/** /**
* Calculates new ratings based on the prior ratings and team ranks. * Calculates new ratings based on the prior ratings and team ranks.
* *
* @param GameInfo $gameInfo Parameters for the game. * @param GameInfo $gameInfo Parameters for the game.
* @param array $teamsOfPlayerToRatings A mapping of team players and their ratings. * @param array $teamsOfPlayerToRatings A mapping of team players and their ratings.
* @param array $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2). * @param array $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2).
* @return All the players and their new ratings. * @return All the players and their new ratings.
*/ */
abstract public function calculateNewRatings( abstract public function calculateNewRatings(
GameInfo $gameInfo, GameInfo $gameInfo,
array $teamsOfPlayerToRatings, array $teamsOfPlayerToRatings,
array $teamRanks); array $teamRanks
);
/** /**
* Calculates the match quality as the likelihood of all teams drawing. * Calculates the match quality as the likelihood of all teams drawing.
* *
* @param GameInfo $gameInfo Parameters for the game. * @param GameInfo $gameInfo Parameters for the game.
* @param array $teamsOfPlayerToRatings A mapping of team players and their ratings. * @param array $teamsOfPlayerToRatings A mapping of team players and their ratings.
* @return float The quality of the match between the teams as a percentage (0% = bad, 100% = well matched). * @return float The quality of the match between the teams as a percentage (0% = bad, 100% = well matched).
*/ */
abstract public function calculateMatchQuality(GameInfo $gameInfo, array $teamsOfPlayerToRatings): float; abstract public function calculateMatchQuality(GameInfo $gameInfo, array $teamsOfPlayerToRatings): float;
@ -49,7 +50,7 @@ abstract class SkillCalculator
} }
/** /**
* @param array<\DNW\Skills\Team> $teams * @param array<\DNW\Skills\Team> $teams
* *
* @throws \Exception * @throws \Exception
*/ */
@ -69,4 +70,3 @@ abstract class SkillCalculator
} }
} }
} }

@ -1,4 +1,4 @@
<?php <?php
namespace DNW\Skills; namespace DNW\Skills;

@ -26,10 +26,11 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
parent::__construct(SkillCalculatorSupportedOptions::PARTIAL_PLAY | SkillCalculatorSupportedOptions::PARTIAL_UPDATE, TeamsRange::atLeast(2), PlayersRange::atLeast(1)); parent::__construct(SkillCalculatorSupportedOptions::PARTIAL_PLAY | SkillCalculatorSupportedOptions::PARTIAL_UPDATE, TeamsRange::atLeast(2), PlayersRange::atLeast(1));
} }
public function calculateNewRatings(GameInfo $gameInfo, public function calculateNewRatings(
array $teams, GameInfo $gameInfo,
array $teamRanks): RatingContainer array $teams,
{ array $teamRanks
): RatingContainer {
Guard::argumentNotNull($gameInfo, 'gameInfo'); Guard::argumentNotNull($gameInfo, 'gameInfo');
$this->validateTeamCountAndPlayersCountPerTeam($teams); $this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -88,8 +89,12 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
private static function getPlayerMeansVector(array $teamAssignmentsList) private static function getPlayerMeansVector(array $teamAssignmentsList)
{ {
// A simple vector of all the player means. // A simple vector of all the player means.
return new Vector(self::getPlayerRatingValues($teamAssignmentsList, return new Vector(
fn ($rating) => $rating->getMean())); self::getPlayerRatingValues(
$teamAssignmentsList,
fn ($rating) => $rating->getMean()
)
);
} }
private static function getPlayerCovarianceMatrix(array $teamAssignmentsList) private static function getPlayerCovarianceMatrix(array $teamAssignmentsList)
@ -97,8 +102,11 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
// This is a square matrix whose diagonal values represent the variance (square of standard deviation) of all // This is a square matrix whose diagonal values represent the variance (square of standard deviation) of all
// players. // players.
return new DiagonalMatrix( return new DiagonalMatrix(
self::getPlayerRatingValues($teamAssignmentsList, self::getPlayerRatingValues(
fn ($rating) => BasicMath::square($rating->getStandardDeviation()))); $teamAssignmentsList,
fn ($rating) => BasicMath::square($rating->getStandardDeviation())
)
);
} }
// Helper function that gets a list of values for all player ratings // Helper function that gets a list of values for all player ratings

@ -26,9 +26,12 @@ abstract class GaussianFactor extends Factor
{ {
$newDistribution = GaussianDistribution::fromPrecisionMean(0, 0); $newDistribution = GaussianDistribution::fromPrecisionMean(0, 0);
return parent::createVariableToMessageBindingWithMessage($variable, return parent::createVariableToMessageBindingWithMessage(
$variable,
new Message( new Message(
$newDistribution, $newDistribution,
sprintf('message from %s to %s', $this, $variable))); sprintf('message from %s to %s', $this, $variable)
)
);
} }
} }

@ -14,22 +14,26 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
*/ */
class GaussianGreaterThanFactor extends GaussianFactor class GaussianGreaterThanFactor extends GaussianFactor
{ {
private $_epsilon; private $epsilon;
public function __construct($epsilon, Variable $variable) public function __construct($epsilon, Variable $variable)
{ {
parent::__construct(\sprintf('%s > %.2f', $variable, $epsilon)); parent::__construct(\sprintf('%s > %.2f', $variable, $epsilon));
$this->_epsilon = $epsilon; $this->epsilon = $epsilon;
$this->createVariableToMessageBinding($variable); $this->createVariableToMessageBinding($variable);
} }
public function getLogNormalization() public function getLogNormalization()
{ {
/** @var Variable[] $vars */ /**
* @var Variable[] $vars
*/
$vars = $this->getVariables(); $vars = $this->getVariables();
$marginal = $vars[0]->getValue(); $marginal = $vars[0]->getValue();
/** @var Message[] $messages */ /**
* @var Message[] $messages
*/
$messages = $this->getMessages(); $messages = $this->getMessages();
$message = $messages[0]->getValue(); $message = $messages[0]->getValue();
$messageFromVariable = GaussianDistribution::divide($marginal, $message); $messageFromVariable = GaussianDistribution::divide($marginal, $message);
@ -38,7 +42,7 @@ class GaussianGreaterThanFactor extends GaussianFactor
+ +
log( log(
GaussianDistribution::cumulativeTo( GaussianDistribution::cumulativeTo(
($messageFromVariable->getMean() - $this->_epsilon) / ($messageFromVariable->getMean() - $this->epsilon) /
$messageFromVariable->getStandardDeviation() $messageFromVariable->getStandardDeviation()
) )
); );
@ -57,7 +61,7 @@ class GaussianGreaterThanFactor extends GaussianFactor
$dOnSqrtC = $d / $sqrtC; $dOnSqrtC = $d / $sqrtC;
$epsilsonTimesSqrtC = $this->_epsilon * $sqrtC; $epsilsonTimesSqrtC = $this->epsilon * $sqrtC;
$d = $messageFromVar->getPrecisionMean(); $d = $messageFromVar->getPrecisionMean();
$denom = 1.0 - TruncatedGaussianCorrectionFunctions::wExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC); $denom = 1.0 - TruncatedGaussianCorrectionFunctions::wExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC);

@ -15,21 +15,25 @@ use Exception;
*/ */
class GaussianLikelihoodFactor extends GaussianFactor class GaussianLikelihoodFactor extends GaussianFactor
{ {
private $_precision; private $precision;
public function __construct($betaSquared, Variable $variable1, Variable $variable2) public function __construct($betaSquared, Variable $variable1, Variable $variable2)
{ {
parent::__construct(sprintf('Likelihood of %s going to %s', $variable2, $variable1)); parent::__construct(sprintf('Likelihood of %s going to %s', $variable2, $variable1));
$this->_precision = 1.0 / $betaSquared; $this->precision = 1.0 / $betaSquared;
$this->createVariableToMessageBinding($variable1); $this->createVariableToMessageBinding($variable1);
$this->createVariableToMessageBinding($variable2); $this->createVariableToMessageBinding($variable2);
} }
public function getLogNormalization(): float public function getLogNormalization(): float
{ {
/** @var KeyedVariable[]|mixed $vars */ /**
* @var KeyedVariable[]|mixed $vars
*/
$vars = $this->getVariables(); $vars = $this->getVariables();
/** @var Message[] $messages */ /**
* @var Message[] $messages
*/
$messages = $this->getMessages(); $messages = $this->getMessages();
return GaussianDistribution::logRatioNormalization( return GaussianDistribution::logRatioNormalization(
@ -46,7 +50,7 @@ class GaussianLikelihoodFactor extends GaussianFactor
$marginal1 = clone $variable1->getValue(); $marginal1 = clone $variable1->getValue();
$marginal2 = clone $variable2->getValue(); $marginal2 = clone $variable2->getValue();
$a = $this->_precision / ($this->_precision + $marginal2->getPrecision() - $message2Value->getPrecision()); $a = $this->precision / ($this->precision + $marginal2->getPrecision() - $message2Value->getPrecision());
$newMessage = GaussianDistribution::fromPrecisionMean( $newMessage = GaussianDistribution::fromPrecisionMean(
$a * ($marginal2->getPrecisionMean() - $message2Value->getPrecisionMean()), $a * ($marginal2->getPrecisionMean() - $message2Value->getPrecisionMean()),
@ -72,10 +76,16 @@ class GaussianLikelihoodFactor extends GaussianFactor
$vars = $this->getVariables(); $vars = $this->getVariables();
return match ($messageIndex) { return match ($messageIndex) {
0 => $this->updateHelper($messages[0], $messages[1], 0 => $this->updateHelper(
$vars[0], $vars[1]), $messages[0],
1 => $this->updateHelper($messages[1], $messages[0], $messages[1],
$vars[1], $vars[0]), $vars[0], $vars[1]
),
1 => $this->updateHelper(
$messages[1],
$messages[0],
$vars[1], $vars[0]
),
default => throw new Exception(), default => throw new Exception(),
}; };
} }

@ -19,8 +19,10 @@ class GaussianPriorFactor extends GaussianFactor
{ {
parent::__construct(sprintf('Prior value going to %s', $variable)); parent::__construct(sprintf('Prior value going to %s', $variable));
$this->_newMessage = new GaussianDistribution($mean, sqrt($variance)); $this->_newMessage = new GaussianDistribution($mean, sqrt($variance));
$newMessage = new Message(GaussianDistribution::fromPrecisionMean(0, 0), $newMessage = new Message(
sprintf('message from %s to %s', $this, $variable)); GaussianDistribution::fromPrecisionMean(0, 0),
sprintf('message from %s to %s', $this, $variable)
);
$this->createVariableToMessageBindingWithMessage($variable, $newMessage); $this->createVariableToMessageBindingWithMessage($variable, $newMessage);
} }

@ -15,13 +15,13 @@ use DNW\Skills\Numerics\GaussianDistribution;
*/ */
class GaussianWeightedSumFactor extends GaussianFactor class GaussianWeightedSumFactor extends GaussianFactor
{ {
private array $_variableIndexOrdersForWeights = []; private array $variableIndexOrdersForWeights = [];
// This following is used for convenience, for example, the first entry is [0, 1, 2] // 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] // corresponding to v[0] = a1*v[1] + a2*v[2]
private array $_weights = []; private array $weights = [];
private array $_weightsSquared = []; private array $weightsSquared = [];
public function __construct(Variable $sumVariable, array $variablesToSum, array $variableWeights = null) public function __construct(Variable $sumVariable, array $variablesToSum, array $variableWeights = null)
{ {
@ -30,20 +30,20 @@ class GaussianWeightedSumFactor extends GaussianFactor
// The first weights are a straightforward copy // The first weights are a straightforward copy
// v_0 = a_1*v_1 + a_2*v_2 + ... + a_n * v_n // v_0 = a_1*v_1 + a_2*v_2 + ... + a_n * v_n
$variableWeightsLength = count((array) $variableWeights); $variableWeightsLength = count((array) $variableWeights);
$this->_weights[0] = array_fill(0, count((array) $variableWeights), 0); $this->weights[0] = array_fill(0, count((array) $variableWeights), 0);
for ($i = 0; $i < $variableWeightsLength; $i++) { for ($i = 0; $i < $variableWeightsLength; $i++) {
$weight = &$variableWeights[$i]; $weight = &$variableWeights[$i];
$this->_weights[0][$i] = $weight; $this->weights[0][$i] = $weight;
$this->_weightsSquared[0][$i] = BasicMath::square($weight); $this->weightsSquared[0][$i] = BasicMath::square($weight);
} }
$variablesToSumLength = count($variablesToSum); $variablesToSumLength = count($variablesToSum);
// 0..n-1 // 0..n-1
$this->_variableIndexOrdersForWeights[0] = []; $this->variableIndexOrdersForWeights[0] = [];
for ($i = 0; $i < ($variablesToSumLength + 1); $i++) { for ($i = 0; $i < ($variablesToSumLength + 1); $i++) {
$this->_variableIndexOrdersForWeights[0][] = $i; $this->variableIndexOrdersForWeights[0][] = $i;
} }
$variableWeightsLength = count((array) $variableWeights); $variableWeightsLength = count((array) $variableWeights);
@ -66,9 +66,11 @@ class GaussianWeightedSumFactor extends GaussianFactor
// This is helpful since we skip over one of the spots // This is helpful since we skip over one of the spots
$currentDestinationWeightIndex = 0; $currentDestinationWeightIndex = 0;
for ($currentWeightSourceIndex = 0; for (
$currentWeightSourceIndex = 0;
$currentWeightSourceIndex < $variableWeightsLength; $currentWeightSourceIndex < $variableWeightsLength;
$currentWeightSourceIndex++) { $currentWeightSourceIndex++
) {
if ($currentWeightSourceIndex === $weightsIndex - 1) { if ($currentWeightSourceIndex === $weightsIndex - 1) {
continue; continue;
} }
@ -97,10 +99,10 @@ class GaussianWeightedSumFactor extends GaussianFactor
$currentWeights[$currentDestinationWeightIndex] = $finalWeight; $currentWeights[$currentDestinationWeightIndex] = $finalWeight;
$currentWeightsSquared[$currentDestinationWeightIndex] = BasicMath::square($finalWeight); $currentWeightsSquared[$currentDestinationWeightIndex] = BasicMath::square($finalWeight);
$variableIndices[count((array) $variableWeights)] = 0; $variableIndices[count((array) $variableWeights)] = 0;
$this->_variableIndexOrdersForWeights[] = $variableIndices; $this->variableIndexOrdersForWeights[] = $variableIndices;
$this->_weights[$weightsIndex] = $currentWeights; $this->weights[$weightsIndex] = $currentWeights;
$this->_weightsSquared[$weightsIndex] = $currentWeightsSquared; $this->weightsSquared[$weightsIndex] = $currentWeightsSquared;
} }
$this->createVariableToMessageBinding($sumVariable); $this->createVariableToMessageBinding($sumVariable);
@ -192,7 +194,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
$updatedMessages = []; $updatedMessages = [];
$updatedVariables = []; $updatedVariables = [];
$indicesToUse = $this->_variableIndexOrdersForWeights[$messageIndex]; $indicesToUse = $this->variableIndexOrdersForWeights[$messageIndex];
// The tricky part here is that we have to put the messages and variables in the same // 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, // order as the weights. Thankfully, the weights and messages share the same index numbers,
@ -203,10 +205,12 @@ class GaussianWeightedSumFactor extends GaussianFactor
$updatedVariables[] = $allVariables[$indicesToUse[$i]]; $updatedVariables[] = $allVariables[$indicesToUse[$i]];
} }
return $this->updateHelper($this->_weights[$messageIndex], return $this->updateHelper(
$this->_weightsSquared[$messageIndex], $this->weights[$messageIndex],
$this->weightsSquared[$messageIndex],
$updatedMessages, $updatedMessages,
$updatedVariables); $updatedVariables
);
} }
private static function createName($sumVariable, $variablesToSum, $weights) private static function createName($sumVariable, $variablesToSum, $weights)

@ -14,30 +14,34 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
*/ */
class GaussianWithinFactor extends GaussianFactor class GaussianWithinFactor extends GaussianFactor
{ {
private $_epsilon; private $epsilon;
public function __construct($epsilon, Variable $variable) public function __construct($epsilon, Variable $variable)
{ {
parent::__construct(sprintf('%s <= %.2f', $variable, $epsilon)); parent::__construct(sprintf('%s <= %.2f', $variable, $epsilon));
$this->_epsilon = $epsilon; $this->epsilon = $epsilon;
$this->createVariableToMessageBinding($variable); $this->createVariableToMessageBinding($variable);
} }
public function getLogNormalization() public function getLogNormalization()
{ {
/** @var Variable[] $variables */ /**
* @var Variable[] $variables
*/
$variables = $this->getVariables(); $variables = $this->getVariables();
$marginal = $variables[0]->getValue(); $marginal = $variables[0]->getValue();
/** @var Message[] $messages */ /**
* @var Message[] $messages
*/
$messages = $this->getMessages(); $messages = $this->getMessages();
$message = $messages[0]->getValue(); $message = $messages[0]->getValue();
$messageFromVariable = GaussianDistribution::divide($marginal, $message); $messageFromVariable = GaussianDistribution::divide($marginal, $message);
$mean = $messageFromVariable->getMean(); $mean = $messageFromVariable->getMean();
$std = $messageFromVariable->getStandardDeviation(); $std = $messageFromVariable->getStandardDeviation();
$z = GaussianDistribution::cumulativeTo(($this->_epsilon - $mean) / $std) $z = GaussianDistribution::cumulativeTo(($this->epsilon - $mean) / $std)
- -
GaussianDistribution::cumulativeTo((-$this->_epsilon - $mean) / $std); GaussianDistribution::cumulativeTo((-$this->epsilon - $mean) / $std);
return -GaussianDistribution::logProductNormalization($messageFromVariable, $message) + log($z); return -GaussianDistribution::logProductNormalization($messageFromVariable, $message) + log($z);
} }
@ -54,7 +58,7 @@ class GaussianWithinFactor extends GaussianFactor
$sqrtC = sqrt($c); $sqrtC = sqrt($c);
$dOnSqrtC = $d / $sqrtC; $dOnSqrtC = $d / $sqrtC;
$epsilonTimesSqrtC = $this->_epsilon * $sqrtC; $epsilonTimesSqrtC = $this->epsilon * $sqrtC;
$d = $messageFromVariable->getPrecisionMean(); $d = $messageFromVariable->getPrecisionMean();
$denominator = 1.0 - TruncatedGaussianCorrectionFunctions::wWithinMargin($dOnSqrtC, $epsilonTimesSqrtC); $denominator = 1.0 - TruncatedGaussianCorrectionFunctions::wWithinMargin($dOnSqrtC, $epsilonTimesSqrtC);

@ -13,28 +13,29 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
{ {
public function __construct( public function __construct(
TrueSkillFactorGraph $parentGraph, TrueSkillFactorGraph $parentGraph,
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $_TeamPerformancesToTeamPerformanceDifferencesLayer, private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $TeamPerformancesToTeamPerformanceDifferencesLayer,
private readonly TeamDifferencesComparisonLayer $_TeamDifferencesComparisonLayer private readonly TeamDifferencesComparisonLayer $TeamDifferencesComparisonLayer
) { ) {
parent::__construct($parentGraph); parent::__construct($parentGraph);
} }
public function getLocalFactors(): array public function getLocalFactors(): array
{ {
return array_merge($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(), return array_merge(
$this->_TeamDifferencesComparisonLayer->getLocalFactors() $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(),
$this->TeamDifferencesComparisonLayer->getLocalFactors()
); );
} }
public function buildLayer() public function buildLayer()
{ {
$inputVariablesGroups = $this->getInputVariablesGroups(); $inputVariablesGroups = $this->getInputVariablesGroups();
$this->_TeamPerformancesToTeamPerformanceDifferencesLayer->setInputVariablesGroups($inputVariablesGroups); $this->TeamPerformancesToTeamPerformanceDifferencesLayer->setInputVariablesGroups($inputVariablesGroups);
$this->_TeamPerformancesToTeamPerformanceDifferencesLayer->buildLayer(); $this->TeamPerformancesToTeamPerformanceDifferencesLayer->buildLayer();
$teamDifferencesOutputVariablesGroups = $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getOutputVariablesGroups(); $teamDifferencesOutputVariablesGroups = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getOutputVariablesGroups();
$this->_TeamDifferencesComparisonLayer->setInputVariablesGroups($teamDifferencesOutputVariablesGroups); $this->TeamDifferencesComparisonLayer->setInputVariablesGroups($teamDifferencesOutputVariablesGroups);
$this->_TeamDifferencesComparisonLayer->buildLayer(); $this->TeamDifferencesComparisonLayer->buildLayer();
} }
public function createPriorSchedule(): ScheduleSequence public function createPriorSchedule(): ScheduleSequence
@ -52,9 +53,9 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
} }
// When dealing with differences, there are always (n-1) differences, so add in the 1 // When dealing with differences, there are always (n-1) differences, so add in the 1
$totalTeamDifferences = is_countable($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) ? count($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) : 0; $totalTeamDifferences = is_countable($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) ? count($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) : 0;
$localFactors = $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(); $localFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
$firstDifferencesFactor = $localFactors[0]; $firstDifferencesFactor = $localFactors[0];
$lastDifferencesFactor = $localFactors[$totalTeamDifferences - 1]; $lastDifferencesFactor = $localFactors[$totalTeamDifferences - 1];
@ -65,18 +66,22 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
$loop, $loop,
new ScheduleStep( new ScheduleStep(
'teamPerformanceToPerformanceDifferenceFactors[0] @ 1', 'teamPerformanceToPerformanceDifferenceFactors[0] @ 1',
$firstDifferencesFactor, 1), $firstDifferencesFactor,
1
),
new ScheduleStep( new ScheduleStep(
sprintf('teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = %d - 1] @ 2', $totalTeamDifferences), sprintf('teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = %d - 1] @ 2', $totalTeamDifferences),
$lastDifferencesFactor, 2), $lastDifferencesFactor,
2
),
] ]
); );
} }
private function createTwoTeamInnerPriorLoopSchedule() private function createTwoTeamInnerPriorLoopSchedule()
{ {
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(); $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
$teamDifferencesComparisonLayerLocalFactors = $this->_TeamDifferencesComparisonLayer->getLocalFactors(); $teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
$firstPerfToTeamDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[0]; $firstPerfToTeamDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[0];
$firstTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[0]; $firstTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[0];
@ -84,27 +89,30 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
new ScheduleStep( new ScheduleStep(
'send team perf to perf differences', 'send team perf to perf differences',
$firstPerfToTeamDiff, $firstPerfToTeamDiff,
0), 0
),
new ScheduleStep( new ScheduleStep(
'send to greater than or within factor', 'send to greater than or within factor',
$firstTeamDiffComparison, $firstTeamDiffComparison,
0), 0
),
]; ];
return $this->scheduleSequence( return $this->scheduleSequence(
$itemsToSequence, $itemsToSequence,
'loop of just two teams inner sequence'); 'loop of just two teams inner sequence'
);
} }
private function createMultipleTeamInnerPriorLoopSchedule() private function createMultipleTeamInnerPriorLoopSchedule()
{ {
$totalTeamDifferences = is_countable($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) ? count($this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) : 0; $totalTeamDifferences = is_countable($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) ? count($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()) : 0;
$forwardScheduleList = []; $forwardScheduleList = [];
for ($i = 0; $i < $totalTeamDifferences - 1; $i++) { for ($i = 0; $i < $totalTeamDifferences - 1; $i++) {
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(); $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
$teamDifferencesComparisonLayerLocalFactors = $this->_TeamDifferencesComparisonLayer->getLocalFactors(); $teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
$currentTeamPerfToTeamPerfDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$i]; $currentTeamPerfToTeamPerfDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$i];
$currentTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[$i]; $currentTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[$i];
@ -114,14 +122,22 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
[ [
new ScheduleStep( new ScheduleStep(
sprintf('team perf to perf diff %d', $i), sprintf('team perf to perf diff %d', $i),
$currentTeamPerfToTeamPerfDiff, 0), $currentTeamPerfToTeamPerfDiff,
0
),
new ScheduleStep( new ScheduleStep(
sprintf('greater than or within result factor %d', $i), sprintf('greater than or within result factor %d', $i),
$currentTeamDiffComparison, 0), $currentTeamDiffComparison,
0
),
new ScheduleStep( new ScheduleStep(
sprintf('team perf to perf diff factors [%d], 2', $i), sprintf('team perf to perf diff factors [%d], 2', $i),
$currentTeamPerfToTeamPerfDiff, 2), $currentTeamPerfToTeamPerfDiff,
], sprintf('current forward schedule piece %d', $i)); 2
),
],
sprintf('current forward schedule piece %d', $i)
);
$forwardScheduleList[] = $currentForwardSchedulePiece; $forwardScheduleList[] = $currentForwardSchedulePiece;
} }
@ -131,8 +147,8 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
$backwardScheduleList = []; $backwardScheduleList = [];
for ($i = 0; $i < $totalTeamDifferences - 1; $i++) { for ($i = 0; $i < $totalTeamDifferences - 1; $i++) {
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->_TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(); $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
$teamDifferencesComparisonLayerLocalFactors = $this->_TeamDifferencesComparisonLayer->getLocalFactors(); $teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
$differencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i]; $differencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i];
$comparisonFactor = $teamDifferencesComparisonLayerLocalFactors[$totalTeamDifferences - 1 - $i]; $comparisonFactor = $teamDifferencesComparisonLayerLocalFactors[$totalTeamDifferences - 1 - $i];
@ -143,17 +159,21 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
[ [
new ScheduleStep( new ScheduleStep(
sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 0', $i), sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 0', $i),
$differencesFactor, 0 $differencesFactor,
0
), ),
new ScheduleStep( new ScheduleStep(
sprintf('greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - %d] @ 0', $i), sprintf('greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - %d] @ 0', $i),
$comparisonFactor, 0 $comparisonFactor,
0
), ),
new ScheduleStep( new ScheduleStep(
sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 1', $i), sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 1', $i),
$performancesToDifferencesFactor, 1 $performancesToDifferencesFactor,
1
), ),
]); ]
);
$backwardScheduleList[] = $currentBackwardSchedulePiece; $backwardScheduleList[] = $currentBackwardSchedulePiece;
} }

@ -32,11 +32,13 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
return $this->scheduleSequence( return $this->scheduleSequence(
array_map( array_map(
fn ($weightedSumFactor) => new ScheduleStep('Perf to Team Perf Step', $weightedSumFactor, 0), fn ($weightedSumFactor) => new ScheduleStep('Perf to Team Perf Step', $weightedSumFactor, 0),
$localFactors), $localFactors
'all player perf to team perf schedule'); ),
'all player perf to team perf schedule'
);
} }
protected function createPlayerToTeamSumFactor($teamMembers, $sumVariable): GaussianWeightedSumFactor protected function createPlayerToTeamSumFactor($teamMembers, $sumVariable): GaussianWeightedSumFactor
{ {
$weights = array_map( $weights = array_map(
function ($v) { function ($v) {
@ -44,12 +46,14 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
return PartialPlay::getPartialPlayPercentage($player); return PartialPlay::getPartialPlayPercentage($player);
}, },
$teamMembers); $teamMembers
);
return new GaussianWeightedSumFactor( return new GaussianWeightedSumFactor(
$sumVariable, $sumVariable,
$teamMembers, $teamMembers,
$weights); $weights
);
} }
public function createPosteriorSchedule(): ScheduleSequence public function createPosteriorSchedule(): ScheduleSequence
@ -60,8 +64,11 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
$localCurrentFactor = $currentFactor; $localCurrentFactor = $currentFactor;
$numberOfMessages = $localCurrentFactor->getNumberOfMessages(); $numberOfMessages = $localCurrentFactor->getNumberOfMessages();
for ($currentIteration = 1; $currentIteration < $numberOfMessages; $currentIteration++) { for ($currentIteration = 1; $currentIteration < $numberOfMessages; $currentIteration++) {
$allFactors[] = new ScheduleStep('team sum perf @'.$currentIteration, $allFactors[] = new ScheduleStep(
$localCurrentFactor, $currentIteration); 'team sum perf @' . $currentIteration,
$localCurrentFactor,
$currentIteration
);
} }
} }
@ -74,6 +81,6 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
$teamMemberNames = \implode(', ', $memberNames); $teamMemberNames = \implode(', ', $memberNames);
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable('Team['.$teamMemberNames."]'s performance"); return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable('Team[' . $teamMemberNames . "]'s performance");
} }
} }

@ -47,8 +47,10 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
return $this->scheduleSequence( return $this->scheduleSequence(
array_map( array_map(
fn ($prior) => new ScheduleStep('Prior to Skill Step', $prior, 0), fn ($prior) => new ScheduleStep('Prior to Skill Step', $prior, 0),
$localFactors), $localFactors
'All priors'); ),
'All priors'
);
} }
private function createPriorFactor(Rating $priorRating, Variable $skillsVariable) private function createPriorFactor(Rating $priorRating, Variable $skillsVariable)
@ -66,6 +68,6 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
$parentFactorGraph = $this->getParentFactorGraph(); $parentFactorGraph = $this->getParentFactorGraph();
$variableFactory = $parentFactorGraph->getVariableFactory(); $variableFactory = $parentFactorGraph->getVariableFactory();
return $variableFactory->createKeyedVariable($key, $key."'s skill"); return $variableFactory->createKeyedVariable($key, $key . "'s skill");
} }
} }

@ -9,13 +9,13 @@ use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
{ {
private $_epsilon; private $epsilon;
public function __construct(TrueSkillFactorGraph $parentGraph, private readonly array $_teamRanks) public function __construct(TrueSkillFactorGraph $parentGraph, private readonly array $teamRanks)
{ {
parent::__construct($parentGraph); parent::__construct($parentGraph);
$gameInfo = $this->getParentFactorGraph()->getGameInfo(); $gameInfo = $this->getParentFactorGraph()->getGameInfo();
$this->_epsilon = DrawMargin::getDrawMarginFromDrawProbability($gameInfo->getDrawProbability(), $gameInfo->getBeta()); $this->epsilon = DrawMargin::getDrawMarginFromDrawProbability($gameInfo->getDrawProbability(), $gameInfo->getBeta());
} }
public function buildLayer() public function buildLayer()
@ -24,13 +24,13 @@ class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
$inputVarGroupsCount = is_countable($inputVarGroups) ? count($inputVarGroups) : 0; $inputVarGroupsCount = is_countable($inputVarGroups) ? count($inputVarGroups) : 0;
for ($i = 0; $i < $inputVarGroupsCount; $i++) { for ($i = 0; $i < $inputVarGroupsCount; $i++) {
$isDraw = ($this->_teamRanks[$i] == $this->_teamRanks[$i + 1]); $isDraw = ($this->teamRanks[$i] == $this->teamRanks[$i + 1]);
$teamDifference = $inputVarGroups[$i][0]; $teamDifference = $inputVarGroups[$i][0];
$factor = $factor =
$isDraw $isDraw
? new GaussianWithinFactor($this->_epsilon, $teamDifference) ? new GaussianWithinFactor($this->epsilon, $teamDifference)
: new GaussianGreaterThanFactor($this->_epsilon, $teamDifference); : new GaussianGreaterThanFactor($this->epsilon, $teamDifference);
$this->addLayerFactor($factor); $this->addLayerFactor($factor);
} }

@ -26,10 +26,11 @@ class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorG
} }
} }
private function createTeamPerformanceToDifferenceFactor(Variable $strongerTeam, private function createTeamPerformanceToDifferenceFactor(
Variable $weakerTeam, Variable $strongerTeam,
Variable $output) Variable $weakerTeam,
{ Variable $output
) {
$teams = [$strongerTeam, $weakerTeam]; $teams = [$strongerTeam, $weakerTeam];
$weights = [1.0, -1.0]; $weights = [1.0, -1.0];

@ -19,38 +19,40 @@ use DNW\Skills\TrueSkill\Layers\TeamPerformancesToTeamPerformanceDifferencesLaye
class TrueSkillFactorGraph extends FactorGraph class TrueSkillFactorGraph extends FactorGraph
{ {
private $_layers; private $layers;
private $_priorLayer; private $priorLayer;
public function __construct(private readonly GameInfo $_gameInfo, array $teams, array $teamRanks) public function __construct(private readonly GameInfo $gameInfo, array $teams, array $teamRanks)
{ {
$this->_priorLayer = new PlayerPriorValuesToSkillsLayer($this, $teams); $this->priorLayer = new PlayerPriorValuesToSkillsLayer($this, $teams);
$newFactory = new VariableFactory( $newFactory = new VariableFactory(
fn () => GaussianDistribution::fromPrecisionMean(0, 0)); fn () => GaussianDistribution::fromPrecisionMean(0, 0)
);
$this->setVariableFactory($newFactory); $this->setVariableFactory($newFactory);
$this->_layers = [ $this->layers = [
$this->_priorLayer, $this->priorLayer,
new PlayerSkillsToPerformancesLayer($this), new PlayerSkillsToPerformancesLayer($this),
new PlayerPerformancesToTeamPerformancesLayer($this), new PlayerPerformancesToTeamPerformancesLayer($this),
new IteratedTeamDifferencesInnerLayer( new IteratedTeamDifferencesInnerLayer(
$this, $this,
new TeamPerformancesToTeamPerformanceDifferencesLayer($this), new TeamPerformancesToTeamPerformanceDifferencesLayer($this),
new TeamDifferencesComparisonLayer($this, $teamRanks)), new TeamDifferencesComparisonLayer($this, $teamRanks)
),
]; ];
} }
public function getGameInfo() public function getGameInfo()
{ {
return $this->_gameInfo; return $this->gameInfo;
} }
public function buildGraph(): void public function buildGraph(): void
{ {
$lastOutput = null; $lastOutput = null;
$layers = $this->_layers; $layers = $this->layers;
foreach ($layers as $currentLayer) { foreach ($layers as $currentLayer) {
if ($lastOutput != null) { if ($lastOutput != null) {
$currentLayer->setInputVariablesGroups($lastOutput); $currentLayer->setInputVariablesGroups($lastOutput);
@ -72,7 +74,7 @@ class TrueSkillFactorGraph extends FactorGraph
{ {
$factorList = new FactorList(); $factorList = new FactorList();
$layers = $this->_layers; $layers = $this->layers;
foreach ($layers as $currentLayer) { foreach ($layers as $currentLayer) {
$localFactors = $currentLayer->getLocalFactors(); $localFactors = $currentLayer->getLocalFactors();
foreach ($localFactors as $currentFactor) { foreach ($localFactors as $currentFactor) {
@ -90,7 +92,7 @@ class TrueSkillFactorGraph extends FactorGraph
{ {
$fullSchedule = []; $fullSchedule = [];
$layers = $this->_layers; $layers = $this->layers;
foreach ($layers as $currentLayer) { foreach ($layers as $currentLayer) {
$currentPriorSchedule = $currentLayer->createPriorSchedule(); $currentPriorSchedule = $currentLayer->createPriorSchedule();
if ($currentPriorSchedule != null) { if ($currentPriorSchedule != null) {
@ -98,7 +100,7 @@ class TrueSkillFactorGraph extends FactorGraph
} }
} }
$allLayersReverse = array_reverse($this->_layers); $allLayersReverse = array_reverse($this->layers);
foreach ($allLayersReverse as $currentLayer) { foreach ($allLayersReverse as $currentLayer) {
$currentPosteriorSchedule = $currentLayer->createPosteriorSchedule(); $currentPosteriorSchedule = $currentLayer->createPosteriorSchedule();
@ -114,12 +116,14 @@ class TrueSkillFactorGraph extends FactorGraph
{ {
$result = new RatingContainer(); $result = new RatingContainer();
$priorLayerOutputVariablesGroups = $this->_priorLayer->getOutputVariablesGroups(); $priorLayerOutputVariablesGroups = $this->priorLayer->getOutputVariablesGroups();
foreach ($priorLayerOutputVariablesGroups as $currentTeam) { foreach ($priorLayerOutputVariablesGroups as $currentTeam) {
foreach ($currentTeam as $currentPlayer) { foreach ($currentTeam as $currentPlayer) {
$localCurrentPlayer = $currentPlayer->getKey(); $localCurrentPlayer = $currentPlayer->getKey();
$newRating = new Rating($currentPlayer->getValue()->getMean(), $newRating = new Rating(
$currentPlayer->getValue()->getStandardDeviation()); $currentPlayer->getValue()->getMean(),
$currentPlayer->getValue()->getStandardDeviation()
);
$result->setRating($localCurrentPlayer, $newRating); $result->setRating($localCurrentPlayer, $newRating);
} }

@ -15,7 +15,8 @@ class TruncatedGaussianCorrectionFunctions
* correction of a single-sided truncated Gaussian with unit variance." * correction of a single-sided truncated Gaussian with unit variance."
* *
* @param $teamPerformanceDifference * @param $teamPerformanceDifference
* @param $drawMargin In the paper, it's referred to as just "ε". * @param $drawMargin In the paper, it's referred to as just
* "ε".
* @param $c * @param $c
*/ */
public static function vExceedsMarginScaled(float $teamPerformanceDifference, float $drawMargin, float $c): float public static function vExceedsMarginScaled(float $teamPerformanceDifference, float $drawMargin, float $c): float
@ -123,7 +124,8 @@ class TruncatedGaussianCorrectionFunctions
($drawMargin - $teamPerformanceDifferenceAbsoluteValue) ($drawMargin - $teamPerformanceDifferenceAbsoluteValue)
* *
GaussianDistribution::at( GaussianDistribution::at(
$drawMargin - $teamPerformanceDifferenceAbsoluteValue) $drawMargin - $teamPerformanceDifferenceAbsoluteValue
)
- (-$drawMargin - $teamPerformanceDifferenceAbsoluteValue) - (-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)
* *
GaussianDistribution::at(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)) / $denominator; GaussianDistribution::at(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)) / $denominator;

@ -27,10 +27,11 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
parent::__construct(SkillCalculatorSupportedOptions::NONE, TeamsRange::exactly(2), PlayersRange::exactly(1)); parent::__construct(SkillCalculatorSupportedOptions::NONE, TeamsRange::exactly(2), PlayersRange::exactly(1));
} }
public function calculateNewRatings(GameInfo $gameInfo, public function calculateNewRatings(
array $teams, GameInfo $gameInfo,
array $teamRanks): RatingContainer array $teams,
{ array $teamRanks
): RatingContainer {
// Basic argument checking // Basic argument checking
Guard::argumentNotNull($gameInfo, 'gameInfo'); Guard::argumentNotNull($gameInfo, 'gameInfo');
$this->validateTeamCountAndPlayersCountPerTeam($teams); $this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -51,17 +52,27 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
$results = new RatingContainer(); $results = new RatingContainer();
$results->setRating($winner, self::calculateNewRating($gameInfo, $results->setRating(
$winnerPreviousRating, $winner,
$loserPreviousRating, self::calculateNewRating(
$wasDraw ? PairwiseComparison::DRAW $gameInfo,
: PairwiseComparison::WIN)); $winnerPreviousRating,
$loserPreviousRating,
$wasDraw ? PairwiseComparison::DRAW
: PairwiseComparison::WIN
)
);
$results->setRating($loser, self::calculateNewRating($gameInfo, $results->setRating(
$loserPreviousRating, $loser,
$winnerPreviousRating, self::calculateNewRating(
$wasDraw ? PairwiseComparison::DRAW $gameInfo,
: PairwiseComparison::LOSE)); $loserPreviousRating,
$winnerPreviousRating,
$wasDraw ? PairwiseComparison::DRAW
: PairwiseComparison::LOSE
)
);
// And we're done! // And we're done!
return $results; return $results;

@ -60,12 +60,13 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
return $results; return $results;
} }
private static function updatePlayerRatings(GameInfo $gameInfo, private static function updatePlayerRatings(
RatingContainer $newPlayerRatings, GameInfo $gameInfo,
Team $selfTeam, RatingContainer $newPlayerRatings,
Team $otherTeam, Team $selfTeam,
PairwiseComparison $selfToOtherTeamComparison): void Team $otherTeam,
{ PairwiseComparison $selfToOtherTeamComparison
): void {
$drawMargin = DrawMargin::getDrawMarginFromDrawProbability( $drawMargin = DrawMargin::getDrawMarginFromDrawProbability(
$gameInfo->getDrawProbability(), $gameInfo->getDrawProbability(),
$gameInfo->getBeta() $gameInfo->getBeta()