vectorious-plus
Version:
A high performance linear algebra library.
1,246 lines (1,070 loc) • 33.4 kB
JavaScript
(function () {
'use strict';
var Vector = require('./vector');
/**
* @method constructor
* @desc Creates a `Matrix` from the supplied arguments.
**/
function Matrix (data, options) {
this.type = Matrix.defaultType;
if (options && options.type)
this.type = options.type;
this.shape = [];
if (data && data.buffer && data.buffer instanceof ArrayBuffer) {
//assign
return Matrix.fromTypedArray(data, options.shape);
} else if (data instanceof Array) {
//convert to typed array
return Matrix.fromArray(data, this.type);
} else if (data instanceof Vector) {
//copy
this.shape = options && options.shape ? options.shape : [data.length, 1];
this.data = new data.type(data.data);
this.type = data.type;
} else if (data instanceof Matrix) {
//copy
this.shape = [data.shape[0], data.shape[1]];
this.data = new data.type(data.data);
this.type = data.type;
} else if(!data && options && options.shape) {
//create empty
this.shape = options.shape;
this.data = new this.type(options.shape[0] * options.shape[1]);
}
}
/**
* Default type for data
**/
Matrix.defaultType = Float64Array;
/**
*
**/
Matrix.fromTypedArray = function (data, shape) {
if (shape[0] * shape[1] != data.length)
throw new Error("Matrix shape != array dimensions.");
var self = Object.create(Matrix.prototype);
self.shape = shape;
self.data = data;
self.type = data.constructor;
return self;
};
/**
*
**/
Matrix.fromArray = function (array, type) {
type = type || Matrix.defaultType;
var r = array.length, // number of rows
c = array[0].length, // number of columns
data = new type(r * c);
var i, j;
for (i = 0; i < r; ++i)
for (j = 0; j < c; ++j)
data[i * c + j] = array[i][j];
return Matrix.fromTypedArray(data, [r, c]);
};
/**
* Static method. Adds two matrices `a` and `b` together.
* @param {Matrix} a
* @param {Matrix} b
* @returns {Matrix} a new matrix containing the sum of `a` and `b`
**/
Matrix.add = function (a, b) {
return new Matrix(a).add(b);
};
/**
* Adds `matrix` to current matrix.
* @param {Matrix} a
* @param {Matrix} b
* @returns {Matrix} `this`
**/
Matrix.prototype.add = function (matrix) {
var r = this.shape[0], // rows in this matrix
c = this.shape[1], // columns in this matrix
d1 = this.data,
d2 = matrix.data;
if (r !== matrix.shape[0] || c !== matrix.shape[1])
throw new Error('sizes do not match!');
var i, size = r * c;
for (i = 0; i < size; i++)
d1[i] += d2[i];
return this;
};
/**
* Static method. Subtracts the matrix `b` from matrix `a`.
* @param {Matrix} a
* @param {Matrix} b
* @returns {Matrix} a new matrix containing the difference between `a` and `b`
**/
Matrix.subtract = function (a, b) {
return new Matrix(a).subtract(b);
};
/**
* Subtracts `matrix` from current matrix.
* @param {Matrix} a
* @param {Matrix} b
* @returns {Matrix} `this`
**/
Matrix.prototype.subtract = function (matrix) {
var r = this.shape[0], // rows in this matrix
c = this.shape[1], // columns in this matrix
d1 = this.data,
d2 = matrix.data;
if (r !== matrix.shape[0] || c !== matrix.shape[1])
throw new Error('sizes do not match');
var i, size = r * c;
for (i = 0; i < size; i++)
d1[i] -= d2[i];
return this;
};
/**
* Static method. Multiplies all elements of a matrix `a` with a specified `scalar`.
* @param {Matrix} a
* @param {Number} scalar
* @returns {Matrix} a new scaled matrix
**/
Matrix.scale = function (a, scalar) {
return new Matrix(a).scale(scalar);
};
/**
* Multiplies all elements of current matrix with a specified `scalar`.
* @param {Number} scalar
* @returns {Matrix} `this`
**/
Matrix.prototype.scale = function (scalar) {
var r = this.shape[0], // rows in this matrix
c = this.shape[1], // columns in this matrix
d1 = this.data,
i,
size = r * c;
for (i = 0; i < size; i++)
d1[i] *= scalar;
return this;
};
/**
* Static method. Hadamard product of matrices
* @param {Matrix} a
* @param {Matrix} b
* @returns {Matrix} a new matrix containing the hadamard product
**/
Matrix.product = function (a, b) {
return new Matrix(a).product(b);
};
/**
* Hadamard product of matrices
* @param {Matrix} a
* @param {Matrix} b
* @returns {Matrix} `this`
**/
Matrix.prototype.product = function (matrix) {
if (this.shape[0] !== matrix.shape[0] || this.shape[1] !== matrix.shape[1])
return new Error('invalid size');
var r = this.shape[0], // rows in this matrix
c = this.shape[1], // columns in this matrix
d1 = this.data,
d2 = matrix.data,
i,
size = r * c;
for (i = 0; i < size; i++)
d1[i] *= d2[i];
return this;
};
/**
* Static method. Creates an `i x j` matrix containing zeros (`0`), takes an
* optional `type` argument which should be an instance of `TypedArray`.
* @param {Number} i
* @param {Number} j
* @param {TypedArray} type
* @returns {Matrix} a matrix of the specified dimensions and `type`
**/
Matrix.zeros = function (i, j, type) {
if (!(i > 0 && j > 0))
throw new Error('invalid size');
type = type || Matrix.defaultType;
var data = new type(i * j);
var m = Matrix.fromTypedArray(data, [i, j]);
m.zeros();
return m;
};
/**
* Fills matrix with 0
*/
Matrix.prototype.zeros = function() {
var r = this.shape[0],
c = this.shape[1],
data = this.data,
k,
size = r * c;
for (k = 0; k < size; k++)
data[k] = +0.0;
return this;
};
/**
* Static method. Creates an `i x j` matrix containing ones (`1`), takes an
* optional `type` argument which should be an instance of `TypedArray`.
* @param {Number} i
* @param {Number} j
* @param {TypedArray} type
* @returns {Matrix} a matrix of the specified dimensions and `type`
**/
Matrix.ones = function (i, j, type) {
if (!(i > 0 && j > 0))
throw new Error('invalid size');
type = type || Matrix.defaultType;
var data = new type(i * j);
var m = Matrix.fromTypedArray(data, [i, j]);
m.ones();
return m;
};
/**
* Fills matrix with 1
*/
Matrix.prototype.ones = function() {
var r = this.shape[0],
c = this.shape[1],
data = this.data,
k,
size = r * c;
for (k = 0; k < size; k++)
data[k] = +1.0;
return this;
};
/**
* Static method. Creates an `i x j` identity matrix, takes an
* optional `type` argument which should be an instance of `TypedArray`.
* Alias for identity()
* @param {Number} r
* @param {Number} c
* @param {TypedArray} type
* @returns {Matrix} a matrix of the specified dimensions and `type`
**/
Matrix.eye = function (r, c, type) {
if (c === undefined)
c = r;
if (!(r > 0 && c > 0))
throw new Error('invalid size');
type = type || Matrix.defaultType;
var data = new type(r * c);
var m = Matrix.fromTypedArray(data, [r, c]);
m.eye();
return m;
};
/**
* Fills matrix with 1 by diagonal, other with 0 (identity matrix)
*/
Matrix.prototype.eye = function() {
return this.diagonal(1.);
};
/**
* Static method. Creates an `i x j` diagonal matrix with diagonal values `val`.
* Takes an optional `type` argument which should be an instance of `TypedArray`.
* @param {Number} r
* @param {Number} c
* @param {Number} val, non-0 diagonal values
* @param {TypedArray} type
* @returns {Matrix} a matrix of the specified dimensions and `type`
**/
Matrix.diagonal = function (r, c, val, type) {
if (c === undefined)
c = r;
if (!(r > 0 && c > 0))
throw new Error('invalid size');
if (val === undefined)
val = 1.;
type = type || Matrix.defaultType;
var data = new type(r * c);
var m = Matrix.fromTypedArray(data, [r, c]);
m.diagonal(val);
return m;
};
/**
* Fills matrix with `val` by diagonal, other with 0 (diagonal matrix)
*/
Matrix.prototype.diagonal = function(val) {
if (val === undefined)
val = 1.;
var r = this.shape[0],
c = this.shape[1],
data = this.data,
i, j,
size = r * c;
for (i = 0; i < r; i++)
for (j = 0; j < c; j++)
data[i * c + j] = i == j ? val : +0.0;
return this;
};
/**
* Static method. Creates an `i x j` matrix containing random values
* according to a normal distribution, takes an optional `type` argument
* which should be an instance of `TypedArray`.
* @param {Number} i
* @param {Number} j
* @param {Number} deviation (default 1)
* @param {Number} mean (default 0)
* @param {TypedArray} type
* @returns {Matrix} a matrix of the specified dimensions and `type`
**/
Matrix.random = function (i, j, deviation, mean, type) {
deviation = deviation || 1;
mean = mean || 0;
type = type || Matrix.defaultType;
var data = new type(i * j),
k,
size = i * j;
for (k = 0; k < size; k++)
data[k] = deviation * Math.random() + mean;
return Matrix.fromTypedArray(data, [i, j]);
};
/**
* Static method. Creates an `i x j` matrix containing random values
* according to a normal (Gaussian) distribution, takes an optional `type` argument
* which should be an instance of `TypedArray`.
* @param {Number} i
* @param {Number} j
* @param {Number} deviation (default 1)
* @param {Number} mean (default 0)
* @param {TypedArray} type
* @returns {Matrix} a matrix of the specified dimensions and `type`
**/
Matrix.randomNormal = function (i, j, deviation, mean, type) {
type = type || Matrix.defaultType;
var data = new type(i * j);
var m = Matrix.fromTypedArray(data, [i, j]);
m.randomNormal(deviation, mean);
return m;
};
/**
* Fills matrix with random values according to a normal (Gaussian) distribution
* https://en.wikipedia.org/wiki/Normal_distribution
* @param {Number} deviation (default 1)
* @param {Number} mean (default 0)
**/
Matrix.prototype.randomNormal = function(deviation, mean) {
this._randomNormal1(deviation, mean);
//this._randomNormal2(deviation, mean);
return this;
}
//Uses Box–Muller method
//https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
Matrix.prototype._randomNormal1 = function(deviation, mean) {
deviation = deviation || 1.0;
mean = mean || 0.0;
var size = this.shape[0] * this.shape[1];
var data = this.data;
var k = 0;
var u1, u2,
a, b0, b1, z0, z1;
do {
do {
u1 = Math.random();
} while ( u1 <= Number.EPSILON );
u2 = Math.random();
a = Math.sqrt( -2.0 * Math.log( u1 ) );
b0 = Math.cos( 2.0 * Math.PI * u2 );
b1 = Math.sin( 2.0 * Math.PI * u2 );
z0 = (a * b0) * deviation + mean;
z1 = (a * b1) * deviation + mean;
data[k] = z0;
k++;
if (k < size) {
data[k] = z1;
k++;
}
} while(k < size);
};
//Uses Marsaglia polar method
//https://en.wikipedia.org/wiki/Marsaglia_polar_method
Matrix._randomNormal2 = function(deviation, mean) {
deviation = deviation || 1.0;
mean = mean || 0.0;
var size = this.shape[0] * this.shape[1];
var data = this.data;
var k = 0;
var u, v, s,
mul, spare, z0, z1;
do {
do {
u = Math.random() * 2 - 1;
v = Math.random() * 2 - 1;
s = u * u + v * v;
} while ( s >= 1 || s == 0 );
mul = Math.sqrt( -2.0 * Math.log(s) / s );
spare = v * mul;
z0 = mean + deviation * u * mul;
z1 = mean + deviation * spare;
data[k] = z0;
k++;
if (k < size) {
data[k] = z1;
k++;
}
} while(k < size);
};
/**
* Static method. Multiplies two matrices `a` and `b` of matching dimensions.
* @param {Matrix} a, size m x n
* @param {Matrix} b, size n x k
* @param {Matrix} c optional, matrix with size m x k to hold result of product of `a` and `b`
* If not present, will be created
* @returns {Matrix} a new resultant matrix with size m x k containing the matrix product of `a` and `b`
**/
Matrix.multiply = function (a, b, c) {
return a.multiply(b, c);
};
/**
* Multiplies two matrices `a` and `b` of matching dimensions.
* this - matrix of size m x n
* @param {Matrix} matrix of size n x k
* @param {Matrix} res optional, matrix with size m x k to hold result of product of `a` and `b`
* If not present, will be created
* @returns {Matrix} resultant matrix with size m x k containing the matrix product of `a` and `b`
**/
Matrix.prototype.multiply = function (matrix, res) {
var r1 = this.shape[0], // rows in this matrix
c1 = this.shape[1], // columns in this matrix
r2 = matrix.shape[0], // rows in multiplicand
c2 = matrix.shape[1], // columns in multiplicand
d1 = this.data,
d2 = matrix.data;
if (c1 !== r2)
throw new Error('sizes do not match');
if (res === undefined)
res = Matrix.fromTypedArray(new this.type(r1 * c2), [r1, c2]);
var i, j, k,
sum;
for (i = 0; i < r1; i++) {
for (j = 0; j < c2; j++) {
sum = +0;
for (k = 0; k < c1; k++)
sum += d1[i * c1 + k] * d2[j + k * c2];
res.data[i * c2 + j] = sum;
}
}
return res;
};
/**
* Transposes a matrix (mirror across the diagonal).
* @returns {Matrix} `this`
**/
Object.defineProperty(Matrix.prototype, 'T', {
get: function() { return this.transpose(); }
});
Matrix.prototype.transpose = function () {
var r = this.shape[0],
c = this.shape[1],
i, j;
var data = new this.type(c * r);
for (i = 0; i < r; i++)
for (j = 0; j < c; j++)
data[j * r + i] = this.data[i * c + j];
return Matrix.fromTypedArray(data, [c, r]);
};
Matrix.prototype.transposed = function () {
var r = this.shape[0],
c = this.shape[1],
i, j;
var data = new this.type(c * r);
for (i = 0; i < r; i++)
for (j = 0; j < c; j++)
data[j * r + i] = this.data[i * c + j];
this.data = data;
this.shape = [c, r];
return this;
};
/**
* Determines the inverse of any invertible square matrix using
* Gaussian elimination.
* @returns {Matrix} the inverse of the matrix
**/
Matrix.prototype.inverse = function () {
var l = this.shape[0],
m = this.shape[1];
if (l !== m)
throw new Error('invalid dimensions');
var identity = Matrix.identity(l);
var augmented = Matrix.augment(this, identity);
var gauss = augmented.gauss();
var left = Matrix.zeros(l, m),
right = Matrix.zeros(l, m),
n = gauss.shape[1],
i, j;
for (i = 0; i < l; i++) {
for (j = 0; j < n; j++) {
if (j < m)
left.set(i, j, gauss.get(i, j));
else
right.set(i, j - l, gauss.get(i, j));
}
}
if (!left.equals(Matrix.identity(l)))
throw new Error('matrix is not invertible');
return right;
};
/**
* Performs Gaussian elimination on a matrix.
* @returns {Matrix} the matrix in reduced row echelon form
**/
Matrix.prototype.gauss = function () {
var l = this.shape[0],
m = this.shape[1];
var copy = new Matrix(this),
lead = 0,
pivot,
i, j, k,
leadValue;
for (i = 0; i < l; i++) {
if (m <= lead)
return new Error('matrix is singular');
j = i;
while (copy.data[j * m + lead] === 0) {
j++;
if (l === j) {
j = i;
lead++;
if (m === lead)
return new Error('matrix is singular');
}
}
copy.swap(i, j);
pivot = copy.data[i * m + lead];
if (pivot !== 0) {
// scale down the row by value of pivot
for (k = 0; k < m; k++)
copy.data[(i * m) + k] = copy.data[(i * m) + k] / pivot;
}
for (j = 0; j < l; j++) {
leadValue = copy.data[j * m + lead];
if (j !== i)
for (k = 0; k < m; k++)
copy.data[j * m + k] = copy.data[j * m + k] - (copy.data[i * m + k] * leadValue);
}
lead++;
}
for (i = 0; i < l; i++) {
pivot = 0;
for (j = 0; j < m; j++)
if (!pivot)
pivot = copy.data[i * m + j];
if (pivot)
// scale down the row by value of pivot
for (k = 0; k < m; k++)
copy.data[(i * m) + k] = copy.data[(i * m) + k] / pivot;
}
return copy;
};
/**
* Performs full LU decomposition on a matrix.
* @returns {Array} a triple (3-tuple) of the lower triangular resultant matrix `L`, the upper
* triangular resultant matrix `U` and the pivot array `ipiv`
**/
Matrix.prototype.lu = function () {
var r = this.shape[0],
c = this.shape[1],
plu = Matrix.plu(this),
ipiv = plu[1],
pivot = Matrix.identity(r),
lower = new Matrix(plu[0]),
upper = new Matrix(plu[0]),
i, j;
for (i = 0; i < r; i++)
for (j = i; j < c; j++)
lower.data[i * c + j] = i === j ? 1 : 0;
for (i = 0; i < r; i++)
for (j = 0; j < i && j < c; j++)
upper.data[i * c + j] = 0;
return [lower, upper, ipiv];
};
/**
* Static method. Performs LU factorization on current matrix.
* @returns {Array} an array with a new instance of the current matrix LU-
* factorized and the corresponding pivot Int32Array
**/
Matrix.plu = function(matrix) {
return new Matrix(matrix).plu();
};
/**
* Performs LU factorization on current matrix.
* @returns {Array} an array with the current matrix LU-factorized and the
* corresponding pivot Int32Array
**/
Matrix.prototype.plu = function () {
var data = this.data,
n = this.shape[0],
ipiv = new Int32Array(n),
max, abs, diag, p,
i, j, k;
for (k = 0; k < n; ++k) {
p = k;
max = Math.abs(data[k * n + k]);
for (j = k + 1; j < n; ++j) {
abs = Math.abs(data[j * n + k]);
if (max < abs) {
max = abs;
p = j;
}
}
ipiv[k] = p;
if (p !== k)
this.swap(k, p);
diag = data[k * n + k];
for (i = k + 1; i < n; ++i)
data[i * n + k] /= diag;
for (i = k + 1; i < n; ++i) {
for (j = k + 1; j < n - 1; ++j) {
data[i * n + j] -= data[i * n + k] * data[k * n + j];
++j;
data[i * n + j] -= data[i * n + k] * data[k * n + j];
}
if(j === n - 1)
data[i * n + j] -= data[i * n + k] * data[k * n + j];
}
}
return [this, ipiv];
};
/**
* Solves an LU factorized matrix with the supplied right hand side(s)
* @param {Matrix} rhs, right hand side(s) to solve for
* @param {Int32Array} array of pivoted row indices
* @returns {Matrix} rhs replaced by the solution
**/
Matrix.prototype.lusolve = function (rhs, ipiv) {
var lu = this.data,
n = rhs.shape[0],
nrhs = rhs.shape[1],
x = rhs.data,
i, j, k;
// pivot right hand side
for (i = 0; i < ipiv.length; i++)
if (i !== ipiv[i])
rhs.swap(i, ipiv[i]);
for (k = 0; k < nrhs; k++) {
// forward solve
for (i = 0; i < n; i++)
for (j = 0; j < i; j++)
x[i * nrhs + k] -= lu[i * n + j] * x[j * nrhs + k];
// backward solve
for (i = n - 1; i >= 0; i--) {
for (j = i + 1; j < n; j++)
x[i * nrhs + k] -= lu[i * n + j] * x[j * nrhs + k];
x[i * nrhs + k] /= lu[i * n + i];
}
}
return rhs;
};
/**
* Solves AX = B using LU factorization, where A is the current matrix and
* B is a Vector/Matrix containing the right hand side(s) of the equation.
* @param {Matrix/Vector} rhs, right hand side(s) to solve for
* @returns {Matrix} a new matrix containing the solutions of the system
**/
Matrix.prototype.solve = function (rhs) {
var plu = Matrix.plu(this),
lu = plu[0],
ipiv = plu[1];
return lu.lusolve(new Matrix(rhs), ipiv);
};
/**
* Solve A * X = B
* A - square matrix, B - this matrix/vector, X - solution with same size as B
* @param {Matrix} a, must be square and of full-rank
* @param {Matrix/Vector} b
* @param {Matrix/Vector} x solution; if passed, solution matrix/vector will be
* written to it, else will be created
* @param {bool} keepA if false, `a` will be LU-factorized, else `a` will be unmodified (requires more RAM)
* @returns {Matrix/Vector} solution
*/
Matrix.solveSquare = function (a, b, x, keepA) {
var isBVector = (b instanceof Vector);
if (keepA === undefined)
keepA = true;
var r1 = a.shape[0],
c1 = a.shape[1];
var r2 = isBVector ? b.length : b.shape[0],
c2 = isBVector ? 1 : b.shape[1];
if (c1 !== r2)
throw new Error('shapes are not aligned');
if (r1 != c1)
throw new Error('input matrix should be square');
if (x === undefined)
x = isBVector ? new Vector(b) : new Matrix(b);
var plu = keepA ? Matrix.plu(a) : a.plu(),
lu = plu[0],
ipiv = plu[1];
var rhs;
if (!isBVector)
rhs = x;
else
rhs = new Matrix(x.data, {shape: [r2, c2]});
lu.lusolve(rhs, ipiv);
return x;
};
/**
* Static method. Augments two matrices `a` and `b` of matching dimensions
* (appends `b` to `a`).
* @param {Matrix} a
* @param {Matrix} b
* @returns {Matrix} the resultant matrix of `b` augmented to `a`
**/
Matrix.augment = function (a, b) {
return new Matrix(a).augment(b);
};
/**
* Augments `matrix` with current matrix.
* @param {Matrix} matrix
* @returns {Matrix} `this`
**/
Matrix.prototype.augment = function (matrix) {
if (matrix.shape.length === 0)
return this;
var r1 = this.shape[0],
c1 = this.shape[1],
r2 = matrix.shape[0],
c2 = matrix.shape[1],
d1 = this.data,
d2 = matrix.data,
i, j;
if (r1 !== r2)
throw new Error("Rows do not match.");
var length = c1 + c2,
data = new this.type(length * r1);
for (i = 0; i < r1; i++)
for (j = 0; j < c1; j++)
data[i * length + j] = d1[i * c1 + j];
for (i = 0; i < r2; i++)
for (j = 0; j < c2; j++)
data[i * length + j + c1] = d2[i * c2 + j];
this.shape = [r1, length];
this.data = data;
return this;
};
/**
* Static method. Creates an identity matrix of `size`, takes an optional `type` argument
* which should be an instance of `TypedArray`.
* @param {Number} size
* @param {TypedArray} type
* @returns {Matrix} an identity matrix of the specified `size` and `type`
**/
Matrix.identity = function (size, type) {
if (size < 0)
throw new Error('invalid size');
type = type || Matrix.defaultType;
var matrix = Matrix.zeros(size, size, type),
i, j;
for (i = 0; i < size; i++)
matrix.data[i * size + i] = 1.0;
return matrix;
};
/**
* Static method. Creates a magic square matrix of `size`, takes an optional `type` argument
* which should be an instance of `TypedArray`.
* @param {Number} size
* @param {Number} type
* @returns {Matrix} a magic square matrix of the specified `size` and `type`
**/
Matrix.magic = function (size, type) {
if (size < 0)
throw new Error('invalid size');
function f(n, x, y) {
return (x + y * 2 + 1) % n;
}
type = type || Matrix.defaultType;
var data = new type(size * size),
i, j;
for (i = 0; i < size; i++)
for (j = 0; j < size; j++)
data[(size - i - 1) * size + (size - j - 1)] =
f(size, size - j - 1, i) * size + f(size, j, i) + 1;
return Matrix.fromTypedArray(data, [size, size]);
};
/**
* Gets the diagonal of a matrix.
* @returns {Vector} the diagonal of the matrix as a vector
**/
Matrix.prototype.diag = function () {
var r = this.shape[0],
c = this.shape[1],
data = new this.type(Math.min(r, c)),
i;
for (i = 0; i < r && i < c; i++)
data[i] = this.data[i * c + i];
return new Vector(data);
};
/**
* Gets the determinant of any square matrix using LU factorization.
* @returns {Number} the determinant of the matrix
**/
Matrix.prototype.determinant = function () {
if (this.shape[0] !== this.shape[1])
throw new Error('matrix is not square');
var plu = Matrix.plu(this),
ipiv = plu.pop(),
lu = plu.pop(),
r = this.shape[0],
c = this.shape[1],
product = 1,
sign = 1,
i;
// get sign from ipiv
for (i = 0; i < r; i++)
if (i !== ipiv[i])
sign *= -1;
for (i = 0; i < r; i++)
product *= lu.data[i * c + i];
return sign * product;
};
/**
* Gets the trace of the matrix (the sum of all diagonal elements).
* @returns {Number} the trace of the matrix
**/
Matrix.prototype.trace = function () {
var diagonal = this.diag(),
result = 0,
i, l;
for (i = 0, l = diagonal.length; i < l; i++)
result += diagonal.get(i);
return result;
};
/**
* Static method. Checks the equality of two matrices `a` and `b`.
* @param {Matrix} a
* @param {Matrix} b
* @returns {Boolean} `true` if equal, `false` otherwise
**/
Matrix.equals = function (a, b) {
return a.equals(b);
};
/**
* Checks the equality of `matrix` and current matrix.
* @param {Matrix} matrix
* @returns {Boolean} `true` if equal, `false` otherwise
**/
Matrix.prototype.equals = function (matrix) {
var r = this.shape[0],
c = this.shape[1],
d1 = this.data,
d2 = matrix.data;
if (r !== matrix.shape[0] || c !== matrix.shape[1] || this.type !== matrix.type)
return false;
var i, size = r * c;
for (i = 0; i < size; i++)
if (d1[i] !== d2[i])
return false;
return true;
};
/**
* Gets the value of the element in row `i`, column `j` of current matrix
* @param {Number} i
* @param {Number} j
* @returns {Number} the element at row `i`, column `j` of current matrix
**/
Matrix.prototype.get = function (i, j) {
if (i < 0 || j < 0 || i > this.shape[0] - 1 || j > this.shape[1] - 1)
throw new Error('index out of bounds');
return this.data[i * this.shape[1] + j];
};
/**
* Sets the element at row `i`, column `j` to value
* @param {Number} i
* @param {Number} j
* @param {Number} value
* @returns {Matrix} `this`
**/
Matrix.prototype.set = function (i, j, value) {
if (i < 0 || j < 0 || i > this.shape[0] - 1 || j > this.shape[1] - 1)
throw new Error('index out of bounds');
this.data[i * this.shape[1] + j] = value;
return this;
};
/**
* Get row of matrix
* @param {Number} r
* @param {Bool} copy
* @returns {Vector} row
**/
Matrix.prototype.row = function(r, copy) {
if (copy === undefined)
copy = true;
var rows = this.shape[0],
cols = this.shape[1];
if(!( r >= 0 && r < rows ))
throw new Error("Incorrect row");
var size1 = this.data.constructor.BYTES_PER_ELEMENT;
var data = new this.type(this.data.buffer, r * cols * size1, cols);
if (copy)
data = new this.type(data);
var v = new Vector(data, {length: cols});
return v;
}
/**
* Get col of matrix
* @param {Number} c
* @returns {Vector} col
**/
Matrix.prototype.col = function(c) {
var rows = this.shape[0],
cols = this.shape[1];
if(!( c >= 0 && c < cols ))
throw new Error("Incorrect col");
var size1 = this.data.constructor.BYTES_PER_ELEMENT;
var data = new this.type(rows);
for (var r = 0 ; r < rows ; r++)
data[r] = this.data[r * cols + c];
var v = new Vector(data, {length: rows});
return v;
}
/**
* Swaps two rows `i` and `j` in a matrix
* @param {Number} i
* @param {Number} j
* @returns {Matrix} `this`
**/
Matrix.prototype.swap = function (i, j) {
if (i < 0 || j < 0 || i > this.shape[0] - 1 || j > this.shape[0] - 1)
throw new Error('index out of bounds');
var c = this.shape[1];
// copy first row
var copy = this.data.slice(i * c, (i + 1) * c);
// move second row into first row spot
this.data.copyWithin(i * c, j * c, (j + 1) * c);
// copy first row back into second row spot
this.data.set(copy, j * c);
return this;
};
/**
* Maps a function `callback` to all elements of a copy of current matrix.
* @param {Function} callback
* @returns {Matrix} the resultant mapped matrix
**/
Matrix.prototype.map = function (callback) {
var r = this.shape[0],
c = this.shape[1],
mapped = new Matrix(this),
data = mapped.data,
i,
size = r * c;
for (i = 0; i < size; i++)
data[i] = callback.call(mapped, data[i], i / c | 0, i % c, data);
return mapped;
};
/**
* Functional version of for-looping the elements in a matrix, is
* equivalent to `Array.prototype.forEach`.
* @param {Function} callback
* @returns {Matrix} `this`
**/
Matrix.prototype.each = function (callback) {
var r = this.shape[0],
c = this.shape[1],
i,
size = r * c;
for (i = 0; i < size; i++)
callback.call(this, this.data[i], i / c | 0, i % c);
return this;
};
/**
* Equivalent to `TypedArray.prototype.reduce`.
* @param {Function} callback
* @param {Number} initialValue
* @returns {Number} result of reduction
**/
Matrix.prototype.reduce = function (callback, initialValue) {
var r = this.shape[0],
c = this.shape[1];
if (r * c === 0 && !initialValue)
throw new Error('Reduce of empty matrix with no initial value.');
var i = 0,
value = initialValue || this.data[i++],
size = r * c;
for (; i < size; i++)
value = callback.call(this, value, this.data[i], i / c | 0, i % c);
return value;
};
/**
* Finds the rank of the matrix using row echelon form
* @returns {Number} rank
**/
Matrix.prototype.rank = function () {
var vectors = this
.toArray()
.map(function(r) {
return new Vector(r);
});
var r = this.shape[0],
c = this.shape[1],
counter = 0,
i, j, tmp,
pivot, target, scalar;
for (i = 0; i < r - 1; i++) {
// go through each row until the row before the last
pivot = null;
for (j = i; j < r; j++) {
// find the pivot (first row where column of same index is non-zero)
if (vectors[i].get(i)) {
if (i !== j) {
// if not the current row, swap the rows, bring pivot the current row index
tmp = vectors[i];
vectors[i] = vectors[j];
vectors[j] = tmp;
}
pivot = vectors[i];
break;
}
}
// if pivot not found, continue
if (!pivot)
continue;
// otherwise, for all rows underneath pivot, cancel all column index to zero
for (j = (i + 1); j < r; j++) {
target = vectors[j];
scalar = target.get(i) / pivot.get(i);
vectors[j] = target.subtract(pivot.scale(scalar));
}
}
// now vectors should be in row echelon form!
// use optimized loops to count number of vectors that have non-zero values
for (i = 0; i < r; i++) {
for (j = 0; j < c; j++) {
if (vectors[i].get(j)) {
counter++;
break;
}
}
}
// should be rank
return counter;
};
Matrix.rank = function (matrix) {
return new Matrix(matrix).rank();
};
/**
* Converts current matrix into a readable formatted string
* @returns {String} a string of the matrix' contents
**/
Matrix.prototype.toString = function () {
var result = [],
r = this.shape[0],
c = this.shape[1],
i;
for (i = 0; i < r; i++)
// get string version of current row and store it
result.push('[' + this.data.subarray(i * c, (i + 1) * c ).toString() + ']');
return '[' + result.join(', \n') + ']';
};
/**
* Converts current matrix into a two-dimensional array
* @returns {Array} an array of the matrix' contents
**/
Matrix.prototype.toArray = function () {
var result = [],
r = this.shape[0],
c = this.shape[1],
i;
for (i = 0; i < r; i++)
// copy current row into a native array and store it
result.push(Array.prototype.slice.call(this.data.subarray(i * c, (i + 1) * c)));
return result;
};
module.exports = Matrix;
try {
window.Matrix = Matrix;
} catch (e) {}
}());