/home/mip/mip/public/img/credit/datatables/classes.tar
src/Operations.php000064400000010116151520545240010171 0ustar00<?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();
    }
}
src/Functions.php000064400000025222151520545240010022 0ustar00<?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);
    }
}
src/Complex.php000064400000026017151520545240007464 0ustar00<?php

/**
 *
 * Class for the management of Complex numbers
 *
 * @copyright  Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPComplex)
 * @license    https://opensource.org/licenses/MIT    MIT
 */
namespace Complex;

/**
 * Complex Number object.
 *
 * @package Complex
 *
 * @method float abs()
 * @method Complex acos()
 * @method Complex acosh()
 * @method Complex acot()
 * @method Complex acoth()
 * @method Complex acsc()
 * @method Complex acsch()
 * @method float argument()
 * @method Complex asec()
 * @method Complex asech()
 * @method Complex asin()
 * @method Complex asinh()
 * @method Complex atan()
 * @method Complex atanh()
 * @method Complex conjugate()
 * @method Complex cos()
 * @method Complex cosh()
 * @method Complex cot()
 * @method Complex coth()
 * @method Complex csc()
 * @method Complex csch()
 * @method Complex exp()
 * @method Complex inverse()
 * @method Complex ln()
 * @method Complex log2()
 * @method Complex log10()
 * @method Complex negative()
 * @method Complex pow(int|float $power)
 * @method float rho()
 * @method Complex sec()
 * @method Complex sech()
 * @method Complex sin()
 * @method Complex sinh()
 * @method Complex sqrt()
 * @method Complex tan()
 * @method Complex tanh()
 * @method float theta()
 * @method Complex add(...$complexValues)
 * @method Complex subtract(...$complexValues)
 * @method Complex multiply(...$complexValues)
 * @method Complex divideby(...$complexValues)
 * @method Complex divideinto(...$complexValues)
 */
class Complex
{
    /**
     * @constant    Euler's Number.
     */
    const EULER = 2.7182818284590452353602874713526624977572;

    /**
     * @constant    Regexp to split an input string into real and imaginary components and suffix
     */
    const NUMBER_SPLIT_REGEXP =
        '` ^
            (                                   # Real part
                [-+]?(\d+\.?\d*|\d*\.?\d+)          # Real value (integer or float)
                ([Ee][-+]?[0-2]?\d{1,3})?           # Optional real exponent for scientific format
            )
            (                                   # Imaginary part
                [-+]?(\d+\.?\d*|\d*\.?\d+)          # Imaginary value (integer or float)
                ([Ee][-+]?[0-2]?\d{1,3})?           # Optional imaginary exponent for scientific format
            )?
            (                                   # Imaginary part is optional
                ([-+]?)                             # Imaginary (implicit 1 or -1) only
                ([ij]?)                             # Imaginary i or j - depending on whether mathematical or engineering
            )
        $`uix';

    /**
     * @var    float    $realPart    The value of of this complex number on the real plane.
     */
    protected $realPart = 0.0;

    /**
     * @var    float    $imaginaryPart    The value of of this complex number on the imaginary plane.
     */
    protected $imaginaryPart = 0.0;

    /**
     * @var    string    $suffix    The suffix for this complex number (i or j).
     */
    protected $suffix;


    /**
     * Validates whether the argument is a valid complex number, converting scalar or array values if possible
     *
     * @param     mixed    $complexNumber   The value to parse
     * @return    array
     * @throws    Exception    If the argument isn't a Complex number or cannot be converted to one
     */
    private static function parseComplex($complexNumber)
    {
        // Test for real number, with no imaginary part
        if (is_numeric($complexNumber)) {
            return [$complexNumber, 0, null];
        }

        // Fix silly human errors
        $complexNumber = str_replace(
            ['+-', '-+', '++', '--'],
            ['-', '-', '+', '+'],
            $complexNumber
        );

        // Basic validation of string, to parse out real and imaginary parts, and any suffix
        $validComplex = preg_match(
            self::NUMBER_SPLIT_REGEXP,
            $complexNumber,
            $complexParts
        );

        if (!$validComplex) {
            // Neither real nor imaginary part, so test to see if we actually have a suffix
            $validComplex = preg_match('/^([\-\+]?)([ij])$/ui', $complexNumber, $complexParts);
            if (!$validComplex) {
                throw new Exception('Invalid complex number');
            }
            // We have a suffix, so set the real to 0, the imaginary to either 1 or -1 (as defined by the sign)
            $imaginary = 1;
            if ($complexParts[1] === '-') {
                $imaginary = 0 - $imaginary;
            }
            return [0, $imaginary, $complexParts[2]];
        }

        // If we don't have an imaginary part, identify whether it should be +1 or -1...
        if (($complexParts[4] === '') && ($complexParts[9] !== '')) {
            if ($complexParts[7] !== $complexParts[9]) {
                $complexParts[4] = 1;
                if ($complexParts[8] === '-') {
                    $complexParts[4] = -1;
                }
            } else {
                // ... or if we have only the real and no imaginary part
                //  (in which case our real should be the imaginary)
                $complexParts[4] = $complexParts[1];
                $complexParts[1] = 0;
            }
        }

        // Return real and imaginary parts and suffix as an array, and set a default suffix if user input lazily
        return [
            $complexParts[1],
            $complexParts[4],
            !empty($complexParts[9]) ? $complexParts[9] : 'i'
        ];
    }


    public function __construct($realPart = 0.0, $imaginaryPart = null, $suffix = 'i')
    {
        if ($imaginaryPart === null) {
            if (is_array($realPart)) {
                // We have an array of (potentially) real and imaginary parts, and any suffix
                list ($realPart, $imaginaryPart, $suffix) = array_values($realPart) + [0.0, 0.0, 'i'];
            } elseif ((is_string($realPart)) || (is_numeric($realPart))) {
                // We've been given a string to parse to extract the real and imaginary parts, and any suffix
                list($realPart, $imaginaryPart, $suffix) = self::parseComplex($realPart);
            }
        }

        if ($imaginaryPart != 0.0 && empty($suffix)) {
            $suffix = 'i';
        } elseif ($imaginaryPart == 0.0 && !empty($suffix)) {
            $suffix = '';
        }

        // Set parsed values in our properties
        $this->realPart = (float) $realPart;
        $this->imaginaryPart = (float) $imaginaryPart;
        $this->suffix = strtolower($suffix ?? '');
    }

    /**
     * Gets the real part of this complex number
     *
     * @return Float
     */
    public function getReal(): float
    {
        return $this->realPart;
    }

    /**
     * Gets the imaginary part of this complex number
     *
     * @return Float
     */
    public function getImaginary(): float
    {
        return $this->imaginaryPart;
    }

    /**
     * Gets the suffix of this complex number
     *
     * @return String
     */
    public function getSuffix(): string
    {
        return $this->suffix;
    }

    /**
     * Returns true if this is a real value, false if a complex value
     *
     * @return Bool
     */
    public function isReal(): bool
    {
        return $this->imaginaryPart == 0.0;
    }

    /**
     * Returns true if this is a complex value, false if a real value
     *
     * @return Bool
     */
    public function isComplex(): bool
    {
        return !$this->isReal();
    }

    public function format(): string
    {
        $str = "";
        if ($this->imaginaryPart != 0.0) {
            if (\abs($this->imaginaryPart) != 1.0) {
                $str .= $this->imaginaryPart . $this->suffix;
            } else {
                $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix;
            }
        }
        if ($this->realPart != 0.0) {
            if (($str) && ($this->imaginaryPart > 0.0)) {
                $str = "+" . $str;
            }
            $str = $this->realPart . $str;
        }
        if (!$str) {
            $str = "0.0";
        }

        return $str;
    }

    public function __toString(): string
    {
        return $this->format();
    }

    /**
     * Validates whether the argument is a valid complex number, converting scalar or array values if possible
     *
     * @param     mixed    $complex   The value to validate
     * @return    Complex
     * @throws    Exception    If the argument isn't a Complex number or cannot be converted to one
     */
    public static function validateComplexArgument($complex): Complex
    {
        if (is_scalar($complex) || is_array($complex)) {
            $complex = new Complex($complex);
        } elseif (!is_object($complex) || !($complex instanceof Complex)) {
            throw new Exception('Value is not a valid complex number');
        }

        return $complex;
    }

    /**
     * Returns the reverse of this complex number
     *
     * @return    Complex
     */
    public function reverse(): Complex
    {
        return new Complex(
            $this->imaginaryPart,
            $this->realPart,
            ($this->realPart == 0.0) ? null : $this->suffix
        );
    }

    public function invertImaginary(): Complex
    {
        return new Complex(
            $this->realPart,
            $this->imaginaryPart * -1,
            ($this->imaginaryPart == 0.0) ? null : $this->suffix
        );
    }

    public function invertReal(): Complex
    {
        return new Complex(
            $this->realPart * -1,
            $this->imaginaryPart,
            ($this->imaginaryPart == 0.0) ? null : $this->suffix
        );
    }

    protected static $functions = [
        'abs',
        'acos',
        'acosh',
        'acot',
        'acoth',
        'acsc',
        'acsch',
        'argument',
        'asec',
        'asech',
        'asin',
        'asinh',
        'atan',
        'atanh',
        'conjugate',
        'cos',
        'cosh',
        'cot',
        'coth',
        'csc',
        'csch',
        'exp',
        'inverse',
        'ln',
        'log2',
        'log10',
        'negative',
        'pow',
        'rho',
        'sec',
        'sech',
        'sin',
        'sinh',
        'sqrt',
        'tan',
        'tanh',
        'theta',
    ];

    protected static $operations = [
        'add',
        'subtract',
        'multiply',
        'divideby',
        'divideinto',
    ];

    /**
     * Returns the result of the function call or operation
     *
     * @return    Complex|float
     * @throws    Exception|\InvalidArgumentException
     */
    public function __call($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('Complex Function or Operation does not exist');
    }
}
src/Exception.php000064400000000357151520545240010012 0ustar00<?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
{
}
src/Builder.php000064400000003260151520664460007444 0ustar00<?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);
    }
}
src/Div0Exception.php000064400000000362151520664460010537 0ustar00<?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
{
}
src/Decomposition/QR.php000064400000012757151520664460011227 0ustar00<?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);
    }
}
src/Decomposition/Decomposition.php000064400000001022151520664460013500 0ustar00<?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');
        }
    }
}
src/Decomposition/LU.php000064400000015471151520664460011221 0ustar00<?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);
    }
}
src/Operators/DirectSum.php000064400000003577151520664460011746 0ustar00<?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;
    }
}
src/Operators/Multiplication.php000064400000005544151520664460013040 0ustar00<?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;
    }
}
src/Operators/Addition.php000064400000004000151520664460011560 0ustar00<?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;
    }
}
src/Operators/Subtraction.php000064400000004100151520664460012323 0ustar00<?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;
    }
}
src/Operators/Operator.php000064400000003721151520664460011631 0ustar00<?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);
    }
}
src/Operators/Division.php000064400000001755151520664460011627 0ustar00<?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');
    }
}
src/Matrix.php000064400000026450151520664460007330 0ustar00<?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');
    }
}