TimeTrex Community Edition v16.2.0

This commit is contained in:
2022-12-13 07:10:06 +01:00
commit 472f000c1b
6810 changed files with 2636142 additions and 0 deletions

View File

@ -0,0 +1,118 @@
name: main
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php-version:
- '7.1'
- '7.2'
- '7.3'
- '7.4'
- '8.0'
- '8.1'
name: PHP ${{ matrix.php-version }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: none
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Set composer flags
id: composer-lock
if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' }}
run: |
echo "::set-output name=flags::--ignore-platform-reqs"
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }}
- name: Setup problem matchers for PHP
run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Test with PHPUnit
run: ./vendor/bin/phpunit
phpcs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: none
tools: cs2pr
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Code style with PHP_CodeSniffer
run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr --graceful-warnings --colorize
coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: pcov
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Coverage
run: |
./vendor/bin/phpunit --coverage-text

215
vendor/markbaker/matrix/README.md vendored Normal file
View File

@ -0,0 +1,215 @@
PHPMatrix
==========
---
PHP Class for handling Matrices
[![Build Status](https://github.com/MarkBaker/PHPMatrix/workflows/main/badge.svg)](https://github.com/MarkBaker/PHPMatrix/actions)
[![Total Downloads](https://img.shields.io/packagist/dt/markbaker/matrix)](https://packagist.org/packages/markbaker/matrix)
[![Latest Stable Version](https://img.shields.io/github/v/release/MarkBaker/PHPMatrix)](https://packagist.org/packages/markbaker/matrix)
[![License](https://img.shields.io/github/license/MarkBaker/PHPMatrix)](https://packagist.org/packages/markbaker/matrix)
[![Matrix Transform](https://imgs.xkcd.com/comics/matrix_transform.png)](https://xkcd.com/184/)
Matrix Transform
---
This library currently provides the following operations:
- addition
- direct sum
- subtraction
- multiplication
- division (using [A].[B]<sup>-1</sup>)
- division by
- division into
together with functions for
- adjoint
- antidiagonal
- cofactors
- determinant
- diagonal
- identity
- inverse
- minors
- trace
- transpose
- solve
Given Matrices A and B, calculate X for A.X = B
and classes for
- Decomposition
- LU Decomposition with partial row pivoting,
such that [P].[A] = [L].[U] and [A] = [P]<sup>|</sup>.[L].[U]
- QR Decomposition
such that [A] = [Q].[R]
## TO DO
- power() function
- Decomposition
- Cholesky Decomposition
- EigenValue Decomposition
- EigenValues
- EigenVectors
---
# Installation
```shell
composer require markbaker/matrix:^3.0
```
# Important BC Note
If you've previously been using procedural calls to functions and operations using this library, then from version 3.0 you should use [MarkBaker/PHPMatrixFunctions](https://github.com/MarkBaker/PHPMatrixFunctions) instead (available on packagist as [markbaker/matrix-functions](https://packagist.org/packages/markbaker/matrix-functions)).
You'll need to replace `markbaker/matrix`in your `composer.json` file with the new library, but otherwise there should be no difference in the namespacing, or in the way that you have called the Matrix functions in the past, so no actual code changes are required.
```shell
composer require markbaker/matrix-functions:^1.0
```
You should not reference this library (`markbaker/matrix`) in your `composer.json`, composer wil take care of that for you.
# Usage
To create a new Matrix object, provide an array as the constructor argument
```php
$grid = [
[16, 3, 2, 13],
[ 5, 10, 11, 8],
[ 9, 6, 7, 12],
[ 4, 15, 14, 1],
];
$matrix = new Matrix\Matrix($grid);
```
The `Builder` class provides helper methods for creating specific matrices, specifically an identity matrix of a specified size; or a matrix of a specified dimensions, with every cell containing a set value.
```php
$matrix = Matrix\Builder::createFilledMatrix(1, 5, 3);
```
Will create a matrix of 5 rows and 3 columns, filled with a `1` in every cell; while
```php
$matrix = Matrix\Builder::createIdentityMatrix(3);
```
will create a 3x3 identity matrix.
Matrix objects are immutable: whenever you call a method or pass a grid to a function that returns a matrix value, a new Matrix object will be returned, and the original will remain unchanged. This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Matrix result).
## Performing Mathematical Operations
To perform mathematical operations with Matrices, you can call the appropriate method against a matrix value, passing other values as arguments
```php
$matrix1 = new Matrix\Matrix([
[2, 7, 6],
[9, 5, 1],
[4, 3, 8],
]);
$matrix2 = new Matrix\Matrix([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
var_dump($matrix1->multiply($matrix2)->toArray());
```
or pass all values to the appropriate static method
```php
$matrix1 = new Matrix\Matrix([
[2, 7, 6],
[9, 5, 1],
[4, 3, 8],
]);
$matrix2 = new Matrix\Matrix([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
var_dump(Matrix\Operations::multiply($matrix1, $matrix2)->toArray());
```
You can pass in the arguments as Matrix objects, or as arrays.
If you want to perform the same operation against multiple values (e.g. to add three or more matrices), then you can pass multiple arguments to any of the operations.
## Using functions
When calling any of the available functions for a matrix value, you can either call the relevant method for the Matrix object
```php
$grid = [
[16, 3, 2, 13],
[ 5, 10, 11, 8],
[ 9, 6, 7, 12],
[ 4, 15, 14, 1],
];
$matrix = new Matrix\Matrix($grid);
echo $matrix->trace();
```
or you can call the static method, passing the Matrix object or array as an argument
```php
$grid = [
[16, 3, 2, 13],
[ 5, 10, 11, 8],
[ 9, 6, 7, 12],
[ 4, 15, 14, 1],
];
$matrix = new Matrix\Matrix($grid);
echo Matrix\Functions::trace($matrix);
```
```php
$grid = [
[16, 3, 2, 13],
[ 5, 10, 11, 8],
[ 9, 6, 7, 12],
[ 4, 15, 14, 1],
];
echo Matrix\Functions::trace($grid);
```
## Decomposition
The library also provides classes for matrix decomposition. You can access these using
```php
$grid = [
[1, 2],
[3, 4],
];
$matrix = new Matrix\Matrix($grid);
$decomposition = new Matrix\Decomposition\QR($matrix);
$Q = $decomposition->getQ();
$R = $decomposition->getR();
```
or alternatively us the `Decomposition` factory, identifying which form of decomposition you want to use
```php
$grid = [
[1, 2],
[3, 4],
];
$matrix = new Matrix\Matrix($grid);
$decomposition = Matrix\Decomposition\Decomposition::decomposition(Matrix\Decomposition\Decomposition::QR, $matrix);
$Q = $decomposition->getQ();
$R = $decomposition->getR();
```

62
vendor/markbaker/matrix/buildPhar.php vendored Normal file
View File

@ -0,0 +1,62 @@
<?php
# required: PHP 5.3+ and zlib extension
// ini option check
if (ini_get('phar.readonly')) {
echo "php.ini: set the 'phar.readonly' option to 0 to enable phar creation\n";
exit(1);
}
// output name
$pharName = 'Matrix.phar';
// target folder
$sourceDir = __DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
// default meta information
$metaData = array(
'Author' => 'Mark Baker <mark@lange.demon.co.uk>',
'Description' => 'PHP Class for working with Matrix numbers',
'Copyright' => 'Mark Baker (c) 2013-' . date('Y'),
'Timestamp' => time(),
'Version' => '0.1.0',
'Date' => date('Y-m-d')
);
// cleanup
if (file_exists($pharName)) {
echo "Removed: {$pharName}\n";
unlink($pharName);
}
echo "Building phar file...\n";
// the phar object
$phar = new Phar($pharName, null, 'Matrix');
$phar->buildFromDirectory($sourceDir);
$phar->setStub(
<<<'EOT'
<?php
spl_autoload_register(function ($className) {
include 'phar://' . $className . '.php';
});
try {
Phar::mapPhar();
} catch (PharException $e) {
error_log($e->getMessage());
exit(1);
}
include 'phar://functions/sqrt.php';
__HALT_COMPILER();
EOT
);
$phar->setMetadata($metaData);
$phar->compressFiles(Phar::GZ);
echo "Complete.\n";
exit();

View 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);
}
}

View 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');
}
}
}

View 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);
}
}

View 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);
}
}

View 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
{
}

View 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
{
}

View 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);
}
}

View 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');
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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');
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

47
vendor/markbaker/matrix/composer.json vendored Normal file
View File

@ -0,0 +1,47 @@
{
"name": "markbaker/matrix",
"type": "library",
"description": "PHP Class for working with matrices",
"keywords": ["matrix", "vector", "mathematics"],
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"license": "MIT",
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phpmd/phpmd": "2.*",
"sebastian/phpcpd": "^4.0",
"phploc/phploc": "^4.0",
"squizlabs/php_codesniffer": "^3.4",
"phpcompatibility/php-compatibility": "^9.0",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0"
},
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"autoload-dev": {
"psr-4": {
"MatrixTest\\": "unitTests/classes/src/"
}
},
"scripts": {
"style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n",
"test": "phpunit -c phpunit.xml.dist",
"mess": "phpmd classes/src/ xml codesize,unusedcode,design,naming -n",
"lines": "phploc classes/src/ -n",
"cpd": "phpcpd classes/src/ -n",
"versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n",
"coverage": "phpunit -c phpunit.xml.dist --coverage-text --coverage-html ./build/coverage"
},
"minimum-stability": "dev"
}

View File

@ -0,0 +1,33 @@
<?php
use Matrix\Matrix;
use Matrix\Decomposition\QR;
include __DIR__ . '/../vendor/autoload.php';
$grid = [
[0, 1],
[-1, 0],
];
$targetGrid = [
[-1],
[2],
];
$matrix = new Matrix($grid);
$target = new Matrix($targetGrid);
$decomposition = new QR($matrix);
$X = $decomposition->solve($target);
echo 'X', PHP_EOL;
var_export($X->toArray());
echo PHP_EOL;
$resolve = $matrix->multiply($X);
echo 'Resolve', PHP_EOL;
var_export($resolve->toArray());
echo PHP_EOL;

View File

@ -0,0 +1,17 @@
{
"timeout": 1,
"source": {
"directories": [
"classes\/src"
]
},
"logs": {
"text": "build/infection/text.log",
"summary": "build/infection/summary.log",
"debug": "build/infection/debug.log",
"perMutator": "build/infection/perMutator.md"
},
"mutators": {
"@default": true
}
}

25
vendor/markbaker/matrix/license.md vendored Normal file
View File

@ -0,0 +1,25 @@
The MIT License (MIT)
=====================
Copyright © `2018` `Mark Baker`
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

6
vendor/markbaker/matrix/phpstan.neon vendored Normal file
View File

@ -0,0 +1,6 @@
parameters:
ignoreErrors:
- '#Property [A-Za-z\\]+::\$[A-Za-z]+ has no typehint specified#'
- '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has no return typehint specified#'
- '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has parameter \$[A-Za-z0-9]+ with no typehint specified#'
checkMissingIterableValueType: false