diff --git a/Algorithm/Rating/Elo.php b/Algorithm/Rating/Elo.php index 098fca94b..881f788fc 100644 --- a/Algorithm/Rating/Elo.php +++ b/Algorithm/Rating/Elo.php @@ -74,4 +74,37 @@ public function rating(int $elo, array $oElo, array $s) : array 'elo' => (int) \max($eloNew, $this->MIN_ELO), ]; } + + /** + * Calculate an approximated win probability based on elo points. + * + * @param int $elo1 Elo of the player we want to calculate the win probability for + * @param int $elo2 Opponent elo + * @param bool $draw Is a draw possible? + * + * @return float + * + * @since 1.0.0 + */ + public function winProbability(int $elo1, int $elo2, bool $draw = false) : float + { + return $draw + ? -1.0 // @todo: implement + : 1 / (1 + \pow(10, ($elo2 - $elo1) / 400)); + } + + /** + * Calculate an approximated draw probability based on elo points. + * + * @param int $elo1 Elo of the player we want to calculate the win probability for + * @param int $elo2 Opponent elo + * + * @return float + * + * @since 1.0.0 + */ + public function drawProbability(int $elo1, int $elo2) : float + { + return -1.0; // @todo: implement + } } diff --git a/Algorithm/Rating/TrueSkill.php b/Algorithm/Rating/TrueSkill.php index 661b17aa1..ab9c61987 100644 --- a/Algorithm/Rating/TrueSkill.php +++ b/Algorithm/Rating/TrueSkill.php @@ -29,18 +29,58 @@ */ class TrueSkill { - public int $DEFAULT_MU = 25; + public const DEFAULT_MU = 25; - public float $DEFAULT_SIGMA = 25 / 3; + public const DEFAULT_SIGMA = 25 / 3; - public float $DEFAULT_BETA = 25 / 3 / 2; + public const DEFAULT_BETA = 25 / 3 / 2; - public float $DEFAULT_TAU = 25 / 3 / 100; + public const DEFAULT_TAU = 25 / 3 / 100; - public float $DEFAULT_DRAW_PROBABILITY = 0.1; + public const DEFAULT_DRAW_PROBABILITY = 0.1; - public function __construct() + private float $mu = 0.0; + private float $sigma = 0.0; + private float $beta = 0.0; + private float $tau = 0.0; + private float $drawProbability = 0.0; + + public function __construct( + float $mu = null, + float $sigma = null, + float $beta = null, + float $tau = null, + float $drawProbability = null) + { + $this->mu = $mu ?? self::DEFAULT_MU; + $this->sigma = $sigma ?? self::DEFAULT_SIGMA; + $this->beta = $beta ?? self::DEFAULT_BETA; + $this->tau = $tau ?? self::DEFAULT_TAU; + $this->drawProbability = $drawProbability ?? self::DEFAULT_DRAW_PROBABILITY; + } + + public function winProbability(array $team1, array $team2, float $drawMargin = 0.0) { + $sigmaSum = 0.0; + $mu1 = 0.0; + foreach ($team1 as $player) { + $mu1 += $player->mu; + $sigmaSum += $player->sigma * $player->sigma; + } + + $mu2 = 0.0; + foreach ($team2 as $player) { + $mu2 += $player->mu; + $sigmaSum += $player->sigma * $player->sigma; + } + + $deltaMu = $mu1 - $mu2; + + return NormalDistribution::getCdf( + ($deltaMu - $drawMargin) / \sqrt((\count($team1) + \count($team2)) * ($this->beta * $this->beta) + $sigmaSum), + 0, + 1 + ); } // Draw margin = epsilon diff --git a/Algorithm/Rating/TrueSkillFactoryGraph.php b/Algorithm/Rating/TrueSkillFactoryGraph.php new file mode 100644 index 000000000..e69de29bb diff --git a/Math/Matrix/Matrix.php b/Math/Matrix/Matrix.php index ab9abb1a4..c2b2f01e6 100755 --- a/Math/Matrix/Matrix.php +++ b/Math/Matrix/Matrix.php @@ -82,9 +82,7 @@ public function __construct(int $m = 1, int $n = 1) $this->n = $n; $this->m = $m; - for ($i = 0; $i < $m; ++$i) { - $this->matrix[$i] = \array_fill(0, $n, 0); - } + $this->matrix = \array_fill(0, $m, \array_fill(0, $n, 0)); } /** @@ -492,7 +490,7 @@ private function addScalar(int | float $scalar) : self $newMatrixArr = $this->matrix; foreach ($newMatrixArr as $i => $vector) { - foreach ($vector as $j => $value) { + foreach ($vector as $j => $_) { $newMatrixArr[$i][$j] += $scalar; } } @@ -542,24 +540,44 @@ private function multMatrix(self $matrix) : self } $matrixArr = $matrix->toArray(); - $newMatrix = new self($this->m, $nDim); - $newMatrixArr = $newMatrix->toArray(); + $newMatrixArr = \array_fill(0, $this->m, \array_fill(0, $nDim, 0)); - for ($i = 0; $i < $this->m; ++$i) { // Row of $this - for ($c = 0; $c < $nDim; ++$c) { // Column of $matrix - $temp = 0; + if ($mDim > 10 || $nDim > 10) { + // Standard transposed for iteration over rows -> higher cache hit + $transposedMatrixArr = array(); + for ($k = 0; $k < $mDim; ++$k) { + for ($j = 0; $j < $nDim; ++$j) { + $transposedMatrixArr[$k][$j] = $matrixArr[$j][$k]; + } + } + + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $temp = 0; - for ($j = 0; $j < $mDim; ++$j) { // Row of $matrix - $temp += ($this->matrix[$i][$j] ?? 0) * ($matrixArr[$j][$c] ?? 0); + for ($k = 0; $k < $mDim; ++$k) { + $temp += $this->matrix[$i][$k] * $transposedMatrixArr[$i][$k]; + } + + $newMatrixArr[$i][$j] = $temp; } + } + } else { + // Standard + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $nDim; ++$j) { + $temp = 0; + + for ($k = 0; $k < $mDim; ++$k) { + $temp += $this->matrix[$i][$k] * $matrixArr[$k][$j]; + } - $newMatrixArr[$i][$c] = $temp; + $newMatrixArr[$i][$j] = $temp; + } } } - $newMatrix->setMatrix($newMatrixArr); /* @phpstan-ignore-line */ - - return $newMatrix; + return self::fromArray($newMatrixArr); } /** diff --git a/Math/Matrix/Vector.php b/Math/Matrix/Vector.php index df011904b..1319fcb86 100755 --- a/Math/Matrix/Vector.php +++ b/Math/Matrix/Vector.php @@ -53,7 +53,7 @@ public static function fromArray(array $vector) : self */ public function setV(int $m, int | float $value) : void { - parent::set($m , 0, $value); + $this->matrix[$m][0] = $value; } /** @@ -67,7 +67,7 @@ public function setV(int $m, int | float $value) : void */ public function getV(int $m) : int | float { - return parent::get($m, 0); + return $this->matrix[$m][0]; } /** @@ -82,7 +82,7 @@ public function getV(int $m) : int | float public function setMatrixV(array $vector) : self { foreach ($vector as $key => $value) { - $this->setV($key, $value); + $this->matrix[$key][0] = $value; } return $this; @@ -194,9 +194,9 @@ public function angle(self $vector) : float public function cross3(self $vector) : self { $crossArray = [ - $this->getV(1) * $vector->getV(2) - $this->getV(2) * $vector->getV(1), - $this->getV(2) * $vector->getV(0) - $this->getV(0) * $vector->getV(2), - $this->getV(0) * $vector->getV(1) - $this->getV(1) * $vector->getV(0), + $this->matrix[1][0] * $vector->matrix[2][0] - $this->matrix[2][0] * $vector->matrix[1][0], + $this->matrix[2][0] * $vector->matrix[0][0] - $this->matrix[0][0] * $vector->matrix[2][0], + $this->matrix[0][0] * $vector->matrix[1][0] - $this->matrix[1][0] * $vector->matrix[0][0], ]; return self::fromArray($crossArray);