AutorÃa | Ultima modificación | Ver Log |
<?phpdeclare(strict_types=1);namespace Phpml\Clustering\KMeans;use InvalidArgumentException;use LogicException;use Phpml\Clustering\KMeans;use SplObjectStorage;class Space extends SplObjectStorage{/*** @var int*/protected $dimension;public function __construct(int $dimension){if ($dimension < 1) {throw new LogicException('a space dimension cannot be null or negative');}$this->dimension = $dimension;}public function toArray(): array{$points = [];/** @var Point $point */foreach ($this as $point) {$points[] = $point->toArray();}return ['points' => $points];}/*** @param mixed $label*/public function newPoint(array $coordinates, $label = null): Point{if (count($coordinates) !== $this->dimension) {throw new LogicException('('.implode(',', $coordinates).') is not a point of this space');}return new Point($coordinates, $label);}/*** @param mixed $label* @param mixed $data*/public function addPoint(array $coordinates, $label = null, $data = null): void{$this->attach($this->newPoint($coordinates, $label), $data);}/*** @param object $point* @param mixed $data*/public function attach($point, $data = null): void{if (!$point instanceof Point) {throw new InvalidArgumentException('can only attach points to spaces');}parent::attach($point, $data);}public function getDimension(): int{return $this->dimension;}/*** @return array|bool*/public function getBoundaries(){if (count($this) === 0) {return false;}$min = $this->newPoint(array_fill(0, $this->dimension, null));$max = $this->newPoint(array_fill(0, $this->dimension, null));/** @var Point $point */foreach ($this as $point) {for ($n = 0; $n < $this->dimension; ++$n) {if ($min[$n] === null || $min[$n] > $point[$n]) {$min[$n] = $point[$n];}if ($max[$n] === null || $max[$n] < $point[$n]) {$max[$n] = $point[$n];}}}return [$min, $max];}public function getRandomPoint(Point $min, Point $max): Point{$point = $this->newPoint(array_fill(0, $this->dimension, null));for ($n = 0; $n < $this->dimension; ++$n) {$point[$n] = random_int($min[$n], $max[$n]);}return $point;}/*** @return Cluster[]*/public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM): array{$clusters = $this->initializeClusters($clustersNumber, $initMethod);do {} while (!$this->iterate($clusters));return $clusters;}/*** @return Cluster[]*/protected function initializeClusters(int $clustersNumber, int $initMethod): array{switch ($initMethod) {case KMeans::INIT_RANDOM:$clusters = $this->initializeRandomClusters($clustersNumber);break;case KMeans::INIT_KMEANS_PLUS_PLUS:$clusters = $this->initializeKMPPClusters($clustersNumber);break;default:return [];}$clusters[0]->attachAll($this);return $clusters;}/*** @param Cluster[] $clusters*/protected function iterate(array $clusters): bool{$convergence = true;$attach = new SplObjectStorage();$detach = new SplObjectStorage();foreach ($clusters as $cluster) {foreach ($cluster as $point) {$closest = $point->getClosest($clusters);if ($closest === null) {continue;}if ($closest !== $cluster) {$attach[$closest] ?? $attach[$closest] = new SplObjectStorage();$detach[$cluster] ?? $detach[$cluster] = new SplObjectStorage();$attach[$closest]->attach($point);$detach[$cluster]->attach($point);$convergence = false;}}}/** @var Cluster $cluster */foreach ($attach as $cluster) {$cluster->attachAll($attach[$cluster]);}/** @var Cluster $cluster */foreach ($detach as $cluster) {$cluster->detachAll($detach[$cluster]);}foreach ($clusters as $cluster) {$cluster->updateCentroid();}return $convergence;}/*** @return Cluster[]*/protected function initializeKMPPClusters(int $clustersNumber): array{$clusters = [];$this->rewind();/** @var Point $current */$current = $this->current();$clusters[] = new Cluster($this, $current->getCoordinates());$distances = new SplObjectStorage();for ($i = 1; $i < $clustersNumber; ++$i) {$sum = 0;/** @var Point $point */foreach ($this as $point) {$closest = $point->getClosest($clusters);if ($closest === null) {continue;}$distance = $point->getDistanceWith($closest);$sum += $distances[$point] = $distance;}$sum = random_int(0, (int) $sum);/** @var Point $point */foreach ($this as $point) {$sum -= $distances[$point];if ($sum > 0) {continue;}$clusters[] = new Cluster($this, $point->getCoordinates());break;}}return $clusters;}/*** @return Cluster[]*/private function initializeRandomClusters(int $clustersNumber): array{$clusters = [];[$min, $max] = $this->getBoundaries();for ($n = 0; $n < $clustersNumber; ++$n) {$clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates());}return $clusters;}}