Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
92.86% |
13 / 14 |
CRAP | |
98.72% |
77 / 78 |
Matrix | |
0.00% |
0 / 1 |
|
92.86% |
13 / 14 |
44 | |
98.72% |
77 / 78 |
__construct | |
100.00% |
1 / 1 |
4 | |
100.00% |
8 / 8 |
|||
fromFlatArray | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
toArray | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getRows | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getColumns | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getColumnValues | |
100.00% |
1 / 1 |
3 | |
100.00% |
6 / 6 |
|||
getDeterminant | |
0.00% |
0 / 1 |
3.07 | |
80.00% |
4 / 5 |
|||
calculateDeterminant | |
100.00% |
1 / 1 |
7 | |
100.00% |
11 / 11 |
|||
isSquare | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
transpose | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
multiply | |
100.00% |
1 / 1 |
5 | |
100.00% |
10 / 10 |
|||
divideByScalar | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
inverse | |
100.00% |
1 / 1 |
5 | |
100.00% |
9 / 9 |
|||
crossOut | |
100.00% |
1 / 1 |
5 | |
100.00% |
11 / 11 |
<?php | |
declare (strict_types = 1); | |
namespace Phpml\Math; | |
use Phpml\Exception\InvalidArgumentException; | |
use Phpml\Exception\MatrixException; | |
class Matrix | |
{ | |
/** | |
* @var array | |
*/ | |
private $matrix; | |
/** | |
* @var int | |
*/ | |
private $rows; | |
/** | |
* @var int | |
*/ | |
private $columns; | |
/** | |
* @var float | |
*/ | |
private $determinant; | |
/** | |
* @param array $matrix | |
* @param bool $validate | |
* | |
* @throws InvalidArgumentException | |
*/ | |
public function __construct(array $matrix, bool $validate = true) | |
{ | |
$this->rows = count($matrix); | |
$this->columns = count($matrix[0]); | |
if ($validate) { | |
for ($i = 0; $i < $this->rows; ++$i) { | |
if (count($matrix[$i]) !== $this->columns) { | |
throw InvalidArgumentException::matrixDimensionsDidNotMatch(); | |
} | |
} | |
} | |
$this->matrix = $matrix; | |
} | |
/** | |
* @param array $array | |
* | |
* @return Matrix | |
*/ | |
public static function fromFlatArray(array $array) | |
{ | |
$matrix = []; | |
foreach ($array as $value) { | |
$matrix[] = [$value]; | |
} | |
return new self($matrix); | |
} | |
/** | |
* @return array | |
*/ | |
public function toArray() | |
{ | |
return $this->matrix; | |
} | |
/** | |
* @return int | |
*/ | |
public function getRows() | |
{ | |
return $this->rows; | |
} | |
/** | |
* @return int | |
*/ | |
public function getColumns() | |
{ | |
return $this->columns; | |
} | |
/** | |
* @param $column | |
* | |
* @return array | |
* | |
* @throws MatrixException | |
*/ | |
public function getColumnValues($column) | |
{ | |
if ($column >= $this->columns) { | |
throw MatrixException::columnOutOfRange(); | |
} | |
$values = []; | |
for ($i = 0; $i < $this->rows; ++$i) { | |
$values[] = $this->matrix[$i][$column]; | |
} | |
return $values; | |
} | |
/** | |
* @return float|int | |
* | |
* @throws MatrixException | |
*/ | |
public function getDeterminant() | |
{ | |
if ($this->determinant) { | |
return $this->determinant; | |
} | |
if (!$this->isSquare()) { | |
throw MatrixException::notSquareMatrix(); | |
} | |
return $this->determinant = $this->calculateDeterminant(); | |
} | |
/** | |
* @return float|int | |
* | |
* @throws MatrixException | |
*/ | |
private function calculateDeterminant() | |
{ | |
$determinant = 0; | |
if ($this->rows == 1 && $this->columns == 1) { | |
$determinant = $this->matrix[0][0]; | |
} elseif ($this->rows == 2 && $this->columns == 2) { | |
$determinant = | |
$this->matrix[0][0] * $this->matrix[1][1] - | |
$this->matrix[0][1] * $this->matrix[1][0]; | |
} else { | |
for ($j = 0; $j < $this->columns; ++$j) { | |
$subMatrix = $this->crossOut(0, $j); | |
$minor = $this->matrix[0][$j] * $subMatrix->getDeterminant(); | |
$determinant += fmod((float) $j, 2.0) == 0 ? $minor : -$minor; | |
} | |
} | |
return $determinant; | |
} | |
/** | |
* @return bool | |
*/ | |
public function isSquare() | |
{ | |
return $this->columns === $this->rows; | |
} | |
/** | |
* @return Matrix | |
*/ | |
public function transpose() | |
{ | |
$newMatrix = []; | |
for ($i = 0; $i < $this->rows; ++$i) { | |
for ($j = 0; $j < $this->columns; ++$j) { | |
$newMatrix[$j][$i] = $this->matrix[$i][$j]; | |
} | |
} | |
return new self($newMatrix, false); | |
} | |
/** | |
* @param Matrix $matrix | |
* | |
* @return Matrix | |
* | |
* @throws InvalidArgumentException | |
*/ | |
public function multiply(Matrix $matrix) | |
{ | |
if ($this->columns != $matrix->getRows()) { | |
throw InvalidArgumentException::inconsistentMatrixSupplied(); | |
} | |
$product = []; | |
$multiplier = $matrix->toArray(); | |
for ($i = 0; $i < $this->rows; ++$i) { | |
for ($j = 0; $j < $matrix->getColumns(); ++$j) { | |
$product[$i][$j] = 0; | |
for ($k = 0; $k < $this->columns; ++$k) { | |
$product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j]; | |
} | |
} | |
} | |
return new self($product, false); | |
} | |
/** | |
* @param $value | |
* | |
* @return Matrix | |
*/ | |
public function divideByScalar($value) | |
{ | |
$newMatrix = array(); | |
for ($i = 0; $i < $this->rows; ++$i) { | |
for ($j = 0; $j < $this->columns; ++$j) { | |
$newMatrix[$i][$j] = $this->matrix[$i][$j] / $value; | |
} | |
} | |
return new self($newMatrix, false); | |
} | |
/** | |
* @return Matrix | |
* | |
* @throws MatrixException | |
*/ | |
public function inverse() | |
{ | |
if (!$this->isSquare()) { | |
throw MatrixException::notSquareMatrix(); | |
} | |
$newMatrix = array(); | |
for ($i = 0; $i < $this->rows; ++$i) { | |
for ($j = 0; $j < $this->columns; ++$j) { | |
$minor = $this->crossOut($i, $j)->getDeterminant(); | |
$newMatrix[$i][$j] = fmod((float) ($i + $j), 2.0) == 0 ? $minor : -$minor; | |
} | |
} | |
$cofactorMatrix = new self($newMatrix, false); | |
return $cofactorMatrix->transpose()->divideByScalar($this->getDeterminant()); | |
} | |
/** | |
* @param int $row | |
* @param int $column | |
* | |
* @return Matrix | |
*/ | |
public function crossOut(int $row, int $column) | |
{ | |
$newMatrix = []; | |
$r = 0; | |
for ($i = 0; $i < $this->rows; ++$i) { | |
$c = 0; | |
if ($row != $i) { | |
for ($j = 0; $j < $this->columns; ++$j) { | |
if ($column != $j) { | |
$newMatrix[$r][$c] = $this->matrix[$i][$j]; | |
++$c; | |
} | |
} | |
++$r; | |
} | |
} | |
return new self($newMatrix, false); | |
} | |
} |