2016-04-29 22:58:54 +00:00
|
|
|
<?php
|
2016-04-29 22:59:10 +00:00
|
|
|
|
2016-11-20 21:53:17 +00:00
|
|
|
declare(strict_types=1);
|
2016-04-29 22:58:54 +00:00
|
|
|
|
|
|
|
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
|
2016-04-29 22:59:10 +00:00
|
|
|
* @param bool $validate
|
2016-04-29 22:58:54 +00:00
|
|
|
*
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function __construct(array $matrix, bool $validate = true)
|
|
|
|
{
|
|
|
|
$this->rows = count($matrix);
|
|
|
|
$this->columns = count($matrix[0]);
|
|
|
|
|
2016-04-29 22:59:10 +00:00
|
|
|
if ($validate) {
|
|
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
2016-04-29 22:58:54 +00:00
|
|
|
if (count($matrix[$i]) !== $this->columns) {
|
|
|
|
throw InvalidArgumentException::matrixDimensionsDidNotMatch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->matrix = $matrix;
|
|
|
|
}
|
|
|
|
|
2016-04-30 11:54:01 +00:00
|
|
|
/**
|
|
|
|
* @param array $array
|
2016-04-30 21:47:35 +00:00
|
|
|
*
|
2016-04-30 11:54:01 +00:00
|
|
|
* @return Matrix
|
|
|
|
*/
|
|
|
|
public static function fromFlatArray(array $array)
|
|
|
|
{
|
|
|
|
$matrix = [];
|
|
|
|
foreach ($array as $value) {
|
|
|
|
$matrix[] = [$value];
|
|
|
|
}
|
|
|
|
|
|
|
|
return new self($matrix);
|
|
|
|
}
|
|
|
|
|
2016-04-29 22:58:54 +00:00
|
|
|
/**
|
|
|
|
* @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)
|
|
|
|
{
|
2016-04-29 22:59:10 +00:00
|
|
|
if ($column >= $this->columns) {
|
2016-04-29 22:58:54 +00:00
|
|
|
throw MatrixException::columnOutOfRange();
|
|
|
|
}
|
|
|
|
|
|
|
|
$values = [];
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
2016-04-29 22:58:54 +00:00
|
|
|
$values[] = $this->matrix[$i][$column];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $values;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return float|int
|
2016-11-20 21:53:17 +00:00
|
|
|
*
|
2016-04-29 22:58:54 +00:00
|
|
|
* @throws MatrixException
|
|
|
|
*/
|
|
|
|
public function getDeterminant()
|
|
|
|
{
|
2016-04-29 22:59:10 +00:00
|
|
|
if ($this->determinant) {
|
2016-04-29 22:58:54 +00:00
|
|
|
return $this->determinant;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$this->isSquare()) {
|
|
|
|
throw MatrixException::notSquareMatrix();
|
|
|
|
}
|
|
|
|
|
2016-04-30 21:54:05 +00:00
|
|
|
return $this->determinant = $this->calculateDeterminant();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return float|int
|
|
|
|
*
|
|
|
|
* @throws MatrixException
|
|
|
|
*/
|
|
|
|
private function calculateDeterminant()
|
|
|
|
{
|
2016-04-29 22:58:54 +00:00
|
|
|
$determinant = 0;
|
|
|
|
if ($this->rows == 1 && $this->columns == 1) {
|
|
|
|
$determinant = $this->matrix[0][0];
|
2016-04-29 22:59:10 +00:00
|
|
|
} elseif ($this->rows == 2 && $this->columns == 2) {
|
2016-04-30 11:54:01 +00:00
|
|
|
$determinant =
|
|
|
|
$this->matrix[0][0] * $this->matrix[1][1] -
|
2016-04-29 22:58:54 +00:00
|
|
|
$this->matrix[0][1] * $this->matrix[1][0];
|
|
|
|
} else {
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($j = 0; $j < $this->columns; ++$j) {
|
2016-04-29 22:58:54 +00:00
|
|
|
$subMatrix = $this->crossOut(0, $j);
|
2016-04-30 11:54:01 +00:00
|
|
|
$minor = $this->matrix[0][$j] * $subMatrix->getDeterminant();
|
2016-05-02 21:36:58 +00:00
|
|
|
$determinant += fmod((float) $j, 2.0) == 0 ? $minor : -$minor;
|
2016-04-29 22:58:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-30 21:54:05 +00:00
|
|
|
return $determinant;
|
2016-04-29 22:58:54 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 22:59:10 +00:00
|
|
|
/**
|
2016-04-29 22:58:54 +00:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isSquare()
|
|
|
|
{
|
|
|
|
return $this->columns === $this->rows;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Matrix
|
|
|
|
*/
|
|
|
|
public function transpose()
|
|
|
|
{
|
|
|
|
$newMatrix = [];
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
|
|
|
for ($j = 0; $j < $this->columns; ++$j) {
|
2016-04-29 22:58:54 +00:00
|
|
|
$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();
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
2016-08-02 11:23:58 +00:00
|
|
|
$columns = $matrix->getColumns();
|
|
|
|
for ($j = 0; $j < $columns; ++$j) {
|
2016-04-29 22:58:54 +00:00
|
|
|
$product[$i][$j] = 0;
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($k = 0; $k < $this->columns; ++$k) {
|
2016-04-29 22:58:54 +00:00
|
|
|
$product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-29 22:59:10 +00:00
|
|
|
|
2016-04-29 22:58:54 +00:00
|
|
|
return new self($product, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $value
|
|
|
|
*
|
|
|
|
* @return Matrix
|
|
|
|
*/
|
|
|
|
public function divideByScalar($value)
|
|
|
|
{
|
2017-01-31 19:33:08 +00:00
|
|
|
$newMatrix = [];
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
|
|
|
for ($j = 0; $j < $this->columns; ++$j) {
|
2016-04-29 22:58:54 +00:00
|
|
|
$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();
|
|
|
|
}
|
|
|
|
|
2017-02-15 09:09:16 +00:00
|
|
|
if ($this->isSingular()) {
|
|
|
|
throw MatrixException::singularMatrix();
|
|
|
|
}
|
|
|
|
|
2017-01-31 19:33:08 +00:00
|
|
|
$newMatrix = [];
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
|
|
|
for ($j = 0; $j < $this->columns; ++$j) {
|
2016-04-30 21:47:35 +00:00
|
|
|
$minor = $this->crossOut($i, $j)->getDeterminant();
|
2016-05-02 21:36:58 +00:00
|
|
|
$newMatrix[$i][$j] = fmod((float) ($i + $j), 2.0) == 0 ? $minor : -$minor;
|
2016-04-29 22:58:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$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;
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($i = 0; $i < $this->rows; ++$i) {
|
2016-04-29 22:58:54 +00:00
|
|
|
$c = 0;
|
|
|
|
if ($row != $i) {
|
2016-04-29 22:59:10 +00:00
|
|
|
for ($j = 0; $j < $this->columns; ++$j) {
|
2016-04-29 22:58:54 +00:00
|
|
|
if ($column != $j) {
|
|
|
|
$newMatrix[$r][$c] = $this->matrix[$i][$j];
|
2016-04-29 22:59:10 +00:00
|
|
|
++$c;
|
2016-04-29 22:58:54 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-29 22:59:10 +00:00
|
|
|
++$r;
|
2016-04-29 22:58:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new self($newMatrix, false);
|
|
|
|
}
|
2017-02-15 09:09:16 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isSingular() : bool
|
|
|
|
{
|
|
|
|
return 0 == $this->getDeterminant();
|
|
|
|
}
|
2016-04-29 22:58:54 +00:00
|
|
|
}
|