mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-11 01:38:41 +00:00
format fixes
This commit is contained in:
parent
74e1684ad0
commit
447efca623
|
|
@ -20,8 +20,8 @@ namespace phpOMS\Algorithm\Clustering;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo Implement
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ namespace phpOMS\Algorithm\Clustering;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo Implement
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ namespace phpOMS\Algorithm\Clustering;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo Implement
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ use phpOMS\Math\Topology\MetricsND;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo Expand to n dimensions
|
||||
*/
|
||||
|
|
@ -66,13 +66,19 @@ final class DBSCAN
|
|||
/**
|
||||
* Clusters
|
||||
*
|
||||
* Array of clusters containing point ids
|
||||
* Array of points assigned to a cluster
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, array{x:float, y:float}>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private array $clusters = [];
|
||||
|
||||
/**
|
||||
* Convex hull of all clusters
|
||||
*
|
||||
* @var array<array>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private array $convexHulls = [];
|
||||
|
||||
/**
|
||||
|
|
@ -85,12 +91,20 @@ final class DBSCAN
|
|||
*/
|
||||
private array $clusteredPoints = [];
|
||||
|
||||
/**
|
||||
* Distance matrix
|
||||
*
|
||||
* Distances between points
|
||||
*
|
||||
* @var array<float[]>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private array $distanceMatrix = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param null|\Closure $metric metric to use for the distance between two points
|
||||
* @param null|\Closure $metric metric to use for the distance between two points
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
|
@ -104,50 +118,88 @@ final class DBSCAN
|
|||
};
|
||||
}
|
||||
|
||||
private function expandCluster(PointInterface $point, array $neighbors, int $c, float $epsilon, int $minPoints) : void
|
||||
{
|
||||
$this->clusters[$c][] = $point;
|
||||
$this->clusteredPoints[] = $point;
|
||||
$nPoint = reset($neighbors);
|
||||
/**
|
||||
* Expand cluster with additional point and potential neighbors.
|
||||
*
|
||||
* @param PointInterface $point Point to add to a cluster
|
||||
* @param array $neighbors Neighbors of point
|
||||
* @param int $c Cluster id
|
||||
* @param float $epsilon Max distance
|
||||
* @param int $minPoints Min amount of points required for a cluster
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function expandCluster(
|
||||
PointInterface $point,
|
||||
array $neighbors,
|
||||
int $c,
|
||||
float $epsilon,
|
||||
int $minPoints
|
||||
) : void
|
||||
{
|
||||
$this->clusters[$c][] = $point;
|
||||
$this->clusteredPoints[] = $point;
|
||||
$nPoint = reset($neighbors);
|
||||
|
||||
while ($nPoint) {
|
||||
$neighbors2 = $this->findNeighbors($nPoint, $epsilon);
|
||||
while ($nPoint) {
|
||||
$neighbors2 = $this->findNeighbors($nPoint, $epsilon);
|
||||
|
||||
if (\count($neighbors2) >= $minPoints) {
|
||||
foreach ($neighbors2 as $nPoint2) {
|
||||
if (!isset($neighbors[$nPoint2->name])) {
|
||||
$neighbors[$nPoint2->name] = $nPoint2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (\count($neighbors2) >= $minPoints) {
|
||||
foreach ($neighbors2 as $nPoint2) {
|
||||
if (!isset($neighbors[$nPoint2->name])) {
|
||||
$neighbors[$nPoint2->name] = $nPoint2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!\in_array($nPoint->name, $this->clusteredPoints)) {
|
||||
$this->clusters[$c][] = $nPoint;
|
||||
$this->clusteredPoints[] = $nPoint;
|
||||
}
|
||||
if (!\in_array($nPoint->name, $this->clusteredPoints)) {
|
||||
$this->clusters[$c][] = $nPoint;
|
||||
$this->clusteredPoints[] = $nPoint;
|
||||
}
|
||||
|
||||
$nPoint = next($neighbors);
|
||||
}
|
||||
}
|
||||
$nPoint = next($neighbors);
|
||||
}
|
||||
}
|
||||
|
||||
private function findNeighbors(PointInterface $point, float $epsilon) : array
|
||||
{
|
||||
$neighbors = [];
|
||||
foreach ($this->points as $point2) {
|
||||
if ($point->isEquals($point2)) {
|
||||
/**
|
||||
* Find neighbors of a point
|
||||
*
|
||||
* @param PointInterface $point Base point for potential neighbors
|
||||
* @param float $epsion Max distance to neighbor
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function findNeighbors(PointInterface $point, float $epsilon) : array
|
||||
{
|
||||
$neighbors = [];
|
||||
foreach ($this->points as $point2) {
|
||||
if ($point->isEquals($point2)) {
|
||||
$distance = isset($this->distanceMatrix[$point->name])
|
||||
? $this->distanceMatrix[$point->name][$point2->name]
|
||||
: $this->distanceMatrix[$point2->name][$point->name];
|
||||
|
||||
if ($distance < $epsilon) {
|
||||
$neighbors[$point2->name] = $point2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($distance < $epsilon) {
|
||||
$neighbors[$point2->name] = $point2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $neighbors;
|
||||
}
|
||||
return $neighbors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate distances between points
|
||||
*
|
||||
* @param array $points Array of all points
|
||||
*
|
||||
* @return float[]
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function generateDistanceMatrix(array $points) : array
|
||||
{
|
||||
$distances = [];
|
||||
|
|
@ -161,6 +213,15 @@ final class DBSCAN
|
|||
return $distances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the cluster for a point
|
||||
*
|
||||
* @param PointInterface $point Point to find the cluster for
|
||||
*
|
||||
* @return int Cluster id
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function cluster(PointInterface $point) : int
|
||||
{
|
||||
if ($this->convexHulls === []) {
|
||||
|
|
@ -193,28 +254,40 @@ final class DBSCAN
|
|||
return -1;
|
||||
}
|
||||
|
||||
public function generateClusters(array $points, float $epsilon, int $minPoints) : void
|
||||
{
|
||||
$this->noisePoints = [];
|
||||
$this->clusters = [];
|
||||
$this->clusteredPoints = [];
|
||||
$this->points = $points;
|
||||
$this->convexHulls = [];
|
||||
/**
|
||||
* Generate the clusters of the points
|
||||
*
|
||||
* @param PointInterface[] $points Points to cluster
|
||||
* @param float $epsilon Max distance
|
||||
* @param int $minPoints Min amount of points required for a cluster
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function generateClusters(array $points, float $epsilon, int $minPoints) : void
|
||||
{
|
||||
$this->noisePoints = [];
|
||||
$this->clusters = [];
|
||||
$this->clusteredPoints = [];
|
||||
$this->points = $points;
|
||||
$this->convexHulls = [];
|
||||
|
||||
$this->distanceMatrix = $this->generateDistanceMatrix($points);
|
||||
|
||||
$c = 0;
|
||||
$this->clusters[$c] = [];
|
||||
foreach ($this->points as $point) {
|
||||
$neighbors = $this->findNeighbors($point, $epsilon);
|
||||
$c = 0;
|
||||
$this->clusters[$c] = [];
|
||||
|
||||
if (\count($neighbors) < $minPoints) {
|
||||
$this->noisePoints[] = $point->name;
|
||||
} elseif (!\in_array($point->name, $this->clusteredPoints)) {
|
||||
$this->expandCluster($point->name, $neighbors, $c, $epsilon, $minPoints);
|
||||
++$c;
|
||||
$this->clusters[$c] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($this->points as $point) {
|
||||
$neighbors = $this->findNeighbors($point, $epsilon);
|
||||
|
||||
if (\count($neighbors) < $minPoints) {
|
||||
$this->noisePoints[] = $point->name;
|
||||
} elseif (!\in_array($point->name, $this->clusteredPoints)) {
|
||||
$this->expandCluster($point, $neighbors, $c, $epsilon, $minPoints);
|
||||
++$c;
|
||||
$this->clusters[$c] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ use phpOMS\Math\Topology\MetricsND;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Kmeans
|
||||
{
|
||||
|
|
@ -54,7 +54,7 @@ final class Kmeans
|
|||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param null|\Closure $metric metric to use for the distance between two points
|
||||
* @param null|\Closure $metric metric to use for the distance between two points
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -23,13 +23,35 @@ use phpOMS\Math\Topology\MetricsND;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class MeanShift
|
||||
{
|
||||
/**
|
||||
* Min distance for clustering
|
||||
*
|
||||
* As long as a point is further away as the min distance the shifting is performed
|
||||
*
|
||||
* @var float
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public const MIN_DISTANCE = 0.001;
|
||||
|
||||
/**
|
||||
* Kernel function
|
||||
*
|
||||
* @var \Closure
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private \Closure $kernel;
|
||||
|
||||
/**
|
||||
* Metric function
|
||||
*
|
||||
* @var \Closure
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private \Closure $metric;
|
||||
|
||||
private array $points;
|
||||
|
|
@ -60,13 +82,21 @@ final class MeanShift
|
|||
*/
|
||||
private $clusterCenters = [];
|
||||
|
||||
public const MIN_DISTANCE = 0.000001;
|
||||
public const GROUP_DISTANCE_TOLERANCE = .1;
|
||||
/**
|
||||
* Max distance to cluster to be still considered part of cluster
|
||||
*
|
||||
* @var float
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public float $groupDistanceTolerance = 0.1;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param null|\Closure $metric metric to use for the distance between two points
|
||||
* Both the metric and kernel function need to be of the same dimension.
|
||||
*
|
||||
* @param null|\Closure $metric Metric to use for the distance between two points
|
||||
* @param null|\Closure $kernel Kernel
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
|
@ -84,10 +114,20 @@ final class MeanShift
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the clusters of the points
|
||||
*
|
||||
* @param PointInterface[] $points Points to cluster
|
||||
* @param array<int|float> $bandwidth Bandwidth(s)
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function generateClusters(array $points, array $bandwidth) : void
|
||||
{
|
||||
$shiftPoints = $points;
|
||||
$maxMinDist = 1;
|
||||
$maxMinDist = 1;
|
||||
|
||||
$stillShifting = \array_fill(0, \count($points), true);
|
||||
|
||||
|
|
@ -101,10 +141,10 @@ final class MeanShift
|
|||
continue;
|
||||
}
|
||||
|
||||
$pNew = $shiftPoints[$i];
|
||||
$pNew = $shiftPoints[$i];
|
||||
$pNewStart = $pNew;
|
||||
$pNew = $this->shiftPoint($pNew, $points, $bandwidth);
|
||||
$dist = ($this->metric)($pNew, $pNewStart);
|
||||
$pNew = $this->shiftPoint($pNew, $points, $bandwidth);
|
||||
$dist = ($this->metric)($pNew, $pNewStart);
|
||||
|
||||
if ($dist > $maxMinDist) {
|
||||
$maxMinDist = $dist;
|
||||
|
|
@ -124,6 +164,17 @@ final class MeanShift
|
|||
$this->clusterCenters = $shiftPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform shift on a point
|
||||
*
|
||||
* @param PointInterface $point Point to shift
|
||||
* @param PointInterface $points Array of all points
|
||||
* @param array<int|float> $bandwidth Bandwidth(s)
|
||||
*
|
||||
* @return PointInterface
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function shiftPoint(PointInterface $point, array $points, array $bandwidth) : PointInterface
|
||||
{
|
||||
$scaleFactor = 0.0;
|
||||
|
|
@ -131,7 +182,7 @@ final class MeanShift
|
|||
$shifted = clone $point;
|
||||
|
||||
foreach ($points as $pTemp) {
|
||||
$dist = ($this->metric)($point, $pTemp);
|
||||
$dist = ($this->metric)($point, $pTemp);
|
||||
$weight = ($this->kernel)($dist, $bandwidth);
|
||||
|
||||
foreach ($point->coordinates as $idx => $_) {
|
||||
|
|
@ -152,6 +203,15 @@ final class MeanShift
|
|||
return $shifted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group points together into clusters
|
||||
*
|
||||
* @param PointInterface[] $points Array of points to assign to groups
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function groupPoints(array $points) : array
|
||||
{
|
||||
$groupAssignment = [];
|
||||
|
|
@ -163,11 +223,12 @@ final class MeanShift
|
|||
|
||||
if ($nearestGroupIndex === -1) {
|
||||
// create new group
|
||||
$groups[] = [$point];
|
||||
$groups[] = [$point];
|
||||
$groupAssignment[] = $groupIndex;
|
||||
|
||||
++$groupIndex;
|
||||
} else {
|
||||
$groupAssignment[] = $nearestGroupIndex;
|
||||
$groupAssignment[] = $nearestGroupIndex;
|
||||
$groups[$nearestGroupIndex][] = $point;
|
||||
}
|
||||
}
|
||||
|
|
@ -175,16 +236,27 @@ final class MeanShift
|
|||
return $groupAssignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the closest cluster/group of a point
|
||||
*
|
||||
* @param PointInterface $point Point to find the cluster for
|
||||
* @param PointInterface[] $group Clusters
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function findNearestGroup(PointInterface $point, array $groups) : int
|
||||
{
|
||||
$nearestGroupIndex = -1;
|
||||
$index = 0;
|
||||
$index = 0;
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$distanceToGroup = $this->distanceToGroup($point, $group);
|
||||
|
||||
if ($distanceToGroup < self::GROUP_DISTANCE_TOLERANCE) {
|
||||
if ($distanceToGroup < $this->groupDistanceTolerance) {
|
||||
$nearestGroupIndex = $index;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -194,6 +266,16 @@ final class MeanShift
|
|||
return $nearestGroupIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find distance of point to best cluster/group
|
||||
*
|
||||
* @param PointInterface $point Point to find the cluster for
|
||||
* @param PointInterface[] $group Clusters
|
||||
*
|
||||
* @return float Distance
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function distanceToGroup(PointInterface $point, array $group) : float
|
||||
{
|
||||
$minDistance = \PHP_FLOAT_MAX;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ class Point implements PointInterface
|
|||
$this->coordinates[$index] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEquals(PointInterface $point) : bool
|
||||
{
|
||||
return $this->name === $point->name && $this->coordinates === $point->coordinates;
|
||||
|
|
|
|||
|
|
@ -60,5 +60,14 @@ interface PointInterface
|
|||
*/
|
||||
public function setCoordinate(int $index, int | float $value) : void;
|
||||
|
||||
/**
|
||||
* Check if two points are equal
|
||||
*
|
||||
* @param self $point Point to compare with
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function isEquals(self $point) : bool;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ namespace phpOMS\Algorithm\Clustering;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo Implement
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ namespace phpOMS\Algorithm\Clustering;
|
|||
* @package phpOMS\Algorithm\Clustering
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see ./clustering_overview.png
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo Implement
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ declare(strict_types=1);
|
|||
namespace phpOMS\Algorithm\CoinMatching;
|
||||
|
||||
/**
|
||||
* Matching a value with a set of coins
|
||||
* Apriori algorithm.
|
||||
*
|
||||
* The algorithm cheks how often a set exists in a given set of sets.
|
||||
*
|
||||
* @package phpOMS\Algorithm\CoinMatching
|
||||
* @license OMS License 2.0
|
||||
|
|
@ -24,7 +26,27 @@ namespace phpOMS\Algorithm\CoinMatching;
|
|||
*/
|
||||
final class Apriori
|
||||
{
|
||||
private static function generateSubsets(array $arr) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate all possible subsets
|
||||
*
|
||||
* @param array $arr Array of eleements
|
||||
*
|
||||
* @return array<array>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function generateSubsets(array $arr) : array
|
||||
{
|
||||
$subsets = [[]];
|
||||
|
||||
foreach ($arr as $element) {
|
||||
|
|
@ -44,13 +66,15 @@ final class Apriori
|
|||
}
|
||||
|
||||
/**
|
||||
* $transactions = array(
|
||||
array('milk', 'bread', 'eggs'),
|
||||
array('milk', 'bread'),
|
||||
array('milk', 'eggs'),
|
||||
array('bread', 'eggs'),
|
||||
array('milk')
|
||||
);
|
||||
* Performs the apriori algorithm.
|
||||
*
|
||||
* The algorithm cheks how often a set exists in a given set of sets.
|
||||
*
|
||||
* @param array<array> $sets Sets of a set (e.g. [[1,2,3,4], [1,2], [1]])
|
||||
*
|
||||
* @return array<array>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function apriori(array $sets) : array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Jingga
|
||||
*
|
||||
* PHP Version 8.1
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 2.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpOMS\Algorithm\Optimization;
|
||||
|
||||
/**
|
||||
* Perform ant colony algorithm.
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class AntColonyOptimization
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Jingga
|
||||
*
|
||||
* PHP Version 8.1
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 2.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpOMS\Algorithm\Optimization;
|
||||
|
||||
/**
|
||||
* Perform bees algorithm.
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class BeesAlgorithm
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Jingga
|
||||
*
|
||||
* PHP Version 8.1
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 2.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpOMS\Algorithm\Optimization;
|
||||
|
||||
/**
|
||||
* Perform firefly algorithm.
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class FireflyAlgorithm
|
||||
{
|
||||
}
|
||||
|
|
@ -25,6 +25,17 @@ namespace phpOMS\Algorithm\Optimization;
|
|||
*/
|
||||
class GeneticOptimization
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
// Fitness function (may require to pass solution space as \Closure variable)
|
||||
// E.g.
|
||||
// highest value of some sorts (e.g. profit)
|
||||
|
|
@ -36,7 +47,6 @@ class GeneticOptimization
|
|||
return $x;
|
||||
}
|
||||
|
||||
// Mutation
|
||||
public static function mutate($parameters, $mutationRate)
|
||||
{
|
||||
for ($i = 0; $i < \count($parameters); $i++) {
|
||||
|
|
@ -64,9 +74,27 @@ class GeneticOptimization
|
|||
|
||||
return [$child1, $child2];
|
||||
}
|
||||
*/
|
||||
|
||||
// the fitness and mutation functions may need to be able to create the object to optimize and/or validate
|
||||
// @todo maybe a crossover function must be provided to find and create the crossover (sometimes there are dependencies between parameters)
|
||||
/**
|
||||
* Perform optimization
|
||||
*
|
||||
* @example See unit test for example use case
|
||||
*
|
||||
* @param array<array> $population List of all elements with ther parameters (i.e. list of "objects" as arrays).
|
||||
* The constraints are defined as array values.
|
||||
* @param \Closure $fitness Fitness function calculates score/feasability of solution
|
||||
* @param \Closure $mutate Mutation function to change the parameters of an "object"
|
||||
* @param \Closure $crossover Crossover function to exchange parameter values between "objects".
|
||||
* Sometimes single parameters can be exchanged but sometimes interdependencies exist between parameters which is why this function is required.
|
||||
* @param int $generations Number of generations to create
|
||||
* @param float $mutationRate Rate at which parameters are changed.
|
||||
* How this is used depends on the mutate function.
|
||||
*
|
||||
* @return array{solutions:array, fitnesses:float[]}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function optimize(
|
||||
array $population,
|
||||
\Closure $fitness,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Jingga
|
||||
*
|
||||
* PHP Version 8.1
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 2.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpOMS\Algorithm\Optimization;
|
||||
|
||||
/**
|
||||
* Perform harmony search algorithm.
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HarmonySearch
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Jingga
|
||||
*
|
||||
* PHP Version 8.1
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 2.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpOMS\Algorithm\Optimization;
|
||||
|
||||
/**
|
||||
* Perform intelligent water drops algorithm.
|
||||
*
|
||||
* @package phpOMS\Algorithm\Optimization
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class IntelligentWaterDrops
|
||||
{
|
||||
}
|
||||
|
|
@ -25,6 +25,17 @@ namespace phpOMS\Algorithm\Optimization;
|
|||
*/
|
||||
class SimulatedAnnealing
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
public static function costFunction($x)
|
||||
{
|
||||
return $x;
|
||||
|
|
@ -44,24 +55,43 @@ class SimulatedAnnealing
|
|||
|
||||
return $newGeneration;
|
||||
}
|
||||
*/
|
||||
|
||||
// Simulated Annealing algorithm
|
||||
// @todo allow to create a solution space (currently all soluctions need to be in space)
|
||||
// @todo: currently only replacing generations, not altering them
|
||||
/**
|
||||
* Perform optimization
|
||||
*
|
||||
* @example See unit test for example use case
|
||||
*
|
||||
* @param array $space List of all elements with ther parameters (i.e. list of "objects" as arrays).
|
||||
* The constraints are defined as array values.
|
||||
* @param int $initialTemperature Starting temperature
|
||||
* @param \Closure $costFunction Fitness function calculates score/feasability of solution
|
||||
* @param \Closure $neighbor Neighbor function to find a new solution/neighbor
|
||||
* @param float $coolingRate Rate at which cooling takes place
|
||||
* @param int $iterations Number of iterations
|
||||
*
|
||||
* @return array{solutions:array, costs:float[]}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function optimize(
|
||||
array $space,
|
||||
int $initialTemperature,
|
||||
\Closure $costFunction,
|
||||
\Closure $neighbor,
|
||||
$coolingRate = 0.98,
|
||||
$numIterations = 1000
|
||||
) {
|
||||
$parameterCount = \count($space);
|
||||
float $coolingRate = 0.98,
|
||||
int $iterations = 1000
|
||||
) : array
|
||||
{
|
||||
$parameterCount = \count($space);
|
||||
$currentGeneration = \reset($space);
|
||||
|
||||
$currentCost = ($costFunction)($currentGeneration);
|
||||
|
||||
for ($i = 0; $i < $numIterations; $i++) {
|
||||
for ($i = 0; $i < $iterations; ++$i) {
|
||||
$newGeneration = ($neighbor)($currentGeneration, $parameterCount);
|
||||
|
||||
$newCost = ($costFunction)($newGeneration);
|
||||
|
|
@ -78,7 +108,7 @@ class SimulatedAnnealing
|
|||
|
||||
return [
|
||||
'solutions' => $currentGeneration,
|
||||
'costs' => $currentCost
|
||||
'costs' => $currentCost
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,31 +25,65 @@ namespace phpOMS\Algorithm\Optimization;
|
|||
*/
|
||||
class TabuSearch
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
// Define your fitness function here
|
||||
public static function fitness($solution) {
|
||||
// Calculate and return the fitness of the solution
|
||||
// This function should be tailored to your specific problem
|
||||
return /* ... */;
|
||||
return $solution;
|
||||
}
|
||||
|
||||
// Define your neighborhood generation function here
|
||||
public static function generateNeighbor($currentSolution) {
|
||||
// Generate a neighboring solution based on the current solution
|
||||
// This function should be tailored to your specific problem
|
||||
return /* ... */;
|
||||
return $currentSolution;
|
||||
}
|
||||
*/
|
||||
|
||||
// Define the Tabu Search algorithm
|
||||
public static function optimize($initialSolution, \Closure $fitness, \Closure $neighbor, $tabuListSize, $maxIterations) {
|
||||
/**
|
||||
* Perform optimization
|
||||
*
|
||||
* @example See unit test for example use case
|
||||
*
|
||||
* @param array $initialSolution List of all elements with ther parameters (i.e. list of "objects" as arrays).
|
||||
* The constraints are defined as array values.
|
||||
* @param \Closure $fitness Fitness function calculates score/feasability of solution
|
||||
* @param \Closure $neighbor Neighbor function to find a new solution/neighbor
|
||||
* @param int $tabuListSize ????
|
||||
* @param int $iterations Number of iterations
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function optimize(
|
||||
array $initialSolution,
|
||||
\Closure $fitness,
|
||||
\Closure $neighbor,
|
||||
int $tabuListSize,
|
||||
int $iterations
|
||||
) : array
|
||||
{
|
||||
$currentSolution = $initialSolution;
|
||||
$bestSolution = $currentSolution;
|
||||
$bestFitness = \PHP_FLOAT_MIN;
|
||||
$tabuList = [];
|
||||
$bestSolution = $currentSolution;
|
||||
$bestFitness = \PHP_FLOAT_MIN;
|
||||
$tabuList = [];
|
||||
|
||||
for ($iteration = 0; $iteration < $maxIterations; ++$iteration) {
|
||||
for ($i = 0; $i < $iterations; ++$i) {
|
||||
$neighbors = [];
|
||||
for ($i = 0; $i < $tabuListSize; ++$i) {
|
||||
$neighbor = ($neighbor)($currentSolution);
|
||||
$neighbor = ($neighbor)($currentSolution);
|
||||
$neighbors[] = $neighbor;
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +111,7 @@ class TabuSearch
|
|||
|
||||
if (($score = ($fitness)($bestNeighbor)) > $bestFitness) {
|
||||
$bestSolution = $bestNeighbor;
|
||||
$bestFitness = $score;
|
||||
$bestFitness = $score;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,14 +25,10 @@ namespace phpOMS\Algorithm\Rating;
|
|||
*/
|
||||
final class BradleyTerry
|
||||
{
|
||||
public int $K = 32;
|
||||
|
||||
public int $DEFAULT_ELO = 1500;
|
||||
|
||||
public int $MIN_ELO = 100;
|
||||
|
||||
// history = matrix of past victories/performance against other teams (diagonal is empty)
|
||||
/**
|
||||
* Rate the strongest to the weakest team based on historic performances (wins/losses)
|
||||
*
|
||||
* The following example contains match results (matrix) of teams A-D facing each other (each point is a victory).
|
||||
* @example rating(
|
||||
* [
|
||||
* 'A' => ['A' => 0, 'B' => 2, 'C' => 0, 'D' => 1],
|
||||
|
|
@ -40,8 +36,15 @@ final class BradleyTerry
|
|||
* '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]
|
||||
* 10
|
||||
* ) // [0.640, 1.043, 0.660, 2.270] -> D is strongest
|
||||
*
|
||||
* @param array[] $history Historic results
|
||||
* @param int $iterations Iterations for estimation
|
||||
*
|
||||
* @return float[] Array of "strength" scores (highest = strongest)
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function rating(array $history, int $iterations = 20) : array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,23 +20,52 @@ namespace phpOMS\Algorithm\Rating;
|
|||
* @package phpOMS\Algorithm\Rating
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see https://en.wikipedia.org/wiki/Elo_rating_system
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Elo
|
||||
{
|
||||
/**
|
||||
* ELO change rate
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $K = 32;
|
||||
|
||||
/**
|
||||
* Default elo to use for new players
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $DEFAULT_ELO = 1500;
|
||||
|
||||
/**
|
||||
* Lowest elo allowed
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $MIN_ELO = 100;
|
||||
|
||||
/**
|
||||
* Calculate the elo rating
|
||||
*
|
||||
* @param int $elo Current player elo
|
||||
* @param int[] $oElo Current elo of all opponents
|
||||
* @param int[] $s Match results against the opponents (1 = victor, 0 = loss, 0.5 = draw)
|
||||
*
|
||||
* @return array{elo:int}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function rating(int $elo, array $oElo, array $s) : array
|
||||
{
|
||||
$eloNew = $elo;
|
||||
foreach ($oElo as $idx => $o) {
|
||||
$expected = 1 / (1 + 10 ** (($o - $elo) / 400));
|
||||
$r = $this->K * ($s[$idx] - $expected);
|
||||
$r = $this->K * ($s[$idx] - $expected);
|
||||
|
||||
$eloNew += $r;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,41 +20,104 @@ namespace phpOMS\Algorithm\Rating;
|
|||
* @package phpOMS\Algorithm\Rating
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see https://en.wikipedia.org/wiki/Glicko_rating_system
|
||||
* @see http://www.glicko.net/glicko/glicko.pdf
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Glicko1
|
||||
{
|
||||
public const Q = 0.00575646273; // ln(10) / 400
|
||||
/**
|
||||
* Helper constant
|
||||
*
|
||||
* @latex Q = ln(10) / 400
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private const Q = 0.00575646273;
|
||||
|
||||
/**
|
||||
* Default elo to use for new players
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $DEFAULT_ELO = 1500;
|
||||
|
||||
/**
|
||||
* Default rd to use for new players
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $DEFAULT_RD = 350;
|
||||
|
||||
/**
|
||||
* C (constant) for RD caclulation
|
||||
*
|
||||
* This is used to adjust the RD value based on the time from the last time a player played a match
|
||||
*
|
||||
* @latex RD = min\left(\sqrt{RD_0^2 + c^2t}, 350\right)
|
||||
*
|
||||
* @see calculateC();
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public float $DEFAULT_C = 34.6;
|
||||
|
||||
/**
|
||||
* Lowest elo allowed
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $MIN_ELO = 100;
|
||||
|
||||
/**
|
||||
* Lowest rd allowed
|
||||
*
|
||||
* @example 50 means that the player rating is probably between -100 / +100 of the current rating
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $MIN_RD = 50;
|
||||
|
||||
/**
|
||||
* Calculate the C value.
|
||||
*
|
||||
* This is only necessary if you change the DEFAULT_RD, want a different rating period or have significantly different average RD values.
|
||||
*
|
||||
* @param int $ratingPeriods Time without matches until the RD returns to the default RD
|
||||
* @param int $avgRD Average RD
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function calculateC(int $ratingPeriods = 100, int $avgRD = 50) : void
|
||||
{
|
||||
$this->DEFAULT_C = \sqrt(($this->DEFAULT_RD ** 2 - $avgRD ** 2) / $ratingPeriods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcualte the glicko-1 elo
|
||||
*
|
||||
* @param int $eloOld Old elo
|
||||
* @param int $rdOld Old deviation (50 === +/-100 elo points)
|
||||
* @param int $lastMatchDate Last match date used to calculate the time difference (can be days, months, ... depending on your match interval)
|
||||
* @param int $matchDate Match date (usually day)
|
||||
* @param float[] $s Match results (1 = victor, 0 = loss, 0.5 = draw)
|
||||
* @param int[] $oElo Opponent elo
|
||||
* @param int[] $oRd Opponent deviation
|
||||
* @param int $elo Current player "elo"
|
||||
* @param int $rdOld Current player deviation (RD)
|
||||
* @param int $lastMatchDate Last match date used to calculate the time difference (can be days, months, ... depending on your match interval)
|
||||
* @param int $matchDate Match date (usually day)
|
||||
* @param float[] $s Match results (1 = victor, 0 = loss, 0.5 = draw)
|
||||
* @param int[] $oElo Opponent "elo"
|
||||
* @param int[] $oRd Opponent deviation (RD)
|
||||
*
|
||||
* @return array{elo:int, rd:int}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function rating(
|
||||
int $eloOld = 1500,
|
||||
int $elo = 1500,
|
||||
int $rdOld = 50,
|
||||
int $lastMatchDate = 0,
|
||||
int $matchDate = 0,
|
||||
|
|
@ -64,8 +127,8 @@ final class Glicko1
|
|||
) : array
|
||||
{
|
||||
// Step 1:
|
||||
$s = [];
|
||||
$E = [];
|
||||
$s = [];
|
||||
$E = [];
|
||||
$gRD = [];
|
||||
|
||||
$RD = \min(
|
||||
|
|
@ -83,7 +146,7 @@ final class Glicko1
|
|||
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));
|
||||
$E[] = 1 / (1 + \pow(10, $gRD_t * ($elo - $e) / -400));
|
||||
}
|
||||
|
||||
$d = 0;
|
||||
|
|
@ -96,7 +159,7 @@ final class Glicko1
|
|||
foreach ($E as $id => $_) {
|
||||
$r += $gRD[$id] * ($s[$id] - $E[$id]);
|
||||
}
|
||||
$r = $eloOld + self::Q / (1 / ($RD * $RD) + 1 / $d2) * $r;
|
||||
$r = $elo + self::Q / (1 / ($RD * $RD) + 1 / $d2) * $r;
|
||||
|
||||
// Step 3:
|
||||
$RD_ = \sqrt(1 / (1 / ($RD * $RD) + 1 / $d2));
|
||||
|
|
|
|||
|
|
@ -22,31 +22,84 @@ use phpOMS\Math\Solver\Root\Bisection;
|
|||
* @package phpOMS\Algorithm\Rating
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see https://en.wikipedia.org/wiki/Glicko_rating_system
|
||||
* @see http://www.glicko.net/glicko/glicko2.pdf
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo: implement
|
||||
*/
|
||||
final class Glicko2
|
||||
{
|
||||
/**
|
||||
* Constraint for the volatility over time (smaller = stronger constraint)
|
||||
*
|
||||
* @var float
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public float $tau = 0.5;
|
||||
|
||||
/**
|
||||
* Default elo to use for new players
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $DEFAULT_ELO = 1500;
|
||||
|
||||
/**
|
||||
* Default rd to use for new players
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $DEFAULT_RD = 350;
|
||||
|
||||
/**
|
||||
* Valatility (sigma)
|
||||
*
|
||||
* Expected flactuation = how erratic is the player's performance
|
||||
*
|
||||
* @var float
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public float $DEFAULT_VOLATILITY = 0.06;
|
||||
|
||||
/**
|
||||
* Lowest elo allowed
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $MIN_ELO = 100;
|
||||
|
||||
/**
|
||||
* Lowest rd allowed
|
||||
*
|
||||
* @example 50 means that the player rating is probably between -100 / +100 of the current rating
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public int $MIN_RD = 50;
|
||||
|
||||
/**
|
||||
* Calcualte the glicko-2 elo
|
||||
*
|
||||
* @example $glicko->elo(1500, 200, 0.06, [1,0,0], [1400,1550,1700], [30,100,300]) // 1464, 151, 0.059
|
||||
*
|
||||
* @param int $elo Current player "elo"
|
||||
* @param int $rdOld Current player deviation (RD)
|
||||
* @param float $volOld Last match date used to calculate the time difference (can be days, months, ... depending on your match interval)
|
||||
* @param float[] $s Match results (1 = victor, 0 = loss, 0.5 = draw)
|
||||
* @param int[] $oElo Opponent "elo"
|
||||
* @param int[] $oRd Opponent deviation (RD)
|
||||
*
|
||||
* @return array{elo:int, rd:int, vol:float}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function rating(
|
||||
int $eloOld = 1500,
|
||||
int $elo = 1500,
|
||||
int $rdOld = 50,
|
||||
float $volOld = 0.06,
|
||||
array $s = [],
|
||||
|
|
@ -58,7 +111,7 @@ final class Glicko2
|
|||
|
||||
// Step 0:
|
||||
$rdOld = $rdOld / 173.7178;
|
||||
$eloOld = ($eloOld - $this->DEFAULT_ELO) / 173.7178;
|
||||
$elo = ($elo - $this->DEFAULT_ELO) / 173.7178;
|
||||
|
||||
foreach ($oElo as $idx => $value) {
|
||||
$oElo[$idx] = ($value - $this->DEFAULT_ELO) / 173.7178;
|
||||
|
|
@ -76,7 +129,7 @@ final class Glicko2
|
|||
|
||||
$E = [];
|
||||
foreach ($oElo as $idx => $elo) {
|
||||
$E[] = 1 / (1 + \exp(-$g[$idx] * ($eloOld - $elo)));
|
||||
$E[] = 1 / (1 + \exp(-$g[$idx] * ($elo - $elo)));
|
||||
}
|
||||
|
||||
$v = 0;
|
||||
|
|
@ -98,12 +151,12 @@ final class Glicko2
|
|||
- ($x - \log($volOld ** 2)) / ($tau ** 2);
|
||||
};
|
||||
|
||||
$root = Bisection::bisection($fn, -100, 100, 1000);
|
||||
$root = Bisection::root($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;
|
||||
$r = $elo + $RD ** 2 * $tDelta;
|
||||
|
||||
// Undo step 0:
|
||||
$RD = 173.7178 * $RD;
|
||||
|
|
@ -111,7 +164,7 @@ final class Glicko2
|
|||
|
||||
return [
|
||||
'elo' => (int) \max($r, $this->MIN_ELO),
|
||||
'rd' => (int) \max($RD, $this->MIN_RD),
|
||||
'rd' => (int) \max($RD, $this->MIN_RD),
|
||||
'vol' => $vol,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,18 +28,42 @@ use phpOMS\Math\Topology\MetricsND;
|
|||
* @package phpOMS\Business\Recommendation
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see https://realpython.com/build-recommendation-engine-collaborative-filtering/
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class MemoryCF
|
||||
{
|
||||
/**
|
||||
* All rankings
|
||||
*
|
||||
* @var array<array>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private array $rankings = [];
|
||||
|
||||
public function __construc(array $rankings)
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array<array> $rankings Array of item ratings by users (or reverse to find users)
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct(array $rankings)
|
||||
{
|
||||
$this->rankings = $this->normalizeRanking($rankings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize all ratings.
|
||||
*
|
||||
* This is necessary because some users my give lower or higher ratings on average (bias).
|
||||
*
|
||||
* @param array<array> $rankings Item ratings/rankings
|
||||
*
|
||||
* @return array<array>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function normalizeRanking(array $rankings) : array
|
||||
{
|
||||
foreach ($rankings as $idx => $items) {
|
||||
|
|
@ -53,7 +77,16 @@ final class MemoryCF
|
|||
return $rankings;
|
||||
}
|
||||
|
||||
// Used to find similar users
|
||||
/**
|
||||
* Euclidean distance between users
|
||||
*
|
||||
* @param array $ranking Rating to find the distance for
|
||||
* @param array<array> $rankings All ratings to find the distance to
|
||||
*
|
||||
* @return float[]
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function euclideanDistance(array $ranking, array $rankings) : array
|
||||
{
|
||||
$distances = [];
|
||||
|
|
@ -64,7 +97,16 @@ final class MemoryCF
|
|||
return $distances;
|
||||
}
|
||||
|
||||
// Used to find similar users
|
||||
/**
|
||||
* Cosine distance between users
|
||||
*
|
||||
* @param array $ranking Rating to find the distance for
|
||||
* @param array<array> $rankings All ratings to find the distance to
|
||||
*
|
||||
* @return float[]
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function cosineDistance(array $ranking, array $rankings) : array
|
||||
{
|
||||
$distances = [];
|
||||
|
|
@ -75,16 +117,28 @@ final class MemoryCF
|
|||
return $distances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a item rank/rating based on the distance to other items
|
||||
*
|
||||
* @param string $itemId Id of the item to rank
|
||||
* @param array $distances Distance to other users
|
||||
* @param array<array> $users All user ratings
|
||||
* @param int $size Only consider the top n distances (best matches with other users)
|
||||
*
|
||||
* @return float Estimated item rank/rating based on similarity to other users
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function weightedItemRank(string $itemId, array $distances, array $users, int $size) : float
|
||||
{
|
||||
$rank = 0.0;
|
||||
$rank = 0.0;
|
||||
$count = 0;
|
||||
foreach ($distances as $uId => $_) {
|
||||
if ($count >= $size) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($user[$itemId])) {
|
||||
if (!isset($users[$itemId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -92,30 +146,43 @@ final class MemoryCF
|
|||
$rank += $users[$uId][$itemId];
|
||||
}
|
||||
|
||||
return $rank / $count;
|
||||
return $count === 0 ? 0.0 : $rank / $count;
|
||||
}
|
||||
|
||||
// This can be used to find items for a specific user (aka might be interested in) or to find users who might be interested in this item
|
||||
// option 1 - find items
|
||||
// ranking[itemId] = itemRank (how much does specific user like item)
|
||||
// rankings[userId][itemId] = itemRank
|
||||
//
|
||||
// option 2 - find user
|
||||
// ranking[userId] = itemRank (how much does user like specific item)
|
||||
// rankings[itemId][userId] = itemRank
|
||||
// option 1 searches for items, option 2 searches for users
|
||||
/**
|
||||
* Find potential items/users which are a good match for a user/item.
|
||||
*
|
||||
* The algorithm uses the ratings of a a user and tries to find other users who have similar rating behavior and then searches for high rated items that the user doesn't have yet.
|
||||
*
|
||||
* This can be used to find items for a specific user (aka might be interested in) or to find users who might be interested in this item
|
||||
*
|
||||
* option 1 - find items
|
||||
* ranking[itemId] = itemRank (how much does specific user like item)
|
||||
* rankings[userId][itemId] = itemRank
|
||||
*
|
||||
* option 2 - find user
|
||||
* ranking[userId] = itemRank (how much does user like specific item)
|
||||
* rankings[itemId][userId] = itemRank
|
||||
* option 1 searches for items, option 2 searches for users
|
||||
*
|
||||
* @param array $ranking Array of item ratings (e.g. products, movies, ...)
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function bestMatch(array $ranking, int $size = 10) : array
|
||||
{
|
||||
$ranking = $this->normalizeRanking([$ranking]);
|
||||
$ranking = $ranking[0];
|
||||
$ranking = $this->normalizeRanking([$ranking]);
|
||||
$ranking = $ranking[0];
|
||||
|
||||
$euclidean = $this->euclideanDistance($ranking, $this->rankings);
|
||||
$cosine = $this->cosineDistance($ranking, $this->rankings);
|
||||
$cosine = $this->cosineDistance($ranking, $this->rankings);
|
||||
|
||||
\asort($euclidean);
|
||||
\asort($cosine);
|
||||
|
||||
$size = \min($size, \count($this->rankings));
|
||||
$size = \min($size, \count($this->rankings));
|
||||
$matches = [];
|
||||
|
||||
$distancePointer = \array_keys($euclidean);
|
||||
|
|
@ -125,8 +192,9 @@ final class MemoryCF
|
|||
for ($i = 1; $i <= $size; ++$i) {
|
||||
$index = (int) ($i / 2) - 1;
|
||||
|
||||
$uId = $i % 2 === 1 ? $distancePointer[$index] : $anglePointer[$index];
|
||||
$uId = $i % 2 === 1 ? $distancePointer[$index] : $anglePointer[$index];
|
||||
$distances = $i % 2 === 1 ? $euclidean : $cosine;
|
||||
|
||||
foreach ($this->rankings[$uId] as $iId => $_) {
|
||||
// Item is not already in dataset and not in historic dataset (we are only interested in new)
|
||||
if (isset($matches[$iId]) || isset($ranking[$iId])) {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ use phpOMS\Math\Matrix\Matrix;
|
|||
* @package phpOMS\Business\Recommendation
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
* @see https://realpython.com/build-recommendation-engine-collaborative-filtering/
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class ModelCF
|
||||
{
|
||||
|
|
@ -37,12 +37,25 @@ final class ModelCF
|
|||
{
|
||||
}
|
||||
|
||||
// $user and $item can also be Vectors resulting in a individual evaluation
|
||||
// e.g. the user matrix contains a user in every row, every column represents a score for a certain attribute
|
||||
// the item matrix contains in every row a score for how much it belongs to a certain attribute. Each column represents an item.
|
||||
// example: users columns define how much a user likes a certain movie genre and the item rows define how much this movie belongs to a certain genre.
|
||||
// the multiplication gives a score of how much the user may like that movie.
|
||||
// A segnificant amount of attributes are required to calculate a good match
|
||||
/**
|
||||
* Calculate the score of a user <-> item match.
|
||||
*
|
||||
* This function calculates how much a user likes a certain item (product, movie etc.)
|
||||
*
|
||||
* $user and $item can also be Vectors resulting in a individual evaluation
|
||||
* e.g. the user matrix contains a user in every row, every column represents a score for a certain attribute
|
||||
* the item matrix contains in every row a score for how much it belongs to a certain attribute. Each column represents an item.
|
||||
* example: users columns define how much a user likes a certain movie genre and the item rows define how much this movie belongs to a certain genre.
|
||||
* the multiplication gives a score of how much the user may like that movie.
|
||||
* A segnificant amount of attributes are required to calculate a good match
|
||||
*
|
||||
* @param Matrix $users A mxa matrix where each "m" defines how much the user likes a certain attribute type and "a" defines different users
|
||||
* @param Matrix $items A bxm matrix where each "b" defines a item and "m" defines how much it belongs to a certain attribute type
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function score(Matrix $users, Matrix $items) : array
|
||||
{
|
||||
return $users->mult($items)->getMatrix();
|
||||
|
|
|
|||
|
|
@ -141,6 +141,18 @@ final class HttpSession implements SessionInterface
|
|||
UriFactory::setQuery('$CSRF', $csrf); /* @phpstan-ignore-line */
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the session from the request.
|
||||
*
|
||||
* This is only used when the session data is stored in the request itself (e.g. JWT)
|
||||
*
|
||||
* @param string $secret Secret to validate the request
|
||||
* @param RequestAbstract $request Request
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function populateFromRequest(string $secret, RequestAbstract $request) : void
|
||||
{
|
||||
$authentication = $request->header->get('Authorization');
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ final class JWT
|
|||
/**
|
||||
* Create JWT signature part
|
||||
*
|
||||
* @param string $secret Secret (at least 256 bit)
|
||||
* @var array{alg:string, typ:string} $header Header
|
||||
* @var array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
|
||||
* @param string $secret Secret (at least 256 bit)
|
||||
* @param array{alg:string, typ:string} $header Header
|
||||
* @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
|
||||
*
|
||||
* @return string hmac(Header64 . Payload64, secret)
|
||||
*
|
||||
|
|
@ -60,11 +60,13 @@ final class JWT
|
|||
/**
|
||||
* Create JWT token
|
||||
*
|
||||
* @param string $secret Secret (at least 256 bit)
|
||||
* @var array{alg:string, typ:string} $header Header
|
||||
* @var array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
|
||||
* @param string $secret Secret (at least 256 bit)
|
||||
* @param array{alg:string, typ:string} $header Header
|
||||
* @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
|
||||
*
|
||||
* @return string Header64 . Payload64 . hmac(Header64 . Payload64, secret)
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function createJWT(string $secret, array $header = [], array $payload = []) : string
|
||||
{
|
||||
|
|
@ -75,6 +77,15 @@ final class JWT
|
|||
return $header64 . $payload64 . Base64Url::encode($signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the header from the jwt string
|
||||
*
|
||||
* @param string $jwt JWT string
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getHeader(string $jwt) : array
|
||||
{
|
||||
$explode = \explode('.', $jwt);
|
||||
|
|
@ -90,6 +101,15 @@ final class JWT
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payload from the jwt string
|
||||
*
|
||||
* @param string $jwt JWT string
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getPayload(string $jwt) : array
|
||||
{
|
||||
$explode = \explode('.', $jwt);
|
||||
|
|
@ -112,6 +132,8 @@ final class JWT
|
|||
* @param string $jwt JWT token [Header64 . Payload64 . hmac(Header64 . Payload64, secret)]
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function validateJWT(string $secret, string $jwt) : bool
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,7 +24,16 @@ namespace phpOMS\Localization;
|
|||
*/
|
||||
trait ISO3166Trait
|
||||
{
|
||||
public static function getBy2Code(string $code)
|
||||
/**
|
||||
* Get value by 2 code
|
||||
*
|
||||
* @param string $code 2-code
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getBy2Code(string $code) : mixed
|
||||
{
|
||||
$code3 = ISO3166TwoEnum::getName($code);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,16 @@ namespace phpOMS\Localization;
|
|||
*/
|
||||
trait ISO639Trait
|
||||
{
|
||||
public static function getBy2Code(string $code)
|
||||
/**
|
||||
* Get value by 2 code
|
||||
*
|
||||
* @param string $code 2-code
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getBy2Code(string $code) : mixed
|
||||
{
|
||||
return self::getByName('_' . \strtoupper($code));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,6 +243,12 @@ final class Functions
|
|||
return \abs(self::mod($value - $start, $length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Error function coefficients for approximation
|
||||
*
|
||||
* @var float[]
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private const ERF_COF = [
|
||||
-1.3026537197817094, 6.4196979235649026e-1,
|
||||
1.9476473204185836e-2,-9.561514786808631e-3,-9.46595344482036e-4,
|
||||
|
|
@ -253,172 +259,99 @@ final class Functions
|
|||
-1.12708e-13,3.81e-16,7.106e-15,-1.523e-15,-9.4e-17,1.21e-16,-2.8e-17
|
||||
];
|
||||
|
||||
/**
|
||||
* Error function
|
||||
*
|
||||
* @param float $x X-Value
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getErf(float $x) : float
|
||||
{
|
||||
return $x > 0.0
|
||||
? 1.0 - self::erfccheb($x)
|
||||
: self::erfccheb(-$x) - 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complementary error function
|
||||
*
|
||||
* @param float $x X-Value
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getErfc(float $x) : float
|
||||
{
|
||||
return $x > 0.0
|
||||
? self::erfccheb($x)
|
||||
: 2.0 - self::erfccheb(-$x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error function helper function
|
||||
*
|
||||
* @param float $z Z-Value
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function erfccheb(float $z) : float
|
||||
{
|
||||
$d = 0.;
|
||||
$d = 0.;
|
||||
$dd = 0.;
|
||||
|
||||
$ncof = \count(self::ERF_COF);
|
||||
|
||||
if ($z < 0.) {
|
||||
if ($z < 0.) {
|
||||
throw new \InvalidArgumentException("erfccheb requires nonnegative argument");
|
||||
}
|
||||
|
||||
$t = 2. / (2. + $z);
|
||||
$ty = 4. * $t - 2.;
|
||||
$t = 2. / (2. + $z);
|
||||
$ty = 4. * $t - 2.;
|
||||
|
||||
for ($j = $ncof - 1; $j > 0; --$j) {
|
||||
$tmp = $d;
|
||||
$d = $ty * $d - $dd + self::ERF_COF[$j];
|
||||
$dd = $tmp;
|
||||
}
|
||||
for ($j = $ncof - 1; $j > 0; --$j) {
|
||||
$tmp = $d;
|
||||
$d = $ty * $d - $dd + self::ERF_COF[$j];
|
||||
$dd = $tmp;
|
||||
}
|
||||
|
||||
return $t * \exp(-$z * $z + 0.5*(self::ERF_COF[0] + $ty * $d) - $dd);
|
||||
}
|
||||
return $t * \exp(-$z * $z + 0.5*(self::ERF_COF[0] + $ty * $d) - $dd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse complementary error function
|
||||
*
|
||||
* @param float $p P-Value
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getInvErfc(float $p) : float
|
||||
{
|
||||
if ($p >= 2.0) {
|
||||
if ($p >= 2.0) {
|
||||
return -100.;
|
||||
} elseif ($p <= 0.0) {
|
||||
return 100.;
|
||||
}
|
||||
|
||||
$pp = ($p < 1.0) ? $p : 2. - $p;
|
||||
$t = sqrt(-2. * \log($pp / 2.));
|
||||
$x = -0.70711 * ((2.30753 + $t * 0.27061)/(1. + $t * (0.99229 + $t * 0.04481)) - $t);
|
||||
$pp = ($p < 1.0) ? $p : 2. - $p;
|
||||
$t = sqrt(-2. * \log($pp / 2.));
|
||||
$x = -0.70711 * ((2.30753 + $t * 0.27061)/(1. + $t * (0.99229 + $t * 0.04481)) - $t);
|
||||
|
||||
for ($j = 0; $j < 2; ++$j) {
|
||||
$err = self::getErfc($x) - $pp;
|
||||
$x += $err / (1.12837916709551257 * \exp(-($x * $x)) - $x * $err);
|
||||
}
|
||||
|
||||
return ($p < 1.0? $x : -$x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the value of the error function (gauss error function)
|
||||
*
|
||||
* @param float $value Value
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @see Sylvain Chevillard; HAL Id: ensl-00356709
|
||||
* @see https://hal-ens-lyon.archives-ouvertes.fr/ensl-00356709v3
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
/*
|
||||
public static function getErf(float $value) : float
|
||||
{
|
||||
if (\abs($value) > 2.2) {
|
||||
return 1 - self::getErfc($value);
|
||||
for ($j = 0; $j < 2; ++$j) {
|
||||
$err = self::getErfc($x) - $pp;
|
||||
$x += $err / (1.12837916709551257 * \exp(-($x * $x)) - $x * $err);
|
||||
}
|
||||
|
||||
$valueSquared = $value * $value;
|
||||
$sum = $value;
|
||||
$term = $value;
|
||||
$i = 1;
|
||||
|
||||
do {
|
||||
$term *= $valueSquared / $i;
|
||||
$sum -= $term / (2 * $i + 1);
|
||||
|
||||
++$i;
|
||||
|
||||
$term *= $valueSquared / $i;
|
||||
$sum += $term / (2 * $i + 1);
|
||||
|
||||
++$i;
|
||||
} while ($sum !== 0.0 && \abs($term / $sum) > self::EPSILON);
|
||||
|
||||
return 2 / \sqrt(\M_PI) * $sum;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate the value of the complementary error fanction
|
||||
*
|
||||
* @param float $value Value
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @see Sylvain Chevillard; HAL Id: ensl-00356709
|
||||
* @see https://hal-ens-lyon.archives-ouvertes.fr/ensl-00356709v3
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
/*
|
||||
public static function getErfc(float $value) : float
|
||||
{
|
||||
if (\abs($value) <= 2.2) {
|
||||
return 1 - self::getErf($value);
|
||||
}
|
||||
|
||||
if ($value < 0.0) {
|
||||
return 2 - self::getErfc(-$value);
|
||||
}
|
||||
|
||||
$a = $n = 1;
|
||||
$b = $c = $value;
|
||||
$d = ($value * $value) + 0.5;
|
||||
$q1 = $q2 = $b / $d;
|
||||
$t = 0;
|
||||
|
||||
do {
|
||||
$t = $a * $n + $b * $value;
|
||||
$a = $b;
|
||||
$b = $t;
|
||||
$t = $c * $n + $d * $value;
|
||||
$c = $d;
|
||||
$d = $t;
|
||||
$n += 0.5;
|
||||
$q1 = $q2;
|
||||
$q2 = $b / $d;
|
||||
} while (\abs($q1 - $q2) / $q2 > self::EPSILON);
|
||||
|
||||
return 1 / \sqrt(\M_PI) * \exp(-$value * $value) * $q2;
|
||||
}
|
||||
*/
|
||||
|
||||
public static function getErfcInv(float $value) : float
|
||||
{
|
||||
if ($value >= 2) {
|
||||
return \PHP_FLOAT_MIN;
|
||||
} elseif ($value <= 0) {
|
||||
return \PHP_FLOAT_MAX;
|
||||
} elseif ($value === 1.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if ($ge = ($value >= 1)) {
|
||||
$value = 2 - $value;
|
||||
}
|
||||
|
||||
$t = \sqrt(-2 * \log($value / 2.0));
|
||||
$x = -0.70711 * ((2.30753 + $t * 0.27061) / (1. + $t * (0.99229 + $t * 0.04481)) - $t);
|
||||
|
||||
$err = self::getErfc($x) - $value;
|
||||
$x = $err / (1.12837916709551257 * \exp(-($x ** 2)) - $x * $err);
|
||||
|
||||
$err = self::getErfc($x) - $value;
|
||||
$x = $err / (1.12837916709551257 * \exp(-($x ** 2)) - $x * $err);
|
||||
|
||||
return $ge ? -$x : $x;
|
||||
return ($p < 1.0? $x : -$x);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -688,9 +688,19 @@ class Matrix implements \ArrayAccess, \Iterator
|
|||
public function det() : float
|
||||
{
|
||||
$L = new LUDecomposition($this);
|
||||
|
||||
return $L->det();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dot product
|
||||
*
|
||||
* @param self $B Matrix
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function dot(self $B) : self
|
||||
{
|
||||
$value1 = $this->matrix;
|
||||
|
|
@ -751,7 +761,16 @@ class Matrix implements \ArrayAccess, \Iterator
|
|||
return self::fromArray($result);
|
||||
}
|
||||
|
||||
public function sum(int $axis = -1)
|
||||
/**
|
||||
* Sum the elements in the matrix
|
||||
*
|
||||
* @param int $axis Axis (-1 -> all dimensions, 0 -> columns, 1 -> rows)
|
||||
*
|
||||
* @return int|float|self Returns int or float for axis -1
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function sum(int $axis = -1) : int|float|self
|
||||
{
|
||||
if ($axis === -1) {
|
||||
$sum = 0;
|
||||
|
|
@ -786,6 +805,13 @@ class Matrix implements \ArrayAccess, \Iterator
|
|||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is matrix a diagonal matrix
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function isDiagonal() : bool
|
||||
{
|
||||
if ($this->m !== $this->n) {
|
||||
|
|
@ -803,6 +829,17 @@ class Matrix implements \ArrayAccess, \Iterator
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the power of a matrix
|
||||
*
|
||||
* @param int|float $exponent Exponent
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @throws InvalidDimensionException
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function pow(int | float $exponent) : self
|
||||
{
|
||||
if ($this->isDiagonal()) {
|
||||
|
|
@ -839,6 +876,15 @@ class Matrix implements \ArrayAccess, \Iterator
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate e^M
|
||||
*
|
||||
* @param int $iterations Iterations for approximation
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function exp(int $iterations = 10) : self
|
||||
{
|
||||
if ($this->m !== $this->n) {
|
||||
|
|
|
|||
|
|
@ -88,10 +88,19 @@ final class Vector extends Matrix
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Angle between two vectors
|
||||
*
|
||||
* @param self $v Vector
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function cosine(self $v) : float
|
||||
{
|
||||
$dotProduct = 0;
|
||||
for ($i = 0; $i < \count($this->matrix); $i++) {
|
||||
for ($i = 0; $i < $this->m; ++$i) {
|
||||
$dotProduct += $this->matrix[$i][0] * $v[$i];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,20 +15,41 @@ declare(strict_types=1);
|
|||
namespace phpOMS\Math\Solver\Root;
|
||||
|
||||
/**
|
||||
* Basic math function evaluation.
|
||||
* Find the root of a function.
|
||||
*
|
||||
* @package phpOMS\Math\Solver\Root
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo: implement
|
||||
*/
|
||||
final class Bisection
|
||||
{
|
||||
/**
|
||||
* Epsilon for float comparison.
|
||||
*
|
||||
* @var float
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public const EPSILON = 1e-6;
|
||||
|
||||
public static function bisection(Callable $func, float $a, float $b, $maxIterations = 100) {
|
||||
/**
|
||||
* Perform bisection to find the root of a function
|
||||
*
|
||||
* Iteratively searches for root between two points on the x-axis
|
||||
*
|
||||
* @param Callable $func Function defintion
|
||||
* @param float $a Start value
|
||||
* @param float $b End value
|
||||
* @param int $maxIterations Maximum amount of iterations
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function root(Callable $func, float $a, float $b, int $maxIterations = 100) : float
|
||||
{
|
||||
if ($func($a) * $func($b) >= 0) {
|
||||
throw new \Exception("Function values at endpoints must have opposite signs.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,27 +15,48 @@ declare(strict_types=1);
|
|||
namespace phpOMS\Math\Solver\Root;
|
||||
|
||||
/**
|
||||
* Basic math function evaluation.
|
||||
* Find the root of a function.
|
||||
*
|
||||
* @package phpOMS\Math\Solver\Root
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo: implement
|
||||
*/
|
||||
final class Illinois
|
||||
{
|
||||
/**
|
||||
* Epsilon for float comparison.
|
||||
*
|
||||
* @var float
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public const EPSILON = 1e-6;
|
||||
|
||||
public static function bisection(Callable $func, float $a, float $b, $maxIterations = 100) {
|
||||
/**
|
||||
* Perform bisection to find the root of a function
|
||||
*
|
||||
* Iteratively searches for root between two points on the x-axis
|
||||
*
|
||||
* @param Callable $func Function defintion
|
||||
* @param float $a Start value
|
||||
* @param float $b End value
|
||||
* @param int $maxIterations Maximum amount of iterations
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function root(Callable $func, float $a, float $b, int $maxIterations = 100) : float
|
||||
{
|
||||
if ($func($a) * $func($b) >= 0) {
|
||||
throw new \Exception("Function values at endpoints must have opposite signs.");
|
||||
}
|
||||
|
||||
$c = $b;
|
||||
$c = $b;
|
||||
$iteration = 0;
|
||||
$sign = 1;
|
||||
$sign = 1;
|
||||
|
||||
while (($y = \abs($func($c))) > self::EPSILON && $iteration < $maxIterations) {
|
||||
$fa = $func($a);
|
||||
|
|
|
|||
|
|
@ -15,25 +15,46 @@ declare(strict_types=1);
|
|||
namespace phpOMS\Math\Solver\Root;
|
||||
|
||||
/**
|
||||
* Basic math function evaluation.
|
||||
* Find the root of a function.
|
||||
*
|
||||
* @package phpOMS\Math\Solver\Root
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo: implement
|
||||
*/
|
||||
final class RegulaFalsi
|
||||
{
|
||||
/**
|
||||
* Epsilon for float comparison.
|
||||
*
|
||||
* @var float
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public const EPSILON = 1e-6;
|
||||
|
||||
public static function bisection(Callable $func, float $a, float $b, $maxIterations = 100) {
|
||||
/**
|
||||
* Perform bisection to find the root of a function
|
||||
*
|
||||
* Iteratively searches for root between two points on the x-axis
|
||||
*
|
||||
* @param Callable $func Function defintion
|
||||
* @param float $a Start value
|
||||
* @param float $b End value
|
||||
* @param int $maxIterations Maximum amount of iterations
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function root(Callable $func, float $a, float $b, int $maxIterations = 100) : float
|
||||
{
|
||||
if ($func($a) * $func($b) >= 0) {
|
||||
throw new \Exception("Function values at endpoints must have opposite signs.");
|
||||
}
|
||||
|
||||
$c = $b;
|
||||
$c = $b;
|
||||
$iteration = 0;
|
||||
|
||||
while (($y = \abs($func($c))) > self::EPSILON && $iteration < $maxIterations) {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,17 @@ final class NormalDistribution
|
|||
return 0.5 * Functions::getErf(-($x - $mu) / ($sig * \sqrt(2)));
|
||||
}
|
||||
|
||||
// AKA Quantile function and sometimes PPF
|
||||
/**
|
||||
* Inverse cumulative distribution function / AKA Quantile function / PPF
|
||||
*
|
||||
* @param float $x Value x
|
||||
* @param float $mu Mean
|
||||
* @param float $sig Sigma
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getICdf(float $x, float $mu, float $sig) : float
|
||||
{
|
||||
return $mu - $sig * \sqrt(2) * Functions::getInvErfc(2 * $x);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ declare(strict_types=1);
|
|||
namespace phpOMS\Math\Topology;
|
||||
|
||||
/**
|
||||
* Metrics.
|
||||
* Kernels.
|
||||
*
|
||||
* @package phpOMS\Math\Topology
|
||||
* @license OMS License 2.0
|
||||
|
|
@ -35,6 +35,16 @@ final class Kernels2D
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniform kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function uniformKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
return \abs($distance) <= $bandwidth / 2
|
||||
|
|
@ -42,6 +52,16 @@ final class Kernels2D
|
|||
: 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triangular kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function triangularKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
return \abs($distance) <= $bandwidth / 2
|
||||
|
|
@ -49,6 +69,16 @@ final class Kernels2D
|
|||
: 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Epanechnikov kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function epanechnikovKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
if (\abs($distance) <= $bandwidth) {
|
||||
|
|
@ -60,6 +90,16 @@ final class Kernels2D
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quartic kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function quarticKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
if (\abs($distance) <= $bandwidth) {
|
||||
|
|
@ -71,6 +111,16 @@ final class Kernels2D
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triweight kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function triweightKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
if (\abs($distance) <= $bandwidth) {
|
||||
|
|
@ -82,6 +132,16 @@ final class Kernels2D
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tricube kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function tricubeKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
if (\abs($distance) <= $bandwidth) {
|
||||
|
|
@ -93,11 +153,31 @@ final class Kernels2D
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gaussian kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function gaussianKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
return \exp(-($distance * $distance) / (2 * $bandwidth * $bandwidth)) / ($bandwidth * \sqrt(2 * \M_PI));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cosine kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function cosineKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
return \abs($distance) <= $bandwidth
|
||||
|
|
@ -105,6 +185,16 @@ final class Kernels2D
|
|||
: 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logistic kernel.
|
||||
*
|
||||
* @param float $distance Distance
|
||||
* @param float $bandwidth Bandwidth
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function logisticKernel(float $distance, float $bandwidth) : float
|
||||
{
|
||||
return 1 / (\exp($distance / $bandwidth) + 2 + \exp(-$distance / $bandwidth));
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use phpOMS\Math\Matrix\IdentityMatrix;
|
|||
use phpOMS\Math\Matrix\Matrix;
|
||||
|
||||
/**
|
||||
* Metrics.
|
||||
* Kernels.
|
||||
*
|
||||
* @package phpOMS\Math\Topology
|
||||
* @license OMS License 2.0
|
||||
|
|
@ -38,6 +38,16 @@ final class KernelsND
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gaussian kernel
|
||||
*
|
||||
* @param array<float|int> $distances Distances
|
||||
* @param array<float|int> $bandwidths Bandwidths
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function gaussianKernel(array $distances, array $bandwidths) : array
|
||||
{
|
||||
$dim = \count($bandwidths);
|
||||
|
|
|
|||
|
|
@ -93,8 +93,24 @@ final class MetricsND
|
|||
return \sqrt($dist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cosine metric.
|
||||
*
|
||||
* @param array<int|string, int|float> $a n-D array
|
||||
* @param array<int|string, int|float> $b n-D array
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @throws InvalidDimensionException
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function cosine(array $a, array $b) : float
|
||||
{
|
||||
if (\count($a) !== \count($b)) {
|
||||
throw new InvalidDimensionException(\count($a) . 'x' . \count($b));
|
||||
}
|
||||
|
||||
$dotProduct = 0;
|
||||
for ($i = 0; $i < \count($a); $i++) {
|
||||
$dotProduct += $a[$i] * $b[$i];
|
||||
|
|
|
|||
|
|
@ -281,6 +281,15 @@ final class FileUtils
|
|||
return !\preg_match('#^[a-z][a-z\d+.-]*://#i', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a string into a safe file name (sanitize a string)
|
||||
*
|
||||
* @param string $name String to sanitize for file name usage
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function makeSafeFileName(string $name) : string
|
||||
{
|
||||
$name = \preg_replace("/[^A-Za-z0-9\-_.]/", '_', $name);
|
||||
|
|
|
|||
|
|
@ -2028,6 +2028,16 @@ abstract class MimeType extends Enum
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension from a mime
|
||||
*
|
||||
* @param string $mime Mime
|
||||
*
|
||||
* @return null|string
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @todo continue implementation
|
||||
*/
|
||||
public static function mimeToExtension(string $mime) : ?string
|
||||
{
|
||||
switch($mime) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ declare(strict_types=1);
|
|||
namespace phpOMS\Utils\Formatter;
|
||||
|
||||
/**
|
||||
* Gray encoding class
|
||||
* Html code formatter
|
||||
*
|
||||
* @package phpOMS\Utils\Formatter
|
||||
* @license OMS License 2.0
|
||||
|
|
@ -24,6 +24,15 @@ namespace phpOMS\Utils\Formatter;
|
|||
*/
|
||||
class HtmlFormatter
|
||||
{
|
||||
/**
|
||||
* Format html code
|
||||
*
|
||||
* @param string $text Html code
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function format(string $text) : string
|
||||
{
|
||||
$dom = new \DOMDocument();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user