commitall
This commit is contained in:
70
vendor/markbaker/matrix/classes/src/Builder.php
vendored
Normal file
70
vendor/markbaker/matrix/classes/src/Builder.php
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* Class for the creating "special" Matrices
|
||||
*
|
||||
* @copyright Copyright (c) 2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix)
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
|
||||
namespace Matrix;
|
||||
|
||||
/**
|
||||
* Matrix Builder class.
|
||||
*
|
||||
* @package Matrix
|
||||
*/
|
||||
class Builder
|
||||
{
|
||||
/**
|
||||
* Create a new matrix of specified dimensions, and filled with a specified value
|
||||
* If the column argument isn't provided, then a square matrix will be created
|
||||
*
|
||||
* @param mixed $fillValue
|
||||
* @param int $rows
|
||||
* @param int|null $columns
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function createFilledMatrix($fillValue, $rows, $columns = null)
|
||||
{
|
||||
if ($columns === null) {
|
||||
$columns = $rows;
|
||||
}
|
||||
|
||||
$rows = Matrix::validateRow($rows);
|
||||
$columns = Matrix::validateColumn($columns);
|
||||
|
||||
return new Matrix(
|
||||
array_fill(
|
||||
0,
|
||||
$rows,
|
||||
array_fill(
|
||||
0,
|
||||
$columns,
|
||||
$fillValue
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new identity matrix of specified dimensions
|
||||
* This will always be a square matrix, with the number of rows and columns matching the provided dimension
|
||||
*
|
||||
* @param int $dimensions
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function createIdentityMatrix($dimensions, $fillValue = null)
|
||||
{
|
||||
$grid = static::createFilledMatrix($fillValue, $dimensions)->toArray();
|
||||
|
||||
for ($x = 0; $x < $dimensions; ++$x) {
|
||||
$grid[$x][$x] = 1;
|
||||
}
|
||||
|
||||
return new Matrix($grid);
|
||||
}
|
||||
}
|
27
vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php
vendored
Normal file
27
vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Decomposition;
|
||||
|
||||
use Matrix\Exception;
|
||||
use Matrix\Matrix;
|
||||
|
||||
class Decomposition
|
||||
{
|
||||
const LU = 'LU';
|
||||
const QR = 'QR';
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function decomposition($type, Matrix $matrix)
|
||||
{
|
||||
switch (strtoupper($type)) {
|
||||
case self::LU:
|
||||
return new LU($matrix);
|
||||
case self::QR:
|
||||
return new QR($matrix);
|
||||
default:
|
||||
throw new Exception('Invalid Decomposition');
|
||||
}
|
||||
}
|
||||
}
|
260
vendor/markbaker/matrix/classes/src/Decomposition/LU.php
vendored
Normal file
260
vendor/markbaker/matrix/classes/src/Decomposition/LU.php
vendored
Normal file
@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Decomposition;
|
||||
|
||||
use Matrix\Exception;
|
||||
use Matrix\Matrix;
|
||||
|
||||
class LU
|
||||
{
|
||||
private $luMatrix;
|
||||
private $rows;
|
||||
private $columns;
|
||||
|
||||
private $pivot = [];
|
||||
|
||||
public function __construct(Matrix $matrix)
|
||||
{
|
||||
$this->luMatrix = $matrix->toArray();
|
||||
$this->rows = $matrix->rows;
|
||||
$this->columns = $matrix->columns;
|
||||
|
||||
$this->buildPivot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lower triangular factor.
|
||||
*
|
||||
* @return Matrix Lower triangular factor
|
||||
*/
|
||||
public function getL(): Matrix
|
||||
{
|
||||
$lower = [];
|
||||
|
||||
$columns = min($this->rows, $this->columns);
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
for ($column = 0; $column < $columns; ++$column) {
|
||||
if ($row > $column) {
|
||||
$lower[$row][$column] = $this->luMatrix[$row][$column];
|
||||
} elseif ($row === $column) {
|
||||
$lower[$row][$column] = 1.0;
|
||||
} else {
|
||||
$lower[$row][$column] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix($lower);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upper triangular factor.
|
||||
*
|
||||
* @return Matrix Upper triangular factor
|
||||
*/
|
||||
public function getU(): Matrix
|
||||
{
|
||||
$upper = [];
|
||||
|
||||
$rows = min($this->rows, $this->columns);
|
||||
for ($row = 0; $row < $rows; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
if ($row <= $column) {
|
||||
$upper[$row][$column] = $this->luMatrix[$row][$column];
|
||||
} else {
|
||||
$upper[$row][$column] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix($upper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pivot permutation vector.
|
||||
*
|
||||
* @return Matrix Pivot matrix
|
||||
*/
|
||||
public function getP(): Matrix
|
||||
{
|
||||
$pMatrix = [];
|
||||
|
||||
$pivots = $this->pivot;
|
||||
$pivotCount = count($pivots);
|
||||
foreach ($pivots as $row => $pivot) {
|
||||
$pMatrix[$row] = array_fill(0, $pivotCount, 0);
|
||||
$pMatrix[$row][$pivot] = 1;
|
||||
}
|
||||
|
||||
return new Matrix($pMatrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pivot permutation vector.
|
||||
*
|
||||
* @return array Pivot vector
|
||||
*/
|
||||
public function getPivot(): array
|
||||
{
|
||||
return $this->pivot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the matrix nonsingular?
|
||||
*
|
||||
* @return bool true if U, and hence A, is nonsingular
|
||||
*/
|
||||
public function isNonsingular(): bool
|
||||
{
|
||||
for ($diagonal = 0; $diagonal < $this->columns; ++$diagonal) {
|
||||
if ($this->luMatrix[$diagonal][$diagonal] === 0.0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function buildPivot(): void
|
||||
{
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
$this->pivot[$row] = $row;
|
||||
}
|
||||
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
$luColumn = $this->localisedReferenceColumn($column);
|
||||
|
||||
$this->applyTransformations($column, $luColumn);
|
||||
|
||||
$pivot = $this->findPivot($column, $luColumn);
|
||||
if ($pivot !== $column) {
|
||||
$this->pivotExchange($pivot, $column);
|
||||
}
|
||||
|
||||
$this->computeMultipliers($column);
|
||||
|
||||
unset($luColumn);
|
||||
}
|
||||
}
|
||||
|
||||
private function localisedReferenceColumn($column): array
|
||||
{
|
||||
$luColumn = [];
|
||||
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
$luColumn[$row] = &$this->luMatrix[$row][$column];
|
||||
}
|
||||
|
||||
return $luColumn;
|
||||
}
|
||||
|
||||
private function applyTransformations($column, array $luColumn): void
|
||||
{
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
$luRow = $this->luMatrix[$row];
|
||||
// Most of the time is spent in the following dot product.
|
||||
$kmax = min($row, $column);
|
||||
$sValue = 0.0;
|
||||
for ($kValue = 0; $kValue < $kmax; ++$kValue) {
|
||||
$sValue += $luRow[$kValue] * $luColumn[$kValue];
|
||||
}
|
||||
$luRow[$column] = $luColumn[$row] -= $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
private function findPivot($column, array $luColumn): int
|
||||
{
|
||||
$pivot = $column;
|
||||
for ($row = $column + 1; $row < $this->rows; ++$row) {
|
||||
if (abs($luColumn[$row]) > abs($luColumn[$pivot])) {
|
||||
$pivot = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $pivot;
|
||||
}
|
||||
|
||||
private function pivotExchange($pivot, $column): void
|
||||
{
|
||||
for ($kValue = 0; $kValue < $this->columns; ++$kValue) {
|
||||
$tValue = $this->luMatrix[$pivot][$kValue];
|
||||
$this->luMatrix[$pivot][$kValue] = $this->luMatrix[$column][$kValue];
|
||||
$this->luMatrix[$column][$kValue] = $tValue;
|
||||
}
|
||||
|
||||
$lValue = $this->pivot[$pivot];
|
||||
$this->pivot[$pivot] = $this->pivot[$column];
|
||||
$this->pivot[$column] = $lValue;
|
||||
}
|
||||
|
||||
private function computeMultipliers($diagonal): void
|
||||
{
|
||||
if (($diagonal < $this->rows) && ($this->luMatrix[$diagonal][$diagonal] != 0.0)) {
|
||||
for ($row = $diagonal + 1; $row < $this->rows; ++$row) {
|
||||
$this->luMatrix[$row][$diagonal] /= $this->luMatrix[$diagonal][$diagonal];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function pivotB(Matrix $B): array
|
||||
{
|
||||
$X = [];
|
||||
foreach ($this->pivot as $rowId) {
|
||||
$row = $B->getRows($rowId + 1)->toArray();
|
||||
$X[] = array_pop($row);
|
||||
}
|
||||
|
||||
return $X;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve A*X = B.
|
||||
*
|
||||
* @param Matrix $B a Matrix with as many rows as A and any number of columns
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Matrix X so that L*U*X = B(piv,:)
|
||||
*/
|
||||
public function solve(Matrix $B): Matrix
|
||||
{
|
||||
if ($B->rows !== $this->rows) {
|
||||
throw new Exception('Matrix row dimensions are not equal');
|
||||
}
|
||||
|
||||
if ($this->rows !== $this->columns) {
|
||||
throw new Exception('LU solve() only works on square matrices');
|
||||
}
|
||||
|
||||
if (!$this->isNonsingular()) {
|
||||
throw new Exception('Can only perform operation on singular matrix');
|
||||
}
|
||||
|
||||
// Copy right hand side with pivoting
|
||||
$nx = $B->columns;
|
||||
$X = $this->pivotB($B);
|
||||
|
||||
// Solve L*Y = B(piv,:)
|
||||
for ($k = 0; $k < $this->columns; ++$k) {
|
||||
for ($i = $k + 1; $i < $this->columns; ++$i) {
|
||||
for ($j = 0; $j < $nx; ++$j) {
|
||||
$X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Solve U*X = Y;
|
||||
for ($k = $this->columns - 1; $k >= 0; --$k) {
|
||||
for ($j = 0; $j < $nx; ++$j) {
|
||||
$X[$k][$j] /= $this->luMatrix[$k][$k];
|
||||
}
|
||||
for ($i = 0; $i < $k; ++$i) {
|
||||
for ($j = 0; $j < $nx; ++$j) {
|
||||
$X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix($X);
|
||||
}
|
||||
}
|
191
vendor/markbaker/matrix/classes/src/Decomposition/QR.php
vendored
Normal file
191
vendor/markbaker/matrix/classes/src/Decomposition/QR.php
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Decomposition;
|
||||
|
||||
use Matrix\Exception;
|
||||
use Matrix\Matrix;
|
||||
|
||||
class QR
|
||||
{
|
||||
private $qrMatrix;
|
||||
private $rows;
|
||||
private $columns;
|
||||
|
||||
private $rDiagonal = [];
|
||||
|
||||
public function __construct(Matrix $matrix)
|
||||
{
|
||||
$this->qrMatrix = $matrix->toArray();
|
||||
$this->rows = $matrix->rows;
|
||||
$this->columns = $matrix->columns;
|
||||
|
||||
$this->decompose();
|
||||
}
|
||||
|
||||
public function getHouseholdVectors(): Matrix
|
||||
{
|
||||
$householdVectors = [];
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
if ($row >= $column) {
|
||||
$householdVectors[$row][$column] = $this->qrMatrix[$row][$column];
|
||||
} else {
|
||||
$householdVectors[$row][$column] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix($householdVectors);
|
||||
}
|
||||
|
||||
public function getQ(): Matrix
|
||||
{
|
||||
$qGrid = [];
|
||||
|
||||
$rowCount = $this->rows;
|
||||
for ($k = $this->columns - 1; $k >= 0; --$k) {
|
||||
for ($i = 0; $i < $this->rows; ++$i) {
|
||||
$qGrid[$i][$k] = 0.0;
|
||||
}
|
||||
$qGrid[$k][$k] = 1.0;
|
||||
if ($this->columns > $this->rows) {
|
||||
$qGrid = array_slice($qGrid, 0, $this->rows);
|
||||
}
|
||||
|
||||
for ($j = $k; $j < $this->columns; ++$j) {
|
||||
if (isset($this->qrMatrix[$k], $this->qrMatrix[$k][$k]) && $this->qrMatrix[$k][$k] != 0.0) {
|
||||
$s = 0.0;
|
||||
for ($i = $k; $i < $this->rows; ++$i) {
|
||||
$s += $this->qrMatrix[$i][$k] * $qGrid[$i][$j];
|
||||
}
|
||||
$s = -$s / $this->qrMatrix[$k][$k];
|
||||
for ($i = $k; $i < $this->rows; ++$i) {
|
||||
$qGrid[$i][$j] += $s * $this->qrMatrix[$i][$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array_walk(
|
||||
$qGrid,
|
||||
function (&$row) use ($rowCount) {
|
||||
$row = array_reverse($row);
|
||||
$row = array_slice($row, 0, $rowCount);
|
||||
}
|
||||
);
|
||||
|
||||
return new Matrix($qGrid);
|
||||
}
|
||||
|
||||
public function getR(): Matrix
|
||||
{
|
||||
$rGrid = [];
|
||||
|
||||
for ($row = 0; $row < $this->columns; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
if ($row < $column) {
|
||||
$rGrid[$row][$column] = $this->qrMatrix[$row][$column] ?? 0.0;
|
||||
} elseif ($row === $column) {
|
||||
$rGrid[$row][$column] = $this->rDiagonal[$row] ?? 0.0;
|
||||
} else {
|
||||
$rGrid[$row][$column] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->columns > $this->rows) {
|
||||
$rGrid = array_slice($rGrid, 0, $this->rows);
|
||||
}
|
||||
|
||||
return new Matrix($rGrid);
|
||||
}
|
||||
|
||||
private function hypo($a, $b): float
|
||||
{
|
||||
if (abs($a) > abs($b)) {
|
||||
$r = $b / $a;
|
||||
$r = abs($a) * sqrt(1 + $r * $r);
|
||||
} elseif ($b != 0.0) {
|
||||
$r = $a / $b;
|
||||
$r = abs($b) * sqrt(1 + $r * $r);
|
||||
} else {
|
||||
$r = 0.0;
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* QR Decomposition computed by Householder reflections.
|
||||
*/
|
||||
private function decompose(): void
|
||||
{
|
||||
for ($k = 0; $k < $this->columns; ++$k) {
|
||||
// Compute 2-norm of k-th column without under/overflow.
|
||||
$norm = 0.0;
|
||||
for ($i = $k; $i < $this->rows; ++$i) {
|
||||
$norm = $this->hypo($norm, $this->qrMatrix[$i][$k]);
|
||||
}
|
||||
if ($norm != 0.0) {
|
||||
// Form k-th Householder vector.
|
||||
if ($this->qrMatrix[$k][$k] < 0.0) {
|
||||
$norm = -$norm;
|
||||
}
|
||||
for ($i = $k; $i < $this->rows; ++$i) {
|
||||
$this->qrMatrix[$i][$k] /= $norm;
|
||||
}
|
||||
$this->qrMatrix[$k][$k] += 1.0;
|
||||
// Apply transformation to remaining columns.
|
||||
for ($j = $k + 1; $j < $this->columns; ++$j) {
|
||||
$s = 0.0;
|
||||
for ($i = $k; $i < $this->rows; ++$i) {
|
||||
$s += $this->qrMatrix[$i][$k] * $this->qrMatrix[$i][$j];
|
||||
}
|
||||
$s = -$s / $this->qrMatrix[$k][$k];
|
||||
for ($i = $k; $i < $this->rows; ++$i) {
|
||||
$this->qrMatrix[$i][$j] += $s * $this->qrMatrix[$i][$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->rDiagonal[$k] = -$norm;
|
||||
}
|
||||
}
|
||||
|
||||
public function isFullRank(): bool
|
||||
{
|
||||
for ($j = 0; $j < $this->columns; ++$j) {
|
||||
if ($this->rDiagonal[$j] == 0.0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Least squares solution of A*X = B.
|
||||
*
|
||||
* @param Matrix $B a Matrix with as many rows as A and any number of columns
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Matrix matrix that minimizes the two norm of Q*R*X-B
|
||||
*/
|
||||
public function solve(Matrix $B): Matrix
|
||||
{
|
||||
if ($B->rows !== $this->rows) {
|
||||
throw new Exception('Matrix row dimensions are not equal');
|
||||
}
|
||||
|
||||
if (!$this->isFullRank()) {
|
||||
throw new Exception('Can only perform this operation on a full-rank matrix');
|
||||
}
|
||||
|
||||
// Compute Y = transpose(Q)*B
|
||||
$Y = $this->getQ()->transpose()
|
||||
->multiply($B);
|
||||
// Solve R*X = Y;
|
||||
return $this->getR()->inverse()
|
||||
->multiply($Y);
|
||||
}
|
||||
}
|
13
vendor/markbaker/matrix/classes/src/Div0Exception.php
vendored
Normal file
13
vendor/markbaker/matrix/classes/src/Div0Exception.php
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception.
|
||||
*
|
||||
* @copyright Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix)
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
namespace Matrix;
|
||||
|
||||
class Div0Exception extends Exception
|
||||
{
|
||||
}
|
13
vendor/markbaker/matrix/classes/src/Exception.php
vendored
Normal file
13
vendor/markbaker/matrix/classes/src/Exception.php
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception.
|
||||
*
|
||||
* @copyright Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix)
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
namespace Matrix;
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
376
vendor/markbaker/matrix/classes/src/Functions.php
vendored
Normal file
376
vendor/markbaker/matrix/classes/src/Functions.php
vendored
Normal file
@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix;
|
||||
|
||||
class Functions
|
||||
{
|
||||
/**
|
||||
* Validates an array of matrix, converting an array to a matrix if required.
|
||||
*
|
||||
* @param Matrix|array $matrix Matrix or an array to treat as a matrix.
|
||||
* @return Matrix The new matrix
|
||||
* @throws Exception If argument isn't a valid matrix or array.
|
||||
*/
|
||||
private static function validateMatrix($matrix)
|
||||
{
|
||||
if (is_array($matrix)) {
|
||||
$matrix = new Matrix($matrix);
|
||||
}
|
||||
if (!$matrix instanceof Matrix) {
|
||||
throw new Exception('Must be Matrix or array');
|
||||
}
|
||||
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the adjoint of the matrix
|
||||
*
|
||||
* @param Matrix $matrix The matrix whose adjoint we wish to calculate
|
||||
* @return Matrix
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getAdjoint(Matrix $matrix)
|
||||
{
|
||||
return self::transpose(
|
||||
self::getCofactors($matrix)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the adjoint of this matrix
|
||||
* The adjugate, classical adjoint, or adjunct of a square matrix is the transpose of its cofactor matrix.
|
||||
* The adjugate has sometimes been called the "adjoint", but today the "adjoint" of a matrix normally refers
|
||||
* to its corresponding adjoint operator, which is its conjugate transpose.
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose adjoint we wish to calculate
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function adjoint($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Adjoint can only be calculated for a square matrix');
|
||||
}
|
||||
|
||||
return self::getAdjoint($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the cofactors of the matrix
|
||||
*
|
||||
* @param Matrix $matrix The matrix whose cofactors we wish to calculate
|
||||
* @return Matrix
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getCofactors(Matrix $matrix)
|
||||
{
|
||||
$cofactors = self::getMinors($matrix);
|
||||
$dimensions = $matrix->rows;
|
||||
|
||||
$cof = 1;
|
||||
for ($i = 0; $i < $dimensions; ++$i) {
|
||||
$cofs = $cof;
|
||||
for ($j = 0; $j < $dimensions; ++$j) {
|
||||
$cofactors[$i][$j] *= $cofs;
|
||||
$cofs = -$cofs;
|
||||
}
|
||||
$cof = -$cof;
|
||||
}
|
||||
|
||||
return new Matrix($cofactors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cofactors of this matrix
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose cofactors we wish to calculate
|
||||
* @return Matrix
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function cofactors($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Cofactors can only be calculated for a square matrix');
|
||||
}
|
||||
|
||||
return self::getCofactors($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Matrix $matrix
|
||||
* @param int $row
|
||||
* @param int $column
|
||||
* @return float
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getDeterminantSegment(Matrix $matrix, $row, $column)
|
||||
{
|
||||
$tmpMatrix = $matrix->toArray();
|
||||
unset($tmpMatrix[$row]);
|
||||
array_walk(
|
||||
$tmpMatrix,
|
||||
function (&$row) use ($column) {
|
||||
unset($row[$column]);
|
||||
}
|
||||
);
|
||||
|
||||
return self::getDeterminant(new Matrix($tmpMatrix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the determinant of the matrix
|
||||
*
|
||||
* @param Matrix $matrix The matrix whose determinant we wish to calculate
|
||||
* @return float
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getDeterminant(Matrix $matrix)
|
||||
{
|
||||
$dimensions = $matrix->rows;
|
||||
$determinant = 0;
|
||||
|
||||
switch ($dimensions) {
|
||||
case 1:
|
||||
$determinant = $matrix->getValue(1, 1);
|
||||
break;
|
||||
case 2:
|
||||
$determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) -
|
||||
$matrix->getValue(1, 2) * $matrix->getValue(2, 1);
|
||||
break;
|
||||
default:
|
||||
for ($i = 1; $i <= $dimensions; ++$i) {
|
||||
$det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1);
|
||||
if (($i % 2) == 0) {
|
||||
$determinant -= $det;
|
||||
} else {
|
||||
$determinant += $det;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $determinant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the determinant of this matrix
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose determinant we wish to calculate
|
||||
* @return float
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function determinant($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Determinant can only be calculated for a square matrix');
|
||||
}
|
||||
|
||||
return self::getDeterminant($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the diagonal of this matrix
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose diagonal we wish to calculate
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function diagonal($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Diagonal can only be extracted from a square matrix');
|
||||
}
|
||||
|
||||
$dimensions = $matrix->rows;
|
||||
$grid = Builder::createFilledMatrix(0, $dimensions, $dimensions)
|
||||
->toArray();
|
||||
|
||||
for ($i = 0; $i < $dimensions; ++$i) {
|
||||
$grid[$i][$i] = $matrix->getValue($i + 1, $i + 1);
|
||||
}
|
||||
|
||||
return new Matrix($grid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the antidiagonal of this matrix
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose antidiagonal we wish to calculate
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function antidiagonal($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Anti-Diagonal can only be extracted from a square matrix');
|
||||
}
|
||||
|
||||
$dimensions = $matrix->rows;
|
||||
$grid = Builder::createFilledMatrix(0, $dimensions, $dimensions)
|
||||
->toArray();
|
||||
|
||||
for ($i = 0; $i < $dimensions; ++$i) {
|
||||
$grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i);
|
||||
}
|
||||
|
||||
return new Matrix($grid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identity matrix
|
||||
* The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix
|
||||
* with ones on the main diagonal and zeros elsewhere
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose identity we wish to calculate
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function identity($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Identity can only be created for a square matrix');
|
||||
}
|
||||
|
||||
$dimensions = $matrix->rows;
|
||||
|
||||
return Builder::createIdentityMatrix($dimensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the inverse of this matrix
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose inverse we wish to calculate
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function inverse($matrix, string $type = 'inverse')
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception(ucfirst($type) . ' can only be calculated for a square matrix');
|
||||
}
|
||||
|
||||
$determinant = self::getDeterminant($matrix);
|
||||
if ($determinant == 0.0) {
|
||||
throw new Div0Exception(ucfirst($type) . ' can only be calculated for a matrix with a non-zero determinant');
|
||||
}
|
||||
|
||||
if ($matrix->rows == 1) {
|
||||
return new Matrix([[1 / $matrix->getValue(1, 1)]]);
|
||||
}
|
||||
|
||||
return self::getAdjoint($matrix)
|
||||
->multiply(1 / $determinant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the minors of the matrix
|
||||
*
|
||||
* @param Matrix $matrix The matrix whose minors we wish to calculate
|
||||
* @return array[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function getMinors(Matrix $matrix)
|
||||
{
|
||||
$minors = $matrix->toArray();
|
||||
$dimensions = $matrix->rows;
|
||||
if ($dimensions == 1) {
|
||||
return $minors;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $dimensions; ++$i) {
|
||||
for ($j = 0; $j < $dimensions; ++$j) {
|
||||
$minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j);
|
||||
}
|
||||
}
|
||||
|
||||
return $minors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minors of the matrix
|
||||
* The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or
|
||||
* more of its rows or columns.
|
||||
* Minors obtained by removing just one row and one column from square matrices (first minors) are required for
|
||||
* calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of
|
||||
* square matrices.
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose minors we wish to calculate
|
||||
* @return Matrix
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function minors($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Minors can only be calculated for a square matrix');
|
||||
}
|
||||
|
||||
return new Matrix(self::getMinors($matrix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the trace of this matrix
|
||||
* The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right)
|
||||
* of the matrix
|
||||
*
|
||||
* @param Matrix|array $matrix The matrix whose trace we wish to calculate
|
||||
* @return float
|
||||
* @throws Exception
|
||||
**/
|
||||
public static function trace($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
if (!$matrix->isSquare()) {
|
||||
throw new Exception('Trace can only be extracted from a square matrix');
|
||||
}
|
||||
|
||||
$dimensions = $matrix->rows;
|
||||
$result = 0;
|
||||
for ($i = 1; $i <= $dimensions; ++$i) {
|
||||
$result += $matrix->getValue($i, $i);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transpose of this matrix
|
||||
*
|
||||
* @param Matrix|\a $matrix The matrix whose transpose we wish to calculate
|
||||
* @return Matrix
|
||||
**/
|
||||
public static function transpose($matrix)
|
||||
{
|
||||
$matrix = self::validateMatrix($matrix);
|
||||
|
||||
$array = array_values(array_merge([null], $matrix->toArray()));
|
||||
$grid = call_user_func_array(
|
||||
'array_map',
|
||||
$array
|
||||
);
|
||||
|
||||
return new Matrix($grid);
|
||||
}
|
||||
}
|
423
vendor/markbaker/matrix/classes/src/Matrix.php
vendored
Normal file
423
vendor/markbaker/matrix/classes/src/Matrix.php
vendored
Normal file
@ -0,0 +1,423 @@
|
||||
<?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;
|
||||
|
||||
use Generator;
|
||||
use Matrix\Decomposition\LU;
|
||||
use Matrix\Decomposition\QR;
|
||||
|
||||
/**
|
||||
* 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 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)
|
||||
* @method Matrix directsum(...$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): void
|
||||
{
|
||||
$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(int $row): int
|
||||
{
|
||||
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(int $column): int
|
||||
{
|
||||
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(int $row): int
|
||||
{
|
||||
$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(int $column): int
|
||||
{
|
||||
$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(int $row, int $rowCount = 1): Matrix
|
||||
{
|
||||
$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(int $column, int $columnCount = 1): Matrix
|
||||
{
|
||||
$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(int $row, int $rowCount = 1): Matrix
|
||||
{
|
||||
$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(int $column, int $columnCount = 1): Matrix
|
||||
{
|
||||
$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(int $row, int $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 column vector
|
||||
*
|
||||
* @return Generator|Matrix[]|mixed[]
|
||||
*/
|
||||
public function rows(): Generator
|
||||
{
|
||||
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 row vector
|
||||
*
|
||||
* @return Generator|Matrix[]|mixed[]
|
||||
*/
|
||||
public function columns(): Generator
|
||||
{
|
||||
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(): bool
|
||||
{
|
||||
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(): bool
|
||||
{
|
||||
return $this->rows === 1 || $this->columns === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the matrix as a 2-dimensional array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve A*X = B.
|
||||
*
|
||||
* @param Matrix $B Right hand side
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Matrix ... Solution if A is square, least squares solution otherwise
|
||||
*/
|
||||
public function solve(Matrix $B): Matrix
|
||||
{
|
||||
if ($this->columns === $this->rows) {
|
||||
return (new LU($this))->solve($B);
|
||||
}
|
||||
|
||||
return (new QR($this))->solve($B);
|
||||
}
|
||||
|
||||
protected static $getters = [
|
||||
'rows',
|
||||
'columns',
|
||||
];
|
||||
|
||||
/**
|
||||
* Access specific properties as read-only (no setters)
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __get(string $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 = [
|
||||
'adjoint',
|
||||
'antidiagonal',
|
||||
'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(string $functionName, $arguments)
|
||||
{
|
||||
$functionName = strtolower(str_replace('_', '', $functionName));
|
||||
|
||||
// Test for function calls
|
||||
if (in_array($functionName, self::$functions, true)) {
|
||||
return Functions::$functionName($this, ...$arguments);
|
||||
}
|
||||
// Test for operation calls
|
||||
if (in_array($functionName, self::$operations, true)) {
|
||||
return Operations::$functionName($this, ...$arguments);
|
||||
}
|
||||
throw new Exception('Function or Operation does not exist');
|
||||
}
|
||||
}
|
157
vendor/markbaker/matrix/classes/src/Operations.php
vendored
Normal file
157
vendor/markbaker/matrix/classes/src/Operations.php
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix;
|
||||
|
||||
use Matrix\Operators\Addition;
|
||||
use Matrix\Operators\DirectSum;
|
||||
use Matrix\Operators\Division;
|
||||
use Matrix\Operators\Multiplication;
|
||||
use Matrix\Operators\Subtraction;
|
||||
|
||||
class Operations
|
||||
{
|
||||
public static function add(...$matrixValues): Matrix
|
||||
{
|
||||
if (count($matrixValues) < 2) {
|
||||
throw new Exception('Addition operation requires at least 2 arguments');
|
||||
}
|
||||
|
||||
$matrix = array_shift($matrixValues);
|
||||
|
||||
if (is_array($matrix)) {
|
||||
$matrix = new Matrix($matrix);
|
||||
}
|
||||
if (!$matrix instanceof Matrix) {
|
||||
throw new Exception('Addition arguments must be Matrix or array');
|
||||
}
|
||||
|
||||
$result = new Addition($matrix);
|
||||
|
||||
foreach ($matrixValues as $matrix) {
|
||||
$result->execute($matrix);
|
||||
}
|
||||
|
||||
return $result->result();
|
||||
}
|
||||
|
||||
public static function directsum(...$matrixValues): Matrix
|
||||
{
|
||||
if (count($matrixValues) < 2) {
|
||||
throw new Exception('DirectSum operation requires at least 2 arguments');
|
||||
}
|
||||
|
||||
$matrix = array_shift($matrixValues);
|
||||
|
||||
if (is_array($matrix)) {
|
||||
$matrix = new Matrix($matrix);
|
||||
}
|
||||
if (!$matrix instanceof Matrix) {
|
||||
throw new Exception('DirectSum arguments must be Matrix or array');
|
||||
}
|
||||
|
||||
$result = new DirectSum($matrix);
|
||||
|
||||
foreach ($matrixValues as $matrix) {
|
||||
$result->execute($matrix);
|
||||
}
|
||||
|
||||
return $result->result();
|
||||
}
|
||||
|
||||
public static function divideby(...$matrixValues): Matrix
|
||||
{
|
||||
if (count($matrixValues) < 2) {
|
||||
throw new Exception('Division operation requires at least 2 arguments');
|
||||
}
|
||||
|
||||
$matrix = array_shift($matrixValues);
|
||||
|
||||
if (is_array($matrix)) {
|
||||
$matrix = new Matrix($matrix);
|
||||
}
|
||||
if (!$matrix instanceof Matrix) {
|
||||
throw new Exception('Division arguments must be Matrix or array');
|
||||
}
|
||||
|
||||
$result = new Division($matrix);
|
||||
|
||||
foreach ($matrixValues as $matrix) {
|
||||
$result->execute($matrix);
|
||||
}
|
||||
|
||||
return $result->result();
|
||||
}
|
||||
|
||||
public static function divideinto(...$matrixValues): Matrix
|
||||
{
|
||||
if (count($matrixValues) < 2) {
|
||||
throw new Exception('Division operation requires at least 2 arguments');
|
||||
}
|
||||
|
||||
$matrix = array_pop($matrixValues);
|
||||
$matrixValues = array_reverse($matrixValues);
|
||||
|
||||
if (is_array($matrix)) {
|
||||
$matrix = new Matrix($matrix);
|
||||
}
|
||||
if (!$matrix instanceof Matrix) {
|
||||
throw new Exception('Division arguments must be Matrix or array');
|
||||
}
|
||||
|
||||
$result = new Division($matrix);
|
||||
|
||||
foreach ($matrixValues as $matrix) {
|
||||
$result->execute($matrix);
|
||||
}
|
||||
|
||||
return $result->result();
|
||||
}
|
||||
|
||||
public static function multiply(...$matrixValues): Matrix
|
||||
{
|
||||
if (count($matrixValues) < 2) {
|
||||
throw new Exception('Multiplication operation requires at least 2 arguments');
|
||||
}
|
||||
|
||||
$matrix = array_shift($matrixValues);
|
||||
|
||||
if (is_array($matrix)) {
|
||||
$matrix = new Matrix($matrix);
|
||||
}
|
||||
if (!$matrix instanceof Matrix) {
|
||||
throw new Exception('Multiplication arguments must be Matrix or array');
|
||||
}
|
||||
|
||||
$result = new Multiplication($matrix);
|
||||
|
||||
foreach ($matrixValues as $matrix) {
|
||||
$result->execute($matrix);
|
||||
}
|
||||
|
||||
return $result->result();
|
||||
}
|
||||
|
||||
public static function subtract(...$matrixValues): Matrix
|
||||
{
|
||||
if (count($matrixValues) < 2) {
|
||||
throw new Exception('Subtraction operation requires at least 2 arguments');
|
||||
}
|
||||
|
||||
$matrix = array_shift($matrixValues);
|
||||
|
||||
if (is_array($matrix)) {
|
||||
$matrix = new Matrix($matrix);
|
||||
}
|
||||
if (!$matrix instanceof Matrix) {
|
||||
throw new Exception('Subtraction arguments must be Matrix or array');
|
||||
}
|
||||
|
||||
$result = new Subtraction($matrix);
|
||||
|
||||
foreach ($matrixValues as $matrix) {
|
||||
$result->execute($matrix);
|
||||
}
|
||||
|
||||
return $result->result();
|
||||
}
|
||||
}
|
68
vendor/markbaker/matrix/classes/src/Operators/Addition.php
vendored
Normal file
68
vendor/markbaker/matrix/classes/src/Operators/Addition.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Operators;
|
||||
|
||||
use Matrix\Matrix;
|
||||
use Matrix\Exception;
|
||||
|
||||
class Addition extends Operator
|
||||
{
|
||||
/**
|
||||
* Execute the addition
|
||||
*
|
||||
* @param mixed $value The matrix or numeric value to add to the current base value
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
* @return $this The operation object, allowing multiple additions to be chained
|
||||
**/
|
||||
public function execute($value): Operator
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = new Matrix($value);
|
||||
}
|
||||
|
||||
if (is_object($value) && ($value instanceof Matrix)) {
|
||||
return $this->addMatrix($value);
|
||||
} elseif (is_numeric($value)) {
|
||||
return $this->addScalar($value);
|
||||
}
|
||||
|
||||
throw new Exception('Invalid argument for addition');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the addition for a scalar
|
||||
*
|
||||
* @param mixed $value The numeric value to add to the current base value
|
||||
* @return $this The operation object, allowing multiple additions to be chained
|
||||
**/
|
||||
protected function addScalar($value): Operator
|
||||
{
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
$this->matrix[$row][$column] += $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the addition for a matrix
|
||||
*
|
||||
* @param Matrix $value The numeric value to add to the current base value
|
||||
* @return $this The operation object, allowing multiple additions to be chained
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
**/
|
||||
protected function addMatrix(Matrix $value): Operator
|
||||
{
|
||||
$this->validateMatchingDimensions($value);
|
||||
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
$this->matrix[$row][$column] += $value->getValue($row + 1, $column + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
64
vendor/markbaker/matrix/classes/src/Operators/DirectSum.php
vendored
Normal file
64
vendor/markbaker/matrix/classes/src/Operators/DirectSum.php
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Operators;
|
||||
|
||||
use Matrix\Matrix;
|
||||
use Matrix\Exception;
|
||||
|
||||
class DirectSum extends Operator
|
||||
{
|
||||
/**
|
||||
* Execute the addition
|
||||
*
|
||||
* @param mixed $value The matrix or numeric value to add to the current base value
|
||||
* @return $this The operation object, allowing multiple additions to be chained
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
*/
|
||||
public function execute($value): Operator
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = new Matrix($value);
|
||||
}
|
||||
|
||||
if ($value instanceof Matrix) {
|
||||
return $this->directSumMatrix($value);
|
||||
}
|
||||
|
||||
throw new Exception('Invalid argument for addition');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the direct sum for a matrix
|
||||
*
|
||||
* @param Matrix $value The numeric value to concatenate/direct sum with the current base value
|
||||
* @return $this The operation object, allowing multiple additions to be chained
|
||||
**/
|
||||
private function directSumMatrix($value): Operator
|
||||
{
|
||||
$originalColumnCount = count($this->matrix[0]);
|
||||
$originalRowCount = count($this->matrix);
|
||||
$valColumnCount = $value->columns;
|
||||
$valRowCount = $value->rows;
|
||||
$value = $value->toArray();
|
||||
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
$this->matrix[$row] = array_merge($this->matrix[$row], array_fill(0, $valColumnCount, 0));
|
||||
}
|
||||
|
||||
$this->matrix = array_merge(
|
||||
$this->matrix,
|
||||
array_fill(0, $valRowCount, array_fill(0, $originalColumnCount, 0))
|
||||
);
|
||||
|
||||
for ($row = $originalRowCount; $row < $originalRowCount + $valRowCount; ++$row) {
|
||||
array_splice(
|
||||
$this->matrix[$row],
|
||||
$originalColumnCount,
|
||||
$valColumnCount,
|
||||
$value[$row - $originalRowCount]
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
35
vendor/markbaker/matrix/classes/src/Operators/Division.php
vendored
Normal file
35
vendor/markbaker/matrix/classes/src/Operators/Division.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Operators;
|
||||
|
||||
use Matrix\Div0Exception;
|
||||
use Matrix\Exception;
|
||||
use \Matrix\Matrix;
|
||||
use \Matrix\Functions;
|
||||
|
||||
class Division extends Multiplication
|
||||
{
|
||||
/**
|
||||
* Execute the division
|
||||
*
|
||||
* @param mixed $value The matrix or numeric value to divide the current base value by
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
* @return $this The operation object, allowing multiple divisions to be chained
|
||||
**/
|
||||
public function execute($value, string $type = 'division'): Operator
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = new Matrix($value);
|
||||
}
|
||||
|
||||
if (is_object($value) && ($value instanceof Matrix)) {
|
||||
$value = Functions::inverse($value, $type);
|
||||
|
||||
return $this->multiplyMatrix($value, $type);
|
||||
} elseif (is_numeric($value)) {
|
||||
return $this->multiplyScalar(1 / $value, $type);
|
||||
}
|
||||
|
||||
throw new Exception('Invalid argument for division');
|
||||
}
|
||||
}
|
86
vendor/markbaker/matrix/classes/src/Operators/Multiplication.php
vendored
Normal file
86
vendor/markbaker/matrix/classes/src/Operators/Multiplication.php
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Operators;
|
||||
|
||||
use Matrix\Matrix;
|
||||
use \Matrix\Builder;
|
||||
use Matrix\Exception;
|
||||
use Throwable;
|
||||
|
||||
class Multiplication extends Operator
|
||||
{
|
||||
/**
|
||||
* Execute the multiplication
|
||||
*
|
||||
* @param mixed $value The matrix or numeric value to multiply the current base value by
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
* @return $this The operation object, allowing multiple multiplications to be chained
|
||||
**/
|
||||
public function execute($value, string $type = 'multiplication'): Operator
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = new Matrix($value);
|
||||
}
|
||||
|
||||
if (is_object($value) && ($value instanceof Matrix)) {
|
||||
return $this->multiplyMatrix($value, $type);
|
||||
} elseif (is_numeric($value)) {
|
||||
return $this->multiplyScalar($value, $type);
|
||||
}
|
||||
|
||||
throw new Exception("Invalid argument for $type");
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the multiplication for a scalar
|
||||
*
|
||||
* @param mixed $value The numeric value to multiply with the current base value
|
||||
* @return $this The operation object, allowing multiple mutiplications to be chained
|
||||
**/
|
||||
protected function multiplyScalar($value, string $type = 'multiplication'): Operator
|
||||
{
|
||||
try {
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
$this->matrix[$row][$column] *= $value;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
throw new Exception("Invalid argument for $type");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the multiplication for a matrix
|
||||
*
|
||||
* @param Matrix $value The numeric value to multiply with the current base value
|
||||
* @return $this The operation object, allowing multiple mutiplications to be chained
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
**/
|
||||
protected function multiplyMatrix(Matrix $value, string $type = 'multiplication'): Operator
|
||||
{
|
||||
$this->validateReflectingDimensions($value);
|
||||
|
||||
$newRows = $this->rows;
|
||||
$newColumns = $value->columns;
|
||||
$matrix = Builder::createFilledMatrix(0, $newRows, $newColumns)
|
||||
->toArray();
|
||||
try {
|
||||
for ($row = 0; $row < $newRows; ++$row) {
|
||||
for ($column = 0; $column < $newColumns; ++$column) {
|
||||
$columnData = $value->getColumns($column + 1)->toArray();
|
||||
foreach ($this->matrix[$row] as $key => $valueData) {
|
||||
$matrix[$row][$column] += $valueData * $columnData[$key][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
throw new Exception("Invalid argument for $type");
|
||||
}
|
||||
$this->matrix = $matrix;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
78
vendor/markbaker/matrix/classes/src/Operators/Operator.php
vendored
Normal file
78
vendor/markbaker/matrix/classes/src/Operators/Operator.php
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Operators;
|
||||
|
||||
use Matrix\Matrix;
|
||||
use Matrix\Exception;
|
||||
|
||||
abstract class Operator
|
||||
{
|
||||
/**
|
||||
* Stored internally as a 2-dimension array of values
|
||||
*
|
||||
* @property mixed[][] $matrix
|
||||
**/
|
||||
protected $matrix;
|
||||
|
||||
/**
|
||||
* Number of rows in the matrix
|
||||
*
|
||||
* @property integer $rows
|
||||
**/
|
||||
protected $rows;
|
||||
|
||||
/**
|
||||
* Number of columns in the matrix
|
||||
*
|
||||
* @property integer $columns
|
||||
**/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* Create an new handler object for the operation
|
||||
*
|
||||
* @param Matrix $matrix The base Matrix object on which the operation will be performed
|
||||
*/
|
||||
public function __construct(Matrix $matrix)
|
||||
{
|
||||
$this->rows = $matrix->rows;
|
||||
$this->columns = $matrix->columns;
|
||||
$this->matrix = $matrix->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the dimensions of the matrices being operated on to see if they are valid for addition/subtraction
|
||||
*
|
||||
* @param Matrix $matrix The second Matrix object on which the operation will be performed
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function validateMatchingDimensions(Matrix $matrix): void
|
||||
{
|
||||
if (($this->rows != $matrix->rows) || ($this->columns != $matrix->columns)) {
|
||||
throw new Exception('Matrices have mismatched dimensions');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the dimensions of the matrices being operated on to see if they are valid for multiplication/division
|
||||
*
|
||||
* @param Matrix $matrix The second Matrix object on which the operation will be performed
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function validateReflectingDimensions(Matrix $matrix): void
|
||||
{
|
||||
if ($this->columns != $matrix->rows) {
|
||||
throw new Exception('Matrices have mismatched dimensions');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of the operation
|
||||
*
|
||||
* @return Matrix
|
||||
*/
|
||||
public function result(): Matrix
|
||||
{
|
||||
return new Matrix($this->matrix);
|
||||
}
|
||||
}
|
68
vendor/markbaker/matrix/classes/src/Operators/Subtraction.php
vendored
Normal file
68
vendor/markbaker/matrix/classes/src/Operators/Subtraction.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Matrix\Operators;
|
||||
|
||||
use Matrix\Matrix;
|
||||
use Matrix\Exception;
|
||||
|
||||
class Subtraction extends Operator
|
||||
{
|
||||
/**
|
||||
* Execute the subtraction
|
||||
*
|
||||
* @param mixed $value The matrix or numeric value to subtract from the current base value
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
* @return $this The operation object, allowing multiple subtractions to be chained
|
||||
**/
|
||||
public function execute($value): Operator
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = new Matrix($value);
|
||||
}
|
||||
|
||||
if (is_object($value) && ($value instanceof Matrix)) {
|
||||
return $this->subtractMatrix($value);
|
||||
} elseif (is_numeric($value)) {
|
||||
return $this->subtractScalar($value);
|
||||
}
|
||||
|
||||
throw new Exception('Invalid argument for subtraction');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the subtraction for a scalar
|
||||
*
|
||||
* @param mixed $value The numeric value to subtracted from the current base value
|
||||
* @return $this The operation object, allowing multiple additions to be chained
|
||||
**/
|
||||
protected function subtractScalar($value): Operator
|
||||
{
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
$this->matrix[$row][$column] -= $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the subtraction for a matrix
|
||||
*
|
||||
* @param Matrix $value The numeric value to subtract from the current base value
|
||||
* @return $this The operation object, allowing multiple subtractions to be chained
|
||||
* @throws Exception If the provided argument is not appropriate for the operation
|
||||
**/
|
||||
protected function subtractMatrix(Matrix $value): Operator
|
||||
{
|
||||
$this->validateMatchingDimensions($value);
|
||||
|
||||
for ($row = 0; $row < $this->rows; ++$row) {
|
||||
for ($column = 0; $column < $this->columns; ++$column) {
|
||||
$this->matrix[$row][$column] -= $value->getValue($row + 1, $column + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user