mirror of
https://github.com/furyfire/trueskill.git
synced 2025-01-16 01:47:39 +00:00
Compare commits
8 Commits
5dc0c7058d
...
master
Author | SHA1 | Date | |
---|---|---|---|
2212d45f61 | |||
24665d6e57 | |||
79f4495a77 | |||
9df860b08b | |||
1e381bfab7 | |||
f6acee18e5 | |||
1b7e26a6b5 | |||
cf588f3fc2 |
@ -1,3 +1,5 @@
|
|||||||
|
when:
|
||||||
|
- event: [push, tag]
|
||||||
steps:
|
steps:
|
||||||
- name: requirements
|
- name: requirements
|
||||||
image: composer
|
image: composer
|
||||||
@ -8,7 +10,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- php examples/3teams.php
|
- php examples/3teams.php
|
||||||
- php examples/basic.php
|
- php examples/basic.php
|
||||||
- name: dependencies
|
- name: test
|
||||||
image: composer
|
image: composer
|
||||||
commands:
|
commands:
|
||||||
- composer install
|
- composer install
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.0",
|
"phpstan/phpstan": "^1.0",
|
||||||
"vimeo/psalm": "^5.21.1",
|
"vimeo/psalm": "dev-master",
|
||||||
"phpmetrics/phpmetrics": "^3.0-dev",
|
"phpmetrics/phpmetrics": "^3.0-dev",
|
||||||
"phpunit/phpunit": "^10.5",
|
"phpunit/phpunit": "^11.2",
|
||||||
"psalm/plugin-phpunit": "^0.18.4",
|
"psalm/plugin-phpunit": "^0.18.4",
|
||||||
"rector/rector": "^1.0",
|
"rector/rector": "^1.0",
|
||||||
"league/csv": "^9.0"
|
"league/csv": "^9.0"
|
||||||
@ -26,12 +26,14 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "phpunit",
|
"test": "phpunit",
|
||||||
"document": "phpDocumentor",
|
"document": "phpDocumentor",
|
||||||
"benchmark": "phpbench run --report=default --output=build-artifact",
|
"benchmark": "phpbench run --report=default --output=build-artifact",
|
||||||
"metrics": "vendor/bin/phpmetrics --config=phpmetrics.json",
|
"metrics": "vendor/bin/phpmetrics --config=phpmetrics.yml",
|
||||||
"lint": [
|
"lint": [
|
||||||
"phplint",
|
"phplint",
|
||||||
"phpcs"
|
"phpcs",
|
||||||
|
"phpcbf src/ tests/ benchmark/ examples/",
|
||||||
|
"phpmd src/,tests/,benchmark/,examples/ text phpmd.ruleset.xml"
|
||||||
],
|
],
|
||||||
"analyze": [
|
"analyze": [
|
||||||
"@analyze-phpstan",
|
"@analyze-phpstan",
|
||||||
@ -47,10 +49,9 @@
|
|||||||
],
|
],
|
||||||
"all": [
|
"all": [
|
||||||
"@test",
|
"@test",
|
||||||
"@document",
|
|
||||||
"@benchmark",
|
|
||||||
"@lint",
|
"@lint",
|
||||||
"@analyze",
|
"@analyze",
|
||||||
|
"@document",
|
||||||
"@metrics",
|
"@metrics",
|
||||||
"@html"
|
"@html"
|
||||||
]
|
]
|
||||||
|
1412
composer.lock
generated
1412
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,7 @@ $team2 = new Team($p2, $gameInfo->getDefaultRating());
|
|||||||
$team3 = new Team($p3, $gameInfo->getDefaultRating());
|
$team3 = new Team($p3, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
|
|
||||||
for($i = 0; $i < 5; ++$i) {
|
for ($i = 0; $i < 5; ++$i) {
|
||||||
echo "Iteration: " . $i . PHP_EOL;
|
echo "Iteration: " . $i . PHP_EOL;
|
||||||
$teams = [$team1, $team2, $team3];
|
$teams = [$team1, $team2, $team3];
|
||||||
|
|
||||||
@ -32,10 +32,7 @@ for($i = 0; $i < 5; ++$i) {
|
|||||||
$team2 = new Team($p2, $newRatings->getRating($p2));
|
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||||
$team3 = new Team($p3, $newRatings->getRating($p3));
|
$team3 = new Team($p3, $newRatings->getRating($p3));
|
||||||
|
|
||||||
echo "P1: ". $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
echo "P1: " . $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
||||||
echo "P2: ". $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
echo "P2: " . $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
||||||
echo "P3: ". $newRatings->getRating($p3)->getConservativeRating() . PHP_EOL;
|
echo "P3: " . $newRatings->getRating($p3)->getConservativeRating() . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ $team1 = new Team($p1, $gameInfo->getDefaultRating());
|
|||||||
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
$team2 = new Team($p2, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
|
|
||||||
for($i = 0; $i < 5; ++$i) {
|
for ($i = 0; $i < 5; ++$i) {
|
||||||
echo "Iteration: " . $i . PHP_EOL;
|
echo "Iteration: " . $i . PHP_EOL;
|
||||||
$teams = [$team1, $team2];
|
$teams = [$team1, $team2];
|
||||||
|
|
||||||
@ -30,9 +30,6 @@ for($i = 0; $i < 5; ++$i) {
|
|||||||
$team1 = new Team($p1, $newRatings->getRating($p1));
|
$team1 = new Team($p1, $newRatings->getRating($p1));
|
||||||
$team2 = new Team($p2, $newRatings->getRating($p2));
|
$team2 = new Team($p2, $newRatings->getRating($p2));
|
||||||
|
|
||||||
echo "P1: ". $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
echo "P1: " . $newRatings->getRating($p1)->getConservativeRating() . PHP_EOL;
|
||||||
echo "P2: ". $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
echo "P2: " . $newRatings->getRating($p2)->getConservativeRating() . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
90
examples/motogp/goat.php
Normal file
90
examples/motogp/goat.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__ . "/../../vendor/autoload.php";
|
||||||
|
|
||||||
|
use League\Csv\Reader;
|
||||||
|
use League\Csv\Statement;
|
||||||
|
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
||||||
|
use DNW\Skills\GameInfo;
|
||||||
|
use DNW\Skills\Player;
|
||||||
|
use DNW\Skills\Team;
|
||||||
|
use DNW\Skills\Teams;
|
||||||
|
|
||||||
|
//load the CSV document from a stream
|
||||||
|
$stream = fopen('motogp.csv', 'r');
|
||||||
|
$csv = Reader::createFromStream($stream);
|
||||||
|
$csv->setDelimiter(',');
|
||||||
|
$csv->setHeaderOffset(0);
|
||||||
|
|
||||||
|
//build a statement
|
||||||
|
$stmt = Statement::create()->where(static fn (array $record): bool => $record['category'] == "MotoGP" || $record['category'] == "500cc");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var $riders Player[]
|
||||||
|
*/
|
||||||
|
$riders = [];
|
||||||
|
//query your records from the document
|
||||||
|
$records = $stmt->process($csv);
|
||||||
|
|
||||||
|
$gameInfo = new GameInfo();
|
||||||
|
$calculator = new FactorGraphTrueSkillCalculator();
|
||||||
|
$first_record = $records->first();
|
||||||
|
$year_race = $first_record['year'] . '_' . $first_record['sequence'] . '_' . $first_record['category'];
|
||||||
|
|
||||||
|
$race_rate = [];
|
||||||
|
foreach ($records as $record) {
|
||||||
|
if ($year_race !== $record['year'] . '_' . $record['sequence'] . '_' . $record['category']) {
|
||||||
|
//Calculate the old race
|
||||||
|
$newRatings = $calculator->calculateNewRatings($gameInfo, $teams, $pos);
|
||||||
|
|
||||||
|
//update ratings
|
||||||
|
$highest_rate = 0;
|
||||||
|
$highest_rider = "";
|
||||||
|
foreach ($riders as $rider) {
|
||||||
|
//echo $rider['P']->getId().": ". $newRatings->getRating($rider['P'])->getConservativeRating() . PHP_EOL;
|
||||||
|
$rider['T']->setRating($rider['P'], $newRatings->getRating($rider['P']));
|
||||||
|
if ($newRatings->getRating($rider['P'])->getConservativeRating() > $highest_rate) {
|
||||||
|
$highest_rate = $newRatings->getRating($rider['P'])->getConservativeRating();
|
||||||
|
$highest_rider = $rider['P']->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo sprintf('Highest rider: %s => %s', $highest_rider, $highest_rate) . PHP_EOL;
|
||||||
|
|
||||||
|
foreach ($global_riders as $r) {
|
||||||
|
$rate = $r['T']->getRating($r['P'])->getConservativeRating();
|
||||||
|
|
||||||
|
$race_rate[$year_race][$r['P']->getId()] = $rate;
|
||||||
|
if (! isset($top_rating[$r['P']->getId()]) || $top_rating[$r['P']->getId()] < $rate) {
|
||||||
|
$top_rating[$r['P']->getId()] = $rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//prepare for next race
|
||||||
|
$year_race = $record['year'] . '_' . $record['sequence'] . '_' . $record['category'];
|
||||||
|
$races[] = ['year' => $record['year'], 'race' => $record['sequence'], 'circuit' => $record['circuit_name']];
|
||||||
|
echo "New Race: " . $year_race . ' => ' . $record['circuit_name'] . PHP_EOL;
|
||||||
|
$riders = [];
|
||||||
|
$teams = [];
|
||||||
|
$pos = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Is it a new rider?
|
||||||
|
if (! isset($global_riders[$record['rider']])) {
|
||||||
|
$global_riders[$record['rider']]['P'] = new Player($record['rider_name']);
|
||||||
|
$global_riders[$record['rider']]['T'] = new Team($global_riders[$record['rider']]['P'], $gameInfo->getDefaultRating());
|
||||||
|
//echo "New Rider: ". $record['rider'] . " => ".$global_riders[$record['rider']]['P']->getId().PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$riders[] = $global_riders[$record['rider']];
|
||||||
|
$teams[] = $global_riders[$record['rider']]['T'];
|
||||||
|
|
||||||
|
//Position or DNF?
|
||||||
|
$pos[] = $record['position'] >= 1 ? $record['position'] : end($pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All time top score" . PHP_EOL;
|
||||||
|
asort($top_rating);
|
||||||
|
foreach ($top_rating as $n => $r) {
|
||||||
|
echo sprintf('%s => %s', $n, $r) . PHP_EOL;
|
||||||
|
}
|
56397
examples/motogp/motogp.csv
Normal file
56397
examples/motogp/motogp.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/infection/infection/0.27.9/resources/schema.json",
|
"$schema": "https://raw.githubusercontent.com/infection/infection/0.29.6/resources/schema.json",
|
||||||
"source": {
|
"source": {
|
||||||
"directories": [
|
"directories": [
|
||||||
"src/"
|
"src/"
|
||||||
|
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"
|
@ -15,7 +15,7 @@ abstract class Factor
|
|||||||
*/
|
*/
|
||||||
private array $messages = [];
|
private array $messages = [];
|
||||||
|
|
||||||
private readonly HashMap $messageToVariableBinding;
|
private readonly HashMap $msgToVariableBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Variable[] $variables
|
* @var Variable[] $variables
|
||||||
@ -24,7 +24,7 @@ abstract class Factor
|
|||||||
|
|
||||||
protected function __construct()
|
protected function __construct()
|
||||||
{
|
{
|
||||||
$this->messageToVariableBinding = new HashMap();
|
$this->msgToVariableBinding = new HashMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,7 +70,7 @@ abstract class Factor
|
|||||||
{
|
{
|
||||||
Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
|
Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
|
||||||
$message = $this->messages[$messageIndex];
|
$message = $this->messages[$messageIndex];
|
||||||
$variable = $this->messageToVariableBinding->getValue($message);
|
$variable = $this->msgToVariableBinding->getValue($message);
|
||||||
|
|
||||||
return $this->updateMessageVariable($message, $variable);
|
return $this->updateMessageVariable($message, $variable);
|
||||||
}
|
}
|
||||||
@ -85,7 +85,7 @@ abstract class Factor
|
|||||||
*/
|
*/
|
||||||
public function resetMarginals(): void
|
public function resetMarginals(): void
|
||||||
{
|
{
|
||||||
$allValues = $this->messageToVariableBinding->getAllValues();
|
$allValues = $this->msgToVariableBinding->getAllValues();
|
||||||
foreach ($allValues as $currentVariable) {
|
foreach ($allValues as $currentVariable) {
|
||||||
$currentVariable->resetToPrior();
|
$currentVariable->resetToPrior();
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ abstract class Factor
|
|||||||
Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
|
Guard::argumentIsValidIndex($messageIndex, count($this->messages), 'messageIndex');
|
||||||
|
|
||||||
$message = $this->messages[$messageIndex];
|
$message = $this->messages[$messageIndex];
|
||||||
$variable = $this->messageToVariableBinding->getValue($message);
|
$variable = $this->msgToVariableBinding->getValue($message);
|
||||||
|
|
||||||
return $this->sendMessageVariable($message, $variable);
|
return $this->sendMessageVariable($message, $variable);
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ abstract class Factor
|
|||||||
|
|
||||||
protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message
|
protected function createVariableToMessageBindingWithMessage(Variable $variable, Message $message): Message
|
||||||
{
|
{
|
||||||
$this->messageToVariableBinding->setValue($message, $variable);
|
$this->msgToVariableBinding->setValue($message, $variable);
|
||||||
$this->messages[] = $message;
|
$this->messages[] = $message;
|
||||||
$this->variables[] = $variable;
|
$this->variables[] = $variable;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ abstract class FactorGraphLayer
|
|||||||
/**
|
/**
|
||||||
* @var array<int,array<int,Variable>>
|
* @var array<int,array<int,Variable>>
|
||||||
*/
|
*/
|
||||||
private array $outputVariablesGroups = [];
|
private array $outputVarGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<int,array<int,Variable>>
|
* @var array<int,array<int,Variable>>
|
||||||
@ -48,7 +48,7 @@ abstract class FactorGraphLayer
|
|||||||
*/
|
*/
|
||||||
public function &getOutputVariablesGroups(): array
|
public function &getOutputVariablesGroups(): array
|
||||||
{
|
{
|
||||||
return $this->outputVariablesGroups;
|
return $this->outputVarGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,12 +26,12 @@ class FactorList
|
|||||||
$listCount = count($this->list);
|
$listCount = count($this->list);
|
||||||
|
|
||||||
for ($i = 0; $i < $listCount; ++$i) {
|
for ($i = 0; $i < $listCount; ++$i) {
|
||||||
$f = $this->list[$i];
|
$factor = $this->list[$i];
|
||||||
|
|
||||||
$numberOfMessages = $f->getNumberOfMessages();
|
$numberOfMessages = $factor->getNumberOfMessages();
|
||||||
|
|
||||||
for ($j = 0; $j < $numberOfMessages; ++$j) {
|
for ($j = 0; $j < $numberOfMessages; ++$j) {
|
||||||
$sumLogZ += $f->sendMessageIndex($j);
|
$sumLogZ += $factor->sendMessageIndex($j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ class Variable
|
|||||||
{
|
{
|
||||||
private mixed $value;
|
private mixed $value;
|
||||||
|
|
||||||
public function __construct(private GaussianDistribution $prior)
|
public function __construct(private readonly GaussianDistribution $prior)
|
||||||
{
|
{
|
||||||
$this->resetToPrior();
|
$this->resetToPrior();
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,20 @@ namespace DNW\Skills\FactorGraphs;
|
|||||||
|
|
||||||
class VariableFactory
|
class VariableFactory
|
||||||
{
|
{
|
||||||
public function __construct(private \Closure $variablePriorInitializer)
|
public function __construct(private readonly \Closure $varPriorInitializer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createBasicVariable(): Variable
|
public function createBasicVariable(): Variable
|
||||||
{
|
{
|
||||||
$initializer = $this->variablePriorInitializer;
|
$initializer = $this->varPriorInitializer;
|
||||||
|
|
||||||
return new Variable($initializer());
|
return new Variable($initializer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createKeyedVariable(mixed $key): KeyedVariable
|
public function createKeyedVariable(mixed $key): KeyedVariable
|
||||||
{
|
{
|
||||||
$initializer = $this->variablePriorInitializer;
|
$initializer = $this->varPriorInitializer;
|
||||||
|
|
||||||
return new KeyedVariable($key, $initializer());
|
return new KeyedVariable($key, $initializer());
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ class GameInfo
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN,
|
private readonly float $initialMean = self::DEFAULT_INITIAL_MEAN,
|
||||||
private readonly float $initialStandardDeviation = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
|
private readonly float $initialStdDev = self::DEFAULT_INITIAL_STANDARD_DEVIATION,
|
||||||
private readonly float $beta = self::DEFAULT_BETA,
|
private readonly float $beta = self::DEFAULT_BETA,
|
||||||
private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
|
private readonly float $dynamicsFactor = self::DEFAULT_DYNAMICS_FACTOR,
|
||||||
private readonly float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
|
private readonly float $drawProbability = self::DEFAULT_DRAW_PROBABILITY
|
||||||
@ -42,7 +42,7 @@ class GameInfo
|
|||||||
|
|
||||||
public function getInitialStandardDeviation(): float
|
public function getInitialStandardDeviation(): float
|
||||||
{
|
{
|
||||||
return $this->initialStandardDeviation;
|
return $this->initialStdDev;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBeta(): float
|
public function getBeta(): float
|
||||||
@ -62,6 +62,6 @@ class GameInfo
|
|||||||
|
|
||||||
public function getDefaultRating(): Rating
|
public function getDefaultRating(): Rating
|
||||||
{
|
{
|
||||||
return new Rating($this->initialMean, $this->initialStandardDeviation);
|
return new Rating($this->initialMean, $this->initialStdDev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,15 +13,15 @@ namespace DNW\Skills\Numerics;
|
|||||||
class BasicMath
|
class BasicMath
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Squares the input (x^2 = x * x)
|
* Squares the input (input^2 = input * input)
|
||||||
*
|
*
|
||||||
* @param $x Value to square (x)
|
* @param $input Value to square (input)
|
||||||
*
|
*
|
||||||
* @return float The squared value (x^2)
|
* @return float The squared value (input^2)
|
||||||
*/
|
*/
|
||||||
public static function square(float $x): float
|
public static function square(float $input): float
|
||||||
{
|
{
|
||||||
return $x * $x;
|
return $input * $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,7 +55,6 @@ class GaussianDistribution
|
|||||||
$this->precisionMean = $this->precision * $this->mean;
|
$this->precisionMean = $this->precision * $this->mean;
|
||||||
} else {
|
} else {
|
||||||
$this->precision = \INF;
|
$this->precision = \INF;
|
||||||
|
|
||||||
$this->precisionMean = $this->mean == 0 ? 0 : \INF;
|
$this->precisionMean = $this->mean == 0 ? 0 : \INF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +174,7 @@ class GaussianDistribution
|
|||||||
BasicMath::square($meanDifference) / (2 * $varianceDifference);
|
BasicMath::square($meanDifference) / (2 * $varianceDifference);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function at(float $x, float $mean = 0.0, float $standardDeviation = 1.0): float
|
public static function at(float $var, float $mean = 0.0, float $standardDeviation = 1.0): float
|
||||||
{
|
{
|
||||||
// See http://mathworld.wolfram.com/NormalDistribution.html
|
// See http://mathworld.wolfram.com/NormalDistribution.html
|
||||||
// 1 -(x-mean)^2 / (2*stdDev^2)
|
// 1 -(x-mean)^2 / (2*stdDev^2)
|
||||||
@ -183,22 +182,22 @@ class GaussianDistribution
|
|||||||
// stdDev * sqrt(2*pi)
|
// stdDev * sqrt(2*pi)
|
||||||
|
|
||||||
$multiplier = 1.0 / ($standardDeviation * self::M_SQRT_2_PI);
|
$multiplier = 1.0 / ($standardDeviation * self::M_SQRT_2_PI);
|
||||||
$expPart = exp((-1.0 * BasicMath::square($x - $mean)) / (2 * BasicMath::square($standardDeviation)));
|
$expPart = exp((-1.0 * BasicMath::square($var - $mean)) / (2 * BasicMath::square($standardDeviation)));
|
||||||
|
|
||||||
return $multiplier * $expPart;
|
return $multiplier * $expPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function cumulativeTo(float $x): float
|
public static function cumulativeTo(float $var): float
|
||||||
{
|
{
|
||||||
$result = GaussianDistribution::errorFunctionCumulativeTo(-M_SQRT1_2 * $x);
|
$result = GaussianDistribution::errorFunctionCumulativeTo(-M_SQRT1_2 * $var);
|
||||||
|
|
||||||
return 0.5 * $result;
|
return 0.5 * $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function errorFunctionCumulativeTo(float $x): float
|
private static function errorFunctionCumulativeTo(float $var): float
|
||||||
{
|
{
|
||||||
// Derived from page 265 of Numerical Recipes 3rd Edition
|
// Derived from page 265 of Numerical Recipes 3rd Edition
|
||||||
$z = abs($x);
|
$z = abs($var);
|
||||||
|
|
||||||
$t = 2.0 / (2.0 + $z);
|
$t = 2.0 / (2.0 + $z);
|
||||||
$ty = 4 * $t - 2;
|
$ty = 4 * $t - 2;
|
||||||
@ -246,7 +245,7 @@ class GaussianDistribution
|
|||||||
|
|
||||||
$ans = $t * exp(-$z * $z + 0.5 * ($coefficients[0] + $ty * $d) - $dd);
|
$ans = $t * exp(-$z * $z + 0.5 * ($coefficients[0] + $ty * $d) - $dd);
|
||||||
|
|
||||||
return ($x >= 0.0) ? $ans : (2.0 - $ans);
|
return ($var >= 0.0) ? $ans : (2.0 - $ans);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function inverseErrorFunctionCumulativeTo(float $p): float
|
private static function inverseErrorFunctionCumulativeTo(float $p): float
|
||||||
@ -272,9 +271,9 @@ class GaussianDistribution
|
|||||||
return ($p < 1.0) ? $x : -$x;
|
return ($p < 1.0) ? $x : -$x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function inverseCumulativeTo(float $x, float $mean = 0.0, float $standardDeviation = 1.0): float
|
public static function inverseCumulativeTo(float $var, float $mean = 0.0, float $standardDeviation = 1.0): float
|
||||||
{
|
{
|
||||||
// From numerical recipes, page 320
|
// From numerical recipes, page 320
|
||||||
return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2 * $x);
|
return $mean - M_SQRT2 * $standardDeviation * GaussianDistribution::inverseErrorFunctionCumulativeTo(2 * $var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,13 +60,34 @@ class Matrix
|
|||||||
return $this->columnCount;
|
return $this->columnCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkRowCol(int $row, int $col): void
|
||||||
|
{
|
||||||
|
if ($row < 0) {
|
||||||
|
throw new Exception("Row negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row >= $this->getRowCount()) {
|
||||||
|
throw new Exception("Row beyond range");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($col < 0) {
|
||||||
|
throw new Exception("Column negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($col >= $this->getColumnCount()) {
|
||||||
|
throw new Exception("Column beyond range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getValue(int $row, int $col): float|int
|
public function getValue(int $row, int $col): float|int
|
||||||
{
|
{
|
||||||
|
$this->checkRowCol($row, $col);
|
||||||
return $this->matrixRowData[$row][$col];
|
return $this->matrixRowData[$row][$col];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setValue(int $row, int $col, float|int $value): void
|
public function setValue(int $row, int $col, float|int $value): void
|
||||||
{
|
{
|
||||||
|
$this->checkRowCol($row, $col);
|
||||||
$this->matrixRowData[$row][$col] = $value;
|
$this->matrixRowData[$row][$col] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,10 +97,10 @@ class Matrix
|
|||||||
$transposeMatrix = [];
|
$transposeMatrix = [];
|
||||||
|
|
||||||
$rowMatrixData = $this->matrixRowData;
|
$rowMatrixData = $this->matrixRowData;
|
||||||
for ($currentRowTransposeMatrix = 0; $currentRowTransposeMatrix < $this->columnCount; ++$currentRowTransposeMatrix) {
|
for ($curRowTransposeMx = 0; $curRowTransposeMx < $this->columnCount; ++$curRowTransposeMx) {
|
||||||
for ($currentColumnTransposeMatrix = 0; $currentColumnTransposeMatrix < $this->rowCount; ++$currentColumnTransposeMatrix) {
|
for ($curColTransposeMx = 0; $curColTransposeMx < $this->rowCount; ++$curColTransposeMx) {
|
||||||
$transposeMatrix[$currentRowTransposeMatrix][$currentColumnTransposeMatrix] =
|
$transposeMatrix[$curRowTransposeMx][$curColTransposeMx] =
|
||||||
$rowMatrixData[$currentColumnTransposeMatrix][$currentRowTransposeMatrix];
|
$rowMatrixData[$curColTransposeMx][$curRowTransposeMx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +121,7 @@ class Matrix
|
|||||||
|
|
||||||
if ($this->rowCount == 1) {
|
if ($this->rowCount == 1) {
|
||||||
// Really happy path :)
|
// Really happy path :)
|
||||||
return $this->matrixRowData[0][0];
|
return $this->getValue(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->rowCount == 2) {
|
if ($this->rowCount == 2) {
|
||||||
@ -109,10 +130,10 @@ class Matrix
|
|||||||
// | a b |
|
// | a b |
|
||||||
// | c d |
|
// | c d |
|
||||||
// The determinant is ad - bc
|
// The determinant is ad - bc
|
||||||
$a = $this->matrixRowData[0][0];
|
$a = $this->getValue(0, 0);
|
||||||
$b = $this->matrixRowData[0][1];
|
$b = $this->getValue(0, 1);
|
||||||
$c = $this->matrixRowData[1][0];
|
$c = $this->getValue(1, 0);
|
||||||
$d = $this->matrixRowData[1][1];
|
$d = $this->getValue(1, 1);
|
||||||
|
|
||||||
return $a * $d - $b * $c;
|
return $a * $d - $b * $c;
|
||||||
}
|
}
|
||||||
@ -127,7 +148,7 @@ class Matrix
|
|||||||
|
|
||||||
// I expand along the first row
|
// I expand along the first row
|
||||||
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
||||||
$firstRowColValue = $this->matrixRowData[0][$currentColumn];
|
$firstRowColValue = $this->getValue(0, $currentColumn);
|
||||||
$cofactor = $this->getCofactor(0, $currentColumn);
|
$cofactor = $this->getCofactor(0, $currentColumn);
|
||||||
$itemToAdd = $firstRowColValue * $cofactor;
|
$itemToAdd = $firstRowColValue * $cofactor;
|
||||||
$result += $itemToAdd;
|
$result += $itemToAdd;
|
||||||
@ -152,10 +173,10 @@ class Matrix
|
|||||||
// | d -b |
|
// | d -b |
|
||||||
// | -c a |
|
// | -c a |
|
||||||
|
|
||||||
$a = $this->matrixRowData[0][0];
|
$a = $this->getValue(0, 0);
|
||||||
$b = $this->matrixRowData[0][1];
|
$b = $this->getValue(0, 1);
|
||||||
$c = $this->matrixRowData[1][0];
|
$c = $this->getValue(1, 0);
|
||||||
$d = $this->matrixRowData[1][1];
|
$d = $this->getValue(1, 1);
|
||||||
|
|
||||||
return new SquareMatrix(
|
return new SquareMatrix(
|
||||||
$d,
|
$d,
|
||||||
@ -180,7 +201,7 @@ class Matrix
|
|||||||
public function getInverse(): Matrix|SquareMatrix
|
public function getInverse(): Matrix|SquareMatrix
|
||||||
{
|
{
|
||||||
if (($this->rowCount == 1) && ($this->columnCount == 1)) {
|
if (($this->rowCount == 1) && ($this->columnCount == 1)) {
|
||||||
return new SquareMatrix(1.0 / $this->matrixRowData[0][0]);
|
return new SquareMatrix(1.0 / $this->getValue(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the simple approach:
|
// Take the simple approach:
|
||||||
@ -262,6 +283,7 @@ class Matrix
|
|||||||
|
|
||||||
private function getMinorMatrix(int $rowToRemove, int $columnToRemove): Matrix
|
private function getMinorMatrix(int $rowToRemove, int $columnToRemove): Matrix
|
||||||
{
|
{
|
||||||
|
$this->checkRowCol($rowToRemove, $columnToRemove);
|
||||||
// See http://en.wikipedia.org/wiki/Minor_(linear_algebra)
|
// See http://en.wikipedia.org/wiki/Minor_(linear_algebra)
|
||||||
|
|
||||||
// I'm going to use a horribly naïve algorithm... because I can :)
|
// I'm going to use a horribly naïve algorithm... because I can :)
|
||||||
@ -281,7 +303,7 @@ class Matrix
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result[$actualRow][$actualCol] = $this->matrixRowData[$currentRow][$currentColumn];
|
$result[$actualRow][$actualCol] = $this->getValue($currentRow, $currentColumn);
|
||||||
|
|
||||||
++$actualCol;
|
++$actualCol;
|
||||||
}
|
}
|
||||||
@ -294,6 +316,7 @@ class Matrix
|
|||||||
|
|
||||||
public function getCofactor(int $rowToRemove, int $columnToRemove): float
|
public function getCofactor(int $rowToRemove, int $columnToRemove): float
|
||||||
{
|
{
|
||||||
|
$this->checkRowCol($rowToRemove, $columnToRemove);
|
||||||
// See http://en.wikipedia.org/wiki/Cofactor_(linear_algebra) for details
|
// See http://en.wikipedia.org/wiki/Cofactor_(linear_algebra) for details
|
||||||
// REVIEW: should things be reversed since I'm 0 indexed?
|
// REVIEW: should things be reversed since I'm 0 indexed?
|
||||||
$sum = $rowToRemove + $columnToRemove;
|
$sum = $rowToRemove + $columnToRemove;
|
||||||
@ -316,7 +339,7 @@ class Matrix
|
|||||||
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) {
|
||||||
$delta =
|
$delta =
|
||||||
abs(
|
abs(
|
||||||
$this->matrixRowData[$currentRow][$currentColumn] -
|
$this->getValue($currentRow, $currentColumn) -
|
||||||
$otherMatrix->getValue($currentRow, $currentColumn)
|
$otherMatrix->getValue($currentRow, $currentColumn)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8,14 +8,14 @@ class PartialPlay
|
|||||||
{
|
{
|
||||||
public static function getPartialPlayPercentage(Player $player): float
|
public static function getPartialPlayPercentage(Player $player): float
|
||||||
{
|
{
|
||||||
$partialPlayPercentage = $player->getPartialPlayPercentage();
|
$partialPlayPct = $player->getPartialPlayPercentage();
|
||||||
|
|
||||||
// HACK to get around bug near 0
|
// HACK to get around bug near 0
|
||||||
$smallestPercentage = 0.0001;
|
$smallestPct = 0.0001;
|
||||||
if ($partialPlayPercentage < $smallestPercentage) {
|
if ($partialPlayPct < $smallestPct) {
|
||||||
return $smallestPercentage;
|
return $smallestPct;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $partialPlayPercentage;
|
return $partialPlayPct;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,27 +13,27 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate
|
|||||||
|
|
||||||
private const DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0;
|
private const DEFAULT_PARTIAL_UPDATE_PERCENTAGE = 1.0;
|
||||||
|
|
||||||
private readonly float $PartialPlayPercentage;
|
private readonly float $PartialPlayPct;
|
||||||
|
|
||||||
private readonly float $PartialUpdatePercentage;
|
private readonly float $PartialUpdatePct;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a player.
|
* Constructs a player.
|
||||||
*
|
*
|
||||||
* @param string|int $Id The identifier for the player, such as a name.
|
* @param string|int $Id The identifier for the player, such as a name.
|
||||||
* @param float $partialPlayPercentage The weight percentage to give this player when calculating a new rank.
|
* @param float $partialPlayPct The weight percentage to give this player when calculating a new rank.
|
||||||
* @param float $partialUpdatePercentage Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.
|
* @param float $partialUpdatePct Indicated how much of a skill update a player should receive where 0 represents no update and 1.0 represents 100% of the update.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly mixed $Id,
|
private readonly mixed $Id,
|
||||||
float $partialPlayPercentage = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE,
|
float $partialPlayPct = self::DEFAULT_PARTIAL_PLAY_PERCENTAGE,
|
||||||
float $partialUpdatePercentage = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE
|
float $partialUpdatePct = self::DEFAULT_PARTIAL_UPDATE_PERCENTAGE
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Guard::argumentInRangeInclusive($partialPlayPercentage, 0.0, 1.0, 'partialPlayPercentage');
|
Guard::argumentInRangeInclusive($partialPlayPct, 0.0, 1.0, 'partialPlayPercentage');
|
||||||
Guard::argumentInRangeInclusive($partialUpdatePercentage, 0, 1.0, 'partialUpdatePercentage');
|
Guard::argumentInRangeInclusive($partialUpdatePct, 0, 1.0, 'partialUpdatePercentage');
|
||||||
$this->PartialPlayPercentage = $partialPlayPercentage;
|
$this->PartialPlayPct = $partialPlayPct;
|
||||||
$this->PartialUpdatePercentage = $partialUpdatePercentage;
|
$this->PartialUpdatePct = $partialUpdatePct;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +49,7 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate
|
|||||||
*/
|
*/
|
||||||
public function getPartialPlayPercentage(): float
|
public function getPartialPlayPercentage(): float
|
||||||
{
|
{
|
||||||
return $this->PartialPlayPercentage;
|
return $this->PartialPlayPct;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,6 +57,6 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate
|
|||||||
*/
|
*/
|
||||||
public function getPartialUpdatePercentage(): float
|
public function getPartialUpdatePercentage(): float
|
||||||
{
|
{
|
||||||
return $this->PartialUpdatePercentage;
|
return $this->PartialUpdatePct;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ abstract class SkillCalculator
|
|||||||
|
|
||||||
public function isSupported(int $option): bool
|
public function isSupported(int $option): bool
|
||||||
{
|
{
|
||||||
return (bool)($this->supportedOptions & $option) == $option;
|
return ($this->supportedOptions & $option) === $option;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,7 +64,7 @@ abstract class SkillCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<\DNW\Skills\Team> $teams
|
* @param Team[] $teams
|
||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
|
@ -171,7 +171,8 @@ class FactorGraphTrueSkillCalculator extends SkillCalculator
|
|||||||
|
|
||||||
$currentColumn = 0;
|
$currentColumn = 0;
|
||||||
|
|
||||||
for ($i = 0; $i < count($teamAssignmentsList) - 1; ++$i) {
|
$teamCnt = count($teamAssignmentsList);
|
||||||
|
for ($i = 0; $i < $teamCnt - 1; ++$i) {
|
||||||
$currentTeam = $teamAssignmentsList[$i];
|
$currentTeam = $teamAssignmentsList[$i];
|
||||||
|
|
||||||
// Need to add in 0's for all the previous players, since they're not
|
// Need to add in 0's for all the previous players, since they're not
|
||||||
|
@ -60,9 +60,9 @@ class GaussianLikelihoodFactor extends GaussianFactor
|
|||||||
$a * ($marginal2->getPrecision() - $message2Value->getPrecision())
|
$a * ($marginal2->getPrecision() - $message2Value->getPrecision())
|
||||||
);
|
);
|
||||||
|
|
||||||
$oldMarginalWithoutMessage = GaussianDistribution::divide($marginal1, $message1Value);
|
$oldMarginalWithoutMsg = GaussianDistribution::divide($marginal1, $message1Value);
|
||||||
|
|
||||||
$newMarginal = GaussianDistribution::multiply($oldMarginalWithoutMessage, $newMessage);
|
$newMarginal = GaussianDistribution::multiply($oldMarginalWithoutMsg, $newMessage);
|
||||||
|
|
||||||
// Update the message and marginal
|
// Update the message and marginal
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ use DNW\Skills\Numerics\GaussianDistribution;
|
|||||||
class GaussianWeightedSumFactor extends GaussianFactor
|
class GaussianWeightedSumFactor extends GaussianFactor
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var array<int[]> $variableIndexOrdersForWeights
|
* @var array<int[]> $varIndexOrdersForWeights
|
||||||
*/
|
*/
|
||||||
private array $variableIndexOrdersForWeights = [];
|
private array $varIndexOrdersForWeights = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This following is used for convenience, for example, the first entry is [0, 1, 2]
|
* This following is used for convenience, for example, the first entry is [0, 1, 2]
|
||||||
@ -58,9 +58,9 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
$variablesToSumLength = count($variablesToSum);
|
$variablesToSumLength = count($variablesToSum);
|
||||||
|
|
||||||
// 0..n-1
|
// 0..n-1
|
||||||
$this->variableIndexOrdersForWeights[0] = [];
|
$this->varIndexOrdersForWeights[0] = [];
|
||||||
for ($i = 0; $i < ($variablesToSumLength + 1); ++$i) {
|
for ($i = 0; $i < ($variablesToSumLength + 1); ++$i) {
|
||||||
$this->variableIndexOrdersForWeights[0][] = $i;
|
$this->varIndexOrdersForWeights[0][] = $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
$variableWeightsLength = count($variableWeights);
|
$variableWeightsLength = count($variableWeights);
|
||||||
@ -113,7 +113,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
$currentWeights[$currentDestinationWeightIndex] = $finalWeight;
|
$currentWeights[$currentDestinationWeightIndex] = $finalWeight;
|
||||||
$currentWeightsSquared[$currentDestinationWeightIndex] = BasicMath::square($finalWeight);
|
$currentWeightsSquared[$currentDestinationWeightIndex] = BasicMath::square($finalWeight);
|
||||||
$variableIndices[count($variableWeights)] = 0;
|
$variableIndices[count($variableWeights)] = 0;
|
||||||
$this->variableIndexOrdersForWeights[] = $variableIndices;
|
$this->varIndexOrdersForWeights[] = $variableIndices;
|
||||||
|
|
||||||
$this->weights[$weightsIndex] = $currentWeights;
|
$this->weights[$weightsIndex] = $currentWeights;
|
||||||
$this->weightsSquared[$weightsIndex] = $currentWeightsSquared;
|
$this->weightsSquared[$weightsIndex] = $currentWeightsSquared;
|
||||||
@ -160,9 +160,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
|
|
||||||
// The math works out so that 1/newPrecision = sum of a_i^2 /marginalsWithoutMessages[i]
|
// The math works out so that 1/newPrecision = sum of a_i^2 /marginalsWithoutMessages[i]
|
||||||
$inverseOfNewPrecisionSum = 0.0;
|
$inverseOfNewPrecisionSum = 0.0;
|
||||||
$anotherInverseOfNewPrecisionSum = 0.0;
|
|
||||||
$weightedMeanSum = 0.0;
|
$weightedMeanSum = 0.0;
|
||||||
$anotherWeightedMeanSum = 0.0;
|
|
||||||
|
|
||||||
$weightsSquaredLength = count($weightsSquared);
|
$weightsSquaredLength = count($weightsSquared);
|
||||||
|
|
||||||
@ -172,16 +170,11 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
$inverseOfNewPrecisionSum += $weightsSquared[$i] /
|
$inverseOfNewPrecisionSum += $weightsSquared[$i] /
|
||||||
($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
|
($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
|
||||||
|
|
||||||
$diff = GaussianDistribution::divide($variables[$i + 1]->getValue(), $messages[$i + 1]->getValue());
|
|
||||||
$anotherInverseOfNewPrecisionSum += $weightsSquared[$i] / $diff->getPrecision();
|
|
||||||
|
|
||||||
$weightedMeanSum += $weights[$i]
|
$weightedMeanSum += $weights[$i]
|
||||||
*
|
*
|
||||||
($variables[$i + 1]->getValue()->getPrecisionMean() - $messages[$i + 1]->getValue()->getPrecisionMean())
|
($variables[$i + 1]->getValue()->getPrecisionMean() - $messages[$i + 1]->getValue()->getPrecisionMean())
|
||||||
/
|
/
|
||||||
($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
|
($variables[$i + 1]->getValue()->getPrecision() - $messages[$i + 1]->getValue()->getPrecision());
|
||||||
|
|
||||||
$anotherWeightedMeanSum += $weights[$i] * $diff->getPrecisionMean() / $diff->getPrecision();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$newPrecision = 1.0 / $inverseOfNewPrecisionSum;
|
$newPrecision = 1.0 / $inverseOfNewPrecisionSum;
|
||||||
@ -214,7 +207,7 @@ class GaussianWeightedSumFactor extends GaussianFactor
|
|||||||
$updatedMessages = [];
|
$updatedMessages = [];
|
||||||
$updatedVariables = [];
|
$updatedVariables = [];
|
||||||
|
|
||||||
$indicesToUse = $this->variableIndexOrdersForWeights[$messageIndex];
|
$indicesToUse = $this->varIndexOrdersForWeights[$messageIndex];
|
||||||
// The tricky part here is that we have to put the messages and variables in the same
|
// The tricky part here is that we have to put the messages and variables in the same
|
||||||
// order as the weights. Thankfully, the weights and messages share the same index numbers,
|
// order as the weights. Thankfully, the weights and messages share the same index numbers,
|
||||||
// so we just need to make sure they're consistent
|
// so we just need to make sure they're consistent
|
||||||
|
@ -15,8 +15,8 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
TrueSkillFactorGraph $parentGraph,
|
TrueSkillFactorGraph $parentGraph,
|
||||||
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $TeamPerformancesToTeamPerformanceDifferencesLayer,
|
private readonly TeamPerformancesToTeamPerformanceDifferencesLayer $teamPerformancesToTeamPerformanceDifferencesLayer,
|
||||||
private readonly TeamDifferencesComparisonLayer $TeamDifferencesComparisonLayer
|
private readonly TeamDifferencesComparisonLayer $teamDifferencesComparisonLayer
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
parent::__construct($parentGraph);
|
parent::__construct($parentGraph);
|
||||||
@ -25,20 +25,20 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
|||||||
public function getLocalFactors(): array
|
public function getLocalFactors(): array
|
||||||
{
|
{
|
||||||
return array_merge(
|
return array_merge(
|
||||||
$this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(),
|
$this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors(),
|
||||||
$this->TeamDifferencesComparisonLayer->getLocalFactors()
|
$this->teamDifferencesComparisonLayer->getLocalFactors()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildLayer(): void
|
public function buildLayer(): void
|
||||||
{
|
{
|
||||||
$inputVariablesGroups = $this->getInputVariablesGroups();
|
$inputVariablesGroups = $this->getInputVariablesGroups();
|
||||||
$this->TeamPerformancesToTeamPerformanceDifferencesLayer->setInputVariablesGroups($inputVariablesGroups);
|
$this->teamPerformancesToTeamPerformanceDifferencesLayer->setInputVariablesGroups($inputVariablesGroups);
|
||||||
$this->TeamPerformancesToTeamPerformanceDifferencesLayer->buildLayer();
|
$this->teamPerformancesToTeamPerformanceDifferencesLayer->buildLayer();
|
||||||
|
|
||||||
$teamDifferencesOutputVariablesGroups = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getOutputVariablesGroups();
|
$teamDifferencesOutputVariablesGroups = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getOutputVariablesGroups();
|
||||||
$this->TeamDifferencesComparisonLayer->setInputVariablesGroups($teamDifferencesOutputVariablesGroups);
|
$this->teamDifferencesComparisonLayer->setInputVariablesGroups($teamDifferencesOutputVariablesGroups);
|
||||||
$this->TeamDifferencesComparisonLayer->buildLayer();
|
$this->teamDifferencesComparisonLayer->buildLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPriorSchedule(): ?ScheduleSequence
|
public function createPriorSchedule(): ?ScheduleSequence
|
||||||
@ -56,9 +56,9 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When dealing with differences, there are always (n-1) differences, so add in the 1
|
// When dealing with differences, there are always (n-1) differences, so add in the 1
|
||||||
$totalTeamDifferences = count($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
|
$totalTeamDifferences = count($this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
|
||||||
|
|
||||||
$localFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
$localFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||||
|
|
||||||
$firstDifferencesFactor = $localFactors[0];
|
$firstDifferencesFactor = $localFactors[0];
|
||||||
$lastDifferencesFactor = $localFactors[$totalTeamDifferences - 1];
|
$lastDifferencesFactor = $localFactors[$totalTeamDifferences - 1];
|
||||||
@ -83,8 +83,8 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
|||||||
|
|
||||||
private function createTwoTeamInnerPriorLoopSchedule(): ScheduleSequence
|
private function createTwoTeamInnerPriorLoopSchedule(): ScheduleSequence
|
||||||
{
|
{
|
||||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||||
$teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
|
$teamDifferencesComparisonLayerLocalFactors = $this->teamDifferencesComparisonLayer->getLocalFactors();
|
||||||
|
|
||||||
$firstPerfToTeamDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[0];
|
$firstPerfToTeamDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[0];
|
||||||
$firstTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[0];
|
$firstTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[0];
|
||||||
@ -109,13 +109,13 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
|||||||
|
|
||||||
private function createMultipleTeamInnerPriorLoopSchedule(): ScheduleLoop
|
private function createMultipleTeamInnerPriorLoopSchedule(): ScheduleLoop
|
||||||
{
|
{
|
||||||
$totalTeamDifferences = count($this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
|
$totalTeamDifferences = count($this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors());
|
||||||
|
|
||||||
$forwardScheduleList = [];
|
$forwardScheduleList = [];
|
||||||
|
|
||||||
for ($i = 0; $i < $totalTeamDifferences - 1; ++$i) {
|
for ($i = 0; $i < $totalTeamDifferences - 1; ++$i) {
|
||||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||||
$teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
|
$teamDifferencesComparisonLayerLocalFactors = $this->teamDifferencesComparisonLayer->getLocalFactors();
|
||||||
|
|
||||||
$currentTeamPerfToTeamPerfDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$i];
|
$currentTeamPerfToTeamPerfDiff = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$i];
|
||||||
$currentTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[$i];
|
$currentTeamDiffComparison = $teamDifferencesComparisonLayerLocalFactors[$i];
|
||||||
@ -151,8 +151,8 @@ class IteratedTeamDifferencesInnerLayer extends TrueSkillFactorGraphLayer
|
|||||||
$backwardScheduleList = [];
|
$backwardScheduleList = [];
|
||||||
|
|
||||||
for ($i = 0; $i < $totalTeamDifferences - 1; ++$i) {
|
for ($i = 0; $i < $totalTeamDifferences - 1; ++$i) {
|
||||||
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->TeamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
$teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors = $this->teamPerformancesToTeamPerformanceDifferencesLayer->getLocalFactors();
|
||||||
$teamDifferencesComparisonLayerLocalFactors = $this->TeamDifferencesComparisonLayer->getLocalFactors();
|
$teamDifferencesComparisonLayerLocalFactors = $this->teamDifferencesComparisonLayer->getLocalFactors();
|
||||||
|
|
||||||
$differencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i];
|
$differencesFactor = $teamPerformancesToTeamPerformanceDifferencesLayerLocalFactors[$totalTeamDifferences - 1 - $i];
|
||||||
$comparisonFactor = $teamDifferencesComparisonLayerLocalFactors[$totalTeamDifferences - 1 - $i];
|
$comparisonFactor = $teamDifferencesComparisonLayerLocalFactors[$totalTeamDifferences - 1 - $i];
|
||||||
|
@ -27,8 +27,8 @@ class PlayerPerformancesToTeamPerformancesLayer extends TrueSkillFactorGraphLaye
|
|||||||
$this->addLayerFactor($newSumFactor);
|
$this->addLayerFactor($newSumFactor);
|
||||||
|
|
||||||
// REVIEW: Does it make sense to have groups of one?
|
// REVIEW: Does it make sense to have groups of one?
|
||||||
$outputVariablesGroups = &$this->getOutputVariablesGroups();
|
$outputVarGroups = &$this->getOutputVariablesGroups();
|
||||||
$outputVariablesGroups[] = [$teamPerformance];
|
$outputVarGroups[] = [$teamPerformance];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,12 +34,12 @@ class PlayerPriorValuesToSkillsLayer extends TrueSkillFactorGraphLayer
|
|||||||
$localCurrentTeam = $currentTeam;
|
$localCurrentTeam = $currentTeam;
|
||||||
$currentTeamSkills = [];
|
$currentTeamSkills = [];
|
||||||
|
|
||||||
$currentTeamAllPlayers = $localCurrentTeam->getAllPlayers();
|
$curTeamAllPlayers = $localCurrentTeam->getAllPlayers();
|
||||||
foreach ($currentTeamAllPlayers as $currentTeamPlayer) {
|
foreach ($curTeamAllPlayers as $curTeamPlayer) {
|
||||||
$localCurrentTeamPlayer = $currentTeamPlayer;
|
$localCurTeamPlayer = $curTeamPlayer;
|
||||||
$currentTeamPlayerRating = $currentTeam->getRating($localCurrentTeamPlayer);
|
$curTeamPlayerRating = $currentTeam->getRating($localCurTeamPlayer);
|
||||||
$playerSkill = $this->createSkillOutputVariable($localCurrentTeamPlayer);
|
$playerSkill = $this->createSkillOutputVariable($localCurTeamPlayer);
|
||||||
$priorFactor = $this->createPriorFactor($currentTeamPlayerRating, $playerSkill);
|
$priorFactor = $this->createPriorFactor($curTeamPlayerRating, $playerSkill);
|
||||||
$this->addLayerFactor($priorFactor);
|
$this->addLayerFactor($priorFactor);
|
||||||
$currentTeamSkills[] = $playerSkill;
|
$currentTeamSkills[] = $playerSkill;
|
||||||
}
|
}
|
||||||
|
@ -15,25 +15,25 @@ class PlayerSkillsToPerformancesLayer extends TrueSkillFactorGraphLayer
|
|||||||
{
|
{
|
||||||
public function buildLayer(): void
|
public function buildLayer(): void
|
||||||
{
|
{
|
||||||
$inputVariablesGroups = $this->getInputVariablesGroups();
|
$inputVarGroups = $this->getInputVariablesGroups();
|
||||||
$outputVariablesGroups = &$this->getOutputVariablesGroups();
|
$outputVarGroups = &$this->getOutputVariablesGroups();
|
||||||
|
|
||||||
foreach ($inputVariablesGroups as $currentTeam) {
|
foreach ($inputVarGroups as $currentTeam) {
|
||||||
$currentTeamPlayerPerformances = [];
|
$currentTeamPlayerPerformances = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Variable $playerSkillVariable
|
* @var Variable $playerSkillVar
|
||||||
*/
|
*/
|
||||||
foreach ($currentTeam as $playerSkillVariable) {
|
foreach ($currentTeam as $playerSkillVar) {
|
||||||
$localPlayerSkillVariable = $playerSkillVariable;
|
$localPlayerSkillVar = $playerSkillVar;
|
||||||
$currentPlayer = ($localPlayerSkillVariable instanceof KeyedVariable) ? $localPlayerSkillVariable->getKey() : "";
|
$currentPlayer = ($localPlayerSkillVar instanceof KeyedVariable) ? $localPlayerSkillVar->getKey() : "";
|
||||||
$playerPerformance = $this->createOutputVariable($currentPlayer);
|
$playerPerformance = $this->createOutputVariable($currentPlayer);
|
||||||
$newLikelihoodFactor = $this->createLikelihood($localPlayerSkillVariable, $playerPerformance);
|
$newLikelihoodFactor = $this->createLikelihood($localPlayerSkillVar, $playerPerformance);
|
||||||
$this->addLayerFactor($newLikelihoodFactor);
|
$this->addLayerFactor($newLikelihoodFactor);
|
||||||
$currentTeamPlayerPerformances[] = $playerPerformance;
|
$currentTeamPlayerPerformances[] = $playerPerformance;
|
||||||
}
|
}
|
||||||
|
|
||||||
$outputVariablesGroups[] = $currentTeamPlayerPerformances;
|
$outputVarGroups[] = $currentTeamPlayerPerformances;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class TrueSkillFactorGraph extends FactorGraph
|
|||||||
/**
|
/**
|
||||||
* @var FactorGraphLayer[] $layers
|
* @var FactorGraphLayer[] $layers
|
||||||
*/
|
*/
|
||||||
private array $layers;
|
private readonly array $layers;
|
||||||
|
|
||||||
private readonly PlayerPriorValuesToSkillsLayer $priorLayer;
|
private readonly PlayerPriorValuesToSkillsLayer $priorLayer;
|
||||||
|
|
||||||
@ -118,9 +118,9 @@ class TrueSkillFactorGraph extends FactorGraph
|
|||||||
$allLayersReverse = array_reverse($this->layers);
|
$allLayersReverse = array_reverse($this->layers);
|
||||||
|
|
||||||
foreach ($allLayersReverse as $currentLayer) {
|
foreach ($allLayersReverse as $currentLayer) {
|
||||||
$currentPosteriorSchedule = $currentLayer->createPosteriorSchedule();
|
$curPosteriorSchedule = $currentLayer->createPosteriorSchedule();
|
||||||
if ($currentPosteriorSchedule != NULL) {
|
if ($curPosteriorSchedule != NULL) {
|
||||||
$fullSchedule[] = $currentPosteriorSchedule;
|
$fullSchedule[] = $curPosteriorSchedule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,33 +102,33 @@ class TruncatedGaussianCorrectionFunctions
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the multiplicative correction of a double-sided truncated Gaussian with unit variance
|
// the multiplicative correction of a double-sided truncated Gaussian with unit variance
|
||||||
public static function wWithinMarginScaled(float $teamPerformanceDifference, float $drawMargin, float $c): float
|
public static function wWithinMarginScaled(float $teamPerformanceDiff, float $drawMargin, float $c): float
|
||||||
{
|
{
|
||||||
return self::wWithinMargin($teamPerformanceDifference / $c, $drawMargin / $c);
|
return self::wWithinMargin($teamPerformanceDiff / $c, $drawMargin / $c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// From F#:
|
// From F#:
|
||||||
public static function wWithinMargin(float $teamPerformanceDifference, float $drawMargin): float
|
public static function wWithinMargin(float $teamPerformanceDiff, float $drawMargin): float
|
||||||
{
|
{
|
||||||
$teamPerformanceDifferenceAbsoluteValue = abs($teamPerformanceDifference);
|
$teamPerformanceDiffAbsValue = abs($teamPerformanceDiff);
|
||||||
$denominator = GaussianDistribution::cumulativeTo($drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
$denominator = GaussianDistribution::cumulativeTo($drawMargin - $teamPerformanceDiffAbsValue)
|
||||||
-
|
-
|
||||||
GaussianDistribution::cumulativeTo(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue);
|
GaussianDistribution::cumulativeTo(-$drawMargin - $teamPerformanceDiffAbsValue);
|
||||||
|
|
||||||
if ($denominator < 2.222758749e-162) {
|
if ($denominator < 2.222758749e-162) {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$vt = self::vWithinMargin($teamPerformanceDifferenceAbsoluteValue, $drawMargin);
|
$vt = self::vWithinMargin($teamPerformanceDiffAbsValue, $drawMargin);
|
||||||
|
|
||||||
return $vt * $vt +
|
return $vt * $vt +
|
||||||
(($drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
(($drawMargin - $teamPerformanceDiffAbsValue)
|
||||||
*
|
*
|
||||||
GaussianDistribution::at(
|
GaussianDistribution::at(
|
||||||
$drawMargin - $teamPerformanceDifferenceAbsoluteValue
|
$drawMargin - $teamPerformanceDiffAbsValue
|
||||||
)
|
)
|
||||||
- (-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)
|
- (-$drawMargin - $teamPerformanceDiffAbsValue)
|
||||||
*
|
*
|
||||||
GaussianDistribution::at(-$drawMargin - $teamPerformanceDifferenceAbsoluteValue)) / $denominator;
|
GaussianDistribution::at(-$drawMargin - $teamPerformanceDiffAbsValue)) / $denominator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,9 +125,9 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
$selfTeamAllPlayers = $selfTeam->getAllPlayers();
|
$selfTeamAllPlayers = $selfTeam->getAllPlayers();
|
||||||
foreach ($selfTeamAllPlayers as $selfTeamCurrentPlayer) {
|
foreach ($selfTeamAllPlayers as $selfTeamCurPlayer) {
|
||||||
$localSelfTeamCurrentPlayer = $selfTeamCurrentPlayer;
|
$localSelfTeamCurPlayer = $selfTeamCurPlayer;
|
||||||
$previousPlayerRating = $selfTeam->getRating($localSelfTeamCurrentPlayer);
|
$previousPlayerRating = $selfTeam->getRating($localSelfTeamCurPlayer);
|
||||||
|
|
||||||
$meanMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / $c;
|
$meanMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / $c;
|
||||||
$stdDevMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / BasicMath::square($c);
|
$stdDevMultiplier = (BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) / BasicMath::square($c);
|
||||||
@ -139,7 +139,7 @@ class TwoTeamTrueSkillCalculator extends SkillCalculator
|
|||||||
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1 - $w * $stdDevMultiplier)
|
(BasicMath::square($previousPlayerRating->getStandardDeviation()) + $tauSquared) * (1 - $w * $stdDevMultiplier)
|
||||||
);
|
);
|
||||||
|
|
||||||
$newPlayerRatings->setRating($localSelfTeamCurrentPlayer, new Rating($newMean, $newStdDev));
|
$newPlayerRatings->setRating($localSelfTeamCurPlayer, new Rating($newMean, $newStdDev));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,9 @@ class GuardTest extends TestCase
|
|||||||
|
|
||||||
public function testargumentIsValidIndexArgumentValid(): void
|
public function testargumentIsValidIndexArgumentValid(): void
|
||||||
{
|
{
|
||||||
Guard::argumentIsValidIndex(5, 10, "dummy");
|
Guard::argumentIsValidIndex(0, 10, "dummy");
|
||||||
|
Guard::argumentIsValidIndex(1, 10, "dummy");
|
||||||
|
Guard::argumentIsValidIndex(9, 10, "dummy");
|
||||||
$this->expectNotToPerformAssertions();
|
$this->expectNotToPerformAssertions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +50,12 @@ class GuardTest extends TestCase
|
|||||||
|
|
||||||
public function testargumentInRangeInclusiveValid(): void
|
public function testargumentInRangeInclusiveValid(): void
|
||||||
{
|
{
|
||||||
|
Guard::argumentInRangeInclusive(0, 0, 100, "dummy");
|
||||||
|
Guard::argumentInRangeInclusive(1, 0, 100, "dummy");
|
||||||
Guard::argumentInRangeInclusive(50, 0, 100, "dummy");
|
Guard::argumentInRangeInclusive(50, 0, 100, "dummy");
|
||||||
|
Guard::argumentInRangeInclusive(99, 0, 100, "dummy");
|
||||||
|
Guard::argumentInRangeInclusive(100, 0, 100, "dummy");
|
||||||
|
|
||||||
$this->expectNotToPerformAssertions();
|
$this->expectNotToPerformAssertions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ namespace DNW\Skills\Tests;
|
|||||||
use DNW\Skills\HashMap;
|
use DNW\Skills\HashMap;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PHPUnit\Framework\Attributes\CoversClass;
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
#[CoversClass(HashMap::class)]
|
#[CoversClass(HashMap::class)]
|
||||||
class HashMapTest extends TestCase
|
class HashMapTest extends TestCase
|
||||||
@ -19,13 +20,14 @@ class HashMapTest extends TestCase
|
|||||||
$this->assertEquals([], $h->getAllValues());
|
$this->assertEquals([], $h->getAllValues());
|
||||||
|
|
||||||
|
|
||||||
$o1 = new \stdClass();
|
$o1 = new stdClass();
|
||||||
$o2 = new \stdClass();
|
$o2 = new stdClass();
|
||||||
|
|
||||||
$h->setValue($o1, 1);
|
$h->setValue($o1, 1);
|
||||||
$h->setvalue($o2, 2);
|
$h->setvalue($o2, 2);
|
||||||
|
|
||||||
$this->assertEquals([1, 2], $h->getAllValues());
|
$this->assertEquals([1, 2], $h->getAllValues());
|
||||||
|
$this->assertEquals([$o1, $o2], $h->getAllKeys());
|
||||||
|
|
||||||
$this->assertEquals(1, $h->getvalue($o1));
|
$this->assertEquals(1, $h->getvalue($o1));
|
||||||
$this->assertEquals(2, $h->getvalue($o2));
|
$this->assertEquals(2, $h->getvalue($o2));
|
||||||
|
@ -92,6 +92,21 @@ class GaussianDistributionTest extends TestCase
|
|||||||
$m3s4 = new GaussianDistribution(3, 4);
|
$m3s4 = new GaussianDistribution(3, 4);
|
||||||
$lpn2 = GaussianDistribution::logProductNormalization($m1s2, $m3s4);
|
$lpn2 = GaussianDistribution::logProductNormalization($m1s2, $m3s4);
|
||||||
$this->assertEqualsWithDelta(-2.5168046699816684, $lpn2, GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(-2.5168046699816684, $lpn2, GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
|
|
||||||
|
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$lrn = GaussianDistribution::logProductNormalization($numerator, $denominator);
|
||||||
|
$this->assertEquals(0, $lrn);
|
||||||
|
|
||||||
|
$numerator = GaussianDistribution::fromPrecisionMean(1, 1);
|
||||||
|
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$lrn = GaussianDistribution::logProductNormalization($numerator, $denominator);
|
||||||
|
$this->assertEquals(0, $lrn);
|
||||||
|
|
||||||
|
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$denominator = GaussianDistribution::fromPrecisionMean(1, 1);
|
||||||
|
$lrn = GaussianDistribution::logProductNormalization($numerator, $denominator);
|
||||||
|
$this->assertEquals(0, $lrn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLogRatioNormalization(): void
|
public function testLogRatioNormalization(): void
|
||||||
@ -101,6 +116,21 @@ class GaussianDistributionTest extends TestCase
|
|||||||
$m3s4 = new GaussianDistribution(3, 4);
|
$m3s4 = new GaussianDistribution(3, 4);
|
||||||
$lrn = GaussianDistribution::logRatioNormalization($m1s2, $m3s4);
|
$lrn = GaussianDistribution::logRatioNormalization($m1s2, $m3s4);
|
||||||
$this->assertEqualsWithDelta(2.6157405972171204, $lrn, GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(2.6157405972171204, $lrn, GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
|
|
||||||
|
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator);
|
||||||
|
$this->assertEquals(0, $lrn);
|
||||||
|
|
||||||
|
$numerator = GaussianDistribution::fromPrecisionMean(1, 1);
|
||||||
|
$denominator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator);
|
||||||
|
$this->assertEquals(0, $lrn);
|
||||||
|
|
||||||
|
$numerator = GaussianDistribution::fromPrecisionMean(1, 0);
|
||||||
|
$denominator = GaussianDistribution::fromPrecisionMean(1, 1);
|
||||||
|
$lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator);
|
||||||
|
$this->assertEquals(0, $lrn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAbsoluteDifference(): void
|
public function testAbsoluteDifference(): void
|
||||||
@ -115,4 +145,27 @@ class GaussianDistributionTest extends TestCase
|
|||||||
$absDiff2 = GaussianDistribution::absoluteDifference($m1s2, $m3s4);
|
$absDiff2 = GaussianDistribution::absoluteDifference($m1s2, $m3s4);
|
||||||
$this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE);
|
$this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSubtract(): void
|
||||||
|
{
|
||||||
|
// Verified with Ralf Herbrich's F# implementation
|
||||||
|
$standardNormal = new GaussianDistribution(0, 1);
|
||||||
|
$absDiff = GaussianDistribution::subtract($standardNormal, $standardNormal);
|
||||||
|
$this->assertEqualsWithDelta(0.0, $absDiff, GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
|
|
||||||
|
$m1s2 = new GaussianDistribution(1, 2);
|
||||||
|
$m3s4 = new GaussianDistribution(3, 4);
|
||||||
|
$absDiff2 = GaussianDistribution::subtract($m1s2, $m3s4);
|
||||||
|
$this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testfromPrecisionMean(): void
|
||||||
|
{
|
||||||
|
$gd = GaussianDistribution::fromPrecisionMean(0, 0);
|
||||||
|
$this->assertInfinite($gd->getVariance());
|
||||||
|
$this->assertInfinite($gd->getStandardDeviation());
|
||||||
|
$this->assertNan($gd->getMean());
|
||||||
|
$this->assertEquals(0, $gd->getPrecisionMean());
|
||||||
|
$this->assertEquals(0, $gd->getPrecision());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,83 @@ use Exception;
|
|||||||
// phpcs:disable PSR2.Methods.FunctionCallSignature,Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
|
// phpcs:disable PSR2.Methods.FunctionCallSignature,Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
|
||||||
class MatrixTest extends TestCase
|
class MatrixTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function testEmptyMatrix(): void
|
||||||
|
{
|
||||||
|
$m1 = new Matrix();
|
||||||
|
$this->assertEquals(0, $m1->getRowCount());
|
||||||
|
$this->assertEquals(0, $m1->getColumnCount());
|
||||||
|
|
||||||
|
$m2 = new Matrix(0, 0);
|
||||||
|
$this->assertEquals(0, $m2->getRowCount());
|
||||||
|
$this->assertEquals(0, $m2->getColumnCount());
|
||||||
|
|
||||||
|
$this->assertEquals(new Matrix(), Matrix::multiply($m1, $m2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexing(): void
|
||||||
|
{
|
||||||
|
$m = new Matrix(5, 5);
|
||||||
|
$m->setValue(0, 0, 1);
|
||||||
|
$this->assertEquals(1, $m->getValue(0, 0));
|
||||||
|
$m->setValue(0, 1, 2);
|
||||||
|
$this->assertEquals(2, $m->getValue(0, 1));
|
||||||
|
$m->setValue(1, 0, 3);
|
||||||
|
$this->assertEquals(3, $m->getValue(1, 0));
|
||||||
|
$m->setValue(1, 1, 4);
|
||||||
|
$this->assertEquals(4, $m->getValue(1, 1));
|
||||||
|
|
||||||
|
$m->setValue(3, 3, 11);
|
||||||
|
$this->assertEquals(11, $m->getValue(3, 3));
|
||||||
|
$m->setValue(4, 3, 22);
|
||||||
|
$this->assertEquals(22, $m->getValue(4, 3));
|
||||||
|
$m->setValue(3, 4, 33);
|
||||||
|
$this->assertEquals(33, $m->getValue(3, 4));
|
||||||
|
$m->setValue(4, 4, 44);
|
||||||
|
$this->assertEquals(44, $m->getValue(4, 4));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$m->getValue(-1, -1);
|
||||||
|
$this->fail("No exception");
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->assertInstanceOf(Exception::class, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$m->getValue(-1, 0);
|
||||||
|
$this->fail("No exception");
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->assertInstanceOf(Exception::class, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$m->getValue(0, -1);
|
||||||
|
$this->fail("No exception");
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->assertInstanceOf(Exception::class, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$m->getValue(5, 5);
|
||||||
|
$this->fail("No exception");
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->assertInstanceOf(Exception::class, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$m->getValue(5, 4);
|
||||||
|
$this->fail("No exception");
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->assertInstanceOf(Exception::class, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$m->getValue(4, 5);
|
||||||
|
$this->fail("No exception");
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->assertInstanceOf(Exception::class, $exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testOneByOneDeterminant(): void
|
public function testOneByOneDeterminant(): void
|
||||||
{
|
{
|
||||||
$a = new SquareMatrix(1);
|
$a = new SquareMatrix(1);
|
||||||
|
29
tests/SkillCalculatorTest.php
Normal file
29
tests/SkillCalculatorTest.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DNW\Skills\Tests;
|
||||||
|
|
||||||
|
use DNW\Skills\SkillCalculator;
|
||||||
|
use DNW\Skills\TeamsRange;
|
||||||
|
use DNW\Skills\PlayersRange;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use PHPUnit\Framework\Attributes\RequiresPhpunit;
|
||||||
|
|
||||||
|
#[CoversClass(SkillCalculator::class)]
|
||||||
|
#[UsesClass(\DNW\Skills\Numerics\Range::class)]
|
||||||
|
#[UsesClass(PlayersRange::class)]
|
||||||
|
#[UsesClass(TeamsRange::class)]
|
||||||
|
#[RequiresPhpunit('<12.0')]
|
||||||
|
class SkillCalculatorTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testisSupported(): void
|
||||||
|
{
|
||||||
|
$calculator = $this->getMockForAbstractClass(SkillCalculator::class, [SkillCalculator::PARTIAL_PLAY, new TeamsRange(1, 2), new PlayersRange(1, 2)]);
|
||||||
|
|
||||||
|
$this->assertEquals(TRUE, $calculator->isSupported(SkillCalculator::PARTIAL_PLAY));
|
||||||
|
$this->assertEquals(FALSE, $calculator->isSupported(SkillCalculator::PARTIAL_UPDATE));
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace DNW\Skills\Tests\TrueSkill;
|
namespace DNW\Skills\Tests\TrueSkill;
|
||||||
|
|
||||||
|
use DNW\Skills\SkillCalculator;
|
||||||
use DNW\Skills\GameInfo;
|
use DNW\Skills\GameInfo;
|
||||||
use DNW\Skills\Player;
|
use DNW\Skills\Player;
|
||||||
use DNW\Skills\Team;
|
use DNW\Skills\Team;
|
||||||
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
use DNW\Skills\TrueSkill\FactorGraphTrueSkillCalculator;
|
||||||
use DNW\Skills\SkillCalculator;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PHPUnit\Framework\Attributes\CoversClass;
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
use PHPUnit\Framework\Attributes\CoversNothing;
|
use PHPUnit\Framework\Attributes\CoversNothing;
|
||||||
|
@ -9,7 +9,6 @@ use DNW\Skills\Player;
|
|||||||
use DNW\Skills\Rating;
|
use DNW\Skills\Rating;
|
||||||
use DNW\Skills\SkillCalculator;
|
use DNW\Skills\SkillCalculator;
|
||||||
use DNW\Skills\Team;
|
use DNW\Skills\Team;
|
||||||
use DNW\Skills\Teams;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class TrueSkillCalculatorTests
|
class TrueSkillCalculatorTests
|
||||||
@ -70,7 +69,6 @@ class TrueSkillCalculatorTests
|
|||||||
// online calculator at http://atom.research.microsoft.com/trueskill/rankcalculator.aspx
|
// online calculator at http://atom.research.microsoft.com/trueskill/rankcalculator.aspx
|
||||||
//
|
//
|
||||||
// All match quality expected values came from the online calculator
|
// All match quality expected values came from the online calculator
|
||||||
|
|
||||||
// In both cases, there may be some discrepancy after the first decimal point. I think this is due to my implementation
|
// In both cases, there may be some discrepancy after the first decimal point. I think this is due to my implementation
|
||||||
// using slightly higher precision in GaussianDistribution.
|
// using slightly higher precision in GaussianDistribution.
|
||||||
|
|
||||||
@ -849,24 +847,7 @@ class TrueSkillCalculatorTests
|
|||||||
$team15 = new Team($player15, $gameInfo->getDefaultRating());
|
$team15 = new Team($player15, $gameInfo->getDefaultRating());
|
||||||
$team16 = new Team($player16, $gameInfo->getDefaultRating());
|
$team16 = new Team($player16, $gameInfo->getDefaultRating());
|
||||||
|
|
||||||
$teams = [
|
$teams = [$team1, $team2, $team3, $team4, $team5, $team6, $team7, $team8, $team9, $team10, $team11, $team12, $team13, $team14, $team15, $team16];
|
||||||
$team1,
|
|
||||||
$team2,
|
|
||||||
$team3,
|
|
||||||
$team4,
|
|
||||||
$team5,
|
|
||||||
$team6,
|
|
||||||
$team7,
|
|
||||||
$team8,
|
|
||||||
$team9,
|
|
||||||
$team10,
|
|
||||||
$team11,
|
|
||||||
$team12,
|
|
||||||
$team13,
|
|
||||||
$team14,
|
|
||||||
$team15,
|
|
||||||
$team16
|
|
||||||
];
|
|
||||||
|
|
||||||
$newRatings = $calculator->calculateNewRatings(
|
$newRatings = $calculator->calculateNewRatings(
|
||||||
$gameInfo,
|
$gameInfo,
|
||||||
|
Reference in New Issue
Block a user