From 0a15561352b84ebbb7452f7d359bd39d62a2c68e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 18 Feb 2018 00:09:24 +0100 Subject: [PATCH] Fix KMeans and EigenvalueDecomposition (#235) * Fix kmeans cluster and eigenvalue decomposition * Fix kmeans space * Fix code style --- src/Clustering/KMeans/Cluster.php | 4 +- src/Clustering/KMeans/Space.php | 2 +- .../LinearAlgebra/EigenvalueDecomposition.php | 10 +-- .../EigenvalueDecompositionTest.php | 66 +++++++++++++++---- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php index 2011b87..a4462fe 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -76,7 +76,8 @@ class Cluster extends Point implements IteratorAggregate, Countable public function updateCentroid(): void { - if (empty($this->points)) { + $count = count($this->points); + if ($count === 0) { return; } @@ -88,7 +89,6 @@ class Cluster extends Point implements IteratorAggregate, Countable } } - $count = count($this->points); for ($n = 0; $n < $this->dimension; ++$n) { $this->coordinates[$n] = $centroid->coordinates[$n] / $count; } diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index 3c9f134..b85b329 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -75,7 +75,7 @@ class Space extends SplObjectStorage */ public function getBoundaries() { - if (empty($this)) { + if (count($this) === 0) { return false; } diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index 8aac90b..19f3c43 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -99,7 +99,7 @@ class EigenvalueDecomposition $this->n = count($Arg[0]); $this->symmetric = true; - for ($j = 0; ($j < $this->n) && $this->symmetric; ++$j) { + for ($j = 0; ($j < $this->n) & $this->symmetric; ++$j) { for ($i = 0; ($i < $this->n) & $this->symmetric; ++$i) { $this->symmetric = ($this->A[$i][$j] == $this->A[$j][$i]); } @@ -204,7 +204,7 @@ class EigenvalueDecomposition $scale += array_sum(array_map('abs', $this->d)); if ($scale == 0.0) { $this->e[$i] = $this->d[$i_]; - $this->d = array_slice($this->V[$i_], 0, $i_); + $this->d = array_slice($this->V[$i_], 0, $this->n - 1); for ($j = 0; $j < $i; ++$j) { $this->V[$j][$i] = $this->V[$i][$j] = 0.0; } @@ -244,7 +244,8 @@ class EigenvalueDecomposition } $f = 0.0; - if ($h === 0 || $h < 1e-32) { + + if ($h == 0.0) { $h = 1e-32; } @@ -274,7 +275,6 @@ class EigenvalueDecomposition } // Accumulate transformations. - $j = 0; for ($i = 0; $i < $this->n - 1; ++$i) { $this->V[$this->n - 1][$i] = $this->V[$i][$i]; $this->V[$i][$i] = 1.0; @@ -302,7 +302,7 @@ class EigenvalueDecomposition } $this->d = $this->V[$this->n - 1]; - $this->V[$this->n - 1] = array_fill(0, $j, 0.0); + $this->V[$this->n - 1] = array_fill(0, $this->n, 0.0); $this->V[$this->n - 1][$this->n - 1] = 1.0; $this->e[0] = 0.0; } diff --git a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php index a08ddc1..73018d0 100644 --- a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php +++ b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php @@ -10,34 +10,72 @@ use PHPUnit\Framework\TestCase; class EigenvalueDecompositionTest extends TestCase { - public function testSymmetricMatrixEigenPairs(): void + public function testKnownSymmetricMatrixDecomposition(): void { - // Acceptable error - $epsilon = 0.001; - // First a simple example whose result is known and given in // http://www.cs.otago.ac.nz/cosc453/student_tutorials/principal_components.pdf $matrix = [ [0.616555556, 0.615444444], [0.614444444, 0.716555556], ]; - $knownEigvalues = [0.0490833989, 1.28402771]; - $knownEigvectors = [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]]; $decomp = new EigenvalueDecomposition($matrix); - $eigVectors = $decomp->getEigenvectors(); - $eigValues = $decomp->getRealEigenvalues(); - $this->assertEquals($knownEigvalues, $eigValues, '', $epsilon); - $this->assertEquals($knownEigvectors, $eigVectors, '', $epsilon); + self::assertEquals([0.0490833989, 1.28402771], $decomp->getRealEigenvalues(), '', 0.001); + self::assertEquals([ + [-0.735178656, 0.677873399], + [-0.677873399, -0.735178656], + ], $decomp->getEigenvectors(), '', 0.001); + } + + public function testMatrixWithAllZeroRow(): void + { + // http://www.wolframalpha.com/widgets/view.jsp?id=9aa01caf50c9307e9dabe159c9068c41 + $matrix = [ + [10, 0, 0], + [0, 6, 0], + [0, 0, 0], + ]; + + $decomp = new EigenvalueDecomposition($matrix); + + self::assertEquals([0.0, 6.0, 10.0], $decomp->getRealEigenvalues(), '', 0.0001); + self::assertEquals([ + [0, 0, 1], + [0, 1, 0], + [1, 0, 0], + ], $decomp->getEigenvectors(), '', 0.0001); + } + + public function testMatrixThatCauseErrorWithStrictComparision(): void + { + // http://www.wolframalpha.com/widgets/view.jsp?id=9aa01caf50c9307e9dabe159c9068c41 + $matrix = [ + [1, 0, 3], + [0, 1, 7], + [3, 7, 4], + ]; + + $decomp = new EigenvalueDecomposition($matrix); + + self::assertEquals([-5.2620873481, 1.0, 10.2620873481], $decomp->getRealEigenvalues(), '', 0.000001); + self::assertEquals([ + [-0.3042688, -0.709960552, 0.63511928], + [-0.9191450, 0.393919298, 0.0], + [0.25018574, 0.5837667, 0.7724140], + ], $decomp->getEigenvectors(), '', 0.0001); + } + + public function testRandomSymmetricMatrixEigenPairs(): void + { + // Acceptable error + $epsilon = 0.001; // Secondly, generate a symmetric square matrix // and test for A.v=λ.v - // // (We, for now, omit non-symmetric matrices whose eigenvalues can be complex numbers) $len = 3; + srand((int) microtime(true) * 1000); $A = array_fill(0, $len, array_fill(0, $len, 0.0)); - $seed = microtime(true) * 1000; - srand((int) $seed); for ($i = 0; $i < $len; ++$i) { for ($k = 0; $k < $len; ++$k) { if ($i > $k) { @@ -60,7 +98,7 @@ class EigenvalueDecompositionTest extends TestCase $leftSide = $m1->multiply($m2)->toArray(); $rightSide = $m2->multiplyByScalar($lambda)->toArray(); - $this->assertEquals($leftSide, $rightSide, '', $epsilon); + self::assertEquals($leftSide, $rightSide, '', $epsilon); } } }