ml-matrix
Version:
Matrix manipulation and computation library
1,431 lines (1,315 loc) • 41.5 kB
JavaScript
'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;
}