Compare commits

..

23 Commits

Author SHA1 Message Date
1d06fa0b0a Requirement update 2025-06-24 12:55:00 +00:00
a097fa7f71 Few more comments 2025-06-18 11:49:40 +00:00
ad9d7f94de Upodate PHPUnit to latest version 2025-06-06 14:24:46 +00:00
1725979b1c Bumping versions 2025-06-02 08:49:46 +00:00
ac7e3f5c2d CodeStandard as HTML / MarkDown 2025-05-14 09:28:51 +00:00
adc587ac0d More coding standards.
Diagrams in API documentation
2025-05-13 12:46:11 +00:00
d729caac1f Simplifying some casts 2025-05-12 09:40:44 +00:00
bd9fccb87b Regular updates and fixing static analysis findings 2025-05-12 09:19:17 +00:00
55656b7889 More standards 2025-04-29 13:21:47 +00:00
38f332b223 Continuing the process of making this modern 2025-04-25 09:22:08 +00:00
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
2212d45f61 Package update and implemented new fixes for Rector findings 2025-01-13 08:57:13 +00:00
24665d6e57 PHPCBF added
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-13 07:26:48 +00:00
79f4495a77 Package update
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-12 13:36:35 +00:00
9df860b08b SkillTest deprecated method
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-08-16 07:57:08 +00:00
1e381bfab7 Quality of life
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-07-31 07:27:13 +00:00
f6acee18e5 More tests more resistance to mutation testing.
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-07-25 11:24:13 +00:00
1b7e26a6b5 Requirements update. (Psalm to dev but analysis tool only) 2024-07-23 10:04:02 +00:00
cf588f3fc2 PHPMD reintroduced. 2024-07-04 09:38:03 +00:00
90 changed files with 61920 additions and 2788 deletions

7
.gitignore vendored
View File

@ -1,6 +1,5 @@
.*/ vendor/
vendor
.*.cache/ .*.cache/
*.phar
.phpdoc/ .phpdoc/
output/ output/
tools/

12
.phive/phars.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpmd" version="^2.15.0" installed="2.15.0" location="./tools/phpmd" copy="false"/>
<phar name="phpstan" version="^2.1.12" installed="2.1.17" location="./tools/phpstan" copy="false"/>
<phar name="psalm" version="^7.0.0-beta6" installed="7.0.0-beta9" location="./tools/psalm" copy="false"/>
<phar name="phpcs" version="^3.12.2" installed="3.13.2" location="./tools/phpcs" copy="false"/>
<phar name="phpcbf" version="^3.12.2" installed="3.13.2" location="./tools/phpcbf" copy="false"/>
<phar name="phpdocumentor" version="^3.7.1" installed="3.8.0" location="./tools/phpdocumentor" copy="false"/>
<phar name="phpbench" version="^1.4.1" installed="1.4.1" location="./tools/phpbench" copy="false"/>
<phar name="infection" version="^0.29.14" installed="0.29.14" location="./tools/infection" copy="false"/>
<phar name="phpunit" version="^12.1.3" installed="12.2.3" location="./tools/phpunit" copy="false"/>
</phive>

View File

@ -6,6 +6,11 @@
<file>tests/</file> <file>tests/</file>
<file>benchmark/</file> <file>benchmark/</file>
<arg name="basepath" value="."/>
<arg name="colors"/>
<arg name="parallel" value="8"/>
<arg name="report" value="emacs"/>
<arg value="p"/>
<rule ref="PSR1"> <rule ref="PSR1">
<exclude name="Generic.Files.LineLength"/> <exclude name="Generic.Files.LineLength"/>
@ -18,23 +23,19 @@
<exclude name="Generic.Files.LowercasedFilename.NotFound"/> <exclude name="Generic.Files.LowercasedFilename.NotFound"/>
<exclude name="Generic.PHP.ClosingPHPTag.NotFound"/> <exclude name="Generic.PHP.ClosingPHPTag.NotFound"/>
<exclude name="Generic.Files.EndFileNoNewline.Found"/> <exclude name="Generic.Files.EndFileNoNewline.Found"/>
<exclude name="Generic.Files.EndFileNoNewline.Found"/>
<exclude name="Generic.Arrays.DisallowShortArraySyntax.Found"/> <exclude name="Generic.Arrays.DisallowShortArraySyntax.Found"/>
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine"/> <exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine"/>
<exclude name="Generic.Classes.OpeningBraceSameLine.BraceOnNewLine"/> <exclude name="Generic.Classes.OpeningBraceSameLine.BraceOnNewLine"/>
<exclude name="Generic.PHP.LowerCaseConstant.Found"/> <exclude name="Generic.PHP.LowerCaseConstant.Found"/>
<exclude name="Generic.Formatting.SpaceAfterCast"/>
<exclude name="Generic.Formatting.MultipleStatementAlignment.NotSameWarning"/> <exclude name="Generic.Formatting.MultipleStatementAlignment.NotSameWarning"/>
<exclude name="Generic.Commenting.DocComment.MissingShort"/> <exclude name="Generic.Commenting.DocComment.MissingShort"/>
<exclude name="Generic.NamingConventions.AbstractClassNamePrefix.Missing"/> <exclude name="Generic.NamingConventions.AbstractClassNamePrefix.Missing"/>
<exclude name="Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed"/> <exclude name="Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed"/>
<exclude name="Generic.NamingConventions.InterfaceNameSuffix.Missing"/> <exclude name="Generic.NamingConventions.InterfaceNameSuffix.Missing"/>
<exclude name="Generic.Commenting.Todo.TaskFound"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUse"/> <exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUse"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed"/> <exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed"/> <exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed"/>
<exclude name="Generic.Formatting.SpaceBeforeCast.NoSpace"/>
<exclude name="Generic.CodeAnalysis.UselessOverridingMethod.Found"/>
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration.NewlineBeforeOpenBrace"/> <exclude name="Squiz.Functions.MultiLineFunctionDeclaration.NewlineBeforeOpenBrace"/>
</rule> </rule>
@ -50,4 +51,25 @@
</property> </property>
</properties> </properties>
</rule> </rule>
<rule ref="Generic.Formatting.SpaceAfterCast">
<properties>
<property name="spacing" value="0"/>
</properties>
</rule>
<!-- Do not allow unreachable code. -->
<rule ref="Squiz.PHP.NonExecutableCode"/>
<!-- Do not allow ambiguous conditions. -->
<rule ref="Generic.CodeAnalysis.RequireExplicitBooleanOperatorPrecedence"/>
<!-- The testing bootstrap file uses string concats to stop IDEs seeing the class aliases -->
<rule ref="Generic.Strings.UnnecessaryStringConcat" />
<!-- This test file specifically *needs* Windows line endings for testing purposes. -->
<rule ref="Generic.Files.LineEndings.InvalidEOLChar" />
<!-- Avoid false positive with this sniff detecting itself -->
<rule ref="Generic.Commenting.Todo"/>
</ruleset> </ruleset>

View File

@ -1,12 +0,0 @@
path:
- src/
- tests/
- benchmark/
jobs: 10
extensions:
- php
exclude:
- vendor
warning: true
memory-limit: -1
log-junit: "output/lint.xml"

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"php.version": "8.4"
}

View File

@ -1,3 +1,5 @@
when:
- event: [push, tag]
steps: steps:
- name: requirements - name: requirements
image: composer image: composer
@ -8,7 +10,7 @@ steps:
commands: commands:
- php examples/3teams.php - php examples/3teams.php
- php examples/basic.php - php examples/basic.php
- name: dependencies - name: test
image: composer image: composer
commands: commands:
- composer install - composer install

4317
CodeStandard.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ use DNW\Skills\Team;
/** /**
* Basic Benchmarks. * Basic Benchmarks.
*/ */
class BasicBench final class BasicBench
{ {
/** /**
* To benchmark performance when using TwoPlayerTrueSkillCalculator * To benchmark performance when using TwoPlayerTrueSkillCalculator
@ -41,9 +41,11 @@ class BasicBench
$team1 = new Team($p1, $newRatings->getRating($p1)); $team1 = new Team($p1, $newRatings->getRating($p1));
$team2 = new Team($p2, $newRatings->getRating($p2)); $team2 = new Team($p2, $newRatings->getRating($p2));
$newRatings->getRating($p1)->getConservativeRating(); ob_start();
$newRatings->getRating($p2)->getConservativeRating(); echo $newRatings->getRating($p1)->getConservativeRating();
echo $newRatings->getRating($p2)->getConservativeRating();
ob_clean();
} }
} }
@ -73,8 +75,10 @@ class BasicBench
$team1 = new Team($p1, $newRatings->getRating($p1)); $team1 = new Team($p1, $newRatings->getRating($p1));
$team2 = new Team($p2, $newRatings->getRating($p2)); $team2 = new Team($p2, $newRatings->getRating($p2));
$newRatings->getRating($p1)->getConservativeRating(); ob_start();
$newRatings->getRating($p2)->getConservativeRating(); echo $newRatings->getRating($p1)->getConservativeRating();
echo $newRatings->getRating($p2)->getConservativeRating();
ob_clean();
} }
} }
@ -104,8 +108,10 @@ class BasicBench
$team1 = new Team($p1, $newRatings->getRating($p1)); $team1 = new Team($p1, $newRatings->getRating($p1));
$team2 = new Team($p2, $newRatings->getRating($p2)); $team2 = new Team($p2, $newRatings->getRating($p2));
$newRatings->getRating($p1)->getConservativeRating(); ob_start();
$newRatings->getRating($p2)->getConservativeRating(); echo $newRatings->getRating($p1)->getConservativeRating();
echo $newRatings->getRating($p2)->getConservativeRating();
ob_clean();
} }
} }
@ -138,9 +144,11 @@ class BasicBench
$team2 = new Team($p2, $newRatings->getRating($p2)); $team2 = new Team($p2, $newRatings->getRating($p2));
$team3 = new Team($p3, $newRatings->getRating($p3)); $team3 = new Team($p3, $newRatings->getRating($p3));
$newRatings->getRating($p1)->getConservativeRating(); ob_start();
$newRatings->getRating($p2)->getConservativeRating(); echo $newRatings->getRating($p1)->getConservativeRating();
$newRatings->getRating($p3)->getConservativeRating(); echo $newRatings->getRating($p2)->getConservativeRating();
echo $newRatings->getRating($p3)->getConservativeRating();
ob_clean();
} }
} }
} }

View File

@ -1,16 +1,19 @@
{ {
"name": "dnw/php-trueskill", "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"], "keywords": [
"trueskill",
"matchmaking",
"ranking",
"skill",
"elo"
],
"require": { "require": {
"php": "^8.2" "php": "^8.4"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.0",
"vimeo/psalm": "^5.21.1",
"phpmetrics/phpmetrics": "^3.0-dev", "phpmetrics/phpmetrics": "^3.0-dev",
"phpunit/phpunit": "^10.5", "phpunit/phpunit": "^11.2",
"psalm/plugin-phpunit": "^0.18.4",
"rector/rector": "^1.0", "rector/rector": "^1.0",
"league/csv": "^9.0" "league/csv": "^9.0"
}, },
@ -25,34 +28,35 @@
} }
}, },
"scripts": { "scripts": {
"test": "phpunit", "test": "tools/phpunit",
"document": "phpDocumentor", "document": "tools/phpdocumentor",
"benchmark": "phpbench run --report=default --output=build-artifact", "benchmark": "tools/phpbench run --report=default --output=build-artifact",
"metrics": "vendor/bin/phpmetrics --config=phpmetrics.json", "metrics": "phpmetrics --config=phpmetrics.yml",
"lint": [ "lint": [
"phplint", "tools/phpcbf",
"phpcs" "tools/phpcs",
"tools/phpmd src/,tests/,benchmark/,examples/ text phpmd.ruleset.xml"
], ],
"analyze": [ "analyze": [
"@analyze-phpstan", "@analyze-phpstan",
"@analyze-psalm", "@analyze-psalm",
"@analyze-rector" "@analyze-rector"
], ],
"analyze-phpstan":"vendor/bin/phpstan analyze --error-format=raw", "analyze-phpstan": "tools/phpstan analyze --error-format=raw",
"analyze-psalm": "vendor/bin/psalm --no-cache --show-info=true", "analyze-psalm": "tools/psalm --show-info=true",
"analyze-rector": "vendor/bin/rector --dry-run", "analyze-rector": "rector --dry-run",
"html": [ "html": [
"pandoc -s README.md -o output/README.html", "pandoc -s README.md -o output/README.html",
"pandoc -s docs/index.rst -o output/index.html" "pandoc -s docs/index.rst -o output/index.html",
"tools/phpcs --generator=MarkDown | pandoc -o output/CodeStandard.html --metadata title=\"Code Standard\""
], ],
"all": [ "all": [
"@test", "@test",
"@document",
"@benchmark",
"@lint", "@lint",
"@analyze", "@analyze",
"@document",
"@metrics", "@metrics",
"@html" "@html"
] ]
} }
} }

2740
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ Links
* `Test report <test/index.html>`_ * `Test report <test/index.html>`_
* `Mutation testing <mutation/infection.html>`_ * `Mutation testing <mutation/infection.html>`_
* `Code metrics <metrics/index.html>`_ * `Code metrics <metrics/index.html>`_
* `Code Standard <CodeStandard.html>`_
Standard Tools Standard Tools

View File

@ -20,7 +20,7 @@ $team2 = new Team($p2, $gameInfo->getDefaultRating());
$team3 = new Team($p3, $gameInfo->getDefaultRating()); $team3 = new Team($p3, $gameInfo->getDefaultRating());
for($i = 0; $i < 5; ++$i) { for ($i = 0; $i < 5; ++$i) {
echo "Iteration: " . $i . PHP_EOL; echo "Iteration: " . $i . PHP_EOL;
$teams = [$team1, $team2, $team3]; $teams = [$team1, $team2, $team3];
@ -32,10 +32,7 @@ for($i = 0; $i < 5; ++$i) {
$team2 = new Team($p2, $newRatings->getRating($p2)); $team2 = new Team($p2, $newRatings->getRating($p2));
$team3 = new Team($p3, $newRatings->getRating($p3)); $team3 = new Team($p3, $newRatings->getRating($p3));
echo "P1: ". $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL; echo "P1: " . $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
echo "P2: ". $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL; echo "P2: " . $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
echo "P3: ". $newRatings->getRating($p3)->getConservativeRating() . PHP_EOL; echo "P3: " . $newRatings->getRating($p3)->getConservativeRating() . PHP_EOL;
} }

View File

@ -19,7 +19,7 @@ $team1 = new Team($p1, $gameInfo->getDefaultRating());
$team2 = new Team($p2, $gameInfo->getDefaultRating()); $team2 = new Team($p2, $gameInfo->getDefaultRating());
for($i = 0; $i < 5; ++$i) { for ($i = 0; $i < 5; ++$i) {
echo "Iteration: " . $i . PHP_EOL; echo "Iteration: " . $i . PHP_EOL;
$teams = [$team1, $team2]; $teams = [$team1, $team2];
@ -30,9 +30,6 @@ for($i = 0; $i < 5; ++$i) {
$team1 = new Team($p1, $newRatings->getRating($p1)); $team1 = new Team($p1, $newRatings->getRating($p1));
$team2 = new Team($p2, $newRatings->getRating($p2)); $team2 = new Team($p2, $newRatings->getRating($p2));
echo "P1: ". $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL; echo "P1: " . $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
echo "P2: ". $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL; echo "P2: " . $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
} }

90
examples/motogp/goat.php Normal file
View File

@ -0,0 +1,90 @@
<?php
require __DIR__ . "/../../vendor/autoload.php";
use League\Csv\Reader;
use League\Csv\Statement;
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
use DNW\Skills\GameInfo;
use DNW\Skills\Player;
use DNW\Skills\Team;
use DNW\Skills\Teams;
//load the CSV document from a stream
$stream = fopen('motogp.csv', 'r');
$csv = Reader::createFromStream($stream);
$csv->setDelimiter(',');
$csv->setHeaderOffset(0);
//build a statement
$stmt = Statement::create()->where(static fn (array $record): bool => $record['category'] == "MotoGP" || $record['category'] == "500cc");
/**
* @var $riders Player[]
*/
$riders = [];
//query your records from the document
$records = $stmt->process($csv);
$gameInfo = new GameInfo();
$calculator = new FactorGraphTrueSkillCalculator();
$first_record = $records->first();
$year_race = $first_record['year'] . '_' . $first_record['sequence'] . '_' . $first_record['category'];
$race_rate = [];
foreach ($records as $record) {
if ($year_race !== $record['year'] . '_' . $record['sequence'] . '_' . $record['category']) {
//Calculate the old race
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, $pos);
//update ratings
$highest_rate = 0;
$highest_rider = "";
foreach ($riders as $rider) {
//echo $rider['P']->getId().": ". $newRatings->getRating($rider['P'])->getConservativeRating() . PHP_EOL;
$rider['T']->setRating($rider['P'], $newRatings->getRating($rider['P']));
if ($newRatings->getRating($rider['P'])->getConservativeRating() > $highest_rate) {
$highest_rate = $newRatings->getRating($rider['P'])->getConservativeRating();
$highest_rider = $rider['P']->getId();
}
}
echo sprintf('Highest rider: %s => %s', $highest_rider, $highest_rate) . PHP_EOL;
foreach ($global_riders as $r) {
$rate = $r['T']->getRating($r['P'])->getConservativeRating();
$race_rate[$year_race][$r['P']->getId()] = $rate;
if (! isset($top_rating[$r['P']->getId()]) || $top_rating[$r['P']->getId()] < $rate) {
$top_rating[$r['P']->getId()] = $rate;
}
}
//prepare for next race
$year_race = $record['year'] . '_' . $record['sequence'] . '_' . $record['category'];
$races[] = ['year' => $record['year'], 'race' => $record['sequence'], 'circuit' => $record['circuit_name']];
echo "New Race: " . $year_race . ' => ' . $record['circuit_name'] . PHP_EOL;
$riders = [];
$teams = [];
$pos = [];
}
//Is it a new rider?
if (! isset($global_riders[$record['rider']])) {
$global_riders[$record['rider']]['P'] = new Player($record['rider_name']);
$global_riders[$record['rider']]['T'] = new Team($global_riders[$record['rider']]['P'], $gameInfo->getDefaultRating());
//echo "New Rider: ". $record['rider'] . " => ".$global_riders[$record['rider']]['P']->getId().PHP_EOL;
}
$riders[] = $global_riders[$record['rider']];
$teams[] = $global_riders[$record['rider']]['T'];
//Position or DNF?
$pos[] = $record['position'] >= 1 ? $record['position'] : end($pos);
}
echo "All time top score" . PHP_EOL;
asort($top_rating);
foreach ($top_rating as $n => $r) {
echo sprintf('%s => %s', $n, $r) . PHP_EOL;
}

56397
examples/motogp/motogp.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"$schema": "https://raw.githubusercontent.com/infection/infection/0.27.9/resources/schema.json", "$schema": "https://raw.githubusercontent.com/infection/infection/0.29.6/resources/schema.json",
"source": { "source": {
"directories": [ "directories": [
"src/" "src/"

View File

@ -16,5 +16,5 @@
</source> </source>
</api> </api>
</version> </version>
<!--setting name="graphs.enabled" value="true"/--> <setting name="graphs.enabled" value="true"/>
</phpdocumentor> </phpdocumentor>

9
phpmd.baseline.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<phpmd-baseline>
<violation rule="PHPMD\Rule\Design\TooManyPublicMethods" file="src/Numerics/GaussianDistribution.php"/>
<violation rule="PHPMD\Rule\UnusedPrivateMethod" file="src/Numerics/GaussianDistribution.php" method="errorFunctionCumulativeTo"/>
<violation rule="PHPMD\Rule\UnusedPrivateMethod" file="src/Numerics/GaussianDistribution.php" method="inverseErrorFunctionCumulativeTo"/>
<violation rule="PHPMD\Rule\Design\WeightedMethodCount" file="src/Numerics/Matrix.php"/>
<violation rule="PHPMD\Rule\Design\CouplingBetweenObjects" file="src/TrueSkill/FactorGraphTrueSkillCalculator.php"/>
<violation rule="PHPMD\Rule\Design\CouplingBetweenObjects" file="src/TrueSkill/TrueSkillFactorGraph.php"/>
</phpmd-baseline>

49
phpmd.ruleset.xml Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0"?>
<ruleset name="TrueSkill custom PHPMD rules"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="
http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>
TrueSkill custom PHPMD rules
</description>
<!-- Import the entire unused code rule set -->
<rule ref="rulesets/cleancode.xml">
<exclude name="StaticAccess" />
<exclude name="ElseExpression" />
</rule>
<rule ref="rulesets/codesize.xml" >
<exclude name="TooManyMethods" />
<exclude name="TooManyPublicMethods" />
</rule>
<rule ref="rulesets/codesize.xml/TooManyMethods">
<priority>1</priority>
<properties>
<property name="ignorepattern" value="#^(set|get|test)|test$#i" />
</properties>
</rule>
<rule ref="rulesets/codesize.xml/TooManyPublicMethods">
<priority>1</priority>
<properties>
<property name="ignorepattern" value="#^(set|get|test)|test$#i" />
</properties>
</rule>
<!--rule ref="rulesets/controversial.xml" /-->
<rule ref="rulesets/design.xml" />
<rule ref="rulesets/naming.xml" >
<exclude name="LongClassName" />
<exclude name="ShortClassName" />
<exclude name="ShortVariable" />
<exclude name="LongVariable" />
<exclude name="ShortMethodName" />
</rule>
<rule ref="rulesets/unusedcode.xml" />
<!-- Import entire naming rule set and exclude rules -->
</ruleset>

View File

@ -1,21 +0,0 @@
{
"composer": true,
"includes": [
"src"
],
"excludes": [
"tests"
],
"report": {
"html": "output/metrics/",
"json": "output/metrics/report.json"
},
"plugins": {
"git": {
"binary": "git"
},
"junit": {
"file": "output/test.xml"
}
}
}

18
phpmetrics.yml Normal file
View File

@ -0,0 +1,18 @@
---
composer: true
includes:
- src
excludes:
- benchmark
- tests
extensions:
- php
report:
html: "output/metrics/"
json: "output/metrics/report.json"
violations: "/tmp/violations.xml"
plugins:
git:
binary: git
junit:
file: "output/test.xml"

View File

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

View File

@ -15,7 +15,7 @@ abstract class Factor
*/ */
private array $messages = []; private array $messages = [];
private readonly HashMap $messageToVariableBinding; private readonly HashMap $msgToVariableBinding;
/** /**
* @var Variable[] $variables * @var Variable[] $variables
@ -24,7 +24,7 @@ abstract class Factor
protected function __construct() protected function __construct()
{ {
$this->messageToVariableBinding = new HashMap(); $this->msgToVariableBinding = new HashMap();
} }
/** /**
@ -70,7 +70,7 @@ abstract class Factor
{ {
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->msgToVariableBinding->getValue($message);
return $this->updateMessageVariable($message, $variable); return $this->updateMessageVariable($message, $variable);
} }
@ -85,7 +85,7 @@ abstract class Factor
*/ */
public function resetMarginals(): void public function resetMarginals(): void
{ {
$allValues = $this->messageToVariableBinding->getAllValues(); $allValues = $this->msgToVariableBinding->getAllValues();
foreach ($allValues as $currentVariable) { foreach ($allValues as $currentVariable) {
$currentVariable->resetToPrior(); $currentVariable->resetToPrior();
} }
@ -101,7 +101,7 @@ abstract class Factor
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->msgToVariableBinding->getValue($message);
return $this->sendMessageVariable($message, $variable); return $this->sendMessageVariable($message, $variable);
} }
@ -112,7 +112,7 @@ abstract class Factor
protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message
{ {
$this->messageToVariableBinding->setValue($message, $variable); $this->msgToVariableBinding->setValue($message, $variable);
$this->messages[] = $message; $this->messages[] = $message;
$this->variables[] = $variable; $this->variables[] = $variable;

View File

@ -17,14 +17,14 @@ abstract class FactorGraphLayer
/** /**
* @var array<int,array<int,Variable>> * @var array<int,array<int,Variable>>
*/ */
private array $outputVariablesGroups = []; private array $outputVarGroups = [];
/** /**
* @var array<int,array<int,Variable>> * @var array<int,array<int,Variable>>
*/ */
private array $inputVariablesGroups = []; private array $inputVariablesGroups = [];
protected function __construct(private readonly TrueSkillFactorGraph $parentFactorGraph) public function __construct(private readonly TrueSkillFactorGraph $parentFactorGraph)
{ {
} }
@ -48,7 +48,7 @@ abstract class FactorGraphLayer
*/ */
public function &getOutputVariablesGroups(): array public function &getOutputVariablesGroups(): array
{ {
return $this->outputVariablesGroups; return $this->outputVarGroups;
} }
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ class Variable
{ {
private mixed $value; private mixed $value;
public function __construct(private GaussianDistribution $prior) public function __construct(private readonly GaussianDistribution $prior)
{ {
$this->resetToPrior(); $this->resetToPrior();
} }

View File

@ -4,22 +4,22 @@ declare(strict_types=1);
namespace DNW\Skills\FactorGraphs; namespace DNW\Skills\FactorGraphs;
class VariableFactory final readonly class VariableFactory
{ {
public function __construct(private \Closure $variablePriorInitializer) public function __construct(private \Closure $varPriorInitializer)
{ {
} }
public function createBasicVariable(): Variable public function createBasicVariable(): Variable
{ {
$initializer = $this->variablePriorInitializer; $initializer = $this->varPriorInitializer;
return new Variable($initializer()); return new Variable($initializer());
} }
public function createKeyedVariable(mixed $key): KeyedVariable public function createKeyedVariable(mixed $key): KeyedVariable
{ {
$initializer = $this->variablePriorInitializer; $initializer = $this->varPriorInitializer;
return new KeyedVariable($key, $initializer()); return new KeyedVariable($key, $initializer());
} }

View File

@ -7,30 +7,30 @@ namespace DNW\Skills;
/** /**
* Parameters about the game for calculating the TrueSkill. * Parameters about the game for calculating the TrueSkill.
*/ */
class GameInfo final readonly class GameInfo
{ {
/** /**
* Default initial mean / 6 * 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 * 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( public function __construct(
private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN, private float $initialMean = self::DEFAULT_INITIAL_MEAN,
private readonly float $initialStandardDeviation = self::DEFAULT_INITIAL_STANDARD_DEVIATION, private float $initialStdDev = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
private readonly float $beta = self::DEFAULT_BETA, private float $beta = self::DEFAULT_BETA,
private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR, private float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
private readonly float $drawProbability = self::DEFAULT_DRAW_PROBABILITY private float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
) )
{ {
} }
@ -42,7 +42,7 @@ class GameInfo
public function getInitialStandardDeviation(): float public function getInitialStandardDeviation(): float
{ {
return $this->initialStandardDeviation; return $this->initialStdDev;
} }
public function getBeta(): float public function getBeta(): float
@ -62,6 +62,6 @@ class GameInfo
public function getDefaultRating(): Rating public function getDefaultRating(): Rating
{ {
return new Rating($this->initialMean, $this->initialStandardDeviation); return new Rating($this->initialMean, $this->initialStdDev);
} }
} }

View File

@ -11,7 +11,7 @@ use Exception;
* *
* @see http://www.moserware.com/2008/01/borrowing-ideas-from-3-interesting.html * @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 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 public static function argumentInRangeInclusive(float $value, float $min, float $max, string $parameterName): void
{ {
if (($value < $min) || ($value > $max)) { 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,15 +7,15 @@ namespace DNW\Skills;
/** /**
* Basic hashmap that supports object keys. * Basic hashmap that supports object keys.
*/ */
class HashMap final class HashMap
{ {
/** /**
* @var mixed[] $hashToValue * @var mixed[] $hashToValue Store the hash to value mapping.
*/ */
private array $hashToValue = []; private array $hashToValue = [];
/** /**
* @var mixed[] $hashToKey * @var mixed[] $hashToKey Store the hash to original key mapping.
*/ */
private array $hashToKey = []; private array $hashToKey = [];

View File

@ -10,24 +10,24 @@ namespace DNW\Skills\Numerics;
* @author Jeff Moser <jeff@moserware.com> * @author Jeff Moser <jeff@moserware.com>
* @copyright 2010 Jeff Moser * @copyright 2010 Jeff Moser
*/ */
class BasicMath final class BasicMath
{ {
/** /**
* Squares the input (x^2 = x * x) * Squares the input (input^2 = input * input)
* *
* @param $x Value to square (x) * @param $input Value to square (input)
* *
* @return float The squared value (x^2) * @return float The squared value (input^2)
*/ */
public static function square(float $x): float public static function square(float $input): float
{ {
return $x * $x; return $input * $input;
} }
/** /**
* Sums the items in $itemsToSum * Sums the items in $itemsToSum
* *
* @param mixed[] $itemsToSum The items to sum, * @param mixed[] $itemsToSum The items to sum.
* @param \Closure $callback The function to apply to each array element before summing. * @param \Closure $callback The function to apply to each array element before summing.
* *
* @return float The sum. * @return float The sum.

View File

@ -10,11 +10,11 @@ namespace DNW\Skills\Numerics;
* @author Jeff Moser <jeff@moserware.com> * @author Jeff Moser <jeff@moserware.com>
* @copyright 2010 Jeff Moser * @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π. * Square Root 2π.
@ -23,7 +23,7 @@ class GaussianDistribution
* *
* @link https://www.wolframalpha.com/input?i=sqrt%282*pi%29 Source of value * @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π. * Log of Square Root 2π.
@ -32,10 +32,11 @@ class GaussianDistribution
* *
* @link https://www.wolframalpha.com/input?i=log%28sqrt%282*pi%29%29 Source of value * @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) * Precision and precisionMean are used because they make multiplying and dividing simpler.
*/
private float $precision = 1.0; private float $precision = 1.0;
private float $precisionMean = 0.0; private float $precisionMean = 0.0;
@ -55,7 +56,6 @@ class GaussianDistribution
$this->precisionMean = $this->precision * $this->mean; $this->precisionMean = $this->precision * $this->mean;
} else { } else {
$this->precision = \INF; $this->precision = \INF;
$this->precisionMean = $this->mean == 0 ? 0 : \INF; $this->precisionMean = $this->mean == 0 ? 0 : \INF;
} }
} }
@ -172,10 +172,10 @@ class GaussianDistribution
$meanDifference = $numerator->mean - $denominator->mean; $meanDifference = $numerator->mean - $denominator->mean;
return log($denominator->variance) + self::M_LOG_SQRT_2_PI - log($varianceDifference) / 2.0 + 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 $x, float $mean = 0.0, float $standardDeviation = 1.0): float public static function at(float $var, float $mean = 0.0, float $standardDeviation = 1.0): float
{ {
// See http://mathworld.wolfram.com/NormalDistribution.html // See http://mathworld.wolfram.com/NormalDistribution.html
// 1 -(x-mean)^2 / (2*stdDev^2) // 1 -(x-mean)^2 / (2*stdDev^2)
@ -183,25 +183,25 @@ class GaussianDistribution
// stdDev * sqrt(2*pi) // stdDev * sqrt(2*pi)
$multiplier = 1.0 / ($standardDeviation * self::M_SQRT_2_PI); $multiplier = 1.0 / ($standardDeviation * self::M_SQRT_2_PI);
$expPart = exp((-1.0 * BasicMath::square($x - $mean)) / (2 * BasicMath::square($standardDeviation))); $expPart = exp((-1.0 * BasicMath::square($var - $mean)) / (2.0 * BasicMath::square($standardDeviation)));
return $multiplier * $expPart; return $multiplier * $expPart;
} }
public static function cumulativeTo(float $x): float public static function cumulativeTo(float $var): float
{ {
$result = GaussianDistribution::errorFunctionCumulativeTo(-M_SQRT1_2 * $x); $result = GaussianDistribution::errorFunctionCumulativeTo(-M_SQRT1_2 * $var);
return 0.5 * $result; return 0.5 * $result;
} }
private static function errorFunctionCumulativeTo(float $x): float private static function errorFunctionCumulativeTo(float $var): float
{ {
// Derived from page 265 of Numerical Recipes 3rd Edition // Derived from page 265 of Numerical Recipes 3rd Edition
$z = abs($x); $z = abs($var);
$t = 2.0 / (2.0 + $z); $t = 2.0 / (2.0 + $z);
$ty = 4 * $t - 2; $ty = 4.0 * $t - 2.0;
$coefficients = [ $coefficients = [
-1.3026537197817094, -1.3026537197817094,
@ -246,7 +246,7 @@ class GaussianDistribution
$ans = $t * exp(-$z * $z + 0.5 * ($coefficients[0] + $ty * $d) - $dd); $ans = $t * exp(-$z * $z + 0.5 * ($coefficients[0] + $ty * $d) - $dd);
return ($x >= 0.0) ? $ans : (2.0 - $ans); return ($var >= 0.0) ? $ans : (2.0 - $ans);
} }
private static function inverseErrorFunctionCumulativeTo(float $p): float private static function inverseErrorFunctionCumulativeTo(float $p): float
@ -260,8 +260,8 @@ class GaussianDistribution
return 100; return 100;
} }
$pp = ($p < 1.0) ? $p : 2 - $p; $pp = ($p < 1.0) ? $p : 2.0 - $p;
$t = sqrt(-2 * log($pp / 2.0)); // Initial guess $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); $x = -M_SQRT1_2 * ((2.30753 + $t * 0.27061) / (1.0 + $t * (0.99229 + $t * 0.04481)) - $t);
for ($j = 0; $j < 2; ++$j) { for ($j = 0; $j < 2; ++$j) {
@ -272,9 +272,9 @@ class GaussianDistribution
return ($p < 1.0) ? $x : -$x; return ($p < 1.0) ? $x : -$x;
} }
public static function inverseCumulativeTo(float $x, float $mean = 0.0, float $standardDeviation = 1.0): float public static function inverseCumulativeTo(float $var, float $mean = 0.0, float $standardDeviation = 1.0): float
{ {
// From numerical recipes, page 320 // From numerical recipes, page 320
return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2 * $x); return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2.0 * $var);
} }
} }

View File

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

View File

@ -8,7 +8,7 @@ use Exception;
class Matrix class Matrix
{ {
public const ERROR_TOLERANCE = 0.0000000001; public const float ERROR_TOLERANCE = 0.0000000001;
/** /**
* @param array<int,array<int,float>> $matrixRowData * @param array<int,array<int,float>> $matrixRowData
@ -60,13 +60,34 @@ class Matrix
return $this->columnCount; return $this->columnCount;
} }
public function getValue(int $row, int $col): float|int private function checkRowCol(int $row, int $col): void
{ {
if ($row < 0) {
throw new Exception("Row negative");
}
if ($row >= $this->getRowCount()) {
throw new Exception("Row beyond range");
}
if ($col < 0) {
throw new Exception("Column negative");
}
if ($col >= $this->getColumnCount()) {
throw new Exception("Column beyond range");
}
}
public function getValue(int $row, int $col): float
{
$this->checkRowCol($row, $col);
return $this->matrixRowData[$row][$col]; return $this->matrixRowData[$row][$col];
} }
public function setValue(int $row, int $col, float|int $value): void public function setValue(int $row, int $col, float|int $value): void
{ {
$this->checkRowCol($row, $col);
$this->matrixRowData[$row][$col] = $value; $this->matrixRowData[$row][$col] = $value;
} }
@ -76,10 +97,10 @@ class Matrix
$transposeMatrix = []; $transposeMatrix = [];
$rowMatrixData = $this->matrixRowData; $rowMatrixData = $this->matrixRowData;
for ($currentRowTransposeMatrix = 0; $currentRowTransposeMatrix < $this->columnCount; ++$currentRowTransposeMatrix) { for ($curRowTransposeMx = 0; $curRowTransposeMx < $this->columnCount; ++$curRowTransposeMx) {
for ($currentColumnTransposeMatrix = 0; $currentColumnTransposeMatrix < $this->rowCount; ++$currentColumnTransposeMatrix) { for ($curColTransposeMx = 0; $curColTransposeMx < $this->rowCount; ++$curColTransposeMx) {
$transposeMatrix[$currentRowTransposeMatrix][$currentColumnTransposeMatrix] = $transposeMatrix[$curRowTransposeMx][$curColTransposeMx] =
$rowMatrixData[$currentColumnTransposeMatrix][$currentRowTransposeMatrix]; $rowMatrixData[$curColTransposeMx][$curRowTransposeMx];
} }
} }
@ -100,7 +121,7 @@ class Matrix
if ($this->rowCount == 1) { if ($this->rowCount == 1) {
// Really happy path :) // Really happy path :)
return $this->matrixRowData[0][0]; return $this->getValue(0, 0);
} }
if ($this->rowCount == 2) { if ($this->rowCount == 2) {
@ -109,10 +130,10 @@ class Matrix
// | a b | // | a b |
// | c d | // | c d |
// The determinant is ad - bc // The determinant is ad - bc
$a = $this->matrixRowData[0][0]; $a = $this->getValue(0, 0);
$b = $this->matrixRowData[0][1]; $b = $this->getValue(0, 1);
$c = $this->matrixRowData[1][0]; $c = $this->getValue(1, 0);
$d = $this->matrixRowData[1][1]; $d = $this->getValue(1, 1);
return $a * $d - $b * $c; return $a * $d - $b * $c;
} }
@ -127,7 +148,7 @@ class Matrix
// I expand along the first row // I expand along the first row
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) { for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
$firstRowColValue = $this->matrixRowData[0][$currentColumn]; $firstRowColValue = $this->getValue(0, $currentColumn);
$cofactor = $this->getCofactor(0, $currentColumn); $cofactor = $this->getCofactor(0, $currentColumn);
$itemToAdd = $firstRowColValue * $cofactor; $itemToAdd = $firstRowColValue * $cofactor;
$result += $itemToAdd; $result += $itemToAdd;
@ -152,10 +173,10 @@ class Matrix
// | d -b | // | d -b |
// | -c a | // | -c a |
$a = $this->matrixRowData[0][0]; $a = $this->getValue(0, 0);
$b = $this->matrixRowData[0][1]; $b = $this->getValue(0, 1);
$c = $this->matrixRowData[1][0]; $c = $this->getValue(1, 0);
$d = $this->matrixRowData[1][1]; $d = $this->getValue(1, 1);
return new SquareMatrix( return new SquareMatrix(
$d, $d,
@ -180,7 +201,7 @@ class Matrix
public function getInverse(): Matrix|SquareMatrix public function getInverse(): Matrix|SquareMatrix
{ {
if (($this->rowCount == 1) && ($this->columnCount == 1)) { if (($this->rowCount == 1) && ($this->columnCount == 1)) {
return new SquareMatrix(1.0 / $this->matrixRowData[0][0]); return new SquareMatrix(1.0 / $this->getValue(0, 0));
} }
// Take the simple approach: // Take the simple approach:
@ -191,7 +212,7 @@ class Matrix
return self::scalarMultiply($determinantInverse, $adjugate); return self::scalarMultiply($determinantInverse, $adjugate);
} }
public static function scalarMultiply(float|int $scalarValue, Matrix $matrix): Matrix public static function scalarMultiply(float $scalarValue, Matrix $matrix): Matrix
{ {
$rows = $matrix->getRowCount(); $rows = $matrix->getRowCount();
$columns = $matrix->getColumnCount(); $columns = $matrix->getColumnCount();
@ -244,7 +265,7 @@ class Matrix
for ($currentRow = 0; $currentRow < $resultRows; ++$currentRow) { for ($currentRow = 0; $currentRow < $resultRows; ++$currentRow) {
for ($currentColumn = 0; $currentColumn < $resultColumns; ++$currentColumn) { for ($currentColumn = 0; $currentColumn < $resultColumns; ++$currentColumn) {
$productValue = 0; $productValue = 0.0;
for ($vectorIndex = 0; $vectorIndex < $left->getColumnCount(); ++$vectorIndex) { for ($vectorIndex = 0; $vectorIndex < $left->getColumnCount(); ++$vectorIndex) {
$leftValue = $left->getValue($currentRow, $vectorIndex); $leftValue = $left->getValue($currentRow, $vectorIndex);
@ -262,6 +283,7 @@ class Matrix
private function getMinorMatrix(int $rowToRemove, int $columnToRemove): Matrix private function getMinorMatrix(int $rowToRemove, int $columnToRemove): Matrix
{ {
$this->checkRowCol($rowToRemove, $columnToRemove);
// See http://en.wikipedia.org/wiki/Minor_(linear_algebra) // See http://en.wikipedia.org/wiki/Minor_(linear_algebra)
// I'm going to use a horribly naïve algorithm... because I can :) // I'm going to use a horribly naïve algorithm... because I can :)
@ -281,7 +303,7 @@ class Matrix
continue; continue;
} }
$result[$actualRow][$actualCol] = $this->matrixRowData[$currentRow][$currentColumn]; $result[$actualRow][$actualCol] = $this->getValue($currentRow, $currentColumn);
++$actualCol; ++$actualCol;
} }
@ -294,6 +316,7 @@ class Matrix
public function getCofactor(int $rowToRemove, int $columnToRemove): float public function getCofactor(int $rowToRemove, int $columnToRemove): float
{ {
$this->checkRowCol($rowToRemove, $columnToRemove);
// See http://en.wikipedia.org/wiki/Cofactor_(linear_algebra) for details // See http://en.wikipedia.org/wiki/Cofactor_(linear_algebra) for details
// REVIEW: should things be reversed since I'm 0 indexed? // REVIEW: should things be reversed since I'm 0 indexed?
$sum = $rowToRemove + $columnToRemove; $sum = $rowToRemove + $columnToRemove;
@ -316,7 +339,7 @@ class Matrix
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) { for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
$delta = $delta =
abs( abs(
$this->matrixRowData[$currentRow][$currentColumn] - $this->getValue($currentRow, $currentColumn) -
$otherMatrix->getValue($currentRow, $currentColumn) $otherMatrix->getValue($currentRow, $currentColumn)
); );

View File

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

View File

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

View File

@ -4,18 +4,18 @@ declare(strict_types=1);
namespace DNW\Skills; namespace DNW\Skills;
class PartialPlay final class PartialPlay
{ {
public static function getPartialPlayPercentage(Player $player): float public static function getPartialPlayPercentage(Player $player): float
{ {
$partialPlayPercentage = $player->getPartialPlayPercentage(); $partialPlayPct = $player->getPartialPlayPercentage();
// HACK to get around bug near 0 // HACK to get around bug near 0
$smallestPercentage = 0.0001; $smallestPct = 0.0001;
if ($partialPlayPercentage < $smallestPercentage) { if ($partialPlayPct < $smallestPct) {
return $smallestPercentage; return $smallestPct;
} }
return $partialPlayPercentage; return $partialPlayPct;
} }
} }

View File

@ -7,33 +7,39 @@ namespace DNW\Skills;
/** /**
* Represents a player who has a Rating. * 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 $PartialPlayPercentage; /**
* @var float The weight percentage to give this player when calculating a new rank.
*/
private float $PartialPlayPct;
private readonly float $PartialUpdatePercentage; /**
* @var float Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.
*/
private float $PartialUpdatePct;
/** /**
* Constructs a player. * Constructs a player.
* *
* @param string|int $Id The identifier for the player, such as a name. * @param string|int $Id The identifier for the player, such as a name.
* @param float $partialPlayPercentage The weight percentage to give this player when calculating a new rank. * @param float $partialPlayPct The weight percentage to give this player when calculating a new rank.
* @param float $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 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( public function __construct(
private readonly mixed $Id, private mixed $Id,
float $partialPlayPercentage = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE, float $partialPlayPct = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE,
float $partialUpdatePercentage = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE float $partialUpdatePct = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE
) )
{ {
Guard::argumentInRangeInclusive($partialPlayPercentage, 0.0, 1.0, 'partialPlayPercentage'); Guard::argumentInRangeInclusive($partialPlayPct, 0.0, 1.0, 'partialPlayPercentage');
Guard::argumentInRangeInclusive($partialUpdatePercentage, 0, 1.0, 'partialUpdatePercentage'); Guard::argumentInRangeInclusive($partialUpdatePct, 0, 1.0, 'partialUpdatePercentage');
$this->PartialPlayPercentage = $partialPlayPercentage; $this->PartialPlayPct = $partialPlayPct;
$this->PartialUpdatePercentage = $partialUpdatePercentage; $this->PartialUpdatePct = $partialUpdatePct;
} }
/** /**
@ -47,16 +53,18 @@ 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. * 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 public function getPartialPlayPercentage(): float
{ {
return $this->PartialPlayPercentage; return $this->PartialPlayPct;
} }
/** /**
* 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. * 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 public function getPartialUpdatePercentage(): float
{ {
return $this->PartialUpdatePercentage; return $this->PartialUpdatePct;
} }
} }

View File

@ -6,6 +6,6 @@ namespace DNW\Skills;
use DNW\Skills\Numerics\Range; 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. * 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. * 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. * 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. * 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 $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

@ -6,6 +6,9 @@ namespace DNW\Skills;
class RatingContainer class RatingContainer
{ {
/**
* Link Player to a Rating using a hash map.
*/
private readonly HashMap $playerToRating; private readonly HashMap $playerToRating;
public function __construct() public function __construct()

View File

@ -11,11 +11,11 @@ use Exception;
*/ */
abstract class SkillCalculator 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( protected function __construct(
private readonly int $supportedOptions, private readonly int $supportedOptions,
@ -52,7 +52,7 @@ abstract class SkillCalculator
public function isSupported(int $option): bool public function isSupported(int $option): bool
{ {
return (bool)($this->supportedOptions & $option) == $option; return ($this->supportedOptions & $option) === $option;
} }
/** /**
@ -64,7 +64,7 @@ abstract class SkillCalculator
} }
/** /**
* @param array<\DNW\Skills\Team> $teams * @param Team[] $teams
* *
* @throws \Exception * @throws \Exception
*/ */

View File

@ -4,9 +4,9 @@ declare(strict_types=1);
namespace DNW\Skills; 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(); parent::__construct();
if (! $player instanceof Player) { if (! $player instanceof Player) {

View File

@ -6,6 +6,6 @@ namespace DNW\Skills;
use DNW\Skills\Numerics\Range; 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 // margin = inversecdf((draw probability + 1)/2) * sqrt(n1+n2) * beta
// n1 and n2 are the number of players on each team // 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. * Calculates TrueSkill using a full factor graph.
*/ */
class FactorGraphTrueSkillCalculator extends SkillCalculator final class FactorGraphTrueSkillCalculator extends SkillCalculator
{ {
public function __construct() public function __construct()
{ {
@ -32,6 +32,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
#[\Override]
public function calculateNewRatings( public function calculateNewRatings(
GameInfo $gameInfo, GameInfo $gameInfo,
array $teams, array $teams,
@ -54,6 +55,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
#[\Override]
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
{ {
// We need to create the A matrix which is the player team assigments. // We need to create the A matrix which is the player team assigments.
@ -171,7 +173,8 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
$currentColumn = 0; $currentColumn = 0;
for ($i = 0; $i < count($teamAssignmentsList) - 1; ++$i) { $teamCnt = count($teamAssignmentsList);
for ($i = 0; $i < $teamCnt - 1; ++$i) {
$currentTeam = $teamAssignmentsList[$i]; $currentTeam = $teamAssignmentsList[$i];
// Need to add in 0's for all the previous players, since they're not // Need to add in 0's for all the previous players, since they're not
@ -189,7 +192,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
$nextTeam = $teamAssignmentsList[$i + 1]; $nextTeam = $teamAssignmentsList[$i + 1];
foreach ($nextTeam->getAllPlayers() as $nextTeamPlayer) { foreach ($nextTeam->getAllPlayers() as $nextTeamPlayer) {
// Add a -1 * playing time to represent the difference // Add a -1 * playing time to represent the difference
$playerAssignments[$currentColumn][] = -1 * PartialPlay::getPartialPlayPercentage($nextTeamPlayer); $playerAssignments[$currentColumn][] = -1.0 * PartialPlay::getPartialPlayPercentage($nextTeamPlayer);
--$rowsRemaining; --$rowsRemaining;
} }

View File

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

View File

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

View File

@ -15,7 +15,7 @@ use Exception;
* *
* See the accompanying math paper for more details. * See the accompanying math paper for more details.
*/ */
class GaussianLikelihoodFactor extends GaussianFactor final class GaussianLikelihoodFactor extends GaussianFactor
{ {
private readonly float $precision; private readonly float $precision;
@ -28,10 +28,11 @@ class GaussianLikelihoodFactor extends GaussianFactor
$this->createVariableToMessageBinding($variable2); $this->createVariableToMessageBinding($variable2);
} }
#[\Override]
public function getLogNormalization(): float public function getLogNormalization(): float
{ {
/** /**
* @var KeyedVariable[]|mixed $vars * @var KeyedVariable[] $vars
*/ */
$vars = $this->getVariables(); $vars = $this->getVariables();
/** /**
@ -60,9 +61,9 @@ class GaussianLikelihoodFactor extends GaussianFactor
$a * ($marginal2->getPrecision() - $message2Value->getPrecision()) $a * ($marginal2->getPrecision() - $message2Value->getPrecision())
); );
$oldMarginalWithoutMessage = GaussianDistribution::divide($marginal1, $message1Value); $oldMarginalWithoutMsg = GaussianDistribution::divide($marginal1, $message1Value);
$newMarginal = GaussianDistribution::multiply($oldMarginalWithoutMessage, $newMessage); $newMarginal = GaussianDistribution::multiply($oldMarginalWithoutMsg, $newMessage);
// Update the message and marginal // Update the message and marginal
@ -73,6 +74,7 @@ class GaussianLikelihoodFactor extends GaussianFactor
return GaussianDistribution::subtract($newMarginal, $marginal1); return GaussianDistribution::subtract($newMarginal, $marginal1);
} }
#[\Override]
public function updateMessageIndex(int $messageIndex): float public function updateMessageIndex(int $messageIndex): float
{ {
$messages = $this->getMessages(); $messages = $this->getMessages();

View File

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

View File

@ -16,12 +16,12 @@ use DNW\Skills\Numerics\GaussianDistribution;
* *
* See the accompanying math paper for more details. * See the accompanying math paper for more details.
*/ */
class GaussianWeightedSumFactor extends GaussianFactor final class GaussianWeightedSumFactor extends GaussianFactor
{ {
/** /**
* @var array<int[]> $variableIndexOrdersForWeights * @var array<int[]> $varIndexOrdersForWeights
*/ */
private array $variableIndexOrdersForWeights = []; private array $varIndexOrdersForWeights = [];
/** /**
* 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]
@ -58,9 +58,9 @@ class GaussianWeightedSumFactor extends GaussianFactor
$variablesToSumLength = count($variablesToSum); $variablesToSumLength = count($variablesToSum);
// 0..n-1 // 0..n-1
$this->variableIndexOrdersForWeights[0] = []; $this->varIndexOrdersForWeights[0] = [];
for ($i = 0; $i < ($variablesToSumLength + 1); ++$i) { for ($i = 0; $i < ($variablesToSumLength + 1); ++$i) {
$this->variableIndexOrdersForWeights[0][] = $i; $this->varIndexOrdersForWeights[0][] = $i;
} }
$variableWeightsLength = count($variableWeights); $variableWeightsLength = count($variableWeights);
@ -72,12 +72,12 @@ class GaussianWeightedSumFactor extends GaussianFactor
$weightsLength = $variableWeightsLength + 1; $weightsLength = $variableWeightsLength + 1;
for ($weightsIndex = 1; $weightsIndex < $weightsLength; ++$weightsIndex) { for ($weightsIndex = 1; $weightsIndex < $weightsLength; ++$weightsIndex) {
$currentWeights = \array_fill(0, $variableWeightsLength, 0); $currentWeights = \array_fill(0, $variableWeightsLength, 0.0);
$variableIndices = \array_fill(0, $variableWeightsLength + 1, 0); $variableIndices = \array_fill(0, $variableWeightsLength + 1, 0.0);
$variableIndices[0] = $weightsIndex; $variableIndices[0] = $weightsIndex;
$currentWeightsSquared = \array_fill(0, $variableWeightsLength, 0); $currentWeightsSquared = \array_fill(0, $variableWeightsLength, 0.0);
// keep a single variable to keep track of where we are in the array. // keep a single variable to keep track of where we are in the array.
// This is helpful since we skip over one of the spots // This is helpful since we skip over one of the spots
@ -90,9 +90,9 @@ class GaussianWeightedSumFactor extends GaussianFactor
$currentWeight = (-$variableWeights[$currentWeightSourceIndex] / $variableWeights[$weightsIndex - 1]); $currentWeight = (-$variableWeights[$currentWeightSourceIndex] / $variableWeights[$weightsIndex - 1]);
if ($variableWeights[$weightsIndex - 1] == 0) { if ($variableWeights[$weightsIndex - 1] == 0.0) {
// HACK: Getting around division by zero // HACK: Getting around division by zero
$currentWeight = 0; $currentWeight = 0.0;
} }
$currentWeights[$currentDestinationWeightIndex] = $currentWeight; $currentWeights[$currentDestinationWeightIndex] = $currentWeight;
@ -112,8 +112,8 @@ class GaussianWeightedSumFactor extends GaussianFactor
$currentWeights[$currentDestinationWeightIndex] = $finalWeight; $currentWeights[$currentDestinationWeightIndex] = $finalWeight;
$currentWeightsSquared[$currentDestinationWeightIndex] = BasicMath::square($finalWeight); $currentWeightsSquared[$currentDestinationWeightIndex] = BasicMath::square($finalWeight);
$variableIndices[count($variableWeights)] = 0; $variableIndices[count($variableWeights)] = 0.0;
$this->variableIndexOrdersForWeights[] = $variableIndices; $this->varIndexOrdersForWeights[] = $variableIndices;
$this->weights[$weightsIndex] = $currentWeights; $this->weights[$weightsIndex] = $currentWeights;
$this->weightsSquared[$weightsIndex] = $currentWeightsSquared; $this->weightsSquared[$weightsIndex] = $currentWeightsSquared;
@ -127,6 +127,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
} }
} }
#[\Override]
public function getLogNormalization(): float public function getLogNormalization(): float
{ {
$vars = $this->getVariables(); $vars = $this->getVariables();
@ -160,9 +161,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
// The math works out so that 1/newPrecision = sum of a_i^2 /marginalsWithoutMessages[i] // The math works out so that 1/newPrecision = sum of a_i^2 /marginalsWithoutMessages[i]
$inverseOfNewPrecisionSum = 0.0; $inverseOfNewPrecisionSum = 0.0;
$anotherInverseOfNewPrecisionSum = 0.0;
$weightedMeanSum = 0.0; $weightedMeanSum = 0.0;
$anotherWeightedMeanSum = 0.0;
$weightsSquaredLength = count($weightsSquared); $weightsSquaredLength = count($weightsSquared);
@ -172,16 +171,11 @@ class GaussianWeightedSumFactor extends GaussianFactor
$inverseOfNewPrecisionSum += $weightsSquared[$i] / $inverseOfNewPrecisionSum += $weightsSquared[$i] /
($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision()); ($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
$diff = GaussianDistribution::divide($variables[$i + 1]->getValue(), $messages[$i + 1]->getValue());
$anotherInverseOfNewPrecisionSum += $weightsSquared[$i] / $diff->getPrecision();
$weightedMeanSum += $weights[$i] $weightedMeanSum += $weights[$i]
* *
($variables[$i + 1]->getValue()->getPrecisionMean() - $messages[$i + 1]->getValue()->getPrecisionMean()) ($variables[$i + 1]->getValue()->getPrecisionMean() - $messages[$i + 1]->getValue()->getPrecisionMean())
/ /
($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision()); ($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
$anotherWeightedMeanSum += $weights[$i] * $diff->getPrecisionMean() / $diff->getPrecision();
} }
$newPrecision = 1.0 / $inverseOfNewPrecisionSum; $newPrecision = 1.0 / $inverseOfNewPrecisionSum;
@ -204,6 +198,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
return $finalDiff; return $finalDiff;
} }
#[\Override]
public function updateMessageIndex(int $messageIndex): float public function updateMessageIndex(int $messageIndex): float
{ {
$allMessages = $this->getMessages(); $allMessages = $this->getMessages();
@ -214,7 +209,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
$updatedMessages = []; $updatedMessages = [];
$updatedVariables = []; $updatedVariables = [];
$indicesToUse = $this->variableIndexOrdersForWeights[$messageIndex]; $indicesToUse = $this->varIndexOrdersForWeights[$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,
// so we just need to make sure they're consistent // so we just need to make sure they're consistent

View File

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

View File

@ -11,37 +11,40 @@ use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
use Exception; use Exception;
// The whole purpose of this is to do a loop on the bottom // 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( 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);
} }
#[\Override]
public function getLocalFactors(): array public function getLocalFactors(): array
{ {
return array_merge( return array_merge(
$this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(), $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(),
$this->TeamDifferencesComparisonLayer->getLocalFactors() $this->teamDifferencesComparisonLayer->getLocalFactors()
); );
} }
#[\Override]
public function buildLayer(): void public function buildLayer(): void
{ {
$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 #[\Override]
public function createPriorSchedule(): ScheduleSequence
{ {
switch (count($this->getInputVariablesGroups())) { switch (count($this->getInputVariablesGroups())) {
case 0: case 0:
@ -56,9 +59,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 = count($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()); $totalTeamDifferences = count($this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
$localFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(); $localFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
$firstDifferencesFactor = $localFactors[0]; $firstDifferencesFactor = $localFactors[0];
$lastDifferencesFactor = $localFactors[$totalTeamDifferences - 1]; $lastDifferencesFactor = $localFactors[$totalTeamDifferences - 1];
@ -83,8 +86,8 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
private function createTwoTeamInnerPriorLoopSchedule(): ScheduleSequence private function createTwoTeamInnerPriorLoopSchedule(): ScheduleSequence
{ {
$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];
@ -109,13 +112,13 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
private function createMultipleTeamInnerPriorLoopSchedule(): ScheduleLoop private function createMultipleTeamInnerPriorLoopSchedule(): ScheduleLoop
{ {
$totalTeamDifferences = count($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors()); $totalTeamDifferences = count($this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
$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];
@ -151,8 +154,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];

View File

@ -11,8 +11,9 @@ use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor;
use DNW\Skills\FactorGraphs\Variable; use DNW\Skills\FactorGraphs\Variable;
use DNW\Skills\FactorGraphs\KeyedVariable; use DNW\Skills\FactorGraphs\KeyedVariable;
class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLayer final class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLayer
{ {
#[\Override]
public function buildLayer(): void public function buildLayer(): void
{ {
$inputVariablesGroups = $this->getInputVariablesGroups(); $inputVariablesGroups = $this->getInputVariablesGroups();
@ -27,12 +28,13 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
$this->addLayerFactor($newSumFactor); $this->addLayerFactor($newSumFactor);
// REVIEW: Does it make sense to have groups of one? // REVIEW: Does it make sense to have groups of one?
$outputVariablesGroups = &$this->getOutputVariablesGroups(); $outputVarGroups = &$this->getOutputVariablesGroups();
$outputVariablesGroups[] = [$teamPerformance]; $outputVarGroups[] = [$teamPerformance];
} }
} }
public function createPriorSchedule(): ?ScheduleSequence #[\Override]
public function createPriorSchedule(): ScheduleSequence
{ {
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();
@ -49,7 +51,7 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
/** /**
* @param KeyedVariable[] $teamMembers * @param KeyedVariable[] $teamMembers
*/ */
protected function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor private function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor
{ {
$weights = array_map( $weights = array_map(
static function ($v): float { static function ($v): float {
@ -66,7 +68,8 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
); );
} }
public function createPosteriorSchedule(): ?ScheduleSequence #[\Override]
public function createPosteriorSchedule(): ScheduleSequence
{ {
$allFactors = []; $allFactors = [];
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();

View File

@ -17,7 +17,7 @@ use DNW\Skills\FactorGraphs\ScheduleSequence;
// We intentionally have no Posterior schedule since the only purpose here is to // We intentionally have no Posterior schedule since the only purpose here is to
// start the process. // start the process.
class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer final class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
{ {
/** /**
* @param Team[] $teams * @param Team[] $teams
@ -27,6 +27,7 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
parent::__construct($parentGraph); parent::__construct($parentGraph);
} }
#[\Override]
public function buildLayer(): void public function buildLayer(): void
{ {
$teams = $this->teams; $teams = $this->teams;
@ -34,12 +35,12 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
$localCurrentTeam = $currentTeam; $localCurrentTeam = $currentTeam;
$currentTeamSkills = []; $currentTeamSkills = [];
$currentTeamAllPlayers = $localCurrentTeam->getAllPlayers(); $curTeamAllPlayers = $localCurrentTeam->getAllPlayers();
foreach ($currentTeamAllPlayers as $currentTeamPlayer) { foreach ($curTeamAllPlayers as $curTeamPlayer) {
$localCurrentTeamPlayer = $currentTeamPlayer; $localCurTeamPlayer = $curTeamPlayer;
$currentTeamPlayerRating = $currentTeam->getRating($localCurrentTeamPlayer); $curTeamPlayerRating = $currentTeam->getRating($localCurTeamPlayer);
$playerSkill = $this->createSkillOutputVariable($localCurrentTeamPlayer); $playerSkill = $this->createSkillOutputVariable($localCurTeamPlayer);
$priorFactor = $this->createPriorFactor($currentTeamPlayerRating, $playerSkill); $priorFactor = $this->createPriorFactor($curTeamPlayerRating, $playerSkill);
$this->addLayerFactor($priorFactor); $this->addLayerFactor($priorFactor);
$currentTeamSkills[] = $playerSkill; $currentTeamSkills[] = $playerSkill;
} }
@ -49,7 +50,8 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
} }
} }
public function createPriorSchedule(): ?ScheduleSequence #[\Override]
public function createPriorSchedule(): ScheduleSequence
{ {
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();

View File

@ -11,29 +11,30 @@ use DNW\Skills\Numerics\BasicMath;
use DNW\Skills\TrueSkill\Factors\GaussianLikelihoodFactor; use DNW\Skills\TrueSkill\Factors\GaussianLikelihoodFactor;
use DNW\Skills\FactorGraphs\ScheduleSequence; use DNW\Skills\FactorGraphs\ScheduleSequence;
class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer final class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
{ {
#[\Override]
public function buildLayer(): void public function buildLayer(): void
{ {
$inputVariablesGroups = $this->getInputVariablesGroups(); $inputVarGroups = $this->getInputVariablesGroups();
$outputVariablesGroups = &$this->getOutputVariablesGroups(); $outputVarGroups = &$this->getOutputVariablesGroups();
foreach ($inputVariablesGroups as $currentTeam) { foreach ($inputVarGroups as $currentTeam) {
$currentTeamPlayerPerformances = []; $currentTeamPlayerPerformances = [];
/** /**
* @var Variable $playerSkillVariable * @var Variable $playerSkillVar
*/ */
foreach ($currentTeam as $playerSkillVariable) { foreach ($currentTeam as $playerSkillVar) {
$localPlayerSkillVariable = $playerSkillVariable; $localPlayerSkillVar = $playerSkillVar;
$currentPlayer = ($localPlayerSkillVariable instanceof KeyedVariable) ? $localPlayerSkillVariable->getKey() : ""; $currentPlayer = ($localPlayerSkillVar instanceof KeyedVariable) ? $localPlayerSkillVar->getKey() : "";
$playerPerformance = $this->createOutputVariable($currentPlayer); $playerPerformance = $this->createOutputVariable($currentPlayer);
$newLikelihoodFactor = $this->createLikelihood($localPlayerSkillVariable, $playerPerformance); $newLikelihoodFactor = $this->createLikelihood($localPlayerSkillVar, $playerPerformance);
$this->addLayerFactor($newLikelihoodFactor); $this->addLayerFactor($newLikelihoodFactor);
$currentTeamPlayerPerformances[] = $playerPerformance; $currentTeamPlayerPerformances[] = $playerPerformance;
} }
$outputVariablesGroups[] = $currentTeamPlayerPerformances; $outputVarGroups[] = $currentTeamPlayerPerformances;
} }
} }
@ -51,7 +52,8 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
return $this->getParentFactorGraph()->getVariableFactory()->createKeyedVariable($key); return $this->getParentFactorGraph()->getVariableFactory()->createKeyedVariable($key);
} }
public function createPriorSchedule(): ?ScheduleSequence #[\Override]
public function createPriorSchedule(): ScheduleSequence
{ {
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();
@ -65,7 +67,8 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
); );
} }
public function createPosteriorSchedule(): ?ScheduleSequence #[\Override]
public function createPosteriorSchedule(): ScheduleSequence
{ {
$localFactors = $this->getLocalFactors(); $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\Factors\GaussianWithinFactor;
use DNW\Skills\TrueSkill\TrueSkillFactorGraph; use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer final class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
{ {
private readonly float $epsilon; private readonly float $epsilon;
@ -23,6 +23,7 @@ class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
$this->epsilon = DrawMargin::getDrawMarginFromDrawProbability($gameInfo->getDrawProbability(), $gameInfo->getBeta()); $this->epsilon = DrawMargin::getDrawMarginFromDrawProbability($gameInfo->getDrawProbability(), $gameInfo->getBeta());
} }
#[\Override]
public function buildLayer(): void public function buildLayer(): void
{ {
$inputVarGroups = $this->getInputVariablesGroups(); $inputVarGroups = $this->getInputVariablesGroups();

View File

@ -7,8 +7,9 @@ namespace DNW\Skills\TrueSkill\Layers;
use DNW\Skills\FactorGraphs\Variable; use DNW\Skills\FactorGraphs\Variable;
use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor; use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor;
class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorGraphLayer final class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorGraphLayer
{ {
#[\Override]
public function buildLayer(): void public function buildLayer(): void
{ {
$inputVariablesGroups = $this->getInputVariablesGroups(); $inputVariablesGroups = $this->getInputVariablesGroups();
@ -28,11 +29,7 @@ class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorG
} }
} }
private function createTeamPerformanceToDifferenceFactor( private function createTeamPerformanceToDifferenceFactor(Variable $strongerTeam, Variable $weakerTeam, Variable $output): GaussianWeightedSumFactor
Variable $strongerTeam,
Variable $weakerTeam,
Variable $output
): GaussianWeightedSumFactor
{ {
$teams = [$strongerTeam, $weakerTeam]; $teams = [$strongerTeam, $weakerTeam];
$weights = [1.0, -1.0]; $weights = [1.0, -1.0];

View File

@ -9,8 +9,4 @@ use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
abstract class TrueSkillFactorGraphLayer extends FactorGraphLayer abstract class TrueSkillFactorGraphLayer extends FactorGraphLayer
{ {
public function __construct(TrueSkillFactorGraph $parentGraph)
{
parent::__construct($parentGraph);
}
} }

View File

@ -23,16 +23,18 @@ use DNW\Skills\TrueSkill\Layers\PlayerSkillsToPerformancesLayer;
use DNW\Skills\TrueSkill\Layers\TeamDifferencesComparisonLayer; use DNW\Skills\TrueSkill\Layers\TeamDifferencesComparisonLayer;
use DNW\Skills\TrueSkill\Layers\TeamPerformancesToTeamPerformanceDifferencesLayer; use DNW\Skills\TrueSkill\Layers\TeamPerformancesToTeamPerformanceDifferencesLayer;
class TrueSkillFactorGraph extends FactorGraph final class TrueSkillFactorGraph extends FactorGraph
{ {
/** /**
* @var FactorGraphLayer[] $layers * @var FactorGraphLayer[] $layers
*/ */
private array $layers; private readonly array $layers;
private readonly PlayerPriorValuesToSkillsLayer $priorLayer; private readonly PlayerPriorValuesToSkillsLayer $priorLayer;
/** /**
* Constructor
*
* @param GameInfo $gameInfo Parameters for the game. * @param GameInfo $gameInfo Parameters for the game.
* @param Team[] $teams A mapping of team players and their ratings. * @param Team[] $teams A mapping of team players and their ratings.
* @param int[] $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2). * @param int[] $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2).
@ -118,9 +120,9 @@ 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(); $curPosteriorSchedule = $currentLayer->createPosteriorSchedule();
if ($currentPosteriorSchedule != NULL) { if ($curPosteriorSchedule != NULL) {
$fullSchedule[] = $currentPosteriorSchedule; $fullSchedule[] = $curPosteriorSchedule;
} }
} }

View File

@ -6,7 +6,7 @@ namespace DNW\Skills\TrueSkill;
use DNW\Skills\Numerics\GaussianDistribution; use DNW\Skills\Numerics\GaussianDistribution;
class TruncatedGaussianCorrectionFunctions final class TruncatedGaussianCorrectionFunctions
{ {
// These functions from the bottom of page 4 of the TrueSkill paper. // These functions from the bottom of page 4 of the TrueSkill paper.
@ -102,33 +102,33 @@ class TruncatedGaussianCorrectionFunctions
} }
// the multiplicative correction of a double-sided truncated Gaussian with unit variance // the multiplicative correction of a double-sided truncated Gaussian with unit variance
public static function wWithinMarginScaled(float $teamPerformanceDifference, float $drawMargin, float $c): float public static function wWithinMarginScaled(float $teamPerformanceDiff, float $drawMargin, float $c): float
{ {
return self::wWithinMargin($teamPerformanceDifference / $c, $drawMargin / $c); return self::wWithinMargin($teamPerformanceDiff / $c, $drawMargin / $c);
} }
// From F#: // From F#:
public static function wWithinMargin(float $teamPerformanceDifference, float $drawMargin): float public static function wWithinMargin(float $teamPerformanceDiff, float $drawMargin): float
{ {
$teamPerformanceDifferenceAbsoluteValue = abs($teamPerformanceDifference); $teamPerformanceDiffAbsValue = abs($teamPerformanceDiff);
$denominator = GaussianDistribution::cumulativeTo($drawMargin - $teamPerformanceDifferenceAbsoluteValue) $denominator = GaussianDistribution::cumulativeTo($drawMargin - $teamPerformanceDiffAbsValue)
- -
GaussianDistribution::cumulativeTo(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue); GaussianDistribution::cumulativeTo(-$drawMargin - $teamPerformanceDiffAbsValue);
if ($denominator < 2.222758749e-162) { if ($denominator < 2.222758749e-162) {
return 1.0; return 1.0;
} }
$vt = self::vWithinMargin($teamPerformanceDifferenceAbsoluteValue, $drawMargin); $vt = self::vWithinMargin($teamPerformanceDiffAbsValue, $drawMargin);
return $vt * $vt + return $vt * $vt +
(($drawMargin - $teamPerformanceDifferenceAbsoluteValue) (($drawMargin - $teamPerformanceDiffAbsValue)
* *
GaussianDistribution::at( GaussianDistribution::at(
$drawMargin - $teamPerformanceDifferenceAbsoluteValue $drawMargin - $teamPerformanceDiffAbsValue
) )
- (-$drawMargin - $teamPerformanceDifferenceAbsoluteValue) - (-$drawMargin - $teamPerformanceDiffAbsValue)
* *
GaussianDistribution::at(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)) / $denominator; GaussianDistribution::at(-$drawMargin - $teamPerformanceDiffAbsValue)) / $denominator;
} }
} }

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 * 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. * 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() public function __construct()
{ {
@ -31,11 +31,8 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function calculateNewRatings( #[\Override]
GameInfo $gameInfo, public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer
array $teams,
array $teamRanks
): RatingContainer
{ {
// Basic argument checking // Basic argument checking
$this->validateTeamCountAndPlayersCountPerTeam($teams); $this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -94,7 +91,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
+ +
BasicMath::square($opponentRating->getStandardDeviation()) BasicMath::square($opponentRating->getStandardDeviation())
+ +
2 * BasicMath::square($gameInfo->getBeta()) 2.0 * BasicMath::square($gameInfo->getBeta())
); );
$winningMean = $selfRating->getMean(); $winningMean = $selfRating->getMean();
@ -117,11 +114,11 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
// non-draw case // non-draw case
$v = TruncatedGaussianCorrectionFunctions::vExceedsMarginScaled($meanDelta, $drawMargin, $c); $v = TruncatedGaussianCorrectionFunctions::vExceedsMarginScaled($meanDelta, $drawMargin, $c);
$w = TruncatedGaussianCorrectionFunctions::wExceedsMarginScaled($meanDelta, $drawMargin, $c); $w = TruncatedGaussianCorrectionFunctions::wExceedsMarginScaled($meanDelta, $drawMargin, $c);
$rankMultiplier = $comparison->value; $rankMultiplier = (float)$comparison->value;
} else { } else {
$v = TruncatedGaussianCorrectionFunctions::vWithinMarginScaled($meanDelta, $drawMargin, $c); $v = TruncatedGaussianCorrectionFunctions::vWithinMarginScaled($meanDelta, $drawMargin, $c);
$w = TruncatedGaussianCorrectionFunctions::wWithinMarginScaled($meanDelta, $drawMargin, $c); $w = TruncatedGaussianCorrectionFunctions::wWithinMarginScaled($meanDelta, $drawMargin, $c);
$rankMultiplier = 1; $rankMultiplier = 1.0;
} }
$meanMultiplier = (BasicMath::square($selfRating->getStandardDeviation()) + BasicMath::square($gameInfo->getDynamicsFactor())) / $c; $meanMultiplier = (BasicMath::square($selfRating->getStandardDeviation()) + BasicMath::square($gameInfo->getDynamicsFactor())) / $c;
@ -130,7 +127,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
$stdDevMultiplier = $varianceWithDynamics / BasicMath::square($c); $stdDevMultiplier = $varianceWithDynamics / BasicMath::square($c);
$newMean = $selfRating->getMean() + ($rankMultiplier * $meanMultiplier * $v); $newMean = $selfRating->getMean() + ($rankMultiplier * $meanMultiplier * $v);
$newStdDev = sqrt($varianceWithDynamics * (1 - $w * $stdDevMultiplier)); $newStdDev = sqrt($varianceWithDynamics * (1.0 - $w * $stdDevMultiplier));
return new Rating($newMean, $newStdDev); return new Rating($newMean, $newStdDev);
} }
@ -138,6 +135,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
#[\Override]
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
{ {
$this->validateTeamCountAndPlayersCountPerTeam($teams); $this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -158,16 +156,16 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
// This is the square root part of the equation: // This is the square root part of the equation:
$sqrtPart = sqrt( $sqrtPart = sqrt(
(2 * $betaSquared) (2.0 * $betaSquared)
/ /
(2 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared) (2.0 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared)
); );
// This is the exponent part of the equation: // This is the exponent part of the equation:
$expPart = exp( $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; 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. * 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() public function __construct()
{ {
@ -31,6 +31,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
#[\Override]
public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer
{ {
$this->validateTeamCountAndPlayersCountPerTeam($teams); $this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -63,13 +64,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
return $results; return $results;
} }
private static function updatePlayerRatings( private static function updatePlayerRatings(GameInfo $gameInfo, RatingContainer $newPlayerRatings, Team $selfTeam, Team $otherTeam, PairwiseComparison $selfToOtherTeamComparison): void
GameInfo $gameInfo,
RatingContainer $newPlayerRatings,
Team $selfTeam,
Team $otherTeam,
PairwiseComparison $selfToOtherTeamComparison
): void
{ {
$drawMargin = DrawMargin::getDrawMarginFromDrawProbability( $drawMargin = DrawMargin::getDrawMarginFromDrawProbability(
$gameInfo->getDrawProbability(), $gameInfo->getDrawProbability(),
@ -93,7 +88,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
+ +
BasicMath::sum($otherTeam->getAllRatings(), $varianceGetter) BasicMath::sum($otherTeam->getAllRatings(), $varianceGetter)
+ +
$totalPlayers * $betaSquared (float)$totalPlayers * $betaSquared
); );
$winningMean = $selfMeanSum; $winningMean = $selfMeanSum;
@ -116,18 +111,18 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
// non-draw case // non-draw case
$v = TruncatedGaussianCorrectionFunctions::vExceedsMarginScaled($meanDelta, $drawMargin, $c); $v = TruncatedGaussianCorrectionFunctions::vExceedsMarginScaled($meanDelta, $drawMargin, $c);
$w = TruncatedGaussianCorrectionFunctions::wExceedsMarginScaled($meanDelta, $drawMargin, $c); $w = TruncatedGaussianCorrectionFunctions::wExceedsMarginScaled($meanDelta, $drawMargin, $c);
$rankMultiplier = $selfToOtherTeamComparison->value; $rankMultiplier = (float)$selfToOtherTeamComparison->value;
} else { } else {
// assume draw // assume draw
$v = TruncatedGaussianCorrectionFunctions::vWithinMarginScaled($meanDelta, $drawMargin, $c); $v = TruncatedGaussianCorrectionFunctions::vWithinMarginScaled($meanDelta, $drawMargin, $c);
$w = TruncatedGaussianCorrectionFunctions::wWithinMarginScaled($meanDelta, $drawMargin, $c); $w = TruncatedGaussianCorrectionFunctions::wWithinMarginScaled($meanDelta, $drawMargin, $c);
$rankMultiplier = 1; $rankMultiplier = 1.0;
} }
$selfTeamAllPlayers = $selfTeam->getAllPlayers(); $selfTeamAllPlayers = $selfTeam->getAllPlayers();
foreach ($selfTeamAllPlayers as $selfTeamCurrentPlayer) { foreach ($selfTeamAllPlayers as $selfTeamCurPlayer) {
$localSelfTeamCurrentPlayer = $selfTeamCurrentPlayer; $localSelfTeamCurPlayer = $selfTeamCurPlayer;
$previousPlayerRating = $selfTeam->getRating($localSelfTeamCurrentPlayer); $previousPlayerRating = $selfTeam->getRating($localSelfTeamCurPlayer);
$meanMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / $c; $meanMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / $c;
$stdDevMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / BasicMath::square($c); $stdDevMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / BasicMath::square($c);
@ -136,16 +131,17 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
$newMean = $previousPlayerRating->getMean() + $playerMeanDelta; $newMean = $previousPlayerRating->getMean() + $playerMeanDelta;
$newStdDev = sqrt( $newStdDev = sqrt(
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1 - $w * $stdDevMultiplier) (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1.0 - $w * $stdDevMultiplier)
); );
$newPlayerRatings->setRating($localSelfTeamCurrentPlayer, new Rating($newMean, $newStdDev)); $newPlayerRatings->setRating($localSelfTeamCurPlayer, new Rating($newMean, $newStdDev));
} }
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
#[\Override]
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
{ {
$this->validateTeamCountAndPlayersCountPerTeam($teams); $this->validateTeamCountAndPlayersCountPerTeam($teams);
@ -157,7 +153,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
$team2Ratings = $teams[1]->getAllRatings(); $team2Ratings = $teams[1]->getAllRatings();
$team2Count = count($team2Ratings); $team2Count = count($team2Ratings);
$totalPlayers = $team1Count + $team2Count; $totalPlayers = (float)($team1Count + $team2Count);
$betaSquared = BasicMath::square($gameInfo->getBeta()); $betaSquared = BasicMath::square($gameInfo->getBeta());
@ -182,9 +178,9 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
); );
$expPart = exp( $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; return $expPart * $sqrtPart;

View File

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

View File

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

View File

@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(Guard::class)] #[CoversClass(Guard::class)]
class GuardTest extends TestCase final class GuardTest extends TestCase
{ {
public function testargumentIsValidIndexArgumentAbove(): void public function testargumentIsValidIndexArgumentAbove(): void
{ {
@ -28,7 +28,9 @@ class GuardTest extends TestCase
public function testargumentIsValidIndexArgumentValid(): void public function testargumentIsValidIndexArgumentValid(): void
{ {
Guard::argumentIsValidIndex(5, 10, "dummy"); Guard::argumentIsValidIndex(0, 10, "dummy");
Guard::argumentIsValidIndex(1, 10, "dummy");
Guard::argumentIsValidIndex(9, 10, "dummy");
$this->expectNotToPerformAssertions(); $this->expectNotToPerformAssertions();
} }
@ -48,7 +50,12 @@ class GuardTest extends TestCase
public function testargumentInRangeInclusiveValid(): void public function testargumentInRangeInclusiveValid(): void
{ {
Guard::argumentInRangeInclusive(0, 0, 100, "dummy");
Guard::argumentInRangeInclusive(1, 0, 100, "dummy");
Guard::argumentInRangeInclusive(50, 0, 100, "dummy"); Guard::argumentInRangeInclusive(50, 0, 100, "dummy");
Guard::argumentInRangeInclusive(99, 0, 100, "dummy");
Guard::argumentInRangeInclusive(100, 0, 100, "dummy");
$this->expectNotToPerformAssertions(); $this->expectNotToPerformAssertions();
} }
} }

View File

@ -7,9 +7,10 @@ namespace DNW\Skills\Tests;
use DNW\Skills\HashMap; use DNW\Skills\HashMap;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use stdClass;
#[CoversClass(HashMap::class)] #[CoversClass(HashMap::class)]
class HashMapTest extends TestCase final class HashMapTest extends TestCase
{ {
public function testHashmap(): void public function testHashmap(): void
{ {
@ -19,13 +20,14 @@ class HashMapTest extends TestCase
$this->assertEquals([], $h->getAllValues()); $this->assertEquals([], $h->getAllValues());
$o1 = new \stdClass(); $o1 = new stdClass();
$o2 = new \stdClass(); $o2 = new stdClass();
$h->setValue($o1, 1); $h->setValue($o1, 1);
$h->setvalue($o2, 2); $h->setvalue($o2, 2);
$this->assertEquals([1, 2], $h->getAllValues()); $this->assertEquals([1, 2], $h->getAllValues());
$this->assertEquals([$o1, $o2], $h->getAllKeys());
$this->assertEquals(1, $h->getvalue($o1)); $this->assertEquals(1, $h->getvalue($o1));
$this->assertEquals(2, $h->getvalue($o2)); $this->assertEquals(2, $h->getvalue($o2));

View File

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

View File

@ -12,9 +12,9 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(GaussianDistribution::class)] #[CoversClass(GaussianDistribution::class)]
#[UsesClass(BasicMath::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 public function testGetters(): void
{ {
@ -24,7 +24,7 @@ class GaussianDistributionTest extends TestCase
$this->assertEquals(9, $gd->getVariance()); $this->assertEquals(9, $gd->getVariance());
$this->assertEquals(3, $gd->getStandardDeviation()); $this->assertEquals(3, $gd->getStandardDeviation());
$this->assertEquals(1 / 9, $gd->getPrecision()); $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); $this->assertEqualsWithDelta(0.13298076013, $gd->getNormalizationConstant(), GaussianDistributionTest::ERROR_TOLERANCE);
} }
@ -57,7 +57,7 @@ class GaussianDistributionTest extends TestCase
$product2 = GaussianDistribution::multiply($m4s5, $m6s7); $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); $this->assertEqualsWithDelta($expectedMean, $product2->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
$expectedSigma = sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7)))); $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(2.0, $productDividedByStandardNormal->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
$this->assertEqualsWithDelta(3.0, $productDividedByStandardNormal->getStandardDeviation(), 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); $m4s5 = new GaussianDistribution(4, 5);
$product2DividedByM4S5 = GaussianDistribution::divide($product2, $m4s5); $product2DividedByM4S5 = GaussianDistribution::divide($product2, $m4s5);
$this->assertEqualsWithDelta(6.0, $product2DividedByM4S5->getMean(), GaussianDistributionTest::ERROR_TOLERANCE); $this->assertEqualsWithDelta(6.0, $product2DividedByM4S5->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
@ -92,6 +92,21 @@ class GaussianDistributionTest extends TestCase
$m3s4 = new GaussianDistribution(3, 4); $m3s4 = new GaussianDistribution(3, 4);
$lpn2 = GaussianDistribution::logProductNormalization($m1s2, $m3s4); $lpn2 = GaussianDistribution::logProductNormalization($m1s2, $m3s4);
$this->assertEqualsWithDelta(-2.5168046699816684, $lpn2, GaussianDistributionTest::ERROR_TOLERANCE); $this->assertEqualsWithDelta(-2.5168046699816684, $lpn2, GaussianDistributionTest::ERROR_TOLERANCE);
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
$lrn = GaussianDistribution::logProductNormalization($numerator, $denominator);
$this->assertEquals(0, $lrn);
$numerator = GaussianDistribution::fromPrecisionMean(1, 1);
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
$lrn = GaussianDistribution::logProductNormalization($numerator, $denominator);
$this->assertEquals(0, $lrn);
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
$denominator = GaussianDistribution::fromPrecisionMean(1, 1);
$lrn = GaussianDistribution::logProductNormalization($numerator, $denominator);
$this->assertEquals(0, $lrn);
} }
public function testLogRatioNormalization(): void public function testLogRatioNormalization(): void
@ -101,6 +116,21 @@ class GaussianDistributionTest extends TestCase
$m3s4 = new GaussianDistribution(3, 4); $m3s4 = new GaussianDistribution(3, 4);
$lrn = GaussianDistribution::logRatioNormalization($m1s2, $m3s4); $lrn = GaussianDistribution::logRatioNormalization($m1s2, $m3s4);
$this->assertEqualsWithDelta(2.6157405972171204, $lrn, GaussianDistributionTest::ERROR_TOLERANCE); $this->assertEqualsWithDelta(2.6157405972171204, $lrn, GaussianDistributionTest::ERROR_TOLERANCE);
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
$lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator);
$this->assertEquals(0, $lrn);
$numerator = GaussianDistribution::fromPrecisionMean(1, 1);
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
$lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator);
$this->assertEquals(0, $lrn);
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
$denominator = GaussianDistribution::fromPrecisionMean(1, 1);
$lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator);
$this->assertEquals(0, $lrn);
} }
public function testAbsoluteDifference(): void public function testAbsoluteDifference(): void
@ -115,4 +145,27 @@ class GaussianDistributionTest extends TestCase
$absDiff2 = GaussianDistribution::absoluteDifference($m1s2, $m3s4); $absDiff2 = GaussianDistribution::absoluteDifference($m1s2, $m3s4);
$this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE); $this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE);
} }
public function testSubtract(): void
{
// Verified with Ralf Herbrich's F# implementation
$standardNormal = new GaussianDistribution(0, 1);
$absDiff = GaussianDistribution::subtract($standardNormal, $standardNormal);
$this->assertEqualsWithDelta(0.0, $absDiff, GaussianDistributionTest::ERROR_TOLERANCE);
$m1s2 = new GaussianDistribution(1, 2);
$m3s4 = new GaussianDistribution(3, 4);
$absDiff2 = GaussianDistribution::subtract($m1s2, $m3s4);
$this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE);
}
public function testfromPrecisionMean(): void
{
$gd = GaussianDistribution::fromPrecisionMean(0, 0);
$this->assertInfinite($gd->getVariance());
$this->assertInfinite($gd->getStandardDeviation());
$this->assertNan($gd->getMean());
$this->assertEquals(0, $gd->getPrecisionMean());
$this->assertEquals(0, $gd->getPrecision());
}
} }

View File

@ -20,8 +20,85 @@ use Exception;
#[CoversClass(DiagonalMatrix::class)] #[CoversClass(DiagonalMatrix::class)]
#[CoversClass(Vector::class)] #[CoversClass(Vector::class)]
// phpcs:disable PSR2.Methods.FunctionCallSignature,Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma // phpcs:disable PSR2.Methods.FunctionCallSignature,Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
class MatrixTest extends TestCase final class MatrixTest extends TestCase
{ {
public function testEmptyMatrix(): void
{
$m1 = new Matrix();
$this->assertEquals(0, $m1->getRowCount());
$this->assertEquals(0, $m1->getColumnCount());
$m2 = new Matrix(0, 0);
$this->assertEquals(0, $m2->getRowCount());
$this->assertEquals(0, $m2->getColumnCount());
$this->assertEquals(new Matrix(), Matrix::multiply($m1, $m2));
}
public function testIndexing(): void
{
$m = new Matrix(5, 5);
$m->setValue(0, 0, 1);
$this->assertEquals(1, $m->getValue(0, 0));
$m->setValue(0, 1, 2);
$this->assertEquals(2, $m->getValue(0, 1));
$m->setValue(1, 0, 3);
$this->assertEquals(3, $m->getValue(1, 0));
$m->setValue(1, 1, 4);
$this->assertEquals(4, $m->getValue(1, 1));
$m->setValue(3, 3, 11);
$this->assertEquals(11, $m->getValue(3, 3));
$m->setValue(4, 3, 22);
$this->assertEquals(22, $m->getValue(4, 3));
$m->setValue(3, 4, 33);
$this->assertEquals(33, $m->getValue(3, 4));
$m->setValue(4, 4, 44);
$this->assertEquals(44, $m->getValue(4, 4));
try {
$m->getValue(-1, -1);
$this->fail("No exception");
} catch (Exception $exception) {
$this->assertInstanceOf(Exception::class, $exception);
}
try {
$m->getValue(-1, 0);
$this->fail("No exception");
} catch (Exception $exception) {
$this->assertInstanceOf(Exception::class, $exception);
}
try {
$m->getValue(0, -1);
$this->fail("No exception");
} catch (Exception $exception) {
$this->assertInstanceOf(Exception::class, $exception);
}
try {
$m->getValue(5, 5);
$this->fail("No exception");
} catch (Exception $exception) {
$this->assertInstanceOf(Exception::class, $exception);
}
try {
$m->getValue(5, 4);
$this->fail("No exception");
} catch (Exception $exception) {
$this->assertInstanceOf(Exception::class, $exception);
}
try {
$m->getValue(4, 5);
$this->fail("No exception");
} catch (Exception $exception) {
$this->assertInstanceOf(Exception::class, $exception);
}
}
public function testOneByOneDeterminant(): void public function testOneByOneDeterminant(): void
{ {
$a = new SquareMatrix(1); $a = new SquareMatrix(1);
@ -218,7 +295,7 @@ class MatrixTest extends TestCase
1, 0, 6); 1, 0, 6);
$cInverse = $c->getInverse(); $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, 5, 3, -5,
-4, 2, 4)); -4, 2, 4));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace DNW\Skills\Tests;
use DNW\Skills\SkillCalculator;
use DNW\Skills\TeamsRange;
use DNW\Skills\PlayersRange;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\Attributes\RequiresPhpunit;
#[CoversClass(SkillCalculator::class)]
#[UsesClass(\DNW\Skills\Numerics\Range::class)]
#[UsesClass(PlayersRange::class)]
#[UsesClass(TeamsRange::class)]
#[RequiresPhpunit('<12.0')]
final class SkillCalculatorTest extends TestCase
{
public function testisSupported(): void
{
$calculator = $this->getMockForAbstractClass(SkillCalculator::class, [SkillCalculator::PARTIAL_PLAY, new TeamsRange(1, 2), new PlayersRange(1, 2)]);
$this->assertEquals(TRUE, $calculator->isSupported(SkillCalculator::PARTIAL_PLAY));
$this->assertEquals(FALSE, $calculator->isSupported(SkillCalculator::PARTIAL_UPDATE));
}
}

View File

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

View File

@ -14,9 +14,9 @@ use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(DrawMargin::class)] #[CoversClass(DrawMargin::class)]
#[UsesClass(BasicMath::class)] #[UsesClass(BasicMath::class)]
#[UsesClass(GaussianDistribution::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 public function testGetDrawMarginFromDrawProbability(): void
{ {

View File

@ -4,22 +4,15 @@ declare(strict_types=1);
namespace DNW\Skills\Tests\TrueSkill; namespace DNW\Skills\Tests\TrueSkill;
use DNW\Skills\SkillCalculator;
use DNW\Skills\GameInfo; use DNW\Skills\GameInfo;
use DNW\Skills\Player; use DNW\Skills\Player;
use DNW\Skills\Team; use DNW\Skills\Team;
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator; use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
use DNW\Skills\SkillCalculator;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(FactorGraphTrueSkillCalculator::class)] final class FactorGraphTrueSkillCalculatorTest extends TestCase
#[UsesClass(\DNW\Skills\Numerics\Range::class)]
#[UsesClass(\DNW\Skills\PlayersRange::class)]
#[UsesClass(\DNW\Skills\SkillCalculator::class)]
#[UsesClass(\DNW\Skills\TeamsRange::class)]
class FactorGraphTrueSkillCalculatorTest extends TestCase
{ {
#[CoversNothing] #[CoversNothing]
public function testMicrosoftResearchExample(): void public function testMicrosoftResearchExample(): void
@ -75,6 +68,7 @@ class FactorGraphTrueSkillCalculatorTest extends TestCase
TrueSkillCalculatorTests::testPartialPlayScenarios($this, $calculator); TrueSkillCalculatorTests::testPartialPlayScenarios($this, $calculator);
} }
#[CoversNothing]
public function testMethodisSupported(): void public function testMethodisSupported(): void
{ {
$calculator = new FactorGraphTrueSkillCalculator(); $calculator = new FactorGraphTrueSkillCalculator();

View File

@ -9,14 +9,13 @@ use DNW\Skills\Player;
use DNW\Skills\Rating; use DNW\Skills\Rating;
use DNW\Skills\SkillCalculator; use DNW\Skills\SkillCalculator;
use DNW\Skills\Team; use DNW\Skills\Team;
use DNW\Skills\Teams;
use PHPUnit\Framework\TestCase; 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 // These are the roll-up ones
@ -70,7 +69,6 @@ class TrueSkillCalculatorTests
// online calculator at http://atom.research.microsoft.com/trueskill/rankcalculator.aspx // online calculator at http://atom.research.microsoft.com/trueskill/rankcalculator.aspx
// //
// All match quality expected values came from the online calculator // All match quality expected values came from the online calculator
// In both cases, there may be some discrepancy after the first decimal point. I think this is due to my implementation // In both cases, there may be some discrepancy after the first decimal point. I think this is due to my implementation
// using slightly higher precision in GaussianDistribution. // using slightly higher precision in GaussianDistribution.
@ -813,15 +811,15 @@ class TrueSkillCalculatorTests
private static function sixteenTeamsOfOneNotDrawn(TestCase $testClass, SkillCalculator $calculator): void private static function sixteenTeamsOfOneNotDrawn(TestCase $testClass, SkillCalculator $calculator): void
{ {
$player1 = new Player(1); $player1 = new Player(1);
$player2 = new Player(2); $player2 = new Player(2);
$player3 = new Player(3); $player3 = new Player(3);
$player4 = new Player(4); $player4 = new Player(4);
$player5 = new Player(5); $player5 = new Player(5);
$player6 = new Player(6); $player6 = new Player(6);
$player7 = new Player(7); $player7 = new Player(7);
$player8 = new Player(8); $player8 = new Player(8);
$player9 = new Player(9); $player9 = new Player(9);
$player10 = new Player(10); $player10 = new Player(10);
$player11 = new Player(11); $player11 = new Player(11);
$player12 = new Player(12); $player12 = new Player(12);
@ -832,15 +830,15 @@ class TrueSkillCalculatorTests
$gameInfo = new GameInfo(); $gameInfo = new GameInfo();
$team1 = new Team($player1, $gameInfo->getDefaultRating()); $team1 = new Team($player1, $gameInfo->getDefaultRating());
$team2 = new Team($player2, $gameInfo->getDefaultRating()); $team2 = new Team($player2, $gameInfo->getDefaultRating());
$team3 = new Team($player3, $gameInfo->getDefaultRating()); $team3 = new Team($player3, $gameInfo->getDefaultRating());
$team4 = new Team($player4, $gameInfo->getDefaultRating()); $team4 = new Team($player4, $gameInfo->getDefaultRating());
$team5 = new Team($player5, $gameInfo->getDefaultRating()); $team5 = new Team($player5, $gameInfo->getDefaultRating());
$team6 = new Team($player6, $gameInfo->getDefaultRating()); $team6 = new Team($player6, $gameInfo->getDefaultRating());
$team7 = new Team($player7, $gameInfo->getDefaultRating()); $team7 = new Team($player7, $gameInfo->getDefaultRating());
$team8 = new Team($player8, $gameInfo->getDefaultRating()); $team8 = new Team($player8, $gameInfo->getDefaultRating());
$team9 = new Team($player9, $gameInfo->getDefaultRating()); $team9 = new Team($player9, $gameInfo->getDefaultRating());
$team10 = new Team($player10, $gameInfo->getDefaultRating()); $team10 = new Team($player10, $gameInfo->getDefaultRating());
$team11 = new Team($player11, $gameInfo->getDefaultRating()); $team11 = new Team($player11, $gameInfo->getDefaultRating());
$team12 = new Team($player12, $gameInfo->getDefaultRating()); $team12 = new Team($player12, $gameInfo->getDefaultRating());
@ -849,24 +847,7 @@ class TrueSkillCalculatorTests
$team15 = new Team($player15, $gameInfo->getDefaultRating()); $team15 = new Team($player15, $gameInfo->getDefaultRating());
$team16 = new Team($player16, $gameInfo->getDefaultRating()); $team16 = new Team($player16, $gameInfo->getDefaultRating());
$teams = [ $teams = [$team1, $team2, $team3, $team4, $team5, $team6, $team7, $team8, $team9, $team10, $team11, $team12, $team13, $team14, $team15, $team16];
$team1,
$team2,
$team3,
$team4,
$team5,
$team6,
$team7,
$team8,
$team9,
$team10,
$team11,
$team12,
$team13,
$team14,
$team15,
$team16
];
$newRatings = $calculator->calculateNewRatings( $newRatings = $calculator->calculateNewRatings(
$gameInfo, $gameInfo,

View File

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

View File

@ -6,11 +6,9 @@ namespace DNW\Skills\Tests\TrueSkill;
use DNW\Skills\TrueSkill\TwoPlayerTrueSkillCalculator; use DNW\Skills\TrueSkill\TwoPlayerTrueSkillCalculator;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\CoversNothing;
#[CoversClass(TwoPlayerTrueSkillCalculator::class)] final class TwoPlayerTrueSkillCalculatorTest extends TestCase
class TwoPlayerTrueSkillCalculatorTest extends TestCase
{ {
#[CoversNothing] #[CoversNothing]
public function testTwoPlayerTrueSkillCalculator(): void public function testTwoPlayerTrueSkillCalculator(): void

View File

@ -6,11 +6,9 @@ namespace DNW\Skills\Tests\TrueSkill;
use DNW\Skills\TrueSkill\TwoTeamTrueSkillCalculator; use DNW\Skills\TrueSkill\TwoTeamTrueSkillCalculator;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\CoversNothing;
#[CoversClass(TwoTeamTrueSkillCalculator::class)] final class TwoTeamTrueSkillCalculatorTest extends TestCase
class TwoTeamTrueSkillCalculatorTest extends TestCase
{ {
#[CoversNothing] #[CoversNothing]
public function testTwoTeamTrueSkillCalculator(): void public function testTwoTeamTrueSkillCalculator(): void