From 9f9b88f5616227463629fd5b2b92f312963acef9 Mon Sep 17 00:00:00 2001 From: Shane Holloway Date: Sun, 11 Feb 2018 18:41:36 -0700 Subject: [PATCH 1/2] Initial conversion to Rollup bundling with CommonJS, ESM/MJS, and AMD format output --- .gitignore | 2 + LICENSE.txt | 3 +- code/index.js | 972 ++++++++++++++++++++++++++++++++++++++++++++++ index.js | 973 ----------------------------------------------- package.json | 13 +- rollup.config.js | 12 + test/index.js | 2 +- 7 files changed, 997 insertions(+), 980 deletions(-) create mode 100644 .gitignore create mode 100644 code/index.js delete mode 100644 index.js create mode 100644 rollup.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23d31d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +package-lock.json +yarn.lock diff --git a/LICENSE.txt b/LICENSE.txt index 88175b4..7a10440 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,7 @@ The MIT License (MIT) Copyright (c) 2015 Matt Asher (http://Mattasher.com) +Copyright (c) 2018 Shane Holloway (http://ShaneHolloway.com) for Rollup-based packaging Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -19,4 +20,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/code/index.js b/code/index.js new file mode 100644 index 0000000..78b8232 --- /dev/null +++ b/code/index.js @@ -0,0 +1,972 @@ +/* ================================================================ + * probability-distributions by Matt Asher (me[at]mattasher.com) + * Originally created for StatisticsBlog.com + * + * first created at : Sat Oct 10 2015 + * + * ================================================================ + * Copyright 2015 Matt Asher + * + * Licensed under the MIT License + * You may not use this file except in compliance with the License. + * + * ================================================================ */ + +import {randomBytes} from 'crypto'; + +// Shortcuts +var exp = Math.exp; +var ln = Math.log; +var PI = Math.PI; +var pow = Math.pow; + + +/** + * This is the core function for generating entropy + * + * @param len number of bytes of entropy to create + * @returns {number} A pseduo random number between 0 and 1 + * + */ +export function prng(len) { + if(len === undefined) len=16; + + var entropy = randomBytes(len); + var result = 0; + + for(var i=0; i 1.0) { + var ainv = Math.sqrt(2.0 * alpha - 1.0); + var bbb = alpha - LOG4; + var ccc = alpha + ainv; + + while (true) { + var u1 = this.prng(); + if ((u1 < 1e-7) || (u > 0.9999999)) { + continue; + } + var u2 = 1.0 - this.prng(); + var v = ln(u1 / (1.0 - u1)) / ainv; + var x = alpha * exp(v); + var z = u1 * u1 * u2; + var r = bbb + ccc * v - x; + if ((r + SG_MAGICCONST - 4.5 * z >= 0.0) || (r >= ln(z))) { + var result = x * beta; + break; + } + } + } else if (alpha == 1.0) { + var u = this.prng(); + while (u <= 1e-7) { + u = this.prng(); + } + var result = - ln(u) * beta; + } else { + while (true) { + var u = this.prng(); + var b = (Math.E + alpha) / Math.E; + var p = b * u; + if (p <= 1.0) { + var x = Math.pow(p, 1.0 / alpha); + } else { + var x = - ln((b - p) / alpha); + } + var u1 = this.prng(); + if (p > 1.0) { + if (u1 <= Math.pow(x, (alpha - 1.0))) { + break; + } + } else if (u1 <= exp(-x)) { + break; + } + } + var result = x * beta; + } + + toReturn[i] = result; + } + + return toReturn; + +} + + +/** + * + * @param n The number of random variates to create. Must be a positive integer + * @param min Minimum value + * @param max Maximum value + * @param inclusive By default the minimum and maximum are inclusive. To make exclusive, set to false + * @returns {Array} + */ +export function rint(n, min, max, inclusive) { + n = this._v(n, "n"); + min = this._v(min, "int"); + max = this._v(max, "int"); + if(inclusive === false) { + min++; + if(min === max) throw new Error("Minimum value cannot be greater than maximum value. For non-inclusive, minimum and maximum must be separated by at least 2."); + } else { + max++ + } + + if(min > max) throw new Error("Minimum value cannot be greater than maximum value. For non-inclusive, minimum and maximum must be separated by at least 2."); + + var toReturn = []; + + var raw = this.runif(n, min, max); + + for(var i=0; i 0) { + result++; + if(this.prng() < p) leftToFind--; + } + + toReturn[i] = result - 1; + } + + return toReturn + +} + +/** + * + * @param x Where to sample the density + * @param mean Mean of the distribution + * @param sd Standard deviation for the distribution + * @returns {Number} The density given the parameter values + */ +export function dnorm(x, mean, sd) { + x = this._v(x, "r"); + mean = this._v(mean, "r", 0); + sd = this._v(sd, "nn", 1); + + // Check for degeneracy + if(sd === 0) { + if(x === mean) return Infinity; + return 0 + } + + var a = sd*(Math.sqrt(2*PI)); + var b = -(x-mean)*(x-mean); + var c = 2*sd*sd; + + return (1/a)*exp(b/c) +} + + +/** + * + * @param n The number of random variates to create. Must be a positive integer. + * @param mean Mean of the distribution + * @param sd Standard Deviation of the distribution + * @returns {Array} Random variates array + */ +export function rnorm(n, mean, sd) { + // Adapted from http://blog.yjl.im/2010/09/simulating-normal-random-variable-using.html + + n = this._v(n, "n"); + mean = this._v(mean, "r", 0); + sd = this._v(sd, "nn", 1); + + var toReturn = []; + + for(var i=0; i 1); + + X = Math.sqrt(-2 * ln(S) / S) * V1; + X = mean + sd * X; + toReturn.push(X); + } + + return toReturn +} + + +/** + * + * @param x Where to sample the density + * @param lambda Mean/variance + * @returns {Number} The density given the parameter values + */ +export function dpois(x, lambda) { + x = this._v(x, "nni"); + lambda = this._v(lambda, "nn"); + + // Check for degeneracy + if(lambda === 0) { + if(x === 0) return 1; + return 0 + } + + var a = pow(lambda, x); + var b = exp(-lambda); + var c = this._factorial(x); + + return a*b/c +} + + +/** + * + * @param n The number of random variates to create. Must be a positive integer. + * @param lambda Mean/Variance of the distribution + * @returns {Array} Random variates array + */ +export function rpois(n, lambda) { + n = this._v(n, "n"); + lambda = this._v(lambda, "pos"); + + var toReturn = []; + + for(var i=0; i L); + toReturn.push(k - 1); + + } else { + + // Roll our own + // Fix total number of samples + var samples = 10000; + var p = lambda/samples; + var k = 0; + for(var j=0; j max) throw new Error("Minimum value cannot be greater than maximum value"); + + if(x < min || x > max) return 0; + if(min === max) return Infinity; + + + return 1/(max-min); +} + + +/** + * + * @param n Number of variates to return + * @param min Lower bound + * @param max Upper bound + * @returns {Array} Random variates array + */ +export function runif(n, min, max) { + n = this._v(n, "n"); + min = this._v(min, "r", 0); + max = this._v(max, "r", 1); + if(min > max) throw new Error("Minimum value cannot be greater than maximum value"); + + var toReturn = []; + + for(var i=0; i 3 && x < 10". Use "x" as the variable. + arrivalTimes: false, + arrivalSymbol: '', + arrivalFlashTime: 0.25, + significantDigits: 0 // 0 will show as many as JS provides, or choose a number to limit + } + + if(typeof options === "undefined") options = {} + + // Merge defaultOptions with user options favoring user options + for (var property in defaultOptions) { + if (defaultOptions.hasOwnProperty(property)) { + if(typeof options[property] === "undefined") { + options[property] = defaultOptions[property]; + } + } + } + + // Elem is a DOM element to output to + var elem = document.getElementById(domID); + if(!elem) throw new Error("Unable to find DOM element " + domID); + + var len = data.length, i = 0; + + var format = function(x) { + var x = data[i]; + + // Are we showing only certain things + if(options.conditions) { + + // Check for only allowed characters, this is NOT complete security + if(!/^[x\&\|=0-9\<\>\s\-\.]+$/.test(options.conditions)) throw new Error("Bad input sent to options.conditions"); + + if (eval(options.conditions) !== true) { + x = options.blank; + return x + } + } + + if(options.significantDigits) { + x = x.toPrecision(options.significantDigits) + } + + if(x === Infinity) { + x = options.inf; + } + return x; + } + + + if(options.arrivalTimes) { + var blankOut = function() { + setTimeout(function() { + elem.innerHTML = options.blank; + }, options.arrivalFlashTime*options.lag); + } + + var cycle = function() { + + elem.innerHTML = options.arrivalSymbol; + blankOut(); + if(++i === len) i = 0; + + if(options.loop || i !== 0) { + setTimeout(cycle, data[i]*options.lag) + } else { + // End on a blank + elem.innerHTML = options.blank; + } + } + cycle(); + + } else { + var update = function() { + // Main vis for non-arrival numbers + + elem.innerHTML = format(data[i]); + if(++i === len) i = 0; + + if(options.loop || i !== 0) { + setTimeout(update, options.lag) + } else { + // End on a blank + setTimeout(function() { elem.innerHTML = options.blank }, options.lag) + + } + } + update(); + } +} + + +// HELPERS + +/** + * + * @param ratios Array of non-negative numbers to be turned into CDF + * @param len length of the collection + * @returns {Array} + * @private + */ +export function _getCumulativeProbs(ratios, len) { + if(len === undefined) throw new Error("An error occurred: len was not sent to _getCumulativeProbs"); + if(ratios.length !== len) throw new Error("Probabilities for sample must be same length as the array to sample from"); + + var toReturn = []; + + if(ratios !== undefined) { + ratios = this._v(ratios, "a"); + if(ratios.length !== len) throw new Error("Probabilities array must be the same length as the array you are sampling from"); + + var sum = 0; + ratios.map(function(ratio) { + ratio = this._v(ratio, "nn"); // Note validating as ANY non-negative number + sum+= ratio; + toReturn.push(sum); + }.bind(this)); + + // Divide by total to normalize + for(var k=0; k cumulativeProbs[cur]) cur++; + + return cur; +} + +export function _factorial(n) { + var toReturn=1; + for (var i = 2; i <= n; i++) + toReturn = toReturn * i; + + return toReturn; +} + +// Return default if undefined, otherwise validate +// Return a COPY of the validated parameter +export function _v(param, type, defaultParam) { + if(param == null && defaultParam != null) + return defaultParam; + + switch(type) { + + // Array of 1 item or more + case "a": + if(!Array.isArray(param) || !param.length) throw new Error("Expected an array of length 1 or greater"); + return param.slice(0); + + // Integer + case "int": + if(param !== Number(param)) throw new Error("A required parameter is missing or not a number"); + if(param !== Math.round(param)) throw new Error("Parameter must be a whole number"); + if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); + return param; + + // Natural number + case "n": + if(param === undefined) throw new Error("You must specify how many values you want"); + if(param !== Number(param)) throw new Error("The number of values must be numeric"); + if(param !== Math.round(param)) throw new Error("The number of values must be a whole number"); + if(param < 1) throw new Error("The number of values must be a whole number of 1 or greater"); + if(param === Infinity) throw new Error("The number of values cannot be infinite ;-)"); + return param; + + // Valid probability + case "p": + if(Number(param) !== param) throw new Error("Probability value is missing or not a number"); + if(param > 1) throw new Error("Probability values cannot be greater than 1"); + if(param < 0) throw new Error("Probability values cannot be less than 0"); + return param; + + // Positive numbers + case "pos": + if(Number(param) !== param) throw new Error("A required parameter is missing or not a number"); + if(param <= 0) throw new Error("Parameter must be greater than 0"); + if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); + return param; + + // Look for numbers (reals) + case "r": + if(Number(param) !== param) throw new Error("A required parameter is missing or not a number"); + if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); + return param; + + // Non negative real number + case "nn": + if(param !== Number(param)) throw new Error("A required parameter is missing or not a number"); + if(param < 0) throw new Error("Parameter cannot be less than 0"); + if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); + return param; + + // Non negative whole number (integer) + case "nni": + if(param !== Number(param)) throw new Error("A required parameter is missing or not a number"); + if(param !== Math.round(param)) throw new Error("Parameter must be a whole number"); + if(param < 0) throw new Error("Parameter cannot be less than zero"); + if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); + return param; + + // Non-empty string + case "str": + if(param !== String(param)) throw new Error("A required parameter is missing or not a string"); + if(param.length === 0) throw new Error("Parameter must be at least one character long"); + return param; + + + } +} + + + + +// ________ _______ ______ _____ _____ __ __ ______ _ _ _______ _ +// | ____\ \ / / __ \| ____| __ \|_ _| \/ | ____| \ | |__ __|/\ | | +// | |__ \ V /| |__) | |__ | |__) | | | | \ / | |__ | \| | | | / \ | | +// | __| > < | ___/| __| | _ / | | | |\/| | __| | . ` | | | / /\ \ | | +// | |____ / . \| | | |____| | \ \ _| |_| | | | |____| |\ | | |/ ____ \| |____ +// |______/_/ \_\_| |______|_| \_\_____|_| |_|______|_| \_| |_/_/ \_\______| + +/** + * + * @param n Number of variates to return + * @param loc Starting point. Must be a non-negative integer. 0 for degenerate distribution of 0. + * @param p Probability of moving towards finish + * @param cap Maximum steps before giving up + * @param trace Variable to track progress + * @returns {Array} Random variates array + * + * The FML distribution is a is based on the number of steps taken to return to the origin + * from a given position, with transition probabilities set at the beginning by picking a + * random variate from U(0,1). + */ +export function rfml (n, loc, p, cap, trace) { + n = this._v(n, "n"); + loc = this._v(loc, "nni", 1); + if(p === undefined) p=this.prng; + cap = this._v(cap, "n", 10000); + if(trace === undefined) trace={}; + + var toReturn = []; + + for(var i=0; i 0 && x < cap); + + if(x === cap) x = -1; // Indicate we failed to do it in time. + toReturn[i] = x; + } + } + return toReturn +} + +// http://www.statisticsblog.com/2013/05/uncovering-the-unreliable-friend-distribution-a-case-study-in-the-limits-of-mc-methods/ +/** + * + * The Unrelaible Friend distribution + * @param n + * @returns {Array} Random variates array + */ +export function ruf(n) { + n = this._v(n, "n"); + + var toReturn = []; + + for(var i=0; i 1.0) { - var ainv = Math.sqrt(2.0 * alpha - 1.0); - var bbb = alpha - LOG4; - var ccc = alpha + ainv; - - while (true) { - var u1 = this.prng(); - if ((u1 < 1e-7) || (u > 0.9999999)) { - continue; - } - var u2 = 1.0 - this.prng(); - var v = ln(u1 / (1.0 - u1)) / ainv; - var x = alpha * exp(v); - var z = u1 * u1 * u2; - var r = bbb + ccc * v - x; - if ((r + SG_MAGICCONST - 4.5 * z >= 0.0) || (r >= ln(z))) { - var result = x * beta; - break; - } - } - } else if (alpha == 1.0) { - var u = this.prng(); - while (u <= 1e-7) { - u = this.prng(); - } - var result = - ln(u) * beta; - } else { - while (true) { - var u = this.prng(); - var b = (Math.E + alpha) / Math.E; - var p = b * u; - if (p <= 1.0) { - var x = Math.pow(p, 1.0 / alpha); - } else { - var x = - ln((b - p) / alpha); - } - var u1 = this.prng(); - if (p > 1.0) { - if (u1 <= Math.pow(x, (alpha - 1.0))) { - break; - } - } else if (u1 <= exp(-x)) { - break; - } - } - var result = x * beta; - } - - toReturn[i] = result; - } - - return toReturn; - - }, - - - /** - * - * @param n The number of random variates to create. Must be a positive integer - * @param min Minimum value - * @param max Maximum value - * @param inclusive By default the minimum and maximum are inclusive. To make exclusive, set to false - * @returns {Array} - */ - rint: function(n, min, max, inclusive) { - n = this._v(n, "n"); - min = this._v(min, "int"); - max = this._v(max, "int"); - if(inclusive === false) { - min++; - if(min === max) throw new Error("Minimum value cannot be greater than maximum value. For non-inclusive, minimum and maximum must be separated by at least 2."); - } else { - max++ - } - - if(min > max) throw new Error("Minimum value cannot be greater than maximum value. For non-inclusive, minimum and maximum must be separated by at least 2."); - - var toReturn = []; - - var raw = this.runif(n, min, max); - - for(var i=0; i 0) { - result++; - if(this.prng() < p) leftToFind--; - } - - toReturn[i] = result - 1; - } - - return toReturn - - }, - - /** - * - * @param x Where to sample the density - * @param mean Mean of the distribution - * @param sd Standard deviation for the distribution - * @returns {Number} The density given the parameter values - */ - dnorm: function(x, mean, sd) { - x = this._v(x, "r"); - mean = this._v(mean, "r", 0); - sd = this._v(sd, "nn", 1); - - // Check for degeneracy - if(sd === 0) { - if(x === mean) return Infinity; - return 0 - } - - var a = sd*(Math.sqrt(2*PI)); - var b = -(x-mean)*(x-mean); - var c = 2*sd*sd; - - return (1/a)*exp(b/c) - }, - - - /** - * - * @param n The number of random variates to create. Must be a positive integer. - * @param mean Mean of the distribution - * @param sd Standard Deviation of the distribution - * @returns {Array} Random variates array - */ - rnorm: function(n, mean, sd) { - // Adapted from http://blog.yjl.im/2010/09/simulating-normal-random-variable-using.html - - n = this._v(n, "n"); - mean = this._v(mean, "r", 0); - sd = this._v(sd, "nn", 1); - - var toReturn = []; - - for(var i=0; i 1); - - X = Math.sqrt(-2 * ln(S) / S) * V1; - X = mean + sd * X; - toReturn.push(X); - } - - return toReturn - }, - - - /** - * - * @param x Where to sample the density - * @param lambda Mean/variance - * @returns {Number} The density given the parameter values - */ - dpois: function(x, lambda) { - x = this._v(x, "nni"); - lambda = this._v(lambda, "nn"); - - // Check for degeneracy - if(lambda === 0) { - if(x === 0) return 1; - return 0 - } - - var a = pow(lambda, x); - var b = exp(-lambda); - var c = this._factorial(x); - - return a*b/c - }, - - - /** - * - * @param n The number of random variates to create. Must be a positive integer. - * @param lambda Mean/Variance of the distribution - * @returns {Array} Random variates array - */ - rpois: function(n, lambda) { - n = this._v(n, "n"); - lambda = this._v(lambda, "pos"); - - var toReturn = []; - - for(var i=0; i L); - toReturn.push(k - 1); - - } else { - - // Roll our own - // Fix total number of samples - var samples = 10000; - var p = lambda/samples; - var k = 0; - for(var j=0; j max) throw new Error("Minimum value cannot be greater than maximum value"); - - if(x < min || x > max) return 0; - if(min === max) return Infinity; - - - return 1/(max-min); - }, - - - /** - * - * @param n Number of variates to return - * @param min Lower bound - * @param max Upper bound - * @returns {Array} Random variates array - */ - runif: function(n, min, max) { - n = this._v(n, "n"); - min = this._v(min, "r", 0); - max = this._v(max, "r", 1); - if(min > max) throw new Error("Minimum value cannot be greater than maximum value"); - - var toReturn = []; - - for(var i=0; i 3 && x < 10". Use "x" as the variable. - arrivalTimes: false, - arrivalSymbol: '', - arrivalFlashTime: 0.25, - significantDigits: 0 // 0 will show as many as JS provides, or choose a number to limit - } - - if(typeof options === "undefined") options = {} - - // Merge defaultOptions with user options favoring user options - for (var property in defaultOptions) { - if (defaultOptions.hasOwnProperty(property)) { - if(typeof options[property] === "undefined") { - options[property] = defaultOptions[property]; - } - } - } - - // Elem is a DOM element to output to - var elem = document.getElementById(domID); - if(!elem) throw new Error("Unable to find DOM element " + domID); - - var len = data.length, i = 0; - - var format = function(x) { - var x = data[i]; - - // Are we showing only certain things - if(options.conditions) { - - // Check for only allowed characters, this is NOT complete security - if(!/^[x\&\|=0-9\<\>\s\-\.]+$/.test(options.conditions)) throw new Error("Bad input sent to options.conditions"); - - if (eval(options.conditions) !== true) { - x = options.blank; - return x - } - } - - if(options.significantDigits) { - x = x.toPrecision(options.significantDigits) - } - - if(x === Infinity) { - x = options.inf; - } - return x; - } - - - if(options.arrivalTimes) { - var blankOut = function() { - setTimeout(function() { - elem.innerHTML = options.blank; - }, options.arrivalFlashTime*options.lag); - } - - var cycle = function() { - - elem.innerHTML = options.arrivalSymbol; - blankOut(); - if(++i === len) i = 0; - - if(options.loop || i !== 0) { - setTimeout(cycle, data[i]*options.lag) - } else { - // End on a blank - elem.innerHTML = options.blank; - } - } - cycle(); - - } else { - var update = function() { - // Main vis for non-arrival numbers - - elem.innerHTML = format(data[i]); - if(++i === len) i = 0; - - if(options.loop || i !== 0) { - setTimeout(update, options.lag) - } else { - // End on a blank - setTimeout(function() { elem.innerHTML = options.blank }, options.lag) - - } - } - update(); - } - }, - - - // HELPERS - - /** - * - * @param ratios Array of non-negative numbers to be turned into CDF - * @param len length of the collection - * @returns {Array} - * @private - */ - _getCumulativeProbs: function(ratios, len) { - if(len === undefined) throw new Error("An error occurred: len was not sent to _getCumulativeProbs"); - if(ratios.length !== len) throw new Error("Probabilities for sample must be same length as the array to sample from"); - - var toReturn = []; - - if(ratios !== undefined) { - ratios = this._v(ratios, "a"); - if(ratios.length !== len) throw new Error("Probabilities array must be the same length as the array you are sampling from"); - - var sum = 0; - ratios.map(function(ratio) { - ratio = this._v(ratio, "nn"); // Note validating as ANY non-negative number - sum+= ratio; - toReturn.push(sum); - }.bind(this)); - - // Divide by total to normalize - for(var k=0; k cumulativeProbs[cur]) cur++; - - return cur; - }, - - _factorial: function(n) { - var toReturn=1; - for (var i = 2; i <= n; i++) - toReturn = toReturn * i; - - return toReturn; - }, - - // Return default if undefined, otherwise validate - // Return a COPY of the validated parameter - _v: function(param, type, defaultParam) { - if(param == null && defaultParam != null) - return defaultParam; - - switch(type) { - - // Array of 1 item or more - case "a": - if(!Array.isArray(param) || !param.length) throw new Error("Expected an array of length 1 or greater"); - return param.slice(0); - - // Integer - case "int": - if(param !== Number(param)) throw new Error("A required parameter is missing or not a number"); - if(param !== Math.round(param)) throw new Error("Parameter must be a whole number"); - if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); - return param; - - // Natural number - case "n": - if(param === undefined) throw new Error("You must specify how many values you want"); - if(param !== Number(param)) throw new Error("The number of values must be numeric"); - if(param !== Math.round(param)) throw new Error("The number of values must be a whole number"); - if(param < 1) throw new Error("The number of values must be a whole number of 1 or greater"); - if(param === Infinity) throw new Error("The number of values cannot be infinite ;-)"); - return param; - - // Valid probability - case "p": - if(Number(param) !== param) throw new Error("Probability value is missing or not a number"); - if(param > 1) throw new Error("Probability values cannot be greater than 1"); - if(param < 0) throw new Error("Probability values cannot be less than 0"); - return param; - - // Positive numbers - case "pos": - if(Number(param) !== param) throw new Error("A required parameter is missing or not a number"); - if(param <= 0) throw new Error("Parameter must be greater than 0"); - if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); - return param; - - // Look for numbers (reals) - case "r": - if(Number(param) !== param) throw new Error("A required parameter is missing or not a number"); - if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); - return param; - - // Non negative real number - case "nn": - if(param !== Number(param)) throw new Error("A required parameter is missing or not a number"); - if(param < 0) throw new Error("Parameter cannot be less than 0"); - if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); - return param; - - // Non negative whole number (integer) - case "nni": - if(param !== Number(param)) throw new Error("A required parameter is missing or not a number"); - if(param !== Math.round(param)) throw new Error("Parameter must be a whole number"); - if(param < 0) throw new Error("Parameter cannot be less than zero"); - if(param === Infinity) throw new Error("Sent 'infinity' as a parameter"); - return param; - - // Non-empty string - case "str": - if(param !== String(param)) throw new Error("A required parameter is missing or not a string"); - if(param.length === 0) throw new Error("Parameter must be at least one character long"); - return param; - - - } - }, - - - - - // ________ _______ ______ _____ _____ __ __ ______ _ _ _______ _ - // | ____\ \ / / __ \| ____| __ \|_ _| \/ | ____| \ | |__ __|/\ | | - // | |__ \ V /| |__) | |__ | |__) | | | | \ / | |__ | \| | | | / \ | | - // | __| > < | ___/| __| | _ / | | | |\/| | __| | . ` | | | / /\ \ | | - // | |____ / . \| | | |____| | \ \ _| |_| | | | |____| |\ | | |/ ____ \| |____ - // |______/_/ \_\_| |______|_| \_\_____|_| |_|______|_| \_| |_/_/ \_\______| - - /** - * - * @param n Number of variates to return - * @param loc Starting point. Must be a non-negative integer. 0 for degenerate distribution of 0. - * @param p Probability of moving towards finish - * @param cap Maximum steps before giving up - * @param trace Variable to track progress - * @returns {Array} Random variates array - * - * The FML distribution is a is based on the number of steps taken to return to the origin - * from a given position, with transition probabilities set at the beginning by picking a - * random variate from U(0,1). - */ - rfml: function (n, loc, p, cap, trace) { - n = this._v(n, "n"); - loc = this._v(loc, "nni", 1); - if(p === undefined) p=this.prng; - cap = this._v(cap, "n", 10000); - if(trace === undefined) trace={}; - - var toReturn = []; - - for(var i=0; i 0 && x < cap); - - if(x === cap) x = -1; // Indicate we failed to do it in time. - toReturn[i] = x; - } - } - return toReturn - }, - - // http://www.statisticsblog.com/2013/05/uncovering-the-unreliable-friend-distribution-a-case-study-in-the-limits-of-mc-methods/ - /** - * - * The Unrelaible Friend distribution - * @param n - * @returns {Array} Random variates array - */ - ruf: function(n) { - n = this._v(n, "n"); - - var toReturn = []; - - for(var i=0; i Date: Sun, 11 Feb 2018 18:44:33 -0700 Subject: [PATCH 2/2] Replaced "conditions" eval with callback and incremented minimum package version due to compatibility break --- code/index.js | 10 ++-------- package.json | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/code/index.js b/code/index.js index 78b8232..e50481e 100644 --- a/code/index.js +++ b/code/index.js @@ -655,8 +655,6 @@ export function sample(collection, n, replace, ratios) { * IMPORTANT: This is not a "pure" function, it takes a DOM element ID as an argument and modifies (completely overwrites) * that element. * - * IMPORTANT 2: options.conditions are eval'd so don't send untrusted code sent to this function! - * */ export function visualize(data, domID, options) { var defaultOptions = { @@ -664,7 +662,7 @@ export function visualize(data, domID, options) { blank: "", // What to show when no number is shown inf: "∞", // Symbol to indicate infinity loop: true, // When done, start again at beginning at end of array - conditions: "", // This is eval'd and checked against true. Example, "x > 3 && x < 10". Use "x" as the variable. + conditions: null, // this optional filter function is passed "x" and should return `true` to show the value arrivalTimes: false, arrivalSymbol: '', arrivalFlashTime: 0.25, @@ -693,11 +691,7 @@ export function visualize(data, domID, options) { // Are we showing only certain things if(options.conditions) { - - // Check for only allowed characters, this is NOT complete security - if(!/^[x\&\|=0-9\<\>\s\-\.]+$/.test(options.conditions)) throw new Error("Bad input sent to options.conditions"); - - if (eval(options.conditions) !== true) { + if (options.conditions(x) !== true) { x = options.blank; return x } diff --git a/package.json b/package.json index 6a96dfc..6241ff2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "probability-distributions", - "version": "0.9.1", + "version": "0.10.0-0", "description": "Generate random variables from a variety of probability distributions. Includes tools to shuffle an array or sample from it.", "main": "dist/index.js", "module": "dist/probability-distributions.esm.js",