mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif
301 lines (244 loc) • 9.23 kB
JavaScript
;
var ArgumentsError = require('../../error/ArgumentsError');
var isCollection = require('../../utils/collection/isCollection');
var isNumber = require('../../utils/number').isNumber; // TODO: rethink math.distribution
// TODO: rework to a typed function
function factory(type, config, load, typed, math) {
var matrix = load(require('../../type/matrix/function/matrix'));
var array = require('../../utils/array'); // seeded pseudo random number generator
var rng = load(require('./seededRNG'));
/**
* Create a distribution object with a set of random functions for given
* random distribution.
*
* Syntax:
*
* math.distribution(name)
*
* Examples:
*
* const normalDist = math.distribution('normal') // create a normal distribution
* normalDist.random(0, 10) // get a random value between 0 and 10
*
* See also:
*
* random, randomInt, pickRandom
*
* @param {string} name Name of a distribution. Choose from 'uniform', 'normal'.
* @return {Object} Returns a distribution object containing functions:
* `random([size] [, min] [, max])`,
* `randomInt([min] [, max])`,
* `pickRandom(array)`
*/
function distribution(name) {
if (!distributions.hasOwnProperty(name)) {
throw new Error('Unknown distribution ' + name);
}
var args = Array.prototype.slice.call(arguments, 1);
var distribution = distributions[name].apply(this, args);
return function (distribution) {
// This is the public API for all distributions
var randFunctions = {
random: function random(arg1, arg2, arg3) {
var size, min, max;
if (arguments.length > 3) {
throw new ArgumentsError('random', arguments.length, 0, 3);
} else if (arguments.length === 1) {
// `random(max)` or `random(size)`
if (isCollection(arg1)) {
size = arg1;
} else {
max = arg1;
}
} else if (arguments.length === 2) {
// `random(min, max)` or `random(size, max)`
if (isCollection(arg1)) {
size = arg1;
max = arg2;
} else {
min = arg1;
max = arg2;
}
} else {
// `random(size, min, max)`
size = arg1;
min = arg2;
max = arg3;
} // TODO: validate type of size
if (min !== undefined && !isNumber(min) || max !== undefined && !isNumber(max)) {
throw new TypeError('Invalid argument in function random');
}
if (max === undefined) max = 1;
if (min === undefined) min = 0;
if (size !== undefined) {
var res = _randomDataForMatrix(size.valueOf(), min, max, _random);
return type.isMatrix(size) ? matrix(res) : res;
}
return _random(min, max);
},
randomInt: typed({
'number | Array': function numberArray(arg) {
var min = 0;
if (isCollection(arg)) {
var size = arg;
var max = 1;
var res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
return type.isMatrix(size) ? matrix(res) : res;
} else {
var _max = arg;
return _randomInt(min, _max);
}
},
'number | Array, number': function numberArrayNumber(arg1, arg2) {
if (isCollection(arg1)) {
var size = arg1;
var max = arg2;
var min = 0;
var res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
return type.isMatrix(size) ? matrix(res) : res;
} else {
var _min = arg1;
var _max2 = arg2;
return _randomInt(_min, _max2);
}
},
'Array, number, number': function ArrayNumberNumber(size, min, max) {
var res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
return size && size.isMatrix === true ? matrix(res) : res;
}
}),
pickRandom: typed({
'Array': function Array(possibles) {
return _pickRandom(possibles);
},
'Array, number | Array': function ArrayNumberArray(possibles, arg2) {
var number, weights;
if (Array.isArray(arg2)) {
weights = arg2;
} else if (isNumber(arg2)) {
number = arg2;
} else {
throw new TypeError('Invalid argument in function pickRandom');
}
return _pickRandom(possibles, number, weights);
},
'Array, number | Array, Array | number': function ArrayNumberArrayArrayNumber(possibles, arg2, arg3) {
var number, weights;
if (Array.isArray(arg2)) {
weights = arg2;
number = arg3;
} else {
weights = arg3;
number = arg2;
}
if (!Array.isArray(weights) || !isNumber(number)) {
throw new TypeError('Invalid argument in function pickRandom');
}
return _pickRandom(possibles, number, weights);
}
})
};
function _pickRandom(possibles, number, weights) {
var single = typeof number === 'undefined';
if (single) {
number = 1;
}
if (type.isMatrix(possibles)) {
possibles = possibles.valueOf(); // get Array
} else if (!Array.isArray(possibles)) {
throw new TypeError('Unsupported type of value in function pickRandom');
}
if (array.size(possibles).length > 1) {
throw new Error('Only one dimensional vectors supported');
}
var totalWeights = 0;
if (typeof weights !== 'undefined') {
if (weights.length !== possibles.length) {
throw new Error('Weights must have the same length as possibles');
}
for (var i = 0, len = weights.length; i < len; i++) {
if (!isNumber(weights[i]) || weights[i] < 0) {
throw new Error('Weights must be an array of positive numbers');
}
totalWeights += weights[i];
}
}
var length = possibles.length;
if (length === 0) {
return [];
} else if (number >= length) {
return number > 1 ? possibles : possibles[0];
}
var result = [];
var pick;
while (result.length < number) {
if (typeof weights === 'undefined') {
pick = possibles[Math.floor(rng() * length)];
} else {
var randKey = rng() * totalWeights;
for (var _i = 0, _len = possibles.length; _i < _len; _i++) {
randKey -= weights[_i];
if (randKey < 0) {
pick = possibles[_i];
break;
}
}
}
if (result.indexOf(pick) === -1) {
result.push(pick);
}
}
return single ? result[0] : result; // TODO: add support for multi dimensional matrices
}
function _random(min, max) {
return min + distribution() * (max - min);
}
function _randomInt(min, max) {
return Math.floor(min + distribution() * (max - min));
} // This is a function for generating a random matrix recursively.
function _randomDataForMatrix(size, min, max, randFunc) {
var data = [];
size = size.slice(0);
if (size.length > 1) {
for (var i = 0, length = size.shift(); i < length; i++) {
data.push(_randomDataForMatrix(size, min, max, randFunc));
}
} else {
for (var _i2 = 0, _length = size.shift(); _i2 < _length; _i2++) {
data.push(randFunc(min, max));
}
}
return data;
}
return randFunctions;
}(distribution);
} // Each distribution is a function that takes no argument and when called returns
// a number between 0 and 1.
var distributions = {
uniform: function uniform() {
return rng;
},
// Implementation of normal distribution using Box-Muller transform
// ref : https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
// We take : mean = 0.5, standard deviation = 1/6
// so that 99.7% values are in [0, 1].
normal: function normal() {
return function () {
var u1;
var u2;
var picked = -1; // We reject values outside of the interval [0, 1]
// TODO: check if it is ok to do that?
while (picked < 0 || picked > 1) {
u1 = rng();
u2 = rng();
picked = 1 / 6 * Math.pow(-2 * Math.log(u1), 0.5) * Math.cos(2 * Math.PI * u2) + 0.5;
}
return picked;
};
}
};
distribution.toTex = undefined; // use default template
return distribution;
}
exports.name = 'distribution';
exports.factory = factory;