Compare commits

...

33 Commits

Author SHA1 Message Date
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
5dc0c7058d Readding metrics to output 2024-07-01 09:31:35 +00:00
ed9013df34 Typical update 2024-06-18 12:50:00 +00:00
da5d782e40 Simplifying 2024-05-21 12:24:59 +00:00
de6a414d2b Types with newest rector version
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-21 06:50:32 +00:00
6fbc2540ab Config update
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-17 06:17:37 +00:00
4b3a328726 Additional tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-15 12:41:34 +00:00
82a5505438 Codestandards 2024-05-15 07:13:01 +00:00
cbf03c5736 Additional tests. 2024-05-15 07:01:15 +00:00
65db7f64ea Teams::concat was unnessecary 2024-05-14 11:12:26 +00:00
5cf6acdfb1 More tests 2024-05-14 09:41:17 +00:00
46dcbed28b Strict code coverage.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-14 08:46:43 +00:00
c33f62af2f Additional tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-13 15:17:13 +00:00
c202330a77 Modern syntax for CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-06 09:25:27 +00:00
1574f1c878 Code coverage not performed on buildserver.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-06 09:19:41 +00:00
002f07003a Dependecy upgrade
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-04-30 07:23:28 +00:00
063a64a4f6 Less Stringable
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-03-19 16:24:19 +00:00
796bd993d0 Even less strings 2024-03-19 15:56:57 +00:00
bbff1fbbbc More stringable removal for performance. 2024-03-19 15:48:29 +00:00
ae5d2a8b73 String based "name" for Variable class removed for performance 2024-03-19 15:09:13 +00:00
0095829906 Stringable removed. 2024-03-19 14:38:55 +00:00
e5a96226ca More stringable 2024-03-19 14:10:19 +00:00
11fafc129a Slowly getting rid of stringable. 2024-03-19 14:10:11 +00:00
73ef2f45c8 Quicker hash 2024-03-19 12:57:56 +00:00
fd91e9b0c1 Minor performance boost 2024-03-19 12:49:14 +00:00
7b09658c25 Minor fixes
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-03-11 08:55:07 +00:00
77 changed files with 58452 additions and 1036 deletions

View File

@ -9,5 +9,4 @@ exclude:
- vendor - vendor
warning: true warning: true
memory-limit: -1 memory-limit: -1
no-cache: true
log-junit: "output/lint.xml" log-junit: "output/lint.xml"

View File

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

View File

@ -10,8 +10,10 @@ use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
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\Teams;
/**
* Basic Benchmarks.
*/
class BasicBench class BasicBench
{ {
/** /**
@ -31,7 +33,7 @@ class BasicBench
$team2 = new Team($p2, $gameInfo->getDefaultRating()); $team2 = new Team($p2, $gameInfo->getDefaultRating());
for ($i = 0; $i < 10; ++$i) { for ($i = 0; $i < 10; ++$i) {
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$calculator = new TwoPlayerTrueSkillCalculator(); $calculator = new TwoPlayerTrueSkillCalculator();
@ -62,7 +64,7 @@ class BasicBench
$team2 = new Team($p2, $gameInfo->getDefaultRating()); $team2 = new Team($p2, $gameInfo->getDefaultRating());
for ($i = 0; $i < 10; ++$i) { for ($i = 0; $i < 10; ++$i) {
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$calculator = new TwoTeamTrueSkillCalculator(); $calculator = new TwoTeamTrueSkillCalculator();
@ -93,7 +95,7 @@ class BasicBench
$team2 = new Team($p2, $gameInfo->getDefaultRating()); $team2 = new Team($p2, $gameInfo->getDefaultRating());
for ($i = 0; $i < 10; ++$i) { for ($i = 0; $i < 10; ++$i) {
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$calculator = new FactorGraphTrueSkillCalculator(); $calculator = new FactorGraphTrueSkillCalculator();
@ -126,7 +128,7 @@ class BasicBench
$team3 = new Team($p3, $gameInfo->getDefaultRating()); $team3 = new Team($p3, $gameInfo->getDefaultRating());
for ($i = 0; $i < 10; ++$i) { for ($i = 0; $i < 10; ++$i) {
$teams = Teams::concat($team1, $team2, $team3); $teams = [$team1, $team2, $team3];
$calculator = new FactorGraphTrueSkillCalculator(); $calculator = new FactorGraphTrueSkillCalculator();

View File

@ -7,11 +7,12 @@
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.0", "phpstan/phpstan": "^1.0",
"vimeo/psalm": "^5.21.1", "vimeo/psalm": "dev-master",
"phpmetrics/phpmetrics": "^3.0-dev", "phpmetrics/phpmetrics": "^3.0-dev",
"phpunit/phpunit": "^10.5", "phpunit/phpunit": "^11.2",
"psalm/plugin-phpunit": "^0.18.4", "psalm/plugin-phpunit": "^0.18.4",
"rector/rector": "^1.0" "rector/rector": "^1.0",
"league/csv": "^9.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -25,12 +26,14 @@
}, },
"scripts": { "scripts": {
"test": "phpunit", "test": "phpunit",
"document": "phpDocumentor", "document": "phpDocumentor",
"benchmark": "phpbench run --report=default --output=build-artifact", "benchmark": "phpbench run --report=default --output=build-artifact",
"metrics": "vendor/bin/phpmetrics --config=phpmetrics.json", "metrics": "vendor/bin/phpmetrics --config=phpmetrics.yml",
"lint": [ "lint": [
"phplint", "phplint",
"phpcs" "phpcs",
"phpcbf src/ tests/ benchmark/ examples/",
"phpmd src/,tests/,benchmark/,examples/ text phpmd.ruleset.xml"
], ],
"analyze": [ "analyze": [
"@analyze-phpstan", "@analyze-phpstan",
@ -46,10 +49,10 @@
], ],
"all": [ "all": [
"@test", "@test",
"@document",
"@benchmark",
"@lint", "@lint",
"@analyze", "@analyze",
"@document",
"@metrics",
"@html" "@html"
] ]
} }

1584
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -30,9 +30,9 @@ Links
* `Project README <README.html>`_ * `Project README <README.html>`_
* `API Documentations <docs/>`_ * `API Documentations <docs/>`_
* `CodeCoverage <coverage/>`_ * `CodeCoverage <coverage/>`_
* `Metrics <metrics/>`_
* `Test report <test/index.html>`_ * `Test report <test/index.html>`_
* `Mutation testing <mutation/>`_ * `Mutation testing <mutation/infection.html>`_
* `Code metrics <metrics/index.html>`_
Standard Tools Standard Tools
@ -47,6 +47,7 @@ PHP Tools
--------- ---------
For development Composer and the following packages are used (Recommended as Phars installed via Phive) For development Composer and the following packages are used (Recommended as Phars installed via Phive)
* sudo phive install -g composer phpdocumentor infection phpcs phpcbf phploc phpbench overtrue/phplint --force-accept-unsigned * sudo phive install -g composer phpdocumentor infection phpcs phpcbf phploc phpbench
* sudo phive install -g overtrue/phplint --force-accept-unsigned
* composer install * composer install
* composer all * composer all

View File

@ -20,9 +20,9 @@ $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 = Teams::concat($team1, $team2, $team3); $teams = [$team1, $team2, $team3];
$calculator = new FactorGraphTrueSkillCalculator(); $calculator = new FactorGraphTrueSkillCalculator();
@ -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,9 +19,9 @@ $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 = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$calculator = new TwoPlayerTrueSkillCalculator(); $calculator = new TwoPlayerTrueSkillCalculator();
@ -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,8 +1,8 @@
{ {
"$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/"
] ]
}, },
"logs": { "logs": {

View File

@ -4,13 +4,11 @@
"runner.php_disable_ini": true, "runner.php_disable_ini": true,
"runner.retry_threshold": 10, "runner.retry_threshold": 10,
"runner.iterations": 10, "runner.iterations": 10,
"storage.xml_storage_path": "output/benchmarking/",
"report.outputs": { "report.outputs": {
"build-artifact": { "build-artifact": {
"renderer": "html", "renderer": "html",
"path": "output/benchmark.html", "path": "output/benchmark.html",
"title": "Benchmarking" "title": "Benchmarking"
} }
} }
} }

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

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false" displayDetailsOnTestsThatTriggerWarnings="true"> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" cacheDirectory=".phpunit.cache" backupStaticProperties="false" displayDetailsOnTestsThatTriggerWarnings="true" beStrictAboutCoverageMetadata="true" requireCoverageMetadata="true">
<testsuites> <testsuites>
<testsuite name="PHPSkills Test Suite"> <testsuite name="PHPSkills Test Suite">
<directory>./tests/</directory> <directory>./tests/</directory>

View File

@ -8,26 +8,23 @@ use DNW\Skills\Guard;
use DNW\Skills\HashMap; use DNW\Skills\HashMap;
use Exception; use Exception;
abstract class Factor implements \Stringable abstract class Factor
{ {
/** /**
* @var Message[] $messages * @var Message[] $messages
*/ */
private array $messages = []; private array $messages = [];
private readonly HashMap $messageToVariableBinding; private readonly HashMap $msgToVariableBinding;
private readonly string $name;
/** /**
* @var Variable[] $variables * @var Variable[] $variables
*/ */
private array $variables = []; private array $variables = [];
protected function __construct(string $name) protected function __construct()
{ {
$this->name = 'Factor[' . $name . ']'; $this->msgToVariableBinding = new HashMap();
$this->messageToVariableBinding = new HashMap();
} }
/** /**
@ -73,14 +70,14 @@ abstract class Factor implements \Stringable
{ {
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);
} }
protected function updateMessageVariable(Message $message, Variable $variable): float protected function updateMessageVariable(Message $message, Variable $variable): float
{ {
throw new Exception(); throw new Exception("Must override updateMessageVariable(" . $message::class . ", " . $variable::class . ")");
} }
/** /**
@ -88,7 +85,7 @@ abstract class Factor implements \Stringable
*/ */
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();
} }
@ -104,7 +101,7 @@ abstract class Factor implements \Stringable
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);
} }
@ -115,15 +112,10 @@ abstract class Factor implements \Stringable
protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message
{ {
$this->messageToVariableBinding->setValue($message, $variable); $this->msgToVariableBinding->setValue($message, $variable);
$this->messages[] = $message; $this->messages[] = $message;
$this->variables[] = $variable; $this->variables[] = $variable;
return $message; return $message;
} }
public function __toString(): string
{
return $this->name;
}
} }

View File

@ -17,7 +17,7 @@ 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>>
@ -48,7 +48,7 @@ abstract class FactorGraphLayer
*/ */
public function &getOutputVariablesGroups(): array public function &getOutputVariablesGroups(): array
{ {
return $this->outputVariablesGroups; return $this->outputVarGroups;
} }
/** /**
@ -70,9 +70,9 @@ abstract class FactorGraphLayer
/** /**
* @param Schedule[] $itemsToSequence * @param Schedule[] $itemsToSequence
*/ */
protected function scheduleSequence(array $itemsToSequence, string $name): ScheduleSequence protected function scheduleSequence(array $itemsToSequence): ScheduleSequence
{ {
return new ScheduleSequence($name, $itemsToSequence); return new ScheduleSequence($itemsToSequence);
} }
protected function addLayerFactor(Factor $factor): void protected function addLayerFactor(Factor $factor): void

View File

@ -26,12 +26,12 @@ 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 += $factor->sendMessageIndex($j);
} }
} }

View File

@ -6,9 +6,9 @@ namespace DNW\Skills\FactorGraphs;
class KeyedVariable extends Variable class KeyedVariable extends Variable
{ {
public function __construct(private readonly mixed $key, string $name, mixed $prior) public function __construct(private readonly mixed $key, mixed $prior)
{ {
parent::__construct($name, $prior); parent::__construct($prior);
} }
public function getKey(): mixed public function getKey(): mixed

View File

@ -6,9 +6,9 @@ namespace DNW\Skills\FactorGraphs;
use DNW\Skills\Numerics\GaussianDistribution; use DNW\Skills\Numerics\GaussianDistribution;
class Message implements \Stringable class Message
{ {
public function __construct(private GaussianDistribution $value, private readonly string $name) public function __construct(private GaussianDistribution $value)
{ {
} }
@ -21,9 +21,4 @@ class Message implements \Stringable
{ {
$this->value = $value; $this->value = $value;
} }
public function __toString(): string
{
return $this->name;
}
} }

View File

@ -4,16 +4,7 @@ declare(strict_types=1);
namespace DNW\Skills\FactorGraphs; namespace DNW\Skills\FactorGraphs;
abstract class Schedule implements \Stringable abstract class Schedule
{ {
protected function __construct(private readonly string $name)
{
}
abstract public function visit(int $depth = -1, int $maxDepth = 0): float; abstract public function visit(int $depth = -1, int $maxDepth = 0): float;
public function __toString(): string
{
return $this->name;
}
} }

View File

@ -6,9 +6,8 @@ namespace DNW\Skills\FactorGraphs;
class ScheduleLoop extends Schedule class ScheduleLoop extends Schedule
{ {
public function __construct(string $name, private readonly Schedule $scheduleToLoop, private readonly float $maxDelta) public function __construct(private readonly Schedule $scheduleToLoop, private readonly float $maxDelta)
{ {
parent::__construct($name);
} }
public function visit(int $depth = -1, int $maxDepth = 0): float public function visit(int $depth = -1, int $maxDepth = 0): float

View File

@ -9,9 +9,8 @@ class ScheduleSequence extends Schedule
/** /**
* @param Schedule[] $schedules * @param Schedule[] $schedules
*/ */
public function __construct(string $name, private readonly array $schedules) public function __construct(private readonly array $schedules)
{ {
parent::__construct($name);
} }
public function visit(int $depth = -1, int $maxDepth = 0): float public function visit(int $depth = -1, int $maxDepth = 0): float

View File

@ -6,15 +6,12 @@ namespace DNW\Skills\FactorGraphs;
class ScheduleStep extends Schedule class ScheduleStep extends Schedule
{ {
public function __construct(string $name, private readonly Factor $factor, private readonly int $index) public function __construct(private readonly Factor $factor, private readonly int $index)
{ {
parent::__construct($name);
} }
public function visit(int $depth = -1, int $maxDepth = 0): float public function visit(int $depth = -1, int $maxDepth = 0): float
{ {
$currentFactor = $this->factor; return $this->factor->updateMessageIndex($this->index);
return $currentFactor->updateMessageIndex($this->index);
} }
} }

View File

@ -6,15 +6,12 @@ namespace DNW\Skills\FactorGraphs;
use DNW\Skills\Numerics\GaussianDistribution; use DNW\Skills\Numerics\GaussianDistribution;
class Variable implements \Stringable class Variable
{ {
private readonly string $name;
private mixed $value; private mixed $value;
public function __construct(string $name, private GaussianDistribution $prior) public function __construct(private readonly GaussianDistribution $prior)
{ {
$this->name = 'Variable[' . $name . ']';
$this->resetToPrior(); $this->resetToPrior();
} }
@ -32,9 +29,4 @@ class Variable implements \Stringable
{ {
$this->value = $this->prior; $this->value = $this->prior;
} }
public function __toString(): string
{
return $this->name;
}
} }

View File

@ -6,21 +6,21 @@ namespace DNW\Skills\FactorGraphs;
class VariableFactory class VariableFactory
{ {
public function __construct(private \Closure $variablePriorInitializer) public function __construct(private readonly \Closure $varPriorInitializer)
{ {
} }
public function createBasicVariable(string $name): Variable public function createBasicVariable(): Variable
{ {
$initializer = $this->variablePriorInitializer; $initializer = $this->varPriorInitializer;
return new Variable($name, $initializer()); return new Variable($initializer());
} }
public function createKeyedVariable(mixed $key, string $name): KeyedVariable public function createKeyedVariable(mixed $key): KeyedVariable
{ {
$initializer = $this->variablePriorInitializer; $initializer = $this->varPriorInitializer;
return new KeyedVariable($key, $name, $initializer()); return new KeyedVariable($key, $initializer());
} }
} }

View File

@ -27,7 +27,7 @@ class GameInfo
public function __construct( public function __construct(
private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN, private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN,
private readonly float $initialStandardDeviation = self::DEFAULT_INITIAL_STANDARD_DEVIATION, private readonly float $initialStdDev = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
private readonly float $beta = self::DEFAULT_BETA, private readonly float $beta = self::DEFAULT_BETA,
private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR, private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
private readonly float $drawProbability = self::DEFAULT_DRAW_PROBABILITY private readonly 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

@ -19,16 +19,16 @@ class HashMap
*/ */
private array $hashToKey = []; private array $hashToKey = [];
public function getValue(string|object $key): mixed public function getValue(object $key): mixed
{ {
$hash = self::getHash($key); $hash = spl_object_id($key);
return $this->hashToValue[$hash]; return $this->hashToValue[$hash];
} }
public function setValue(string|object $key, mixed $value): self public function setValue(object $key, mixed $value): self
{ {
$hash = self::getHash($key); $hash = spl_object_id($key);
$this->hashToKey[$hash] = $key; $this->hashToKey[$hash] = $key;
$this->hashToValue[$hash] = $value; $this->hashToValue[$hash] = $value;
@ -55,13 +55,4 @@ class HashMap
{ {
return count($this->hashToKey); return count($this->hashToKey);
} }
private static function getHash(string|object $key): string
{
if (is_object($key)) {
return spl_object_hash($key);
}
return $key;
}
} }

View File

@ -13,15 +13,15 @@ namespace DNW\Skills\Numerics;
class BasicMath 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;
} }
/** /**

View File

@ -10,8 +10,12 @@ 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 implements \Stringable class GaussianDistribution
{ {
private const DEFAULT_STANDARD_DEVIATION = 1.0;
private const DEFAULT_MEAN = 0.0;
/** /**
* Square Root 2π. * Square Root 2π.
* Precalculated constant for performance reasons * Precalculated constant for performance reasons
@ -31,23 +35,26 @@ class GaussianDistribution implements \Stringable
private const M_LOG_SQRT_2_PI = 0.9189385332046727417803297364056176398613974736377834128171515404; private const M_LOG_SQRT_2_PI = 0.9189385332046727417803297364056176398613974736377834128171515404;
// precision and precisionMean are used because they make multiplying and dividing simpler // precision and precisionMean are used because they make multiplying and dividing simpler
// (the the accompanying math paper for more details) // (see the accompanying math paper for more details)
private float $precision; private float $precision = 1.0;
private float $precisionMean; private float $precisionMean = 0.0;
private float $variance; private float $variance = 1.0;
public function __construct(private float $mean = 0.0, private float $standardDeviation = 1.0) public function __construct(private float $mean = self::DEFAULT_MEAN, private float $standardDeviation = self::DEFAULT_STANDARD_DEVIATION)
{ {
$this->variance = BasicMath::square($standardDeviation); if ($mean == self::DEFAULT_MEAN && $standardDeviation == self::DEFAULT_STANDARD_DEVIATION) {
//Use all the defaults
return;
}
$this->variance = BasicMath::square($standardDeviation);
if ($this->variance != 0) { if ($this->variance != 0) {
$this->precision = 1.0 / $this->variance; $this->precision = 1.0 / $this->variance;
$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;
} }
} }
@ -167,7 +174,7 @@ class GaussianDistribution implements \Stringable
BasicMath::square($meanDifference) / (2 * $varianceDifference); BasicMath::square($meanDifference) / (2 * $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)
@ -175,22 +182,22 @@ class GaussianDistribution implements \Stringable
// 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 * BasicMath::square($standardDeviation)));
return $multiplier * $expPart; return $multiplier * $expPart;
} }
public static function cumulativeTo(float $x, float $mean = 0.0, float $standardDeviation = 1.0): 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 * $t - 2;
@ -238,13 +245,12 @@ class GaussianDistribution implements \Stringable
$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
{ {
// From page 265 of numerical recipes // From page 265 of numerical recipes
if ($p >= 2.0) { if ($p >= 2.0) {
return -100; return -100;
} }
@ -265,14 +271,9 @@ class GaussianDistribution implements \Stringable
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 * $var);
}
public function __toString(): string
{
return sprintf('mean=%.4f standardDeviation=%.4f', $this->mean, $this->standardDeviation);
} }
} }

View File

@ -60,13 +60,34 @@ class Matrix
return $this->columnCount; return $this->columnCount;
} }
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|int public function getValue(int $row, int $col): float|int
{ {
$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:
@ -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

@ -8,14 +8,14 @@ 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,33 @@ namespace DNW\Skills;
/** /**
* Represents a player who has a Rating. * Represents a player who has a Rating.
*/ */
class Player implements ISupportPartialPlay, ISupportPartialUpdate, \Stringable class Player implements ISupportPartialPlay, ISupportPartialUpdate
{ {
private const DEFAULT_PARTIAL_PLAY_PERCENTAGE = 1.0; // = 100% play time private const DEFAULT_PARTIAL_PLAY_PERCENTAGE = 1.0; // = 100% play time
private const DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0; private const DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0;
private readonly float $PartialPlayPercentage; private readonly float $PartialPlayPct;
private readonly float $PartialUpdatePercentage; private readonly float $PartialUpdatePct;
/** /**
* Constructs a player. * Constructs a player.
* *
* @param mixed $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 readonly 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;
} }
/** /**
@ -49,7 +49,7 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate, \Stringable
*/ */
public function getPartialPlayPercentage(): float public function getPartialPlayPercentage(): float
{ {
return $this->PartialPlayPercentage; return $this->PartialPlayPct;
} }
/** /**
@ -57,11 +57,6 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate, \Stringable
*/ */
public function getPartialUpdatePercentage(): float public function getPartialUpdatePercentage(): float
{ {
return $this->PartialUpdatePercentage; return $this->PartialUpdatePct;
}
public function __toString(): string
{
return (string)$this->Id;
} }
} }

View File

@ -8,8 +8,4 @@ use DNW\Skills\Numerics\Range;
class PlayersRange extends Range class PlayersRange extends Range
{ {
protected static function create(int $min, int $max): static
{
return new static($min, $max);
}
} }

View File

@ -9,7 +9,7 @@ use DNW\Skills\Numerics\GaussianDistribution;
/** /**
* Container for a player's rating. * Container for a player's rating.
*/ */
class Rating implements \Stringable class Rating
{ {
private const CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3; private const CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3;
@ -73,9 +73,4 @@ class Rating implements \Stringable
return new Rating($partialPosteriorGaussion->getMean(), $partialPosteriorGaussion->getStandardDeviation(), $prior->conservativeStandardDeviationMultiplier); return new Rating($partialPosteriorGaussion->getMean(), $partialPosteriorGaussion->getStandardDeviation(), $prior->conservativeStandardDeviationMultiplier);
} }
public function __toString(): string
{
return sprintf('mean=%.4f, standardDeviation=%.4f', $this->mean, $this->standardDeviation);
}
} }

View File

@ -11,6 +11,12 @@ use Exception;
*/ */
abstract class SkillCalculator abstract class SkillCalculator
{ {
public const NONE = 0x00;
public const PARTIAL_PLAY = 0x01;
public const PARTIAL_UPDATE = 0x02;
protected function __construct( protected function __construct(
private readonly int $supportedOptions, private readonly int $supportedOptions,
private readonly TeamsRange $totalTeamsAllowed, private readonly TeamsRange $totalTeamsAllowed,
@ -46,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;
} }
/** /**
@ -58,7 +64,7 @@ abstract class SkillCalculator
} }
/** /**
* @param array<\DNW\Skills\Team> $teams * @param Team[] $teams
* *
* @throws \Exception * @throws \Exception
*/ */

View File

@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace DNW\Skills;
class SkillCalculatorSupportedOptions
{
public const NONE = 0x00;
public const PARTIAL_PLAY = 0x01;
public const PARTIAL_UPDATE = 0x02;
}

View File

@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace DNW\Skills;
class Teams
{
/**
* @return Team[]
*/
public static function concat(Team ...$args/*variable arguments*/): array
{
$result = [];
foreach ($args as $currentTeam) {
$localCurrentTeam = $currentTeam;
$result[] = $localCurrentTeam;
}
return $result;
}
}

View File

@ -8,8 +8,4 @@ use DNW\Skills\Numerics\Range;
class TeamsRange extends Range class TeamsRange extends Range
{ {
protected static function create(int $min, int $max): static
{
return new static($min, $max);
}
} }

View File

@ -14,7 +14,6 @@ use DNW\Skills\PartialPlay;
use DNW\Skills\PlayersRange; use DNW\Skills\PlayersRange;
use DNW\Skills\RankSorter; use DNW\Skills\RankSorter;
use DNW\Skills\SkillCalculator; use DNW\Skills\SkillCalculator;
use DNW\Skills\SkillCalculatorSupportedOptions;
use DNW\Skills\Team; use DNW\Skills\Team;
use DNW\Skills\TeamsRange; use DNW\Skills\TeamsRange;
use DNW\Skills\RatingContainer; use DNW\Skills\RatingContainer;
@ -27,7 +26,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
{ {
public function __construct() public function __construct()
{ {
parent::__construct(SkillCalculatorSupportedOptions::PARTIAL_PLAY | SkillCalculatorSupportedOptions::PARTIAL_UPDATE, TeamsRange::atLeast(2), PlayersRange::atLeast(1)); parent::__construct(SkillCalculator::PARTIAL_PLAY | SkillCalculator::PARTIAL_UPDATE, TeamsRange::atLeast(2), PlayersRange::atLeast(1));
} }
/** /**
@ -172,7 +171,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

View File

@ -32,7 +32,6 @@ abstract class GaussianFactor extends Factor
$variable, $variable,
new Message( new Message(
$newDistribution, $newDistribution,
sprintf('message from %s to %s', (string)$this, (string)$variable)
) )
); );
} }

View File

@ -16,12 +16,9 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
*/ */
class GaussianGreaterThanFactor extends GaussianFactor class GaussianGreaterThanFactor extends GaussianFactor
{ {
private readonly float $epsilon; public function __construct(private readonly float $epsilon, Variable $variable)
public function __construct(float $epsilon, Variable $variable)
{ {
parent::__construct(\sprintf('%s > %.2f', (string)$variable, $epsilon)); parent::__construct();
$this->epsilon = $epsilon;
$this->createVariableToMessageBinding($variable); $this->createVariableToMessageBinding($variable);
} }

View File

@ -21,7 +21,8 @@ class GaussianLikelihoodFactor extends GaussianFactor
public function __construct(float $betaSquared, Variable $variable1, Variable $variable2) public function __construct(float $betaSquared, Variable $variable1, Variable $variable2)
{ {
parent::__construct(sprintf('Likelihood of %s going to %s', (string)$variable2, (string)$variable1)); //Likelihood of $variable1 going to $variable2
parent::__construct();
$this->precision = 1.0 / $betaSquared; $this->precision = 1.0 / $betaSquared;
$this->createVariableToMessageBinding($variable1); $this->createVariableToMessageBinding($variable1);
$this->createVariableToMessageBinding($variable2); $this->createVariableToMessageBinding($variable2);
@ -59,9 +60,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

View File

@ -19,11 +19,11 @@ class GaussianPriorFactor extends GaussianFactor
public function __construct(float $mean, float $variance, Variable $variable) public function __construct(float $mean, float $variance, Variable $variable)
{ {
parent::__construct(sprintf('Prior value going to %s', (string)$variable)); //Prior value going to $variable
parent::__construct();
$this->newMessage = new GaussianDistribution($mean, sqrt($variance)); $this->newMessage = new GaussianDistribution($mean, sqrt($variance));
$newMessage = new Message( $newMessage = new Message(
GaussianDistribution::fromPrecisionMean(0, 0), GaussianDistribution::fromPrecisionMean(0, 0)
sprintf('message from %s to %s', (string)$this, (string)$variable)
); );
$this->createVariableToMessageBindingWithMessage($variable, $newMessage); $this->createVariableToMessageBindingWithMessage($variable, $newMessage);

View File

@ -19,9 +19,9 @@ use DNW\Skills\Numerics\GaussianDistribution;
class GaussianWeightedSumFactor extends GaussianFactor 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]
@ -42,7 +42,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
*/ */
public function __construct(Variable $sumVariable, array $variablesToSum, array $variableWeights) public function __construct(Variable $sumVariable, array $variablesToSum, array $variableWeights)
{ {
parent::__construct(self::createName((string)$sumVariable, $variablesToSum, $variableWeights)); parent::__construct();
// The first weights are a straightforward copy // The first weights are a straightforward copy
// v_0 = a_1*v_1 + a_2*v_2 + ... + a_n * v_n // v_0 = a_1*v_1 + a_2*v_2 + ... + a_n * v_n
@ -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);
@ -113,7 +113,7 @@ 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;
$this->variableIndexOrdersForWeights[] = $variableIndices; $this->varIndexOrdersForWeights[] = $variableIndices;
$this->weights[$weightsIndex] = $currentWeights; $this->weights[$weightsIndex] = $currentWeights;
$this->weightsSquared[$weightsIndex] = $currentWeightsSquared; $this->weightsSquared[$weightsIndex] = $currentWeightsSquared;
@ -160,9 +160,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 +170,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;
@ -214,7 +207,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
@ -235,42 +228,4 @@ class GaussianWeightedSumFactor extends GaussianFactor
$updatedVariables $updatedVariables
); );
} }
/**
* @param Variable[] $variablesToSum
* @param float[] $weights
*/
private static function createName(string $sumVariable, array $variablesToSum, array $weights): string
{
// TODO: Perf? Use PHP equivalent of StringBuilder? implode on arrays?
$result = $sumVariable;
$result .= ' = ';
$totalVars = count($variablesToSum);
for ($i = 0; $i < $totalVars; ++$i) {
$isFirst = ($i == 0);
if ($isFirst && ($weights[$i] < 0)) {
$result .= '-';
}
$absValue = sprintf('%.2f', \abs($weights[$i])); // 0.00?
$result .= $absValue;
$result .= '*[';
$result .= (string)$variablesToSum[$i];
$result .= ']';
$isLast = ($i === $totalVars - 1);
if (! $isLast) {
if ($weights[$i + 1] >= 0) {
$result .= ' + ';
} else {
$result .= ' - ';
}
}
}
return $result;
}
} }

View File

@ -16,12 +16,10 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
*/ */
class GaussianWithinFactor extends GaussianFactor class GaussianWithinFactor extends GaussianFactor
{ {
private readonly float $epsilon; public function __construct(private readonly float $epsilon, Variable $variable)
public function __construct(float $epsilon, Variable $variable)
{ {
parent::__construct(sprintf('%s <= %.2f', (string)$variable, $epsilon)); //$epsilon <= $variable
$this->epsilon = $epsilon; parent::__construct();
$this->createVariableToMessageBinding($variable); $this->createVariableToMessageBinding($variable);
} }

View File

@ -15,8 +15,8 @@ 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);
@ -25,20 +25,20 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
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()
); );
} }
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 public function createPriorSchedule(): ?ScheduleSequence
@ -56,24 +56,24 @@ 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];
//inner schedule
return new ScheduleSequence( return new ScheduleSequence(
'inner schedule',
[ [
$loop, $loop,
//teamPerformanceToPerformanceDifferenceFactors[0] @ 1
new ScheduleStep( new ScheduleStep(
'teamPerformanceToPerformanceDifferenceFactors[0] @ 1',
$firstDifferencesFactor, $firstDifferencesFactor,
1 1
), ),
//teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = %d - 1] @ 2
new ScheduleStep( new ScheduleStep(
sprintf('teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = %d - 1] @ 2', $totalTeamDifferences),
$lastDifferencesFactor, $lastDifferencesFactor,
2 2
), ),
@ -83,95 +83,96 @@ 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];
$itemsToSequence = [ $itemsToSequence = [
//send team perf to perf differences
new ScheduleStep( new ScheduleStep(
'send team perf to perf differences',
$firstPerfToTeamDiff, $firstPerfToTeamDiff,
0 0
), ),
//send to greater than or within factor
new ScheduleStep( new ScheduleStep(
'send to greater than or within factor',
$firstTeamDiffComparison, $firstTeamDiffComparison,
0 0
), ),
]; ];
//loop of just two teams inner sequence
return $this->scheduleSequence( return $this->scheduleSequence(
$itemsToSequence, $itemsToSequence
'loop of just two teams inner sequence'
); );
} }
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];
//current forward schedule piece $i
$currentForwardSchedulePiece = $currentForwardSchedulePiece =
$this->scheduleSequence( $this->scheduleSequence(
[ [
//team perf to perf diff
new ScheduleStep( new ScheduleStep(
sprintf('team perf to perf diff %d', $i),
$currentTeamPerfToTeamPerfDiff, $currentTeamPerfToTeamPerfDiff,
0 0
), ),
//greater than or within result factor
new ScheduleStep( new ScheduleStep(
sprintf('greater than or within result factor %d', $i),
$currentTeamDiffComparison, $currentTeamDiffComparison,
0 0
), ),
//'team perf to perf diff factors
new ScheduleStep( new ScheduleStep(
sprintf('team perf to perf diff factors [%d], 2', $i),
$currentTeamPerfToTeamPerfDiff, $currentTeamPerfToTeamPerfDiff,
2 2
), ),
], ]
sprintf('current forward schedule piece %d', $i)
); );
$forwardScheduleList[] = $currentForwardSchedulePiece; $forwardScheduleList[] = $currentForwardSchedulePiece;
} }
$forwardSchedule = new ScheduleSequence('forward schedule', $forwardScheduleList); //forward schedule
$forwardSchedule = new ScheduleSequence($forwardScheduleList);
$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];
$performancesToDifferencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i]; $performancesToDifferencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i];
//current backward schedule piece
$currentBackwardSchedulePiece = new ScheduleSequence( $currentBackwardSchedulePiece = new ScheduleSequence(
'current backward schedule piece',
[ [
//teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 0
new ScheduleStep( new ScheduleStep(
sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 0', $i),
$differencesFactor, $differencesFactor,
0 0
), ),
//greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - %d] @ 0
new ScheduleStep( new ScheduleStep(
sprintf('greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - %d] @ 0', $i),
$comparisonFactor, $comparisonFactor,
0 0
), ),
//teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 1
new ScheduleStep( new ScheduleStep(
sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 1', $i),
$performancesToDifferencesFactor, $performancesToDifferencesFactor,
1 1
), ),
@ -180,18 +181,19 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
$backwardScheduleList[] = $currentBackwardSchedulePiece; $backwardScheduleList[] = $currentBackwardSchedulePiece;
} }
$backwardSchedule = new ScheduleSequence('backward schedule', $backwardScheduleList); //backward schedule
$backwardSchedule = new ScheduleSequence($backwardScheduleList);
$forwardBackwardScheduleToLoop = $forwardBackwardScheduleToLoop =
//forward Backward Schedule To Loop
new ScheduleSequence( new ScheduleSequence(
'forward Backward Schedule To Loop',
[$forwardSchedule, $backwardSchedule] [$forwardSchedule, $backwardSchedule]
); );
$initialMaxDelta = 0.0001; $initialMaxDelta = 0.0001;
//loop with max delta
return new ScheduleLoop( return new ScheduleLoop(
sprintf('loop with max delta of %f', $initialMaxDelta),
$forwardBackwardScheduleToLoop, $forwardBackwardScheduleToLoop,
$initialMaxDelta $initialMaxDelta
); );

View File

@ -7,8 +7,6 @@ namespace DNW\Skills\TrueSkill\Layers;
use DNW\Skills\FactorGraphs\ScheduleStep; use DNW\Skills\FactorGraphs\ScheduleStep;
use DNW\Skills\FactorGraphs\ScheduleSequence; use DNW\Skills\FactorGraphs\ScheduleSequence;
use DNW\Skills\PartialPlay; use DNW\Skills\PartialPlay;
use DNW\Skills\Player;
use DNW\Skills\Team;
use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor; 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;
@ -23,14 +21,14 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
*/ */
foreach ($inputVariablesGroups as $currentTeam) { foreach ($inputVariablesGroups as $currentTeam) {
$localCurrentTeam = $currentTeam; $localCurrentTeam = $currentTeam;
$teamPerformance = $this->createOutputVariable($localCurrentTeam); $teamPerformance = $this->createOutputVariable();
$newSumFactor = $this->createPlayerToTeamSumFactor($localCurrentTeam, $teamPerformance); $newSumFactor = $this->createPlayerToTeamSumFactor($localCurrentTeam, $teamPerformance);
$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];
} }
} }
@ -38,12 +36,13 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
{ {
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();
//all player perf to team perf schedule
return $this->scheduleSequence( return $this->scheduleSequence(
array_map( array_map(
static fn($weightedSumFactor): ScheduleStep => new ScheduleStep('Perf to Team Perf Step', $weightedSumFactor, 0), //Perf to Team Perf Step
static fn($weightedSumFactor): ScheduleStep => new ScheduleStep($weightedSumFactor, 0),
$localFactors $localFactors
), )
'all player perf to team perf schedule'
); );
} }
@ -75,26 +74,23 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
$localCurrentFactor = $currentFactor; $localCurrentFactor = $currentFactor;
$numberOfMessages = $localCurrentFactor->getNumberOfMessages(); $numberOfMessages = $localCurrentFactor->getNumberOfMessages();
for ($currentIteration = 1; $currentIteration < $numberOfMessages; ++$currentIteration) { for ($currentIteration = 1; $currentIteration < $numberOfMessages; ++$currentIteration) {
//team sum perf
$allFactors[] = new ScheduleStep( $allFactors[] = new ScheduleStep(
'team sum perf @' . $currentIteration,
$localCurrentFactor, $localCurrentFactor,
$currentIteration $currentIteration
); );
} }
} }
return $this->scheduleSequence($allFactors, "all of the team's sum iterations"); //all of the team's sum iterations
return $this->scheduleSequence($allFactors);
} }
/** /**
* @param KeyedVariable[] $team * Team's performance
*/ */
private function createOutputVariable(array $team): Variable private function createOutputVariable(): Variable
{ {
$memberNames = array_map(static fn($currentPlayer): string => (string)($currentPlayer->getKey()), $team); return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable();
$teamMemberNames = \implode(', ', $memberNames);
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable('Team[' . $teamMemberNames . "]'s performance");
} }
} }

View File

@ -34,12 +34,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;
} }
@ -53,12 +53,13 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
{ {
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();
//All priors
return $this->scheduleSequence( return $this->scheduleSequence(
array_map( array_map(
static fn($prior): ScheduleStep => new ScheduleStep('Prior to Skill Step', $prior, 0), //Prior to Skill Step
static fn($prior): ScheduleStep => new ScheduleStep($prior, 0),
$localFactors $localFactors
), )
'All priors'
); );
} }
@ -77,6 +78,6 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
$parentFactorGraph = $this->getParentFactorGraph(); $parentFactorGraph = $this->getParentFactorGraph();
$variableFactory = $parentFactorGraph->getVariableFactory(); $variableFactory = $parentFactorGraph->getVariableFactory();
return $variableFactory->createKeyedVariable($key, $key . "'s skill"); return $variableFactory->createKeyedVariable($key);
} }
} }

View File

@ -15,25 +15,25 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
{ {
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;
} }
} }
@ -48,19 +48,20 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
private function createOutputVariable(mixed $key): KeyedVariable private function createOutputVariable(mixed $key): KeyedVariable
{ {
return $this->getParentFactorGraph()->getVariableFactory()->createKeyedVariable($key, $key . "'s performance"); return $this->getParentFactorGraph()->getVariableFactory()->createKeyedVariable($key);
} }
public function createPriorSchedule(): ?ScheduleSequence public function createPriorSchedule(): ?ScheduleSequence
{ {
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();
//All skill to performance sending
return $this->scheduleSequence( return $this->scheduleSequence(
array_map( array_map(
static fn($likelihood): ScheduleStep => new ScheduleStep('Skill to Perf step', $likelihood, 0), //Skill to Perf step
static fn($likelihood): ScheduleStep => new ScheduleStep($likelihood, 0),
$localFactors $localFactors
), )
'All skill to performance sending'
); );
} }
@ -68,12 +69,12 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
{ {
$localFactors = $this->getLocalFactors(); $localFactors = $this->getLocalFactors();
//All skill to performance sending
return $this->scheduleSequence( return $this->scheduleSequence(
array_map( array_map(
static fn($likelihood): ScheduleStep => new ScheduleStep('name', $likelihood, 1), static fn($likelihood): ScheduleStep => new ScheduleStep($likelihood, 1),
$localFactors $localFactors
), )
'All skill to performance sending'
); );
} }
} }

View File

@ -40,8 +40,11 @@ class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorG
return new GaussianWeightedSumFactor($output, $teams, $weights); return new GaussianWeightedSumFactor($output, $teams, $weights);
} }
/**
* Team performance difference
*/
private function createOutputVariable(): Variable private function createOutputVariable(): Variable
{ {
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable('Team performance difference'); return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable();
} }
} }

View File

@ -28,7 +28,7 @@ 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;
@ -118,13 +118,14 @@ 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;
} }
} }
return new ScheduleSequence('Full schedule', $fullSchedule); //Full schedule
return new ScheduleSequence($fullSchedule);
} }
public function getUpdatedRatings(): RatingContainer public function getUpdatedRatings(): RatingContainer

View File

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

@ -13,9 +13,7 @@ use DNW\Skills\RankSorter;
use DNW\Skills\Rating; use DNW\Skills\Rating;
use DNW\Skills\RatingContainer; use DNW\Skills\RatingContainer;
use DNW\Skills\SkillCalculator; use DNW\Skills\SkillCalculator;
use DNW\Skills\SkillCalculatorSupportedOptions;
use DNW\Skills\TeamsRange; use DNW\Skills\TeamsRange;
use DNW\Skills\Team;
/** /**
* Calculates the new ratings for only two players. * Calculates the new ratings for only two players.
@ -27,7 +25,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
{ {
public function __construct() public function __construct()
{ {
parent::__construct(SkillCalculatorSupportedOptions::NONE, TeamsRange::exactly(2), PlayersRange::exactly(1)); parent::__construct(SkillCalculator::NONE, TeamsRange::exactly(2), PlayersRange::exactly(1));
} }
/** /**

View File

@ -13,7 +13,6 @@ use DNW\Skills\RankSorter;
use DNW\Skills\Rating; use DNW\Skills\Rating;
use DNW\Skills\RatingContainer; use DNW\Skills\RatingContainer;
use DNW\Skills\SkillCalculator; use DNW\Skills\SkillCalculator;
use DNW\Skills\SkillCalculatorSupportedOptions;
use DNW\Skills\Team; use DNW\Skills\Team;
use DNW\Skills\TeamsRange; use DNW\Skills\TeamsRange;
@ -26,7 +25,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
{ {
public function __construct() public function __construct()
{ {
parent::__construct(SkillCalculatorSupportedOptions::NONE, TeamsRange::exactly(2), PlayersRange::atLeast(1)); parent::__construct(SkillCalculator::NONE, TeamsRange::exactly(2), PlayersRange::atLeast(1));
} }
/** /**
@ -126,9 +125,9 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
} }
$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);
@ -140,7 +139,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1 - $w * $stdDevMultiplier) (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1 - $w * $stdDevMultiplier)
); );
$newPlayerRatings->setRating($localSelfTeamCurrentPlayer, new Rating($newMean, $newStdDev)); $newPlayerRatings->setRating($localSelfTeamCurPlayer, new Rating($newMean, $newStdDev));
} }
} }

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace DNW\Skills\Tests\FactorGraphs;
use DNW\Skills\FactorGraphs\ScheduleStep;
use DNW\Skills\FactorGraphs\Factor;
use PHPUnit\Framework\TestCase;
class ScheduleStepTest extends TestCase
{
public function testtoStringInterface(): void
{
$stub = $this->createStub(Factor::class);
$ss = new ScheduleStep('dummy', $stub, 0);
$this->assertEquals('dummy', (string)$ss);
}
}

View File

@ -7,19 +7,22 @@ namespace DNW\Skills\Tests\FactorGraphs;
use DNW\Skills\FactorGraphs\Variable; use DNW\Skills\FactorGraphs\Variable;
use DNW\Skills\Numerics\GaussianDistribution; use DNW\Skills\Numerics\GaussianDistribution;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(Variable::class)]
#[UsesClass(GaussianDistribution::class)]
class VariableTest extends TestCase class VariableTest extends TestCase
{ {
public function testGetterSetter(): void public function testGetterSetter(): void
{ {
$gd_prior = new GaussianDistribution(); $gd_prior = new GaussianDistribution();
$var = new Variable('dummy', $gd_prior); $var = new Variable($gd_prior);
$this->assertEquals($gd_prior, $var->getValue()); $this->assertEquals($gd_prior, $var->getValue());
$gd_new = new GaussianDistribution(); $gd_new = new GaussianDistribution();
$this->assertEquals($gd_new, $var->getValue()); $this->assertEquals($gd_new, $var->getValue());
$var->resetToPrior(); $var->resetToPrior();
$this->assertEquals($gd_prior, $var->getValue()); $this->assertEquals($gd_prior, $var->getValue());
$this->assertEquals('Variable[dummy]', (string)$var);
} }
} }

View File

@ -5,8 +5,13 @@ declare(strict_types=1);
namespace DNW\Skills\Tests; namespace DNW\Skills\Tests;
use DNW\Skills\GameInfo; use DNW\Skills\GameInfo;
use DNW\Skills\Rating;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(GameInfo::class)]
#[UsesClass(Rating::class)]
class GameInfoTest extends TestCase class GameInfoTest extends TestCase
{ {
public function testMembers(): void public function testMembers(): void
@ -17,6 +22,6 @@ class GameInfoTest extends TestCase
$this->assertEquals(3, $gi->getBeta()); $this->assertEquals(3, $gi->getBeta());
$this->assertEquals(4, $gi->getDynamicsFactor()); $this->assertEquals(4, $gi->getDynamicsFactor());
$this->assertEquals(5, $gi->getDrawProbability()); $this->assertEquals(5, $gi->getDrawProbability());
$this->assertInstanceOf(\DNW\Skills\Rating::class, $gi->getDefaultRating()); $this->assertInstanceOf(Rating::class, $gi->getDefaultRating());
} }
} }

View File

@ -7,27 +7,55 @@ namespace DNW\Skills\Tests;
use DNW\Skills\Guard; use DNW\Skills\Guard;
use Exception; use Exception;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(Guard::class)]
class GuardTest extends TestCase class GuardTest extends TestCase
{ {
public function testargumentIsValidIndex(): void public function testargumentIsValidIndexArgumentAbove(): void
{ {
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage('dummy is an invalid index'); $this->expectExceptionMessage('dummy is an invalid index');
Guard::argumentIsValidIndex(10, 10, "dummy"); Guard::argumentIsValidIndex(10, 10, "dummy");
} }
public function testargumentIsValidIndex2(): void public function testargumentIsValidIndexArgumentBelow(): void
{ {
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage('dummy is an invalid index'); $this->expectExceptionMessage('dummy is an invalid index');
Guard::argumentIsValidIndex(-1, 10, "dummy"); Guard::argumentIsValidIndex(-1, 10, "dummy");
} }
public function testargumentInRangeInclusive(): void public function testargumentIsValidIndexArgumentValid(): void
{
Guard::argumentIsValidIndex(0, 10, "dummy");
Guard::argumentIsValidIndex(1, 10, "dummy");
Guard::argumentIsValidIndex(9, 10, "dummy");
$this->expectNotToPerformAssertions();
}
public function testargumentInRangeInclusiveAbove(): void
{ {
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage('dummy is not in the valid range [0, 100]'); $this->expectExceptionMessage('dummy is not in the valid range [0, 100]');
Guard::argumentInRangeInclusive(101, 0, 100, "dummy"); Guard::argumentInRangeInclusive(101, 0, 100, "dummy");
} }
public function testargumentInRangeInclusiveBelow(): void
{
$this->expectException(Exception::class);
$this->expectExceptionMessage('dummy is not in the valid range [0, 100]');
Guard::argumentInRangeInclusive(-1, 0, 100, "dummy");
}
public function testargumentInRangeInclusiveValid(): void
{
Guard::argumentInRangeInclusive(0, 0, 100, "dummy");
Guard::argumentInRangeInclusive(1, 0, 100, "dummy");
Guard::argumentInRangeInclusive(50, 0, 100, "dummy");
Guard::argumentInRangeInclusive(99, 0, 100, "dummy");
Guard::argumentInRangeInclusive(100, 0, 100, "dummy");
$this->expectNotToPerformAssertions();
}
} }

37
tests/HashMapTest.php Normal file
View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace DNW\Skills\Tests;
use DNW\Skills\HashMap;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use stdClass;
#[CoversClass(HashMap::class)]
class HashMapTest extends TestCase
{
public function testHashmap(): void
{
$h = new HashMap();
$this->assertEquals([], $h->getAllKeys());
$this->assertEquals([], $h->getAllValues());
$o1 = new stdClass();
$o2 = new stdClass();
$h->setValue($o1, 1);
$h->setvalue($o2, 2);
$this->assertEquals([1, 2], $h->getAllValues());
$this->assertEquals([$o1, $o2], $h->getAllKeys());
$this->assertEquals(1, $h->getvalue($o1));
$this->assertEquals(2, $h->getvalue($o2));
$this->assertEquals(2, $h->count());
}
}

View File

@ -6,7 +6,9 @@ namespace DNW\Skills\Tests\Numerics;
use DNW\Skills\Numerics\BasicMath; use DNW\Skills\Numerics\BasicMath;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(BasicMath::class)]
class BasicMathTest extends TestCase class BasicMathTest extends TestCase
{ {
public function testSquare(): void public function testSquare(): void

View File

@ -7,7 +7,11 @@ namespace DNW\Skills\Tests\Numerics;
use DNW\Skills\Numerics\BasicMath; use DNW\Skills\Numerics\BasicMath;
use DNW\Skills\Numerics\GaussianDistribution; use DNW\Skills\Numerics\GaussianDistribution;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(GaussianDistribution::class)]
#[UsesClass(BasicMath::class)]
class GaussianDistributionTest extends TestCase class GaussianDistributionTest extends TestCase
{ {
private const ERROR_TOLERANCE = 0.000001; private const ERROR_TOLERANCE = 0.000001;
@ -88,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
@ -97,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
@ -111,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

@ -8,6 +8,7 @@ use DNW\Skills\Numerics\IdentityMatrix;
use DNW\Skills\Numerics\Matrix; use DNW\Skills\Numerics\Matrix;
use DNW\Skills\Numerics\SquareMatrix; use DNW\Skills\Numerics\SquareMatrix;
use DNW\Skills\Numerics\DiagonalMatrix; use DNW\Skills\Numerics\DiagonalMatrix;
use DNW\Skills\Numerics\Vector;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\Attributes\UsesClass;
@ -17,9 +18,87 @@ use Exception;
#[CoversClass(SquareMatrix::class)] #[CoversClass(SquareMatrix::class)]
#[CoversClass(IdentityMatrix::class)] #[CoversClass(IdentityMatrix::class)]
#[CoversClass(DiagonalMatrix::class)] #[CoversClass(DiagonalMatrix::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 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);
@ -264,6 +343,15 @@ class MatrixTest extends TestCase
$m2 = new Matrix(1, 1, [[1,1]]); $m2 = new Matrix(1, 1, [[1,1]]);
Matrix::multiply($m1, $m2); Matrix::multiply($m1, $m2);
} }
public function testVector(): void
{
$vector = new Vector([1,2,3,4]);
$m1 = new Matrix(4, 1, [[1],[2],[3],[4]]);
$this->assertTrue($vector->equals($m1));
}
} }
// phpcs:enable // phpcs:enable

View File

@ -6,8 +6,10 @@ namespace DNW\Skills\Tests\Numerics;
use DNW\Skills\Numerics\Range; use DNW\Skills\Numerics\Range;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Exception; use Exception;
#[CoversClass(Range::class)]
class RangeTest extends TestCase class RangeTest extends TestCase
{ {
public function testConstructInvalidParam(): void public function testConstructInvalidParam(): void

27
tests/PartialPlayTest.php Normal file
View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace DNW\Skills\Tests;
use DNW\Skills\PartialPlay;
use DNW\Skills\Player;
use DNW\Skills\Guard;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(PartialPlay::class)]
#[UsesClass(Player::class)]
#[UsesClass(Guard::class)]
class PartialPlayTest extends TestCase
{
public function testgetPartialPlayPercentage(): void
{
$p = new Player(1, 0.5);
$this->assertEquals($p->getPartialPlayPercentage(), PartialPlay::getPartialPlayPercentage($p));
$p = new Player(1, 0.000000);
$this->assertNotEquals(0.0, PartialPlay::getPartialPlayPercentage($p));
}
}

View File

@ -5,14 +5,18 @@ declare(strict_types=1);
namespace DNW\Skills\Tests; namespace DNW\Skills\Tests;
use DNW\Skills\Player; use DNW\Skills\Player;
use DNW\Skills\Guard;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(Player::class)]
#[UsesClass(Guard::class)]
class PlayerTest extends TestCase class PlayerTest extends TestCase
{ {
public function testPlayerObjectGetterSetter(): void public function testPlayerObjectGetterSetter(): void
{ {
$p = new Player('dummy', 0.1, 0.2); $p = new Player('dummy', 0.1, 0.2);
$this->assertEquals('dummy', (string)$p);
$this->assertEquals('dummy', $p->getId()); $this->assertEquals('dummy', $p->getId());
$this->assertEquals(0.1, $p->getPartialPlayPercentage()); $this->assertEquals(0.1, $p->getPartialPlayPercentage());
$this->assertEquals(0.2, $p->getPartialUpdatePercentage()); $this->assertEquals(0.2, $p->getPartialUpdatePercentage());

View File

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

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace DNW\Skills\Tests;
use DNW\Skills\RatingContainer;
use DNW\Skills\HashMap;
use DNW\Skills\Player;
use DNW\Skills\Rating;
use DNW\Skills\Guard;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(RatingContainer::class)]
#[UsesClass(Hashmap::class)]
#[UsesClass(Player::class)]
#[UsesClass(Rating::class)]
#[UsesClass(Guard::class)]
class RatingContainerTest extends TestCase
{
public function testRatingContainer(): void
{
$rc = new RatingContainer();
$this->assertEquals([], $rc->getAllPlayers());
$this->assertEquals([], $rc->getAllRatings());
$this->assertEquals(0, $rc->count());
$p1 = new Player(1);
$p2 = new Player(2);
$r1 = new Rating(100, 10);
$r2 = new Rating(200, 20);
$rc->setRating($p1, $r1);
$rc->setRating($p2, $r2);
$this->assertEquals($r1, $rc->getRating($p1));
$this->assertEquals($r2, $rc->getRating($p2));
$this->assertEquals([$p1, $p2], $rc->getAllPlayers());
$this->assertEquals([$r1, $r2], $rc->getAllRatings());
$this->assertEquals(2, $rc->count());
}
}

View File

@ -5,8 +5,15 @@ declare(strict_types=1);
namespace DNW\Skills\Tests; namespace DNW\Skills\Tests;
use DNW\Skills\Rating; use DNW\Skills\Rating;
use DNW\Skills\Numerics\BasicMath;
use DNW\Skills\Numerics\GaussianDistribution;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(Rating::class)]
#[UsesClass(BasicMath::class)]
#[UsesClass(GaussianDistribution::class)]
class RatingTest extends TestCase class RatingTest extends TestCase
{ {
public function testGetRatingParameters(): void public function testGetRatingParameters(): void
@ -15,7 +22,6 @@ class RatingTest extends TestCase
$this->assertEquals(100, $rating->getMean()); $this->assertEquals(100, $rating->getMean());
$this->assertEquals(10, $rating->getStandardDeviation()); $this->assertEquals(10, $rating->getStandardDeviation());
$this->assertEquals(50, $rating->getConservativeRating()); $this->assertEquals(50, $rating->getConservativeRating());
$this->assertEquals("mean=100.0000, standardDeviation=10.0000", (string)$rating);
} }
public function testPartialUpdate(): void public function testPartialUpdate(): void
@ -26,10 +32,8 @@ class RatingTest extends TestCase
$rating_partial = $rating->getPartialUpdate($ratingOld, $ratingNew, 0.5); $rating_partial = $rating->getPartialUpdate($ratingOld, $ratingNew, 0.5);
$this->assertEquals(150, $rating_partial->getMean()); $this->assertEquals(150, $rating_partial->getMean());
$this->assertEquals(10, $rating_partial->getStandardDeviation()); $this->assertEquals(10, $rating_partial->getStandardDeviation());
$this->assertEquals(100, $rating_partial->getConservativeRating()); $this->assertEquals(100, $rating_partial->getConservativeRating());
$this->assertEquals("mean=150.0000, standardDeviation=10.0000", (string)$rating_partial);
} }
} }

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')]
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));
}
}

69
tests/TeamTest.php Normal file
View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace DNW\Skills\Tests;
use DNW\Skills\Team;
use DNW\Skills\RatingContainer;
use DNW\Skills\HashMap;
use DNW\Skills\Player;
use DNW\Skills\Rating;
use DNW\Skills\Guard;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(Team::class)]
#[CoversClass(RatingContainer::class)]
#[UsesClass(Hashmap::class)]
#[UsesClass(Player::class)]
#[UsesClass(Rating::class)]
#[UsesClass(Guard::class)]
class TeamTest extends TestCase
{
public function testTeam(): void
{
$p1 = new Player(1);
$p2 = new Player(2);
$r1 = new Rating(100, 10);
$r2 = new Rating(200, 20);
$rc = new Team($p1, $r1);
$this->assertEquals($r1, $rc->getRating($p1));
$this->assertEquals([$p1], $rc->getAllPlayers());
$this->assertEquals([$r1], $rc->getAllRatings());
$this->assertEquals(1, $rc->count());
$rc->addPlayer($p2, $r2);
$this->assertEquals($r2, $rc->getRating($p2));
$this->assertEquals([$p1, $p2], $rc->getAllPlayers());
$this->assertEquals([$r1, $r2], $rc->getAllRatings());
$this->assertEquals(2, $rc->count());
}
public function testTeamConstructor(): void
{
$p = new Player(0);
$r = new Rating(100, 10);
$rc = new Team(NULL, NULL);
$this->assertEquals(0, $rc->count());
$rc = new Team($p, NULL);
$this->assertEquals(0, $rc->count());
$rc = new Team(NULL, $r);
$this->assertEquals(0, $rc->count());
$rc = new Team($p, $r);
$this->assertEquals($r, $rc->getRating($p));
$this->assertEquals([$p], $rc->getAllPlayers());
$this->assertEquals([$r], $rc->getAllRatings());
$this->assertEquals(1, $rc->count());
}
}

View File

@ -5,8 +5,15 @@ declare(strict_types=1);
namespace DNW\Skills\Tests\TrueSkill; namespace DNW\Skills\Tests\TrueSkill;
use DNW\Skills\TrueSkill\DrawMargin; use DNW\Skills\TrueSkill\DrawMargin;
use DNW\Skills\Numerics\BasicMath;
use DNW\Skills\Numerics\GaussianDistribution;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(DrawMargin::class)]
#[UsesClass(BasicMath::class)]
#[UsesClass(GaussianDistribution::class)]
class DrawMarginTest extends TestCase class DrawMarginTest extends TestCase
{ {
private const ERROR_TOLERANCE = 0.000001; private const ERROR_TOLERANCE = 0.000001;
@ -23,6 +30,6 @@ class DrawMarginTest extends TestCase
private function assertDrawMargin(float $drawProbability, float $beta, float $expected): void private function assertDrawMargin(float $drawProbability, float $beta, float $expected): void
{ {
$actual = DrawMargin::getDrawMarginFromDrawProbability($drawProbability, $beta); $actual = DrawMargin::getDrawMarginFromDrawProbability($drawProbability, $beta);
$this->assertEqualsWithDelta($expected, $actual, DrawMarginTest::ERROR_TOLERANCE); $this->assertEqualsWithDelta($expected, $actual, self::ERROR_TOLERANCE);
} }
} }

View File

@ -4,11 +4,11 @@ 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\SkillCalculatorSupportedOptions;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\CoversNothing;
@ -78,6 +78,6 @@ class FactorGraphTrueSkillCalculatorTest extends TestCase
public function testMethodisSupported(): void public function testMethodisSupported(): void
{ {
$calculator = new FactorGraphTrueSkillCalculator(); $calculator = new FactorGraphTrueSkillCalculator();
$this->assertEquals(TRUE, $calculator->isSupported(SkillCalculatorSupportedOptions::PARTIAL_PLAY)); $this->assertEquals(TRUE, $calculator->isSupported(SkillCalculator::PARTIAL_PLAY));
} }
} }

View File

@ -9,7 +9,6 @@ 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 class TrueSkillCalculatorTests
@ -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.
@ -86,7 +84,7 @@ class TrueSkillCalculatorTests
$team1 = new Team($player1, $gameInfo->getDefaultRating()); $team1 = new Team($player1, $gameInfo->getDefaultRating());
$team2 = new Team($player2, $gameInfo->getDefaultRating()); $team2 = new Team($player2, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
@ -109,7 +107,7 @@ class TrueSkillCalculatorTests
$team1 = new Team($player1, $gameInfo->getDefaultRating()); $team1 = new Team($player1, $gameInfo->getDefaultRating());
$team2 = new Team($player2, $gameInfo->getDefaultRating()); $team2 = new Team($player2, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
@ -132,7 +130,7 @@ class TrueSkillCalculatorTests
$team1 = new Team($player1, new Rating(1301.0007, 42.9232)); $team1 = new Team($player1, new Rating(1301.0007, 42.9232));
$team2 = new Team($player2, new Rating(1188.7560, 42.5570)); $team2 = new Team($player2, new Rating(1188.7560, 42.5570));
$newRatings = $calculator->calculateNewRatings($gameInfo, Teams::concat($team1, $team2), [1, 2]); $newRatings = $calculator->calculateNewRatings($gameInfo, [$team1, $team2], [1, 2]);
$player1NewRating = $newRatings->getRating($player1); $player1NewRating = $newRatings->getRating($player1);
self::assertRating($testClass, 1304.7820836053318, 42.843513887848658, $player1NewRating); self::assertRating($testClass, 1304.7820836053318, 42.843513887848658, $player1NewRating);
@ -153,7 +151,7 @@ class TrueSkillCalculatorTests
$team2 = new Team($player2, new Rating(50, 12.5)); $team2 = new Team($player2, new Rating(50, 12.5));
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
@ -184,7 +182,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player2, $gameInfo->getDefaultRating()); $team2->addPlayer($player2, $gameInfo->getDefaultRating());
$team2->addPlayer($player3, $gameInfo->getDefaultRating()); $team2->addPlayer($player3, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
// Winners // Winners
@ -219,7 +217,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player3, $gameInfo->getDefaultRating()); $team2->addPlayer($player3, $gameInfo->getDefaultRating());
$team2->addPlayer($player4, $gameInfo->getDefaultRating()); $team2->addPlayer($player4, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
// Winners // Winners
@ -249,7 +247,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player2, new Rating(20, 7)); $team2->addPlayer($player2, new Rating(20, 7));
$team2->addPlayer($player3, new Rating(25, 8)); $team2->addPlayer($player3, new Rating(25, 8));
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
// Winners // Winners
@ -280,7 +278,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player3, $gameInfo->getDefaultRating()); $team2->addPlayer($player3, $gameInfo->getDefaultRating());
$team2->addPlayer($player4, $gameInfo->getDefaultRating()); $team2->addPlayer($player4, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
// Winners // Winners
@ -310,7 +308,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player2, $gameInfo->getDefaultRating()); $team2->addPlayer($player2, $gameInfo->getDefaultRating());
$team2->addPlayer($player3, $gameInfo->getDefaultRating()); $team2->addPlayer($player3, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
// Winners // Winners
@ -341,7 +339,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player3, $gameInfo->getDefaultRating()); $team2->addPlayer($player3, $gameInfo->getDefaultRating());
$team2->addPlayer($player4, $gameInfo->getDefaultRating()); $team2->addPlayer($player4, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
// Winners // Winners
@ -381,7 +379,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player7, $gameInfo->getDefaultRating()); $team2->addPlayer($player7, $gameInfo->getDefaultRating());
$team2->addPlayer($player8, $gameInfo->getDefaultRating()); $team2->addPlayer($player8, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
// Winners // Winners
@ -419,7 +417,7 @@ class TrueSkillCalculatorTests
$gameInfo = new GameInfo(); $gameInfo = new GameInfo();
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLoseExpected = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLoseExpected = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
// Winners // Winners
@ -431,7 +429,7 @@ class TrueSkillCalculatorTests
self::assertRating($testClass, 29.785, 3.958, $newRatingsWinLoseExpected->getRating($player4)); self::assertRating($testClass, 29.785, 3.958, $newRatingsWinLoseExpected->getRating($player4));
self::assertRating($testClass, 30.879, 2.983, $newRatingsWinLoseExpected->getRating($player5)); self::assertRating($testClass, 30.879, 2.983, $newRatingsWinLoseExpected->getRating($player5));
$newRatingsWinLoseUpset = $calculator->calculateNewRatings($gameInfo, Teams::concat($team1, $team2), [2, 1]); $newRatingsWinLoseUpset = $calculator->calculateNewRatings($gameInfo, [$team1, $team2], [2, 1]);
// Winners // Winners
self::assertRating($testClass, 32.012, 3.877, $newRatingsWinLoseUpset->getRating($player4)); self::assertRating($testClass, 32.012, 3.877, $newRatingsWinLoseUpset->getRating($player4));
@ -463,7 +461,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player3, new Rating(25, 4)); $team2->addPlayer($player3, new Rating(25, 4));
$team2->addPlayer($player4, new Rating(30, 3)); $team2->addPlayer($player4, new Rating(30, 3));
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
// Winners // Winners
@ -495,7 +493,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player3, new Rating(35, 7)); $team2->addPlayer($player3, new Rating(35, 7));
$team2->addPlayer($player4, new Rating(40, 5)); $team2->addPlayer($player4, new Rating(40, 5));
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
// Winners // Winners
@ -535,7 +533,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player7, $gameInfo->getDefaultRating()); $team2->addPlayer($player7, $gameInfo->getDefaultRating());
$team2->addPlayer($player8, $gameInfo->getDefaultRating()); $team2->addPlayer($player8, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
@ -572,7 +570,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($player3, $gameInfo->getDefaultRating()); $team2->addPlayer($player3, $gameInfo->getDefaultRating());
$team2->addPlayer($player4, $gameInfo->getDefaultRating()); $team2->addPlayer($player4, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
// Winners // Winners
@ -598,7 +596,7 @@ class TrueSkillCalculatorTests
$team2 = new Team($player2, $gameInfo->getDefaultRating()); $team2 = new Team($player2, $gameInfo->getDefaultRating());
$team3 = new Team($player3, $gameInfo->getDefaultRating()); $team3 = new Team($player3, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2, $team3); $teams = [$team1, $team2, $team3];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3]);
$player1NewRating = $newRatings->getRating($player1); $player1NewRating = $newRatings->getRating($player1);
@ -625,7 +623,7 @@ class TrueSkillCalculatorTests
$team2 = new Team($player2, $gameInfo->getDefaultRating()); $team2 = new Team($player2, $gameInfo->getDefaultRating());
$team3 = new Team($player3, $gameInfo->getDefaultRating()); $team3 = new Team($player3, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2, $team3); $teams = [$team1, $team2, $team3];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1, 1]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1, 1]);
$player1NewRating = $newRatings->getRating($player1); $player1NewRating = $newRatings->getRating($player1);
@ -653,7 +651,7 @@ class TrueSkillCalculatorTests
$team3 = new Team($player3, $gameInfo->getDefaultRating()); $team3 = new Team($player3, $gameInfo->getDefaultRating());
$team4 = new Team($player4, $gameInfo->getDefaultRating()); $team4 = new Team($player4, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2, $team3, $team4); $teams = [$team1, $team2, $team3, $team4];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3, 4]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3, 4]);
@ -687,7 +685,7 @@ class TrueSkillCalculatorTests
$team4 = new Team($player4, $gameInfo->getDefaultRating()); $team4 = new Team($player4, $gameInfo->getDefaultRating());
$team5 = new Team($player5, $gameInfo->getDefaultRating()); $team5 = new Team($player5, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2, $team3, $team4, $team5); $teams = [$team1, $team2, $team3, $team4, $team5];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3, 4, 5]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3, 4, 5]);
$player1NewRating = $newRatings->getRating($player1); $player1NewRating = $newRatings->getRating($player1);
@ -729,7 +727,7 @@ class TrueSkillCalculatorTests
$team7 = new Team($player7, $gameInfo->getDefaultRating()); $team7 = new Team($player7, $gameInfo->getDefaultRating());
$team8 = new Team($player8, $gameInfo->getDefaultRating()); $team8 = new Team($player8, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2, $team3, $team4, $team5, $team6, $team7, $team8); $teams = [$team1, $team2, $team3, $team4, $team5, $team6, $team7, $team8];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1, 1, 1, 1, 1, 1, 1]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1, 1, 1, 1, 1, 1, 1]);
$player1NewRating = $newRatings->getRating($player1); $player1NewRating = $newRatings->getRating($player1);
@ -781,7 +779,7 @@ class TrueSkillCalculatorTests
$team7 = new Team($player7, new Rating(40, 2)); $team7 = new Team($player7, new Rating(40, 2));
$team8 = new Team($player8, new Rating(45, 1)); $team8 = new Team($player8, new Rating(45, 1));
$teams = Teams::concat($team1, $team2, $team3, $team4, $team5, $team6, $team7, $team8); $teams = [$team1, $team2, $team3, $team4, $team5, $team6, $team7, $team8];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3, 4, 5, 6, 7, 8]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3, 4, 5, 6, 7, 8]);
$player1NewRating = $newRatings->getRating($player1); $player1NewRating = $newRatings->getRating($player1);
@ -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::concat( $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,
@ -952,7 +933,7 @@ class TrueSkillCalculatorTests
$team3->addPlayer($player7, new Rating(50, 5)); $team3->addPlayer($player7, new Rating(50, 5));
$team3->addPlayer($player8, new Rating(30, 2)); $team3->addPlayer($player8, new Rating(30, 2));
$teams = Teams::concat($team1, $team2, $team3); $teams = [$team1, $team2, $team3];
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 2]); $newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 2]);
// Winners // Winners
@ -986,7 +967,7 @@ class TrueSkillCalculatorTests
$team2->addPlayer($p2, $gameInfo->getDefaultRating()); $team2->addPlayer($p2, $gameInfo->getDefaultRating());
$team2->addPlayer($p3, $gameInfo->getDefaultRating()); $team2->addPlayer($p3, $gameInfo->getDefaultRating());
$teams = Teams::concat($team1, $team2); $teams = [$team1, $team2];
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]); $newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
$p1NewRating = $newRatings->getRating($p1); $p1NewRating = $newRatings->getRating($p1);

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace DNW\Skills\Tests\TrueSkill;
use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
use DNW\Skills\Numerics\BasicMath;
use DNW\Skills\Numerics\GaussianDistribution;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(TruncatedGaussianCorrectionFunctions::class)]
#[UsesClass(BasicMath::class)]
#[UsesClass(GaussianDistribution::class)]
class TruncatedGaussianCorrectionFunctionsTest extends TestCase
{
public function testvGreaterThan(): void
{
// Test values taken from Ralf Herbrich's F# TrueSkill implementation
$want = 0.4181660649773850;
$tVar = 0.7495591915280050;
$eps = 0.0631282276750071;
$this->assertEqualsWithDelta($want, TruncatedGaussianCorrectionFunctions::vExceedsMargin($tVar, $eps), 1e-6);
}
public function testwGreaterThan(): void
{
// Test values taken from Ralf Herbrich's F# TrueSkill implementation
$want = 0.4619049929317120;
$tVar = 0.7495591915280050;
$eps = 0.0631282276750071;
$this->assertEqualsWithDelta($want, TruncatedGaussianCorrectionFunctions::wExceedsMargin($tVar, $eps), 1e-6);
}
public function testvWithin(): void
{
// Test values taken from Ralf Herbrich's F# TrueSkill implementation
$want = -0.7485644072749330;
$tVar = 0.7495591915280050;
$eps = 0.0631282276750071;
$this->assertEqualsWithDelta($want, TruncatedGaussianCorrectionFunctions::vWithinMargin($tVar, $eps), 1e-6);
}
public function testwWithin(): void
{
// Test values taken from Ralf Herbrich's F# TrueSkill implementation
$want = 0.9986734210033660;
$tVar = 0.7495591915280050;
$eps = 0.0631282276750071;
$this->assertEqualsWithDelta($want, TruncatedGaussianCorrectionFunctions::wWithinMargin($tVar, $eps), 1e-6);
}
}