/src/LinearAlgebra/MatrixFactory.php
https://github.com/markrogoyski/math-php · PHP · 588 lines · 218 code · 52 blank · 318 comment · 50 complexity · 0297099926bf76af2a2b8f7648d226fb MD5 · raw file
- <?php
- namespace MathPHP\LinearAlgebra;
- use MathPHP\Exception;
- /**
- * Matrix factory to create matrices of all types.
- * Use factory instead of instantiating individual Matrix classes.
- */
- class MatrixFactory
- {
- /**
- * Factory method
- *
- * @param array[] $A 2-dimensional array of Matrix data
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- */
- public static function create(array $A): Matrix
- {
- self::checkParams($A);
- $matrix_type = self::determineMatrixType($A);
- switch ($matrix_type) {
- case 'matrix':
- return new Matrix($A);
- case 'square':
- return new SquareMatrix($A);
- case 'function':
- return new FunctionMatrix($A);
- case 'function_square':
- return new FunctionSquareMatrix($A);
- case 'object_square':
- return new ObjectSquareMatrix($A);
- }
- throw new Exception\IncorrectTypeException('Unknown matrix type');
- }
- /**
- * Factory method to create a matrix from an array of Vectors
- *
- * Example:
- * [1] [4] [7] [8]
- * X₁ = [2] X₂ = [2] X₃ = [8] X₄ = [4]
- * [1] [13] [1] [5]
- *
- * [1 4 7 8]
- * R = [2 2 8 4]
- * [1 13 1 5]
- *
- * @param Vector[] $A array of Vectors
- *
- * @return Matrix
- *
- * @throws Exception\MatrixException if the Vectors are not all the same length
- * @throws Exception\IncorrectTypeException
- * @throws Exception\BadDataException
- */
- public static function createFromVectors(array $A): Matrix
- {
- // Check that all vectors are the same length
- $m = $A[0]->getN();
- $n = count($A);
- for ($j = 1; $j < $n; $j++) {
- if ($A[$j]->getN() !== $m) {
- throw new Exception\MatrixException('Vectors being combined into matrix have different lengths');
- }
- }
- // Concatenate all the vectors
- $R = [];
- foreach ($A as $V) {
- $R[] = $V->getVector();
- }
- // Transpose to create matrix from the vector columns
- return (new Matrix($R))->transpose();
- }
- /**************************************************************************
- * SPECIAL MATRICES - Not created from an array of arrays
- * - identity
- * - exchange
- * - downshiftPermutation
- * - upshiftPermutation
- * - zero
- * - one
- * - eye
- * - diagonal
- * - hilbert
- * - vandermonde
- * - givens
- **************************************************************************/
- /**
- * Identity matrix - n x n matrix with ones in the diagonal
- *
- * Example:
- * n = 3;
- *
- * [1 0 0]
- * A = [0 1 0]
- * [0 0 1]
- *
- * @param int $n size of matrix
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException if n < 0
- */
- public static function identity(int $n): Matrix
- {
- if ($n < 0) {
- throw new Exception\OutOfBoundsException("n must be ≥ 0. n = $n");
- }
- $R = [];
- for ($i = 0; $i < $n; $i++) {
- for ($j = 0; $j < $n; $j++) {
- $R[$i][$j] = $i == $j ? 1 : 0;
- }
- }
- return self::create($R);
- }
- /**
- * Exchange matrix - n x n matrix with ones in the reverse diagonal
- * Row-reversed, or column-reversed version of the identity matrix.
- * https://en.wikipedia.org/wiki/Exchange_matrix
- *
- * Example:
- * n = 3;
- *
- * [0 0 1]
- * A = [0 1 0]
- * [1 0 0]
- *
- * @param int $n size of matrix
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException if n < 0
- */
- public static function exchange(int $n): Matrix
- {
- if ($n < 0) {
- throw new Exception\OutOfBoundsException("n must be ≥ 0. n = $n");
- }
- $R = [];
- $one = $n - 1;
- for ($i = 0; $i < $n; $i++) {
- for ($j = 0; $j < $n; $j++) {
- $R[$i][$j] = $j == $one ? 1 : 0;
- }
- $one--;
- }
- return self::create($R);
- }
- /**
- * Downshift permutation matrix
- * Pushes the components of a vector down one notch with wraparound
- *
- * [0, 0, 0, 1] [x₁] [x₄]
- * [1, 0, 0, 0] [x₂] [x₁]
- * D₄x = [0, 1, 0, 0] [x₃] = [x₂]
- * [0, 0, 1, 0] [x₄] [x₃]
- *
- * @param int $n
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException if n < 0
- */
- public static function downshiftPermutation(int $n): Matrix
- {
- $I = self::identity($n)->getMatrix();
- $bottom_row = array_pop($I);
- array_unshift($I, $bottom_row);
- return self::create($I);
- }
- /**
- * Upshift permutation matrix - Dᵀ
- * Pushes the components of a vector up one notch with wraparound
- *
- * @param int $n
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException
- */
- public static function upshiftPermutation(int $n): Matrix
- {
- return self::downshiftPermutation($n)->transpose();
- }
- /**
- * Zero matrix - m x n matrix with all elements being zeros
- *
- * Example:
- * m = 3; n = 3
- *
- * [0 0 0]
- * A = [0 0 0]
- * [0 0 0]
- *
- * @param int $m rows
- * @param int $n columns
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException if m < 1 or n < 1
- */
- public static function zero(int $m, int $n): Matrix
- {
- if ($m < 1 || $n < 1) {
- throw new Exception\OutOfBoundsException("m and n must be > 0. m = $m, n = $n");
- }
- $R = [];
- for ($i = 0; $i < $m; $i++) {
- for ($j = 0; $j < $n; $j++) {
- $R[$i][$j] = 0;
- }
- }
- return self::create($R);
- }
- /**
- * Ones matrix - m x n matrix with all elements being ones
- *
- * Example:
- * m = 3; n = 3
- *
- * [1 1 1]
- * A = [1 1 1]
- * [1 1 1]
- *
- * @param int $m rows
- * @param int $n columns
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException if m or n < 1
- */
- public static function one(int $m, int $n): Matrix
- {
- if ($m < 1 || $n < 1) {
- throw new Exception\OutOfBoundsException("m and n must be > 0. m = $m, n = $n");
- }
- $R = [];
- for ($i = 0; $i < $m; $i++) {
- for ($j = 0; $j < $n; $j++) {
- $R[$i][$j] = 1;
- }
- }
- return self::create($R);
- }
- /**
- * Eye matrix - ones on the k diagonal and zeros everywhere else.
- * Diagonal can start at any column.
- * Option to set the diagonal to any number.
- *
- * Example:
- * m = 3; n = 3; k = 1; x = 1 (3x3 matrix with 1s on the kth (1) diagonal)
- *
- * [0 1 0]
- * A = [0 0 1]
- * [0 0 0]
- *
- * @param int $m number of rows
- * @param int $n number of columns
- * @param int $k Diagonal to fill with xs
- * @param float $x (optional; default 1)
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException if m, n, or k are < 0; if k >= n
- */
- public static function eye(int $m, int $n, int $k, float $x = null): Matrix
- {
- if ($n < 0 || $m < 0 || $k < 0) {
- throw new Exception\OutOfBoundsException("m, n and k must be ≥ 0. m = $m, n = $n, k = $k");
- }
- if ($k >= $n) {
- throw new Exception\OutOfBoundsException("k must be < n. k = $k, n = $n");
- }
- $x = $x ?? 1;
- $R = (self::zero($m, $n))->getMatrix();
- for ($i = 0; $i < $m; $i++) {
- if (($k + $i) < $n) {
- $R[$i][$k + $i] = $x;
- }
- }
- return self::create($R);
- }
- /**
- * A Diagonal Matrix is constructed from a single-row array.
- * The elements of this array are placed on the diagonal of a square matrix.
- *
- * Example:
- * D = [1, 2, 3]
- *
- * [1 0 0]
- * A = [0 2 0]
- * [0 0 3]
- *
- * @param array $D elements of the diagonal
- *
- * @return DiagonalMatrix
- *
- * @throws Exception\MatrixException
- */
- public static function diagonal(array $D): DiagonalMatrix
- {
- $m = count($D);
- $A = [];
- for ($i = 0; $i < $m; $i++) {
- for ($j = 0; $j < $m; $j++) {
- if ($i == $j) {
- $A[$i][$j] = $D[$i];
- } else {
- $A[$i][$j] = 0;
- }
- }
- }
- return new DiagonalMatrix($A);
- }
- /**
- * Hilbert matrix - a square matrix with entries being the unit fractions
- * https://en.wikipedia.org/wiki/Hilbert_matrix
- *
- * 1
- * Hij = ---------
- * i + j - 1
- *
- * Example: n = 5
- *
- * [1 ½ ⅓ ¼ ⅕]
- * [½ ⅓ ¼ ⅕ ⅙]
- * H = [⅓ ¼ ⅕ ⅙ ⅐]
- * [¼ ⅕ ⅙ ⅐ ⅛]
- * [⅕ ⅙ ⅐ ⅛ ⅑]
- *
- * @param int $n
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException
- */
- public static function hilbert(int $n): Matrix
- {
- if ($n < 1) {
- throw new Exception\OutOfBoundsException("n must be > 0. m = $n");
- }
- $H = [];
- for ($i = 1; $i <= $n; $i++) {
- for ($j = 1; $j <= $n; $j++) {
- $H[$i - 1][$j - 1] = 1 / ($i + $j - 1);
- }
- }
- return self::create($H);
- }
- /**
- * Create the Vandermonde Matrix from a simple array.
- *
- * @param array $M (α₁, α₂, α₃ ⋯ αm)
- * @param int $n
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- */
- public static function vandermonde(array $M, int $n): Matrix
- {
- $A = [];
- foreach ($M as $row => $α) {
- for ($i = 0; $i < $n; $i++) {
- $A[$row][$i] = $α ** $i;
- }
- }
- return self::create($A);
- }
- /**
- * Construct a Givens rotation matrix
- *
- * [ 1 ⋯ 0 ⋯ 0 ⋯ 0 ]
- * [ ⋮ ⋱ ⋮ ⋮ ⋮ ]
- * [ 0 ⋯ c ⋯-s ⋯ 0 ]
- * G (𝒾,𝒿,θ) = [ ⋮ ⋮ ⋱ ⋮ ⋮ ]
- * [ 0 ⋯ s ⋯ c ⋯ 0 ]
- * [ ⋮ ⋮ ⋮ ⋱ ⋮ ]
- * [ 0 ⋯ 0 ⋯ 0 ⋯ 1 ]
- *
- * https://en.wikipedia.org/wiki/Givens_rotation
- *
- * @param int $m The row in G in which the top of the rotation lies
- * @param int $n The column in G in which the left of the rotation lies
- * @param float $angle The angle to use in the trigonometric functions
- * @param int $size The total number of rows in G
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- * @throws Exception\OutOfBoundsException
- */
- public static function givens(int $m, int $n, float $angle, int $size): Matrix
- {
- if ($m >= $size || $n >= $size || $m < 0 || $n < 0) {
- throw new Exception\OutOfBoundsException("m and n must be within the matrix");
- }
- $G = MatrixFactory::identity($size)->getMatrix();
- $G[$m][$m] = cos($angle);
- $G[$n][$n] = cos($angle);
- $G[$m][$n] = -1 * sin($angle);
- $G[$n][$m] = sin($angle);
- return MatrixFactory::create($G);
- }
- /**
- * Create a Matrix of random numbers
- *
- * @param int $m number of rows
- * @param int $n number of columns
- * @param int $min lower bound for the random number (optional - default: 0)
- * @param int $max upper bound for the random number (optional - default: 20)
- *
- * @return Matrix
- *
- * @throws Exception\BadDataException
- * @throws Exception\IncorrectTypeException
- * @throws Exception\MathException
- * @throws Exception\MatrixException
- */
- public static function random(int $m, int $n, int $min = 0, int $max = 20): Matrix
- {
- $A = [];
- for ($i = 0; $i < $m; $i++) {
- for ($j = 0; $j < $n; $j++) {
- $A[$i][$j] = rand($min, $max);
- }
- }
- return self::create($A);
- }
- /* ************************************************************************
- * PRIVATE HELPER METHODS
- * ***********************************************************************/
- /**
- * Check input parameters
- *
- * @param array $A
- *
- * @return bool
- *
- * @throws Exception\BadDataException if array data not provided for matrix creation
- * @throws Exception\MatrixException if any row has a different column count
- */
- private static function checkParams(array $A): bool
- {
- if (empty($A)) {
- throw new Exception\BadDataException('Array data not provided for Matrix creation');
- }
- if (isset($A[0]) && is_array($A[0])) {
- $column_count = count($A[0]);
- foreach ($A as $i => $row) {
- if (count($row) !== $column_count) {
- throw new Exception\MatrixException("Row $i has a different column count: " . count($row) . "; was expecting $column_count.");
- }
- }
- }
- return true;
- }
- /**
- * Determine what type of matrix to create
- *
- * @param array[] $A 2-dimensional array of Matrix data
- *
- * @return string indicating what matrix type to create
- */
- private static function determineMatrixType(array $A): string
- {
- $m = count($A);
- $n = count($A[0]);
- // Square Matrices have the same number of rows (m) and columns (n)
- if ($m === $n) {
- // closures are objects, so we need to separate them out.
- if (is_object($A[0][0])) {
- if ($A[0][0] instanceof \Closure) {
- return 'function_square';
- } else {
- return 'object_square';
- }
- }
- return 'square';
- }
- // Non square Matrices
- // First check to make sure it isn't something strange
- if (is_array($A[0][0])) {
- return 'unknown';
- }
- // Then check remaining matrix types
- if (is_callable($A[0][0])) {
- return 'function';
- }
- // Numeric matrix
- return 'matrix';
- }
- }