diff --git a/Algorithm/Clustering/Kmeans.php b/Algorithm/Clustering/Kmeans.php index 2fa85fa47..08041cae7 100755 --- a/Algorithm/Clustering/Kmeans.php +++ b/Algorithm/Clustering/Kmeans.php @@ -35,10 +35,10 @@ final class Kmeans /** * Metric to calculate the distance between two points * - * @var \Closure + * @var Callable * @since 1.0.0 */ - private \Closure $metric; + private Callable $metric; /** * Points of the cluster centers @@ -53,11 +53,11 @@ final class Kmeans * * @param PointInterface[] $points Points to cluster * @param int<0, max> $clusters Amount of clusters - * @param null|\Closure $metric metric to use for the distance between two points + * @param null|Callable $metric metric to use for the distance between two points * * @since 1.0.0 */ - public function __construct(array $points, int $clusters, \Closure $metric = null) + public function __construct(array $points, int $clusters, Callable $metric = null) { $this->metric = $metric ?? function (PointInterface $a, PointInterface $b) { $aCoordinates = $a->getCoordinates(); diff --git a/Algorithm/Rating/BradleyTerry.php b/Algorithm/Rating/BradleyTerry.php new file mode 100644 index 000000000..dcc995113 --- /dev/null +++ b/Algorithm/Rating/BradleyTerry.php @@ -0,0 +1,82 @@ + ['A' => 0, 'B' => 2, 'C' => 0, 'D' => 1], + * 'B' => ['A' => 3, 'B' => 0, 'C' => 5, 'D' => 0], + * 'C' => ['A' => 0, 'B' => 3, 'C' => 0, 'D' => 1], + * 'D' => ['A' => 4, 'B' => 0, 'C' => 3, 'D' => 0], + * ], + * 20 + * ) // [0.139, 0.226, 0.143, 0.492] + */ + public function rating(array $history, int $iterations = 20) : array + { + $keys = \array_keys($history); + $pOld = []; + foreach ($keys as $key) { + $pOld[$key] = 1; + } + + $p = $pOld; + for ($i = 0; $i < $iterations; ++$i) { + foreach ($history as $idx => $row) { + $W = \array_sum($row); + + $d = 0; + foreach ($history as $idx2 => $_) { + if ($idx === $idx2) { + continue; + } + + $d += ($history[$idx][$idx2] + $history[$idx2][$idx]) + / ($pOld[$idx] + $pOld[$idx2]); + } + + $p[$idx] = $W / $d; + } + + $norm = \array_sum($p); + foreach ($p as $idx => $_) { + $p[$idx] /= $norm; + } + + $pOld = $p; + } + + return $p; + } +} diff --git a/Algorithm/Rating/Elo.php b/Algorithm/Rating/Elo.php new file mode 100644 index 000000000..0a3cb6b9c --- /dev/null +++ b/Algorithm/Rating/Elo.php @@ -0,0 +1,48 @@ + $o) { + $expected = 1 / (1 + 10 ** (($o - $elo) / 400)); + $r = $this->K * ($s[$idx] - $expected); + + $eloNew += $r; + } + + return [ + 'elo' => (int) \max((int) $eloNew, $this->MIN_ELO), + ]; + } +} diff --git a/Algorithm/Rating/Glicko1.php b/Algorithm/Rating/Glicko1.php new file mode 100644 index 000000000..51e933a96 --- /dev/null +++ b/Algorithm/Rating/Glicko1.php @@ -0,0 +1,109 @@ +DEFAULT_C * $this->DEFAULT_C * \max(0, $matchDate - $lastMatchDate) + ), + $this->MIN_RD + ) + ); + + // Step 2: + foreach ($oElo as $id => $e) { + $gRD_t = 1 / (\sqrt(1 + 3 * self::Q * self::Q * $oRd[$id] * $oRd[$id] / (\M_PI * \M_PI))); + $gRD[] = $gRD_t; + $E[] = 1 / (1 + \pow(10, $gRD_t * ($eloOld - $e) / -400)); + } + + $d = 0; + foreach ($E as $id => $_) { + $d += $gRD[$id] * $gRD[$id] * $E[$id] * (1 - $E[$id]); + } + $d2 = 1 / (self::Q * self::Q * $d); + + $r = 0; + foreach ($E as $id => $_) { + $r += $gRD[$id] * ($s[$id] - $E[$id]); + } + $r = $eloOld + self::Q / (1 / ($RD * $RD) + 1 / $d2) * $r; + + // Step 3: + $RD_ = \sqrt(1 / (1 / ($RD * $RD) + 1 / $d2)); + + return [ + 'elo' => (int) \max((int) $r, $this->MIN_ELO), + 'rd' => (int) \max($RD_, $this->MIN_RD) + ]; + } +} diff --git a/Algorithm/Rating/Glicko2.php b/Algorithm/Rating/Glicko2.php new file mode 100644 index 000000000..ecc7bb2ea --- /dev/null +++ b/Algorithm/Rating/Glicko2.php @@ -0,0 +1,118 @@ +elo(1500, 200, 0.06, [1,0,0], [1400,1550,1700], [30,100,300]) // 1464, 151, 0.059 + */ + public function rating( + int $eloOld = 1500, + int $rdOld = 50, + float $volOld = 0.06, + array $s = [], + array $oElo = [], + array $oRd = [] + ) : array + { + $tau = $this->tau; + + // Step 0: + $rdOld = $rdOld / 173.7178; + $eloOld = ($eloOld - $this->DEFAULT_ELO) / 173.7178; + + foreach ($oElo as $idx => $value) { + $oElo[$idx] = ($value - $this->DEFAULT_ELO) / 173.7178; + } + + foreach ($oRd as $idx => $value) { + $oRd[$idx] = $value / 173.7178; + } + + // Step 1: + $g = []; + foreach ($oRd as $rd) { + $g[] = 1 / \sqrt(1 + 3 * $rd * $rd / (\M_PI * \M_PI)); + } + + $E = []; + foreach ($oElo as $idx => $elo) { + $E[] = 1 / (1 + \exp(-$g[$idx] * ($eloOld - $elo))); + } + + $v = 0; + foreach ($g as $idx => $t) { + $v += $t * $t * $E[$idx] * (1 - $E[$idx]); + } + $v = 1 / $v; + + $tDelta = 0; + foreach ($g as $idx => $t) { + $tDelta += $t * ($s[$idx] - $E[$idx]); + } + $Delta = $v * $tDelta; + + // Step 2: + $fn = function($x) use ($Delta, $rdOld, $v, $tau, $volOld) + { + return 0.5 * (\exp($x) * ($Delta ** 2 - $rdOld ** 2 - $v - \exp($x))) / (($rdOld ** 2 + $v + \exp($x)) ** 2) + - ($x - \log($volOld ** 2)) / ($tau ** 2); + }; + + $root = Bisection::bisection($fn, -100, 100, 1000); + $vol = \exp($root / 2); + + // Step 3: + $RD = 1 / \sqrt(1 / ($rdOld ** 2 + $vol ** 2) + 1 / $v); + $r = $eloOld + $RD ** 2 * $tDelta; + + // Undo step 0: + $RD = 173.7178 * $RD; + $r = 173.7178 * $r + $this->DEFAULT_ELO; + + return [ + 'elo' => (int) \max($r, $this->MIN_ELO), + 'rd' => (int) \max($RD, $this->MIN_RD), + 'vol' => $vol, + ]; + } +} diff --git a/Algorithm/Rating/TrueSkill.php b/Algorithm/Rating/TrueSkill.php new file mode 100644 index 000000000..1372f81b7 --- /dev/null +++ b/Algorithm/Rating/TrueSkill.php @@ -0,0 +1,234 @@ +vWin($t, $epsilon); + + return $v * ($v + $t - $epsilon); + } + + /** + * Variance multiplicative function "w" for draws + * + * @latex w = \nu^2 + \dfrac{(\epsilon - t) * \mathcal{N}(\epsilon - t) + (\epsilon + t) * \mathcal{N}(\epsilon + t)}{\Phi(\epsilon - t) - \Phi(-\epsilon - t)} + * + * @param float $t Difference winner and loser mu + * @param float $epsilon Draw margin + * + * @return float + * + * @since 1.0.0 + */ + private function wDraw(float $t, float $epsilon) : float + { + $tAbs = \abs($t); + + $v = $this->vDraw($t, $epsilon); + + return $v * $v + + (($epsilon - $t) * NormalDistribution::getPdf($epsilon - $tAbs, 0.0, 1.0) + ($epsilon + $tAbs) * NormalDistribution::getPdf($epsilon + $tAbs, 0.0, 1.0)) + / (NormalDistribution::getCdf($epsilon - $tAbs, 0.0, 1.0) - NormalDistribution::getCdf(-$epsilon - $tAbs, 0.0, 1.0)); + } + + private function buildRatingLayer() + { + + } + + private function buildPerformanceLayer() + { + + } + + private function buildTeamPerformanceLayer() + { + + } + + private function buildTruncLayer() + { + + } + + private function factorGraphBuilders() + { + // Rating layer + + // Performance layer + + // Team Performance layer + + // Trunc layer + + return [ + 'rating_layer' => $ratingLayer, + 'performance_layer' => $ratingLayer, + 'team_performance_layer' => $ratingLayer, + 'trunc_layer' => $ratingLayer, + ]; + } + + public function rating() + { + // Start values + $mu = 25; + $sigma = $mu / 3; + $beta = $sigma / 2; + $tau = $sigma / 100; + $Pdraw = 0.1; + + $alpha = 0.25; + + // Partial update + $sigmaPartial = $sigmaOld * $sigmaNew / \sqrt($alpha * $sigmaOld * $sigmaOld - ($alpha - 1) * $sigmaNew * $sigmaNew); + $muPartial = $muOld * ($alpha - 1) * $sigmaNew * $sigmaNew - $muNew * $alpha * $sigmaOld * $sigmaOld + / (($alpha - 1) * $sigmaNew * $sigmaNew - $alpha * $sigmaOld * $sigmaOld); + + + // New + $tau = $pi * $mu; + + $P = NormalDistribution::getCdf(($s1 - $s2) / (\sqrt(2) * $beta)); + $Delta = $alpha * $beta * \sqrt($pi) * (($y + 1) / 2 - $P); + + $K = NormalDistribution::getCdf(); + + + + + + $pi = 1 / ($sigma * $sigma); + } + + +} diff --git a/DataStorage/Database/Mapper/UpdateMapper.php b/DataStorage/Database/Mapper/UpdateMapper.php index cc80ca949..8afd35e68 100755 --- a/DataStorage/Database/Mapper/UpdateMapper.php +++ b/DataStorage/Database/Mapper/UpdateMapper.php @@ -137,12 +137,12 @@ final class UpdateMapper extends DataMapperAbstract $id = \is_object($tValue) ? $this->updateOwnsOne($propertyName, $tValue) : $tValue; $value = $this->parseValue($column['type'], $id); - $query->set([$this->mapper::TABLE . '.' . $column['name'] => $value]); + $query->set([$column['name'] => $value]); } elseif (isset($this->mapper::BELONGS_TO[$propertyName])) { $id = \is_object($tValue) ? $this->updateBelongsTo($propertyName, $tValue) : $tValue; $value = $this->parseValue($column['type'], $id); - $query->set([$this->mapper::TABLE . '.' . $column['name'] => $value]); + $query->set([$column['name'] => $value]); } elseif ($column['name'] !== $this->mapper::PRIMARYFIELD) { if (\stripos($column['internal'], '/') !== false) { $path = \substr($column['internal'], \stripos($column['internal'], '/') + 1); @@ -151,11 +151,14 @@ final class UpdateMapper extends DataMapperAbstract $value = $this->parseValue($column['type'], $tValue); - $query->set([$this->mapper::TABLE . '.' . $column['name'] => $value]); + $query->set([$column['name'] => $value]); } } - $sth = $this->db->con->prepare($query->toSql()); + // @todo: + // @bug: Sqlite doesn't allow table_name.column_name in set queries for whatver reason. + + $sth = $this->db->con->prepare($a = $query->toSql()); if ($sth !== false) { $sth->execute(); } diff --git a/Dispatcher/Dispatcher.php b/Dispatcher/Dispatcher.php index 2d1703255..90f72c528 100755 --- a/Dispatcher/Dispatcher.php +++ b/Dispatcher/Dispatcher.php @@ -61,7 +61,7 @@ final class Dispatcher implements DispatcherInterface /** * {@inheritdoc} */ - public function dispatch(array | string | \Closure $controller, mixed ...$data) : array + public function dispatch(array | string | Callable $controller, mixed ...$data) : array { $views = []; $data = \array_values($data); @@ -165,14 +165,14 @@ final class Dispatcher implements DispatcherInterface /** * Dispatch closure. * - * @param \Closure $controller Controller string + * @param Callable $controller Controller string * @param null|array $data Data * * @return mixed * * @since 1.0.0 */ - private function dispatchClosure(\Closure $controller, array $data = null) : mixed + private function dispatchClosure(Callable $controller, array $data = null) : mixed { return $data === null ? $controller($this->app) : $controller($this->app, ...$data); } diff --git a/Dispatcher/DispatcherInterface.php b/Dispatcher/DispatcherInterface.php index 1e35d3c5e..76c73c197 100755 --- a/Dispatcher/DispatcherInterface.php +++ b/Dispatcher/DispatcherInterface.php @@ -27,7 +27,7 @@ interface DispatcherInterface /** * Dispatch controller. * - * @param array|\Closure|string $controller Controller + * @param array|Callable|string $controller Controller * @param mixed ...$data Data * * @return array Returns array of all dispatched results @@ -36,5 +36,5 @@ interface DispatcherInterface * * @since 1.0.0 */ - public function dispatch(array | string | \Closure $controller, mixed ...$data) : array; + public function dispatch(array | string | Callable $controller, mixed ...$data) : array; } diff --git a/Event/EventManager.php b/Event/EventManager.php index 9f5ee68ec..c0d267951 100755 --- a/Event/EventManager.php +++ b/Event/EventManager.php @@ -70,9 +70,9 @@ final class EventManager implements \Countable /** * {@inheritdoc} */ - public function dispatch(array | string | \Closure $func, mixed ...$data) : array + public function dispatch(array | string | Callable $func, mixed ...$data) : array { - if (!($func instanceof \Closure)) { + if (!\is_callable($func)) { return []; } @@ -136,7 +136,7 @@ final class EventManager implements \Countable * Attach new event * * @param string $group Name of the event (unique) - * @param string|\Closure $callback Callback or route for the event + * @param string|Callable $callback Callback or route for the event * @param bool $remove Remove event after triggering it? * @param bool $reset Reset event after triggering it? Remove must be false! * @@ -144,7 +144,7 @@ final class EventManager implements \Countable * * @since 1.0.0 */ - public function attach(string $group, string | \Closure $callback, bool $remove = false, bool $reset = false) : bool + public function attach(string $group, string | Callable $callback, bool $remove = false, bool $reset = false) : bool { if (!isset($this->callbacks[$group])) { $this->callbacks[$group] = ['remove' => $remove, 'reset' => $reset, 'callbacks' => []];