mirror of
https://github.com/josdejong/mathjs.git
synced 2026-01-18 14:59:29 +00:00
292 lines
8.8 KiB
JavaScript
292 lines
8.8 KiB
JavaScript
'use strict'
|
|
|
|
import { isCollection, isMatrix, isNumber } from '../../utils/is'
|
|
import { ArgumentsError } from '../../error/ArgumentsError'
|
|
import { arraySize } from '../../utils/array'
|
|
import { factory } from '../../utils/factory'
|
|
import { createRng } from './util/seededRNG'
|
|
|
|
const name = 'distribution'
|
|
const dependencies = ['typed', 'matrix', 'config.randomSeed']
|
|
|
|
// TODO: rethink math.distribution
|
|
// TODO: rework to a typed function
|
|
export const createDistribution = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, config: { randomSeed } }) => {
|
|
// seeded pseudo random number generator
|
|
const rng = createRng(randomSeed)
|
|
|
|
/**
|
|
* 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) }
|
|
|
|
const args = Array.prototype.slice.call(arguments, 1)
|
|
const distribution = distributions[name].apply(this, args)
|
|
|
|
return (function (distribution) {
|
|
// This is the public API for all distributions
|
|
return {
|
|
|
|
random: function (arg1, arg2, arg3) {
|
|
let 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) {
|
|
const res = _randomDataForMatrix(size.valueOf(), min, max, _random)
|
|
return isMatrix(size) ? matrix(res) : res
|
|
}
|
|
return _random(min, max)
|
|
},
|
|
|
|
randomInt: typed({
|
|
'number | Array': function (arg) {
|
|
const min = 0
|
|
|
|
if (isCollection(arg)) {
|
|
const size = arg
|
|
const max = 1
|
|
const res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt)
|
|
return !isMatrix(size) ? res : matrix(res)
|
|
} else {
|
|
const max = arg
|
|
return _randomInt(min, max)
|
|
}
|
|
},
|
|
'number | Array, number': function (arg1, arg2) {
|
|
if (isCollection(arg1)) {
|
|
const size = arg1
|
|
const max = arg2
|
|
const min = 0
|
|
const res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt)
|
|
return isMatrix(size) ? matrix(res) : res
|
|
} else {
|
|
const min = arg1
|
|
const max = arg2
|
|
return _randomInt(min, max)
|
|
}
|
|
},
|
|
'Array, number, number': function (size, min, max) {
|
|
const res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt)
|
|
return (size && size.isMatrix === true) ? matrix(res) : res
|
|
}
|
|
}),
|
|
|
|
pickRandom: typed({
|
|
'Array': function (possibles) {
|
|
return _pickRandom(possibles)
|
|
},
|
|
'Array, number | Array': function (possibles, arg2) {
|
|
let 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 (possibles, arg2, arg3) {
|
|
let 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) {
|
|
const single = (typeof number === 'undefined')
|
|
|
|
if (single) {
|
|
number = 1
|
|
}
|
|
|
|
if (isMatrix(possibles)) {
|
|
possibles = possibles.valueOf() // get Array
|
|
} else if (!Array.isArray(possibles)) {
|
|
throw new TypeError('Unsupported type of value in function pickRandom')
|
|
}
|
|
|
|
if (arraySize(possibles).length > 1) {
|
|
throw new Error('Only one dimensional vectors supported')
|
|
}
|
|
|
|
let totalWeights = 0
|
|
|
|
if (typeof weights !== 'undefined') {
|
|
if (weights.length !== possibles.length) {
|
|
throw new Error('Weights must have the same length as possibles')
|
|
}
|
|
|
|
for (let 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]
|
|
}
|
|
}
|
|
|
|
const length = possibles.length
|
|
|
|
if (length === 0) {
|
|
return []
|
|
} else if (number >= length) {
|
|
return number > 1 ? possibles : possibles[0]
|
|
}
|
|
|
|
const result = []
|
|
let pick
|
|
|
|
while (result.length < number) {
|
|
if (typeof weights === 'undefined') {
|
|
pick = possibles[Math.floor(rng() * length)]
|
|
} else {
|
|
let randKey = rng() * totalWeights
|
|
|
|
for (let 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) {
|
|
const data = []
|
|
size = size.slice(0)
|
|
|
|
if (size.length > 1) {
|
|
for (let i = 0, length = size.shift(); i < length; i++) {
|
|
data.push(_randomDataForMatrix(size, min, max, randFunc))
|
|
}
|
|
} else {
|
|
for (let i = 0, length = size.shift(); i < length; i++) {
|
|
data.push(randFunc(min, max))
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
})(distribution)
|
|
}
|
|
|
|
// Each distribution is a function that takes no argument and when called returns
|
|
// a number between 0 and 1.
|
|
let distributions = {
|
|
|
|
uniform: function () {
|
|
return rng
|
|
},
|
|
|
|
// Implementation of normal distribution using Box-Muller transform
|
|
// ref : http://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 () {
|
|
return function () {
|
|
let u1
|
|
let u2
|
|
let 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
|
|
}
|
|
}
|
|
}
|
|
|
|
return distribution
|
|
})
|