mirror of
https://github.com/furyfire/trueskill.git
synced 2025-01-16 09:57:40 +00:00
Compare commits
66 Commits
23fc14af0a
...
master
Author | SHA1 | Date | |
---|---|---|---|
2212d45f61 | |||
24665d6e57 | |||
79f4495a77 | |||
9df860b08b | |||
1e381bfab7 | |||
f6acee18e5 | |||
1b7e26a6b5 | |||
cf588f3fc2 | |||
5dc0c7058d | |||
ed9013df34 | |||
da5d782e40 | |||
de6a414d2b | |||
6fbc2540ab | |||
4b3a328726 | |||
82a5505438 | |||
cbf03c5736 | |||
65db7f64ea | |||
5cf6acdfb1 | |||
46dcbed28b | |||
c33f62af2f | |||
c202330a77 | |||
1574f1c878 | |||
002f07003a | |||
063a64a4f6 | |||
796bd993d0 | |||
bbff1fbbbc | |||
ae5d2a8b73 | |||
0095829906 | |||
e5a96226ca | |||
11fafc129a | |||
73ef2f45c8 | |||
fd91e9b0c1 | |||
7b09658c25 | |||
4c8e1fcb1b | |||
f4a86016a8 | |||
ec00087025 | |||
03d26de045 | |||
231173dbf2 | |||
49953d1329 | |||
3a14da5388 | |||
783a12e744 | |||
edc80f9a43 | |||
61af342cfa | |||
c781540aa6 | |||
0184d0432b | |||
f3e5912ebb | |||
703372fda9 | |||
df5b48b2c4 | |||
660fbd1486 | |||
c72112c5aa | |||
1ea48d8dd0 | |||
f372d9a028 | |||
5bebd9310d | |||
b966a930a4 | |||
8a0869d535 | |||
4e7ae9acb9 | |||
38d5d731de | |||
99eb3271c2 | |||
747f06f7c9 | |||
1bbc4bb91c | |||
1359cbeb6b | |||
c18ccd38e2 | |||
3dddfc05db | |||
968c78d989 | |||
769514b38e | |||
36dea4ea03 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
.vscode
|
.*/
|
||||||
vendor
|
vendor
|
||||||
.phpunit.cache
|
.*.cache/
|
||||||
*.phar
|
*.phar
|
||||||
.phpdoc/
|
.phpdoc/
|
||||||
output/
|
output/
|
53
.phpcs.xml
Normal file
53
.phpcs.xml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer" xsi:noNamespaceSchemaLocation="phpcs.xsd">
|
||||||
|
<description>Coding standard</description>
|
||||||
|
|
||||||
|
<file>src/</file>
|
||||||
|
<file>tests/</file>
|
||||||
|
<file>benchmark/</file>
|
||||||
|
|
||||||
|
|
||||||
|
<rule ref="PSR1">
|
||||||
|
<exclude name="Generic.Files.LineLength"/>
|
||||||
|
</rule>
|
||||||
|
<rule ref="PSR2"></rule>
|
||||||
|
<rule ref="PSR12"></rule>
|
||||||
|
|
||||||
|
<rule ref="Generic">
|
||||||
|
<exclude name="Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed"/>
|
||||||
|
<exclude name="Generic.Files.LowercasedFilename.NotFound"/>
|
||||||
|
<exclude name="Generic.PHP.ClosingPHPTag.NotFound"/>
|
||||||
|
<exclude name="Generic.Files.EndFileNoNewline.Found"/>
|
||||||
|
<exclude name="Generic.Files.EndFileNoNewline.Found"/>
|
||||||
|
<exclude name="Generic.Arrays.DisallowShortArraySyntax.Found"/>
|
||||||
|
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine"/>
|
||||||
|
<exclude name="Generic.Classes.OpeningBraceSameLine.BraceOnNewLine"/>
|
||||||
|
<exclude name="Generic.PHP.LowerCaseConstant.Found"/>
|
||||||
|
<exclude name="Generic.Formatting.SpaceAfterCast"/>
|
||||||
|
<exclude name="Generic.Formatting.MultipleStatementAlignment.NotSameWarning"/>
|
||||||
|
<exclude name="Generic.Commenting.DocComment.MissingShort"/>
|
||||||
|
<exclude name="Generic.NamingConventions.AbstractClassNamePrefix.Missing"/>
|
||||||
|
<exclude name="Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed"/>
|
||||||
|
<exclude name="Generic.NamingConventions.InterfaceNameSuffix.Missing"/>
|
||||||
|
<exclude name="Generic.Commenting.Todo.TaskFound"/>
|
||||||
|
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUse"/>
|
||||||
|
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed"/>
|
||||||
|
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed"/>
|
||||||
|
<exclude name="Generic.Formatting.SpaceBeforeCast.NoSpace"/>
|
||||||
|
<exclude name="Generic.CodeAnalysis.UselessOverridingMethod.Found"/>
|
||||||
|
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration.NewlineBeforeOpenBrace"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Ban some functions -->
|
||||||
|
<rule ref="Generic.PHP.ForbiddenFunctions">
|
||||||
|
<properties>
|
||||||
|
<property name="forbiddenFunctions" type="array">
|
||||||
|
<element key="sizeof" value="count"/>
|
||||||
|
<element key="delete" value="unset"/>
|
||||||
|
<element key="print" value="echo"/>
|
||||||
|
<element key="is_null" value="null"/>
|
||||||
|
<element key="create_function" value="null"/>
|
||||||
|
</property>
|
||||||
|
</properties>
|
||||||
|
</rule>
|
||||||
|
</ruleset>
|
12
.phplint.yml
Normal file
12
.phplint.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
path:
|
||||||
|
- src/
|
||||||
|
- tests/
|
||||||
|
- benchmark/
|
||||||
|
jobs: 10
|
||||||
|
extensions:
|
||||||
|
- php
|
||||||
|
exclude:
|
||||||
|
- vendor
|
||||||
|
warning: true
|
||||||
|
memory-limit: -1
|
||||||
|
log-junit: "output/lint.xml"
|
@ -1,23 +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
|
||||||
- composer analyze
|
- vendor/bin/phpunit tests --no-coverage
|
||||||
test:
|
|
||||||
image: php:cli-bookworm
|
|
||||||
commands:
|
|
||||||
- vendor/bin/phpunit tests
|
|
||||||
# document:
|
|
||||||
# image: phpdoc/phpdoc
|
|
||||||
# commands:
|
|
||||||
# - phpdoc
|
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: README
|
||||||
|
...
|
||||||
|
# PHP TrueSkill Implementation
|
||||||
This is a PHP port of the Moserware.Skills project that's available at
|
This is a PHP port of the Moserware.Skills project that's available at
|
||||||
|
|
||||||
http://github.com/moserware/Skills
|
http://github.com/moserware/Skills
|
||||||
@ -6,4 +10,4 @@ For more details on how the algorithm works, see
|
|||||||
|
|
||||||
http://www.moserware.com/2010/03/computing-your-skill.html
|
http://www.moserware.com/2010/03/computing-your-skill.html
|
||||||
|
|
||||||
For details on how to use this project, see the accompanying unit tests with this project
|
For details on how to use this project, see the accompanying example snippets with this project.
|
146
benchmark/BasicBench.php
Normal file
146
benchmark/BasicBench.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DNW\Skills\Benchmark;
|
||||||
|
|
||||||
|
use DNW\Skills\TrueSkill\TwoPlayerTrueSkillCalculator;
|
||||||
|
use DNW\Skills\TrueSkill\TwoTeamTrueSkillCalculator;
|
||||||
|
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
||||||
|
use DNW\Skills\GameInfo;
|
||||||
|
use DNW\Skills\Player;
|
||||||
|
use DNW\Skills\Team;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic Benchmarks.
|
||||||
|
*/
|
||||||
|
class BasicBench
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* To benchmark performance when using TwoPlayerTrueSkillCalculator
|
||||||
|
*
|
||||||
|
* @Revs(20)
|
||||||
|
* @Iterations(20)
|
||||||
|
*/
|
||||||
|
public function benchBasic2PlayersUsingTwoPlayerTrueSkillCalculator(): void
|
||||||
|
{
|
||||||
|
$gameInfo = new GameInfo();
|
||||||
|
|
||||||
|
$p1 = new Player("Winner");
|
||||||
|
$p2 = new Player("Average");
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $gameInfo->getDefaultRating());
|
||||||
|
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
|
for ($i = 0; $i < 10; ++$i) {
|
||||||
|
$teams = [$team1, $team2];
|
||||||
|
|
||||||
|
$calculator = new TwoPlayerTrueSkillCalculator();
|
||||||
|
|
||||||
|
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||||
|
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||||
|
|
||||||
|
$newRatings->getRating($p1)->getConservativeRating();
|
||||||
|
$newRatings->getRating($p2)->getConservativeRating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To benchmark performance when using TwoTeamTrueSkillCalculator for just two players in two teams
|
||||||
|
*
|
||||||
|
* @Revs(20)
|
||||||
|
* @Iterations(20)
|
||||||
|
*/
|
||||||
|
public function benchBasic2PlayersUsingTwoTeamTrueSkillCalculator(): void
|
||||||
|
{
|
||||||
|
$gameInfo = new GameInfo();
|
||||||
|
|
||||||
|
$p1 = new Player("Winner");
|
||||||
|
$p2 = new Player("Average");
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $gameInfo->getDefaultRating());
|
||||||
|
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
|
for ($i = 0; $i < 10; ++$i) {
|
||||||
|
$teams = [$team1, $team2];
|
||||||
|
|
||||||
|
$calculator = new TwoTeamTrueSkillCalculator();
|
||||||
|
|
||||||
|
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||||
|
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||||
|
|
||||||
|
$newRatings->getRating($p1)->getConservativeRating();
|
||||||
|
$newRatings->getRating($p2)->getConservativeRating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To benchmark performance when using FactorGraphTrueSkillCalculator for just two players in two teams
|
||||||
|
*
|
||||||
|
* @Revs(20)
|
||||||
|
* @Iterations(20)
|
||||||
|
*/
|
||||||
|
public function benchBasic2PlayersUsingFactorGraphTrueSkillCalculator(): void
|
||||||
|
{
|
||||||
|
$gameInfo = new GameInfo();
|
||||||
|
|
||||||
|
$p1 = new Player("Winner");
|
||||||
|
$p2 = new Player("Average");
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $gameInfo->getDefaultRating());
|
||||||
|
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
|
for ($i = 0; $i < 10; ++$i) {
|
||||||
|
$teams = [$team1, $team2];
|
||||||
|
|
||||||
|
$calculator = new FactorGraphTrueSkillCalculator();
|
||||||
|
|
||||||
|
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||||
|
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||||
|
|
||||||
|
$newRatings->getRating($p1)->getConservativeRating();
|
||||||
|
$newRatings->getRating($p2)->getConservativeRating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To benchmark performance when using FactorGraphTrueSkillCalculator with 3 players in 3 teams
|
||||||
|
*
|
||||||
|
* @Revs(20)
|
||||||
|
* @Iterations(20)
|
||||||
|
*/
|
||||||
|
public function bench3Teams(): void
|
||||||
|
{
|
||||||
|
$gameInfo = new GameInfo();
|
||||||
|
|
||||||
|
$p1 = new Player("Winner");
|
||||||
|
$p2 = new Player("Average");
|
||||||
|
$p3 = new Player("Looser");
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $gameInfo->getDefaultRating());
|
||||||
|
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||||
|
$team3 = new Team($p3, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
|
for ($i = 0; $i < 10; ++$i) {
|
||||||
|
$teams = [$team1, $team2, $team3];
|
||||||
|
|
||||||
|
$calculator = new FactorGraphTrueSkillCalculator();
|
||||||
|
|
||||||
|
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2, 3]);
|
||||||
|
|
||||||
|
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||||
|
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||||
|
$team3 = new Team($p3, $newRatings->getRating($p3));
|
||||||
|
|
||||||
|
$newRatings->getRating($p1)->getConservativeRating();
|
||||||
|
$newRatings->getRating($p2)->getConservativeRating();
|
||||||
|
$newRatings->getRating($p3)->getConservativeRating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,13 @@
|
|||||||
"php": "^8.2"
|
"php": "^8.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^10",
|
"phpstan/phpstan": "^1.0",
|
||||||
"phpstan/phpstan": "^1",
|
"vimeo/psalm": "dev-master",
|
||||||
"squizlabs/php_codesniffer": "*",
|
"phpmetrics/phpmetrics": "^3.0-dev",
|
||||||
"vimeo/psalm": "^5.14"
|
"phpunit/phpunit": "^11.2",
|
||||||
|
"psalm/plugin-phpunit": "^0.18.4",
|
||||||
|
"rector/rector": "^1.0",
|
||||||
|
"league/csv": "^9.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@ -22,16 +25,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vendor/bin/phpunit tests --display-warnings",
|
"test": "phpunit",
|
||||||
"test-coverage": "vendor/bin/phpunit tests --testdox --coverage-filter src --coverage-html output/coverage --coverage-text --testdox-html output/test.html --log-junit output/test.xml",
|
"document": "phpDocumentor",
|
||||||
"document": "phpDocumentor --setting=graphs.enabled=true",
|
"benchmark": "phpbench run --report=default --output=build-artifact",
|
||||||
|
"metrics": "vendor/bin/phpmetrics --config=phpmetrics.yml",
|
||||||
|
"lint": [
|
||||||
|
"phplint",
|
||||||
|
"phpcs",
|
||||||
|
"phpcbf src/ tests/ benchmark/ examples/",
|
||||||
|
"phpmd src/,tests/,benchmark/,examples/ text phpmd.ruleset.xml"
|
||||||
|
],
|
||||||
"analyze": [
|
"analyze": [
|
||||||
"@analyze-phpstan",
|
"@analyze-phpstan",
|
||||||
"@analyze-psalm",
|
"@analyze-psalm",
|
||||||
"@analyze-phpcs"
|
"@analyze-rector"
|
||||||
],
|
],
|
||||||
"analyze-phpstan":"vendor/bin/phpstan analyze --error-format=raw",
|
"analyze-phpstan":"vendor/bin/phpstan analyze --error-format=raw",
|
||||||
"analyze-psalm": "vendor/bin/psalm --no-cache",
|
"analyze-psalm": "vendor/bin/psalm --no-cache --show-info=true",
|
||||||
"analyze-phpcs": "vendor/bin/phpcs --report=emacs --standard=PSR1,PSR2,PSR12 --exclude=Generic.Files.LineLength src tests"
|
"analyze-rector": "vendor/bin/rector --dry-run",
|
||||||
|
"html": [
|
||||||
|
"pandoc -s README.md -o output/README.html",
|
||||||
|
"pandoc -s docs/index.rst -o output/index.html"
|
||||||
|
],
|
||||||
|
"all": [
|
||||||
|
"@test",
|
||||||
|
"@lint",
|
||||||
|
"@analyze",
|
||||||
|
"@document",
|
||||||
|
"@metrics",
|
||||||
|
"@html"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1889
composer.lock
generated
1889
composer.lock
generated
File diff suppressed because it is too large
Load Diff
53
docs/index.rst
Normal file
53
docs/index.rst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
Documentation
|
||||||
|
=============
|
||||||
|
This is a PHP port of the Moserware.Skills project that's available at
|
||||||
|
|
||||||
|
http://github.com/moserware/Skills
|
||||||
|
|
||||||
|
For more details on how the algorithm works, see
|
||||||
|
|
||||||
|
http://www.moserware.com/2010/03/computing-your-skill.html
|
||||||
|
|
||||||
|
For details on how to use this project, see the accompanying example snippets with this project.
|
||||||
|
|
||||||
|
https://www.microsoft.com/en-us/research/project/trueskill-ranking-system/
|
||||||
|
https://github.com/moserware/PHPSkills
|
||||||
|
https://www.moserware.com/2010/03/computing-your-skill.html
|
||||||
|
|
||||||
|
|
||||||
|
From Microsoft
|
||||||
|
--------------
|
||||||
|
The TrueSkill ranking system is a skill based ranking system for Xbox Live(opens in new tab) developed at Microsoft Research(opens in new tab). The purpose of a ranking system is to both identify and track the skills of gamers in a game (mode) in order to be able to match them into competitive matches. TrueSkill has been used to rank and match players in many different games, from Halo 3 to Forza Motorsport 7(opens in new tab).
|
||||||
|
|
||||||
|
An improved version of the TrueSkill ranking system, named TrueSkill 2(opens in new tab), launched with Gears of War 4(opens in new tab) and was later incorporated into Halo 5(opens in new tab).
|
||||||
|
|
||||||
|
The classic TrueSkill ranking system only uses the final standings of all teams in a match in order to update the skill estimates (ranks) of all players in the match. The TrueSkill 2 ranking system also uses the individual scores of players in order to weight the contribution of each player to each team. As a result, TrueSkill 2 is much faster at figuring out the skill of a new player.
|
||||||
|
|
||||||
|
|
||||||
|
Links
|
||||||
|
-----
|
||||||
|
|
||||||
|
* `Project README <README.html>`_
|
||||||
|
* `API Documentations <docs/>`_
|
||||||
|
* `CodeCoverage <coverage/>`_
|
||||||
|
* `Test report <test/index.html>`_
|
||||||
|
* `Mutation testing <mutation/infection.html>`_
|
||||||
|
* `Code metrics <metrics/index.html>`_
|
||||||
|
|
||||||
|
|
||||||
|
Standard Tools
|
||||||
|
--------------
|
||||||
|
* PHP8.3
|
||||||
|
* PlantUML
|
||||||
|
* GraphViz
|
||||||
|
* Pandoc
|
||||||
|
|
||||||
|
|
||||||
|
PHP Tools
|
||||||
|
---------
|
||||||
|
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
|
||||||
|
* sudo phive install -g overtrue/phplint --force-accept-unsigned
|
||||||
|
* composer install
|
||||||
|
* composer all
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("vendor/autoload.php");
|
require_once(__DIR__ . "/../vendor/autoload.php");
|
||||||
|
|
||||||
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
||||||
use DNW\Skills\GameInfo;
|
use DNW\Skills\GameInfo;
|
||||||
@ -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\n";
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("vendor/autoload.php");
|
require_once(__DIR__ . "/../vendor/autoload.php");
|
||||||
|
|
||||||
use DNW\Skills\TrueSkill\TwoPlayerTrueSkillCalculator;
|
use DNW\Skills\TrueSkill\TwoPlayerTrueSkillCalculator;
|
||||||
use DNW\Skills\GameInfo;
|
use DNW\Skills\GameInfo;
|
||||||
@ -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\n";
|
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
90
examples/motogp/goat.php
Normal 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
56397
examples/motogp/motogp.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -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": {
|
||||||
|
14
phpbench.json
Normal file
14
phpbench.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"runner.bootstrap": "vendor/autoload.php",
|
||||||
|
"runner.path": "benchmark/",
|
||||||
|
"runner.php_disable_ini": true,
|
||||||
|
"runner.retry_threshold": 10,
|
||||||
|
"runner.iterations": 10,
|
||||||
|
"report.outputs": {
|
||||||
|
"build-artifact": {
|
||||||
|
"renderer": "html",
|
||||||
|
"path": "output/benchmark.html",
|
||||||
|
"title": "Benchmarking"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,16 +3,18 @@
|
|||||||
configVersion="3"
|
configVersion="3"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns="https://www.phpdoc.org"
|
xmlns="https://www.phpdoc.org"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://docs.phpdoc.org/latest/phpdoc.xsd"
|
||||||
>
|
>
|
||||||
<title>backupscript</title>
|
<title>trueskill</title>
|
||||||
<paths>
|
<paths>
|
||||||
<output>output/docs</output>
|
<output>output/docs</output>
|
||||||
</paths>
|
</paths>
|
||||||
<version number="latest">
|
<version number="latest">
|
||||||
<api>
|
<api>
|
||||||
<source dsn=".">
|
<source>
|
||||||
<path>src</path>
|
<path>src</path>
|
||||||
</source>
|
</source>
|
||||||
</api>
|
</api>
|
||||||
</version>
|
</version>
|
||||||
|
<!--setting name="graphs.enabled" value="true"/-->
|
||||||
</phpdocumentor>
|
</phpdocumentor>
|
9
phpmd.baseline.xml
Normal file
9
phpmd.baseline.xml
Normal 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
49
phpmd.ruleset.xml
Normal 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>
|
18
phpmetrics.yml
Normal file
18
phpmetrics.yml
Normal 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"
|
@ -3,3 +3,4 @@ parameters:
|
|||||||
paths:
|
paths:
|
||||||
- src
|
- src
|
||||||
- tests
|
- tests
|
||||||
|
- benchmark
|
||||||
|
14
phpunit.xml
14
phpunit.xml
@ -1,6 +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">
|
<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">
|
||||||
<coverage/>
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="PHPSkills Test Suite">
|
<testsuite name="PHPSkills Test Suite">
|
||||||
<directory>./tests/</directory>
|
<directory>./tests/</directory>
|
||||||
@ -11,4 +10,15 @@
|
|||||||
<directory suffix=".php">src/</directory>
|
<directory suffix=".php">src/</directory>
|
||||||
</include>
|
</include>
|
||||||
</source>
|
</source>
|
||||||
|
<logging>
|
||||||
|
<junit outputFile="output/test/junit.xml"/>
|
||||||
|
<testdoxHtml outputFile="output/test/index.html"/>
|
||||||
|
</logging>
|
||||||
|
|
||||||
|
<coverage>
|
||||||
|
<report>
|
||||||
|
<html outputDirectory="output/coverage" />
|
||||||
|
<clover outputFile ="output/coverage/clover.xml" />
|
||||||
|
</report>
|
||||||
|
</coverage>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
10
psalm.xml
10
psalm.xml
@ -9,10 +9,14 @@
|
|||||||
findUnusedCode="false"
|
findUnusedCode="false"
|
||||||
>
|
>
|
||||||
<projectFiles>
|
<projectFiles>
|
||||||
<directory name="src" />
|
<directory name="src"/>
|
||||||
<directory name="tests" />
|
<directory name="tests"/>
|
||||||
|
<directory name="benchmark"/>
|
||||||
<ignoreFiles>
|
<ignoreFiles>
|
||||||
<directory name="vendor" />
|
<directory name="vendor"/>
|
||||||
</ignoreFiles>
|
</ignoreFiles>
|
||||||
</projectFiles>
|
</projectFiles>
|
||||||
|
<plugins>
|
||||||
|
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
|
||||||
|
</plugins>
|
||||||
</psalm>
|
</psalm>
|
||||||
|
20
rector.php
Normal file
20
rector.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Rector\Config\RectorConfig;
|
||||||
|
use Rector\CodeQuality\Rector\ClassMethod\LocallyCalledStaticMethodToNonStaticRector;
|
||||||
|
|
||||||
|
return RectorConfig::configure()
|
||||||
|
->withPaths([
|
||||||
|
__DIR__ . '/benchmark',
|
||||||
|
__DIR__ . '/examples',
|
||||||
|
__DIR__ . '/src',
|
||||||
|
__DIR__ . '/tests',
|
||||||
|
])
|
||||||
|
// uncomment to reach your current PHP version
|
||||||
|
->withPhpSets()
|
||||||
|
->withPreparedSets(deadCode: true, codeQuality: true, codingStyle: true, typeDeclarations : true, privatization: true, naming: false, instanceOf: true, earlyReturn: true, strictBooleans: true)
|
||||||
|
->withSkip([
|
||||||
|
LocallyCalledStaticMethodToNonStaticRector::class
|
||||||
|
]);;
|
@ -1,37 +1,36 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
use DNW\Skills\Guard;
|
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 HashMap $messageToVariableBinding;
|
private readonly HashMap $msgToVariableBinding;
|
||||||
|
|
||||||
private 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed The log-normalization constant of that factor
|
* @return float The log-normalization constant of that factor
|
||||||
*/
|
*/
|
||||||
public function getLogNormalization()
|
public function getLogNormalization(): float
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -71,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 . ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,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();
|
||||||
}
|
}
|
||||||
@ -94,6 +93,7 @@ abstract class Factor implements \Stringable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the ith message to the marginal and returns the log-normalization constant
|
* Sends the ith message to the marginal and returns the log-normalization constant
|
||||||
|
*
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function sendMessageIndex(int $messageIndex): float|int
|
public function sendMessageIndex(int $messageIndex): float|int
|
||||||
@ -101,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);
|
||||||
}
|
}
|
||||||
@ -112,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
class FactorGraph
|
abstract class FactorGraph
|
||||||
{
|
{
|
||||||
private VariableFactory $variableFactory;
|
private VariableFactory $variableFactory;
|
||||||
|
|
||||||
public function __construct(VariableFactory $factory)
|
protected function __construct()
|
||||||
{
|
{
|
||||||
$this->variableFactory = $factory;
|
$this->variableFactory = new VariableFactory(static fn(): null => NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getVariableFactory(): VariableFactory
|
public function getVariableFactory(): VariableFactory
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\ScheduleSequence;
|
use DNW\Skills\FactorGraphs\ScheduleSequence;
|
||||||
@ -13,28 +15,27 @@ abstract class FactorGraphLayer
|
|||||||
private array $localFactors = [];
|
private array $localFactors = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<int,array<int,object>>
|
* @var array<int,array<int,Variable>>
|
||||||
*/
|
*/
|
||||||
private array $outputVariablesGroups = [];
|
private array $outputVarGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<int,array<int,object>>
|
* @var array<int,array<int,Variable>>
|
||||||
*/
|
*/
|
||||||
private $inputVariablesGroups = [];
|
private array $inputVariablesGroups = [];
|
||||||
|
|
||||||
protected function __construct(private readonly TrueSkillFactorGraph $parentFactorGraph)
|
protected function __construct(private readonly TrueSkillFactorGraph $parentFactorGraph)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int,array<int,object>>
|
* @return array<int,array<int,Variable>>
|
||||||
*/
|
*/
|
||||||
protected function getInputVariablesGroups(): array
|
protected function getInputVariablesGroups(): array
|
||||||
{
|
{
|
||||||
return $this->inputVariablesGroups;
|
return $this->inputVariablesGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK
|
|
||||||
|
|
||||||
public function getParentFactorGraph(): TrueSkillFactorGraph
|
public function getParentFactorGraph(): TrueSkillFactorGraph
|
||||||
{
|
{
|
||||||
return $this->parentFactorGraph;
|
return $this->parentFactorGraph;
|
||||||
@ -42,11 +43,12 @@ abstract class FactorGraphLayer
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This reference is still needed
|
* This reference is still needed
|
||||||
* @return array<int,array<int,object>>
|
*
|
||||||
|
* @return array<int,array<int,Variable>>
|
||||||
*/
|
*/
|
||||||
public function &getOutputVariablesGroups(): array
|
public function &getOutputVariablesGroups(): array
|
||||||
{
|
{
|
||||||
return $this->outputVariablesGroups;
|
return $this->outputVarGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +60,7 @@ abstract class FactorGraphLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int,array<int,object>> $value
|
* @param array<int,array<int,Variable>> $value
|
||||||
*/
|
*/
|
||||||
public function setInputVariablesGroups(array $value): void
|
public function setInputVariablesGroups(array $value): void
|
||||||
{
|
{
|
||||||
@ -68,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
|
||||||
@ -82,11 +84,11 @@ abstract class FactorGraphLayer
|
|||||||
|
|
||||||
public function createPriorSchedule(): ?ScheduleSequence
|
public function createPriorSchedule(): ?ScheduleSequence
|
||||||
{
|
{
|
||||||
return null;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPosteriorSchedule(): ?ScheduleSequence
|
public function createPosteriorSchedule(): ?ScheduleSequence
|
||||||
{
|
{
|
||||||
return null;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,13 +25,13 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
class KeyedVariable extends Variable
|
class KeyedVariable extends Variable
|
||||||
{
|
{
|
||||||
public function __construct(private 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
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
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 string $name)
|
public function __construct(private GaussianDistribution $value)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,9 +21,4 @@ class Message implements \Stringable
|
|||||||
{
|
{
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString(): string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
abstract class Schedule implements \Stringable
|
abstract class Schedule
|
||||||
{
|
{
|
||||||
protected function __construct(private 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
class ScheduleLoop extends Schedule
|
class ScheduleLoop extends Schedule
|
||||||
{
|
{
|
||||||
public function __construct(string $name, private readonly Schedule $scheduleToLoop, private 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
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
class ScheduleSequence extends Schedule
|
class ScheduleSequence extends Schedule
|
||||||
@ -7,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
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
class ScheduleStep extends Schedule
|
class ScheduleStep extends Schedule
|
||||||
{
|
{
|
||||||
public function __construct(string $name, private readonly Factor $factor, private 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
namespace DNW\Skills\FactorGraphs;
|
||||||
|
|
||||||
use DNW\Skills\Numerics\GaussianDistribution;
|
use DNW\Skills\Numerics\GaussianDistribution;
|
||||||
|
|
||||||
class Variable implements \Stringable
|
class Variable
|
||||||
{
|
{
|
||||||
private 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,9 +29,4 @@ class Variable implements \Stringable
|
|||||||
{
|
{
|
||||||
$this->value = $this->prior;
|
$this->value = $this->prior;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString(): string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\FactorGraphs;
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7,23 +9,30 @@ namespace DNW\Skills;
|
|||||||
*/
|
*/
|
||||||
class GameInfo
|
class GameInfo
|
||||||
{
|
{
|
||||||
private const DEFAULT_BETA = 4.1666666666666666666666666666667; // Default initial mean / 6
|
/**
|
||||||
|
* Default initial mean / 6
|
||||||
|
*/
|
||||||
|
private const DEFAULT_BETA = 4.1666666666666666666666666666667;
|
||||||
|
|
||||||
private const DEFAULT_DRAW_PROBABILITY = 0.10;
|
private const DEFAULT_DRAW_PROBABILITY = 0.10;
|
||||||
|
|
||||||
private const DEFAULT_DYNAMICS_FACTOR = 0.083333333333333333333333333333333; // Default initial mean / 300
|
/**
|
||||||
|
* Default initial mean / 300
|
||||||
|
*/
|
||||||
|
private const DEFAULT_DYNAMICS_FACTOR = 0.083333333333333333333333333333333;
|
||||||
|
|
||||||
private const DEFAULT_INITIAL_MEAN = 25.0;
|
private const DEFAULT_INITIAL_MEAN = 25.0;
|
||||||
|
|
||||||
private const DEFAULT_INITIAL_STANDARD_DEVIATION = 8.3333333333333333333333333333333;
|
private const DEFAULT_INITIAL_STANDARD_DEVIATION = 8.3333333333333333333333333333333;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private float $initialMean = self::DEFAULT_INITIAL_MEAN,
|
private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN,
|
||||||
private float $initialStandardDeviation = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
|
private readonly float $initialStdDev = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
|
||||||
private float $beta = self::DEFAULT_BETA,
|
private readonly float $beta = self::DEFAULT_BETA,
|
||||||
private float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
|
private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
|
||||||
private float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
|
private readonly float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInitialMean(): float
|
public function getInitialMean(): float
|
||||||
@ -33,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
|
||||||
@ -53,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
@ -11,13 +13,6 @@ use Exception;
|
|||||||
*/
|
*/
|
||||||
class Guard
|
class Guard
|
||||||
{
|
{
|
||||||
public static function argumentNotNull(mixed $value, string $parameterName): void
|
|
||||||
{
|
|
||||||
if ($value == null) {
|
|
||||||
throw new Exception($parameterName . ' can not be null');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function argumentIsValidIndex(int $index, int $count, string $parameterName): void
|
public static function argumentIsValidIndex(int $index, int $count, string $parameterName): void
|
||||||
{
|
{
|
||||||
if (($index < 0) || ($index >= $count)) {
|
if (($index < 0) || ($index >= $count)) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,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;
|
||||||
|
|
||||||
@ -53,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
interface ISupportPartialUpdate
|
interface ISupportPartialUpdate
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,21 +13,23 @@ namespace DNW\Skills\Numerics;
|
|||||||
class BasicMath
|
class BasicMath
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Squares the input (x^2 = x * x)
|
* Squares the input (input^2 = input * input)
|
||||||
*
|
*
|
||||||
* @param float $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($x): float
|
public static function square(float $input): float
|
||||||
{
|
{
|
||||||
return $x * $x;
|
return $input * $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sums the items in $itemsToSum
|
* Sums the items in $itemsToSum
|
||||||
*
|
*
|
||||||
* @param mixed[] $itemsToSum The items to sum,
|
* @param mixed[] $itemsToSum The items to sum,
|
||||||
* @param \Closure $callback The function to apply to each array element before summing.
|
* @param \Closure $callback The function to apply to each array element before summing.
|
||||||
|
*
|
||||||
* @return float The sum.
|
* @return float The sum.
|
||||||
*/
|
*/
|
||||||
public static function sum(array $itemsToSum, \Closure $callback): float
|
public static function sum(array $itemsToSum, \Closure $callback): float
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
class DiagonalMatrix extends Matrix
|
class DiagonalMatrix extends Matrix
|
||||||
@ -10,13 +12,11 @@ class DiagonalMatrix extends Matrix
|
|||||||
public function __construct(array $diagonalValues)
|
public function __construct(array $diagonalValues)
|
||||||
{
|
{
|
||||||
$diagonalCount = count($diagonalValues);
|
$diagonalCount = count($diagonalValues);
|
||||||
$rowCount = $diagonalCount;
|
|
||||||
$colCount = $rowCount;
|
|
||||||
|
|
||||||
parent::__construct($rowCount, $colCount);
|
parent::__construct($diagonalCount, $diagonalCount);
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $rowCount; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $diagonalCount; ++$currentRow) {
|
||||||
for ($currentCol = 0; $currentCol < $colCount; $currentCol++) {
|
for ($currentCol = 0; $currentCol < $diagonalCount; ++$currentCol) {
|
||||||
if ($currentRow === $currentCol) {
|
if ($currentRow === $currentCol) {
|
||||||
$this->setValue($currentRow, $currentCol, $diagonalValues[$currentRow]);
|
$this->setValue($currentRow, $currentCol, $diagonalValues[$currentRow]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,26 +10,51 @@ 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π.
|
||||||
|
* Precalculated constant for performance reasons
|
||||||
|
* sqrt(2*pi)
|
||||||
|
*
|
||||||
|
* @link https://www.wolframalpha.com/input?i=sqrt%282*pi%29 Source of value
|
||||||
|
*/
|
||||||
|
private const M_SQRT_2_PI = 2.5066282746310005024157652848110452530069867406099383166299235763;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log of Square Root 2π.
|
||||||
|
* Precalculated constant for performance reasons
|
||||||
|
* log(sqrt(2*pi))
|
||||||
|
*
|
||||||
|
* @link https://www.wolframalpha.com/input?i=log%28sqrt%282*pi%29%29 Source of value
|
||||||
|
*/
|
||||||
|
private const M_LOG_SQRT_2_PI = 0.9189385332046727417803297364056176398613974736377834128171515404;
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,10 +84,14 @@ class GaussianDistribution implements \Stringable
|
|||||||
return $this->precisionMean;
|
return $this->precisionMean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Great derivation of this is at
|
||||||
|
*
|
||||||
|
* @link http://www.astro.psu.edu/~mce/A451_2/A451/downloads/notes0.pdf
|
||||||
|
*/
|
||||||
public function getNormalizationConstant(): float
|
public function getNormalizationConstant(): float
|
||||||
{
|
{
|
||||||
// Great derivation of this is at http://www.astro.psu.edu/~mce/A451_2/A451/downloads/notes0.pdf
|
return 1.0 / (self::M_SQRT_2_PI * $this->standardDeviation);
|
||||||
return 1.0 / (sqrt(2 * M_PI) * $this->standardDeviation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromPrecisionMean(float $precisionMean, float $precision): self
|
public static function fromPrecisionMean(float $precisionMean, float $precision): self
|
||||||
@ -82,14 +113,18 @@ class GaussianDistribution implements \Stringable
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For details, see http://www.tina-vision.net/tina-knoppix/tina-memo/2003-003.pdf
|
/**
|
||||||
// for multiplication, the precision mean ones are easier to write :)
|
* For details, see http://www.tina-vision.net/tina-knoppix/tina-memo/2003-003.pdf
|
||||||
|
* for multiplication, the precision mean ones are easier to write :)
|
||||||
|
*/
|
||||||
public static function multiply(GaussianDistribution $left, GaussianDistribution $right): self
|
public static function multiply(GaussianDistribution $left, GaussianDistribution $right): self
|
||||||
{
|
{
|
||||||
return GaussianDistribution::fromPrecisionMean($left->precisionMean + $right->precisionMean, $left->precision + $right->precision);
|
return GaussianDistribution::fromPrecisionMean($left->precisionMean + $right->precisionMean, $left->precision + $right->precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the absolute difference between two Gaussians
|
/**
|
||||||
|
* Computes the absolute difference between two Gaussians
|
||||||
|
*/
|
||||||
public static function absoluteDifference(GaussianDistribution $left, GaussianDistribution $right): float
|
public static function absoluteDifference(GaussianDistribution $left, GaussianDistribution $right): float
|
||||||
{
|
{
|
||||||
return max(
|
return max(
|
||||||
@ -98,7 +133,9 @@ class GaussianDistribution implements \Stringable
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the absolute difference between two Gaussians
|
/**
|
||||||
|
* Computes the absolute difference between two Gaussians
|
||||||
|
*/
|
||||||
public static function subtract(GaussianDistribution $left, GaussianDistribution $right): float
|
public static function subtract(GaussianDistribution $left, GaussianDistribution $right): float
|
||||||
{
|
{
|
||||||
return GaussianDistribution::absoluteDifference($left, $right);
|
return GaussianDistribution::absoluteDifference($left, $right);
|
||||||
@ -113,9 +150,7 @@ class GaussianDistribution implements \Stringable
|
|||||||
$varianceSum = $left->variance + $right->variance;
|
$varianceSum = $left->variance + $right->variance;
|
||||||
$meanDifference = $left->mean - $right->mean;
|
$meanDifference = $left->mean - $right->mean;
|
||||||
|
|
||||||
$logSqrt2Pi = log(sqrt(2 * M_PI));
|
return -self::M_LOG_SQRT_2_PI - (log($varianceSum) / 2.0) - (BasicMath::square($meanDifference) / (2.0 * $varianceSum));
|
||||||
|
|
||||||
return -$logSqrt2Pi - (log($varianceSum) / 2.0) - (BasicMath::square($meanDifference) / (2.0 * $varianceSum));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function divide(GaussianDistribution $numerator, GaussianDistribution $denominator): self
|
public static function divide(GaussianDistribution $numerator, GaussianDistribution $denominator): self
|
||||||
@ -135,37 +170,34 @@ class GaussianDistribution implements \Stringable
|
|||||||
$varianceDifference = $denominator->variance - $numerator->variance;
|
$varianceDifference = $denominator->variance - $numerator->variance;
|
||||||
$meanDifference = $numerator->mean - $denominator->mean;
|
$meanDifference = $numerator->mean - $denominator->mean;
|
||||||
|
|
||||||
$logSqrt2Pi = log(sqrt(2 * M_PI));
|
return log($denominator->variance) + self::M_LOG_SQRT_2_PI - log($varianceDifference) / 2.0 +
|
||||||
|
|
||||||
return log($denominator->variance) + $logSqrt2Pi - log($varianceDifference) / 2.0 +
|
|
||||||
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)
|
||||||
// P(x) = ------------------- * e
|
// P(x) = ------------------- * e
|
||||||
// stdDev * sqrt(2*pi)
|
// stdDev * sqrt(2*pi)
|
||||||
|
|
||||||
$multiplier = 1.0 / ($standardDeviation * sqrt(2 * M_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
|
||||||
{
|
{
|
||||||
$invsqrt2 = -0.707106781186547524400844362104;
|
$result = GaussianDistribution::errorFunctionCumulativeTo(-M_SQRT1_2 * $var);
|
||||||
$result = GaussianDistribution::errorFunctionCumulativeTo($invsqrt2 * $x);
|
|
||||||
|
|
||||||
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;
|
||||||
@ -198,13 +230,14 @@ class GaussianDistribution implements \Stringable
|
|||||||
-1.523e-15,
|
-1.523e-15,
|
||||||
-9.4e-17,
|
-9.4e-17,
|
||||||
1.21e-16,
|
1.21e-16,
|
||||||
-2.8e-17, ];
|
-2.8e-17,
|
||||||
|
];
|
||||||
|
|
||||||
$ncof = count($coefficients);
|
$ncof = count($coefficients);
|
||||||
$d = 0.0;
|
$d = 0.0;
|
||||||
$dd = 0.0;
|
$dd = 0.0;
|
||||||
|
|
||||||
for ($j = $ncof - 1; $j > 0; $j--) {
|
for ($j = $ncof - 1; $j > 0; --$j) {
|
||||||
$tmp = $d;
|
$tmp = $d;
|
||||||
$d = $ty * $d - $dd + $coefficients[$j];
|
$d = $ty * $d - $dd + $coefficients[$j];
|
||||||
$dd = $tmp;
|
$dd = $tmp;
|
||||||
@ -212,40 +245,35 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($p <= 0.0) {
|
if ($p <= 0.0) {
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
$pp = ($p < 1.0) ? $p : 2 - $p;
|
$pp = ($p < 1.0) ? $p : 2 - $p;
|
||||||
$t = sqrt(-2 * log($pp / 2.0)); // Initial guess
|
$t = sqrt(-2 * log($pp / 2.0)); // Initial guess
|
||||||
$x = -0.70711 * ((2.30753 + $t * 0.27061) / (1.0 + $t * (0.99229 + $t * 0.04481)) - $t);
|
$x = -M_SQRT1_2 * ((2.30753 + $t * 0.27061) / (1.0 + $t * (0.99229 + $t * 0.04481)) - $t);
|
||||||
|
|
||||||
for ($j = 0; $j < 2; $j++) {
|
for ($j = 0; $j < 2; ++$j) {
|
||||||
$err = GaussianDistribution::errorFunctionCumulativeTo($x) - $pp;
|
$err = GaussianDistribution::errorFunctionCumulativeTo($x) - $pp;
|
||||||
$x += $err / (1.12837916709551257 * exp(-BasicMath::square($x)) - $x * $err); // Halley
|
$x += $err / (M_2_SQRTPI * exp(-BasicMath::square($x)) - $x * $err); // Halley
|
||||||
}
|
}
|
||||||
|
|
||||||
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 - sqrt(2) * $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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
class IdentityMatrix extends DiagonalMatrix
|
class IdentityMatrix extends DiagonalMatrix
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
@ -11,7 +13,7 @@ class Matrix
|
|||||||
/**
|
/**
|
||||||
* @param array<int,array<int,float>> $matrixRowData
|
* @param array<int,array<int,float>> $matrixRowData
|
||||||
*/
|
*/
|
||||||
public function __construct(private int $rowCount = 0, private int $columnCount = 0, private array $matrixRowData = array())
|
public function __construct(private readonly int $rowCount = 0, private readonly int $columnCount = 0, private array $matrixRowData = [])
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,10 +25,10 @@ class Matrix
|
|||||||
$data = [];
|
$data = [];
|
||||||
$result = new Matrix($rows, $columns, $data);
|
$result = new Matrix($rows, $columns, $data);
|
||||||
|
|
||||||
for ($currentColumn = 0; $currentColumn < $columns; $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $columns; ++$currentColumn) {
|
||||||
$currentColumnData = $columnValues[$currentColumn];
|
$currentColumnData = $columnValues[$currentColumn];
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $rows; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $rows; ++$currentRow) {
|
||||||
$result->setValue($currentRow, $currentColumn, $currentColumnData[$currentRow]);
|
$result->setValue($currentRow, $currentColumn, $currentColumnData[$currentRow]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,8 +41,8 @@ class Matrix
|
|||||||
$result = new Matrix($rows, $cols);
|
$result = new Matrix($rows, $cols);
|
||||||
$currentIndex = 0;
|
$currentIndex = 0;
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $rows; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $rows; ++$currentRow) {
|
||||||
for ($currentCol = 0; $currentCol < $cols; $currentCol++) {
|
for ($currentCol = 0; $currentCol < $cols; ++$currentCol) {
|
||||||
$result->setValue($currentRow, $currentCol, $args[$currentIndex++]);
|
$result->setValue($currentRow, $currentCol, $args[$currentIndex++]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +109,7 @@ class Matrix
|
|||||||
|
|
||||||
private function isSquare(): bool
|
private function isSquare(): bool
|
||||||
{
|
{
|
||||||
return ($this->rowCount == $this->columnCount) && ($this->rowCount > 0);
|
return ($this->rowCount === $this->columnCount) && ($this->rowCount > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDeterminant(): float
|
public function getDeterminant(): float
|
||||||
@ -98,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) {
|
||||||
@ -107,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;
|
||||||
}
|
}
|
||||||
@ -124,8 +147,8 @@ class Matrix
|
|||||||
$result = 0.0;
|
$result = 0.0;
|
||||||
|
|
||||||
// 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;
|
||||||
@ -150,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,
|
||||||
@ -166,8 +189,8 @@ class Matrix
|
|||||||
// The idea is that it's the transpose of the cofactors
|
// The idea is that it's the transpose of the cofactors
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
for ($currentColumn = 0; $currentColumn < $this->columnCount; $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
||||||
for ($currentRow = 0; $currentRow < $this->rowCount; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $this->rowCount; ++$currentRow) {
|
||||||
$result[$currentColumn][$currentRow] = $this->getCofactor($currentRow, $currentColumn);
|
$result[$currentColumn][$currentRow] = $this->getCofactor($currentRow, $currentColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,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:
|
||||||
@ -195,8 +218,8 @@ class Matrix
|
|||||||
$columns = $matrix->getColumnCount();
|
$columns = $matrix->getColumnCount();
|
||||||
$newValues = [];
|
$newValues = [];
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $rows; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $rows; ++$currentRow) {
|
||||||
for ($currentColumn = 0; $currentColumn < $columns; $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $columns; ++$currentColumn) {
|
||||||
$newValues[$currentRow][$currentColumn] = $scalarValue * $matrix->getValue($currentRow, $currentColumn);
|
$newValues[$currentRow][$currentColumn] = $scalarValue * $matrix->getValue($currentRow, $currentColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,7 +229,7 @@ class Matrix
|
|||||||
|
|
||||||
public static function add(Matrix $left, Matrix $right): Matrix
|
public static function add(Matrix $left, Matrix $right): Matrix
|
||||||
{
|
{
|
||||||
if (($left->getRowCount() != $right->getRowCount()) || ($left->getColumnCount() != $right->getColumnCount())) {
|
if (($left->getRowCount() !== $right->getRowCount()) || ($left->getColumnCount() !== $right->getColumnCount())) {
|
||||||
throw new Exception('Matrices must be of the same size');
|
throw new Exception('Matrices must be of the same size');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,8 +237,8 @@ class Matrix
|
|||||||
|
|
||||||
$resultMatrix = [];
|
$resultMatrix = [];
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $left->getRowCount(); $currentRow++) {
|
for ($currentRow = 0; $currentRow < $left->getRowCount(); ++$currentRow) {
|
||||||
for ($currentColumn = 0; $currentColumn < $right->getColumnCount(); $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $right->getColumnCount(); ++$currentColumn) {
|
||||||
$resultMatrix[$currentRow][$currentColumn] =
|
$resultMatrix[$currentRow][$currentColumn] =
|
||||||
$left->getValue($currentRow, $currentColumn)
|
$left->getValue($currentRow, $currentColumn)
|
||||||
+
|
+
|
||||||
@ -231,7 +254,7 @@ class Matrix
|
|||||||
// Just your standard matrix multiplication.
|
// Just your standard matrix multiplication.
|
||||||
// See http://en.wikipedia.org/wiki/Matrix_multiplication for details
|
// See http://en.wikipedia.org/wiki/Matrix_multiplication for details
|
||||||
|
|
||||||
if ($left->getColumnCount() != $right->getRowCount()) {
|
if ($left->getColumnCount() !== $right->getRowCount()) {
|
||||||
throw new Exception('The width of the left matrix must match the height of the right matrix');
|
throw new Exception('The width of the left matrix must match the height of the right matrix');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,11 +263,11 @@ class Matrix
|
|||||||
|
|
||||||
$resultMatrix = [];
|
$resultMatrix = [];
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $resultRows; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $resultRows; ++$currentRow) {
|
||||||
for ($currentColumn = 0; $currentColumn < $resultColumns; $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $resultColumns; ++$currentColumn) {
|
||||||
$productValue = 0;
|
$productValue = 0;
|
||||||
|
|
||||||
for ($vectorIndex = 0; $vectorIndex < $left->getColumnCount(); $vectorIndex++) {
|
for ($vectorIndex = 0; $vectorIndex < $left->getColumnCount(); ++$vectorIndex) {
|
||||||
$leftValue = $left->getValue($currentRow, $vectorIndex);
|
$leftValue = $left->getValue($currentRow, $vectorIndex);
|
||||||
$rightValue = $right->getValue($vectorIndex, $currentColumn);
|
$rightValue = $right->getValue($vectorIndex, $currentColumn);
|
||||||
$vectorIndexProduct = $leftValue * $rightValue;
|
$vectorIndexProduct = $leftValue * $rightValue;
|
||||||
@ -260,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 :)
|
||||||
@ -267,24 +291,24 @@ class Matrix
|
|||||||
|
|
||||||
$actualRow = 0;
|
$actualRow = 0;
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $this->rowCount; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $this->rowCount; ++$currentRow) {
|
||||||
if ($currentRow == $rowToRemove) {
|
if ($currentRow == $rowToRemove) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$actualCol = 0;
|
$actualCol = 0;
|
||||||
|
|
||||||
for ($currentColumn = 0; $currentColumn < $this->columnCount; $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
||||||
if ($currentColumn == $columnToRemove) {
|
if ($currentColumn == $columnToRemove) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result[$actualRow][$actualCol] = $this->matrixRowData[$currentRow][$currentColumn];
|
$result[$actualRow][$actualCol] = $this->getValue($currentRow, $currentColumn);
|
||||||
|
|
||||||
$actualCol++;
|
++$actualCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
$actualRow++;
|
++$actualRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Matrix($this->rowCount - 1, $this->columnCount - 1, $result);
|
return new Matrix($this->rowCount - 1, $this->columnCount - 1, $result);
|
||||||
@ -292,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;
|
||||||
@ -299,31 +324,31 @@ class Matrix
|
|||||||
|
|
||||||
if ($isEven) {
|
if ($isEven) {
|
||||||
return $this->getMinorMatrix($rowToRemove, $columnToRemove)->getDeterminant();
|
return $this->getMinorMatrix($rowToRemove, $columnToRemove)->getDeterminant();
|
||||||
} else {
|
|
||||||
return -1.0 * $this->getMinorMatrix($rowToRemove, $columnToRemove)->getDeterminant();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return -1.0 * $this->getMinorMatrix($rowToRemove, $columnToRemove)->getDeterminant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function equals(Matrix $otherMatrix): bool
|
public function equals(Matrix $otherMatrix): bool
|
||||||
{
|
{
|
||||||
if (($this->rowCount != $otherMatrix->getRowCount()) || ($this->columnCount != $otherMatrix->getColumnCount())) {
|
if (($this->rowCount !== $otherMatrix->getRowCount()) || ($this->columnCount !== $otherMatrix->getColumnCount())) {
|
||||||
return false;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $this->rowCount; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $this->rowCount; ++$currentRow) {
|
||||||
for ($currentColumn = 0; $currentColumn < $this->columnCount; $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
||||||
$delta =
|
$delta =
|
||||||
abs(
|
abs(
|
||||||
$this->matrixRowData[$currentRow][$currentColumn] -
|
$this->getValue($currentRow, $currentColumn) -
|
||||||
$otherMatrix->getValue($currentRow, $currentColumn)
|
$otherMatrix->getValue($currentRow, $currentColumn)
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($delta > self::ERROR_TOLERANCE) {
|
if ($delta > self::ERROR_TOLERANCE) {
|
||||||
return false;
|
return FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
// The whole purpose of this class is to make the code for the SkillCalculator(s)
|
// The whole purpose of this class is to make the code for the SkillCalculator(s)
|
||||||
@ -9,7 +11,7 @@ use Exception;
|
|||||||
|
|
||||||
class Range
|
class Range
|
||||||
{
|
{
|
||||||
final public function __construct(private int $min, private int $max)
|
final public function __construct(private readonly int $min, private readonly int $max)
|
||||||
{
|
{
|
||||||
if ($min > $max) {
|
if ($min > $max) {
|
||||||
throw new Exception('min > max');
|
throw new Exception('min > max');
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
class SquareMatrix extends Matrix
|
class SquareMatrix extends Matrix
|
||||||
{
|
{
|
||||||
public function __construct(float|int ...$allValues)
|
public function __construct(float|int ...$allValues)
|
||||||
{
|
{
|
||||||
$rows = (int) sqrt(count($allValues));
|
$size = (int)sqrt(count($allValues));
|
||||||
$cols = $rows;
|
|
||||||
|
|
||||||
$matrixData = [];
|
$matrixData = [];
|
||||||
$allValuesIndex = 0;
|
$allValuesIndex = 0;
|
||||||
|
|
||||||
for ($currentRow = 0; $currentRow < $rows; $currentRow++) {
|
for ($currentRow = 0; $currentRow < $size; ++$currentRow) {
|
||||||
for ($currentColumn = 0; $currentColumn < $cols; $currentColumn++) {
|
for ($currentColumn = 0; $currentColumn < $size; ++$currentColumn) {
|
||||||
$matrixData[$currentRow][$currentColumn] = $allValues[$allValuesIndex++];
|
$matrixData[$currentRow][$currentColumn] = $allValues[$allValuesIndex++];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::__construct($rows, $cols, $matrixData);
|
parent::__construct($size, $size, $matrixData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Numerics;
|
namespace DNW\Skills\Numerics;
|
||||||
|
|
||||||
class Vector extends Matrix
|
class Vector extends Matrix
|
||||||
@ -13,6 +15,7 @@ class Vector extends Matrix
|
|||||||
foreach ($vectorValues as $currentVectorValue) {
|
foreach ($vectorValues as $currentVectorValue) {
|
||||||
$columnValues[] = [$currentVectorValue];
|
$columnValues[] = [$currentVectorValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::__construct(count($vectorValues), 1, $columnValues);
|
parent::__construct(count($vectorValues), 1, $columnValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
class PartialPlay
|
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) {
|
||||||
$partialPlayPercentage = $smallestPercentage;
|
return $smallestPct;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $partialPlayPercentage;
|
return $partialPlayPct;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,39 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
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 float $PartialPlayPercentage;
|
private readonly float $PartialPlayPct;
|
||||||
|
|
||||||
private 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 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
|
||||||
) {
|
)
|
||||||
// If they don't want to give a player an id, that's ok...
|
{
|
||||||
Guard::argumentInRangeInclusive($partialPlayPercentage, 0.0, 1.0, 'partialPlayPercentage');
|
Guard::argumentInRangeInclusive($partialPlayPct, 0.0, 1.0, 'partialPlayPercentage');
|
||||||
Guard::argumentInRangeInclusive($partialUpdatePercentage, 0, 1.0, 'partialUpdatePercentage');
|
Guard::argumentInRangeInclusive($partialUpdatePct, 0, 1.0, 'partialUpdatePercentage');
|
||||||
$this->PartialPlayPercentage = $partialPlayPercentage;
|
$this->PartialPlayPct = $partialPlayPct;
|
||||||
$this->PartialUpdatePercentage = $partialUpdatePercentage;
|
$this->PartialUpdatePct = $partialUpdatePct;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,7 +49,7 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate, \Stringable
|
|||||||
*/
|
*/
|
||||||
public function getPartialPlayPercentage(): float
|
public function getPartialPlayPercentage(): float
|
||||||
{
|
{
|
||||||
return $this->PartialPlayPercentage;
|
return $this->PartialPlayPct;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
use DNW\Skills\Numerics\Range;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,8 +12,9 @@ class RankSorter
|
|||||||
/**
|
/**
|
||||||
* Performs an in-place sort of the items in according to the ranks in non-decreasing order.
|
* Performs an in-place sort of the items in according to the ranks in non-decreasing order.
|
||||||
*
|
*
|
||||||
* @param array<mixed> $teams The items to sort according to the order specified by ranks.
|
* @param array<mixed> $teams The items to sort according to the order specified by ranks.
|
||||||
* @param array<int> $teamRanks The ranks for each item where 1 is first place.
|
* @param array<int> $teamRanks The ranks for each item where 1 is first place.
|
||||||
|
*
|
||||||
* @return array<int>
|
* @return array<int>
|
||||||
*/
|
*/
|
||||||
public static function sort(array &$teams, array &$teamRanks): array
|
public static function sort(array &$teams, array &$teamRanks): array
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
// Container for a player's rating.
|
|
||||||
use DNW\Skills\Numerics\GaussianDistribution;
|
use DNW\Skills\Numerics\GaussianDistribution;
|
||||||
|
|
||||||
class Rating implements \Stringable
|
/**
|
||||||
|
* Container for a player's rating.
|
||||||
|
*/
|
||||||
|
class Rating
|
||||||
{
|
{
|
||||||
private const CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3;
|
private const CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3;
|
||||||
|
|
||||||
@ -16,7 +20,7 @@ class Rating implements \Stringable
|
|||||||
* @param float $standardDeviation The standard deviation of the rating (also known as s).
|
* @param float $standardDeviation The standard deviation of the rating (also known as s).
|
||||||
* @param float|int $conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating.
|
* @param float|int $conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating.
|
||||||
*/
|
*/
|
||||||
public function __construct(private float $mean, private float $standardDeviation, private float|int $conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER)
|
public function __construct(private readonly float $mean, private readonly float $standardDeviation, private readonly float|int $conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +48,9 @@ class Rating implements \Stringable
|
|||||||
return $this->mean - $this->conservativeStandardDeviationMultiplier * $this->standardDeviation;
|
return $this->mean - $this->conservativeStandardDeviationMultiplier * $this->standardDeviation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a partial rating update.
|
||||||
|
*/
|
||||||
public function getPartialUpdate(Rating $prior, Rating $fullPosterior, float $updatePercentage): Rating
|
public function getPartialUpdate(Rating $prior, Rating $fullPosterior, float $updatePercentage): Rating
|
||||||
{
|
{
|
||||||
$priorGaussian = new GaussianDistribution($prior->getMean(), $prior->getStandardDeviation());
|
$priorGaussian = new GaussianDistribution($prior->getMean(), $prior->getStandardDeviation());
|
||||||
@ -66,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
class RatingContainer
|
class RatingContainer
|
||||||
{
|
{
|
||||||
private HashMap $playerToRating;
|
private readonly HashMap $playerToRating;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
@ -9,19 +11,27 @@ 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 int $supportedOptions,
|
private readonly int $supportedOptions,
|
||||||
private readonly TeamsRange $totalTeamsAllowed,
|
private readonly TeamsRange $totalTeamsAllowed,
|
||||||
private readonly PlayersRange $playersPerTeamAllowed
|
private readonly PlayersRange $playersPerTeamAllowed
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates new ratings based on the prior ratings and team ranks.
|
* Calculates new ratings based on the prior ratings and team ranks.
|
||||||
*
|
*
|
||||||
* @param GameInfo $gameInfo Parameters for the game.
|
* @param GameInfo $gameInfo Parameters for the game.
|
||||||
* @param Team[] $teams A mapping of team players and their ratings.
|
* @param Team[] $teams A mapping of team players and their ratings.
|
||||||
* @param int[] $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2).
|
* @param int[] $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2).
|
||||||
|
*
|
||||||
* @return RatingContainer All the players and their new ratings.
|
* @return RatingContainer All the players and their new ratings.
|
||||||
*/
|
*/
|
||||||
abstract public function calculateNewRatings(
|
abstract public function calculateNewRatings(
|
||||||
@ -33,15 +43,16 @@ abstract class SkillCalculator
|
|||||||
/**
|
/**
|
||||||
* Calculates the match quality as the likelihood of all teams drawing.
|
* Calculates the match quality as the likelihood of all teams drawing.
|
||||||
*
|
*
|
||||||
* @param GameInfo $gameInfo Parameters for the game.
|
* @param GameInfo $gameInfo Parameters for the game.
|
||||||
* @param Team[] $teams A mapping of team players and their ratings.
|
* @param Team[] $teams A mapping of team players and their ratings.
|
||||||
|
*
|
||||||
* @return float The quality of the match between the teams as a percentage (0% = bad, 100% = well matched).
|
* @return float The quality of the match between the teams as a percentage (0% = bad, 100% = well matched).
|
||||||
*/
|
*/
|
||||||
abstract public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float;
|
abstract public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float;
|
||||||
|
|
||||||
public function isSupported(int $option): bool
|
public function isSupported(int $option): bool
|
||||||
{
|
{
|
||||||
return (bool)($this->supportedOptions & $option) == $option;
|
return ($this->supportedOptions & $option) === $option;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,7 +64,7 @@ abstract class SkillCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<\DNW\Skills\Team> $teams
|
* @param Team[] $teams
|
||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
@ -65,7 +76,8 @@ abstract class SkillCalculator
|
|||||||
if (! $playersPerTeam->isInRange($currentTeam->count())) {
|
if (! $playersPerTeam->isInRange($currentTeam->count())) {
|
||||||
throw new Exception('Player count is not in range');
|
throw new Exception('Player count is not in range');
|
||||||
}
|
}
|
||||||
$countOfTeams++;
|
|
||||||
|
++$countOfTeams;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $totalTeams->isInRange($countOfTeams)) {
|
if (! $totalTeams->isInRange($countOfTeams)) {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace DNW\Skills;
|
|
||||||
|
|
||||||
class SkillCalculatorSupportedOptions
|
|
||||||
{
|
|
||||||
public const NONE = 0x00;
|
|
||||||
|
|
||||||
public const PARTIAL_PLAY = 0x01;
|
|
||||||
|
|
||||||
public const PARTIAL_UPDATE = 0x02;
|
|
||||||
}
|
|
15
src/Team.php
15
src/Team.php
@ -1,16 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
class Team extends RatingContainer
|
class Team extends RatingContainer
|
||||||
{
|
{
|
||||||
public function __construct(Player $player = null, Rating $rating = null)
|
public function __construct(Player $player = NULL, Rating $rating = NULL)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
if (! $player instanceof Player) {
|
||||||
if (! is_null($player) && ! is_null($rating)) {
|
return;
|
||||||
$this->addPlayer($player, $rating);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $rating instanceof Rating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addPlayer($player, $rating);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addPlayer(Player $player, Rating $rating): self
|
public function addPlayer(Player $player, Rating $rating): self
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills;
|
namespace DNW\Skills;
|
||||||
|
|
||||||
use DNW\Skills\Numerics\Range;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill;
|
namespace DNW\Skills\TrueSkill;
|
||||||
|
|
||||||
use DNW\Skills\Numerics\GaussianDistribution;
|
use DNW\Skills\Numerics\GaussianDistribution;
|
||||||
@ -16,6 +18,6 @@ final class DrawMargin
|
|||||||
//
|
//
|
||||||
// margin = inversecdf((draw probability + 1)/2) * sqrt(n1+n2) * beta
|
// margin = inversecdf((draw probability + 1)/2) * sqrt(n1+n2) * beta
|
||||||
// n1 and n2 are the number of players on each team
|
// n1 and n2 are the number of players on each team
|
||||||
return GaussianDistribution::inverseCumulativeTo(.5 * ($drawProbability + 1), 0, 1) * sqrt(1 + 1) * $beta;
|
return GaussianDistribution::inverseCumulativeTo(.5 * ($drawProbability + 1), 0, 1) * M_SQRT2 * $beta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill;
|
namespace DNW\Skills\TrueSkill;
|
||||||
|
|
||||||
use DNW\Skills\GameInfo;
|
use DNW\Skills\GameInfo;
|
||||||
@ -12,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;
|
||||||
@ -25,8 +26,9 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -34,8 +36,8 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
GameInfo $gameInfo,
|
GameInfo $gameInfo,
|
||||||
array $teams,
|
array $teams,
|
||||||
array $teamRanks
|
array $teamRanks
|
||||||
): RatingContainer {
|
): RatingContainer
|
||||||
Guard::argumentNotNull($gameInfo, 'gameInfo');
|
{
|
||||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||||
|
|
||||||
RankSorter::sort($teams, $teamRanks);
|
RankSorter::sort($teams, $teamRanks);
|
||||||
@ -48,6 +50,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
|
|
||||||
return $factorGraph->getUpdatedRatings();
|
return $factorGraph->getUpdatedRatings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -101,7 +104,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
return new Vector(
|
return new Vector(
|
||||||
self::getPlayerRatingValues(
|
self::getPlayerRatingValues(
|
||||||
$teamAssignmentsList,
|
$teamAssignmentsList,
|
||||||
fn (Rating $rating): float => $rating->getMean()
|
static fn(Rating $rating): float => $rating->getMean()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -116,7 +119,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
return new DiagonalMatrix(
|
return new DiagonalMatrix(
|
||||||
self::getPlayerRatingValues(
|
self::getPlayerRatingValues(
|
||||||
$teamAssignmentsList,
|
$teamAssignmentsList,
|
||||||
fn (Rating $rating): float => BasicMath::square($rating->getStandardDeviation())
|
static fn(Rating $rating): float => BasicMath::square($rating->getStandardDeviation())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -124,7 +127,9 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function that gets a list of values for all player ratings
|
* Helper function that gets a list of values for all player ratings
|
||||||
|
*
|
||||||
* @param Team[] $teamAssignmentsList
|
* @param Team[] $teamAssignmentsList
|
||||||
|
*
|
||||||
* @return int[]
|
* @return int[]
|
||||||
*/
|
*/
|
||||||
private static function getPlayerRatingValues(array $teamAssignmentsList, \Closure $playerRatingFunction): array
|
private static function getPlayerRatingValues(array $teamAssignmentsList, \Closure $playerRatingFunction): array
|
||||||
@ -166,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
|
||||||
@ -176,7 +182,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
foreach ($currentTeam->getAllPlayers() as $currentPlayer) {
|
foreach ($currentTeam->getAllPlayers() as $currentPlayer) {
|
||||||
$playerAssignments[$currentColumn][] = PartialPlay::getPartialPlayPercentage($currentPlayer);
|
$playerAssignments[$currentColumn][] = PartialPlay::getPartialPlayPercentage($currentPlayer);
|
||||||
// indicates the player is on the team
|
// indicates the player is on the team
|
||||||
$totalPreviousPlayers++;
|
++$totalPreviousPlayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rowsRemaining = $totalPlayers - $totalPreviousPlayers;
|
$rowsRemaining = $totalPlayers - $totalPreviousPlayers;
|
||||||
@ -185,15 +191,15 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
foreach ($nextTeam->getAllPlayers() as $nextTeamPlayer) {
|
foreach ($nextTeam->getAllPlayers() as $nextTeamPlayer) {
|
||||||
// Add a -1 * playing time to represent the difference
|
// Add a -1 * playing time to represent the difference
|
||||||
$playerAssignments[$currentColumn][] = -1 * PartialPlay::getPartialPlayPercentage($nextTeamPlayer);
|
$playerAssignments[$currentColumn][] = -1 * PartialPlay::getPartialPlayPercentage($nextTeamPlayer);
|
||||||
$rowsRemaining--;
|
--$rowsRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($ixAdditionalRow = 0; $ixAdditionalRow < $rowsRemaining; $ixAdditionalRow++) {
|
for ($ixAdditionalRow = 0; $ixAdditionalRow < $rowsRemaining; ++$ixAdditionalRow) {
|
||||||
// Pad with zeros
|
// Pad with zeros
|
||||||
$playerAssignments[$currentColumn][] = 0;
|
$playerAssignments[$currentColumn][] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$currentColumn++;
|
++$currentColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Matrix::fromColumnValues($totalPlayers, count($teamAssignmentsList) - 1, $playerAssignments);
|
return Matrix::fromColumnValues($totalPlayers, count($teamAssignmentsList) - 1, $playerAssignments);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Factors;
|
namespace DNW\Skills\TrueSkill\Factors;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\Factor;
|
use DNW\Skills\FactorGraphs\Factor;
|
||||||
@ -30,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)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Factors;
|
namespace DNW\Skills\TrueSkill\Factors;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\Message;
|
use DNW\Skills\FactorGraphs\Message;
|
||||||
@ -14,12 +16,9 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
|
|||||||
*/
|
*/
|
||||||
class GaussianGreaterThanFactor extends GaussianFactor
|
class GaussianGreaterThanFactor extends GaussianFactor
|
||||||
{
|
{
|
||||||
private 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,11 +61,9 @@ class GaussianGreaterThanFactor extends GaussianFactor
|
|||||||
$denom = 1.0 - TruncatedGaussianCorrectionFunctions::wExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC);
|
$denom = 1.0 - TruncatedGaussianCorrectionFunctions::wExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC);
|
||||||
|
|
||||||
$newPrecision = $c / $denom;
|
$newPrecision = $c / $denom;
|
||||||
$newPrecisionMean = (
|
$newPrecisionMean = ($d +
|
||||||
$d +
|
|
||||||
$sqrtC *
|
$sqrtC *
|
||||||
TruncatedGaussianCorrectionFunctions::vExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC)
|
TruncatedGaussianCorrectionFunctions::vExceedsMargin($dOnSqrtC, $epsilsonTimesSqrtC)) / $denom;
|
||||||
) / $denom;
|
|
||||||
|
|
||||||
$newMarginal = GaussianDistribution::fromPrecisionMean($newPrecisionMean, $newPrecision);
|
$newMarginal = GaussianDistribution::fromPrecisionMean($newPrecisionMean, $newPrecision);
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Factors;
|
namespace DNW\Skills\TrueSkill\Factors;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\KeyedVariable;
|
use DNW\Skills\FactorGraphs\KeyedVariable;
|
||||||
@ -15,11 +17,12 @@ use Exception;
|
|||||||
*/
|
*/
|
||||||
class GaussianLikelihoodFactor extends GaussianFactor
|
class GaussianLikelihoodFactor extends GaussianFactor
|
||||||
{
|
{
|
||||||
private float $precision;
|
private readonly float $precision;
|
||||||
|
|
||||||
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);
|
||||||
@ -57,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
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Factors;
|
namespace DNW\Skills\TrueSkill\Factors;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\Message;
|
use DNW\Skills\FactorGraphs\Message;
|
||||||
@ -13,15 +15,15 @@ use DNW\Skills\Numerics\GaussianDistribution;
|
|||||||
*/
|
*/
|
||||||
class GaussianPriorFactor extends GaussianFactor
|
class GaussianPriorFactor extends GaussianFactor
|
||||||
{
|
{
|
||||||
private GaussianDistribution $newMessage;
|
private readonly GaussianDistribution $newMessage;
|
||||||
|
|
||||||
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);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Factors;
|
namespace DNW\Skills\TrueSkill\Factors;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\Message;
|
use DNW\Skills\FactorGraphs\Message;
|
||||||
@ -17,16 +19,18 @@ 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]
|
||||||
* corresponding to v[0] = a1*v[1] + a2*v[2]
|
* corresponding to v[0] = a1*v[1] + a2*v[2]
|
||||||
|
*
|
||||||
* @var array<float[]> $weights
|
* @var array<float[]> $weights
|
||||||
*/
|
*/
|
||||||
private array $weights = [];
|
private array $weights = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<float[]> $weightsSquared
|
* @var array<float[]> $weightsSquared
|
||||||
*/
|
*/
|
||||||
@ -38,14 +42,14 @@ 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
|
||||||
$variableWeightsLength = count($variableWeights);
|
$variableWeightsLength = count($variableWeights);
|
||||||
$this->weights[0] = array_fill(0, count($variableWeights), 0);
|
$this->weights[0] = array_fill(0, count($variableWeights), 0);
|
||||||
|
|
||||||
for ($i = 0; $i < $variableWeightsLength; $i++) {
|
for ($i = 0; $i < $variableWeightsLength; ++$i) {
|
||||||
$weight = &$variableWeights[$i];
|
$weight = &$variableWeights[$i];
|
||||||
$this->weights[0][$i] = $weight;
|
$this->weights[0][$i] = $weight;
|
||||||
$this->weightsSquared[0][$i] = BasicMath::square($weight);
|
$this->weightsSquared[0][$i] = BasicMath::square($weight);
|
||||||
@ -54,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);
|
||||||
@ -67,7 +71,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
// By convention, we'll put the v_0 term at the end
|
// By convention, we'll put the v_0 term at the end
|
||||||
|
|
||||||
$weightsLength = $variableWeightsLength + 1;
|
$weightsLength = $variableWeightsLength + 1;
|
||||||
for ($weightsIndex = 1; $weightsIndex < $weightsLength; $weightsIndex++) {
|
for ($weightsIndex = 1; $weightsIndex < $weightsLength; ++$weightsIndex) {
|
||||||
$currentWeights = \array_fill(0, $variableWeightsLength, 0);
|
$currentWeights = \array_fill(0, $variableWeightsLength, 0);
|
||||||
|
|
||||||
$variableIndices = \array_fill(0, $variableWeightsLength + 1, 0);
|
$variableIndices = \array_fill(0, $variableWeightsLength + 1, 0);
|
||||||
@ -79,7 +83,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
// This is helpful since we skip over one of the spots
|
// This is helpful since we skip over one of the spots
|
||||||
$currentDestinationWeightIndex = 0;
|
$currentDestinationWeightIndex = 0;
|
||||||
|
|
||||||
for ($currentWeightSourceIndex = 0; $currentWeightSourceIndex < $variableWeightsLength; $currentWeightSourceIndex++) {
|
for ($currentWeightSourceIndex = 0; $currentWeightSourceIndex < $variableWeightsLength; ++$currentWeightSourceIndex) {
|
||||||
if ($currentWeightSourceIndex === $weightsIndex - 1) {
|
if ($currentWeightSourceIndex === $weightsIndex - 1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -95,7 +99,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
$currentWeightsSquared[$currentDestinationWeightIndex] = $currentWeight * $currentWeight;
|
$currentWeightsSquared[$currentDestinationWeightIndex] = $currentWeight * $currentWeight;
|
||||||
|
|
||||||
$variableIndices[$currentDestinationWeightIndex + 1] = $currentWeightSourceIndex + 1;
|
$variableIndices[$currentDestinationWeightIndex + 1] = $currentWeightSourceIndex + 1;
|
||||||
$currentDestinationWeightIndex++;
|
++$currentDestinationWeightIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// And the final one
|
// And the final one
|
||||||
@ -105,10 +109,11 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
// HACK: Getting around division by zero
|
// HACK: Getting around division by zero
|
||||||
$finalWeight = 0;
|
$finalWeight = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$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;
|
||||||
@ -122,15 +127,17 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLogNormalization()
|
public function getLogNormalization(): float
|
||||||
{
|
{
|
||||||
$vars = $this->getVariables();
|
$vars = $this->getVariables();
|
||||||
$messages = $this->getMessages();
|
$messages = $this->getMessages();
|
||||||
|
|
||||||
$result = 0.0;
|
$result = 0.0;
|
||||||
|
// We start at 1 since offset 0 has the sum
|
||||||
|
$counter = count($vars);
|
||||||
|
|
||||||
// We start at 1 since offset 0 has the sum
|
// We start at 1 since offset 0 has the sum
|
||||||
for ($i = 1; $i < count($vars); $i++) {
|
for ($i = 1; $i < $counter; ++$i) {
|
||||||
$result += GaussianDistribution::logRatioNormalization($vars[$i]->getValue(), $messages[$i]->getValue());
|
$result += GaussianDistribution::logRatioNormalization($vars[$i]->getValue(), $messages[$i]->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,32 +160,24 @@ 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);
|
||||||
|
|
||||||
for ($i = 0; $i < $weightsSquaredLength; $i++) {
|
for ($i = 0; $i < $weightsSquaredLength; ++$i) {
|
||||||
// These flow directly from the paper
|
// These flow directly from the paper
|
||||||
|
|
||||||
$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;
|
||||||
$anotherNewPrecision = 1.0 / $anotherInverseOfNewPrecisionSum;
|
|
||||||
|
|
||||||
$newPrecisionMean = $newPrecision * $weightedMeanSum;
|
$newPrecisionMean = $newPrecision * $weightedMeanSum;
|
||||||
|
|
||||||
@ -208,12 +207,16 @@ 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
|
||||||
|
// order as the weights. Thankfully, the weights and messages share the same index numbers,
|
||||||
|
// so we just need to make sure they're consistent
|
||||||
|
$counter = count($allMessages);
|
||||||
|
|
||||||
// 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
|
||||||
for ($i = 0; $i < count($allMessages); $i++) {
|
for ($i = 0; $i < $counter; ++$i) {
|
||||||
$updatedMessages[] = $allMessages[$indicesToUse[$i]];
|
$updatedMessages[] = $allMessages[$indicesToUse[$i]];
|
||||||
$updatedVariables[] = $allVariables[$indicesToUse[$i]];
|
$updatedVariables[] = $allVariables[$indicesToUse[$i]];
|
||||||
}
|
}
|
||||||
@ -225,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Factors;
|
namespace DNW\Skills\TrueSkill\Factors;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\Message;
|
use DNW\Skills\FactorGraphs\Message;
|
||||||
@ -14,12 +16,10 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
|
|||||||
*/
|
*/
|
||||||
class GaussianWithinFactor extends GaussianFactor
|
class GaussianWithinFactor extends GaussianFactor
|
||||||
{
|
{
|
||||||
private 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,8 +65,7 @@ class GaussianWithinFactor extends GaussianFactor
|
|||||||
$newPrecision = $c / $denominator;
|
$newPrecision = $c / $denominator;
|
||||||
$newPrecisionMean = ($d +
|
$newPrecisionMean = ($d +
|
||||||
$sqrtC *
|
$sqrtC *
|
||||||
TruncatedGaussianCorrectionFunctions::vWithinMargin($dOnSqrtC, $epsilonTimesSqrtC)
|
TruncatedGaussianCorrectionFunctions::vWithinMargin($dOnSqrtC, $epsilonTimesSqrtC)) / $denominator;
|
||||||
) / $denominator;
|
|
||||||
|
|
||||||
$newMarginal = GaussianDistribution::fromPrecisionMean($newPrecisionMean, $newPrecision);
|
$newMarginal = GaussianDistribution::fromPrecisionMean($newPrecisionMean, $newPrecision);
|
||||||
$newMessage = GaussianDistribution::divide(
|
$newMessage = GaussianDistribution::divide(
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Layers;
|
namespace DNW\Skills\TrueSkill\Layers;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\ScheduleLoop;
|
use DNW\Skills\FactorGraphs\ScheduleLoop;
|
||||||
@ -13,29 +15,30 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
TrueSkillFactorGraph $parentGraph,
|
TrueSkillFactorGraph $parentGraph,
|
||||||
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $TeamPerformancesToTeamPerformanceDifferencesLayer,
|
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $teamPerformancesToTeamPerformanceDifferencesLayer,
|
||||||
private readonly TeamDifferencesComparisonLayer $TeamDifferencesComparisonLayer
|
private readonly TeamDifferencesComparisonLayer $teamDifferencesComparisonLayer
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
parent::__construct($parentGraph);
|
parent::__construct($parentGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLocalFactors(): array
|
public function getLocalFactors(): array
|
||||||
{
|
{
|
||||||
return array_merge(
|
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
|
||||||
@ -53,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
|
||||||
),
|
),
|
||||||
@ -80,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
|
||||||
),
|
),
|
||||||
@ -177,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
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Layers;
|
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;
|
||||||
@ -16,16 +16,19 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
|
|||||||
public function buildLayer(): void
|
public function buildLayer(): void
|
||||||
{
|
{
|
||||||
$inputVariablesGroups = $this->getInputVariablesGroups();
|
$inputVariablesGroups = $this->getInputVariablesGroups();
|
||||||
|
/**
|
||||||
|
* @var KeyedVariable[] $currentTeam
|
||||||
|
*/
|
||||||
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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,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(
|
||||||
fn ($weightedSumFactor) => 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'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,9 +52,8 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
|
|||||||
protected function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor
|
protected function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor
|
||||||
{
|
{
|
||||||
$weights = array_map(
|
$weights = array_map(
|
||||||
function ($v) {
|
static function ($v): float {
|
||||||
$player = $v->getKey();
|
$player = $v->getKey();
|
||||||
|
|
||||||
return PartialPlay::getPartialPlayPercentage($player);
|
return PartialPlay::getPartialPlayPercentage($player);
|
||||||
},
|
},
|
||||||
$teamMembers
|
$teamMembers
|
||||||
@ -70,27 +73,24 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
|
|||||||
foreach ($localFactors as $currentFactor) {
|
foreach ($localFactors as $currentFactor) {
|
||||||
$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(fn ($currentPlayer) => (string) ($currentPlayer->getKey()), $team);
|
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable();
|
||||||
|
|
||||||
$teamMemberNames = \implode(', ', $memberNames);
|
|
||||||
|
|
||||||
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable('Team[' . $teamMemberNames . "]'s performance");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Layers;
|
namespace DNW\Skills\TrueSkill\Layers;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\ScheduleStep;
|
use DNW\Skills\FactorGraphs\ScheduleStep;
|
||||||
@ -32,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;
|
||||||
}
|
}
|
||||||
@ -51,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(
|
||||||
fn ($prior) => new ScheduleStep('Prior to Skill Step', $prior, 0),
|
//Prior to Skill Step
|
||||||
|
static fn($prior): ScheduleStep => new ScheduleStep($prior, 0),
|
||||||
$localFactors
|
$localFactors
|
||||||
),
|
)
|
||||||
'All priors'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Layers;
|
namespace DNW\Skills\TrueSkill\Layers;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\KeyedVariable;
|
use DNW\Skills\FactorGraphs\KeyedVariable;
|
||||||
|
use DNW\Skills\FactorGraphs\Variable;
|
||||||
use DNW\Skills\FactorGraphs\ScheduleStep;
|
use DNW\Skills\FactorGraphs\ScheduleStep;
|
||||||
use DNW\Skills\Numerics\BasicMath;
|
use DNW\Skills\Numerics\BasicMath;
|
||||||
use DNW\Skills\TrueSkill\Factors\GaussianLikelihoodFactor;
|
use DNW\Skills\TrueSkill\Factors\GaussianLikelihoodFactor;
|
||||||
@ -12,26 +15,29 @@ 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 = [];
|
||||||
|
|
||||||
foreach ($currentTeam as $playerSkillVariable) {
|
/**
|
||||||
$localPlayerSkillVariable = $playerSkillVariable;
|
* @var Variable $playerSkillVar
|
||||||
$currentPlayer = $localPlayerSkillVariable->getKey();
|
*/
|
||||||
|
foreach ($currentTeam as $playerSkillVar) {
|
||||||
|
$localPlayerSkillVar = $playerSkillVar;
|
||||||
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createLikelihood(KeyedVariable $playerSkill, KeyedVariable $playerPerformance): GaussianLikelihoodFactor
|
private function createLikelihood(Variable $playerSkill, Variable $playerPerformance): GaussianLikelihoodFactor
|
||||||
{
|
{
|
||||||
return new GaussianLikelihoodFactor(
|
return new GaussianLikelihoodFactor(
|
||||||
BasicMath::square($this->getParentFactorGraph()->getGameInfo()->getBeta()),
|
BasicMath::square($this->getParentFactorGraph()->getGameInfo()->getBeta()),
|
||||||
@ -42,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(
|
||||||
fn ($likelihood) => 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'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,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(
|
||||||
fn ($likelihood) => new ScheduleStep('name', $likelihood, 1),
|
static fn($likelihood): ScheduleStep => new ScheduleStep($likelihood, 1),
|
||||||
$localFactors
|
$localFactors
|
||||||
),
|
)
|
||||||
'All skill to performance sending'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Layers;
|
namespace DNW\Skills\TrueSkill\Layers;
|
||||||
|
|
||||||
use DNW\Skills\TrueSkill\DrawMargin;
|
use DNW\Skills\TrueSkill\DrawMargin;
|
||||||
@ -9,7 +11,7 @@ use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
|
|||||||
|
|
||||||
class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
|
class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
|
||||||
{
|
{
|
||||||
private float $epsilon;
|
private readonly float $epsilon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int[] $teamRanks
|
* @param int[] $teamRanks
|
||||||
@ -26,7 +28,7 @@ class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
|
|||||||
$inputVarGroups = $this->getInputVariablesGroups();
|
$inputVarGroups = $this->getInputVariablesGroups();
|
||||||
$inputVarGroupsCount = count($inputVarGroups);
|
$inputVarGroupsCount = count($inputVarGroups);
|
||||||
|
|
||||||
for ($i = 0; $i < $inputVarGroupsCount; $i++) {
|
for ($i = 0; $i < $inputVarGroupsCount; ++$i) {
|
||||||
$isDraw = ($this->teamRanks[$i] == $this->teamRanks[$i + 1]);
|
$isDraw = ($this->teamRanks[$i] == $this->teamRanks[$i + 1]);
|
||||||
$teamDifference = $inputVarGroups[$i][0];
|
$teamDifference = $inputVarGroups[$i][0];
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Layers;
|
namespace DNW\Skills\TrueSkill\Layers;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\Variable;
|
use DNW\Skills\FactorGraphs\Variable;
|
||||||
@ -13,7 +15,7 @@ class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorG
|
|||||||
$inputVariablesGroupsCount = count($inputVariablesGroups);
|
$inputVariablesGroupsCount = count($inputVariablesGroups);
|
||||||
$outputVariablesGroup = &$this->getOutputVariablesGroups();
|
$outputVariablesGroup = &$this->getOutputVariablesGroups();
|
||||||
|
|
||||||
for ($i = 0; $i < $inputVariablesGroupsCount - 1; $i++) {
|
for ($i = 0; $i < $inputVariablesGroupsCount - 1; ++$i) {
|
||||||
$strongerTeam = $inputVariablesGroups[$i][0];
|
$strongerTeam = $inputVariablesGroups[$i][0];
|
||||||
$weakerTeam = $inputVariablesGroups[$i + 1][0];
|
$weakerTeam = $inputVariablesGroups[$i + 1][0];
|
||||||
|
|
||||||
@ -30,15 +32,19 @@ class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorG
|
|||||||
Variable $strongerTeam,
|
Variable $strongerTeam,
|
||||||
Variable $weakerTeam,
|
Variable $weakerTeam,
|
||||||
Variable $output
|
Variable $output
|
||||||
): GaussianWeightedSumFactor {
|
): GaussianWeightedSumFactor
|
||||||
|
{
|
||||||
$teams = [$strongerTeam, $weakerTeam];
|
$teams = [$strongerTeam, $weakerTeam];
|
||||||
$weights = [1.0, -1.0];
|
$weights = [1.0, -1.0];
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill\Layers;
|
namespace DNW\Skills\TrueSkill\Layers;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\FactorGraphLayer;
|
use DNW\Skills\FactorGraphs\FactorGraphLayer;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill;
|
namespace DNW\Skills\TrueSkill;
|
||||||
|
|
||||||
use DNW\Skills\FactorGraphs\FactorGraph;
|
use DNW\Skills\FactorGraphs\FactorGraph;
|
||||||
@ -10,8 +12,10 @@ use DNW\Skills\GameInfo;
|
|||||||
use DNW\Skills\Numerics\GaussianDistribution;
|
use DNW\Skills\Numerics\GaussianDistribution;
|
||||||
use DNW\Skills\Rating;
|
use DNW\Skills\Rating;
|
||||||
use DNW\Skills\Team;
|
use DNW\Skills\Team;
|
||||||
|
use DNW\Skills\Player;
|
||||||
use DNW\Skills\RatingContainer;
|
use DNW\Skills\RatingContainer;
|
||||||
use DNW\Skills\FactorGraphs\FactorGraphLayer;
|
use DNW\Skills\FactorGraphs\FactorGraphLayer;
|
||||||
|
use DNW\Skills\FactorGraphs\KeyedVariable;
|
||||||
use DNW\Skills\TrueSkill\Layers\IteratedTeamDifferencesInnerLayer;
|
use DNW\Skills\TrueSkill\Layers\IteratedTeamDifferencesInnerLayer;
|
||||||
use DNW\Skills\TrueSkill\Layers\PlayerPerformancesToTeamPerformancesLayer;
|
use DNW\Skills\TrueSkill\Layers\PlayerPerformancesToTeamPerformancesLayer;
|
||||||
use DNW\Skills\TrueSkill\Layers\PlayerPriorValuesToSkillsLayer;
|
use DNW\Skills\TrueSkill\Layers\PlayerPriorValuesToSkillsLayer;
|
||||||
@ -24,20 +28,21 @@ class TrueSkillFactorGraph extends FactorGraph
|
|||||||
/**
|
/**
|
||||||
* @var FactorGraphLayer[] $layers
|
* @var FactorGraphLayer[] $layers
|
||||||
*/
|
*/
|
||||||
private array $layers;
|
private readonly array $layers;
|
||||||
|
|
||||||
private PlayerPriorValuesToSkillsLayer $priorLayer;
|
private readonly PlayerPriorValuesToSkillsLayer $priorLayer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param GameInfo $gameInfo Parameters for the game.
|
* @param GameInfo $gameInfo Parameters for the game.
|
||||||
* @param Team[] $teams A mapping of team players and their ratings.
|
* @param Team[] $teams A mapping of team players and their ratings.
|
||||||
* @param int[] $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2).
|
* @param int[] $teamRanks The ranks of the teams where 1 is first place. For a tie, repeat the number (e.g. 1, 2, 2).
|
||||||
*/
|
*/
|
||||||
public function __construct(private readonly GameInfo $gameInfo, array $teams, array $teamRanks)
|
public function __construct(private readonly GameInfo $gameInfo, array $teams, array $teamRanks)
|
||||||
{
|
{
|
||||||
|
parent::__construct();
|
||||||
$this->priorLayer = new PlayerPriorValuesToSkillsLayer($this, $teams);
|
$this->priorLayer = new PlayerPriorValuesToSkillsLayer($this, $teams);
|
||||||
$newFactory = new VariableFactory(
|
$newFactory = new VariableFactory(
|
||||||
fn () => GaussianDistribution::fromPrecisionMean(0, 0)
|
static fn(): GaussianDistribution => GaussianDistribution::fromPrecisionMean(0, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->setVariableFactory($newFactory);
|
$this->setVariableFactory($newFactory);
|
||||||
@ -60,11 +65,11 @@ class TrueSkillFactorGraph extends FactorGraph
|
|||||||
|
|
||||||
public function buildGraph(): void
|
public function buildGraph(): void
|
||||||
{
|
{
|
||||||
$lastOutput = null;
|
$lastOutput = NULL;
|
||||||
|
|
||||||
$layers = $this->layers;
|
$layers = $this->layers;
|
||||||
foreach ($layers as $currentLayer) {
|
foreach ($layers as $currentLayer) {
|
||||||
if ($lastOutput != null) {
|
if ($lastOutput != NULL) {
|
||||||
$currentLayer->setInputVariablesGroups($lastOutput);
|
$currentLayer->setInputVariablesGroups($lastOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +110,7 @@ class TrueSkillFactorGraph extends FactorGraph
|
|||||||
$layers = $this->layers;
|
$layers = $this->layers;
|
||||||
foreach ($layers as $currentLayer) {
|
foreach ($layers as $currentLayer) {
|
||||||
$currentPriorSchedule = $currentLayer->createPriorSchedule();
|
$currentPriorSchedule = $currentLayer->createPriorSchedule();
|
||||||
if ($currentPriorSchedule != null) {
|
if ($currentPriorSchedule != NULL) {
|
||||||
$fullSchedule[] = $currentPriorSchedule;
|
$fullSchedule[] = $currentPriorSchedule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,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
|
||||||
@ -129,7 +135,7 @@ class TrueSkillFactorGraph extends FactorGraph
|
|||||||
$priorLayerOutputVariablesGroups = $this->priorLayer->getOutputVariablesGroups();
|
$priorLayerOutputVariablesGroups = $this->priorLayer->getOutputVariablesGroups();
|
||||||
foreach ($priorLayerOutputVariablesGroups as $currentTeam) {
|
foreach ($priorLayerOutputVariablesGroups as $currentTeam) {
|
||||||
foreach ($currentTeam as $currentPlayer) {
|
foreach ($currentTeam as $currentPlayer) {
|
||||||
$localCurrentPlayer = $currentPlayer->getKey();
|
$localCurrentPlayer = ($currentPlayer instanceof KeyedVariable) ? $currentPlayer->getKey() : new Player("");
|
||||||
$newRating = new Rating(
|
$newRating = new Rating(
|
||||||
$currentPlayer->getValue()->getMean(),
|
$currentPlayer->getValue()->getMean(),
|
||||||
$currentPlayer->getValue()->getStandardDeviation()
|
$currentPlayer->getValue()->getStandardDeviation()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill;
|
namespace DNW\Skills\TrueSkill;
|
||||||
|
|
||||||
use DNW\Skills\Numerics\GaussianDistribution;
|
use DNW\Skills\Numerics\GaussianDistribution;
|
||||||
@ -100,34 +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 - $teamPerformanceDiffAbsValue)
|
||||||
($drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
|
||||||
*
|
*
|
||||||
GaussianDistribution::at(
|
GaussianDistribution::at(
|
||||||
$drawMargin - $teamPerformanceDifferenceAbsoluteValue
|
$drawMargin - $teamPerformanceDiffAbsValue
|
||||||
)
|
)
|
||||||
- (-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
- (-$drawMargin - $teamPerformanceDiffAbsValue)
|
||||||
*
|
*
|
||||||
GaussianDistribution::at(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)) / $denominator;
|
GaussianDistribution::at(-$drawMargin - $teamPerformanceDiffAbsValue)) / $denominator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill;
|
namespace DNW\Skills\TrueSkill;
|
||||||
|
|
||||||
use DNW\Skills\GameInfo;
|
use DNW\Skills\GameInfo;
|
||||||
@ -11,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.
|
||||||
@ -25,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,9 +35,9 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
|||||||
GameInfo $gameInfo,
|
GameInfo $gameInfo,
|
||||||
array $teams,
|
array $teams,
|
||||||
array $teamRanks
|
array $teamRanks
|
||||||
): RatingContainer {
|
): RatingContainer
|
||||||
|
{
|
||||||
// Basic argument checking
|
// Basic argument checking
|
||||||
Guard::argumentNotNull($gameInfo, 'gameInfo');
|
|
||||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||||
|
|
||||||
// Make sure things are in order
|
// Make sure things are in order
|
||||||
@ -140,7 +140,6 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
|||||||
*/
|
*/
|
||||||
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
|
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
|
||||||
{
|
{
|
||||||
Guard::argumentNotNull($gameInfo, 'gameInfo');
|
|
||||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||||
|
|
||||||
$team1 = $teams[0];
|
$team1 = $teams[0];
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\TrueSkill;
|
namespace DNW\Skills\TrueSkill;
|
||||||
|
|
||||||
use DNW\Skills\GameInfo;
|
use DNW\Skills\GameInfo;
|
||||||
@ -11,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;
|
||||||
|
|
||||||
@ -24,14 +25,14 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer
|
public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer
|
||||||
{
|
{
|
||||||
Guard::argumentNotNull($gameInfo, 'gameInfo');
|
|
||||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||||
|
|
||||||
RankSorter::sort($teams, $teamRanks);
|
RankSorter::sort($teams, $teamRanks);
|
||||||
@ -68,7 +69,8 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
|||||||
Team $selfTeam,
|
Team $selfTeam,
|
||||||
Team $otherTeam,
|
Team $otherTeam,
|
||||||
PairwiseComparison $selfToOtherTeamComparison
|
PairwiseComparison $selfToOtherTeamComparison
|
||||||
): void {
|
): void
|
||||||
|
{
|
||||||
$drawMargin = DrawMargin::getDrawMarginFromDrawProbability(
|
$drawMargin = DrawMargin::getDrawMarginFromDrawProbability(
|
||||||
$gameInfo->getDrawProbability(),
|
$gameInfo->getDrawProbability(),
|
||||||
$gameInfo->getBeta()
|
$gameInfo->getBeta()
|
||||||
@ -79,12 +81,12 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
|||||||
|
|
||||||
$totalPlayers = $selfTeam->count() + $otherTeam->count();
|
$totalPlayers = $selfTeam->count() + $otherTeam->count();
|
||||||
|
|
||||||
$meanGetter = fn (Rating $currentRating): float => $currentRating->getMean();
|
$meanGetter = static fn(Rating $currentRating): float => $currentRating->getMean();
|
||||||
|
|
||||||
$selfMeanSum = BasicMath::sum($selfTeam->getAllRatings(), $meanGetter);
|
$selfMeanSum = BasicMath::sum($selfTeam->getAllRatings(), $meanGetter);
|
||||||
$otherTeamMeanSum = BasicMath::sum($otherTeam->getAllRatings(), $meanGetter);
|
$otherTeamMeanSum = BasicMath::sum($otherTeam->getAllRatings(), $meanGetter);
|
||||||
|
|
||||||
$varianceGetter = fn (Rating $currentRating): float => BasicMath::square($currentRating->getStandardDeviation());
|
$varianceGetter = static fn(Rating $currentRating): float => BasicMath::square($currentRating->getStandardDeviation());
|
||||||
|
|
||||||
$c = sqrt(
|
$c = sqrt(
|
||||||
BasicMath::sum($selfTeam->getAllRatings(), $varianceGetter)
|
BasicMath::sum($selfTeam->getAllRatings(), $varianceGetter)
|
||||||
@ -123,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);
|
||||||
@ -137,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +148,6 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
|||||||
*/
|
*/
|
||||||
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
|
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
|
||||||
{
|
{
|
||||||
Guard::argumentNotNull($gameInfo, 'gameInfo');
|
|
||||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||||
|
|
||||||
// We've verified that there's just two teams
|
// We've verified that there's just two teams
|
||||||
@ -160,9 +161,9 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
|||||||
|
|
||||||
$betaSquared = BasicMath::square($gameInfo->getBeta());
|
$betaSquared = BasicMath::square($gameInfo->getBeta());
|
||||||
|
|
||||||
$meanGetter = fn (Rating $currentRating): float => $currentRating->getMean();
|
$meanGetter = static fn(Rating $currentRating): float => $currentRating->getMean();
|
||||||
|
|
||||||
$varianceGetter = fn (Rating $currentRating): float => BasicMath::square($currentRating->getStandardDeviation());
|
$varianceGetter = static fn(Rating $currentRating): float => BasicMath::square($currentRating->getStandardDeviation());
|
||||||
|
|
||||||
$team1MeanSum = BasicMath::sum($team1Ratings, $meanGetter);
|
$team1MeanSum = BasicMath::sum($team1Ratings, $meanGetter);
|
||||||
$team1StdDevSquared = BasicMath::sum($team1Ratings, $varianceGetter);
|
$team1StdDevSquared = BasicMath::sum($team1Ratings, $varianceGetter);
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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 test(): void
|
|
||||||
{
|
|
||||||
$stub = $this->createStub(Factor::class);
|
|
||||||
$ss = new ScheduleStep('dummy', $stub, 0);
|
|
||||||
$this->assertEquals('dummy', (string)$ss);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests\FactorGraphs;
|
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 test(): 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
tests/GameInfoTest.php
Normal file
27
tests/GameInfoTest.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DNW\Skills\Tests;
|
||||||
|
|
||||||
|
use DNW\Skills\GameInfo;
|
||||||
|
use DNW\Skills\Rating;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
|
||||||
|
#[CoversClass(GameInfo::class)]
|
||||||
|
#[UsesClass(Rating::class)]
|
||||||
|
class GameInfoTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testMembers(): void
|
||||||
|
{
|
||||||
|
$gi = new GameInfo(1, 2, 3, 4, 5);
|
||||||
|
$this->assertEquals(1, $gi->getInitialMean());
|
||||||
|
$this->assertEquals(2, $gi->getInitialStandardDeviation());
|
||||||
|
$this->assertEquals(3, $gi->getBeta());
|
||||||
|
$this->assertEquals(4, $gi->getDynamicsFactor());
|
||||||
|
$this->assertEquals(5, $gi->getDrawProbability());
|
||||||
|
$this->assertInstanceOf(Rating::class, $gi->getDefaultRating());
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +1,61 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests;
|
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 testArgumentNotNull(): void
|
public function testargumentIsValidIndexArgumentAbove(): void
|
||||||
{
|
|
||||||
$this->expectException(Exception::class);
|
|
||||||
$this->expectExceptionMessage('dummy can not be null');
|
|
||||||
Guard::argumentNotNull(null, "dummy");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testargumentIsValidIndex(): 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
37
tests/HashMapTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests\Numerics;
|
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
|
||||||
@ -13,4 +17,14 @@ class BasicMathTest extends TestCase
|
|||||||
$this->assertEquals(1.44, BasicMath::square(1.2));
|
$this->assertEquals(1.44, BasicMath::square(1.2));
|
||||||
$this->assertEquals(4, BasicMath::square(2));
|
$this->assertEquals(4, BasicMath::square(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSum(): void
|
||||||
|
{
|
||||||
|
$arr = [1, 1, 1, 1];
|
||||||
|
|
||||||
|
$func_return = static fn(float $f): float => $f;
|
||||||
|
$func_double = static fn(float $f): float => $f * 2;
|
||||||
|
$this->assertEquals(4, BasicMath::sum($arr, $func_return));
|
||||||
|
$this->assertEquals(8, BasicMath::sum($arr, $func_double));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests\Numerics;
|
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;
|
||||||
@ -39,9 +45,9 @@ class GaussianDistributionTest extends TestCase
|
|||||||
public function testMultiplication(): void
|
public function testMultiplication(): void
|
||||||
{
|
{
|
||||||
// I verified this against the formula at http://www.tina-vision.net/tina-knoppix/tina-memo/2003-003.pdf
|
// I verified this against the formula at http://www.tina-vision.net/tina-knoppix/tina-memo/2003-003.pdf
|
||||||
$standardNormal = new GaussianDistribution(0, 1);
|
$standardNormal = new GaussianDistribution(0, 1);
|
||||||
$shiftedGaussian = new GaussianDistribution(2, 3);
|
$shiftedGaussian = new GaussianDistribution(2, 3);
|
||||||
$product = GaussianDistribution::multiply($standardNormal, $shiftedGaussian);
|
$product = GaussianDistribution::multiply($standardNormal, $shiftedGaussian);
|
||||||
|
|
||||||
$this->assertEqualsWithDelta(0.2, $product->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(0.2, $product->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
$this->assertEqualsWithDelta(3.0 / sqrt(10), $product->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(3.0 / sqrt(10), $product->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
@ -61,15 +67,15 @@ class GaussianDistributionTest extends TestCase
|
|||||||
public function testDivision(): void
|
public function testDivision(): void
|
||||||
{
|
{
|
||||||
// Since the multiplication was worked out by hand, we use the same numbers but work backwards
|
// Since the multiplication was worked out by hand, we use the same numbers but work backwards
|
||||||
$product = new GaussianDistribution(0.2, 3.0 / sqrt(10));
|
$product = new GaussianDistribution(0.2, 3.0 / sqrt(10));
|
||||||
$standardNormal = new GaussianDistribution(0, 1);
|
$standardNormal = new GaussianDistribution(0, 1);
|
||||||
|
|
||||||
$productDividedByStandardNormal = GaussianDistribution::divide($product, $standardNormal);
|
$productDividedByStandardNormal = GaussianDistribution::divide($product, $standardNormal);
|
||||||
$this->assertEqualsWithDelta(2.0, $productDividedByStandardNormal->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(2.0, $productDividedByStandardNormal->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
$this->assertEqualsWithDelta(3.0, $productDividedByStandardNormal->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(3.0, $productDividedByStandardNormal->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
|
|
||||||
$product2 = new GaussianDistribution((4 * BasicMath::square(7) + 6 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7)), sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7)))));
|
$product2 = new GaussianDistribution((4 * BasicMath::square(7) + 6 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7)), sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7)))));
|
||||||
$m4s5 = new GaussianDistribution(4, 5);
|
$m4s5 = new GaussianDistribution(4, 5);
|
||||||
$product2DividedByM4S5 = GaussianDistribution::divide($product2, $m4s5);
|
$product2DividedByM4S5 = GaussianDistribution::divide($product2, $m4s5);
|
||||||
$this->assertEqualsWithDelta(6.0, $product2DividedByM4S5->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(6.0, $product2DividedByM4S5->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
$this->assertEqualsWithDelta(7.0, $product2DividedByM4S5->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(7.0, $product2DividedByM4S5->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
@ -86,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
|
||||||
@ -93,15 +114,30 @@ class GaussianDistributionTest extends TestCase
|
|||||||
// Verified with Ralf Herbrich's F# implementation
|
// Verified with Ralf Herbrich's F# implementation
|
||||||
$m1s2 = new GaussianDistribution(1, 2);
|
$m1s2 = new GaussianDistribution(1, 2);
|
||||||
$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
|
||||||
{
|
{
|
||||||
// Verified with Ralf Herbrich's F# implementation
|
// Verified with Ralf Herbrich's F# implementation
|
||||||
$standardNormal = new GaussianDistribution(0, 1);
|
$standardNormal = new GaussianDistribution(0, 1);
|
||||||
$absDiff = GaussianDistribution::absoluteDifference($standardNormal, $standardNormal);
|
$absDiff = GaussianDistribution::absoluteDifference($standardNormal, $standardNormal);
|
||||||
$this->assertEqualsWithDelta(0.0, $absDiff, GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(0.0, $absDiff, GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
|
|
||||||
$m1s2 = new GaussianDistribution(1, 2);
|
$m1s2 = new GaussianDistribution(1, 2);
|
||||||
@ -109,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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,112 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests\Numerics;
|
namespace DNW\Skills\Tests\Numerics;
|
||||||
|
|
||||||
use DNW\Skills\Numerics\IdentityMatrix;
|
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\Vector;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
|
#[CoversClass(Matrix::class)]
|
||||||
|
#[CoversClass(SquareMatrix::class)]
|
||||||
|
#[CoversClass(IdentityMatrix::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
|
||||||
|
{
|
||||||
|
$a = new SquareMatrix(1);
|
||||||
|
|
||||||
|
$this->assertEquals(1, $a->getDeterminant());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testTwoByTwoDeterminant(): void
|
public function testTwoByTwoDeterminant(): void
|
||||||
{
|
{
|
||||||
$a = new SquareMatrix(1, 2,
|
$a = new SquareMatrix(1, 2,
|
||||||
@ -108,9 +204,7 @@ class MatrixTest extends TestCase
|
|||||||
1, 2, 3,
|
1, 2, 3,
|
||||||
4, 5, 6);
|
4, 5, 6);
|
||||||
|
|
||||||
$d = Matrix::fromRowsColumns(2, 3,
|
$d = Matrix::fromColumnValues(2, 3, [[1, 4], [2, 5], [3,6]]);
|
||||||
1, 2, 3,
|
|
||||||
4, 5, 6);
|
|
||||||
|
|
||||||
$this->assertTrue($c->equals($d));
|
$this->assertTrue($c->equals($d));
|
||||||
|
|
||||||
@ -142,6 +236,21 @@ class MatrixTest extends TestCase
|
|||||||
$this->assertFalse($k->equals($l));
|
$this->assertFalse($k->equals($l));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAdd(): void
|
||||||
|
{
|
||||||
|
// From Wikipedia: http://en.wikipedia.org/wiki/Adjugate_matrix
|
||||||
|
$a = new SquareMatrix(1, 2,
|
||||||
|
3, 4);
|
||||||
|
|
||||||
|
$b = new SquareMatrix(4, 3,
|
||||||
|
2, 1);
|
||||||
|
|
||||||
|
$sum = Matrix::add($a, $b);
|
||||||
|
|
||||||
|
$result = new SquareMatrix(5, 5, 5, 5);
|
||||||
|
$this->assertEquals(TRUE, $result->equals($sum));
|
||||||
|
}
|
||||||
|
|
||||||
public function testAdjugate(): void
|
public function testAdjugate(): void
|
||||||
{
|
{
|
||||||
// From Wikipedia: http://en.wikipedia.org/wiki/Adjugate_matrix
|
// From Wikipedia: http://en.wikipedia.org/wiki/Adjugate_matrix
|
||||||
@ -195,6 +304,14 @@ class MatrixTest extends TestCase
|
|||||||
|
|
||||||
$ccInverse = Matrix::multiply($c, $cInverse);
|
$ccInverse = Matrix::multiply($c, $cInverse);
|
||||||
$this->assertTrue($identity3x3->equals($ccInverse));
|
$this->assertTrue($identity3x3->equals($ccInverse));
|
||||||
|
|
||||||
|
|
||||||
|
$e = new SquareMatrix(10);
|
||||||
|
|
||||||
|
$f = new SquareMatrix(0.1);
|
||||||
|
|
||||||
|
$eInverse = $e->getInverse();
|
||||||
|
$this->assertTrue($f->equals($eInverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testErrorDeterminant(): void
|
public function testErrorDeterminant(): void
|
||||||
@ -226,5 +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
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests\Numerics;
|
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
|
||||||
{
|
{
|
||||||
$this->expectException(Exception::class);
|
$this->expectException(Exception::class);
|
||||||
$range = new Range(10, 5);
|
new Range(10, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFactoryInclusiveInvalidParam(): void
|
public function testFactoryInclusiveInvalidParam(): void
|
||||||
{
|
{
|
||||||
$this->expectException(Exception::class);
|
$this->expectException(Exception::class);
|
||||||
$range = Range::inclusive(10, 5);
|
Range::inclusive(10, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNormalUse(): void
|
public function testNormalUse(): void
|
||||||
@ -25,5 +29,19 @@ class RangeTest extends TestCase
|
|||||||
$range = Range::inclusive(1, 10);
|
$range = Range::inclusive(1, 10);
|
||||||
$this->assertEquals(1, $range->getMin());
|
$this->assertEquals(1, $range->getMin());
|
||||||
$this->assertEquals(10, $range->getMax());
|
$this->assertEquals(10, $range->getMax());
|
||||||
|
$this->assertEquals(FALSE, $range->isInRange(0));
|
||||||
|
$this->assertEquals(TRUE, $range->isInRange(1));
|
||||||
|
$this->assertEquals(TRUE, $range->isInRange(2));
|
||||||
|
$this->assertEquals(TRUE, $range->isInRange(9));
|
||||||
|
$this->assertEquals(TRUE, $range->isInRange(10));
|
||||||
|
$this->assertEquals(FALSE, $range->isInRange(11));
|
||||||
|
|
||||||
|
$range = Range::atLeast(20);
|
||||||
|
$this->assertEquals(20, $range->getMin());
|
||||||
|
$this->assertEquals(PHP_INT_MAX, $range->getMax());
|
||||||
|
|
||||||
|
$range = Range::exactly(5);
|
||||||
|
$this->assertEquals(5, $range->getMin());
|
||||||
|
$this->assertEquals(5, $range->getMax());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
tests/PartialPlayTest.php
Normal file
27
tests/PartialPlayTest.php
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
24
tests/PlayerTest.php
Normal file
24
tests/PlayerTest.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DNW\Skills\Tests;
|
||||||
|
|
||||||
|
use DNW\Skills\Player;
|
||||||
|
use DNW\Skills\Guard;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
|
||||||
|
#[CoversClass(Player::class)]
|
||||||
|
#[UsesClass(Guard::class)]
|
||||||
|
class PlayerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testPlayerObjectGetterSetter(): void
|
||||||
|
{
|
||||||
|
$p = new Player('dummy', 0.1, 0.2);
|
||||||
|
$this->assertEquals('dummy', $p->getId());
|
||||||
|
$this->assertEquals(0.1, $p->getPartialPlayPercentage());
|
||||||
|
$this->assertEquals(0.2, $p->getPartialUpdatePercentage());
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests;
|
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
|
||||||
|
47
tests/RatingContainerTest.php
Normal file
47
tests/RatingContainerTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
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
|
||||||
@ -13,21 +22,18 @@ 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
|
||||||
{
|
{
|
||||||
$rating = new Rating(100, 10, 5);
|
$rating = new Rating(100, 10, 5);
|
||||||
$rating_prior = new Rating(100, 10, 5);
|
$ratingOld = new Rating(100, 10, 5);
|
||||||
$rating_new = new Rating(200, 10, 5);
|
$ratingNew = new Rating(200, 10, 5);
|
||||||
|
|
||||||
$rating_partial = $rating ->getPartialUpdate($rating_prior, $rating_new, 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
tests/SkillCalculatorTest.php
Normal file
29
tests/SkillCalculatorTest.php
Normal 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
69
tests/TeamTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
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;
|
||||||
@ -21,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace DNW\Skills\Tests\TrueSkill;
|
|
||||||
|
|
||||||
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
|
||||||
use DNW\Skills\SkillCalculatorSupportedOptions;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class FactorGraphTeamTrueSkillCalculatorTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testFactorGraphTrueSkillCalculator(): void
|
|
||||||
{
|
|
||||||
$calculator = new FactorGraphTrueSkillCalculator();
|
|
||||||
|
|
||||||
TrueSkillCalculatorTests::testAllTwoPlayerScenarios($this, $calculator);
|
|
||||||
TrueSkillCalculatorTests::testAllTwoTeamScenarios($this, $calculator);
|
|
||||||
TrueSkillCalculatorTests::testAllMultipleTeamScenarios($this, $calculator);
|
|
||||||
TrueSkillCalculatorTests::testPartialPlayScenarios($this, $calculator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMethodisSupported(): void
|
|
||||||
{
|
|
||||||
$calculator = new FactorGraphTrueSkillCalculator();
|
|
||||||
$this->assertEquals(true, $calculator->isSupported(SkillCalculatorSupportedOptions::PARTIAL_PLAY));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
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 PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversNothing;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
|
||||||
|
#[CoversClass(FactorGraphTrueSkillCalculator::class)]
|
||||||
|
#[UsesClass(\DNW\Skills\Numerics\Range::class)]
|
||||||
|
#[UsesClass(\DNW\Skills\PlayersRange::class)]
|
||||||
|
#[UsesClass(\DNW\Skills\SkillCalculator::class)]
|
||||||
|
#[UsesClass(\DNW\Skills\TeamsRange::class)]
|
||||||
class FactorGraphTrueSkillCalculatorTest extends TestCase
|
class FactorGraphTrueSkillCalculatorTest extends TestCase
|
||||||
{
|
{
|
||||||
|
#[CoversNothing]
|
||||||
public function testMicrosoftResearchExample(): void
|
public function testMicrosoftResearchExample(): void
|
||||||
{
|
{
|
||||||
$gameInfo = new GameInfo();
|
$gameInfo = new GameInfo();
|
||||||
@ -25,7 +37,7 @@ class FactorGraphTrueSkillCalculatorTest extends TestCase
|
|||||||
new Player("hillary"),
|
new Player("hillary"),
|
||||||
];
|
];
|
||||||
|
|
||||||
$teams = array();
|
$teams = [];
|
||||||
foreach ($players as $player) {
|
foreach ($players as $player) {
|
||||||
$teams[] = new Team($player, $gameInfo->getDefaultRating());
|
$teams[] = new Team($player, $gameInfo->getDefaultRating());
|
||||||
}
|
}
|
||||||
@ -51,4 +63,21 @@ class FactorGraphTrueSkillCalculatorTest extends TestCase
|
|||||||
$this->assertEqualsWithDelta($expected[$player->getId()][1], $rating->getStandardDeviation(), 0.001);
|
$this->assertEqualsWithDelta($expected[$player->getId()][1], $rating->getStandardDeviation(), 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[CoversNothing]
|
||||||
|
public function testFactorGraphTrueSkillCalculator(): void
|
||||||
|
{
|
||||||
|
$calculator = new FactorGraphTrueSkillCalculator();
|
||||||
|
|
||||||
|
TrueSkillCalculatorTests::testAllTwoPlayerScenarios($this, $calculator);
|
||||||
|
TrueSkillCalculatorTests::testAllTwoTeamScenarios($this, $calculator);
|
||||||
|
TrueSkillCalculatorTests::testAllMultipleTeamScenarios($this, $calculator);
|
||||||
|
TrueSkillCalculatorTests::testPartialPlayScenarios($this, $calculator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMethodisSupported(): void
|
||||||
|
{
|
||||||
|
$calculator = new FactorGraphTrueSkillCalculator();
|
||||||
|
$this->assertEquals(TRUE, $calculator->isSupported(SkillCalculator::PARTIAL_PLAY));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace DNW\Skills\Tests\TrueSkill;
|
namespace DNW\Skills\Tests\TrueSkill;
|
||||||
|
|
||||||
use DNW\Skills\GameInfo;
|
use DNW\Skills\GameInfo;
|
||||||
@ -7,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
|
||||||
@ -68,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.
|
||||||
|
|
||||||
@ -78,13 +78,13 @@ class TrueSkillCalculatorTests
|
|||||||
|
|
||||||
private static function twoPlayerTestNotDrawn(TestCase $testClass, SkillCalculator $calculator): void
|
private static function twoPlayerTestNotDrawn(TestCase $testClass, SkillCalculator $calculator): void
|
||||||
{
|
{
|
||||||
$player1 = new Player(1);
|
$player1 = new Player(1);
|
||||||
$player2 = new Player(2);
|
$player2 = new Player(2);
|
||||||
$gameInfo = new GameInfo();
|
$gameInfo = new GameInfo();
|
||||||
|
|
||||||
$team1 = new Team($player1, $gameInfo->getDefaultRating());
|
$team1 = new Team($player1, $gameInfo->getDefaultRating());
|
||||||
$team2 = new Team($player2, $gameInfo->getDefaultRating());
|
$team2 = new Team($player2, $gameInfo->getDefaultRating());
|
||||||
$teams = Teams::concat($team1, $team2);
|
$teams = [$team1, $team2];
|
||||||
|
|
||||||
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||||
|
|
||||||
@ -107,7 +107,8 @@ 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]);
|
||||||
|
|
||||||
$player1NewRating = $newRatings->getRating($player1);
|
$player1NewRating = $newRatings->getRating($player1);
|
||||||
@ -122,14 +123,14 @@ class TrueSkillCalculatorTests
|
|||||||
private static function twoPlayerChessTestNotDrawn(TestCase $testClass, SkillCalculator $calculator): void
|
private static function twoPlayerChessTestNotDrawn(TestCase $testClass, SkillCalculator $calculator): void
|
||||||
{
|
{
|
||||||
// Inspired by a real bug :-)
|
// Inspired by a real bug :-)
|
||||||
$player1 = new Player(1);
|
$player1 = new Player(1);
|
||||||
$player2 = new Player(2);
|
$player2 = new Player(2);
|
||||||
$gameInfo = new GameInfo(1200.0, 1200.0 / 3.0, 200.0, 1200.0 / 300.0, 0.03);
|
$gameInfo = new GameInfo(1200.0, 1200.0 / 3.0, 200.0, 1200.0 / 300.0, 0.03);
|
||||||
|
|
||||||
$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);
|
||||||
@ -150,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]);
|
||||||
|
|
||||||
@ -169,11 +170,9 @@ class TrueSkillCalculatorTests
|
|||||||
|
|
||||||
private static function oneOnTwoSimpleTest(TestCase $testClass, SkillCalculator $calculator): void
|
private static function oneOnTwoSimpleTest(TestCase $testClass, SkillCalculator $calculator): void
|
||||||
{
|
{
|
||||||
$player1 = new Player(1);
|
$player1 = new Player(1);
|
||||||
|
|
||||||
$gameInfo = new GameInfo();
|
$gameInfo = new GameInfo();
|
||||||
|
$team1 = new Team();
|
||||||
$team1 = new Team();
|
|
||||||
$team1->addPlayer($player1, $gameInfo->getDefaultRating());
|
$team1->addPlayer($player1, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
$player2 = new Player(2);
|
$player2 = new Player(2);
|
||||||
@ -183,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
|
||||||
@ -218,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
|
||||||
@ -248,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
|
||||||
@ -279,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
|
||||||
@ -309,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
|
||||||
@ -340,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
|
||||||
@ -380,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
|
||||||
@ -418,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
|
||||||
@ -430,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));
|
||||||
@ -462,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
|
||||||
@ -494,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
|
||||||
@ -534,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]);
|
||||||
|
|
||||||
@ -571,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
|
||||||
@ -597,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);
|
||||||
@ -624,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);
|
||||||
@ -652,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]);
|
||||||
|
|
||||||
@ -686,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);
|
||||||
@ -728,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);
|
||||||
@ -780,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);
|
||||||
@ -848,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,
|
||||||
@ -951,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
|
||||||
@ -975,7 +957,7 @@ class TrueSkillCalculatorTests
|
|||||||
{
|
{
|
||||||
$gameInfo = new GameInfo();
|
$gameInfo = new GameInfo();
|
||||||
|
|
||||||
$p1 = new Player(1);
|
$p1 = new Player(1);
|
||||||
$team1 = new Team($p1, $gameInfo->getDefaultRating());
|
$team1 = new Team($p1, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
$p2 = new Player(2, 0.0);
|
$p2 = new Player(2, 0.0);
|
||||||
@ -985,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);
|
||||||
|
54
tests/TrueSkill/TruncatedGaussianCorrectionFunctionsTest.php
Normal file
54
tests/TrueSkill/TruncatedGaussianCorrectionFunctionsTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user