|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- <?php
-
- /**
- *
- * Class for the management of Matrices
- *
- * @copyright Copyright (c) 2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix)
- * @license https://opensource.org/licenses/MIT MIT
- */
-
- namespace Matrix;
-
- /**
- * Matrix object.
- *
- * @package Matrix
- *
- * @property-read int $rows The number of rows in the matrix
- * @property-read int $columns The number of columns in the matrix
- * @method Matrix antidiagonal()
- * @method Matrix adjoint()
- * @method Matrix cofactors()
- * @method float determinant()
- * @method Matrix diagonal()
- * @method Matrix identity()
- * @method Matrix inverse()
- * @method Matrix pseudoInverse()
- * @method Matrix minors()
- * @method float trace()
- * @method Matrix transpose()
- * @method Matrix add(...$matrices)
- * @method Matrix subtract(...$matrices)
- * @method Matrix multiply(...$matrices)
- * @method Matrix divideby(...$matrices)
- * @method Matrix divideinto(...$matrices)
- */
- class Matrix
- {
- protected $rows;
- protected $columns;
- protected $grid = [];
-
- /*
- * Create a new Matrix object from an array of values
- *
- * @param array $grid
- */
- final public function __construct(array $grid)
- {
- $this->buildFromArray(array_values($grid));
- }
-
- /*
- * Create a new Matrix object from an array of values
- *
- * @param array $grid
- */
- protected function buildFromArray(array $grid)
- {
- $this->rows = count($grid);
- $columns = array_reduce(
- $grid,
- function ($carry, $value) {
- return max($carry, is_array($value) ? count($value) : 1);
- }
- );
- $this->columns = $columns;
-
- array_walk(
- $grid,
- function (&$value) use ($columns) {
- if (!is_array($value)) {
- $value = [$value];
- }
- $value = array_pad(array_values($value), $columns, null);
- }
- );
-
- $this->grid = $grid;
- }
-
- /**
- * Validate that a row number is a positive integer
- *
- * @param int $row
- * @return int
- * @throws Exception
- */
- public static function validateRow($row)
- {
- if ((!is_numeric($row)) || (intval($row) < 1)) {
- throw new Exception('Invalid Row');
- }
-
- return (int)$row;
- }
-
- /**
- * Validate that a column number is a positive integer
- *
- * @param int $column
- * @return int
- * @throws Exception
- */
- public static function validateColumn($column)
- {
- if ((!is_numeric($column)) || (intval($column) < 1)) {
- throw new Exception('Invalid Column');
- }
-
- return (int)$column;
- }
-
- /**
- * Validate that a row number falls within the set of rows for this matrix
- *
- * @param int $row
- * @return int
- * @throws Exception
- */
- protected function validateRowInRange($row)
- {
- $row = static::validateRow($row);
- if ($row > $this->rows) {
- throw new Exception('Requested Row exceeds matrix size');
- }
-
- return $row;
- }
-
- /**
- * Validate that a column number falls within the set of columns for this matrix
- *
- * @param int $column
- * @return int
- * @throws Exception
- */
- protected function validateColumnInRange($column)
- {
- $column = static::validateColumn($column);
- if ($column > $this->columns) {
- throw new Exception('Requested Column exceeds matrix size');
- }
-
- return $column;
- }
-
- /**
- * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows
- * A $rowCount value of 0 will return all rows of the matrix from $row
- * A negative $rowCount value will return rows until that many rows from the end of the matrix
- *
- * Note that row numbers start from 1, not from 0
- *
- * @param int $row
- * @param int $rowCount
- * @return static
- * @throws Exception
- */
- public function getRows($row, $rowCount = 1)
- {
- $row = $this->validateRowInRange($row);
- if ($rowCount === 0) {
- $rowCount = $this->rows - $row + 1;
- }
-
- return new static(array_slice($this->grid, $row - 1, (int)$rowCount));
- }
-
- /**
- * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns
- * A $columnCount value of 0 will return all columns of the matrix from $column
- * A negative $columnCount value will return columns until that many columns from the end of the matrix
- *
- * Note that column numbers start from 1, not from 0
- *
- * @param int $column
- * @param int $columnCount
- * @return Matrix
- * @throws Exception
- */
- public function getColumns($column, $columnCount = 1)
- {
- $column = $this->validateColumnInRange($column);
- if ($columnCount < 1) {
- $columnCount = $this->columns + $columnCount - $column + 1;
- }
-
- $grid = [];
- for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) {
- $grid[] = array_column($this->grid, $i);
- }
-
- return (new static($grid))->transpose();
- }
-
- /**
- * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row,
- * and $rowCount rows
- * A negative $rowCount value will drop rows until that many rows from the end of the matrix
- * A $rowCount value of 0 will remove all rows of the matrix from $row
- *
- * Note that row numbers start from 1, not from 0
- *
- * @param int $row
- * @param int $rowCount
- * @return static
- * @throws Exception
- */
- public function dropRows($row, $rowCount = 1)
- {
- $this->validateRowInRange($row);
- if ($rowCount === 0) {
- $rowCount = $this->rows - $row + 1;
- }
-
- $grid = $this->grid;
- array_splice($grid, $row - 1, (int)$rowCount);
-
- return new static($grid);
- }
-
- /**
- * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column,
- * and $columnCount columns
- * A negative $columnCount value will drop columns until that many columns from the end of the matrix
- * A $columnCount value of 0 will remove all columns of the matrix from $column
- *
- * Note that column numbers start from 1, not from 0
- *
- * @param int $column
- * @param int $columnCount
- * @return static
- * @throws Exception
- */
- public function dropColumns($column, $columnCount = 1)
- {
- $this->validateColumnInRange($column);
- if ($columnCount < 1) {
- $columnCount = $this->columns + $columnCount - $column + 1;
- }
-
- $grid = $this->grid;
- array_walk(
- $grid,
- function (&$row) use ($column, $columnCount) {
- array_splice($row, $column - 1, (int)$columnCount);
- }
- );
-
- return new static($grid);
- }
-
- /**
- * Return a value from this matrix, from the "cell" identified by the row and column numbers
- * Note that row and column numbers start from 1, not from 0
- *
- * @param int $row
- * @param int $column
- * @return mixed
- * @throws Exception
- */
- public function getValue($row, $column)
- {
- $row = $this->validateRowInRange($row);
- $column = $this->validateColumnInRange($column);
-
- return $this->grid[$row - 1][$column - 1];
- }
-
- /**
- * Returns a Generator that will yield each row of the matrix in turn as a vector matrix
- * or the value of each cell if the matrix is a vector
- *
- * @return \Generator|Matrix[]|mixed[]
- */
- public function rows()
- {
- foreach ($this->grid as $i => $row) {
- yield $i + 1 => ($this->columns == 1)
- ? $row[0]
- : new static([$row]);
- }
- }
-
- /**
- * Returns a Generator that will yield each column of the matrix in turn as a vector matrix
- * or the value of each cell if the matrix is a vector
- *
- * @return \Generator|Matrix[]|mixed[]
- */
- public function columns()
- {
- for ($i = 0; $i < $this->columns; ++$i) {
- yield $i + 1 => ($this->rows == 1)
- ? $this->grid[0][$i]
- : new static(array_column($this->grid, $i));
- }
- }
-
- /**
- * Identify if the row and column dimensions of this matrix are equal,
- * i.e. if it is a "square" matrix
- *
- * @return bool
- */
- public function isSquare()
- {
- return $this->rows == $this->columns;
- }
-
- /**
- * Identify if this matrix is a vector
- * i.e. if it comprises only a single row or a single column
- *
- * @return bool
- */
- public function isVector()
- {
- return $this->rows == 1 || $this->columns == 1;
- }
-
- /**
- * Return the matrix as a 2-dimensional array
- *
- * @return array
- */
- public function toArray()
- {
- return $this->grid;
- }
-
- protected static $getters = [
- 'rows',
- 'columns',
- ];
-
- /**
- * Access specific properties as read-only (no setters)
- *
- * @param string $propertyName
- * @return mixed
- * @throws Exception
- */
- public function __get($propertyName)
- {
- $propertyName = strtolower($propertyName);
-
- // Test for function calls
- if (in_array($propertyName, self::$getters)) {
- return $this->$propertyName;
- }
-
- throw new Exception('Property does not exist');
- }
-
- protected static $functions = [
- 'antidiagonal',
- 'adjoint',
- 'cofactors',
- 'determinant',
- 'diagonal',
- 'identity',
- 'inverse',
- 'minors',
- 'trace',
- 'transpose',
- ];
-
- protected static $operations = [
- 'add',
- 'subtract',
- 'multiply',
- 'divideby',
- 'divideinto',
- 'directsum',
- ];
-
- /**
- * Returns the result of the function call or operation
- *
- * @param string $functionName
- * @param mixed[] $arguments
- * @return Matrix|float
- * @throws Exception
- */
- public function __call($functionName, $arguments)
- {
- $functionName = strtolower(str_replace('_', '', $functionName));
-
- if (in_array($functionName, self::$functions) || in_array($functionName, self::$operations)) {
- $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}";
- if (is_callable($functionName)) {
- $arguments = array_values(array_merge([$this], $arguments));
- return call_user_func_array($functionName, $arguments);
- }
- }
- throw new Exception('Function or Operation does not exist');
- }
- }
|