UNPKG

ml-matrix

Version:

Matrix manipulation and computation library

1,431 lines (1,315 loc) 41.5 kB
'use strict'; /** * Real matrix * @class Matrix * @param {number|Array|Matrix} nRows - Number of rows of the new matrix, * 2D array containing the data or Matrix instance to clone * @param {number} [nColumns] - Number of columns of the new matrix */ class Matrix extends Array { constructor(nRows, nColumns) { if (Matrix.isMatrix(nRows)) { return nRows.clone(); } else if (Number.isInteger(nRows) && nRows > 0) { // Create an empty matrix super(nRows); if (Number.isInteger(nColumns) && nColumns > 0) { for (var i = 0; i < nRows; i++) { this[i] = new Array(nColumns); } } else { throw new TypeError('nColumns must be a positive integer'); } } else if (Array.isArray(nRows)) { // Copy the values from the 2D array var matrix = nRows; nRows = matrix.length; nColumns = matrix[0].length; if (typeof nColumns !== 'number' || nColumns === 0) { throw new TypeError('Data must be a 2D array with at least one element'); } super(nRows); for (var i = 0; i < nRows; i++) { if (matrix[i].length !== nColumns) { throw new RangeError('Inconsistent array dimensions'); } this[i] = [].concat(matrix[i]); } } else { throw new TypeError('First argument must be a positive number or an array'); } this.rows = nRows; this.columns = nColumns; } // Native array methods should return instances of Array, not Matrix static get [Symbol.species]() { return Array; } /** * Constructs a Matrix with the chosen dimensions from a 1D array * @param {number} newRows - Number of rows * @param {number} newColumns - Number of columns * @param {Array} newData - A 1D array containing data for the matrix * @returns {Matrix} - The new matrix */ static from1DArray(newRows, newColumns, newData) { var length = newRows * newColumns; if (length !== newData.length) { throw new RangeError('Data length does not match given dimensions'); } var newMatrix = new Matrix(newRows, newColumns); for (var row = 0; row < newRows; row++) { for (var column = 0; column < newColumns; column++) { newMatrix[row][column] = newData[row * newColumns + column]; } } return newMatrix; } /** * Creates a row vector, a matrix with only one row. * @param {Array} newData - A 1D array containing data for the vector * @returns {Matrix} - The new matrix */ static rowVector(newData) { var vector = new Matrix(1, newData.length); for (var i = 0; i < newData.length; i++) { vector[0][i] = newData[i]; } return vector; } /** * Creates a column vector, a matrix with only one column. * @param {Array} newData - A 1D array containing data for the vector * @returns {Matrix} - The new matrix */ static columnVector(newData) { var vector = new Matrix(newData.length, 1); for (var i = 0; i < newData.length; i++) { vector[i][0] = newData[i]; } return vector; } /** * Creates an empty matrix with the given dimensions. Values will be undefined. Same as using new Matrix(rows, columns). * @param {number} rows - Number of rows * @param {number} columns - Number of columns * @returns {Matrix} - The new matrix */ static empty(rows, columns) { return new Matrix(rows, columns); } /** * Creates a matrix with the given dimensions. Values will be set to zero. * @param {number} rows - Number of rows * @param {number} columns - Number of columns * @returns {Matrix} - The new matrix */ static zeros(rows, columns) { return Matrix.empty(rows, columns).fill(0); } /** * Creates a matrix with the given dimensions. Values will be set to one. * @param {number} rows - Number of rows * @param {number} columns - Number of columns * @returns {Matrix} - The new matrix */ static ones(rows, columns) { return Matrix.empty(rows, columns).fill(1); } /** * Creates a matrix with the given dimensions. Values will be randomly set. * @param {number} rows - Number of rows * @param {number} columns - Number of columns * @param {function} [rng] - Random number generator (default: Math.random) * @returns {Matrix} The new matrix */ static rand(rows, columns, rng) { if (rng === undefined) rng = Math.random; var matrix = Matrix.empty(rows, columns); for (var i = 0; i < rows; i++) { for (var j = 0; j < columns; j++) { matrix[i][j] = rng(); } } return matrix; } /** * Creates an identity matrix with the given dimension. Values of the diagonal will be 1 and others will be 0. * @param {number} rows - Number of rows * @param {number} [columns] - Number of columns (Default: rows) * @returns {Matrix} - The new identity matrix */ static eye(rows, columns) { if (columns === undefined) columns = rows; var min = Math.min(rows, columns); var matrix = Matrix.zeros(rows, columns); for (var i = 0; i < min; i++) { matrix[i][i] = 1; } return matrix; } /** * Creates a diagonal matrix based on the given array. * @param {Array} data - Array containing the data for the diagonal * @param {number} [rows] - Number of rows (Default: data.length) * @param {number} [columns] - Number of columns (Default: rows) * @returns {Matrix} - The new diagonal matrix */ static diag(data, rows, columns) { var l = data.length; if (rows === undefined) rows = l; if (columns === undefined) columns = rows; var min = Math.min(l, rows, columns); var matrix = Matrix.zeros(rows, columns); for (var i = 0; i < min; i++) { matrix[i][i] = data[i]; } return matrix; } /** * Returns a matrix whose elements are the minimum between matrix1 and matrix2 * @param matrix1 * @param matrix2 * @returns {Matrix} */ static min(matrix1, matrix2) { var rows = matrix1.length; var columns = matrix1[0].length; var result = new Matrix(rows, columns); for (var i = 0; i < rows; i++) { for(var j = 0; j < columns; j++) { result[i][j] = Math.min(matrix1[i][j], matrix2[i][j]); } } return result; } /** * Returns a matrix whose elements are the maximum between matrix1 and matrix2 * @param matrix1 * @param matrix2 * @returns {Matrix} */ static max(matrix1, matrix2) { var rows = matrix1.length; var columns = matrix1[0].length; var result = new Matrix(rows, columns); for (var i = 0; i < rows; i++) { for(var j = 0; j < columns; j++) { result[i][j] = Math.max(matrix1[i][j], matrix2[i][j]); } } return result; } /** * Check that the provided value is a Matrix and tries to instantiate one if not * @param value - The value to check * @returns {Matrix} */ static checkMatrix(value) { return Matrix.isMatrix(value) ? value : new Matrix(value); } /** * Returns true if the argument is a Matrix, false otherwise * @param value - The value to check * @return {boolean} */ static isMatrix(value) { return (value != null) && (value.klass === 'Matrix'); } /** * @property {number} - The number of elements in the matrix. */ get size() { return this.rows * this.columns; } /** * Applies a callback for each element of the matrix. The function is called in the matrix (this) context. * @param {function} callback - Function that will be called with two parameters : i (row) and j (column) * @returns {Matrix} this */ apply(callback) { if (typeof callback !== 'function') { throw new TypeError('callback must be a function'); } var ii = this.rows; var jj = this.columns; for (var i = 0; i < ii; i++) { for (var j = 0; j < jj; j++) { callback.call(this, i, j); } } return this; } /** * Creates an exact and independent copy of the matrix * @returns {Matrix} */ clone() { var newMatrix = new Matrix(this.rows, this.columns); for (var row = 0; row < this.rows; row++) { for (var column = 0; column < this.columns; column++) { newMatrix[row][column] = this[row][column]; } } return newMatrix; } /** * Returns a new 1D array filled row by row with the matrix values * @returns {Array} */ to1DArray() { var array = new Array(this.size); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { array[i * this.columns + j] = this[i][j]; } } return array; } /** * Returns a 2D array containing a copy of the data * @returns {Array} */ to2DArray() { var copy = new Array(this.rows); for (var i = 0; i < this.rows; i++) { copy[i] = [].concat(this[i]); } return copy; } /** * @returns {boolean} true if the matrix has one row */ isRowVector() { return this.rows === 1; } /** * @returns {boolean} true if the matrix has one column */ isColumnVector() { return this.columns === 1; } /** * @returns {boolean} true if the matrix has one row or one column */ isVector() { return (this.rows === 1) || (this.columns === 1); } /** * @returns {boolean} true if the matrix has the same number of rows and columns */ isSquare() { return this.rows === this.columns; } /** * @returns {boolean} true if the matrix is square and has the same values on both sides of the diagonal */ isSymmetric() { if (this.isSquare()) { for (var i = 0; i < this.rows; i++) { for (var j = 0; j <= i; j++) { if (this[i][j] !== this[j][i]) { return false; } } } return true; } return false; } /** * Sets a given element of the matrix. mat.set(3,4,1) is equivalent to mat[3][4]=1 * @param {number} rowIndex - Index of the row * @param {number} columnIndex - Index of the column * @param {number} value - The new value for the element * @returns {Matrix} this */ set(rowIndex, columnIndex, value) { this[rowIndex][columnIndex] = value; return this; } /** * Returns the given element of the matrix. mat.get(3,4) is equivalent to matrix[3][4] * @param {number} rowIndex - Index of the row * @param {number} columnIndex - Index of the column * @returns {number} */ get(rowIndex, columnIndex) { return this[rowIndex][columnIndex]; } /** * Fills the matrix with a given value. All elements will be set to this value. * @param {number} value - New value * @returns {Matrix} this */ fill(value) { for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] = value; } } return this; } /** * Negates the matrix. All elements will be multiplied by (-1) * @returns {Matrix} this */ neg() { return this.mulS(-1); } /** * Returns a new array from the given row index * @param {number} index - Row index * @returns {Array} */ getRow(index) { checkRowIndex(this, index); return [].concat(this[index]); } /** * Returns a new row vector from the given row index * @param {number} index - Row index * @returns {Matrix} */ getRowVector(index) { return Matrix.rowVector(this.getRow(index)); } /** * Sets a row at the given index * @param {number} index - Row index * @param {Array|Matrix} array - Array or vector * @returns {Matrix} this */ setRow(index, array) { checkRowIndex(this, index); array = checkRowVector(this, array, true); this[index] = array; return this; } /** * Removes a row from the given index * @param {number} index - Row index * @returns {Matrix} this */ removeRow(index) { checkRowIndex(this, index); if (this.rows === 1) throw new RangeError('A matrix cannot have less than one row'); this.splice(index, 1); this.rows -= 1; return this; } /** * Adds a row at the given index * @param {number} [index = this.rows] - Row index * @param {Array|Matrix} array - Array or vector * @returns {Matrix} this */ addRow(index, array) { if (array === undefined) { array = index; index = this.rows; } checkRowIndex(this, index, true); array = checkRowVector(this, array, true); this.splice(index, 0, array); this.rows += 1; return this; } /** * Swaps two rows * @param {number} row1 - First row index * @param {number} row2 - Second row index * @returns {Matrix} this */ swapRows(row1, row2) { checkRowIndex(this, row1); checkRowIndex(this, row2); var temp = this[row1]; this[row1] = this[row2]; this[row2] = temp; return this; } /** * Returns a new array from the given column index * @param {number} index - Column index * @returns {Array} */ getColumn(index) { checkColumnIndex(this, index); var column = new Array(this.rows); for (var i = 0; i < this.rows; i++) { column[i] = this[i][index]; } return column; } /** * Returns a new column vector from the given column index * @param {number} index - Column index * @returns {Matrix} */ getColumnVector(index) { return Matrix.columnVector(this.getColumn(index)); } /** * Sets a column at the given index * @param {number} index - Column index * @param {Array|Matrix} array - Array or vector * @returns {Matrix} this */ setColumn(index, array) { checkColumnIndex(this, index); array = checkColumnVector(this, array); for (var i = 0; i < this.rows; i++) { this[i][index] = array[i]; } return this; } /** * Removes a column from the given index * @param {number} index - Column index * @returns {Matrix} this */ removeColumn(index) { checkColumnIndex(this, index); if (this.columns === 1) throw new RangeError('A matrix cannot have less than one column'); for (var i = 0; i < this.rows; i++) { this[i].splice(index, 1); } this.columns -= 1; return this; } /** * Adds a column at the given index * @param {number} [index = this.columns] - Column index * @param {Array|Matrix} array - Array or vector * @returns {Matrix} this */ addColumn(index, array) { if (typeof array === 'undefined') { array = index; index = this.columns; } checkColumnIndex(this, index, true); array = checkColumnVector(this, array); for (var i = 0; i < this.rows; i++) { this[i].splice(index, 0, array[i]); } this.columns += 1; return this; } /** * Swaps two columns * @param {number} column1 - First column index * @param {number} column2 - Second column index * @returns {Matrix} this */ swapColumns(column1, column2) { checkColumnIndex(this, column1); checkColumnIndex(this, column2); var temp, row; for (var i = 0; i < this.rows; i++) { row = this[i]; temp = row[column1]; row[column1] = row[column2]; row[column2] = temp; } return this; } /** * Adds the values of a vector to each row * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ addRowVector(vector) { vector = checkRowVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] += vector[j]; } } return this; } /** * Subtracts the values of a vector from each row * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ subRowVector(vector) { vector = checkRowVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] -= vector[j]; } } return this; } /** * Multiplies the values of a vector with each row * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ mulRowVector(vector) { vector = checkRowVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] *= vector[j]; } } return this; } /** * Divides the values of each row by those of a vector * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ divRowVector(vector) { vector = checkRowVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] /= vector[j]; } } return this; } /** * Adds the values of a vector to each column * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ addColumnVector(vector) { vector = checkColumnVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] += vector[i]; } } return this; } /** * Subtracts the values of a vector from each column * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ subColumnVector(vector) { vector = checkColumnVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] -= vector[i]; } } return this; } /** * Multiplies the values of a vector with each column * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ mulColumnVector(vector) { vector = checkColumnVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] *= vector[i]; } } return this; } /** * Divides the values of each column by those of a vector * @param {Array|Matrix} vector - Array or vector * @returns {Matrix} this */ divColumnVector(vector) { vector = checkColumnVector(this, vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] /= vector[i]; } } return this; } /** * Multiplies the values of a row with a scalar * @param {number} index - Row index * @param {number} value * @returns {Matrix} this */ mulRow(index, value) { checkRowIndex(this, index); for (var i = 0; i < this.columns; i++) { this[index][i] *= value; } return this; } /** * Multiplies the values of a column with a scalar * @param {number} index - Column index * @param {number} value * @returns {Matrix} this */ mulColumn(index, value) { checkColumnIndex(this, index); for (var i = 0; i < this.rows; i++) { this[i][index] *= value; } } /** * Returns the maximum value of the matrix * @returns {number} */ max() { var v = this[0][0]; for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { if (this[i][j] > v) { v = this[i][j]; } } } return v; } /** * Returns the index of the maximum value * @returns {Array} */ maxIndex() { var v = this[0][0]; var idx = [0, 0]; for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { if (this[i][j] > v) { v = this[i][j]; idx[0] = i; idx[1] = j; } } } return idx; } /** * Returns the minimum value of the matrix * @returns {number} */ min() { var v = this[0][0]; for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { if (this[i][j] < v) { v = this[i][j]; } } } return v; } /** * Returns the index of the minimum value * @returns {Array} */ minIndex() { var v = this[0][0]; var idx = [0, 0]; for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { if (this[i][j] < v) { v = this[i][j]; idx[0] = i; idx[1] = j; } } } return idx; } /** * Returns the maximum value of one row * @param {number} row - Row index * @returns {number} */ maxRow(row) { checkRowIndex(this, row); var v = this[row][0]; for (var i = 1; i < this.columns; i++) { if (this[row][i] > v) { v = this[row][i]; } } return v; } /** * Returns the index of the maximum value of one row * @param {number} row - Row index * @returns {Array} */ maxRowIndex(row) { checkRowIndex(this, row); var v = this[row][0]; var idx = [row, 0]; for (var i = 1; i < this.columns; i++) { if (this[row][i] > v) { v = this[row][i]; idx[1] = i; } } return idx; } /** * Returns the minimum value of one row * @param {number} row - Row index * @returns {number} */ minRow(row) { checkRowIndex(this, row); var v = this[row][0]; for (var i = 1; i < this.columns; i++) { if (this[row][i] < v) { v = this[row][i]; } } return v; } /** * Returns the index of the maximum value of one row * @param {number} row - Row index * @returns {Array} */ minRowIndex(row) { checkRowIndex(this, row); var v = this[row][0]; var idx = [row, 0]; for (var i = 1; i < this.columns; i++) { if (this[row][i] < v) { v = this[row][i]; idx[1] = i; } } return idx; } /** * Returns the maximum value of one column * @param {number} column - Column index * @returns {number} */ maxColumn(column) { checkColumnIndex(this, column); var v = this[0][column]; for (var i = 1; i < this.rows; i++) { if (this[i][column] > v) { v = this[i][column]; } } return v; } /** * Returns the index of the maximum value of one column * @param {number} column - Column index * @returns {Array} */ maxColumnIndex(column) { checkColumnIndex(this, column); var v = this[0][column]; var idx = [0, column]; for (var i = 1; i < this.rows; i++) { if (this[i][column] > v) { v = this[i][column]; idx[0] = i; } } return idx; } /** * Returns the minimum value of one column * @param {number} column - Column index * @returns {number} */ minColumn(column) { checkColumnIndex(this, column); var v = this[0][column]; for (var i = 1; i < this.rows; i++) { if (this[i][column] < v) { v = this[i][column]; } } return v; } /** * Returns the index of the minimum value of one column * @param {number} column - Column index * @returns {Array} */ minColumnIndex(column) { checkColumnIndex(this, column); var v = this[0][column]; var idx = [0, column]; for (var i = 1; i < this.rows; i++) { if (this[i][column] < v) { v = this[i][column]; idx[0] = i; } } return idx; } /** * Returns an array containing the diagonal values of the matrix * @returns {Array} */ diag() { var min = Math.min(this.rows, this.columns); var diag = new Array(min); for (var i = 0; i < min; i++) { diag[i] = this[i][i]; } return diag; } /** * Returns the sum of all elements of the matrix * @returns {number} */ sum() { var v = 0; for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { v += this[i][j]; } } return v; } /** * Returns the mean of all elements of the matrix * @returns {number} */ mean() { return this.sum() / this.size; } /** * Returns the product of all elements of the matrix * @returns {number} */ prod() { var prod = 1; for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { prod *= this[i][j]; } } return prod; } /** * Computes the cumulative sum of the matrix elements (in place, row by row) * @returns {Matrix} this */ cumulativeSum() { var sum = 0; for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { sum += this[i][j]; this[i][j] = sum; } } return this; } /** * Computes the dot (scalar) product between the matrix and another * @param {Matrix} vector2 vector * @returns {number} */ dot(vector2) { if (Matrix.isMatrix(vector2)) vector2 = vector2.to1DArray(); var vector1 = this.to1DArray(); if (vector1.length !== vector2.length) { throw new RangeError('vectors do not have the same size'); } var dot = 0; for (var i = 0; i < vector1.length; i++) { dot += vector1[i] * vector2[i]; } return dot; } /** * Returns the matrix product between this and other * @param {Matrix} other * @returns {Matrix} */ mmul(other) { other = Matrix.checkMatrix(other); if (this.columns !== other.rows) console.warn('Number of columns of left matrix are not equal to number of rows of right matrix.'); var m = this.rows; var n = this.columns; var p = other.columns; var result = new Matrix(m, p); var Bcolj = new Array(n); for (var j = 0; j < p; j++) { for (var k = 0; k < n; k++) Bcolj[k] = other[k][j]; for (var i = 0; i < m; i++) { var Arowi = this[i]; var s = 0; for (k = 0; k < n; k++) s += Arowi[k] * Bcolj[k]; result[i][j] = s; } } return result; } /** * Returns the Kronecker product (also known as tensor product) between this and other * See https://en.wikipedia.org/wiki/Kronecker_product * @param {Matrix} other * @return {Matrix} */ kroneckerProduct(other) { other = Matrix.checkMatrix(other); var m = this.rows; var n = this.columns; var p = other.rows; var q = other.columns; var result = new Matrix(m * p, n * q); for (var i = 0; i < m; i++) { for (var j = 0; j < n; j++) { for (var k = 0; k < p; k++) { for (var l = 0; l < q; l++) { result[p * i + k][q * j + l] = this[i][j] * other[k][l]; } } } } return result; } /** * Transposes the matrix and returns a new one containing the result * @returns {Matrix} */ transpose() { var result = new Matrix(this.columns, this.rows); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { result[j][i] = this[i][j]; } } return result; } /** * Sorts the rows (in place) * @param {function} compareFunction - usual Array.prototype.sort comparison function * @returns {Matrix} this */ sortRows(compareFunction) { if (compareFunction === undefined) compareFunction = compareNumbers; for (var i = 0; i < this.rows; i++) { this[i].sort(compareFunction); } return this; } /** * Sorts the columns (in place) * @param {function} compareFunction - usual Array.prototype.sort comparison function * @returns {Matrix} this */ sortColumns(compareFunction) { if (compareFunction === undefined) compareFunction = compareNumbers; for (var i = 0; i < this.columns; i++) { this.setColumn(i, this.getColumn(i).sort(compareFunction)); } return this; } /** * Returns a subset of the matrix * @param {number} startRow - First row index * @param {number} endRow - Last row index * @param {number} startColumn - First column index * @param {number} endColumn - Last column index * @returns {Matrix} */ subMatrix(startRow, endRow, startColumn, endColumn) { if ((startRow > endRow) || (startColumn > endColumn) || (startRow < 0) || (startRow >= this.rows) || (endRow < 0) || (endRow >= this.rows) || (startColumn < 0) || (startColumn >= this.columns) || (endColumn < 0) || (endColumn >= this.columns)) { throw new RangeError('Argument out of range'); } var newMatrix = new Matrix(endRow - startRow + 1, endColumn - startColumn + 1); for (var i = startRow; i <= endRow; i++) { for (var j = startColumn; j <= endColumn; j++) { newMatrix[i - startRow][j - startColumn] = this[i][j]; } } return newMatrix; } /** * Returns a subset of the matrix based on an array of row indices * @param {Array} indices - Array containing the row indices * @param {number} [startColumn = 0] - First column index * @param {number} [endColumn = this.columns-1] - Last column index * @returns {Matrix} */ subMatrixRow(indices, startColumn, endColumn) { if (startColumn === undefined) startColumn = 0; if (endColumn === undefined) endColumn = this.columns - 1; if ((startColumn > endColumn) || (startColumn < 0) || (startColumn >= this.columns) || (endColumn < 0) || (endColumn >= this.columns)) { throw new RangeError('Argument out of range'); } var newMatrix = new Matrix(indices.length, endColumn - startColumn + 1); for (var i = 0; i < indices.length; i++) { for (var j = startColumn; j <= endColumn; j++) { if (indices[i] < 0 || indices[i] >= this.rows) { throw new RangeError('Row index out of range: ' + indices[i]); } newMatrix[i][j - startColumn] = this[indices[i]][j]; } } return newMatrix; } /** * Returns a subset of the matrix based on an array of column indices * @param {Array} indices - Array containing the column indices * @param {number} [startRow = 0] - First row index * @param {number} [endRow = this.rows-1] - Last row index * @returns {Matrix} */ subMatrixColumn(indices, startRow, endRow) { if (startRow === undefined) startRow = 0; if (endRow === undefined) endRow = this.rows - 1; if ((startRow > endRow) || (startRow < 0) || (startRow >= this.rows) || (endRow < 0) || (endRow >= this.rows)) { throw new RangeError('Argument out of range'); } var newMatrix = new Matrix(endRow - startRow + 1, indices.length); for (var i = 0; i < indices.length; i++) { for (var j = startRow; j <= endRow; j++) { if (indices[i] < 0 || indices[i] >= this.columns) { throw new RangeError('Column index out of range: ' + indices[i]); } newMatrix[j - startRow][i] = this[j][indices[i]]; } } return newMatrix; } /** * Returns the trace of the matrix (sum of the diagonal elements) * @returns {number} */ trace() { var min = Math.min(this.rows, this.columns); var trace = 0; for (var i = 0; i < min; i++) { trace += this[i][i]; } return trace; } } Matrix.prototype.klass = 'Matrix'; module.exports = Matrix; /** * @private * Check that a row index is not out of bounds * @param {Matrix} matrix * @param {number} index * @param {boolean} [outer] */ function checkRowIndex(matrix, index, outer) { var max = outer ? matrix.rows : matrix.rows - 1; if (index < 0 || index > max) throw new RangeError('Row index out of range'); } /** * @private * Check that the provided vector is an array with the right length * @param {Matrix} matrix * @param {Array|Matrix} vector * @param {boolean} copy * @returns {Array} * @throws {RangeError} */ function checkRowVector(matrix, vector, copy) { if (Matrix.isMatrix(vector)) { vector = vector.to1DArray(); } else if (copy) { vector = [].concat(vector); } if (vector.length !== matrix.columns) throw new RangeError('vector size must be the same as the number of columns'); return vector; } /** * @private * Check that the provided vector is an array with the right length * @param {Matrix} matrix * @param {Array|Matrix} vector * @param {boolean} copy * @returns {Array} * @throws {RangeError} */ function checkColumnVector(matrix, vector, copy) { if (Matrix.isMatrix(vector)) { vector = vector.to1DArray(); } else if (copy) { vector = [].concat(vector); } if (vector.length !== matrix.rows) throw new RangeError('vector size must be the same as the number of rows'); return vector; } /** * @private * Check that a column index is not out of bounds * @param {Matrix} matrix * @param {number} index * @param {boolean} [outer] */ function checkColumnIndex(matrix, index, outer) { var max = outer ? matrix.columns : matrix.columns - 1; if (index < 0 || index > max) throw new RangeError('Column index out of range'); } /** * @private * Check that two matrices have the same dimensions * @param {Matrix} matrix * @param {Matrix} otherMatrix */ function checkDimensions(matrix, otherMatrix) { if (matrix.rows !== otherMatrix.length || matrix.columns !== otherMatrix[0].length) { throw new RangeError('Matrices dimensions must be equal'); } } function compareNumbers(a, b) { return a - b; } /* Synonyms */ Matrix.random = Matrix.rand; Matrix.diagonal = Matrix.diag; Matrix.prototype.diagonal = Matrix.prototype.diag; Matrix.identity = Matrix.eye; Matrix.prototype.negate = Matrix.prototype.neg; Matrix.prototype.tensorProduct = Matrix.prototype.kroneckerProduct; /* Add dynamically instance and static methods for mathematical operations */ var inplaceOperator = ` (function %name%(value) { if (typeof value === 'number') return this.%name%S(value); return this.%name%M(value); }) `; var inplaceOperatorScalar = ` (function %name%S(value) { for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] = this[i][j] %op% value; } } return this; }) `; var inplaceOperatorMatrix = ` (function %name%M(matrix) { checkDimensions(this, matrix); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] = this[i][j] %op% matrix[i][j]; } } return this; }) `; var staticOperator = ` (function %name%(matrix, value) { var newMatrix = new Matrix(matrix); return newMatrix.%name%(value); }) `; var inplaceMethod = ` (function %name%() { for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] = %method%(this[i][j]); } } return this; }) `; var staticMethod = ` (function %name%(matrix) { var newMatrix = new Matrix(matrix); return newMatrix.%name%(); }) `; var inplaceMethodWithArgs = ` (function %name%(%args%) { for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this[i][j] = %method%(this[i][j], %args%); } } return this; }) `; var staticMethodWithArgs = ` (function %name%(matrix, %args%) { var newMatrix = new Matrix(matrix); return newMatrix.%name%(%args%); }) `; var operators = [ // Arithmetic operators ['+', 'add'], ['-', 'sub', 'subtract'], ['*', 'mul', 'multiply'], ['/', 'div', 'divide'], ['%', 'mod', 'modulus'], // Bitwise operators ['&', 'and'], ['|', 'or'], ['^', 'xor'], ['<<', 'leftShift'], ['>>', 'signPropagatingRightShift'], ['>>>', 'rightShift', 'zeroFillRightShift'] ]; for (var operator of operators) { var inplaceOp = eval(fillTemplateFunction(inplaceOperator, {name: operator[1], op: operator[0]})); var inplaceOpS = eval(fillTemplateFunction(inplaceOperatorScalar, {name: operator[1] + 'S', op: operator[0]})); var inplaceOpM = eval(fillTemplateFunction(inplaceOperatorMatrix, {name: operator[1] + 'M', op: operator[0]})); var staticOp = eval(fillTemplateFunction(staticOperator, {name: operator[1]})); for (var i = 1; i < operator.length; i++) { Matrix.prototype[operator[i]] = inplaceOp; Matrix.prototype[operator[i] + 'S'] = inplaceOpS; Matrix.prototype[operator[i] + 'M'] = inplaceOpM; Matrix[operator[i]] = staticOp; } } var methods = [ ['~', 'not'] ]; [ 'abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'cbrt', 'ceil', 'clz32', 'cos', 'cosh', 'exp', 'expm1', 'floor', 'fround', 'log', 'log1p', 'log10', 'log2', 'round', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc' ].forEach(function (mathMethod) { methods.push(['Math.' + mathMethod, mathMethod]); }); for (var method of methods) { var inplaceMeth = eval(fillTemplateFunction(inplaceMethod, {name: method[1], method: method[0]})); var staticMeth = eval(fillTemplateFunction(staticMethod, {name: method[1]})); for (var i = 1; i < method.length; i++) { Matrix.prototype[method[i]] = inplaceMeth; Matrix[method[i]] = staticMeth; } } var methodsWithArgs = [ ['Math.pow', 1, 'pow'] ]; for (var methodWithArg of methodsWithArgs) { var args = 'arg0'; for (var i = 1; i < methodWithArg[1]; i++) { args += `, arg${i}`; } var inplaceMethWithArgs = eval(fillTemplateFunction(inplaceMethodWithArgs, {name: methodWithArg[2], method: methodWithArg[0], args: args})); var staticMethWithArgs = eval(fillTemplateFunction(staticMethodWithArgs, {name: methodWithArg[2], args: args})); for (var i = 2; i < methodWithArg.length; i++) { Matrix.prototype[methodWithArg[i]] = inplaceMethWithArgs; Matrix[methodWithArg[i]] = staticMethWithArgs; } } function fillTemplateFunction(template, values) { for (var i in values) { template = template.replace(new RegExp('%' + i + '%', 'g'), values[i]); } return template; }