Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
92.86% covered (success)
92.86%
13 / 14
CRAP
98.72% covered (success)
98.72%
77 / 78
Matrix
0.00% covered (danger)
0.00%
0 / 1
92.86% covered (success)
92.86%
13 / 14
44
98.72% covered (success)
98.72%
77 / 78
 __construct
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
8 / 8
 fromFlatArray
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 toArray
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getRows
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getColumns
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getColumnValues
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 getDeterminant
0.00% covered (danger)
0.00%
0 / 1
3.07
80.00% covered (warning)
80.00%
4 / 5
 calculateDeterminant
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
11 / 11
 isSquare
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 transpose
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 multiply
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
10 / 10
 divideByScalar
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 inverse
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
9 / 9
 crossOut
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
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);
    }
}