UNPKG

multivariate-normal

Version:

Port of NumPy's random.multivariate_normal to Node.JS

140 lines (106 loc) 4.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateCovAndGetSVD = exports.validateMean = undefined; var _freeze = require("babel-runtime/core-js/object/freeze"); var _freeze2 = _interopRequireDefault(_freeze); var _lodash = require("lodash.isarray"); var _lodash2 = _interopRequireDefault(_lodash); var _lodash3 = require("lodash.every"); var _lodash4 = _interopRequireDefault(_lodash3); var _lodash5 = require("lodash.isnumber"); var _lodash6 = _interopRequireDefault(_lodash5); var _lodash7 = require("lodash.some"); var _lodash8 = _interopRequireDefault(_lodash7); var _numeric = require("numeric"); var _numeric2 = _interopRequireDefault(_numeric); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // freezes nested arrays var deepFreeze = function deepFreeze(ary) { if ((0, _lodash2.default)(ary)) { ary.forEach(deepFreeze); (0, _freeze2.default)(ary); } }; // validates a mean vector that's supposed to be of length n // // on success, freezes mean and returns it var validateMean = function validateMean(mean, n) { // must be an array if (!(0, _lodash2.default)(mean)) { throw new Error("Mean must be an array"); } // must be an array of numbers if (!(0, _lodash4.default)(mean, _lodash6.default)) { throw new Error("Mean must be an array of numbers"); } // must have the correct length if (mean.length !== n) { throw new Error("Expected mean to have length " + n + ", but had length " + mean.length); } (0, _freeze2.default)(mean); return mean; }; // validates a covariance matrix that's supposed to be NxN. If successful, // computes the SVD, freezes cov, and returns {cov, svd: { u, s, v }} var validateCovAndGetSVD = function validateCovAndGetSVD(cov, n) { // must be an array if (!(0, _lodash2.default)(cov)) { throw new Error("Covariance must be an array"); } // must have n elements if (cov.length !== n) { throw new Error("Covariance matrix had " + cov.length + " rows, but it should be a " + n + "x" + n + " square matrix"); } // validate each row cov.forEach(function (row, idx) { // must be an array if (!(0, _lodash2.default)(row)) { throw new Error("Row " + idx + " of covariance matrix was not an array"); } // must have n elements if (row.length !== n) { throw new Error("Row " + idx + " of covariance matrix had length " + row.length + ", but it should have length " + n); } // each element must be a number if (!(0, _lodash4.default)(row, _lodash6.default)) { throw new Error("Row " + idx + " of covariance matrix contained a non-numeric value"); } }); // matrix must be positive semidefinite var eigenvalues = _numeric2.default.eig(cov).lambda.x; if ((0, _lodash8.default)(eigenvalues, function (v) { return v < 0; })) { throw new Error("Covariance isn't positive semidefinite"); } // matrix must be symmetric if (!_numeric2.default.same(_numeric2.default.transpose(cov), cov)) { throw new Error("Covariance isn't symmetric"); } // do decomposition // We use the SVD algorithm from Numeric.js because it's efficient and // reliable. Sylvester includes an SVD algorithm that doesn't hand some // edge cases and is also extremely slow (takes ~500ms to compute and SVD // for a 15x15 matrix). Numeric can do a 250x250 matrix in ~500ms. // // There's also node-svd, which is a wrapper around a C implementation. // It's slightly faster than Numeric (it can do a 370x370 matrix in ~500ms), // but can't run the browser and doesn't handle some edge cases well. var _Numeric$svd = _numeric2.default.svd(cov), u = _Numeric$svd.U, s = _Numeric$svd.S, v = _Numeric$svd.V; // deep freeze cov and svd deepFreeze(cov); deepFreeze(u); deepFreeze(s); deepFreeze(v); return { cov: cov, svd: { u: u, s: s, v: v } }; }; exports.validateMean = validateMean; exports.validateCovAndGetSVD = validateCovAndGetSVD;