(function() { 'use strict'; self.webgazer = self.webgazer || {}; self.webgazer.mat = self.webgazer.mat || {}; /** * Transposes an mxn array * @param {Array.>} matrix - of 'M x N' dimensionality * @return {Array.>} transposed matrix */ self.webgazer.mat.transpose = function(matrix){ var m = matrix.length; var n = matrix[0].length; var transposedMatrix = new Array(n); for (var i = 0; i < m; i++){ for (var j = 0; j < n; j++){ if (i === 0) transposedMatrix[j] = new Array(m); transposedMatrix[j][i] = matrix[i][j]; } } return transposedMatrix; }; /** * Get a sub-matrix of matrix * @param {Array.>} matrix - original matrix * @param {Array.} r - Array of row indices * @param {Number} j0 - Initial column index * @param {Number} j1 - Final column index * @returns {Array} The sub-matrix matrix(r(:),j0:j1) */ self.webgazer.mat.getMatrix = function(matrix, r, j0, j1){ var X = new Array(r.length), m = j1-j0+1; for (var i = 0; i < r.length; i++){ X[i] = new Array(m); for (var j = j0; j <= j1; j++){ X[i][j-j0] = matrix[r[i]][j]; } } return X; }; /** * Get a submatrix of matrix * @param {Array.>} matrix - original matrix * @param {Number} i0 - Initial row index * @param {Number} i1 - Final row index * @param {Number} j0 - Initial column index * @param {Number} j1 - Final column index * @return {Array} The sub-matrix matrix(i0:i1,j0:j1) */ self.webgazer.mat.getSubMatrix = function(matrix, i0, i1, j0, j1){ var size = j1 - j0 + 1, X = new Array(i1-i0+1); for (var i = i0; i <= i1; i++){ var subI = i-i0; X[subI] = new Array(size); for (var j = j0; j <= j1; j++){ X[subI][j-j0] = matrix[i][j]; } } return X; }; /** * Linear algebraic matrix multiplication, matrix1 * matrix2 * @param {Array.>} matrix1 * @param {Array.>} matrix2 * @return {Array.>} Matrix product, matrix1 * matrix2 */ self.webgazer.mat.mult = function(matrix1, matrix2){ if (matrix2.length != matrix1[0].length){ console.log('Matrix inner dimensions must agree:'); } var X = new Array(matrix1.length), Bcolj = new Array(matrix1[0].length); for (var j = 0; j < matrix2[0].length; j++){ for (var k = 0; k < matrix1[0].length; k++){ Bcolj[k] = matrix2[k][j]; } for (var i = 0; i < matrix1.length; i++){ if (j === 0) X[i] = new Array(matrix2[0].length); var Arowi = matrix1[i]; var s = 0; for (var k = 0; k < matrix1[0].length; k++){ s += Arowi[k]*Bcolj[k]; } X[i][j] = s; } } return X; }; /** * LUDecomposition to solve A*X = B, based on WEKA code * @param {Array.>} A - left matrix of equation to be solved * @param {Array.>} B - right matrix of equation to be solved * @return {Array.>} X so that L*U*X = B(piv,:) */ self.webgazer.mat.LUDecomposition = function(A,B){ var LU = new Array(A.length); for (var i = 0; i < A.length; i++){ LU[i] = new Array(A[0].length); for (var j = 0; j < A[0].length; j++){ LU[i][j] = A[i][j]; } } var m = A.length; var n = A[0].length; var piv = new Array(m); for (var i = 0; i < m; i++){ piv[i] = i; } var pivsign = 1; var LUrowi = new Array(); var LUcolj = new Array(m); // Outer loop. for (var j = 0; j < n; j++){ // Make a copy of the j-th column to localize references. for (var i = 0; i < m; i++){ LUcolj[i] = LU[i][j]; } // Apply previous transformations. for (var i = 0; i < m; i++){ LUrowi = LU[i]; // Most of the time is spent in the following dot product. var kmax = Math.min(i,j); var s = 0; for (var k = 0; k < kmax; k++){ s += LUrowi[k]*LUcolj[k]; } LUrowi[j] = LUcolj[i] -= s; } // Find pivot and exchange if necessary. var p = j; for (var i = j+1; i < m; i++){ if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])){ p = i; } } if (p != j){ for (var k = 0; k < n; k++){ var t = LU[p][k]; LU[p][k] = LU[j][k]; LU[j][k] = t; } var k = piv[p]; piv[p] = piv[j]; piv[j] = k; pivsign = -pivsign; } // Compute multipliers. if (j < m & LU[j][j] != 0){ for (var i = j+1; i < m; i++){ LU[i][j] /= LU[j][j]; } } } if (B.length != m){ console.log('Matrix row dimensions must agree.'); } for (var j = 0; j < n; j++){ if (LU[j][j] === 0){ console.log('Matrix is singular.') } } var nx = B[0].length; var X = self.webgazer.mat.getMatrix(B,piv,0,nx-1); // Solve L*Y = B(piv,:) for (var k = 0; k < n; k++){ for (var i = k+1; i < n; i++){ for (var j = 0; j < nx; j++){ X[i][j] -= X[k][j]*LU[i][k]; } } } // Solve U*X = Y; for (var k = n-1; k >= 0; k--){ for (var j = 0; j < nx; j++){ X[k][j] /= LU[k][k]; } for (var i = 0; i < k; i++){ for (var j = 0; j < nx; j++){ X[i][j] -= X[k][j]*LU[i][k]; } } } return X; }; /** * Least squares solution of A*X = B, based on WEKA code * @param {Array.>} A - left side matrix to be solved * @param {Array.>} B - a matrix with as many rows as A and any number of columns. * @return {Array.>} X - that minimizes the two norms of QR*X-B. */ self.webgazer.mat.QRDecomposition = function(A, B){ // Initialize. var QR = new Array(A.length); for (var i = 0; i < A.length; i++){ QR[i] = new Array(A[0].length); for (var j = 0; j < A[0].length; j++){ QR[i][j] = A[i][j]; } } var m = A.length; var n = A[0].length; var Rdiag = new Array(n); var nrm; // Main loop. for (var k = 0; k < n; k++){ // Compute 2-norm of k-th column without under/overflow. nrm = 0; for (var i = k; i < m; i++){ nrm = Math.hypot(nrm,QR[i][k]); } if (nrm != 0){ // Form k-th Householder vector. if (QR[k][k] < 0){ nrm = -nrm; } for (var i = k; i < m; i++){ QR[i][k] /= nrm; } QR[k][k] += 1; // Apply transformation to remaining columns. for (var j = k+1; j < n; j++){ var s = 0; for (var i = k; i < m; i++){ s += QR[i][k]*QR[i][j]; } s = -s/QR[k][k]; for (var i = k; i < m; i++){ QR[i][j] += s*QR[i][k]; } } } Rdiag[k] = -nrm; } if (B.length != m){ console.log('Matrix row dimensions must agree.'); } for (var j = 0; j < n; j++){ if (Rdiag[j] === 0) console.log('Matrix is rank deficient'); } // Copy right hand side var nx = B[0].length; var X = new Array(B.length); for(var i=0; i= 0; k--){ for (var j = 0; j < nx; j++){ X[k][j] /= Rdiag[k]; } for (var i = 0; i < k; i++){ for (var j = 0; j < nx; j++){ X[i][j] -= X[k][j]*QR[i][k]; } } } return self.webgazer.mat.getSubMatrix(X,0,n-1,0,nx-1); } }());