diff --git a/composer.lock b/composer.lock index 2af6840..33a80c6 100644 --- a/composer.lock +++ b/composer.lock @@ -1636,16 +1636,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.7", + "version": "1.11.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", "shasum": "" }, "require": { @@ -1690,7 +1690,7 @@ "type": "github" } ], - "time": "2024-07-06T11:17:41+00:00" + "time": "2024-07-24T07:01:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2280,16 +2280,16 @@ }, { "name": "rector/rector", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "b38a3eed3ce2046f40c001255e2fec9d2746bacf" + "reference": "044e6364017882d1e346da8690eeabc154da5495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/b38a3eed3ce2046f40c001255e2fec9d2746bacf", - "reference": "b38a3eed3ce2046f40c001255e2fec9d2746bacf", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/044e6364017882d1e346da8690eeabc154da5495", + "reference": "044e6364017882d1e346da8690eeabc154da5495", "shasum": "" }, "require": { @@ -2327,7 +2327,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.1" + "source": "https://github.com/rectorphp/rector/tree/1.2.2" }, "funding": [ { @@ -2335,7 +2335,7 @@ "type": "github" } ], - "time": "2024-07-16T00:22:54+00:00" + "time": "2024-07-25T07:44:34+00:00" }, { "name": "revolt/event-loop", diff --git a/src/Numerics/Matrix.php b/src/Numerics/Matrix.php index 3c6daf4..f3ff6ea 100644 --- a/src/Numerics/Matrix.php +++ b/src/Numerics/Matrix.php @@ -60,13 +60,34 @@ class Matrix return $this->columnCount; } + private function checkRowCol(int $row, int $col): void + { + if ($row < 0) { + throw new Exception("Row negative"); + } + + if ($row >= $this->getRowCount()) { + throw new Exception("Row beyond range"); + } + + if ($col < 0) { + throw new Exception("Column negative"); + } + + if ($col >= $this->getColumnCount()) { + throw new Exception("Column beyond range"); + } + } + public function getValue(int $row, int $col): float|int { + $this->checkRowCol($row, $col); return $this->matrixRowData[$row][$col]; } public function setValue(int $row, int $col, float|int $value): void { + $this->checkRowCol($row, $col); $this->matrixRowData[$row][$col] = $value; } @@ -100,7 +121,7 @@ class Matrix if ($this->rowCount == 1) { // Really happy path :) - return $this->matrixRowData[0][0]; + return $this->getValue(0, 0); } if ($this->rowCount == 2) { @@ -109,10 +130,10 @@ class Matrix // | a b | // | c d | // The determinant is ad - bc - $a = $this->matrixRowData[0][0]; - $b = $this->matrixRowData[0][1]; - $c = $this->matrixRowData[1][0]; - $d = $this->matrixRowData[1][1]; + $a = $this->getValue(0, 0); + $b = $this->getValue(0, 1); + $c = $this->getValue(1, 0); + $d = $this->getValue(1, 1); return $a * $d - $b * $c; } @@ -127,7 +148,7 @@ class Matrix // I expand along the first row for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) { - $firstRowColValue = $this->matrixRowData[0][$currentColumn]; + $firstRowColValue = $this->getValue(0, $currentColumn); $cofactor = $this->getCofactor(0, $currentColumn); $itemToAdd = $firstRowColValue * $cofactor; $result += $itemToAdd; @@ -152,10 +173,10 @@ class Matrix // | d -b | // | -c a | - $a = $this->matrixRowData[0][0]; - $b = $this->matrixRowData[0][1]; - $c = $this->matrixRowData[1][0]; - $d = $this->matrixRowData[1][1]; + $a = $this->getValue(0, 0); + $b = $this->getValue(0, 1); + $c = $this->getValue(1, 0); + $d = $this->getValue(1, 1); return new SquareMatrix( $d, @@ -180,7 +201,7 @@ class Matrix public function getInverse(): Matrix|SquareMatrix { if (($this->rowCount == 1) && ($this->columnCount == 1)) { - return new SquareMatrix(1.0 / $this->matrixRowData[0][0]); + return new SquareMatrix(1.0 / $this->getValue(0, 0)); } // Take the simple approach: @@ -262,6 +283,7 @@ class Matrix private function getMinorMatrix(int $rowToRemove, int $columnToRemove): Matrix { + $this->checkRowCol($rowToRemove, $columnToRemove); // See http://en.wikipedia.org/wiki/Minor_(linear_algebra) // I'm going to use a horribly naïve algorithm... because I can :) @@ -281,7 +303,7 @@ class Matrix continue; } - $result[$actualRow][$actualCol] = $this->matrixRowData[$currentRow][$currentColumn]; + $result[$actualRow][$actualCol] = $this->getValue($currentRow, $currentColumn); ++$actualCol; } @@ -294,6 +316,7 @@ class Matrix public function getCofactor(int $rowToRemove, int $columnToRemove): float { + $this->checkRowCol($rowToRemove, $columnToRemove); // See http://en.wikipedia.org/wiki/Cofactor_(linear_algebra) for details // REVIEW: should things be reversed since I'm 0 indexed? $sum = $rowToRemove + $columnToRemove; @@ -316,7 +339,7 @@ class Matrix for ($currentColumn = 0; $currentColumn < $this->columnCount; ++$currentColumn) { $delta = abs( - $this->matrixRowData[$currentRow][$currentColumn] - + $this->getValue($currentRow, $currentColumn) - $otherMatrix->getValue($currentRow, $currentColumn) ); diff --git a/tests/GuardTest.php b/tests/GuardTest.php index 3de9b7d..df9f9d1 100644 --- a/tests/GuardTest.php +++ b/tests/GuardTest.php @@ -28,7 +28,9 @@ class GuardTest extends TestCase public function testargumentIsValidIndexArgumentValid(): void { - Guard::argumentIsValidIndex(5, 10, "dummy"); + Guard::argumentIsValidIndex(0, 10, "dummy"); + Guard::argumentIsValidIndex(1, 10, "dummy"); + Guard::argumentIsValidIndex(9, 10, "dummy"); $this->expectNotToPerformAssertions(); } @@ -48,7 +50,12 @@ class GuardTest extends TestCase public function testargumentInRangeInclusiveValid(): void { + Guard::argumentInRangeInclusive(0, 0, 100, "dummy"); + Guard::argumentInRangeInclusive(1, 0, 100, "dummy"); Guard::argumentInRangeInclusive(50, 0, 100, "dummy"); + Guard::argumentInRangeInclusive(99, 0, 100, "dummy"); + Guard::argumentInRangeInclusive(100, 0, 100, "dummy"); + $this->expectNotToPerformAssertions(); } } diff --git a/tests/HashMapTest.php b/tests/HashMapTest.php index b62f6cb..052d333 100644 --- a/tests/HashMapTest.php +++ b/tests/HashMapTest.php @@ -27,6 +27,7 @@ class HashMapTest extends TestCase $h->setvalue($o2, 2); $this->assertEquals([1, 2], $h->getAllValues()); + $this->assertEquals([$o1, $o2], $h->getAllKeys()); $this->assertEquals(1, $h->getvalue($o1)); $this->assertEquals(2, $h->getvalue($o2)); diff --git a/tests/Numerics/GaussianDistributionTest.php b/tests/Numerics/GaussianDistributionTest.php index 90ee863..853bd1a 100644 --- a/tests/Numerics/GaussianDistributionTest.php +++ b/tests/Numerics/GaussianDistributionTest.php @@ -92,6 +92,20 @@ class GaussianDistributionTest extends TestCase $m3s4 = new GaussianDistribution(3, 4); $lpn2 = GaussianDistribution::logProductNormalization($m1s2, $m3s4); $this->assertEqualsWithDelta(-2.5168046699816684, $lpn2, GaussianDistributionTest::ERROR_TOLERANCE); + + $numerator = GaussianDistribution::fromPrecisionMean(1, 0); + $denominator = GaussianDistribution::fromPrecisionMean(1, 0); + $lrn = GaussianDistribution::logProductNormalization($numerator, $denominator); + $this->assertEquals(0, $lrn); + + $numerator = GaussianDistribution::fromPrecisionMean(1, 1); + $denominator = GaussianDistribution::fromPrecisionMean(1, 0); + $lrn = GaussianDistribution::logProductNormalization($numerator, $denominator); + $this->assertEquals(0, $lrn); + $numerator = GaussianDistribution::fromPrecisionMean(1, 0); + $denominator = GaussianDistribution::fromPrecisionMean(1, 1); + $lrn = GaussianDistribution::logProductNormalization($numerator, $denominator); + $this->assertEquals(0, $lrn); } public function testLogRatioNormalization(): void @@ -101,6 +115,20 @@ class GaussianDistributionTest extends TestCase $m3s4 = new GaussianDistribution(3, 4); $lrn = GaussianDistribution::logRatioNormalization($m1s2, $m3s4); $this->assertEqualsWithDelta(2.6157405972171204, $lrn, GaussianDistributionTest::ERROR_TOLERANCE); + + $numerator = GaussianDistribution::fromPrecisionMean(1, 0); + $denominator = GaussianDistribution::fromPrecisionMean(1, 0); + $lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator); + $this->assertEquals(0, $lrn); + + $numerator = GaussianDistribution::fromPrecisionMean(1, 1); + $denominator = GaussianDistribution::fromPrecisionMean(1, 0); + $lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator); + $this->assertEquals(0, $lrn); + $numerator = GaussianDistribution::fromPrecisionMean(1, 0); + $denominator = GaussianDistribution::fromPrecisionMean(1, 1); + $lrn = GaussianDistribution::logRatioNormalization($numerator, $denominator); + $this->assertEquals(0, $lrn); } public function testAbsoluteDifference(): void @@ -115,4 +143,27 @@ class GaussianDistributionTest extends TestCase $absDiff2 = GaussianDistribution::absoluteDifference($m1s2, $m3s4); $this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE); } + + public function testSubtract(): void + { + // Verified with Ralf Herbrich's F# implementation + $standardNormal = new GaussianDistribution(0, 1); + $absDiff = GaussianDistribution::subtract($standardNormal, $standardNormal); + $this->assertEqualsWithDelta(0.0, $absDiff, GaussianDistributionTest::ERROR_TOLERANCE); + + $m1s2 = new GaussianDistribution(1, 2); + $m3s4 = new GaussianDistribution(3, 4); + $absDiff2 = GaussianDistribution::subtract($m1s2, $m3s4); + $this->assertEqualsWithDelta(0.4330127018922193, $absDiff2, GaussianDistributionTest::ERROR_TOLERANCE); + } + + public function testfromPrecisionMean(): void + { + $gd = GaussianDistribution::fromPrecisionMean(0, 0); + $this->assertInfinite($gd->getVariance()); + $this->assertInfinite($gd->getStandardDeviation()); + $this->assertNan($gd->getMean()); + $this->assertEquals(0, $gd->getPrecisionMean()); + $this->assertEquals(0, $gd->getPrecision()); + } } diff --git a/tests/Numerics/MatrixTest.php b/tests/Numerics/MatrixTest.php index 346f2be..d6c7541 100644 --- a/tests/Numerics/MatrixTest.php +++ b/tests/Numerics/MatrixTest.php @@ -22,6 +22,83 @@ use Exception; // phpcs:disable PSR2.Methods.FunctionCallSignature,Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma class MatrixTest extends TestCase { + public function testEmptyMatrix(): void + { + $m1 = new Matrix(); + $this->assertEquals(0, $m1->getRowCount()); + $this->assertEquals(0, $m1->getColumnCount()); + + $m2 = new Matrix(0, 0); + $this->assertEquals(0, $m2->getRowCount()); + $this->assertEquals(0, $m2->getColumnCount()); + + $this->assertEquals(new Matrix(), Matrix::multiply($m1, $m2)); + } + + public function testIndexing(): void + { + $m = new Matrix(5, 5); + $m->setValue(0, 0, 1); + $this->assertEquals(1, $m->getValue(0, 0)); + $m->setValue(0, 1, 2); + $this->assertEquals(2, $m->getValue(0, 1)); + $m->setValue(1, 0, 3); + $this->assertEquals(3, $m->getValue(1, 0)); + $m->setValue(1, 1, 4); + $this->assertEquals(4, $m->getValue(1, 1)); + + $m->setValue(3, 3, 11); + $this->assertEquals(11, $m->getValue(3, 3)); + $m->setValue(4, 3, 22); + $this->assertEquals(22, $m->getValue(4, 3)); + $m->setValue(3, 4, 33); + $this->assertEquals(33, $m->getValue(3, 4)); + $m->setValue(4, 4, 44); + $this->assertEquals(44, $m->getValue(4, 4)); + + try { + $m->getValue(-1, -1); + $this->fail("No exception"); + } catch (Exception $exception) { + $this->assertInstanceOf(Exception::class, $exception); + } + + try { + $m->getValue(-1, 0); + $this->fail("No exception"); + } catch (Exception $exception) { + $this->assertInstanceOf(Exception::class, $exception); + } + + try { + $m->getValue(0, -1); + $this->fail("No exception"); + } catch (Exception $exception) { + $this->assertInstanceOf(Exception::class, $exception); + } + + try { + $m->getValue(5, 5); + $this->fail("No exception"); + } catch (Exception $exception) { + $this->assertInstanceOf(Exception::class, $exception); + } + + try { + $m->getValue(5, 4); + $this->fail("No exception"); + } catch (Exception $exception) { + $this->assertInstanceOf(Exception::class, $exception); + } + + try { + $m->getValue(4, 5); + $this->fail("No exception"); + } catch (Exception $exception) { + $this->assertInstanceOf(Exception::class, $exception); + } + } + public function testOneByOneDeterminant(): void { $a = new SquareMatrix(1);