UNPKG

qminer

Version:

A C++ based data analytics platform for processing large-scale real-time streams containing structured and unstructured data

705 lines (661 loc) 25.4 kB
/** * Copyright (c) 2015, Jozef Stefan Institute, Quintelligence d.o.o. and contributors * All rights reserved. * * This source code is licensed under the FreeBSD license found in the * LICENSE file in the root directory of this source tree. */ module.exports = exports = function (pathQmBinary) { var qm = require(pathQmBinary); // This loads only c++ functions of qm exports = qm.la; var assert = require('assert'); //!STARTJSDOC /** * Calculates the frobenious norm squared of the matrix. * @returns {number} Frobenious norm squared. * @example * // import la module * var la = require('qminer').la; * // create a sparse matrix * var spMat = new la.SparseMatrix([[[0, 1], [1, 5]], [[0, 2], [2, -3]]]); * // get the forbenious norm squared of the sparse matrix * var frob = spMat.frob2(); */ exports.SparseMatrix.prototype.frob2 = function () { return Math.pow(this.frob(), 2); } /** * Returns a string displaying rows, columns and number of non-zero elements of sparse matrix. * @returns {string} String displaying row, columns and number of non-zero elements. * @example * // import la module * var la = require('qminer').la; * // create a new sparse matrix * var mat = new la.SparseMatrix([[[0, 1]], [[0, 2], [1, 8]]]); * // create the string * var text = mat.toString(); // returns 'rows: -1, cols: 2, nnz: 3' */ exports.SparseMatrix.prototype.toString = function () { return "rows: " + this.rows + ", cols:" + this.cols + ", nnz: " + this.nnz(); } /** * Returns the number of non-zero elements of sparse matrix. * @returns {number} Number of non-zero elements. * @example * // import la module * var la = require('qminer').la; * // create a sparse matrix * var spMat = new la.SparseMatrix([[[0, 1], [1, 5]], [[0, 2], [2, -3]]]); * // get the number of non-zero elements * // returns 4 * var nnz = spMat.nnz(); */ exports.SparseMatrix.prototype.nnz = function () { var nnz = 0; //iterate over matrix and sum nnz of each column for (var colN = 0; colN < this.cols; colN++) { nnz += this[colN].nnz; } return nnz; }; /** * Prints the sparse vector on-screen. * @example * // import la module * var la = require('qminer').la; * // create a new sparse vector * var spVec = new la.SparseVector([[0, 1], [2, 3]]); * // print sparse vector * spVec.print(); // shows on-screen [(0, 1), (2, 3)] */ exports.SparseVector.prototype.print = function () { console.log(this.toString()); } /** * Prints the matrix on-screen. * @example * // import la module * var la = require('qminer').la; * // create a new matrix * var mat = new la.Matrix([[1, 2], [3, 4]]); * // print the matrix * // each row represents a row in the matrix. For this example: * // 1 2 * // 3 4 * mat.print(); */ exports.Matrix.prototype.print = function () { console.log(this.toString()); } /** * Returns a copy of the matrix. * @returns {module:la.Matrix} Matrix copy. * @example * // import la module * var la = require('qminer').la; * // create a random matrix * var mat = new la.Matrix({ rows: 5, cols: 4, random: true }); * // create a copy of the matrix * var copy = mat.toMat(); */ exports.Matrix.prototype.toMat = function () { return new exports.Matrix(this); } /** * Prints the vector on-screen. * @example * // import la module * var la = require('qminer').la; * // create a new vector * var vec = new la.Vector([1, 2, 3]); * // print the vector * // For this example it prints: * // [1, 2, 3] * vec.print(); */ exports.Vector.prototype.print = function () { console.log(this.toString()); } function vec2arr(vec) { var len = vec.length; var arr = []; for (var elN = 0; elN < len; elN++) { arr[elN] = vec[elN]; } return arr; } /** * Copies the vector into a JavaScript array of numbers. * @returns {Array.<number>} A JavaScript array of numbers. * @example * // import la module * var la = require('qminer').la; * // create a new vector * var vec = new la.Vector([1, 2, 3]); * // create a JavaScript array out of vec * var arr = vec.toArray(); // returns an array [1, 2, 3] */ exports.Vector.prototype.toArray = function () { return vec2arr(this); } /** * Copies the vector into a JavaScript array of numbers. * @returns {Array.<number>} A JavaScript array of integers. * @example * // import la module * var la = require('qminer').la; * // create a new integer vector * var vec = new la.IntVector([1, 2, 3]); * // create a JavaScript array out of vec * var arr = vec.toArray(); // returns an array [1, 2, 3] */ exports.IntVector.prototype.toArray = function () { return vec2arr(this); } /** * Copies the vector into a JavaScript array of strings. * @returns {Array.<string>} A JavaScript array of strings. * @example * // import la module * var la = require('qminer').la; * // create a new vector * var vec = new la.StrVector(["one", "two", "three"]); * // create a JavaScript array out of vec * var arr = vec.toArray(); // returns an array ["one", "two", "three"] */ exports.StrVector.prototype.toArray = function () { return vec2arr(this); } /** * Copies the vector into a JavaScript array of booleans. * @returns {Array.<boolean>} A JavaScript array of booleans. * @example * // import la module * var la = require('qminer').la; * // create a new vector * var vec = new la.BoolVector([true, false, true]); * // create a JavaScript array out of vec * var arr = vec.toArray(); // returns an array [true, false, true] */ exports.BoolVector.prototype.toArray = function () { return vec2arr(this); } /** * Copies the matrix into a JavaScript array of arrays of numbers. * @returns {Array<Array<number>>} A JavaScript array of arrays of numbers. * @example * // import la module * var la = require('qminer').la; * // create a new matrix * var mat = new la.Matrix([[1, 2], [3, 4]]); * // create a JavaScript array out of matrix * var arr = mat.toArray(); // returns an array [[1, 2], [3, 4]] */ exports.Matrix.prototype.toArray = function () { var rows = this.rows; var cols = this.cols; var arr = []; for (var i = 0; i < rows; i++) { var arr_row = []; for (var j = 0; j < cols; j++) { arr_row.push(this.at(i, j)); } arr.push(arr_row); } return arr; } /** * Copies the vector into a JavaScript array of numbers. * @param {module:la.Vector} vec - Copied vector. * @returns {Array<number>} A JavaScript array of numbers. * @example * // import la module * var la = require('qminer').la; * // create a new vector * var vec = new la.Vector([1, 2, 3]); * // create a JavaScript array out of vec * var arr = la.copyVecToArray(vec); // returns an array [1, 2, 3] */ exports.copyVecToArray = function (vec) { return vec.toArray(); }; function isInt(value) { return !isNaN(value) && parseInt(Number(value)) == value && !isNaN(parseInt(value, 10)); } ///////// RANDOM GENERATORS /** * Returns an object with random numbers. * @param {number} [arg1] - Represents dimension of vector or number of rows in matrix. Must be an integer. * @param {number} [arg2] - Represents number of columns in matrix. Must be an integer. * @returns {(number | module:la.Vector | module:la.Matrix)} * <br>1. Number, if no parameters are given. * <br>2. {@link module:la.Vector}, if parameter `arg1` is given. * <br>3. {@link module:la.Matrix}, if parameters `arg1` and `arg2` are given. * @example * // import la module * var la = require('qminer').la; * // generate a random number * var number = la.randn(); * // generate a random vector of length 7 * var vector = la.randn(7); * // generate a random matrix with 7 rows and 10 columns * var mat = la.randn(7, 10); */ exports.randn = function (arg1, arg2) { //arguments.length var len = arguments.length; if (len === 0) { var x1, x2, rad, y1; do { x1 = 2 * Math.random() - 1; x2 = 2 * Math.random() - 1; rad = x1 * x1 + x2 * x2; } while (rad >= 1 || rad == 0); var c = Math.sqrt(-2 * Math.log(rad) / rad); return x1 * c; } else if (len === 1) { var dim = arguments[0]; assert(isInt(dim)); var vec = new exports.Vector({ "vals": dim }); for (var elN = 0; elN < dim; elN++) { vec.put(elN, exports.randn()); } return vec; } else if (len === 2) { var rows = arguments[0]; var cols = arguments[1]; assert(isInt(rows)); assert(isInt(cols)); var mat = new exports.Matrix({ "cols": cols, "rows": rows }); for (var colN = 0; colN < cols; colN++) { for (var rowN = 0; rowN < rows; rowN++) { mat.put(rowN, colN, exports.randn()); } } return mat; } }; /** * Returns a randomly selected integer(s) from an array. * @param {number} num - The upper bound of the array. Must be an integer. * @param {number} [len] - The number of selected integers. Must be an integer. * @returns {(number | la.IntVector)} * <br>1. Randomly selected integer from the array `[0,...,num-1]`, if no parameters are given. * <br>2. {@link module:la.IntVector}, if parameter `len` is given. The vector contains random integers from the array `[0,...,num-1]` (with repetition). * @example * // import la module * var la = require('qminer').la; * // generate a random integer between 0 and 10 * var number = la.randi(10); * // generate an integer vector containing 5 random integers between 0 and 10 * var vec = la.randi(10, 5); */ exports.randi = function () { var len = arguments.length; if (len === 1) { var n = arguments[0]; assert(isInt(n), "one integer argument expected"); return Math.floor((Math.random() * n)); } else if (len == 2) { var n = arguments[0]; var size = arguments[1]; assert(isInt(n), "integer argument[0] expected"); assert(isInt(size), "integer argument[1] expected"); var result = new exports.IntVector({ "vals": size }); for (var i = 0; i < size; i++) { result[i] = Math.floor((Math.random() * n)); } return result; } else { throw new Error("one integer argument expected"); } }; /** * Returns a JavaScript array, which is a sample of integers from an array. * @param {number} n - The upper bound of the generated array `[0,...,n-1]`. Must be an integer. * @param {number} k - Length of the sample. Must be smaller or equal to `n`. * @returns {Array<number>} The sample of `k` numbers from `[0,...,n-1]`, sampled without replacement. * @example * // import la module * var la = require('qminer').la; * // create an array containing 5 integers between 0 and 15 * var arr = la.randVariation(15, 5); */ exports.randVariation = function (n, k) { var n = arguments[0]; var k = arguments[1]; assert(isInt(n)); assert(isInt(k)); var perm = exports.copyVecToArray(exports.randPerm(n)); var idx = perm.slice(0, k); return idx; }; /** * Returns a permutation of elements. * @param {number} k - Number of elements to permutate. * @returns {Array<number>} A JavaScript array of integers. Represents a permutation of `k` elements. * @example * // import la module * var la = require('qminer').la; * // create an array/permutation of 5 elements * var perm = la.randPerm(5); */ exports.randPerm = function (k) { assert(isInt(k)); // gaussian random vector var vec = exports.randn(k); var res = vec.sortPerm(); return res.perm; }; ///////// COMMON MATRICES /** * Returns an dense identity matrix. * @param {number} dim - The dimension of the identity matrix. Must be a positive integer. * @returns {module:la.Matrix} A `dim`-by-`dim` identity matrix. * @example * // import la module * var la = require('qminer').la; * // generate a dense identity matrix of dimension 5 * var id = la.eye(5); */ exports.eye = function(dim) { var identity = new exports.Matrix({ "rows": dim, "cols": dim }); for (var rowN = 0; rowN < identity.rows; rowN++) { identity.put(rowN, rowN, 1.0); } return identity; }; /** * Returns a sparse identity matrix * @param {number} dim - The dimension of the identity matrix. Must be a positive integer. * @returns {module:la.SparseMatrix} A dim-by-dim identity matrix. * @example * // import la module * var la = require('qminer').la; * // generate a sparse identity matrix of dimension 5 * var spId = la.speye(5); */ exports.speye = function (dim) { var vec = exports.ones(dim); return vec.spDiag(); }; /** * Returns a sparse zero matrix. * @param {number} rows - Number of rows of the sparse matrix. * @param {number} [cols = rows] - Number of columns of the sparse matrix. * @returns {module:la.SparseMatrix} A `rows`-by-`cols` sparse zero matrix. * @example * // import la module * var la = require('qminer').la; * // create a sparse zero matrix with 5 rows and columns * var spMat = la.sparse(5); */ exports.sparse = function (rows, cols) { cols = typeof cols == 'undefined' ? rows : cols; var spmat = new exports.SparseMatrix({ "rows": rows, "cols": cols }); return spmat; }; /** * Returns a dense zero matrix. * @param {number} rows - Number of rows of the matrix. * @param {number} [cols = rows] - Number of columns of the matrix. * @returns {module:la.Matrix} A `rows`-by-`cols` dense zero matrix. * @example * // import la module * var la = require('qminer').la; * // create a sparse zero matrix with 5 rows and 3 columns * var mat = la.zeros(5, 3); */ exports.zeros = function (rows, cols) { cols = typeof cols == 'undefined' ? rows : cols; var mat = new exports.Matrix({ "rows": rows, "cols": cols }); return mat; }; /** * Returns a vector with all entries set to 1.0. * @param {number} dim - Dimension of the vector. * @returns {module:la.Vector} A `dim`-dimensional vector whose entries are set to 1.0. * @example * // import la module * var la = require('qminer').la; * // create a 3-dimensional vector with all entries set to 1.0 * var vec = la.ones(3); */ exports.ones = function(k) { var ones_k = new exports.Vector({ "vals": k }); for (var i = 0; i < k; i++) { ones_k.put(i, 1.0); } return ones_k; }; /** * Constructs a matrix by concatenating a double-nested array of matrices. * @param {Array<Array<module:la.Matrix>> } nestedArrMat - An array of block rows, where each block row is an array of matrices. * For example: `[[m_11, m_12], [m_21, m_22]]` is used to construct a matrix where the (i,j)-th block submatrix is `m_ij`. * @returns {module:la.Matrix} Concatenated matrix. * @example * // import la module * var la = require('qminer').la; * // create four matrices and concatenate (2 block columns, 2 block rows) * var la = require('qminer').la; * var A = new la.Matrix([[1,2], [3,4]]); * var B = new la.Matrix([[5,6], [7,8]]); * var C = new la.Matrix([[9,10], [11,12]]); * var D = new la.Matrix([[13,14], [15,16]]); * // create a nested matrix * // returns the matrix: * // 1 2 5 6 * // 3 4 7 8 * // 9 10 13 14 * // 11 12 15 16 * var mat = la.cat([[A,B], [C,D]]); */ exports.cat = function (nestedArrMat) { var dimx = []; //cell row dimensions var dimy = []; //cell col dimensions var cdimx = []; //cumulative row dims var cdimy = []; //cumulative coldims var rows = nestedArrMat.length; var cols = nestedArrMat[0].length; for (var row = 0; row < rows; row++) { for (var col = 0; col < cols; col++) { if (col > 0) { assert(dimx[row] == nestedArrMat[row][col].rows, 'inconsistent row dimensions!'); } else { dimx[row] = nestedArrMat[row][col].rows; } if (row > 0) { assert(dimy[col] == nestedArrMat[row][col].cols, 'inconsistent column dimensions!'); } else { dimy[col] = nestedArrMat[row][col].cols; } } } cdimx[0] = 0; cdimy[0] = 0; for (var row = 1; row < rows; row++) { cdimx[row] = cdimx[row - 1] + dimx[row - 1]; } for (var col = 1; col < cols; col++) { cdimy[col] = cdimy[col - 1] + dimy[col - 1]; } var res = new exports.Matrix({ rows: (cdimx[rows - 1] + dimx[rows - 1]), cols: (cdimy[cols - 1] + dimy[cols - 1]) }); // copy submatrices for (var row = 0; row < rows; row++) { for (var col = 0; col < cols; col++) { res.put(cdimx[row], cdimy[col], nestedArrMat[row][col]); } } return res; } /** * Generates an integer vector given range. * @param {number} min - Start value. Should be an integer. * @param {number} max - End value. Should be an integer. * @returns {module:la.IntVector} Integer range vector. * @example * // import la module * var la = require('qminer').la; * // create a range vector containing 1, 2, 3 * var vec = la.rangeVec(1, 3); */ exports.rangeVec = function (min, max) { var len = max - min + 1; var rangeV = new exports.IntVector({ "vals": len }); for (var elN = 0; elN < len; elN++) { rangeV[elN] = elN + min; } return rangeV; }; //////// METHODS /** * Squares the values in vector. * @param {number | module:la.Vector} x - The value/vector. * @returns {number | module:la.Vector} * <br> 1. If `x` is a number, returns square of `x`. * <br> 2. If `x` is a {@link module:la.Vector}, returns a {@link module:la.Vector}, where the i-th value of the vector is the square of `x[i]`. * @example * // import la module * var la = require('qminer').la; * // create a vector * var vec = new la.Vector([1, 2, 3]); * // square the values of the vector * // returns the vector containing the values 1, 4, 9 * var sqr = la.square(vec); */ exports.square = function(x) { if (typeof x.length == "undefined") { return x * x; } var res = new exports.Vector(x); for (var i = 0; i < x.length; i++) { res[i] = x[i] * x[i]; } return res; }; /** * Returns a JS array of indices `idxArray` that correspond to the max elements in each column of dense matrix. The resulting array has one element for vector input. * @param {(module:la.Matrix | module:la.Vector)} X - The matrix or vector. * @returns {Array<number>} Array of indexes where maximum is found, one for each column. * @example * // import la module * var la = require('qminer').la; * // create a dense matrix * var mat = new la.Matrix([[1, 2], [2, 0]]); * // get the indices of the maximum elements in each column of mat * // returns the array: * // [1, 0] * la.findMaxIdx(mat); */ exports.findMaxIdx = function (X) { var idxv = new Array(); // X is a dense matrix if (typeof X.cols !== "undefined") { var cols = X.cols; for (var colN = 0; colN < cols; colN++) { idxv.push(X.colMaxIdx(colN)); } } // X is a dense vector if (typeof X.length !== "undefined") { idxv.push(X.getMaxIdx()); } return idxv; }; /** * Computes and returns the pairwise squared euclidean distances between columns of `X1` and `X2` (`mat3[i,j] = ||mat(:,i) - mat2(:,j)||^2`). * @param {module:la.Matrix} X1 - First matrix. * @param {module:la.Matrix} X2 - Second matrix. * @returns {module:la.Matrix} Matrix with `X1.cols` rows and `X2.cols` columns containing squared euclidean distances. * @example * // import la module * var la = require('qminer').la; * // construct two input matrices * var X1 = new la.Matrix([[1,2], [2,0]]); * var X2 = new la.Matrix([[1,0.5,0],[0,-0.5,-1]]); * // get the pairwise squared distance between the matrices * // returns the matrix: * // 4 6.5 10 * // 1 2.5 5 * la.pdist2(X1, X2); */ exports.pdist2 = function (X1, X2) { var snorm1 = exports.square(X1.colNorms()); var snorm2 = exports.square(X2.colNorms()); var ones_1 = exports.ones(X1.cols); var ones_2 = exports.ones(X2.cols); var D = (X1.multiplyT(X2).multiply(-2)).plus(snorm1.outer(ones_2)).plus(ones_1.outer(snorm2)); return D; } ///////// ALGORITHMS /** * Calculates the inverse matrix with SVD. * @param {module:la.Matrix} mat - The matrix we want to inverse. * @returns {module:la.Matrix} The inverse matrix of `mat`. * @example * // import la module * var la = require('qminer').la; * // create a random matrix * var mat = new la.Matrix({ rows: 5, cols: 5, random: true }); * // get the inverse of mat * var inv = la.inverseSVD(mat); */ exports.inverseSVD = function (mat) { var k = Math.min(mat.rows, mat.cols); var svdRes = exports.svd(mat, k, { "iter": 10, "tol": 1E-15 }); // returns U, s and V var B = new exports.Matrix({ "cols": mat.cols, "rows": mat.rows }); // http://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_pseudoinverse#Singular_value_decomposition_.28SVD.29 var tol = 1E-16 * Math.max(mat.cols, mat.rows) * svdRes.s.at(svdRes.s.getMaxIdx()); // calculate reciprocal values for diagonal matrix = inverse diagonal for (i = 0; i < svdRes.s.length; i++) { if (svdRes.s.at(i) > tol) svdRes.s.put(i, 1 / svdRes.s.at(i)); else svdRes.s.put(0); } var sum; for (i = 0; i < svdRes.U.cols; i++) { for (j = 0; j < svdRes.V.rows; j++) { sum = 0; for (k = 0; k < svdRes.U.cols; k++) { if (svdRes.s.at(k) != 0) { sum += svdRes.s.at(k) * svdRes.V.at(i, k) * svdRes.U.at(j, k); } } B.put(i, j, sum); } } return B; } /** * Solves the PSD symmetric system: A x = b, where A is a positive-definite symmetric matrix. * @param {(module:la.Matrix | module:la.SparseMatrix)} A - The matrix on the left-hand side of the system. * @param {module:la.Vector} b - The vector on the right-hand side of the system. * @param {module:la.Vector} [x] - Current solution. Default is a vector of zeros. * @param {boolean} [verbose=false] - If true, console logs the residuum value. * @returns {module:la.Vector} Solution to the system. * @example * // import la module * var la = require('qminer').la; * // create a positive-definite symmetric matrix * var vecTemp = new la.Vector([1, 2, 3]); * var mat = vecTemp.diag(); * // create the right-hand side vector * var vec = new la.Vector([0.5, 3, -2]); * // solve the PSD symmetric system * var x = la.conjgrad(mat, vec); */ exports.conjgrad = function (A, b, x, verbose) { verbose = verbose === undefined ? false : verbose; x = x || new exports.Vector({vals: A.cols}); var r = b.minus(A.multiply(x)); var p = new exports.Vector(r); //clone var rsold = r.inner(r); for (var i = 0; i < 2 * x.length; i++) { var Ap = A.multiply(p); var alpha = rsold / Ap.inner(p); x = x.plus(p.multiply(alpha)); r = r.minus(Ap.multiply(alpha)); var rsnew = r.inner(r); if (verbose) { console.log("resid = " + rsnew); } if (Math.sqrt(rsnew) < 1e-6) { break; } p = r.plus(p.multiply(rsnew / rsold)); rsold = rsnew; } return x; } //!ENDJSDOC return exports; }