Compare commits

..

5 Commits

Author SHA1 Message Date
b38a9656eb Much stricter coding standards for phpstan, phpstan and psalm. 2025-04-15 08:14:08 +00:00
3c617e9869 Move 2025-04-01 11:53:37 +00:00
5a414b8307 More warnings from Psalm. Reduced Psalm config size 2025-01-28 09:38:14 +00:00
e2620fde53 PHP8.3 style overrides added 2025-01-28 09:20:03 +00:00
22002891c5 Upgrade to PHP8.4 2025-01-28 09:18:50 +00:00
68 changed files with 1285 additions and 356 deletions

View File

@ -1,17 +0,0 @@
when:
- event: [push, tag]
steps:
- name: requirements
image: composer
commands:
- composer install --no-dev
- name: run
image: php:cli-bookworm
commands:
- php examples/3teams.php
- php examples/basic.php
- name: test
image: composer
commands:
- composer install
- vendor/bin/phpunit tests --no-coverage

View File

@ -14,7 +14,7 @@ use DNW\Skills\Team;
/**
* Basic Benchmarks.
*/
class BasicBench
final class BasicBench
{
/**
* To benchmark performance when using TwoPlayerTrueSkillCalculator

View File

@ -1,9 +1,9 @@
{
"name": "dnw/php-trueskill",
"description": "Trueskill implementation by Moserware updated for PHP 8.2",
"description": "Trueskill implementation by Moserware updated for PHP 8.4",
"keywords": ["trueskill", "matchmaking", "ranking", "skill", "elo"],
"require": {
"php": "^8.2"
"php": "^8.4"
},
"require-dev": {
"phpstan/phpstan": "^1.0",
@ -51,7 +51,6 @@
"@test",
"@lint",
"@analyze",
"@document",
"@metrics",
"@html"
]

1318
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,5 @@
<directory name="src"/>
<directory name="tests"/>
<directory name="benchmark"/>
<ignoreFiles>
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
</psalm>

View File

@ -7,7 +7,7 @@ namespace DNW\Skills\FactorGraphs;
/**
* Helper class for computing the factor graph's normalization constant.
*/
class FactorList
final class FactorList
{
/**
* @var Factor[] $list
@ -31,11 +31,11 @@ class FactorList
$numberOfMessages = $factor->getNumberOfMessages();
for ($j = 0; $j < $numberOfMessages; ++$j) {
$sumLogZ += $factor->sendMessageIndex($j);
$sumLogZ += (float)$factor->sendMessageIndex($j);
}
}
$sumLogS = 0;
$sumLogS = 0.0;
foreach ($list as &$currentFactor) {
$sumLogS += $currentFactor->getLogNormalization();

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace DNW\Skills\FactorGraphs;
class KeyedVariable extends Variable
final class KeyedVariable extends Variable
{
public function __construct(private readonly mixed $key, mixed $prior)
{

View File

@ -6,7 +6,7 @@ namespace DNW\Skills\FactorGraphs;
use DNW\Skills\Numerics\GaussianDistribution;
class Message
final class Message
{
public function __construct(private GaussianDistribution $value)
{

View File

@ -4,12 +4,13 @@ declare(strict_types=1);
namespace DNW\Skills\FactorGraphs;
class ScheduleLoop extends Schedule
final class ScheduleLoop extends Schedule
{
public function __construct(private readonly Schedule $scheduleToLoop, private readonly float $maxDelta)
{
}
#[\Override]
public function visit(int $depth = -1, int $maxDepth = 0): float
{
$delta = $this->scheduleToLoop->visit($depth + 1, $maxDepth);

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace DNW\Skills\FactorGraphs;
class ScheduleSequence extends Schedule
final class ScheduleSequence extends Schedule
{
/**
* @param Schedule[] $schedules
@ -13,6 +13,7 @@ class ScheduleSequence extends Schedule
{
}
#[\Override]
public function visit(int $depth = -1, int $maxDepth = 0): float
{
$maxDelta = 0;

View File

@ -4,12 +4,13 @@ declare(strict_types=1);
namespace DNW\Skills\FactorGraphs;
class ScheduleStep extends Schedule
final class ScheduleStep extends Schedule
{
public function __construct(private readonly Factor $factor, private readonly int $index)
{
}
#[\Override]
public function visit(int $depth = -1, int $maxDepth = 0): float
{
return $this->factor->updateMessageIndex($this->index);

View File

@ -4,9 +4,9 @@ declare(strict_types=1);
namespace DNW\Skills\FactorGraphs;
class VariableFactory
final readonly class VariableFactory
{
public function __construct(private readonly \Closure $varPriorInitializer)
public function __construct(private \Closure $varPriorInitializer)
{
}

View File

@ -7,30 +7,30 @@ namespace DNW\Skills;
/**
* Parameters about the game for calculating the TrueSkill.
*/
class GameInfo
final readonly class GameInfo
{
/**
* Default initial mean / 6
*/
private const DEFAULT_BETA = 4.1666666666666666666666666666667;
private const float DEFAULT_BETA = 4.1666666666666666666666666666667;
private const DEFAULT_DRAW_PROBABILITY = 0.10;
private const float DEFAULT_DRAW_PROBABILITY = 0.10;
/**
* Default initial mean / 300
*/
private const DEFAULT_DYNAMICS_FACTOR = 0.083333333333333333333333333333333;
private const float DEFAULT_DYNAMICS_FACTOR = 0.083333333333333333333333333333333;
private const DEFAULT_INITIAL_MEAN = 25.0;
private const float DEFAULT_INITIAL_MEAN = 25.0;
private const DEFAULT_INITIAL_STANDARD_DEVIATION = 8.3333333333333333333333333333333;
private const float DEFAULT_INITIAL_STANDARD_DEVIATION = 8.3333333333333333333333333333333;
public function __construct(
private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN,
private readonly float $initialStdDev = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
private readonly float $beta = self::DEFAULT_BETA,
private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
private readonly float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
private float $initialMean = self::DEFAULT_INITIAL_MEAN,
private float $initialStdDev = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
private float $beta = self::DEFAULT_BETA,
private float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
private float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
)
{
}

View File

@ -11,7 +11,7 @@ use Exception;
*
* @see http://www.moserware.com/2008/01/borrowing-ideas-from-3-interesting.html
*/
class Guard
final class Guard
{
public static function argumentIsValidIndex(int $index, int $count, string $parameterName): void
{
@ -23,7 +23,7 @@ class Guard
public static function argumentInRangeInclusive(float $value, float $min, float $max, string $parameterName): void
{
if (($value < $min) || ($value > $max)) {
throw new Exception($parameterName . ' is not in the valid range [' . $min . ', ' . $max . ']');
throw new Exception($parameterName . ' is not in the valid range [' . (int)$min . ', ' . (int)$max . ']');
}
}
}

View File

@ -7,7 +7,7 @@ namespace DNW\Skills;
/**
* Basic hashmap that supports object keys.
*/
class HashMap
final class HashMap
{
/**
* @var mixed[] $hashToValue

View File

@ -10,7 +10,7 @@ namespace DNW\Skills\Numerics;
* @author Jeff Moser <jeff@moserware.com>
* @copyright 2010 Jeff Moser
*/
class BasicMath
final class BasicMath
{
/**
* Squares the input (input^2 = input * input)

View File

@ -10,11 +10,11 @@ namespace DNW\Skills\Numerics;
* @author Jeff Moser <jeff@moserware.com>
* @copyright 2010 Jeff Moser
*/
class GaussianDistribution
final class GaussianDistribution
{
private const DEFAULT_STANDARD_DEVIATION = 1.0;
private const float DEFAULT_STANDARD_DEVIATION = 1.0;
private const DEFAULT_MEAN = 0.0;
private const float DEFAULT_MEAN = 0.0;
/**
* Square Root 2π.
@ -23,7 +23,7 @@ class GaussianDistribution
*
* @link https://www.wolframalpha.com/input?i=sqrt%282*pi%29 Source of value
*/
private const M_SQRT_2_PI = 2.5066282746310005024157652848110452530069867406099383166299235763;
private const float M_SQRT_2_PI = 2.5066282746310005024157652848110452530069867406099383166299235763;
/**
* Log of Square Root 2π.
@ -32,7 +32,7 @@ class GaussianDistribution
*
* @link https://www.wolframalpha.com/input?i=log%28sqrt%282*pi%29%29 Source of value
*/
private const M_LOG_SQRT_2_PI = 0.9189385332046727417803297364056176398613974736377834128171515404;
private const float M_LOG_SQRT_2_PI = 0.9189385332046727417803297364056176398613974736377834128171515404;
// precision and precisionMean are used because they make multiplying and dividing simpler
// (see the accompanying math paper for more details)
@ -171,7 +171,7 @@ class GaussianDistribution
$meanDifference = $numerator->mean - $denominator->mean;
return log($denominator->variance) + self::M_LOG_SQRT_2_PI - log($varianceDifference) / 2.0 +
BasicMath::square($meanDifference) / (2 * $varianceDifference);
BasicMath::square($meanDifference) / (2.0 * $varianceDifference);
}
public static function at(float $var, float $mean = 0.0, float $standardDeviation = 1.0): float
@ -182,7 +182,7 @@ class GaussianDistribution
// stdDev * sqrt(2*pi)
$multiplier = 1.0 / ($standardDeviation * self::M_SQRT_2_PI);
$expPart = exp((-1.0 * BasicMath::square($var - $mean)) / (2 * BasicMath::square($standardDeviation)));
$expPart = exp((-1.0 * BasicMath::square($var - $mean)) / (2.0 * BasicMath::square($standardDeviation)));
return $multiplier * $expPart;
}
@ -200,7 +200,7 @@ class GaussianDistribution
$z = abs($var);
$t = 2.0 / (2.0 + $z);
$ty = 4 * $t - 2;
$ty = 4.0 * $t - 2.0;
$coefficients = [
-1.3026537197817094,
@ -259,8 +259,8 @@ class GaussianDistribution
return 100;
}
$pp = ($p < 1.0) ? $p : 2 - $p;
$t = sqrt(-2 * log($pp / 2.0)); // Initial guess
$pp = ($p < 1.0) ? $p : 2.0 - $p;
$t = sqrt(-2.0 * log($pp / 2.0)); // Initial guess
$x = -M_SQRT1_2 * ((2.30753 + $t * 0.27061) / (1.0 + $t * (0.99229 + $t * 0.04481)) - $t);
for ($j = 0; $j < 2; ++$j) {
@ -274,6 +274,6 @@ class GaussianDistribution
public static function inverseCumulativeTo(float $var, float $mean = 0.0, float $standardDeviation = 1.0): float
{
// From numerical recipes, page 320
return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2 * $var);
return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2.0 * $var);
}
}

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace DNW\Skills\Numerics;
class IdentityMatrix extends DiagonalMatrix
final class IdentityMatrix extends DiagonalMatrix
{
public function __construct(int $rows)
{

View File

@ -8,7 +8,7 @@ use Exception;
class Matrix
{
public const ERROR_TOLERANCE = 0.0000000001;
public const float ERROR_TOLERANCE = 0.0000000001;
/**
* @param array<int,array<int,float>> $matrixRowData
@ -130,10 +130,10 @@ class Matrix
// | a b |
// | c d |
// The determinant is ad - bc
$a = $this->getValue(0, 0);
$b = $this->getValue(0, 1);
$c = $this->getValue(1, 0);
$d = $this->getValue(1, 1);
$a = (float)$this->getValue(0, 0);
$b = (float)$this->getValue(0, 1);
$c = (float)$this->getValue(1, 0);
$d = (float)$this->getValue(1, 1);
return $a * $d - $b * $c;
}
@ -148,7 +148,7 @@ class Matrix
// I expand along the first row
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
$firstRowColValue = $this->getValue(0, $currentColumn);
$firstRowColValue = (float)$this->getValue(0, $currentColumn);
$cofactor = $this->getCofactor(0, $currentColumn);
$itemToAdd = $firstRowColValue * $cofactor;
$result += $itemToAdd;
@ -201,7 +201,7 @@ class Matrix
public function getInverse(): Matrix|SquareMatrix
{
if (($this->rowCount == 1) && ($this->columnCount == 1)) {
return new SquareMatrix(1.0 / $this->getValue(0, 0));
return new SquareMatrix(1.0 / (float)$this->getValue(0, 0));
}
// Take the simple approach:
@ -240,9 +240,9 @@ class Matrix
for ($currentRow = 0; $currentRow < $left->getRowCount(); ++$currentRow) {
for ($currentColumn = 0; $currentColumn < $right->getColumnCount(); ++$currentColumn) {
$resultMatrix[$currentRow][$currentColumn] =
$left->getValue($currentRow, $currentColumn)
(float)$left->getValue($currentRow, $currentColumn)
+
$right->getValue($currentRow, $currentColumn);
(float)$right->getValue($currentRow, $currentColumn);
}
}
@ -268,8 +268,8 @@ class Matrix
$productValue = 0;
for ($vectorIndex = 0; $vectorIndex < $left->getColumnCount(); ++$vectorIndex) {
$leftValue = $left->getValue($currentRow, $vectorIndex);
$rightValue = $right->getValue($vectorIndex, $currentColumn);
$leftValue = (float)$left->getValue($currentRow, $vectorIndex);
$rightValue = (float)$right->getValue($vectorIndex, $currentColumn);
$vectorIndexProduct = $leftValue * $rightValue;
$productValue += $vectorIndexProduct;
}
@ -339,8 +339,8 @@ class Matrix
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
$delta =
abs(
$this->getValue($currentRow, $currentColumn) -
$otherMatrix->getValue($currentRow, $currentColumn)
(float)$this->getValue($currentRow, $currentColumn) -
(float)$otherMatrix->getValue($currentRow, $currentColumn)
);
if ($delta > self::ERROR_TOLERANCE) {

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace DNW\Skills\Numerics;
class SquareMatrix extends Matrix
final class SquareMatrix extends Matrix
{
public function __construct(float|int ...$allValues)
{

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace DNW\Skills\Numerics;
class Vector extends Matrix
final class Vector extends Matrix
{
/**
* @param float[] $vectorValues

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace DNW\Skills;
class PartialPlay
final class PartialPlay
{
public static function getPartialPlayPercentage(Player $player): float
{

View File

@ -7,15 +7,15 @@ namespace DNW\Skills;
/**
* Represents a player who has a Rating.
*/
class Player implements ISupportPartialPlay, ISupportPartialUpdate
final readonly class Player implements ISupportPartialPlay, ISupportPartialUpdate
{
private const DEFAULT_PARTIAL_PLAY_PERCENTAGE = 1.0; // = 100% play time
private const float DEFAULT_PARTIAL_PLAY_PERCENTAGE = 1.0; // = 100% play time
private const DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0;
private const float DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0;
private readonly float $PartialPlayPct;
private float $PartialPlayPct;
private readonly float $PartialUpdatePct;
private float $PartialUpdatePct;
/**
* Constructs a player.
@ -25,7 +25,7 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate
* @param float $partialUpdatePct 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 readonly mixed $Id,
private mixed $Id,
float $partialPlayPct = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE,
float $partialUpdatePct = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE
)
@ -47,6 +47,7 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate
/**
* Indicates the percent of the time the player should be weighted where 0.0 indicates the player didn't play and 1.0 indicates the player played 100% of the time.
*/
#[\Override]
public function getPartialPlayPercentage(): float
{
return $this->PartialPlayPct;
@ -55,6 +56,7 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate
/**
* Indicated how much of a skill update a player should receive where 0.0 represents no update and 1.0 represents 100% of the update.
*/
#[\Override]
public function getPartialUpdatePercentage(): float
{
return $this->PartialUpdatePct;

View File

@ -6,6 +6,6 @@ namespace DNW\Skills;
use DNW\Skills\Numerics\Range;
class PlayersRange extends Range
final class PlayersRange extends Range
{
}

View File

@ -7,7 +7,7 @@ namespace DNW\Skills;
/**
* Helper class to sort ranks in non-decreasing order.
*/
class RankSorter
final class RankSorter
{
/**
* Performs an in-place sort of the items in according to the ranks in non-decreasing order.

View File

@ -9,18 +9,18 @@ use DNW\Skills\Numerics\GaussianDistribution;
/**
* Container for a player's rating.
*/
class Rating
final readonly class Rating
{
private const CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3;
private const float CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3;
/**
* Constructs a rating.
*
* @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|int $conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating.
* @param float $conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating.
*/
public function __construct(private readonly float $mean, private readonly float $standardDeviation, private readonly float|int $conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER)
public function __construct(private float $mean, private float $standardDeviation, private float $conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER)
{
}

View File

@ -11,11 +11,11 @@ use Exception;
*/
abstract class SkillCalculator
{
public const NONE = 0x00;
public const int NONE = 0x00;
public const PARTIAL_PLAY = 0x01;
public const int PARTIAL_PLAY = 0x01;
public const PARTIAL_UPDATE = 0x02;
public const int PARTIAL_UPDATE = 0x02;
protected function __construct(
private readonly int $supportedOptions,

View File

@ -4,9 +4,9 @@ declare(strict_types=1);
namespace DNW\Skills;
class Team extends RatingContainer
final class Team extends RatingContainer
{
public function __construct(Player $player = NULL, Rating $rating = NULL)
public function __construct(?Player $player = NULL, ?Rating $rating = NULL)
{
parent::__construct();
if (! $player instanceof Player) {

View File

@ -6,6 +6,6 @@ namespace DNW\Skills;
use DNW\Skills\Numerics\Range;
class TeamsRange extends Range
final class TeamsRange extends Range
{
}

View File

@ -18,6 +18,6 @@ final class DrawMargin
//
// margin = inversecdf((draw probability + 1)/2) * sqrt(n1+n2) * beta
// n1 and n2 are the number of players on each team
return GaussianDistribution::inverseCumulativeTo(.5 * ($drawProbability + 1), 0, 1) * M_SQRT2 * $beta;
return GaussianDistribution::inverseCumulativeTo(0.5 * ($drawProbability + 1.0), 0.0, 1.0) * M_SQRT2 * $beta;
}
}

View File

@ -22,7 +22,7 @@ use DNW\Skills\Rating;
/**
* Calculates TrueSkill using a full factor graph.
*/
class FactorGraphTrueSkillCalculator extends SkillCalculator
final class FactorGraphTrueSkillCalculator extends SkillCalculator
{
public function __construct()
{
@ -32,6 +32,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
/**
* {@inheritdoc}
*/
#[\Override]
public function calculateNewRatings(
GameInfo $gameInfo,
array $teams,
@ -54,6 +55,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
/**
* {@inheritdoc}
*/
#[\Override]
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
{
// We need to create the A matrix which is the player team assigments.

View File

@ -14,6 +14,7 @@ abstract class GaussianFactor extends Factor
/**
* Sends the factor-graph message with and returns the log-normalization constant.
*/
#[\Override]
protected function sendMessageVariable(Message $message, Variable $variable): float|int
{
$marginal = $variable->getValue();
@ -24,6 +25,7 @@ abstract class GaussianFactor extends Factor
return $logZ;
}
#[\Override]
public function createVariableToMessageBinding(Variable $variable): Message
{
$newDistribution = GaussianDistribution::fromPrecisionMean(0, 0);

View File

@ -14,7 +14,7 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
*
* See the accompanying math paper for more details.
*/
class GaussianGreaterThanFactor extends GaussianFactor
final class GaussianGreaterThanFactor extends GaussianFactor
{
public function __construct(private readonly float $epsilon, Variable $variable)
{
@ -22,6 +22,7 @@ class GaussianGreaterThanFactor extends GaussianFactor
$this->createVariableToMessageBinding($variable);
}
#[\Override]
public function getLogNormalization(): float
{
$vars = $this->getVariables();
@ -42,6 +43,7 @@ class GaussianGreaterThanFactor extends GaussianFactor
);
}
#[\Override]
protected function updateMessageVariable(Message $message, Variable $variable): float
{
$oldMarginal = clone $variable->getValue();

View File

@ -15,7 +15,7 @@ use Exception;
*
* See the accompanying math paper for more details.
*/
class GaussianLikelihoodFactor extends GaussianFactor
final class GaussianLikelihoodFactor extends GaussianFactor
{
private readonly float $precision;
@ -28,6 +28,7 @@ class GaussianLikelihoodFactor extends GaussianFactor
$this->createVariableToMessageBinding($variable2);
}
#[\Override]
public function getLogNormalization(): float
{
/**
@ -73,6 +74,7 @@ class GaussianLikelihoodFactor extends GaussianFactor
return GaussianDistribution::subtract($newMarginal, $marginal1);
}
#[\Override]
public function updateMessageIndex(int $messageIndex): float
{
$messages = $this->getMessages();

View File

@ -13,7 +13,7 @@ use DNW\Skills\Numerics\GaussianDistribution;
*
* See the accompanying math paper for more details.
*/
class GaussianPriorFactor extends GaussianFactor
final class GaussianPriorFactor extends GaussianFactor
{
private readonly GaussianDistribution $newMessage;
@ -29,6 +29,7 @@ class GaussianPriorFactor extends GaussianFactor
$this->createVariableToMessageBindingWithMessage($variable, $newMessage);
}
#[\Override]
protected function updateMessageVariable(Message $message, Variable $variable): float
{
$oldMarginal = clone $variable->getValue();

View File

@ -16,7 +16,7 @@ use DNW\Skills\Numerics\GaussianDistribution;
*
* See the accompanying math paper for more details.
*/
class GaussianWeightedSumFactor extends GaussianFactor
final class GaussianWeightedSumFactor extends GaussianFactor
{
/**
* @var array<int[]> $varIndexOrdersForWeights
@ -127,6 +127,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
}
}
#[\Override]
public function getLogNormalization(): float
{
$vars = $this->getVariables();
@ -197,6 +198,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
return $finalDiff;
}
#[\Override]
public function updateMessageIndex(int $messageIndex): float
{
$allMessages = $this->getMessages();

View File

@ -14,7 +14,7 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
*
* See the accompanying math paper for more details.
*/
class GaussianWithinFactor extends GaussianFactor
final class GaussianWithinFactor extends GaussianFactor
{
public function __construct(private readonly float $epsilon, Variable $variable)
{
@ -23,6 +23,7 @@ class GaussianWithinFactor extends GaussianFactor
$this->createVariableToMessageBinding($variable);
}
#[\Override]
public function getLogNormalization(): float
{
/**
@ -46,6 +47,7 @@ class GaussianWithinFactor extends GaussianFactor
return -GaussianDistribution::logProductNormalization($messageFromVariable, $message) + log($z);
}
#[\Override]
protected function updateMessageVariable(Message $message, Variable $variable): float
{
$oldMarginal = clone $variable->getValue();

View File

@ -11,7 +11,7 @@ use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
use Exception;
// The whole purpose of this is to do a loop on the bottom
class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
final class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
{
public function __construct(
TrueSkillFactorGraph $parentGraph,
@ -22,6 +22,7 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
parent::__construct($parentGraph);
}
#[\Override]
public function getLocalFactors(): array
{
return array_merge(
@ -30,6 +31,7 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
);
}
#[\Override]
public function buildLayer(): void
{
$inputVariablesGroups = $this->getInputVariablesGroups();
@ -41,6 +43,7 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
$this->teamDifferencesComparisonLayer->buildLayer();
}
#[\Override]
public function createPriorSchedule(): ?ScheduleSequence
{
switch (count($this->getInputVariablesGroups())) {

View File

@ -11,8 +11,9 @@ use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor;
use DNW\Skills\FactorGraphs\Variable;
use DNW\Skills\FactorGraphs\KeyedVariable;
class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLayer
final class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLayer
{
#[\Override]
public function buildLayer(): void
{
$inputVariablesGroups = $this->getInputVariablesGroups();
@ -32,6 +33,7 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
}
}
#[\Override]
public function createPriorSchedule(): ?ScheduleSequence
{
$localFactors = $this->getLocalFactors();
@ -49,7 +51,7 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
/**
* @param KeyedVariable[] $teamMembers
*/
protected function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor
private function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor
{
$weights = array_map(
static function ($v): float {
@ -66,6 +68,7 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
);
}
#[\Override]
public function createPosteriorSchedule(): ?ScheduleSequence
{
$allFactors = [];

View File

@ -17,7 +17,7 @@ use DNW\Skills\FactorGraphs\ScheduleSequence;
// We intentionally have no Posterior schedule since the only purpose here is to
// start the process.
class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
final class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
{
/**
* @param Team[] $teams
@ -27,6 +27,7 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
parent::__construct($parentGraph);
}
#[\Override]
public function buildLayer(): void
{
$teams = $this->teams;
@ -49,6 +50,7 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
}
}
#[\Override]
public function createPriorSchedule(): ?ScheduleSequence
{
$localFactors = $this->getLocalFactors();

View File

@ -11,8 +11,9 @@ use DNW\Skills\Numerics\BasicMath;
use DNW\Skills\TrueSkill\Factors\GaussianLikelihoodFactor;
use DNW\Skills\FactorGraphs\ScheduleSequence;
class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
final class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
{
#[\Override]
public function buildLayer(): void
{
$inputVarGroups = $this->getInputVariablesGroups();
@ -51,6 +52,7 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
return $this->getParentFactorGraph()->getVariableFactory()->createKeyedVariable($key);
}
#[\Override]
public function createPriorSchedule(): ?ScheduleSequence
{
$localFactors = $this->getLocalFactors();
@ -65,6 +67,7 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
);
}
#[\Override]
public function createPosteriorSchedule(): ?ScheduleSequence
{
$localFactors = $this->getLocalFactors();

View File

@ -9,7 +9,7 @@ use DNW\Skills\TrueSkill\Factors\GaussianGreaterThanFactor;
use DNW\Skills\TrueSkill\Factors\GaussianWithinFactor;
use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
final class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
{
private readonly float $epsilon;
@ -23,6 +23,7 @@ class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
$this->epsilon = DrawMargin::getDrawMarginFromDrawProbability($gameInfo->getDrawProbability(), $gameInfo->getBeta());
}
#[\Override]
public function buildLayer(): void
{
$inputVarGroups = $this->getInputVariablesGroups();

View File

@ -7,8 +7,9 @@ namespace DNW\Skills\TrueSkill\Layers;
use DNW\Skills\FactorGraphs\Variable;
use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor;
class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorGraphLayer
final class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorGraphLayer
{
#[\Override]
public function buildLayer(): void
{
$inputVariablesGroups = $this->getInputVariablesGroups();

View File

@ -23,7 +23,7 @@ use DNW\Skills\TrueSkill\Layers\PlayerSkillsToPerformancesLayer;
use DNW\Skills\TrueSkill\Layers\TeamDifferencesComparisonLayer;
use DNW\Skills\TrueSkill\Layers\TeamPerformancesToTeamPerformanceDifferencesLayer;
class TrueSkillFactorGraph extends FactorGraph
final class TrueSkillFactorGraph extends FactorGraph
{
/**
* @var FactorGraphLayer[] $layers

View File

@ -6,7 +6,7 @@ namespace DNW\Skills\TrueSkill;
use DNW\Skills\Numerics\GaussianDistribution;
class TruncatedGaussianCorrectionFunctions
final class TruncatedGaussianCorrectionFunctions
{
// These functions from the bottom of page 4 of the TrueSkill paper.

View File

@ -21,7 +21,7 @@ use DNW\Skills\TeamsRange;
* When you only have two players, a lot of the math simplifies. The main purpose of this class
* is to show the bare minimum of what a TrueSkill implementation should have.
*/
class TwoPlayerTrueSkillCalculator extends SkillCalculator
final class TwoPlayerTrueSkillCalculator extends SkillCalculator
{
public function __construct()
{
@ -31,6 +31,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
/**
* {@inheritdoc}
*/
#[\Override]
public function calculateNewRatings(
GameInfo $gameInfo,
array $teams,
@ -94,7 +95,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
+
BasicMath::square($opponentRating->getStandardDeviation())
+
2 * BasicMath::square($gameInfo->getBeta())
2.0 * BasicMath::square($gameInfo->getBeta())
);
$winningMean = $selfRating->getMean();
@ -130,7 +131,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
$stdDevMultiplier = $varianceWithDynamics / BasicMath::square($c);
$newMean = $selfRating->getMean() + ($rankMultiplier * $meanMultiplier * $v);
$newStdDev = sqrt($varianceWithDynamics * (1 - $w * $stdDevMultiplier));
$newStdDev = sqrt($varianceWithDynamics * (1.0 - $w * $stdDevMultiplier));
return new Rating($newMean, $newStdDev);
}
@ -138,6 +139,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
/**
* {@inheritdoc}
*/
#[\Override]
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
{
$this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -158,16 +160,16 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
// This is the square root part of the equation:
$sqrtPart = sqrt(
(2 * $betaSquared)
(2.0 * $betaSquared)
/
(2 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared)
(2.0 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared)
);
// This is the exponent part of the equation:
$expPart = exp(
(-1 * BasicMath::square($player1Rating->getMean() - $player2Rating->getMean()))
(-1.0 * BasicMath::square($player1Rating->getMean() - $player2Rating->getMean()))
/
(2 * (2 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared))
(2.0 * (2.0 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared))
);
return $sqrtPart * $expPart;

View File

@ -21,7 +21,7 @@ use DNW\Skills\TeamsRange;
*
* When you only have two teams, the math is still simple: no factor graphs are used yet.
*/
class TwoTeamTrueSkillCalculator extends SkillCalculator
final class TwoTeamTrueSkillCalculator extends SkillCalculator
{
public function __construct()
{
@ -31,6 +31,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
/**
* {@inheritdoc}
*/
#[\Override]
public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer
{
$this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -121,7 +122,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
// assume draw
$v = TruncatedGaussianCorrectionFunctions::vWithinMarginScaled($meanDelta, $drawMargin, $c);
$w = TruncatedGaussianCorrectionFunctions::wWithinMarginScaled($meanDelta, $drawMargin, $c);
$rankMultiplier = 1;
$rankMultiplier = 1.0;
}
$selfTeamAllPlayers = $selfTeam->getAllPlayers();
@ -136,7 +137,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
$newMean = $previousPlayerRating->getMean() + $playerMeanDelta;
$newStdDev = sqrt(
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1 - $w * $stdDevMultiplier)
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1.0 - $w * $stdDevMultiplier)
);
$newPlayerRatings->setRating($localSelfTeamCurPlayer, new Rating($newMean, $newStdDev));
@ -146,6 +147,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
/**
* {@inheritdoc}
*/
#[\Override]
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
{
$this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -182,9 +184,9 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
);
$expPart = exp(
(-1 * BasicMath::square($team1MeanSum - $team2MeanSum))
(-1.0 * BasicMath::square($team1MeanSum - $team2MeanSum))
/
(2 * ($totalPlayers * $betaSquared + $team1StdDevSquared + $team2StdDevSquared))
(2.0 * ($totalPlayers * $betaSquared + $team1StdDevSquared + $team2StdDevSquared))
);
return $expPart * $sqrtPart;

View File

@ -12,7 +12,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(Variable::class)]
#[UsesClass(GaussianDistribution::class)]
class VariableTest extends TestCase
final class VariableTest extends TestCase
{
public function testGetterSetter(): void
{

View File

@ -12,7 +12,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(GameInfo::class)]
#[UsesClass(Rating::class)]
class GameInfoTest extends TestCase
final class GameInfoTest extends TestCase
{
public function testMembers(): void
{

View File

@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(Guard::class)]
class GuardTest extends TestCase
final class GuardTest extends TestCase
{
public function testargumentIsValidIndexArgumentAbove(): void
{

View File

@ -10,7 +10,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
use stdClass;
#[CoversClass(HashMap::class)]
class HashMapTest extends TestCase
final class HashMapTest extends TestCase
{
public function testHashmap(): void
{

View File

@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(BasicMath::class)]
class BasicMathTest extends TestCase
final class BasicMathTest extends TestCase
{
public function testSquare(): void
{
@ -23,7 +23,7 @@ class BasicMathTest extends TestCase
$arr = [1, 1, 1, 1];
$func_return = static fn(float $f): float => $f;
$func_double = static fn(float $f): float => $f * 2;
$func_double = static fn(float $f): float => $f * 2.0;
$this->assertEquals(4, BasicMath::sum($arr, $func_return));
$this->assertEquals(8, BasicMath::sum($arr, $func_double));
}

View File

@ -12,9 +12,9 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(GaussianDistribution::class)]
#[UsesClass(BasicMath::class)]
class GaussianDistributionTest extends TestCase
final class GaussianDistributionTest extends TestCase
{
private const ERROR_TOLERANCE = 0.000001;
private const float ERROR_TOLERANCE = 0.000001;
public function testGetters(): void
{
@ -24,7 +24,7 @@ class GaussianDistributionTest extends TestCase
$this->assertEquals(9, $gd->getVariance());
$this->assertEquals(3, $gd->getStandardDeviation());
$this->assertEquals(1 / 9, $gd->getPrecision());
$this->assertEquals(1 / 9 * 10, $gd->getPrecisionMean());
$this->assertEquals(1.0 / 9.0 * 10.0, $gd->getPrecisionMean());
$this->assertEqualsWithDelta(0.13298076013, $gd->getNormalizationConstant(), GaussianDistributionTest::ERROR_TOLERANCE);
}
@ -57,7 +57,7 @@ class GaussianDistributionTest extends TestCase
$product2 = GaussianDistribution::multiply($m4s5, $m6s7);
$expectedMean = (4 * BasicMath::square(7) + 6 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7));
$expectedMean = (4.0 * BasicMath::square(7) + 6.0 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7));
$this->assertEqualsWithDelta($expectedMean, $product2->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
$expectedSigma = sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7))));
@ -74,7 +74,7 @@ class GaussianDistributionTest extends TestCase
$this->assertEqualsWithDelta(2.0, $productDividedByStandardNormal->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
$this->assertEqualsWithDelta(3.0, $productDividedByStandardNormal->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
$product2 = new GaussianDistribution((4 * BasicMath::square(7) + 6 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7)), sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7)))));
$product2 = new GaussianDistribution((4.0 * BasicMath::square(7) + 6.0 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7)), sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7)))));
$m4s5 = new GaussianDistribution(4, 5);
$product2DividedByM4S5 = GaussianDistribution::divide($product2, $m4s5);
$this->assertEqualsWithDelta(6.0, $product2DividedByM4S5->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);

View File

@ -20,7 +20,7 @@ use Exception;
#[CoversClass(DiagonalMatrix::class)]
#[CoversClass(Vector::class)]
// phpcs:disable PSR2.Methods.FunctionCallSignature,Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
class MatrixTest extends TestCase
final class MatrixTest extends TestCase
{
public function testEmptyMatrix(): void
{
@ -295,7 +295,7 @@ class MatrixTest extends TestCase
1, 0, 6);
$cInverse = $c->getInverse();
$d = Matrix::scalarMultiply((1.0 / 22), new SquareMatrix(24, -12, -2,
$d = Matrix::scalarMultiply((1.0 / 22.0), new SquareMatrix(24, -12, -2,
5, 3, -5,
-4, 2, 4));

View File

@ -10,7 +10,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
use Exception;
#[CoversClass(Range::class)]
class RangeTest extends TestCase
final class RangeTest extends TestCase
{
public function testConstructInvalidParam(): void
{

View File

@ -14,7 +14,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(PartialPlay::class)]
#[UsesClass(Player::class)]
#[UsesClass(Guard::class)]
class PartialPlayTest extends TestCase
final class PartialPlayTest extends TestCase
{
public function testgetPartialPlayPercentage(): void
{

View File

@ -12,7 +12,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(Player::class)]
#[UsesClass(Guard::class)]
class PlayerTest extends TestCase
final class PlayerTest extends TestCase
{
public function testPlayerObjectGetterSetter(): void
{

View File

@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(RankSorter::class)]
class RankSorterTest extends TestCase
final class RankSorterTest extends TestCase
{
public function testSort(): void
{

View File

@ -18,7 +18,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[UsesClass(Player::class)]
#[UsesClass(Rating::class)]
#[UsesClass(Guard::class)]
class RatingContainerTest extends TestCase
final class RatingContainerTest extends TestCase
{
public function testRatingContainer(): void
{

View File

@ -14,7 +14,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(Rating::class)]
#[UsesClass(BasicMath::class)]
#[UsesClass(GaussianDistribution::class)]
class RatingTest extends TestCase
final class RatingTest extends TestCase
{
public function testGetRatingParameters(): void
{

View File

@ -17,7 +17,7 @@ use PHPUnit\Framework\Attributes\RequiresPhpunit;
#[UsesClass(PlayersRange::class)]
#[UsesClass(TeamsRange::class)]
#[RequiresPhpunit('<12.0')]
class SkillCalculatorTest extends TestCase
final class SkillCalculatorTest extends TestCase
{
public function testisSupported(): void
{

View File

@ -20,7 +20,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[UsesClass(Player::class)]
#[UsesClass(Rating::class)]
#[UsesClass(Guard::class)]
class TeamTest extends TestCase
final class TeamTest extends TestCase
{
public function testTeam(): void
{

View File

@ -14,9 +14,9 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(DrawMargin::class)]
#[UsesClass(BasicMath::class)]
#[UsesClass(GaussianDistribution::class)]
class DrawMarginTest extends TestCase
final class DrawMarginTest extends TestCase
{
private const ERROR_TOLERANCE = 0.000001;
private const float ERROR_TOLERANCE = 0.000001;
public function testGetDrawMarginFromDrawProbability(): void
{

View File

@ -19,7 +19,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[UsesClass(\DNW\Skills\PlayersRange::class)]
#[UsesClass(\DNW\Skills\SkillCalculator::class)]
#[UsesClass(\DNW\Skills\TeamsRange::class)]
class FactorGraphTrueSkillCalculatorTest extends TestCase
final class FactorGraphTrueSkillCalculatorTest extends TestCase
{
#[CoversNothing]
public function testMicrosoftResearchExample(): void

View File

@ -11,11 +11,11 @@ use DNW\Skills\SkillCalculator;
use DNW\Skills\Team;
use PHPUnit\Framework\TestCase;
class TrueSkillCalculatorTests
final class TrueSkillCalculatorTests
{
private const ERROR_TOLERANCE_TRUESKILL = 0.085;
private const float ERROR_TOLERANCE_TRUESKILL = 0.085;
private const ERROR_TOLERANCE_MATCH_QUALITY = 0.0005;
private const float ERROR_TOLERANCE_MATCH_QUALITY = 0.0005;
// These are the roll-up ones

View File

@ -14,7 +14,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(TruncatedGaussianCorrectionFunctions::class)]
#[UsesClass(BasicMath::class)]
#[UsesClass(GaussianDistribution::class)]
class TruncatedGaussianCorrectionFunctionsTest extends TestCase
final class TruncatedGaussianCorrectionFunctionsTest extends TestCase
{
public function testvGreaterThan(): void
{

View File

@ -10,7 +10,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversNothing;
#[CoversClass(TwoPlayerTrueSkillCalculator::class)]
class TwoPlayerTrueSkillCalculatorTest extends TestCase
final class TwoPlayerTrueSkillCalculatorTest extends TestCase
{
#[CoversNothing]
public function testTwoPlayerTrueSkillCalculator(): void

View File

@ -10,7 +10,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversNothing;
#[CoversClass(TwoTeamTrueSkillCalculator::class)]
class TwoTeamTrueSkillCalculatorTest extends TestCase
final class TwoTeamTrueSkillCalculatorTest extends TestCase
{
#[CoversNothing]
public function testTwoTeamTrueSkillCalculator(): void