mirror of
https://github.com/furyfire/trueskill.git
synced 2025-12-12 10:47:00 +01:00
Compare commits
53 Commits
7b09658c25
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 770c4dbcc6 | |||
| d7a60176bb | |||
| 60102910e3 | |||
| dd4ecff60a | |||
| 03a9a3987a | |||
| ec2e637315 | |||
| 1d06fa0b0a | |||
| a097fa7f71 | |||
| ad9d7f94de | |||
| 1725979b1c | |||
| ac7e3f5c2d | |||
| adc587ac0d | |||
| d729caac1f | |||
| bd9fccb87b | |||
| 55656b7889 | |||
| 38f332b223 | |||
| b38a9656eb | |||
| 3c617e9869 | |||
| 5a414b8307 | |||
| e2620fde53 | |||
| 22002891c5 | |||
| 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 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
.*/
|
||||
vendor
|
||||
vendor/
|
||||
.*.cache/
|
||||
*.phar
|
||||
.phpdoc/
|
||||
output/
|
||||
output/
|
||||
tools/
|
||||
12
.phive/phars.xml
Normal file
12
.phive/phars.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phive xmlns="https://phar.io/phive">
|
||||
<phar name="phpmd" version="^2.15.0" installed="2.15.0" location="./tools/phpmd" copy="false"/>
|
||||
<phar name="phpstan" version="^2.1.12" installed="2.1.32" location="./tools/phpstan" copy="false"/>
|
||||
<phar name="psalm" version="^7.0.0-beta6" installed="7.0.0-beta11" location="./tools/psalm" copy="false"/>
|
||||
<phar name="phpcs" version="^3.12.2" installed="3.13.5" location="./tools/phpcs" copy="false"/>
|
||||
<phar name="phpcbf" version="^3.12.2" installed="3.13.5" location="./tools/phpcbf" copy="false"/>
|
||||
<phar name="phpdocumentor" version="^3.7.1" installed="3.9.1" location="./tools/phpdocumentor" copy="false"/>
|
||||
<phar name="phpbench" version="^1.4.1" installed="1.4.3" location="./tools/phpbench" copy="false"/>
|
||||
<phar name="infection" version="^0.29.14" installed="0.29.14" location="./tools/infection" copy="false"/>
|
||||
<phar name="phpunit" version="^12.1.3" installed="12.4.4" location="./tools/phpunit" copy="false"/>
|
||||
</phive>
|
||||
32
.phpcs.xml
32
.phpcs.xml
@@ -6,6 +6,11 @@
|
||||
<file>tests/</file>
|
||||
<file>benchmark/</file>
|
||||
|
||||
<arg name="basepath" value="."/>
|
||||
<arg name="colors"/>
|
||||
<arg name="parallel" value="8"/>
|
||||
<arg name="report" value="emacs"/>
|
||||
<arg value="p"/>
|
||||
|
||||
<rule ref="PSR1">
|
||||
<exclude name="Generic.Files.LineLength"/>
|
||||
@@ -18,23 +23,19 @@
|
||||
<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>
|
||||
|
||||
@@ -50,4 +51,25 @@
|
||||
</property>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<rule ref="Generic.Formatting.SpaceAfterCast">
|
||||
<properties>
|
||||
<property name="spacing" value="0"/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<!-- Do not allow unreachable code. -->
|
||||
<rule ref="Squiz.PHP.NonExecutableCode"/>
|
||||
|
||||
<!-- Do not allow ambiguous conditions. -->
|
||||
<rule ref="Generic.CodeAnalysis.RequireExplicitBooleanOperatorPrecedence"/>
|
||||
|
||||
<!-- The testing bootstrap file uses string concats to stop IDEs seeing the class aliases -->
|
||||
<rule ref="Generic.Strings.UnnecessaryStringConcat" />
|
||||
|
||||
<!-- This test file specifically *needs* Windows line endings for testing purposes. -->
|
||||
<rule ref="Generic.Files.LineEndings.InvalidEOLChar" />
|
||||
|
||||
<!-- Avoid false positive with this sniff detecting itself -->
|
||||
<rule ref="Generic.Commenting.Todo"/>
|
||||
</ruleset>
|
||||
13
.phplint.yml
13
.phplint.yml
@@ -1,13 +0,0 @@
|
||||
path:
|
||||
- src/
|
||||
- tests/
|
||||
- benchmark/
|
||||
jobs: 10
|
||||
extensions:
|
||||
- php
|
||||
exclude:
|
||||
- vendor
|
||||
warning: true
|
||||
memory-limit: -1
|
||||
no-cache: true
|
||||
log-junit: "output/lint.xml"
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"php.version": "8.4"
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
pipeline:
|
||||
requirements:
|
||||
when:
|
||||
- event: [push, tag]
|
||||
steps:
|
||||
- name: requirements
|
||||
image: composer
|
||||
commands:
|
||||
- composer install --no-dev
|
||||
run:
|
||||
- name: run
|
||||
image: php:cli-bookworm
|
||||
commands:
|
||||
- php examples/3teams.php
|
||||
- php examples/basic.php
|
||||
dependencies:
|
||||
- name: test
|
||||
image: composer
|
||||
commands:
|
||||
- composer install
|
||||
- vendor/bin/phpunit tests
|
||||
- vendor/bin/phpunit tests --no-coverage
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: README
|
||||
lang: en
|
||||
...
|
||||
# PHP TrueSkill Implementation
|
||||
This is a PHP port of the Moserware.Skills project that's available at
|
||||
|
||||
@@ -10,9 +10,11 @@ use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
||||
use DNW\Skills\GameInfo;
|
||||
use DNW\Skills\Player;
|
||||
use DNW\Skills\Team;
|
||||
use DNW\Skills\Teams;
|
||||
|
||||
class BasicBench
|
||||
/**
|
||||
* Basic Benchmarks.
|
||||
*/
|
||||
final class BasicBench
|
||||
{
|
||||
/**
|
||||
* To benchmark performance when using TwoPlayerTrueSkillCalculator
|
||||
@@ -31,7 +33,7 @@ class BasicBench
|
||||
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$calculator = new TwoPlayerTrueSkillCalculator();
|
||||
|
||||
@@ -39,9 +41,11 @@ class BasicBench
|
||||
|
||||
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||
|
||||
$newRatings->getRating($p1)->getConservativeRating();
|
||||
$newRatings->getRating($p2)->getConservativeRating();
|
||||
|
||||
ob_start();
|
||||
echo $newRatings->getRating($p1)->getConservativeRating();
|
||||
echo $newRatings->getRating($p2)->getConservativeRating();
|
||||
ob_clean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +66,7 @@ class BasicBench
|
||||
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$calculator = new TwoTeamTrueSkillCalculator();
|
||||
|
||||
@@ -71,8 +75,10 @@ class BasicBench
|
||||
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||
|
||||
$newRatings->getRating($p1)->getConservativeRating();
|
||||
$newRatings->getRating($p2)->getConservativeRating();
|
||||
ob_start();
|
||||
echo $newRatings->getRating($p1)->getConservativeRating();
|
||||
echo $newRatings->getRating($p2)->getConservativeRating();
|
||||
ob_clean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +99,7 @@ class BasicBench
|
||||
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$calculator = new FactorGraphTrueSkillCalculator();
|
||||
|
||||
@@ -102,8 +108,10 @@ class BasicBench
|
||||
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||
|
||||
$newRatings->getRating($p1)->getConservativeRating();
|
||||
$newRatings->getRating($p2)->getConservativeRating();
|
||||
ob_start();
|
||||
echo $newRatings->getRating($p1)->getConservativeRating();
|
||||
echo $newRatings->getRating($p2)->getConservativeRating();
|
||||
ob_clean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +134,7 @@ class BasicBench
|
||||
$team3 = new Team($p3, $gameInfo->getDefaultRating());
|
||||
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
$teams = Teams::concat($team1, $team2, $team3);
|
||||
$teams = [$team1, $team2, $team3];
|
||||
|
||||
$calculator = new FactorGraphTrueSkillCalculator();
|
||||
|
||||
@@ -136,9 +144,11 @@ class BasicBench
|
||||
$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();
|
||||
ob_start();
|
||||
echo $newRatings->getRating($p1)->getConservativeRating();
|
||||
echo $newRatings->getRating($p2)->getConservativeRating();
|
||||
echo $newRatings->getRating($p3)->getConservativeRating();
|
||||
ob_clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
{
|
||||
"name": "dnw/php-trueskill",
|
||||
"description": "Trueskill implementation by Moserware updated for PHP 8.2",
|
||||
"keywords": ["trueskill", "matchmaking", "ranking", "skill", "elo"],
|
||||
"description": "Trueskill implementation by Moserware updated for PHP 8.4",
|
||||
"keywords": [
|
||||
"trueskill",
|
||||
"matchmaking",
|
||||
"ranking",
|
||||
"skill",
|
||||
"elo"
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
"php": "^8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"vimeo/psalm": "^5.21.1",
|
||||
"phpmetrics/phpmetrics": "^3.0-dev",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"psalm/plugin-phpunit": "^0.18.4",
|
||||
"rector/rector": "^1.0"
|
||||
"phpunit/phpunit": "^11.2",
|
||||
"rector/rector": "^1.0",
|
||||
"league/csv": "^9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -24,33 +28,36 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"document": "phpDocumentor",
|
||||
"benchmark": "phpbench run --report=default --output=build-artifact",
|
||||
"metrics": "vendor/bin/phpmetrics --config=phpmetrics.json",
|
||||
"test": "tools/phpunit",
|
||||
"document": "tools/phpdocumentor",
|
||||
"benchmark": "tools/phpbench run --report=default --output=build-artifact",
|
||||
"metrics": "phpmetrics --config=phpmetrics.yml",
|
||||
"lint": [
|
||||
"phplint",
|
||||
"phpcs"
|
||||
"tools/phpcbf",
|
||||
"tools/phpcs",
|
||||
"tools/phpmd src/,tests/,benchmark/,examples/ text phpmd.ruleset.xml"
|
||||
],
|
||||
"analyze": [
|
||||
"@analyze-phpstan",
|
||||
"@analyze-psalm",
|
||||
"@analyze-rector"
|
||||
"@analyze-phpstan",
|
||||
"@analyze-psalm",
|
||||
"@analyze-rector"
|
||||
],
|
||||
"analyze-phpstan":"vendor/bin/phpstan analyze --error-format=raw",
|
||||
"analyze-psalm": "vendor/bin/psalm --no-cache --show-info=true",
|
||||
"analyze-rector": "vendor/bin/rector --dry-run",
|
||||
"analyze-phpstan": "tools/phpstan analyze --error-format=raw",
|
||||
"analyze-psalm": "tools/psalm --show-info=true",
|
||||
"analyze-rector": "rector --dry-run",
|
||||
"html": [
|
||||
"pandoc -s README.md -o output/README.html",
|
||||
"pandoc -s docs/index.rst -o output/index.html"
|
||||
"pandoc --verbose -s README.md -o output/README.html",
|
||||
"pandoc --verbose -f rst -s docs/index.rst -o output/index.html --metadata=lang:en",
|
||||
"tools/phpcs --generator=MarkDown | pandoc --verbose -f markdown -s -o output/CodeStandard.html --metadata title='Code Standard' --metadata=lang:en"
|
||||
],
|
||||
"all": [
|
||||
"@test",
|
||||
"@document",
|
||||
"@benchmark",
|
||||
"@lint",
|
||||
"@analyze",
|
||||
"@document",
|
||||
"@metrics",
|
||||
"@html"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "library"
|
||||
}
|
||||
|
||||
2713
composer.lock
generated
2713
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
=============
|
||||
Documentation
|
||||
=============
|
||||
|
||||
This is a PHP port of the Moserware.Skills project that's available at
|
||||
|
||||
http://github.com/moserware/Skills
|
||||
@@ -17,9 +19,9 @@ 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).
|
||||
The TrueSkill ranking system is a skill based ranking system for Xbox Live developed at Microsoft Research. 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.
|
||||
|
||||
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).
|
||||
An improved version of the TrueSkill ranking system, named TrueSkill 2, launched with Gears of War 4 and was later incorporated into Halo 5.
|
||||
|
||||
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.
|
||||
|
||||
@@ -30,14 +32,19 @@ Links
|
||||
* `Project README <README.html>`_
|
||||
* `API Documentations <docs/>`_
|
||||
* `CodeCoverage <coverage/>`_
|
||||
* `Metrics <metrics/>`_
|
||||
* `Test report <test/index.html>`_
|
||||
* `Mutation testing <mutation/>`_
|
||||
* `Mutation testing <mutation/infection.html>`_
|
||||
* `Code metrics <metrics/index.html>`_
|
||||
* `Code Standard <CodeStandard.html>`_
|
||||
* `Benchmark <benchmark.html>`_
|
||||
|
||||
|
||||
Standard Tools
|
||||
--------------
|
||||
* PHP8.3
|
||||
* PHP8.4
|
||||
|
||||
Development Tools
|
||||
-------------------
|
||||
* PlantUML
|
||||
* GraphViz
|
||||
* Pandoc
|
||||
@@ -47,7 +54,5 @@ 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
|
||||
@@ -20,9 +20,9 @@ $team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||
$team3 = new Team($p3, $gameInfo->getDefaultRating());
|
||||
|
||||
|
||||
for($i = 0; $i < 5; ++$i) {
|
||||
for ($i = 0; $i < 5; ++$i) {
|
||||
echo "Iteration: " . $i . PHP_EOL;
|
||||
$teams = Teams::concat($team1, $team2, $team3);
|
||||
$teams = [$team1, $team2, $team3];
|
||||
|
||||
$calculator = new FactorGraphTrueSkillCalculator();
|
||||
|
||||
@@ -32,10 +32,7 @@ for($i = 0; $i < 5; ++$i) {
|
||||
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||
$team3 = new Team($p3, $newRatings->getRating($p3));
|
||||
|
||||
echo "P1: ". $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
||||
echo "P2: ". $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
||||
echo "P3: ". $newRatings->getRating($p3)->getConservativeRating() . PHP_EOL;
|
||||
echo "P1: " . $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
||||
echo "P2: " . $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
||||
echo "P3: " . $newRatings->getRating($p3)->getConservativeRating() . PHP_EOL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ $team1 = new Team($p1, $gameInfo->getDefaultRating());
|
||||
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||
|
||||
|
||||
for($i = 0; $i < 5; ++$i) {
|
||||
for ($i = 0; $i < 5; ++$i) {
|
||||
echo "Iteration: " . $i . PHP_EOL;
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$calculator = new TwoPlayerTrueSkillCalculator();
|
||||
|
||||
@@ -30,9 +30,6 @@ for($i = 0; $i < 5; ++$i) {
|
||||
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||
|
||||
echo "P1: ". $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
||||
echo "P2: ". $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
||||
echo "P1: " . $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
||||
echo "P2: " . $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
91
examples/motogp/goat.php
Normal file
91
examples/motogp/goat.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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
|
||||
$csv = Reader::createFromPath('motogp.csv', 'r');
|
||||
$csv->setDelimiter(',');
|
||||
$csv->setHeaderOffset(0);
|
||||
$csv->setEscape('');
|
||||
|
||||
//build a statement
|
||||
$statement = new Statement();
|
||||
$stmt = $statement->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": {
|
||||
"directories": [
|
||||
"src"
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"logs": {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{
|
||||
"runner.bootstrap": "vendor/autoload.php",
|
||||
"runner.path": "benchmark/",
|
||||
"runner.php_config": {
|
||||
"xdebug.mode": "none"
|
||||
},
|
||||
"runner.php_disable_ini": true,
|
||||
"runner.retry_threshold": 10,
|
||||
"runner.iterations": 10,
|
||||
"report.outputs": {
|
||||
@@ -12,6 +10,5 @@
|
||||
"path": "output/benchmark.html",
|
||||
"title": "Benchmarking"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
</source>
|
||||
</api>
|
||||
</version>
|
||||
<!--setting name="graphs.enabled" value="true"/-->
|
||||
<setting name="graphs.enabled" value="true"/>
|
||||
</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>
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"composer": true,
|
||||
"includes": [
|
||||
"src"
|
||||
],
|
||||
"excludes": [
|
||||
"tests"
|
||||
],
|
||||
"report": {
|
||||
"html": "output/metrics/",
|
||||
"json": "output/metrics/report.json"
|
||||
},
|
||||
"plugins": {
|
||||
"git": {
|
||||
"binary": "git"
|
||||
},
|
||||
"junit": {
|
||||
"file": "output/test.xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
18
phpmetrics.yml
Normal file
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"
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false" displayDetailsOnTestsThatTriggerWarnings="true">
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" cacheDirectory=".phpunit.cache" backupStaticProperties="false" displayDetailsOnTestsThatTriggerWarnings="true" beStrictAboutCoverageMetadata="true" requireCoverageMetadata="true">
|
||||
<testsuites>
|
||||
<testsuite name="PHPSkills Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
|
||||
@@ -12,11 +12,5 @@
|
||||
<directory name="src"/>
|
||||
<directory name="tests"/>
|
||||
<directory name="benchmark"/>
|
||||
<ignoreFiles>
|
||||
<directory name="vendor"/>
|
||||
</ignoreFiles>
|
||||
</projectFiles>
|
||||
<plugins>
|
||||
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
|
||||
</plugins>
|
||||
</psalm>
|
||||
|
||||
@@ -8,26 +8,23 @@ use DNW\Skills\Guard;
|
||||
use DNW\Skills\HashMap;
|
||||
use Exception;
|
||||
|
||||
abstract class Factor implements \Stringable
|
||||
abstract class Factor
|
||||
{
|
||||
/**
|
||||
* @var Message[] $messages
|
||||
*/
|
||||
private array $messages = [];
|
||||
|
||||
private readonly HashMap $messageToVariableBinding;
|
||||
|
||||
private readonly string $name;
|
||||
private readonly HashMap $msgToVariableBinding;
|
||||
|
||||
/**
|
||||
* @var Variable[] $variables
|
||||
*/
|
||||
private array $variables = [];
|
||||
|
||||
protected function __construct(string $name)
|
||||
protected function __construct()
|
||||
{
|
||||
$this->name = 'Factor[' . $name . ']';
|
||||
$this->messageToVariableBinding = new HashMap();
|
||||
$this->msgToVariableBinding = new HashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,14 +70,14 @@ abstract class Factor implements \Stringable
|
||||
{
|
||||
Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
|
||||
$message = $this->messages[$messageIndex];
|
||||
$variable = $this->messageToVariableBinding->getValue($message);
|
||||
$variable = $this->msgToVariableBinding->getValue($message);
|
||||
|
||||
return $this->updateMessageVariable($message, $variable);
|
||||
}
|
||||
|
||||
protected function updateMessageVariable(Message $message, Variable $variable): float
|
||||
{
|
||||
throw new Exception();
|
||||
throw new Exception("Must override updateMessageVariable(" . $message::class . ", " . $variable::class . ")");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +85,7 @@ abstract class Factor implements \Stringable
|
||||
*/
|
||||
public function resetMarginals(): void
|
||||
{
|
||||
$allValues = $this->messageToVariableBinding->getAllValues();
|
||||
$allValues = $this->msgToVariableBinding->getAllValues();
|
||||
foreach ($allValues as $currentVariable) {
|
||||
$currentVariable->resetToPrior();
|
||||
}
|
||||
@@ -104,7 +101,7 @@ abstract class Factor implements \Stringable
|
||||
Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
|
||||
|
||||
$message = $this->messages[$messageIndex];
|
||||
$variable = $this->messageToVariableBinding->getValue($message);
|
||||
$variable = $this->msgToVariableBinding->getValue($message);
|
||||
|
||||
return $this->sendMessageVariable($message, $variable);
|
||||
}
|
||||
@@ -115,15 +112,10 @@ abstract class Factor implements \Stringable
|
||||
|
||||
protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message
|
||||
{
|
||||
$this->messageToVariableBinding->setValue($message, $variable);
|
||||
$this->msgToVariableBinding->setValue($message, $variable);
|
||||
$this->messages[] = $message;
|
||||
$this->variables[] = $variable;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ abstract class FactorGraphLayer
|
||||
/**
|
||||
* @var array<int,array<int,Variable>>
|
||||
*/
|
||||
private array $outputVariablesGroups = [];
|
||||
private array $outputVarGroups = [];
|
||||
|
||||
/**
|
||||
* @var array<int,array<int,Variable>>
|
||||
*/
|
||||
private array $inputVariablesGroups = [];
|
||||
|
||||
protected function __construct(private readonly TrueSkillFactorGraph $parentFactorGraph)
|
||||
public function __construct(private readonly TrueSkillFactorGraph $parentFactorGraph)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ abstract class FactorGraphLayer
|
||||
*/
|
||||
public function &getOutputVariablesGroups(): array
|
||||
{
|
||||
return $this->outputVariablesGroups;
|
||||
return $this->outputVarGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,9 +70,9 @@ abstract class FactorGraphLayer
|
||||
/**
|
||||
* @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
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace DNW\Skills\FactorGraphs;
|
||||
/**
|
||||
* Helper class for computing the factor graph's normalization constant.
|
||||
*/
|
||||
class FactorList
|
||||
final class FactorList
|
||||
{
|
||||
/**
|
||||
* @var Factor[] $list
|
||||
@@ -26,16 +26,16 @@ class FactorList
|
||||
$listCount = count($this->list);
|
||||
|
||||
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) {
|
||||
$sumLogZ += $f->sendMessageIndex($j);
|
||||
$sumLogZ += (float)$factor->sendMessageIndex($j);
|
||||
}
|
||||
}
|
||||
|
||||
$sumLogS = 0;
|
||||
$sumLogS = 0.0;
|
||||
|
||||
foreach ($list as &$currentFactor) {
|
||||
$sumLogS += $currentFactor->getLogNormalization();
|
||||
|
||||
@@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
class KeyedVariable extends Variable
|
||||
final class KeyedVariable extends Variable
|
||||
{
|
||||
public function __construct(private readonly mixed $key, string $name, mixed $prior)
|
||||
public function __construct(private readonly mixed $key, mixed $prior)
|
||||
{
|
||||
parent::__construct($name, $prior);
|
||||
parent::__construct($prior);
|
||||
}
|
||||
|
||||
public function getKey(): mixed
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
use DNW\Skills\Numerics\GaussianDistribution;
|
||||
|
||||
class Message implements \Stringable
|
||||
final class Message
|
||||
{
|
||||
public function __construct(private GaussianDistribution $value, private readonly string $name)
|
||||
public function __construct(private GaussianDistribution $value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -21,9 +21,4 @@ class Message implements \Stringable
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
abstract class Schedule implements \Stringable
|
||||
abstract class Schedule
|
||||
{
|
||||
protected function __construct(private readonly string $name)
|
||||
{
|
||||
}
|
||||
|
||||
abstract public function visit(int $depth = -1, int $maxDepth = 0): float;
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
class ScheduleLoop extends Schedule
|
||||
final class ScheduleLoop extends Schedule
|
||||
{
|
||||
public function __construct(string $name, private readonly Schedule $scheduleToLoop, private readonly float $maxDelta)
|
||||
public function __construct(private readonly Schedule $scheduleToLoop, private readonly float $maxDelta)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function visit(int $depth = -1, int $maxDepth = 0): float
|
||||
{
|
||||
$delta = $this->scheduleToLoop->visit($depth + 1, $maxDepth);
|
||||
|
||||
@@ -4,16 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
class ScheduleSequence extends Schedule
|
||||
final class ScheduleSequence extends Schedule
|
||||
{
|
||||
/**
|
||||
* @param Schedule[] $schedules
|
||||
*/
|
||||
public function __construct(string $name, private readonly array $schedules)
|
||||
public function __construct(private readonly array $schedules)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function visit(int $depth = -1, int $maxDepth = 0): float
|
||||
{
|
||||
$maxDelta = 0;
|
||||
|
||||
@@ -4,17 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
class ScheduleStep extends Schedule
|
||||
final class ScheduleStep extends Schedule
|
||||
{
|
||||
public function __construct(string $name, private readonly Factor $factor, private readonly int $index)
|
||||
public function __construct(private readonly Factor $factor, private readonly int $index)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function visit(int $depth = -1, int $maxDepth = 0): float
|
||||
{
|
||||
$currentFactor = $this->factor;
|
||||
|
||||
return $currentFactor->updateMessageIndex($this->index);
|
||||
return $this->factor->updateMessageIndex($this->index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,12 @@ namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
use DNW\Skills\Numerics\GaussianDistribution;
|
||||
|
||||
class Variable implements \Stringable
|
||||
class Variable
|
||||
{
|
||||
private readonly string $name;
|
||||
|
||||
private mixed $value;
|
||||
|
||||
public function __construct(string $name, private GaussianDistribution $prior)
|
||||
public function __construct(private readonly GaussianDistribution $prior)
|
||||
{
|
||||
$this->name = 'Variable[' . $name . ']';
|
||||
$this->resetToPrior();
|
||||
}
|
||||
|
||||
@@ -32,9 +29,4 @@ class Variable implements \Stringable
|
||||
{
|
||||
$this->value = $this->prior;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\FactorGraphs;
|
||||
|
||||
class VariableFactory
|
||||
final readonly class VariableFactory
|
||||
{
|
||||
public function __construct(private \Closure $variablePriorInitializer)
|
||||
public function __construct(private \Closure $varPriorInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
public function createBasicVariable(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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,30 +7,30 @@ namespace DNW\Skills;
|
||||
/**
|
||||
* Parameters about the game for calculating the TrueSkill.
|
||||
*/
|
||||
class GameInfo
|
||||
final readonly class GameInfo
|
||||
{
|
||||
/**
|
||||
* Default initial mean / 6
|
||||
*/
|
||||
private const DEFAULT_BETA = 4.1666666666666666666666666666667;
|
||||
private const float DEFAULT_BETA = 4.1666666666666666666666666666667;
|
||||
|
||||
private const DEFAULT_DRAW_PROBABILITY = 0.10;
|
||||
private const float DEFAULT_DRAW_PROBABILITY = 0.10;
|
||||
|
||||
/**
|
||||
* Default initial mean / 300
|
||||
*/
|
||||
private const DEFAULT_DYNAMICS_FACTOR = 0.083333333333333333333333333333333;
|
||||
private const float DEFAULT_DYNAMICS_FACTOR = 0.083333333333333333333333333333333;
|
||||
|
||||
private const DEFAULT_INITIAL_MEAN = 25.0;
|
||||
private const float DEFAULT_INITIAL_MEAN = 25.0;
|
||||
|
||||
private const DEFAULT_INITIAL_STANDARD_DEVIATION = 8.3333333333333333333333333333333;
|
||||
private const float DEFAULT_INITIAL_STANDARD_DEVIATION = 8.3333333333333333333333333333333;
|
||||
|
||||
public function __construct(
|
||||
private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN,
|
||||
private readonly float $initialStandardDeviation = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
|
||||
private readonly float $beta = self::DEFAULT_BETA,
|
||||
private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
|
||||
private readonly float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
|
||||
private float $initialMean = self::DEFAULT_INITIAL_MEAN,
|
||||
private float $initialStdDev = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
|
||||
private float $beta = self::DEFAULT_BETA,
|
||||
private float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
|
||||
private float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
|
||||
)
|
||||
{
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class GameInfo
|
||||
|
||||
public function getInitialStandardDeviation(): float
|
||||
{
|
||||
return $this->initialStandardDeviation;
|
||||
return $this->initialStdDev;
|
||||
}
|
||||
|
||||
public function getBeta(): float
|
||||
@@ -62,6 +62,6 @@ class GameInfo
|
||||
|
||||
public function getDefaultRating(): Rating
|
||||
{
|
||||
return new Rating($this->initialMean, $this->initialStandardDeviation);
|
||||
return new Rating($this->initialMean, $this->initialStdDev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use Exception;
|
||||
*
|
||||
* @see http://www.moserware.com/2008/01/borrowing-ideas-from-3-interesting.html
|
||||
*/
|
||||
class Guard
|
||||
final class Guard
|
||||
{
|
||||
public static function argumentIsValidIndex(int $index, int $count, string $parameterName): void
|
||||
{
|
||||
@@ -23,7 +23,7 @@ class Guard
|
||||
public static function argumentInRangeInclusive(float $value, float $min, float $max, string $parameterName): void
|
||||
{
|
||||
if (($value < $min) || ($value > $max)) {
|
||||
throw new Exception($parameterName . ' is not in the valid range [' . $min . ', ' . $max . ']');
|
||||
throw new Exception($parameterName . ' is not in the valid range [' . (int)$min . ', ' . (int)$max . ']');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,28 +7,28 @@ namespace DNW\Skills;
|
||||
/**
|
||||
* Basic hashmap that supports object keys.
|
||||
*/
|
||||
class HashMap
|
||||
final class HashMap
|
||||
{
|
||||
/**
|
||||
* @var mixed[] $hashToValue
|
||||
* @var mixed[] $hashToValue Store the hash to value mapping.
|
||||
*/
|
||||
private array $hashToValue = [];
|
||||
|
||||
/**
|
||||
* @var mixed[] $hashToKey
|
||||
* @var mixed[] $hashToKey Store the hash to original key mapping.
|
||||
*/
|
||||
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];
|
||||
}
|
||||
|
||||
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->hashToValue[$hash] = $value;
|
||||
|
||||
@@ -55,13 +55,4 @@ class HashMap
|
||||
{
|
||||
return count($this->hashToKey);
|
||||
}
|
||||
|
||||
private static function getHash(string|object $key): string
|
||||
{
|
||||
if (is_object($key)) {
|
||||
return spl_object_hash($key);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,24 +10,24 @@ namespace DNW\Skills\Numerics;
|
||||
* @author Jeff Moser <jeff@moserware.com>
|
||||
* @copyright 2010 Jeff Moser
|
||||
*/
|
||||
class BasicMath
|
||||
final class BasicMath
|
||||
{
|
||||
/**
|
||||
* Squares the input (x^2 = x * x)
|
||||
* Squares the input (input^2 = input * input)
|
||||
*
|
||||
* @param $x Value to square (x)
|
||||
* @param $input Value to square (input)
|
||||
*
|
||||
* @return float The squared value (x^2)
|
||||
* @return float The squared value (input^2)
|
||||
*/
|
||||
public static function square(float $x): float
|
||||
public static function square(float $input): float
|
||||
{
|
||||
return $x * $x;
|
||||
return $input * $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sums the items in $itemsToSum
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return float The sum.
|
||||
|
||||
@@ -10,8 +10,12 @@ namespace DNW\Skills\Numerics;
|
||||
* @author Jeff Moser <jeff@moserware.com>
|
||||
* @copyright 2010 Jeff Moser
|
||||
*/
|
||||
class GaussianDistribution implements \Stringable
|
||||
final class GaussianDistribution
|
||||
{
|
||||
private const float DEFAULT_STANDARD_DEVIATION = 1.0;
|
||||
|
||||
private const float DEFAULT_MEAN = 0.0;
|
||||
|
||||
/**
|
||||
* Square Root 2π.
|
||||
* Precalculated constant for performance reasons
|
||||
@@ -19,7 +23,7 @@ class GaussianDistribution implements \Stringable
|
||||
*
|
||||
* @link https://www.wolframalpha.com/input?i=sqrt%282*pi%29 Source of value
|
||||
*/
|
||||
private const M_SQRT_2_PI = 2.5066282746310005024157652848110452530069867406099383166299235763;
|
||||
private const float M_SQRT_2_PI = 2.5066282746310005024157652848110452530069867406099383166299235763;
|
||||
|
||||
/**
|
||||
* Log of Square Root 2π.
|
||||
@@ -28,26 +32,30 @@ class GaussianDistribution implements \Stringable
|
||||
*
|
||||
* @link https://www.wolframalpha.com/input?i=log%28sqrt%282*pi%29%29 Source of value
|
||||
*/
|
||||
private const M_LOG_SQRT_2_PI = 0.9189385332046727417803297364056176398613974736377834128171515404;
|
||||
private const float M_LOG_SQRT_2_PI = 0.9189385332046727417803297364056176398613974736377834128171515404;
|
||||
|
||||
// precision and precisionMean are used because they make multiplying and dividing simpler
|
||||
// (the the accompanying math paper for more details)
|
||||
private float $precision;
|
||||
/**
|
||||
* Precision and precisionMean are used because they make multiplying and dividing simpler.
|
||||
*/
|
||||
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) {
|
||||
$this->precision = 1.0 / $this->variance;
|
||||
$this->precisionMean = $this->precision * $this->mean;
|
||||
} else {
|
||||
$this->precision = \INF;
|
||||
|
||||
$this->precisionMean = $this->mean == 0 ? 0 : \INF;
|
||||
}
|
||||
}
|
||||
@@ -164,10 +172,10 @@ class GaussianDistribution implements \Stringable
|
||||
$meanDifference = $numerator->mean - $denominator->mean;
|
||||
|
||||
return log($denominator->variance) + self::M_LOG_SQRT_2_PI - log($varianceDifference) / 2.0 +
|
||||
BasicMath::square($meanDifference) / (2 * $varianceDifference);
|
||||
BasicMath::square($meanDifference) / (2.0 * $varianceDifference);
|
||||
}
|
||||
|
||||
public static function at(float $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
|
||||
// 1 -(x-mean)^2 / (2*stdDev^2)
|
||||
@@ -175,25 +183,25 @@ class GaussianDistribution implements \Stringable
|
||||
// stdDev * sqrt(2*pi)
|
||||
|
||||
$multiplier = 1.0 / ($standardDeviation * self::M_SQRT_2_PI);
|
||||
$expPart = exp((-1.0 * BasicMath::square($x - $mean)) / (2 * BasicMath::square($standardDeviation)));
|
||||
$expPart = exp((-1.0 * BasicMath::square($var - $mean)) / (2.0 * BasicMath::square($standardDeviation)));
|
||||
|
||||
return $multiplier * $expPart;
|
||||
}
|
||||
|
||||
public static function cumulativeTo(float $x, float $mean = 0.0, float $standardDeviation = 1.0): float
|
||||
public static function cumulativeTo(float $var): float
|
||||
{
|
||||
$result = GaussianDistribution::errorFunctionCumulativeTo(-M_SQRT1_2 * $x);
|
||||
$result = GaussianDistribution::errorFunctionCumulativeTo(-M_SQRT1_2 * $var);
|
||||
|
||||
return 0.5 * $result;
|
||||
}
|
||||
|
||||
private static function errorFunctionCumulativeTo(float $x): float
|
||||
private static function errorFunctionCumulativeTo(float $var): float
|
||||
{
|
||||
// Derived from page 265 of Numerical Recipes 3rd Edition
|
||||
$z = abs($x);
|
||||
$z = abs($var);
|
||||
|
||||
$t = 2.0 / (2.0 + $z);
|
||||
$ty = 4 * $t - 2;
|
||||
$ty = 4.0 * $t - 2.0;
|
||||
|
||||
$coefficients = [
|
||||
-1.3026537197817094,
|
||||
@@ -238,13 +246,12 @@ class GaussianDistribution implements \Stringable
|
||||
|
||||
$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
|
||||
{
|
||||
// From page 265 of numerical recipes
|
||||
|
||||
if ($p >= 2.0) {
|
||||
return -100;
|
||||
}
|
||||
@@ -253,8 +260,8 @@ class GaussianDistribution implements \Stringable
|
||||
return 100;
|
||||
}
|
||||
|
||||
$pp = ($p < 1.0) ? $p : 2 - $p;
|
||||
$t = sqrt(-2 * log($pp / 2.0)); // Initial guess
|
||||
$pp = ($p < 1.0) ? $p : 2.0 - $p;
|
||||
$t = sqrt(-2.0 * log($pp / 2.0)); // Initial guess
|
||||
$x = -M_SQRT1_2 * ((2.30753 + $t * 0.27061) / (1.0 + $t * (0.99229 + $t * 0.04481)) - $t);
|
||||
|
||||
for ($j = 0; $j < 2; ++$j) {
|
||||
@@ -265,14 +272,9 @@ class GaussianDistribution implements \Stringable
|
||||
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
|
||||
return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2 * $x);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf('mean=%.4f standardDeviation=%.4f', $this->mean, $this->standardDeviation);
|
||||
return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2.0 * $var);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\Numerics;
|
||||
|
||||
class IdentityMatrix extends DiagonalMatrix
|
||||
final class IdentityMatrix extends DiagonalMatrix
|
||||
{
|
||||
public function __construct(int $rows)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ use Exception;
|
||||
|
||||
class Matrix
|
||||
{
|
||||
public const ERROR_TOLERANCE = 0.0000000001;
|
||||
public const float ERROR_TOLERANCE = 0.0000000001;
|
||||
|
||||
/**
|
||||
* @param array<int,array<int,float>> $matrixRowData
|
||||
@@ -60,13 +60,34 @@ class Matrix
|
||||
return $this->columnCount;
|
||||
}
|
||||
|
||||
public function getValue(int $row, int $col): float|int
|
||||
private function checkRowCol(int $row, int $col): void
|
||||
{
|
||||
if ($row < 0) {
|
||||
throw new Exception("Row negative");
|
||||
}
|
||||
|
||||
if ($row >= $this->getRowCount()) {
|
||||
throw new Exception("Row beyond range");
|
||||
}
|
||||
|
||||
if ($col < 0) {
|
||||
throw new Exception("Column negative");
|
||||
}
|
||||
|
||||
if ($col >= $this->getColumnCount()) {
|
||||
throw new Exception("Column beyond range");
|
||||
}
|
||||
}
|
||||
|
||||
public function getValue(int $row, int $col): float
|
||||
{
|
||||
$this->checkRowCol($row, $col);
|
||||
return $this->matrixRowData[$row][$col];
|
||||
}
|
||||
|
||||
public function setValue(int $row, int $col, float|int $value): void
|
||||
{
|
||||
$this->checkRowCol($row, $col);
|
||||
$this->matrixRowData[$row][$col] = $value;
|
||||
}
|
||||
|
||||
@@ -76,10 +97,10 @@ class Matrix
|
||||
$transposeMatrix = [];
|
||||
|
||||
$rowMatrixData = $this->matrixRowData;
|
||||
for ($currentRowTransposeMatrix = 0; $currentRowTransposeMatrix < $this->columnCount; ++$currentRowTransposeMatrix) {
|
||||
for ($currentColumnTransposeMatrix = 0; $currentColumnTransposeMatrix < $this->rowCount; ++$currentColumnTransposeMatrix) {
|
||||
$transposeMatrix[$currentRowTransposeMatrix][$currentColumnTransposeMatrix] =
|
||||
$rowMatrixData[$currentColumnTransposeMatrix][$currentRowTransposeMatrix];
|
||||
for ($curRowTransposeMx = 0; $curRowTransposeMx < $this->columnCount; ++$curRowTransposeMx) {
|
||||
for ($curColTransposeMx = 0; $curColTransposeMx < $this->rowCount; ++$curColTransposeMx) {
|
||||
$transposeMatrix[$curRowTransposeMx][$curColTransposeMx] =
|
||||
$rowMatrixData[$curColTransposeMx][$curRowTransposeMx];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +121,7 @@ class Matrix
|
||||
|
||||
if ($this->rowCount == 1) {
|
||||
// Really happy path :)
|
||||
return $this->matrixRowData[0][0];
|
||||
return $this->getValue(0, 0);
|
||||
}
|
||||
|
||||
if ($this->rowCount == 2) {
|
||||
@@ -109,10 +130,10 @@ class Matrix
|
||||
// | a b |
|
||||
// | c d |
|
||||
// The determinant is ad - bc
|
||||
$a = $this->matrixRowData[0][0];
|
||||
$b = $this->matrixRowData[0][1];
|
||||
$c = $this->matrixRowData[1][0];
|
||||
$d = $this->matrixRowData[1][1];
|
||||
$a = $this->getValue(0, 0);
|
||||
$b = $this->getValue(0, 1);
|
||||
$c = $this->getValue(1, 0);
|
||||
$d = $this->getValue(1, 1);
|
||||
|
||||
return $a * $d - $b * $c;
|
||||
}
|
||||
@@ -127,7 +148,7 @@ class Matrix
|
||||
|
||||
// I expand along the first row
|
||||
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
||||
$firstRowColValue = $this->matrixRowData[0][$currentColumn];
|
||||
$firstRowColValue = $this->getValue(0, $currentColumn);
|
||||
$cofactor = $this->getCofactor(0, $currentColumn);
|
||||
$itemToAdd = $firstRowColValue * $cofactor;
|
||||
$result += $itemToAdd;
|
||||
@@ -152,10 +173,10 @@ class Matrix
|
||||
// | d -b |
|
||||
// | -c a |
|
||||
|
||||
$a = $this->matrixRowData[0][0];
|
||||
$b = $this->matrixRowData[0][1];
|
||||
$c = $this->matrixRowData[1][0];
|
||||
$d = $this->matrixRowData[1][1];
|
||||
$a = $this->getValue(0, 0);
|
||||
$b = $this->getValue(0, 1);
|
||||
$c = $this->getValue(1, 0);
|
||||
$d = $this->getValue(1, 1);
|
||||
|
||||
return new SquareMatrix(
|
||||
$d,
|
||||
@@ -180,7 +201,7 @@ class Matrix
|
||||
public function getInverse(): Matrix|SquareMatrix
|
||||
{
|
||||
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:
|
||||
@@ -191,7 +212,7 @@ class Matrix
|
||||
return self::scalarMultiply($determinantInverse, $adjugate);
|
||||
}
|
||||
|
||||
public static function scalarMultiply(float|int $scalarValue, Matrix $matrix): Matrix
|
||||
public static function scalarMultiply(float $scalarValue, Matrix $matrix): Matrix
|
||||
{
|
||||
$rows = $matrix->getRowCount();
|
||||
$columns = $matrix->getColumnCount();
|
||||
@@ -244,7 +265,7 @@ class Matrix
|
||||
|
||||
for ($currentRow = 0; $currentRow < $resultRows; ++$currentRow) {
|
||||
for ($currentColumn = 0; $currentColumn < $resultColumns; ++$currentColumn) {
|
||||
$productValue = 0;
|
||||
$productValue = 0.0;
|
||||
|
||||
for ($vectorIndex = 0; $vectorIndex < $left->getColumnCount(); ++$vectorIndex) {
|
||||
$leftValue = $left->getValue($currentRow, $vectorIndex);
|
||||
@@ -262,6 +283,7 @@ class Matrix
|
||||
|
||||
private function getMinorMatrix(int $rowToRemove, int $columnToRemove): Matrix
|
||||
{
|
||||
$this->checkRowCol($rowToRemove, $columnToRemove);
|
||||
// See http://en.wikipedia.org/wiki/Minor_(linear_algebra)
|
||||
|
||||
// I'm going to use a horribly naïve algorithm... because I can :)
|
||||
@@ -281,7 +303,7 @@ class Matrix
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$actualRow][$actualCol] = $this->matrixRowData[$currentRow][$currentColumn];
|
||||
$result[$actualRow][$actualCol] = $this->getValue($currentRow, $currentColumn);
|
||||
|
||||
++$actualCol;
|
||||
}
|
||||
@@ -294,6 +316,7 @@ class Matrix
|
||||
|
||||
public function getCofactor(int $rowToRemove, int $columnToRemove): float
|
||||
{
|
||||
$this->checkRowCol($rowToRemove, $columnToRemove);
|
||||
// See http://en.wikipedia.org/wiki/Cofactor_(linear_algebra) for details
|
||||
// REVIEW: should things be reversed since I'm 0 indexed?
|
||||
$sum = $rowToRemove + $columnToRemove;
|
||||
@@ -316,7 +339,7 @@ class Matrix
|
||||
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
||||
$delta =
|
||||
abs(
|
||||
$this->matrixRowData[$currentRow][$currentColumn] -
|
||||
$this->getValue($currentRow, $currentColumn) -
|
||||
$otherMatrix->getValue($currentRow, $currentColumn)
|
||||
);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\Numerics;
|
||||
|
||||
class SquareMatrix extends Matrix
|
||||
final class SquareMatrix extends Matrix
|
||||
{
|
||||
public function __construct(float|int ...$allValues)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\Numerics;
|
||||
|
||||
class Vector extends Matrix
|
||||
final class Vector extends Matrix
|
||||
{
|
||||
/**
|
||||
* @param float[] $vectorValues
|
||||
|
||||
@@ -4,18 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills;
|
||||
|
||||
class PartialPlay
|
||||
final class PartialPlay
|
||||
{
|
||||
public static function getPartialPlayPercentage(Player $player): float
|
||||
{
|
||||
$partialPlayPercentage = $player->getPartialPlayPercentage();
|
||||
$partialPlayPct = $player->getPartialPlayPercentage();
|
||||
|
||||
// HACK to get around bug near 0
|
||||
$smallestPercentage = 0.0001;
|
||||
if ($partialPlayPercentage < $smallestPercentage) {
|
||||
return $smallestPercentage;
|
||||
$smallestPct = 0.0001;
|
||||
if ($partialPlayPct < $smallestPct) {
|
||||
return $smallestPct;
|
||||
}
|
||||
|
||||
return $partialPlayPercentage;
|
||||
return $partialPlayPct;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,33 +7,39 @@ namespace DNW\Skills;
|
||||
/**
|
||||
* Represents a player who has a Rating.
|
||||
*/
|
||||
class Player implements ISupportPartialPlay, ISupportPartialUpdate, \Stringable
|
||||
final readonly class Player implements ISupportPartialPlay, ISupportPartialUpdate
|
||||
{
|
||||
private const DEFAULT_PARTIAL_PLAY_PERCENTAGE = 1.0; // = 100% play time
|
||||
private const float DEFAULT_PARTIAL_PLAY_PERCENTAGE = 1.0; // = 100% play time
|
||||
|
||||
private const DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0;
|
||||
private const float DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0;
|
||||
|
||||
private readonly float $PartialPlayPercentage;
|
||||
/**
|
||||
* @var float The weight percentage to give this player when calculating a new rank.
|
||||
*/
|
||||
private float $PartialPlayPct;
|
||||
|
||||
private readonly float $PartialUpdatePercentage;
|
||||
/**
|
||||
* @var float Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.
|
||||
*/
|
||||
private float $PartialUpdatePct;
|
||||
|
||||
/**
|
||||
* Constructs a player.
|
||||
*
|
||||
* @param mixed $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 $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 string|int $Id The identifier for the player, such as a name.
|
||||
* @param float $partialPlayPct The weight percentage to give this player when calculating a new rank.
|
||||
* @param float $partialUpdatePct Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly mixed $Id,
|
||||
float $partialPlayPercentage = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE,
|
||||
float $partialUpdatePercentage = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE
|
||||
private mixed $Id,
|
||||
float $partialPlayPct = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE,
|
||||
float $partialUpdatePct = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE
|
||||
)
|
||||
{
|
||||
Guard::argumentInRangeInclusive($partialPlayPercentage, 0.0, 1.0, 'partialPlayPercentage');
|
||||
Guard::argumentInRangeInclusive($partialUpdatePercentage, 0, 1.0, 'partialUpdatePercentage');
|
||||
$this->PartialPlayPercentage = $partialPlayPercentage;
|
||||
$this->PartialUpdatePercentage = $partialUpdatePercentage;
|
||||
Guard::argumentInRangeInclusive($partialPlayPct, 0.0, 1.0, 'partialPlayPercentage');
|
||||
Guard::argumentInRangeInclusive($partialUpdatePct, 0, 1.0, 'partialUpdatePercentage');
|
||||
$this->PartialPlayPct = $partialPlayPct;
|
||||
$this->PartialUpdatePct = $partialUpdatePct;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,21 +53,18 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate, \Stringable
|
||||
/**
|
||||
* Indicates the percent of the time the player should be weighted where 0.0 indicates the player didn't play and 1.0 indicates the player played 100% of the time.
|
||||
*/
|
||||
#[\Override]
|
||||
public function getPartialPlayPercentage(): float
|
||||
{
|
||||
return $this->PartialPlayPercentage;
|
||||
return $this->PartialPlayPct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicated how much of a skill update a player should receive where 0.0 represents no update and 1.0 represents 100% of the update.
|
||||
*/
|
||||
#[\Override]
|
||||
public function getPartialUpdatePercentage(): float
|
||||
{
|
||||
return $this->PartialUpdatePercentage;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string)$this->Id;
|
||||
return $this->PartialUpdatePct;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@ namespace DNW\Skills;
|
||||
|
||||
use DNW\Skills\Numerics\Range;
|
||||
|
||||
class PlayersRange extends Range
|
||||
final class PlayersRange extends Range
|
||||
{
|
||||
protected static function create(int $min, int $max): static
|
||||
{
|
||||
return new static($min, $max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace DNW\Skills;
|
||||
/**
|
||||
* Helper class to sort ranks in non-decreasing order.
|
||||
*/
|
||||
class RankSorter
|
||||
final class RankSorter
|
||||
{
|
||||
/**
|
||||
* Performs an in-place sort of the items in according to the ranks in non-decreasing order.
|
||||
|
||||
@@ -9,18 +9,18 @@ use DNW\Skills\Numerics\GaussianDistribution;
|
||||
/**
|
||||
* Container for a player's rating.
|
||||
*/
|
||||
class Rating implements \Stringable
|
||||
final readonly class Rating
|
||||
{
|
||||
private const CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3;
|
||||
private const float CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER = 3;
|
||||
|
||||
/**
|
||||
* Constructs a rating.
|
||||
*
|
||||
* @param float $mean The statistical mean value of the rating (also known as mu).
|
||||
* @param float $standardDeviation The standard deviation of the rating (also known as s).
|
||||
* @param float|int $conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating.
|
||||
* @param float $conservativeStandardDeviationMultiplier optional The number of standardDeviations to subtract from the mean to achieve a conservative rating.
|
||||
*/
|
||||
public function __construct(private readonly float $mean, private readonly float $standardDeviation, private readonly float|int $conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER)
|
||||
public function __construct(private float $mean, private float $standardDeviation, private float $conservativeStandardDeviationMultiplier = self::CONSERVATIVE_STANDARD_DEVIATION_MULTIPLIER)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -73,9 +73,4 @@ class Rating implements \Stringable
|
||||
|
||||
return new Rating($partialPosteriorGaussion->getMean(), $partialPosteriorGaussion->getStandardDeviation(), $prior->conservativeStandardDeviationMultiplier);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf('mean=%.4f, standardDeviation=%.4f', $this->mean, $this->standardDeviation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace DNW\Skills;
|
||||
|
||||
class RatingContainer
|
||||
{
|
||||
/**
|
||||
* Link Player to a Rating using a hash map.
|
||||
*/
|
||||
private readonly HashMap $playerToRating;
|
||||
|
||||
public function __construct()
|
||||
|
||||
@@ -11,6 +11,12 @@ use Exception;
|
||||
*/
|
||||
abstract class SkillCalculator
|
||||
{
|
||||
public const int NONE = 0x00;
|
||||
|
||||
public const int PARTIAL_PLAY = 0x01;
|
||||
|
||||
public const int PARTIAL_UPDATE = 0x02;
|
||||
|
||||
protected function __construct(
|
||||
private readonly int $supportedOptions,
|
||||
private readonly TeamsRange $totalTeamsAllowed,
|
||||
@@ -46,7 +52,7 @@ abstract class SkillCalculator
|
||||
|
||||
public function isSupported(int $option): bool
|
||||
{
|
||||
return (bool)($this->supportedOptions & $option) == $option;
|
||||
return ($this->supportedOptions & $option) === $option;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +64,7 @@ abstract class SkillCalculator
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<\DNW\Skills\Team> $teams
|
||||
* @param Team[] $teams
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills;
|
||||
|
||||
class SkillCalculatorSupportedOptions
|
||||
{
|
||||
public const NONE = 0x00;
|
||||
|
||||
public const PARTIAL_PLAY = 0x01;
|
||||
|
||||
public const PARTIAL_UPDATE = 0x02;
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills;
|
||||
|
||||
class Team extends RatingContainer
|
||||
final class Team extends RatingContainer
|
||||
{
|
||||
public function __construct(Player $player = NULL, Rating $rating = NULL)
|
||||
public function __construct(?Player $player = NULL, ?Rating $rating = NULL)
|
||||
{
|
||||
parent::__construct();
|
||||
if (! $player instanceof Player) {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills;
|
||||
|
||||
class Teams
|
||||
{
|
||||
/**
|
||||
* @return Team[]
|
||||
*/
|
||||
public static function concat(Team ...$args/*variable arguments*/): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($args as $currentTeam) {
|
||||
$localCurrentTeam = $currentTeam;
|
||||
$result[] = $localCurrentTeam;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,6 @@ namespace DNW\Skills;
|
||||
|
||||
use DNW\Skills\Numerics\Range;
|
||||
|
||||
class TeamsRange extends Range
|
||||
final class TeamsRange extends Range
|
||||
{
|
||||
protected static function create(int $min, int $max): static
|
||||
{
|
||||
return new static($min, $max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ final class DrawMargin
|
||||
//
|
||||
// margin = inversecdf((draw probability + 1)/2) * sqrt(n1+n2) * beta
|
||||
// n1 and n2 are the number of players on each team
|
||||
return GaussianDistribution::inverseCumulativeTo(.5 * ($drawProbability + 1), 0, 1) * M_SQRT2 * $beta;
|
||||
return GaussianDistribution::inverseCumulativeTo(0.5 * ($drawProbability + 1.0), 0.0, 1.0) * M_SQRT2 * $beta;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use DNW\Skills\PartialPlay;
|
||||
use DNW\Skills\PlayersRange;
|
||||
use DNW\Skills\RankSorter;
|
||||
use DNW\Skills\SkillCalculator;
|
||||
use DNW\Skills\SkillCalculatorSupportedOptions;
|
||||
use DNW\Skills\Team;
|
||||
use DNW\Skills\TeamsRange;
|
||||
use DNW\Skills\RatingContainer;
|
||||
@@ -23,16 +22,17 @@ use DNW\Skills\Rating;
|
||||
/**
|
||||
* Calculates TrueSkill using a full factor graph.
|
||||
*/
|
||||
class FactorGraphTrueSkillCalculator extends SkillCalculator
|
||||
final class FactorGraphTrueSkillCalculator extends SkillCalculator
|
||||
{
|
||||
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}
|
||||
*/
|
||||
#[\Override]
|
||||
public function calculateNewRatings(
|
||||
GameInfo $gameInfo,
|
||||
array $teams,
|
||||
@@ -55,6 +55,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\Override]
|
||||
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
|
||||
{
|
||||
// We need to create the A matrix which is the player team assigments.
|
||||
@@ -172,7 +173,8 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
||||
|
||||
$currentColumn = 0;
|
||||
|
||||
for ($i = 0; $i < count($teamAssignmentsList) - 1; ++$i) {
|
||||
$teamCnt = count($teamAssignmentsList);
|
||||
for ($i = 0; $i < $teamCnt - 1; ++$i) {
|
||||
$currentTeam = $teamAssignmentsList[$i];
|
||||
|
||||
// Need to add in 0's for all the previous players, since they're not
|
||||
@@ -190,7 +192,7 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
||||
$nextTeam = $teamAssignmentsList[$i + 1];
|
||||
foreach ($nextTeam->getAllPlayers() as $nextTeamPlayer) {
|
||||
// Add a -1 * playing time to represent the difference
|
||||
$playerAssignments[$currentColumn][] = -1 * PartialPlay::getPartialPlayPercentage($nextTeamPlayer);
|
||||
$playerAssignments[$currentColumn][] = -1.0 * PartialPlay::getPartialPlayPercentage($nextTeamPlayer);
|
||||
--$rowsRemaining;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ abstract class GaussianFactor extends Factor
|
||||
/**
|
||||
* Sends the factor-graph message with and returns the log-normalization constant.
|
||||
*/
|
||||
#[\Override]
|
||||
protected function sendMessageVariable(Message $message, Variable $variable): float|int
|
||||
{
|
||||
$marginal = $variable->getValue();
|
||||
@@ -24,6 +25,7 @@ abstract class GaussianFactor extends Factor
|
||||
return $logZ;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function createVariableToMessageBinding(Variable $variable): Message
|
||||
{
|
||||
$newDistribution = GaussianDistribution::fromPrecisionMean(0, 0);
|
||||
@@ -32,7 +34,6 @@ abstract class GaussianFactor extends Factor
|
||||
$variable,
|
||||
new Message(
|
||||
$newDistribution,
|
||||
sprintf('message from %s to %s', (string)$this, (string)$variable)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,17 +14,15 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
|
||||
*
|
||||
* See the accompanying math paper for more details.
|
||||
*/
|
||||
class GaussianGreaterThanFactor extends GaussianFactor
|
||||
final class GaussianGreaterThanFactor extends GaussianFactor
|
||||
{
|
||||
private readonly float $epsilon;
|
||||
|
||||
public function __construct(float $epsilon, Variable $variable)
|
||||
public function __construct(private readonly float $epsilon, Variable $variable)
|
||||
{
|
||||
parent::__construct(\sprintf('%s > %.2f', (string)$variable, $epsilon));
|
||||
$this->epsilon = $epsilon;
|
||||
parent::__construct();
|
||||
$this->createVariableToMessageBinding($variable);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getLogNormalization(): float
|
||||
{
|
||||
$vars = $this->getVariables();
|
||||
@@ -45,6 +43,7 @@ class GaussianGreaterThanFactor extends GaussianFactor
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function updateMessageVariable(Message $message, Variable $variable): float
|
||||
{
|
||||
$oldMarginal = clone $variable->getValue();
|
||||
|
||||
@@ -15,22 +15,24 @@ use Exception;
|
||||
*
|
||||
* See the accompanying math paper for more details.
|
||||
*/
|
||||
class GaussianLikelihoodFactor extends GaussianFactor
|
||||
final class GaussianLikelihoodFactor extends GaussianFactor
|
||||
{
|
||||
private readonly float $precision;
|
||||
|
||||
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->createVariableToMessageBinding($variable1);
|
||||
$this->createVariableToMessageBinding($variable2);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getLogNormalization(): float
|
||||
{
|
||||
/**
|
||||
* @var KeyedVariable[]|mixed $vars
|
||||
* @var KeyedVariable[] $vars
|
||||
*/
|
||||
$vars = $this->getVariables();
|
||||
/**
|
||||
@@ -59,9 +61,9 @@ class GaussianLikelihoodFactor extends GaussianFactor
|
||||
$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
|
||||
|
||||
@@ -72,6 +74,7 @@ class GaussianLikelihoodFactor extends GaussianFactor
|
||||
return GaussianDistribution::subtract($newMarginal, $marginal1);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function updateMessageIndex(int $messageIndex): float
|
||||
{
|
||||
$messages = $this->getMessages();
|
||||
|
||||
@@ -13,22 +13,23 @@ use DNW\Skills\Numerics\GaussianDistribution;
|
||||
*
|
||||
* See the accompanying math paper for more details.
|
||||
*/
|
||||
class GaussianPriorFactor extends GaussianFactor
|
||||
final class GaussianPriorFactor extends GaussianFactor
|
||||
{
|
||||
private readonly GaussianDistribution $newMessage;
|
||||
|
||||
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));
|
||||
$newMessage = new Message(
|
||||
GaussianDistribution::fromPrecisionMean(0, 0),
|
||||
sprintf('message from %s to %s', (string)$this, (string)$variable)
|
||||
GaussianDistribution::fromPrecisionMean(0, 0)
|
||||
);
|
||||
|
||||
$this->createVariableToMessageBindingWithMessage($variable, $newMessage);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function updateMessageVariable(Message $message, Variable $variable): float
|
||||
{
|
||||
$oldMarginal = clone $variable->getValue();
|
||||
|
||||
@@ -16,12 +16,12 @@ use DNW\Skills\Numerics\GaussianDistribution;
|
||||
*
|
||||
* See the accompanying math paper for more details.
|
||||
*/
|
||||
class GaussianWeightedSumFactor extends GaussianFactor
|
||||
final class GaussianWeightedSumFactor extends GaussianFactor
|
||||
{
|
||||
/**
|
||||
* @var array<int[]> $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]
|
||||
@@ -42,7 +42,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
*/
|
||||
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
|
||||
// v_0 = a_1*v_1 + a_2*v_2 + ... + a_n * v_n
|
||||
@@ -58,9 +58,9 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
$variablesToSumLength = count($variablesToSum);
|
||||
|
||||
// 0..n-1
|
||||
$this->variableIndexOrdersForWeights[0] = [];
|
||||
$this->varIndexOrdersForWeights[0] = [];
|
||||
for ($i = 0; $i < ($variablesToSumLength + 1); ++$i) {
|
||||
$this->variableIndexOrdersForWeights[0][] = $i;
|
||||
$this->varIndexOrdersForWeights[0][] = $i;
|
||||
}
|
||||
|
||||
$variableWeightsLength = count($variableWeights);
|
||||
@@ -72,12 +72,12 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
|
||||
$weightsLength = $variableWeightsLength + 1;
|
||||
for ($weightsIndex = 1; $weightsIndex < $weightsLength; ++$weightsIndex) {
|
||||
$currentWeights = \array_fill(0, $variableWeightsLength, 0);
|
||||
$currentWeights = \array_fill(0, $variableWeightsLength, 0.0);
|
||||
|
||||
$variableIndices = \array_fill(0, $variableWeightsLength + 1, 0);
|
||||
$variableIndices = \array_fill(0, $variableWeightsLength + 1, 0.0);
|
||||
$variableIndices[0] = $weightsIndex;
|
||||
|
||||
$currentWeightsSquared = \array_fill(0, $variableWeightsLength, 0);
|
||||
$currentWeightsSquared = \array_fill(0, $variableWeightsLength, 0.0);
|
||||
|
||||
// keep a single variable to keep track of where we are in the array.
|
||||
// This is helpful since we skip over one of the spots
|
||||
@@ -90,9 +90,9 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
|
||||
$currentWeight = (-$variableWeights[$currentWeightSourceIndex] / $variableWeights[$weightsIndex - 1]);
|
||||
|
||||
if ($variableWeights[$weightsIndex - 1] == 0) {
|
||||
if ($variableWeights[$weightsIndex - 1] == 0.0) {
|
||||
// HACK: Getting around division by zero
|
||||
$currentWeight = 0;
|
||||
$currentWeight = 0.0;
|
||||
}
|
||||
|
||||
$currentWeights[$currentDestinationWeightIndex] = $currentWeight;
|
||||
@@ -112,8 +112,8 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
|
||||
$currentWeights[$currentDestinationWeightIndex] = $finalWeight;
|
||||
$currentWeightsSquared[$currentDestinationWeightIndex] = BasicMath::square($finalWeight);
|
||||
$variableIndices[count($variableWeights)] = 0;
|
||||
$this->variableIndexOrdersForWeights[] = $variableIndices;
|
||||
$variableIndices[count($variableWeights)] = 0.0;
|
||||
$this->varIndexOrdersForWeights[] = $variableIndices;
|
||||
|
||||
$this->weights[$weightsIndex] = $currentWeights;
|
||||
$this->weightsSquared[$weightsIndex] = $currentWeightsSquared;
|
||||
@@ -127,6 +127,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getLogNormalization(): float
|
||||
{
|
||||
$vars = $this->getVariables();
|
||||
@@ -160,9 +161,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
|
||||
// The math works out so that 1/newPrecision = sum of a_i^2 /marginalsWithoutMessages[i]
|
||||
$inverseOfNewPrecisionSum = 0.0;
|
||||
$anotherInverseOfNewPrecisionSum = 0.0;
|
||||
$weightedMeanSum = 0.0;
|
||||
$anotherWeightedMeanSum = 0.0;
|
||||
|
||||
$weightsSquaredLength = count($weightsSquared);
|
||||
|
||||
@@ -172,16 +171,11 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
$inverseOfNewPrecisionSum += $weightsSquared[$i] /
|
||||
($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]
|
||||
*
|
||||
($variables[$i + 1]->getValue()->getPrecisionMean() - $messages[$i + 1]->getValue()->getPrecisionMean())
|
||||
/
|
||||
($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
|
||||
|
||||
$anotherWeightedMeanSum += $weights[$i] * $diff->getPrecisionMean() / $diff->getPrecision();
|
||||
}
|
||||
|
||||
$newPrecision = 1.0 / $inverseOfNewPrecisionSum;
|
||||
@@ -204,6 +198,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
return $finalDiff;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function updateMessageIndex(int $messageIndex): float
|
||||
{
|
||||
$allMessages = $this->getMessages();
|
||||
@@ -214,7 +209,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
$updatedMessages = [];
|
||||
$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
|
||||
@@ -235,42 +230,4 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,16 @@ use DNW\Skills\TrueSkill\TruncatedGaussianCorrectionFunctions;
|
||||
*
|
||||
* See the accompanying math paper for more details.
|
||||
*/
|
||||
class GaussianWithinFactor extends GaussianFactor
|
||||
final class GaussianWithinFactor extends GaussianFactor
|
||||
{
|
||||
private readonly float $epsilon;
|
||||
|
||||
public function __construct(float $epsilon, Variable $variable)
|
||||
public function __construct(private readonly float $epsilon, Variable $variable)
|
||||
{
|
||||
parent::__construct(sprintf('%s <= %.2f', (string)$variable, $epsilon));
|
||||
$this->epsilon = $epsilon;
|
||||
//$epsilon <= $variable
|
||||
parent::__construct();
|
||||
$this->createVariableToMessageBinding($variable);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getLogNormalization(): float
|
||||
{
|
||||
/**
|
||||
@@ -48,6 +47,7 @@ class GaussianWithinFactor extends GaussianFactor
|
||||
return -GaussianDistribution::logProductNormalization($messageFromVariable, $message) + log($z);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function updateMessageVariable(Message $message, Variable $variable): float
|
||||
{
|
||||
$oldMarginal = clone $variable->getValue();
|
||||
|
||||
@@ -11,37 +11,40 @@ use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
|
||||
use Exception;
|
||||
|
||||
// The whole purpose of this is to do a loop on the bottom
|
||||
class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
||||
final class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
||||
{
|
||||
public function __construct(
|
||||
TrueSkillFactorGraph $parentGraph,
|
||||
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $TeamPerformancesToTeamPerformanceDifferencesLayer,
|
||||
private readonly TeamDifferencesComparisonLayer $TeamDifferencesComparisonLayer
|
||||
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $teamPerformancesToTeamPerformanceDifferencesLayer,
|
||||
private readonly TeamDifferencesComparisonLayer $teamDifferencesComparisonLayer
|
||||
)
|
||||
{
|
||||
parent::__construct($parentGraph);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getLocalFactors(): array
|
||||
{
|
||||
return array_merge(
|
||||
$this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(),
|
||||
$this->TeamDifferencesComparisonLayer->getLocalFactors()
|
||||
$this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(),
|
||||
$this->teamDifferencesComparisonLayer->getLocalFactors()
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function buildLayer(): void
|
||||
{
|
||||
$inputVariablesGroups = $this->getInputVariablesGroups();
|
||||
$this->TeamPerformancesToTeamPerformanceDifferencesLayer->setInputVariablesGroups($inputVariablesGroups);
|
||||
$this->TeamPerformancesToTeamPerformanceDifferencesLayer->buildLayer();
|
||||
$this->teamPerformancesToTeamPerformanceDifferencesLayer->setInputVariablesGroups($inputVariablesGroups);
|
||||
$this->teamPerformancesToTeamPerformanceDifferencesLayer->buildLayer();
|
||||
|
||||
$teamDifferencesOutputVariablesGroups = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getOutputVariablesGroups();
|
||||
$this->TeamDifferencesComparisonLayer->setInputVariablesGroups($teamDifferencesOutputVariablesGroups);
|
||||
$this->TeamDifferencesComparisonLayer->buildLayer();
|
||||
$teamDifferencesOutputVariablesGroups = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getOutputVariablesGroups();
|
||||
$this->teamDifferencesComparisonLayer->setInputVariablesGroups($teamDifferencesOutputVariablesGroups);
|
||||
$this->teamDifferencesComparisonLayer->buildLayer();
|
||||
}
|
||||
|
||||
public function createPriorSchedule(): ?ScheduleSequence
|
||||
#[\Override]
|
||||
public function createPriorSchedule(): ScheduleSequence
|
||||
{
|
||||
switch (count($this->getInputVariablesGroups())) {
|
||||
case 0:
|
||||
@@ -56,24 +59,24 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
||||
}
|
||||
|
||||
// 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];
|
||||
$lastDifferencesFactor = $localFactors[$totalTeamDifferences - 1];
|
||||
|
||||
//inner schedule
|
||||
return new ScheduleSequence(
|
||||
'inner schedule',
|
||||
[
|
||||
$loop,
|
||||
//teamPerformanceToPerformanceDifferenceFactors[0] @ 1
|
||||
new ScheduleStep(
|
||||
'teamPerformanceToPerformanceDifferenceFactors[0] @ 1',
|
||||
$firstDifferencesFactor,
|
||||
1
|
||||
),
|
||||
//teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = %d - 1] @ 2
|
||||
new ScheduleStep(
|
||||
sprintf('teamPerformanceToPerformanceDifferenceFactors[teamTeamDifferences = %d - 1] @ 2', $totalTeamDifferences),
|
||||
$lastDifferencesFactor,
|
||||
2
|
||||
),
|
||||
@@ -83,95 +86,96 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
||||
|
||||
private function createTwoTeamInnerPriorLoopSchedule(): ScheduleSequence
|
||||
{
|
||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||
$teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
|
||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||
$teamDifferencesComparisonLayerLocalFactors = $this->teamDifferencesComparisonLayer->getLocalFactors();
|
||||
|
||||
$firstPerfToTeamDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[0];
|
||||
$firstTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[0];
|
||||
$itemsToSequence = [
|
||||
//send team perf to perf differences
|
||||
new ScheduleStep(
|
||||
'send team perf to perf differences',
|
||||
$firstPerfToTeamDiff,
|
||||
0
|
||||
),
|
||||
//send to greater than or within factor
|
||||
new ScheduleStep(
|
||||
'send to greater than or within factor',
|
||||
$firstTeamDiffComparison,
|
||||
0
|
||||
),
|
||||
];
|
||||
|
||||
//loop of just two teams inner sequence
|
||||
return $this->scheduleSequence(
|
||||
$itemsToSequence,
|
||||
'loop of just two teams inner sequence'
|
||||
$itemsToSequence
|
||||
);
|
||||
}
|
||||
|
||||
private function createMultipleTeamInnerPriorLoopSchedule(): ScheduleLoop
|
||||
{
|
||||
$totalTeamDifferences = count($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
|
||||
$totalTeamDifferences = count($this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
|
||||
|
||||
$forwardScheduleList = [];
|
||||
|
||||
for ($i = 0; $i < $totalTeamDifferences - 1; ++$i) {
|
||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||
$teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
|
||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||
$teamDifferencesComparisonLayerLocalFactors = $this->teamDifferencesComparisonLayer->getLocalFactors();
|
||||
|
||||
$currentTeamPerfToTeamPerfDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$i];
|
||||
$currentTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[$i];
|
||||
|
||||
//current forward schedule piece $i
|
||||
$currentForwardSchedulePiece =
|
||||
$this->scheduleSequence(
|
||||
[
|
||||
//team perf to perf diff
|
||||
new ScheduleStep(
|
||||
sprintf('team perf to perf diff %d', $i),
|
||||
$currentTeamPerfToTeamPerfDiff,
|
||||
0
|
||||
),
|
||||
//greater than or within result factor
|
||||
new ScheduleStep(
|
||||
sprintf('greater than or within result factor %d', $i),
|
||||
$currentTeamDiffComparison,
|
||||
0
|
||||
),
|
||||
//'team perf to perf diff factors
|
||||
new ScheduleStep(
|
||||
sprintf('team perf to perf diff factors [%d], 2', $i),
|
||||
$currentTeamPerfToTeamPerfDiff,
|
||||
2
|
||||
),
|
||||
],
|
||||
sprintf('current forward schedule piece %d', $i)
|
||||
]
|
||||
);
|
||||
|
||||
$forwardScheduleList[] = $currentForwardSchedulePiece;
|
||||
}
|
||||
|
||||
$forwardSchedule = new ScheduleSequence('forward schedule', $forwardScheduleList);
|
||||
//forward schedule
|
||||
$forwardSchedule = new ScheduleSequence($forwardScheduleList);
|
||||
|
||||
$backwardScheduleList = [];
|
||||
|
||||
for ($i = 0; $i < $totalTeamDifferences - 1; ++$i) {
|
||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||
$teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
|
||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||
$teamDifferencesComparisonLayerLocalFactors = $this->teamDifferencesComparisonLayer->getLocalFactors();
|
||||
|
||||
$differencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i];
|
||||
$comparisonFactor = $teamDifferencesComparisonLayerLocalFactors[$totalTeamDifferences - 1 - $i];
|
||||
$performancesToDifferencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i];
|
||||
|
||||
//current backward schedule piece
|
||||
$currentBackwardSchedulePiece = new ScheduleSequence(
|
||||
'current backward schedule piece',
|
||||
[
|
||||
//teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 0
|
||||
new ScheduleStep(
|
||||
sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 0', $i),
|
||||
$differencesFactor,
|
||||
0
|
||||
),
|
||||
//greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - %d] @ 0
|
||||
new ScheduleStep(
|
||||
sprintf('greaterThanOrWithinResultFactors[totalTeamDifferences - 1 - %d] @ 0', $i),
|
||||
$comparisonFactor,
|
||||
0
|
||||
),
|
||||
//teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 1
|
||||
new ScheduleStep(
|
||||
sprintf('teamPerformanceToPerformanceDifferenceFactors[totalTeamDifferences - 1 - %d] @ 1', $i),
|
||||
$performancesToDifferencesFactor,
|
||||
1
|
||||
),
|
||||
@@ -180,18 +184,19 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
||||
$backwardScheduleList[] = $currentBackwardSchedulePiece;
|
||||
}
|
||||
|
||||
$backwardSchedule = new ScheduleSequence('backward schedule', $backwardScheduleList);
|
||||
//backward schedule
|
||||
$backwardSchedule = new ScheduleSequence($backwardScheduleList);
|
||||
|
||||
$forwardBackwardScheduleToLoop =
|
||||
//forward Backward Schedule To Loop
|
||||
new ScheduleSequence(
|
||||
'forward Backward Schedule To Loop',
|
||||
[$forwardSchedule, $backwardSchedule]
|
||||
);
|
||||
|
||||
$initialMaxDelta = 0.0001;
|
||||
|
||||
//loop with max delta
|
||||
return new ScheduleLoop(
|
||||
sprintf('loop with max delta of %f', $initialMaxDelta),
|
||||
$forwardBackwardScheduleToLoop,
|
||||
$initialMaxDelta
|
||||
);
|
||||
|
||||
@@ -7,14 +7,13 @@ namespace DNW\Skills\TrueSkill\Layers;
|
||||
use DNW\Skills\FactorGraphs\ScheduleStep;
|
||||
use DNW\Skills\FactorGraphs\ScheduleSequence;
|
||||
use DNW\Skills\PartialPlay;
|
||||
use DNW\Skills\Player;
|
||||
use DNW\Skills\Team;
|
||||
use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor;
|
||||
use DNW\Skills\FactorGraphs\Variable;
|
||||
use DNW\Skills\FactorGraphs\KeyedVariable;
|
||||
|
||||
class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLayer
|
||||
final class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLayer
|
||||
{
|
||||
#[\Override]
|
||||
public function buildLayer(): void
|
||||
{
|
||||
$inputVariablesGroups = $this->getInputVariablesGroups();
|
||||
@@ -23,34 +22,36 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
|
||||
*/
|
||||
foreach ($inputVariablesGroups as $currentTeam) {
|
||||
$localCurrentTeam = $currentTeam;
|
||||
$teamPerformance = $this->createOutputVariable($localCurrentTeam);
|
||||
$teamPerformance = $this->createOutputVariable();
|
||||
$newSumFactor = $this->createPlayerToTeamSumFactor($localCurrentTeam, $teamPerformance);
|
||||
|
||||
$this->addLayerFactor($newSumFactor);
|
||||
|
||||
// REVIEW: Does it make sense to have groups of one?
|
||||
$outputVariablesGroups = &$this->getOutputVariablesGroups();
|
||||
$outputVariablesGroups[] = [$teamPerformance];
|
||||
$outputVarGroups = &$this->getOutputVariablesGroups();
|
||||
$outputVarGroups[] = [$teamPerformance];
|
||||
}
|
||||
}
|
||||
|
||||
public function createPriorSchedule(): ?ScheduleSequence
|
||||
#[\Override]
|
||||
public function createPriorSchedule(): ScheduleSequence
|
||||
{
|
||||
$localFactors = $this->getLocalFactors();
|
||||
|
||||
//all player perf to team perf schedule
|
||||
return $this->scheduleSequence(
|
||||
array_map(
|
||||
static fn($weightedSumFactor): ScheduleStep => new ScheduleStep('Perf to Team Perf Step', $weightedSumFactor, 0),
|
||||
//Perf to Team Perf Step
|
||||
static fn($weightedSumFactor): ScheduleStep => new ScheduleStep($weightedSumFactor, 0),
|
||||
$localFactors
|
||||
),
|
||||
'all player perf to team perf schedule'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param KeyedVariable[] $teamMembers
|
||||
*/
|
||||
protected function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor
|
||||
private function createPlayerToTeamSumFactor(array $teamMembers, Variable $sumVariable): GaussianWeightedSumFactor
|
||||
{
|
||||
$weights = array_map(
|
||||
static function ($v): float {
|
||||
@@ -67,7 +68,8 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
|
||||
);
|
||||
}
|
||||
|
||||
public function createPosteriorSchedule(): ?ScheduleSequence
|
||||
#[\Override]
|
||||
public function createPosteriorSchedule(): ScheduleSequence
|
||||
{
|
||||
$allFactors = [];
|
||||
$localFactors = $this->getLocalFactors();
|
||||
@@ -75,26 +77,23 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
|
||||
$localCurrentFactor = $currentFactor;
|
||||
$numberOfMessages = $localCurrentFactor->getNumberOfMessages();
|
||||
for ($currentIteration = 1; $currentIteration < $numberOfMessages; ++$currentIteration) {
|
||||
//team sum perf
|
||||
$allFactors[] = new ScheduleStep(
|
||||
'team sum perf @' . $currentIteration,
|
||||
$localCurrentFactor,
|
||||
$currentIteration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->scheduleSequence($allFactors, "all of the team's sum iterations");
|
||||
//all of the team's sum iterations
|
||||
return $this->scheduleSequence($allFactors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param KeyedVariable[] $team
|
||||
* Team's performance
|
||||
*/
|
||||
private function createOutputVariable(array $team): Variable
|
||||
private function createOutputVariable(): Variable
|
||||
{
|
||||
$memberNames = array_map(static fn($currentPlayer): string => (string)($currentPlayer->getKey()), $team);
|
||||
|
||||
$teamMemberNames = \implode(', ', $memberNames);
|
||||
|
||||
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable('Team[' . $teamMemberNames . "]'s performance");
|
||||
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use DNW\Skills\FactorGraphs\ScheduleSequence;
|
||||
|
||||
// We intentionally have no Posterior schedule since the only purpose here is to
|
||||
// start the process.
|
||||
class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
|
||||
final class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
|
||||
{
|
||||
/**
|
||||
* @param Team[] $teams
|
||||
@@ -27,6 +27,7 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
|
||||
parent::__construct($parentGraph);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function buildLayer(): void
|
||||
{
|
||||
$teams = $this->teams;
|
||||
@@ -34,12 +35,12 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
|
||||
$localCurrentTeam = $currentTeam;
|
||||
$currentTeamSkills = [];
|
||||
|
||||
$currentTeamAllPlayers = $localCurrentTeam->getAllPlayers();
|
||||
foreach ($currentTeamAllPlayers as $currentTeamPlayer) {
|
||||
$localCurrentTeamPlayer = $currentTeamPlayer;
|
||||
$currentTeamPlayerRating = $currentTeam->getRating($localCurrentTeamPlayer);
|
||||
$playerSkill = $this->createSkillOutputVariable($localCurrentTeamPlayer);
|
||||
$priorFactor = $this->createPriorFactor($currentTeamPlayerRating, $playerSkill);
|
||||
$curTeamAllPlayers = $localCurrentTeam->getAllPlayers();
|
||||
foreach ($curTeamAllPlayers as $curTeamPlayer) {
|
||||
$localCurTeamPlayer = $curTeamPlayer;
|
||||
$curTeamPlayerRating = $currentTeam->getRating($localCurTeamPlayer);
|
||||
$playerSkill = $this->createSkillOutputVariable($localCurTeamPlayer);
|
||||
$priorFactor = $this->createPriorFactor($curTeamPlayerRating, $playerSkill);
|
||||
$this->addLayerFactor($priorFactor);
|
||||
$currentTeamSkills[] = $playerSkill;
|
||||
}
|
||||
@@ -49,16 +50,18 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
|
||||
}
|
||||
}
|
||||
|
||||
public function createPriorSchedule(): ?ScheduleSequence
|
||||
#[\Override]
|
||||
public function createPriorSchedule(): ScheduleSequence
|
||||
{
|
||||
$localFactors = $this->getLocalFactors();
|
||||
|
||||
//All priors
|
||||
return $this->scheduleSequence(
|
||||
array_map(
|
||||
static fn($prior): ScheduleStep => new ScheduleStep('Prior to Skill Step', $prior, 0),
|
||||
//Prior to Skill Step
|
||||
static fn($prior): ScheduleStep => new ScheduleStep($prior, 0),
|
||||
$localFactors
|
||||
),
|
||||
'All priors'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,6 +80,6 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
|
||||
$parentFactorGraph = $this->getParentFactorGraph();
|
||||
$variableFactory = $parentFactorGraph->getVariableFactory();
|
||||
|
||||
return $variableFactory->createKeyedVariable($key, $key . "'s skill");
|
||||
return $variableFactory->createKeyedVariable($key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,29 +11,30 @@ use DNW\Skills\Numerics\BasicMath;
|
||||
use DNW\Skills\TrueSkill\Factors\GaussianLikelihoodFactor;
|
||||
use DNW\Skills\FactorGraphs\ScheduleSequence;
|
||||
|
||||
class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
|
||||
final class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
|
||||
{
|
||||
#[\Override]
|
||||
public function buildLayer(): void
|
||||
{
|
||||
$inputVariablesGroups = $this->getInputVariablesGroups();
|
||||
$outputVariablesGroups = &$this->getOutputVariablesGroups();
|
||||
$inputVarGroups = $this->getInputVariablesGroups();
|
||||
$outputVarGroups = &$this->getOutputVariablesGroups();
|
||||
|
||||
foreach ($inputVariablesGroups as $currentTeam) {
|
||||
foreach ($inputVarGroups as $currentTeam) {
|
||||
$currentTeamPlayerPerformances = [];
|
||||
|
||||
/**
|
||||
* @var Variable $playerSkillVariable
|
||||
* @var Variable $playerSkillVar
|
||||
*/
|
||||
foreach ($currentTeam as $playerSkillVariable) {
|
||||
$localPlayerSkillVariable = $playerSkillVariable;
|
||||
$currentPlayer = ($localPlayerSkillVariable instanceof KeyedVariable) ? $localPlayerSkillVariable->getKey() : "";
|
||||
foreach ($currentTeam as $playerSkillVar) {
|
||||
$localPlayerSkillVar = $playerSkillVar;
|
||||
$currentPlayer = ($localPlayerSkillVar instanceof KeyedVariable) ? $localPlayerSkillVar->getKey() : "";
|
||||
$playerPerformance = $this->createOutputVariable($currentPlayer);
|
||||
$newLikelihoodFactor = $this->createLikelihood($localPlayerSkillVariable, $playerPerformance);
|
||||
$newLikelihoodFactor = $this->createLikelihood($localPlayerSkillVar, $playerPerformance);
|
||||
$this->addLayerFactor($newLikelihoodFactor);
|
||||
$currentTeamPlayerPerformances[] = $playerPerformance;
|
||||
}
|
||||
|
||||
$outputVariablesGroups[] = $currentTeamPlayerPerformances;
|
||||
$outputVarGroups[] = $currentTeamPlayerPerformances;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,32 +49,35 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
|
||||
|
||||
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
|
||||
#[\Override]
|
||||
public function createPriorSchedule(): ScheduleSequence
|
||||
{
|
||||
$localFactors = $this->getLocalFactors();
|
||||
|
||||
//All skill to performance sending
|
||||
return $this->scheduleSequence(
|
||||
array_map(
|
||||
static fn($likelihood): ScheduleStep => new ScheduleStep('Skill to Perf step', $likelihood, 0),
|
||||
//Skill to Perf step
|
||||
static fn($likelihood): ScheduleStep => new ScheduleStep($likelihood, 0),
|
||||
$localFactors
|
||||
),
|
||||
'All skill to performance sending'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function createPosteriorSchedule(): ?ScheduleSequence
|
||||
#[\Override]
|
||||
public function createPosteriorSchedule(): ScheduleSequence
|
||||
{
|
||||
$localFactors = $this->getLocalFactors();
|
||||
|
||||
//All skill to performance sending
|
||||
return $this->scheduleSequence(
|
||||
array_map(
|
||||
static fn($likelihood): ScheduleStep => new ScheduleStep('name', $likelihood, 1),
|
||||
static fn($likelihood): ScheduleStep => new ScheduleStep($likelihood, 1),
|
||||
$localFactors
|
||||
),
|
||||
'All skill to performance sending'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use DNW\Skills\TrueSkill\Factors\GaussianGreaterThanFactor;
|
||||
use DNW\Skills\TrueSkill\Factors\GaussianWithinFactor;
|
||||
use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
|
||||
|
||||
class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
|
||||
final class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
|
||||
{
|
||||
private readonly float $epsilon;
|
||||
|
||||
@@ -23,6 +23,7 @@ class TeamDifferencesComparisonLayer extends TrueSkillFactorGraphLayer
|
||||
$this->epsilon = DrawMargin::getDrawMarginFromDrawProbability($gameInfo->getDrawProbability(), $gameInfo->getBeta());
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function buildLayer(): void
|
||||
{
|
||||
$inputVarGroups = $this->getInputVariablesGroups();
|
||||
|
||||
@@ -7,8 +7,9 @@ namespace DNW\Skills\TrueSkill\Layers;
|
||||
use DNW\Skills\FactorGraphs\Variable;
|
||||
use DNW\Skills\TrueSkill\Factors\GaussianWeightedSumFactor;
|
||||
|
||||
class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorGraphLayer
|
||||
final class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorGraphLayer
|
||||
{
|
||||
#[\Override]
|
||||
public function buildLayer(): void
|
||||
{
|
||||
$inputVariablesGroups = $this->getInputVariablesGroups();
|
||||
@@ -28,11 +29,7 @@ class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorG
|
||||
}
|
||||
}
|
||||
|
||||
private function createTeamPerformanceToDifferenceFactor(
|
||||
Variable $strongerTeam,
|
||||
Variable $weakerTeam,
|
||||
Variable $output
|
||||
): GaussianWeightedSumFactor
|
||||
private function createTeamPerformanceToDifferenceFactor(Variable $strongerTeam, Variable $weakerTeam, Variable $output): GaussianWeightedSumFactor
|
||||
{
|
||||
$teams = [$strongerTeam, $weakerTeam];
|
||||
$weights = [1.0, -1.0];
|
||||
@@ -40,8 +37,11 @@ class TeamPerformancesToTeamPerformanceDifferencesLayer extends TrueSkillFactorG
|
||||
return new GaussianWeightedSumFactor($output, $teams, $weights);
|
||||
}
|
||||
|
||||
/**
|
||||
* Team performance difference
|
||||
*/
|
||||
private function createOutputVariable(): Variable
|
||||
{
|
||||
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable('Team performance difference');
|
||||
return $this->getParentFactorGraph()->getVariableFactory()->createBasicVariable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,4 @@ use DNW\Skills\TrueSkill\TrueSkillFactorGraph;
|
||||
|
||||
abstract class TrueSkillFactorGraphLayer extends FactorGraphLayer
|
||||
{
|
||||
public function __construct(TrueSkillFactorGraph $parentGraph)
|
||||
{
|
||||
parent::__construct($parentGraph);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,16 +23,18 @@ use DNW\Skills\TrueSkill\Layers\PlayerSkillsToPerformancesLayer;
|
||||
use DNW\Skills\TrueSkill\Layers\TeamDifferencesComparisonLayer;
|
||||
use DNW\Skills\TrueSkill\Layers\TeamPerformancesToTeamPerformanceDifferencesLayer;
|
||||
|
||||
class TrueSkillFactorGraph extends FactorGraph
|
||||
final class TrueSkillFactorGraph extends FactorGraph
|
||||
{
|
||||
/**
|
||||
* @var FactorGraphLayer[] $layers
|
||||
*/
|
||||
private array $layers;
|
||||
private readonly array $layers;
|
||||
|
||||
private readonly PlayerPriorValuesToSkillsLayer $priorLayer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param GameInfo $gameInfo Parameters for the game.
|
||||
* @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).
|
||||
@@ -118,13 +120,14 @@ class TrueSkillFactorGraph extends FactorGraph
|
||||
$allLayersReverse = array_reverse($this->layers);
|
||||
|
||||
foreach ($allLayersReverse as $currentLayer) {
|
||||
$currentPosteriorSchedule = $currentLayer->createPosteriorSchedule();
|
||||
if ($currentPosteriorSchedule != NULL) {
|
||||
$fullSchedule[] = $currentPosteriorSchedule;
|
||||
$curPosteriorSchedule = $currentLayer->createPosteriorSchedule();
|
||||
if ($curPosteriorSchedule != NULL) {
|
||||
$fullSchedule[] = $curPosteriorSchedule;
|
||||
}
|
||||
}
|
||||
|
||||
return new ScheduleSequence('Full schedule', $fullSchedule);
|
||||
//Full schedule
|
||||
return new ScheduleSequence($fullSchedule);
|
||||
}
|
||||
|
||||
public function getUpdatedRatings(): RatingContainer
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace DNW\Skills\TrueSkill;
|
||||
|
||||
use DNW\Skills\Numerics\GaussianDistribution;
|
||||
|
||||
class TruncatedGaussianCorrectionFunctions
|
||||
final class TruncatedGaussianCorrectionFunctions
|
||||
{
|
||||
// These functions from the bottom of page 4 of the TrueSkill paper.
|
||||
|
||||
@@ -102,33 +102,33 @@ class TruncatedGaussianCorrectionFunctions
|
||||
}
|
||||
|
||||
// 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#:
|
||||
public static function wWithinMargin(float $teamPerformanceDifference, float $drawMargin): float
|
||||
public static function wWithinMargin(float $teamPerformanceDiff, float $drawMargin): float
|
||||
{
|
||||
$teamPerformanceDifferenceAbsoluteValue = abs($teamPerformanceDifference);
|
||||
$denominator = GaussianDistribution::cumulativeTo($drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
||||
$teamPerformanceDiffAbsValue = abs($teamPerformanceDiff);
|
||||
$denominator = GaussianDistribution::cumulativeTo($drawMargin - $teamPerformanceDiffAbsValue)
|
||||
-
|
||||
GaussianDistribution::cumulativeTo(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue);
|
||||
GaussianDistribution::cumulativeTo(-$drawMargin - $teamPerformanceDiffAbsValue);
|
||||
|
||||
if ($denominator < 2.222758749e-162) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
$vt = self::vWithinMargin($teamPerformanceDifferenceAbsoluteValue, $drawMargin);
|
||||
$vt = self::vWithinMargin($teamPerformanceDiffAbsValue, $drawMargin);
|
||||
|
||||
return $vt * $vt +
|
||||
(($drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
||||
(($drawMargin - $teamPerformanceDiffAbsValue)
|
||||
*
|
||||
GaussianDistribution::at(
|
||||
$drawMargin - $teamPerformanceDifferenceAbsoluteValue
|
||||
$drawMargin - $teamPerformanceDiffAbsValue
|
||||
)
|
||||
- (-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
||||
- (-$drawMargin - $teamPerformanceDiffAbsValue)
|
||||
*
|
||||
GaussianDistribution::at(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)) / $denominator;
|
||||
GaussianDistribution::at(-$drawMargin - $teamPerformanceDiffAbsValue)) / $denominator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ use DNW\Skills\RankSorter;
|
||||
use DNW\Skills\Rating;
|
||||
use DNW\Skills\RatingContainer;
|
||||
use DNW\Skills\SkillCalculator;
|
||||
use DNW\Skills\SkillCalculatorSupportedOptions;
|
||||
use DNW\Skills\TeamsRange;
|
||||
use DNW\Skills\Team;
|
||||
|
||||
/**
|
||||
* Calculates the new ratings for only two players.
|
||||
@@ -23,21 +21,18 @@ use DNW\Skills\Team;
|
||||
* When you only have two players, a lot of the math simplifies. The main purpose of this class
|
||||
* is to show the bare minimum of what a TrueSkill implementation should have.
|
||||
*/
|
||||
class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
||||
final class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(SkillCalculatorSupportedOptions::NONE, TeamsRange::exactly(2), PlayersRange::exactly(1));
|
||||
parent::__construct(SkillCalculator::NONE, TeamsRange::exactly(2), PlayersRange::exactly(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateNewRatings(
|
||||
GameInfo $gameInfo,
|
||||
array $teams,
|
||||
array $teamRanks
|
||||
): RatingContainer
|
||||
#[\Override]
|
||||
public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer
|
||||
{
|
||||
// Basic argument checking
|
||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||
@@ -96,7 +91,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
||||
+
|
||||
BasicMath::square($opponentRating->getStandardDeviation())
|
||||
+
|
||||
2 * BasicMath::square($gameInfo->getBeta())
|
||||
2.0 * BasicMath::square($gameInfo->getBeta())
|
||||
);
|
||||
|
||||
$winningMean = $selfRating->getMean();
|
||||
@@ -119,11 +114,11 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
||||
// non-draw case
|
||||
$v = TruncatedGaussianCorrectionFunctions::vExceedsMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$w = TruncatedGaussianCorrectionFunctions::wExceedsMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$rankMultiplier = $comparison->value;
|
||||
$rankMultiplier = (float)$comparison->value;
|
||||
} else {
|
||||
$v = TruncatedGaussianCorrectionFunctions::vWithinMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$w = TruncatedGaussianCorrectionFunctions::wWithinMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$rankMultiplier = 1;
|
||||
$rankMultiplier = 1.0;
|
||||
}
|
||||
|
||||
$meanMultiplier = (BasicMath::square($selfRating->getStandardDeviation()) + BasicMath::square($gameInfo->getDynamicsFactor())) / $c;
|
||||
@@ -132,7 +127,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
||||
$stdDevMultiplier = $varianceWithDynamics / BasicMath::square($c);
|
||||
|
||||
$newMean = $selfRating->getMean() + ($rankMultiplier * $meanMultiplier * $v);
|
||||
$newStdDev = sqrt($varianceWithDynamics * (1 - $w * $stdDevMultiplier));
|
||||
$newStdDev = sqrt($varianceWithDynamics * (1.0 - $w * $stdDevMultiplier));
|
||||
|
||||
return new Rating($newMean, $newStdDev);
|
||||
}
|
||||
@@ -140,6 +135,7 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\Override]
|
||||
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
|
||||
{
|
||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||
@@ -160,16 +156,16 @@ class TwoPlayerTrueSkillCalculator extends SkillCalculator
|
||||
|
||||
// This is the square root part of the equation:
|
||||
$sqrtPart = sqrt(
|
||||
(2 * $betaSquared)
|
||||
(2.0 * $betaSquared)
|
||||
/
|
||||
(2 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared)
|
||||
(2.0 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared)
|
||||
);
|
||||
|
||||
// This is the exponent part of the equation:
|
||||
$expPart = exp(
|
||||
(-1 * BasicMath::square($player1Rating->getMean() - $player2Rating->getMean()))
|
||||
(-1.0 * BasicMath::square($player1Rating->getMean() - $player2Rating->getMean()))
|
||||
/
|
||||
(2 * (2 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared))
|
||||
(2.0 * (2.0 * $betaSquared + $player1SigmaSquared + $player2SigmaSquared))
|
||||
);
|
||||
|
||||
return $sqrtPart * $expPart;
|
||||
|
||||
@@ -13,7 +13,6 @@ use DNW\Skills\RankSorter;
|
||||
use DNW\Skills\Rating;
|
||||
use DNW\Skills\RatingContainer;
|
||||
use DNW\Skills\SkillCalculator;
|
||||
use DNW\Skills\SkillCalculatorSupportedOptions;
|
||||
use DNW\Skills\Team;
|
||||
use DNW\Skills\TeamsRange;
|
||||
|
||||
@@ -22,16 +21,17 @@ use DNW\Skills\TeamsRange;
|
||||
*
|
||||
* When you only have two teams, the math is still simple: no factor graphs are used yet.
|
||||
*/
|
||||
class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
final class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(SkillCalculatorSupportedOptions::NONE, TeamsRange::exactly(2), PlayersRange::atLeast(1));
|
||||
parent::__construct(SkillCalculator::NONE, TeamsRange::exactly(2), PlayersRange::atLeast(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\Override]
|
||||
public function calculateNewRatings(GameInfo $gameInfo, array $teams, array $teamRanks): RatingContainer
|
||||
{
|
||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||
@@ -64,13 +64,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
return $results;
|
||||
}
|
||||
|
||||
private static function updatePlayerRatings(
|
||||
GameInfo $gameInfo,
|
||||
RatingContainer $newPlayerRatings,
|
||||
Team $selfTeam,
|
||||
Team $otherTeam,
|
||||
PairwiseComparison $selfToOtherTeamComparison
|
||||
): void
|
||||
private static function updatePlayerRatings(GameInfo $gameInfo, RatingContainer $newPlayerRatings, Team $selfTeam, Team $otherTeam, PairwiseComparison $selfToOtherTeamComparison): void
|
||||
{
|
||||
$drawMargin = DrawMargin::getDrawMarginFromDrawProbability(
|
||||
$gameInfo->getDrawProbability(),
|
||||
@@ -94,7 +88,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
+
|
||||
BasicMath::sum($otherTeam->getAllRatings(), $varianceGetter)
|
||||
+
|
||||
$totalPlayers * $betaSquared
|
||||
(float)$totalPlayers * $betaSquared
|
||||
);
|
||||
|
||||
$winningMean = $selfMeanSum;
|
||||
@@ -117,18 +111,18 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
// non-draw case
|
||||
$v = TruncatedGaussianCorrectionFunctions::vExceedsMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$w = TruncatedGaussianCorrectionFunctions::wExceedsMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$rankMultiplier = $selfToOtherTeamComparison->value;
|
||||
$rankMultiplier = (float)$selfToOtherTeamComparison->value;
|
||||
} else {
|
||||
// assume draw
|
||||
$v = TruncatedGaussianCorrectionFunctions::vWithinMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$w = TruncatedGaussianCorrectionFunctions::wWithinMarginScaled($meanDelta, $drawMargin, $c);
|
||||
$rankMultiplier = 1;
|
||||
$rankMultiplier = 1.0;
|
||||
}
|
||||
|
||||
$selfTeamAllPlayers = $selfTeam->getAllPlayers();
|
||||
foreach ($selfTeamAllPlayers as $selfTeamCurrentPlayer) {
|
||||
$localSelfTeamCurrentPlayer = $selfTeamCurrentPlayer;
|
||||
$previousPlayerRating = $selfTeam->getRating($localSelfTeamCurrentPlayer);
|
||||
foreach ($selfTeamAllPlayers as $selfTeamCurPlayer) {
|
||||
$localSelfTeamCurPlayer = $selfTeamCurPlayer;
|
||||
$previousPlayerRating = $selfTeam->getRating($localSelfTeamCurPlayer);
|
||||
|
||||
$meanMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / $c;
|
||||
$stdDevMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / BasicMath::square($c);
|
||||
@@ -137,16 +131,17 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
$newMean = $previousPlayerRating->getMean() + $playerMeanDelta;
|
||||
|
||||
$newStdDev = sqrt(
|
||||
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1 - $w * $stdDevMultiplier)
|
||||
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1.0 - $w * $stdDevMultiplier)
|
||||
);
|
||||
|
||||
$newPlayerRatings->setRating($localSelfTeamCurrentPlayer, new Rating($newMean, $newStdDev));
|
||||
$newPlayerRatings->setRating($localSelfTeamCurPlayer, new Rating($newMean, $newStdDev));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\Override]
|
||||
public function calculateMatchQuality(GameInfo $gameInfo, array $teams): float
|
||||
{
|
||||
$this->validateTeamCountAndPlayersCountPerTeam($teams);
|
||||
@@ -158,7 +153,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
$team2Ratings = $teams[1]->getAllRatings();
|
||||
$team2Count = count($team2Ratings);
|
||||
|
||||
$totalPlayers = $team1Count + $team2Count;
|
||||
$totalPlayers = (float)($team1Count + $team2Count);
|
||||
|
||||
$betaSquared = BasicMath::square($gameInfo->getBeta());
|
||||
|
||||
@@ -183,9 +178,9 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
||||
);
|
||||
|
||||
$expPart = exp(
|
||||
(-1 * BasicMath::square($team1MeanSum - $team2MeanSum))
|
||||
(-1.0 * BasicMath::square($team1MeanSum - $team2MeanSum))
|
||||
/
|
||||
(2 * ($totalPlayers * $betaSquared + $team1StdDevSquared + $team2StdDevSquared))
|
||||
(2.0 * ($totalPlayers * $betaSquared + $team1StdDevSquared + $team2StdDevSquared))
|
||||
);
|
||||
|
||||
return $expPart * $sqrtPart;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\Tests\FactorGraphs;
|
||||
|
||||
use DNW\Skills\FactorGraphs\ScheduleStep;
|
||||
use DNW\Skills\FactorGraphs\Factor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ScheduleStepTest extends TestCase
|
||||
{
|
||||
public function testtoStringInterface(): void
|
||||
{
|
||||
$stub = $this->createStub(Factor::class);
|
||||
$ss = new ScheduleStep('dummy', $stub, 0);
|
||||
$this->assertEquals('dummy', (string)$ss);
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,22 @@ namespace DNW\Skills\Tests\FactorGraphs;
|
||||
use DNW\Skills\FactorGraphs\Variable;
|
||||
use DNW\Skills\Numerics\GaussianDistribution;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
class VariableTest extends TestCase
|
||||
#[CoversClass(Variable::class)]
|
||||
#[UsesClass(GaussianDistribution::class)]
|
||||
final class VariableTest extends TestCase
|
||||
{
|
||||
public function testGetterSetter(): void
|
||||
{
|
||||
$gd_prior = new GaussianDistribution();
|
||||
$var = new Variable('dummy', $gd_prior);
|
||||
$var = new Variable($gd_prior);
|
||||
$this->assertEquals($gd_prior, $var->getValue());
|
||||
|
||||
$gd_new = new GaussianDistribution();
|
||||
$this->assertEquals($gd_new, $var->getValue());
|
||||
$var->resetToPrior();
|
||||
$this->assertEquals($gd_prior, $var->getValue());
|
||||
$this->assertEquals('Variable[dummy]', (string)$var);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,14 @@ 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;
|
||||
|
||||
class GameInfoTest extends TestCase
|
||||
#[CoversClass(GameInfo::class)]
|
||||
#[UsesClass(Rating::class)]
|
||||
final class GameInfoTest extends TestCase
|
||||
{
|
||||
public function testMembers(): void
|
||||
{
|
||||
@@ -17,6 +22,6 @@ class GameInfoTest extends TestCase
|
||||
$this->assertEquals(3, $gi->getBeta());
|
||||
$this->assertEquals(4, $gi->getDynamicsFactor());
|
||||
$this->assertEquals(5, $gi->getDrawProbability());
|
||||
$this->assertInstanceOf(\DNW\Skills\Rating::class, $gi->getDefaultRating());
|
||||
$this->assertInstanceOf(Rating::class, $gi->getDefaultRating());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,27 +7,55 @@ namespace DNW\Skills\Tests;
|
||||
use DNW\Skills\Guard;
|
||||
use Exception;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
|
||||
class GuardTest extends TestCase
|
||||
#[CoversClass(Guard::class)]
|
||||
final class GuardTest extends TestCase
|
||||
{
|
||||
public function testargumentIsValidIndex(): void
|
||||
public function testargumentIsValidIndexArgumentAbove(): void
|
||||
{
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage('dummy is an invalid index');
|
||||
Guard::argumentIsValidIndex(10, 10, "dummy");
|
||||
}
|
||||
|
||||
public function testargumentIsValidIndex2(): void
|
||||
public function testargumentIsValidIndexArgumentBelow(): void
|
||||
{
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage('dummy is an invalid index');
|
||||
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->expectExceptionMessage('dummy is not in the valid range [0, 100]');
|
||||
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)]
|
||||
final 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());
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@ namespace DNW\Skills\Tests\Numerics;
|
||||
|
||||
use DNW\Skills\Numerics\BasicMath;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
|
||||
class BasicMathTest extends TestCase
|
||||
#[CoversClass(BasicMath::class)]
|
||||
final class BasicMathTest extends TestCase
|
||||
{
|
||||
public function testSquare(): void
|
||||
{
|
||||
@@ -21,7 +23,7 @@ class BasicMathTest extends TestCase
|
||||
$arr = [1, 1, 1, 1];
|
||||
|
||||
$func_return = static fn(float $f): float => $f;
|
||||
$func_double = static fn(float $f): float => $f * 2;
|
||||
$func_double = static fn(float $f): float => $f * 2.0;
|
||||
$this->assertEquals(4, BasicMath::sum($arr, $func_return));
|
||||
$this->assertEquals(8, BasicMath::sum($arr, $func_double));
|
||||
}
|
||||
|
||||
@@ -7,10 +7,14 @@ namespace DNW\Skills\Tests\Numerics;
|
||||
use DNW\Skills\Numerics\BasicMath;
|
||||
use DNW\Skills\Numerics\GaussianDistribution;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
class GaussianDistributionTest extends TestCase
|
||||
#[CoversClass(GaussianDistribution::class)]
|
||||
#[UsesClass(BasicMath::class)]
|
||||
final class GaussianDistributionTest extends TestCase
|
||||
{
|
||||
private const ERROR_TOLERANCE = 0.000001;
|
||||
private const float ERROR_TOLERANCE = 0.000001;
|
||||
|
||||
public function testGetters(): void
|
||||
{
|
||||
@@ -20,7 +24,7 @@ class GaussianDistributionTest extends TestCase
|
||||
$this->assertEquals(9, $gd->getVariance());
|
||||
$this->assertEquals(3, $gd->getStandardDeviation());
|
||||
$this->assertEquals(1 / 9, $gd->getPrecision());
|
||||
$this->assertEquals(1 / 9 * 10, $gd->getPrecisionMean());
|
||||
$this->assertEquals(1.0 / 9.0 * 10.0, $gd->getPrecisionMean());
|
||||
$this->assertEqualsWithDelta(0.13298076013, $gd->getNormalizationConstant(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||
}
|
||||
|
||||
@@ -53,7 +57,7 @@ class GaussianDistributionTest extends TestCase
|
||||
|
||||
$product2 = GaussianDistribution::multiply($m4s5, $m6s7);
|
||||
|
||||
$expectedMean = (4 * BasicMath::square(7) + 6 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7));
|
||||
$expectedMean = (4.0 * BasicMath::square(7) + 6.0 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7));
|
||||
$this->assertEqualsWithDelta($expectedMean, $product2->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||
|
||||
$expectedSigma = sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7))));
|
||||
@@ -70,7 +74,7 @@ class GaussianDistributionTest extends TestCase
|
||||
$this->assertEqualsWithDelta(2.0, $productDividedByStandardNormal->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||
$this->assertEqualsWithDelta(3.0, $productDividedByStandardNormal->getStandardDeviation(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||
|
||||
$product2 = new GaussianDistribution((4 * BasicMath::square(7) + 6 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7)), sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7)))));
|
||||
$product2 = new GaussianDistribution((4.0 * BasicMath::square(7) + 6.0 * BasicMath::square(5)) / (BasicMath::square(5) + BasicMath::square(7)), sqrt(((BasicMath::square(5) * BasicMath::square(7)) / (BasicMath::square(5) + BasicMath::square(7)))));
|
||||
$m4s5 = new GaussianDistribution(4, 5);
|
||||
$product2DividedByM4S5 = GaussianDistribution::divide($product2, $m4s5);
|
||||
$this->assertEqualsWithDelta(6.0, $product2DividedByM4S5->getMean(), GaussianDistributionTest::ERROR_TOLERANCE);
|
||||
@@ -88,6 +92,21 @@ class GaussianDistributionTest extends TestCase
|
||||
$m3s4 = new GaussianDistribution(3, 4);
|
||||
$lpn2 = GaussianDistribution::logProductNormalization($m1s2, $m3s4);
|
||||
$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
|
||||
@@ -97,6 +116,21 @@ class GaussianDistributionTest extends TestCase
|
||||
$m3s4 = new GaussianDistribution(3, 4);
|
||||
$lrn = GaussianDistribution::logRatioNormalization($m1s2, $m3s4);
|
||||
$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
|
||||
@@ -111,4 +145,27 @@ class GaussianDistributionTest extends TestCase
|
||||
$absDiff2 = GaussianDistribution::absoluteDifference($m1s2, $m3s4);
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use DNW\Skills\Numerics\IdentityMatrix;
|
||||
use DNW\Skills\Numerics\Matrix;
|
||||
use DNW\Skills\Numerics\SquareMatrix;
|
||||
use DNW\Skills\Numerics\DiagonalMatrix;
|
||||
use DNW\Skills\Numerics\Vector;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
@@ -17,9 +18,87 @@ use Exception;
|
||||
#[CoversClass(SquareMatrix::class)]
|
||||
#[CoversClass(IdentityMatrix::class)]
|
||||
#[CoversClass(DiagonalMatrix::class)]
|
||||
#[CoversClass(Vector::class)]
|
||||
// phpcs:disable PSR2.Methods.FunctionCallSignature,Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
|
||||
class MatrixTest extends TestCase
|
||||
final class MatrixTest extends TestCase
|
||||
{
|
||||
public function testEmptyMatrix(): void
|
||||
{
|
||||
$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);
|
||||
@@ -216,7 +295,7 @@ class MatrixTest extends TestCase
|
||||
1, 0, 6);
|
||||
|
||||
$cInverse = $c->getInverse();
|
||||
$d = Matrix::scalarMultiply((1.0 / 22), new SquareMatrix(24, -12, -2,
|
||||
$d = Matrix::scalarMultiply((1.0 / 22.0), new SquareMatrix(24, -12, -2,
|
||||
5, 3, -5,
|
||||
-4, 2, 4));
|
||||
|
||||
@@ -264,6 +343,15 @@ class MatrixTest extends TestCase
|
||||
$m2 = new Matrix(1, 1, [[1,1]]);
|
||||
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
|
||||
|
||||
@@ -6,9 +6,11 @@ namespace DNW\Skills\Tests\Numerics;
|
||||
|
||||
use DNW\Skills\Numerics\Range;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Exception;
|
||||
|
||||
class RangeTest extends TestCase
|
||||
#[CoversClass(Range::class)]
|
||||
final class RangeTest extends TestCase
|
||||
{
|
||||
public function testConstructInvalidParam(): void
|
||||
{
|
||||
|
||||
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)]
|
||||
final 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));
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,18 @@ 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;
|
||||
|
||||
class PlayerTest extends TestCase
|
||||
#[CoversClass(Player::class)]
|
||||
#[UsesClass(Guard::class)]
|
||||
final class PlayerTest extends TestCase
|
||||
{
|
||||
public function testPlayerObjectGetterSetter(): void
|
||||
{
|
||||
$p = new Player('dummy', 0.1, 0.2);
|
||||
$this->assertEquals('dummy', (string)$p);
|
||||
$this->assertEquals('dummy', $p->getId());
|
||||
$this->assertEquals(0.1, $p->getPartialPlayPercentage());
|
||||
$this->assertEquals(0.2, $p->getPartialUpdatePercentage());
|
||||
|
||||
@@ -6,8 +6,10 @@ namespace DNW\Skills\Tests;
|
||||
|
||||
use DNW\Skills\RankSorter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
|
||||
class RankSorterTest extends TestCase
|
||||
#[CoversClass(RankSorter::class)]
|
||||
final class RankSorterTest extends TestCase
|
||||
{
|
||||
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)]
|
||||
final 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());
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,16 @@ declare(strict_types=1);
|
||||
namespace DNW\Skills\Tests;
|
||||
|
||||
use DNW\Skills\Rating;
|
||||
use DNW\Skills\Numerics\BasicMath;
|
||||
use DNW\Skills\Numerics\GaussianDistribution;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
class RatingTest extends TestCase
|
||||
#[CoversClass(Rating::class)]
|
||||
#[UsesClass(BasicMath::class)]
|
||||
#[UsesClass(GaussianDistribution::class)]
|
||||
final class RatingTest extends TestCase
|
||||
{
|
||||
public function testGetRatingParameters(): void
|
||||
{
|
||||
@@ -15,7 +22,6 @@ class RatingTest extends TestCase
|
||||
$this->assertEquals(100, $rating->getMean());
|
||||
$this->assertEquals(10, $rating->getStandardDeviation());
|
||||
$this->assertEquals(50, $rating->getConservativeRating());
|
||||
$this->assertEquals("mean=100.0000, standardDeviation=10.0000", (string)$rating);
|
||||
}
|
||||
|
||||
public function testPartialUpdate(): void
|
||||
@@ -26,10 +32,8 @@ class RatingTest extends TestCase
|
||||
|
||||
$rating_partial = $rating->getPartialUpdate($ratingOld, $ratingNew, 0.5);
|
||||
|
||||
|
||||
$this->assertEquals(150, $rating_partial->getMean());
|
||||
$this->assertEquals(10, $rating_partial->getStandardDeviation());
|
||||
$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')]
|
||||
final class SkillCalculatorTest extends TestCase
|
||||
{
|
||||
public function testisSupported(): void
|
||||
{
|
||||
$calculator = $this->getMockForAbstractClass(SkillCalculator::class, [SkillCalculator::PARTIAL_PLAY, new TeamsRange(1, 2), new PlayersRange(1, 2)]);
|
||||
|
||||
$this->assertEquals(TRUE, $calculator->isSupported(SkillCalculator::PARTIAL_PLAY));
|
||||
$this->assertEquals(FALSE, $calculator->isSupported(SkillCalculator::PARTIAL_UPDATE));
|
||||
}
|
||||
}
|
||||
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)]
|
||||
final 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());
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,18 @@ declare(strict_types=1);
|
||||
namespace DNW\Skills\Tests\TrueSkill;
|
||||
|
||||
use DNW\Skills\TrueSkill\DrawMargin;
|
||||
use DNW\Skills\Numerics\BasicMath;
|
||||
use DNW\Skills\Numerics\GaussianDistribution;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
class DrawMarginTest extends TestCase
|
||||
#[CoversClass(DrawMargin::class)]
|
||||
#[UsesClass(BasicMath::class)]
|
||||
#[UsesClass(GaussianDistribution::class)]
|
||||
final class DrawMarginTest extends TestCase
|
||||
{
|
||||
private const ERROR_TOLERANCE = 0.000001;
|
||||
private const float ERROR_TOLERANCE = 0.000001;
|
||||
|
||||
public function testGetDrawMarginFromDrawProbability(): void
|
||||
{
|
||||
@@ -23,6 +30,6 @@ class DrawMarginTest extends TestCase
|
||||
private function assertDrawMargin(float $drawProbability, float $beta, float $expected): void
|
||||
{
|
||||
$actual = DrawMargin::getDrawMarginFromDrawProbability($drawProbability, $beta);
|
||||
$this->assertEqualsWithDelta($expected, $actual, DrawMarginTest::ERROR_TOLERANCE);
|
||||
$this->assertEqualsWithDelta($expected, $actual, self::ERROR_TOLERANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace DNW\Skills\Tests\TrueSkill;
|
||||
|
||||
use DNW\Skills\SkillCalculator;
|
||||
use DNW\Skills\GameInfo;
|
||||
use DNW\Skills\Player;
|
||||
use DNW\Skills\Team;
|
||||
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
||||
use DNW\Skills\SkillCalculatorSupportedOptions;
|
||||
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
|
||||
final class FactorGraphTrueSkillCalculatorTest extends TestCase
|
||||
{
|
||||
#[CoversNothing]
|
||||
public function testMicrosoftResearchExample(): void
|
||||
@@ -75,9 +68,10 @@ class FactorGraphTrueSkillCalculatorTest extends TestCase
|
||||
TrueSkillCalculatorTests::testPartialPlayScenarios($this, $calculator);
|
||||
}
|
||||
|
||||
#[CoversNothing]
|
||||
public function testMethodisSupported(): void
|
||||
{
|
||||
$calculator = new FactorGraphTrueSkillCalculator();
|
||||
$this->assertEquals(TRUE, $calculator->isSupported(SkillCalculatorSupportedOptions::PARTIAL_PLAY));
|
||||
$this->assertEquals(TRUE, $calculator->isSupported(SkillCalculator::PARTIAL_PLAY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,13 @@ use DNW\Skills\Player;
|
||||
use DNW\Skills\Rating;
|
||||
use DNW\Skills\SkillCalculator;
|
||||
use DNW\Skills\Team;
|
||||
use DNW\Skills\Teams;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TrueSkillCalculatorTests
|
||||
final class TrueSkillCalculatorTests
|
||||
{
|
||||
private const ERROR_TOLERANCE_TRUESKILL = 0.085;
|
||||
private const float ERROR_TOLERANCE_TRUESKILL = 0.085;
|
||||
|
||||
private const ERROR_TOLERANCE_MATCH_QUALITY = 0.0005;
|
||||
private const float ERROR_TOLERANCE_MATCH_QUALITY = 0.0005;
|
||||
|
||||
// These are the roll-up ones
|
||||
|
||||
@@ -70,7 +69,6 @@ class TrueSkillCalculatorTests
|
||||
// online calculator at http://atom.research.microsoft.com/trueskill/rankcalculator.aspx
|
||||
//
|
||||
// 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
|
||||
// using slightly higher precision in GaussianDistribution.
|
||||
|
||||
@@ -86,7 +84,7 @@ class TrueSkillCalculatorTests
|
||||
|
||||
$team1 = new Team($player1, $gameInfo->getDefaultRating());
|
||||
$team2 = new Team($player2, $gameInfo->getDefaultRating());
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
@@ -109,7 +107,7 @@ class TrueSkillCalculatorTests
|
||||
$team1 = new Team($player1, $gameInfo->getDefaultRating());
|
||||
$team2 = new Team($player2, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
|
||||
|
||||
@@ -132,7 +130,7 @@ class TrueSkillCalculatorTests
|
||||
$team1 = new Team($player1, new Rating(1301.0007, 42.9232));
|
||||
$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);
|
||||
self::assertRating($testClass, 1304.7820836053318, 42.843513887848658, $player1NewRating);
|
||||
@@ -153,7 +151,7 @@ class TrueSkillCalculatorTests
|
||||
|
||||
$team2 = new Team($player2, new Rating(50, 12.5));
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
|
||||
|
||||
@@ -184,7 +182,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player2, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player3, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
// Winners
|
||||
@@ -219,7 +217,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player3, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player4, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
// Winners
|
||||
@@ -249,7 +247,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player2, new Rating(20, 7));
|
||||
$team2->addPlayer($player3, new Rating(25, 8));
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
// Winners
|
||||
@@ -280,7 +278,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player3, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player4, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
// Winners
|
||||
@@ -310,7 +308,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player2, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player3, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
|
||||
|
||||
// Winners
|
||||
@@ -341,7 +339,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player3, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player4, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
|
||||
|
||||
// Winners
|
||||
@@ -381,7 +379,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player7, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player8, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
// Winners
|
||||
@@ -419,7 +417,7 @@ class TrueSkillCalculatorTests
|
||||
|
||||
$gameInfo = new GameInfo();
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLoseExpected = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
// Winners
|
||||
@@ -431,7 +429,7 @@ class TrueSkillCalculatorTests
|
||||
self::assertRating($testClass, 29.785, 3.958, $newRatingsWinLoseExpected->getRating($player4));
|
||||
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
|
||||
self::assertRating($testClass, 32.012, 3.877, $newRatingsWinLoseUpset->getRating($player4));
|
||||
@@ -463,7 +461,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player3, new Rating(25, 4));
|
||||
$team2->addPlayer($player4, new Rating(30, 3));
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
|
||||
|
||||
// Winners
|
||||
@@ -495,7 +493,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player3, new Rating(35, 7));
|
||||
$team2->addPlayer($player4, new Rating(40, 5));
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
// Winners
|
||||
@@ -535,7 +533,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player7, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player8, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
@@ -572,7 +570,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($player3, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($player4, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatingsWinLose = $calculator->calculateNewRatings($gameInfo, $teams, [1, 1]);
|
||||
|
||||
// Winners
|
||||
@@ -598,7 +596,7 @@ class TrueSkillCalculatorTests
|
||||
$team2 = new Team($player2, $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]);
|
||||
|
||||
$player1NewRating = $newRatings->getRating($player1);
|
||||
@@ -625,7 +623,7 @@ class TrueSkillCalculatorTests
|
||||
$team2 = new Team($player2, $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]);
|
||||
|
||||
$player1NewRating = $newRatings->getRating($player1);
|
||||
@@ -653,7 +651,7 @@ class TrueSkillCalculatorTests
|
||||
$team3 = new Team($player3, $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]);
|
||||
|
||||
@@ -687,7 +685,7 @@ class TrueSkillCalculatorTests
|
||||
$team4 = new Team($player4, $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]);
|
||||
|
||||
$player1NewRating = $newRatings->getRating($player1);
|
||||
@@ -729,7 +727,7 @@ class TrueSkillCalculatorTests
|
||||
$team7 = new Team($player7, $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]);
|
||||
|
||||
$player1NewRating = $newRatings->getRating($player1);
|
||||
@@ -781,7 +779,7 @@ class TrueSkillCalculatorTests
|
||||
$team7 = new Team($player7, new Rating(40, 2));
|
||||
$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]);
|
||||
|
||||
$player1NewRating = $newRatings->getRating($player1);
|
||||
@@ -813,15 +811,15 @@ class TrueSkillCalculatorTests
|
||||
|
||||
private static function sixteenTeamsOfOneNotDrawn(TestCase $testClass, SkillCalculator $calculator): void
|
||||
{
|
||||
$player1 = new Player(1);
|
||||
$player2 = new Player(2);
|
||||
$player3 = new Player(3);
|
||||
$player4 = new Player(4);
|
||||
$player5 = new Player(5);
|
||||
$player6 = new Player(6);
|
||||
$player7 = new Player(7);
|
||||
$player8 = new Player(8);
|
||||
$player9 = new Player(9);
|
||||
$player1 = new Player(1);
|
||||
$player2 = new Player(2);
|
||||
$player3 = new Player(3);
|
||||
$player4 = new Player(4);
|
||||
$player5 = new Player(5);
|
||||
$player6 = new Player(6);
|
||||
$player7 = new Player(7);
|
||||
$player8 = new Player(8);
|
||||
$player9 = new Player(9);
|
||||
$player10 = new Player(10);
|
||||
$player11 = new Player(11);
|
||||
$player12 = new Player(12);
|
||||
@@ -832,15 +830,15 @@ class TrueSkillCalculatorTests
|
||||
|
||||
$gameInfo = new GameInfo();
|
||||
|
||||
$team1 = new Team($player1, $gameInfo->getDefaultRating());
|
||||
$team2 = new Team($player2, $gameInfo->getDefaultRating());
|
||||
$team3 = new Team($player3, $gameInfo->getDefaultRating());
|
||||
$team4 = new Team($player4, $gameInfo->getDefaultRating());
|
||||
$team5 = new Team($player5, $gameInfo->getDefaultRating());
|
||||
$team6 = new Team($player6, $gameInfo->getDefaultRating());
|
||||
$team7 = new Team($player7, $gameInfo->getDefaultRating());
|
||||
$team8 = new Team($player8, $gameInfo->getDefaultRating());
|
||||
$team9 = new Team($player9, $gameInfo->getDefaultRating());
|
||||
$team1 = new Team($player1, $gameInfo->getDefaultRating());
|
||||
$team2 = new Team($player2, $gameInfo->getDefaultRating());
|
||||
$team3 = new Team($player3, $gameInfo->getDefaultRating());
|
||||
$team4 = new Team($player4, $gameInfo->getDefaultRating());
|
||||
$team5 = new Team($player5, $gameInfo->getDefaultRating());
|
||||
$team6 = new Team($player6, $gameInfo->getDefaultRating());
|
||||
$team7 = new Team($player7, $gameInfo->getDefaultRating());
|
||||
$team8 = new Team($player8, $gameInfo->getDefaultRating());
|
||||
$team9 = new Team($player9, $gameInfo->getDefaultRating());
|
||||
$team10 = new Team($player10, $gameInfo->getDefaultRating());
|
||||
$team11 = new Team($player11, $gameInfo->getDefaultRating());
|
||||
$team12 = new Team($player12, $gameInfo->getDefaultRating());
|
||||
@@ -849,24 +847,7 @@ class TrueSkillCalculatorTests
|
||||
$team15 = new Team($player15, $gameInfo->getDefaultRating());
|
||||
$team16 = new Team($player16, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat(
|
||||
$team1,
|
||||
$team2,
|
||||
$team3,
|
||||
$team4,
|
||||
$team5,
|
||||
$team6,
|
||||
$team7,
|
||||
$team8,
|
||||
$team9,
|
||||
$team10,
|
||||
$team11,
|
||||
$team12,
|
||||
$team13,
|
||||
$team14,
|
||||
$team15,
|
||||
$team16
|
||||
);
|
||||
$teams = [$team1, $team2, $team3, $team4, $team5, $team6, $team7, $team8, $team9, $team10, $team11, $team12, $team13, $team14, $team15, $team16];
|
||||
|
||||
$newRatings = $calculator->calculateNewRatings(
|
||||
$gameInfo,
|
||||
@@ -952,7 +933,7 @@ class TrueSkillCalculatorTests
|
||||
$team3->addPlayer($player7, new Rating(50, 5));
|
||||
$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]);
|
||||
|
||||
// Winners
|
||||
@@ -986,7 +967,7 @@ class TrueSkillCalculatorTests
|
||||
$team2->addPlayer($p2, $gameInfo->getDefaultRating());
|
||||
$team2->addPlayer($p3, $gameInfo->getDefaultRating());
|
||||
|
||||
$teams = Teams::concat($team1, $team2);
|
||||
$teams = [$team1, $team2];
|
||||
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, [1, 2]);
|
||||
|
||||
$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)]
|
||||
final 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);
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,9 @@ namespace DNW\Skills\Tests\TrueSkill;
|
||||
|
||||
use DNW\Skills\TrueSkill\TwoPlayerTrueSkillCalculator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\CoversNothing;
|
||||
|
||||
#[CoversClass(TwoPlayerTrueSkillCalculator::class)]
|
||||
class TwoPlayerTrueSkillCalculatorTest extends TestCase
|
||||
final class TwoPlayerTrueSkillCalculatorTest extends TestCase
|
||||
{
|
||||
#[CoversNothing]
|
||||
public function testTwoPlayerTrueSkillCalculator(): void
|
||||
|
||||
@@ -6,11 +6,9 @@ namespace DNW\Skills\Tests\TrueSkill;
|
||||
|
||||
use DNW\Skills\TrueSkill\TwoTeamTrueSkillCalculator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\CoversNothing;
|
||||
|
||||
#[CoversClass(TwoTeamTrueSkillCalculator::class)]
|
||||
class TwoTeamTrueSkillCalculatorTest extends TestCase
|
||||
final class TwoTeamTrueSkillCalculatorTest extends TestCase
|
||||
{
|
||||
#[CoversNothing]
|
||||
public function testTwoTeamTrueSkillCalculator(): void
|
||||
|
||||
Reference in New Issue
Block a user