2016-04-30 21:21:32 +00:00
|
|
|
<?php
|
|
|
|
|
2016-11-20 21:53:17 +00:00
|
|
|
declare(strict_types=1);
|
2016-04-30 21:21:32 +00:00
|
|
|
|
2018-01-06 12:09:33 +00:00
|
|
|
namespace Phpml\Tests\Math;
|
2016-04-30 21:21:32 +00:00
|
|
|
|
2017-11-28 07:00:13 +00:00
|
|
|
use Phpml\Exception\InvalidArgumentException;
|
|
|
|
use Phpml\Exception\MatrixException;
|
2016-04-30 21:21:32 +00:00
|
|
|
use Phpml\Math\Matrix;
|
2017-02-03 11:58:25 +00:00
|
|
|
use PHPUnit\Framework\TestCase;
|
2016-04-30 21:21:32 +00:00
|
|
|
|
2017-02-03 11:58:25 +00:00
|
|
|
class MatrixTest extends TestCase
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testThrowExceptionOnInvalidMatrixSupplied(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
2017-11-28 07:00:13 +00:00
|
|
|
$this->expectException(InvalidArgumentException::class);
|
2016-04-30 21:21:32 +00:00
|
|
|
new Matrix([[1, 2], [3]]);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testCreateMatrixFromFlatArray(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
|
|
|
$flatArray = [1, 2, 3, 4];
|
|
|
|
$matrix = Matrix::fromFlatArray($flatArray);
|
|
|
|
|
|
|
|
$this->assertInstanceOf(Matrix::class, $matrix);
|
|
|
|
$this->assertEquals([[1], [2], [3], [4]], $matrix->toArray());
|
|
|
|
$this->assertEquals(4, $matrix->getRows());
|
|
|
|
$this->assertEquals(1, $matrix->getColumns());
|
|
|
|
$this->assertEquals($flatArray, $matrix->getColumnValues(0));
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testThrowExceptionOnInvalidColumnNumber(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
2017-11-28 07:00:13 +00:00
|
|
|
$this->expectException(MatrixException::class);
|
2016-04-30 21:21:32 +00:00
|
|
|
$matrix = new Matrix([[1, 2, 3], [4, 5, 6]]);
|
|
|
|
$matrix->getColumnValues(4);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testThrowExceptionOnGetDeterminantIfArrayIsNotSquare(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
2017-11-28 07:00:13 +00:00
|
|
|
$this->expectException(MatrixException::class);
|
2016-04-30 21:21:32 +00:00
|
|
|
$matrix = new Matrix([[1, 2, 3], [4, 5, 6]]);
|
|
|
|
$matrix->getDeterminant();
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testGetMatrixDeterminant(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
|
|
|
//http://matrix.reshish.com/determinant.php
|
|
|
|
$matrix = new Matrix([
|
|
|
|
[3, 3, 3],
|
|
|
|
[4, 2, 1],
|
|
|
|
[5, 6, 7],
|
|
|
|
]);
|
|
|
|
$this->assertEquals(-3, $matrix->getDeterminant());
|
|
|
|
|
|
|
|
$matrix = new Matrix([
|
|
|
|
[1, 2, 3, 3, 2, 1],
|
|
|
|
[1 / 2, 5, 6, 7, 1, 1],
|
|
|
|
[3 / 2, 7 / 2, 2, 0, 6, 8],
|
|
|
|
[1, 8, 10, 1, 2, 2],
|
|
|
|
[1 / 4, 4, 1, 0, 2, 3 / 7],
|
|
|
|
[1, 8, 7, 5, 4, 4 / 5],
|
|
|
|
]);
|
|
|
|
$this->assertEquals(1116.5035, $matrix->getDeterminant(), '', $delta = 0.0001);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testMatrixTranspose(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
|
|
|
$matrix = new Matrix([
|
|
|
|
[3, 3, 3],
|
|
|
|
[4, 2, 1],
|
|
|
|
[5, 6, 7],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$transposedMatrix = [
|
|
|
|
[3, 4, 5],
|
|
|
|
[3, 2, 6],
|
|
|
|
[3, 1, 7],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($transposedMatrix, $matrix->transpose()->toArray());
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testThrowExceptionOnMultiplyWhenInconsistentMatrixSupplied(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
2017-11-28 07:00:13 +00:00
|
|
|
$this->expectException(InvalidArgumentException::class);
|
2016-04-30 21:21:32 +00:00
|
|
|
$matrix1 = new Matrix([[1, 2, 3], [4, 5, 6]]);
|
|
|
|
$matrix2 = new Matrix([[3, 2, 1], [6, 5, 4]]);
|
|
|
|
$matrix1->multiply($matrix2);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testMatrixMultiplyByMatrix(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
|
|
|
$matrix1 = new Matrix([
|
|
|
|
[1, 2, 3],
|
|
|
|
[4, 5, 6],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$matrix2 = new Matrix([
|
|
|
|
[7, 8],
|
|
|
|
[9, 10],
|
|
|
|
[11, 12],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$product = [
|
|
|
|
[58, 64],
|
|
|
|
[139, 154],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($product, $matrix1->multiply($matrix2)->toArray());
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testDivideByScalar(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
|
|
|
$matrix = new Matrix([
|
|
|
|
[4, 6, 8],
|
|
|
|
[2, 10, 20],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$quotient = [
|
|
|
|
[2, 3, 4],
|
|
|
|
[1, 5, 10],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($quotient, $matrix->divideByScalar(2)->toArray());
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testThrowExceptionWhenInverseIfArrayIsNotSquare(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
2017-11-28 07:00:13 +00:00
|
|
|
$this->expectException(MatrixException::class);
|
2016-04-30 21:21:32 +00:00
|
|
|
$matrix = new Matrix([[1, 2, 3], [4, 5, 6]]);
|
|
|
|
$matrix->inverse();
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testThrowExceptionWhenInverseIfMatrixIsSingular(): void
|
2017-02-15 09:09:16 +00:00
|
|
|
{
|
2017-11-28 07:00:13 +00:00
|
|
|
$this->expectException(MatrixException::class);
|
2017-02-23 19:59:30 +00:00
|
|
|
$matrix = new Matrix([
|
2017-02-15 09:09:16 +00:00
|
|
|
[0, 0, 0],
|
|
|
|
[0, 0, 0],
|
|
|
|
[0, 0, 0],
|
|
|
|
]);
|
2017-02-23 19:59:30 +00:00
|
|
|
$matrix->inverse();
|
2017-02-15 09:09:16 +00:00
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testInverseMatrix(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
|
|
|
//http://ncalculators.com/matrix/inverse-matrix.htm
|
|
|
|
$matrix = new Matrix([
|
|
|
|
[3, 4, 2],
|
|
|
|
[4, 5, 5],
|
|
|
|
[1, 1, 1],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$inverseMatrix = [
|
|
|
|
[0, -1, 5],
|
|
|
|
[1 / 2, 1 / 2, -7 / 2],
|
|
|
|
[-1 / 2, 1 / 2, -1 / 2],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($inverseMatrix, $matrix->inverse()->toArray(), '', $delta = 0.0001);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testCrossOutMatrix(): void
|
2016-04-30 21:21:32 +00:00
|
|
|
{
|
|
|
|
$matrix = new Matrix([
|
|
|
|
[3, 4, 2],
|
|
|
|
[4, 5, 5],
|
|
|
|
[1, 1, 1],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$crossOuted = [
|
|
|
|
[3, 2],
|
|
|
|
[1, 1],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($crossOuted, $matrix->crossOut(1, 1)->toArray());
|
|
|
|
}
|
2017-04-23 07:03:30 +00:00
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testToScalar(): void
|
2017-04-23 07:03:30 +00:00
|
|
|
{
|
|
|
|
$matrix = new Matrix([[1, 2, 3], [3, 2, 3]]);
|
|
|
|
|
|
|
|
$this->assertEquals($matrix->toScalar(), 1);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testMultiplyByScalar(): void
|
2017-04-23 07:03:30 +00:00
|
|
|
{
|
|
|
|
$matrix = new Matrix([
|
|
|
|
[4, 6, 8],
|
|
|
|
[2, 10, 20],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$result = [
|
|
|
|
[-8, -12, -16],
|
|
|
|
[-4, -20, -40],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($result, $matrix->multiplyByScalar(-2)->toArray());
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testAdd(): void
|
2017-04-23 07:03:30 +00:00
|
|
|
{
|
|
|
|
$array1 = [1, 1, 1];
|
|
|
|
$array2 = [2, 2, 2];
|
|
|
|
$result = [3, 3, 3];
|
|
|
|
|
|
|
|
$m1 = new Matrix($array1);
|
|
|
|
$m2 = new Matrix($array2);
|
|
|
|
|
|
|
|
$this->assertEquals($result, $m1->add($m2)->toArray()[0]);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testSubtract(): void
|
2017-04-23 07:03:30 +00:00
|
|
|
{
|
|
|
|
$array1 = [1, 1, 1];
|
|
|
|
$array2 = [2, 2, 2];
|
|
|
|
$result = [-1, -1, -1];
|
|
|
|
|
|
|
|
$m1 = new Matrix($array1);
|
|
|
|
$m2 = new Matrix($array2);
|
|
|
|
|
|
|
|
$this->assertEquals($result, $m1->subtract($m2)->toArray()[0]);
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testTransposeArray(): void
|
2017-04-23 07:03:30 +00:00
|
|
|
{
|
|
|
|
$array = [
|
|
|
|
[1, 1, 1],
|
2017-11-22 21:16:10 +00:00
|
|
|
[2, 2, 2],
|
2017-04-23 07:03:30 +00:00
|
|
|
];
|
|
|
|
$transposed = [
|
|
|
|
[1, 2],
|
|
|
|
[1, 2],
|
2017-11-22 21:16:10 +00:00
|
|
|
[1, 2],
|
2017-04-23 07:03:30 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($transposed, Matrix::transposeArray($array));
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:21:23 +00:00
|
|
|
public function testDot(): void
|
2017-04-23 07:03:30 +00:00
|
|
|
{
|
|
|
|
$vect1 = [2, 2, 2];
|
|
|
|
$vect2 = [3, 3, 3];
|
|
|
|
$dot = [18];
|
|
|
|
|
|
|
|
$this->assertEquals($dot, Matrix::dot($vect1, $vect2));
|
|
|
|
|
|
|
|
$matrix1 = [[1, 1], [2, 2]];
|
|
|
|
$matrix2 = [[3, 3], [3, 3], [3, 3]];
|
|
|
|
$dot = [6, 12];
|
|
|
|
$this->assertEquals($dot, Matrix::dot($matrix2, $matrix1));
|
|
|
|
}
|
2018-02-11 23:26:34 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider dataProviderForFrobeniusNorm
|
|
|
|
*/
|
|
|
|
public function testFrobeniusNorm(array $matrix, float $norm): void
|
|
|
|
{
|
|
|
|
$matrix = new Matrix($matrix);
|
|
|
|
|
|
|
|
$this->assertEquals($norm, $matrix->frobeniusNorm(), '', 0.0001);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function dataProviderForFrobeniusNorm()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[1, -7],
|
|
|
|
[2, 3],
|
|
|
|
], 7.93725,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[1, 2, 3],
|
|
|
|
[2, 3, 4],
|
|
|
|
[3, 4, 5],
|
|
|
|
], 9.643651,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[1, 5, 3, 9],
|
|
|
|
[2, 3, 4, 12],
|
|
|
|
[4, 2, 5, 11],
|
|
|
|
], 21.330729,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[1, 5, 3],
|
|
|
|
[2, 3, 4],
|
|
|
|
[4, 2, 5],
|
|
|
|
[6, 6, 3],
|
|
|
|
], 13.784049,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[5, -4, 2],
|
|
|
|
[-1, 2, 3],
|
|
|
|
[-2, 1, 0],
|
|
|
|
], 8,
|
|
|
|
],
|
|
|
|
];
|
|
|
|
}
|
2016-04-30 21:21:32 +00:00
|
|
|
}
|