UNPKG

9.08 kBJavaScriptView Raw
1'use strict'
2
3const ArgumentsError = require('../../error/ArgumentsError')
4const isCollection = require('../../utils/collection/isCollection')
5const isNumber = require('../../utils/number').isNumber
6
7// TODO: rethink math.distribution
8// TODO: rework to a typed function
9function factory (type, config, load, typed, math) {
10 const matrix = load(require('../../type/matrix/function/matrix'))
11 const array = require('../../utils/array')
12
13 // seeded pseudo random number generator
14 const rng = load(require('./seededRNG'))
15
16 /**
17 * Create a distribution object with a set of random functions for given
18 * random distribution.
19 *
20 * Syntax:
21 *
22 * math.distribution(name)
23 *
24 * Examples:
25 *
26 * const normalDist = math.distribution('normal') // create a normal distribution
27 * normalDist.random(0, 10) // get a random value between 0 and 10
28 *
29 * See also:
30 *
31 * random, randomInt, pickRandom
32 *
33 * @param {string} name Name of a distribution. Choose from 'uniform', 'normal'.
34 * @return {Object} Returns a distribution object containing functions:
35 * `random([size] [, min] [, max])`,
36 * `randomInt([min] [, max])`,
37 * `pickRandom(array)`
38 */
39 function distribution (name) {
40 if (!distributions.hasOwnProperty(name)) { throw new Error('Unknown distribution ' + name) }
41
42 const args = Array.prototype.slice.call(arguments, 1)
43 const distribution = distributions[name].apply(this, args)
44
45 return (function (distribution) {
46 // This is the public API for all distributions
47 const randFunctions = {
48
49 random: function (arg1, arg2, arg3) {
50 let size, min, max
51
52 if (arguments.length > 3) {
53 throw new ArgumentsError('random', arguments.length, 0, 3)
54 } else if (arguments.length === 1) {
55 // `random(max)` or `random(size)`
56 if (isCollection(arg1)) {
57 size = arg1
58 } else {
59 max = arg1
60 }
61 } else if (arguments.length === 2) {
62 // `random(min, max)` or `random(size, max)`
63 if (isCollection(arg1)) {
64 size = arg1
65 max = arg2
66 } else {
67 min = arg1
68 max = arg2
69 }
70 } else {
71 // `random(size, min, max)`
72 size = arg1
73 min = arg2
74 max = arg3
75 }
76
77 // TODO: validate type of size
78 if ((min !== undefined && !isNumber(min)) || (max !== undefined && !isNumber(max))) {
79 throw new TypeError('Invalid argument in function random')
80 }
81
82 if (max === undefined) max = 1
83 if (min === undefined) min = 0
84 if (size !== undefined) {
85 const res = _randomDataForMatrix(size.valueOf(), min, max, _random)
86 return type.isMatrix(size) ? matrix(res) : res
87 }
88 return _random(min, max)
89 },
90
91 randomInt: typed({
92 'number | Array': function (arg) {
93 const min = 0
94
95 if (isCollection(arg)) {
96 const size = arg
97 const max = 1
98 const res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt)
99 return type.isMatrix(size) ? matrix(res) : res
100 } else {
101 const max = arg
102 return _randomInt(min, max)
103 }
104 },
105 'number | Array, number': function (arg1, arg2) {
106 if (isCollection(arg1)) {
107 const size = arg1
108 const max = arg2
109 const min = 0
110 const res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt)
111 return type.isMatrix(size) ? matrix(res) : res
112 } else {
113 const min = arg1
114 const max = arg2
115 return _randomInt(min, max)
116 }
117 },
118 'Array, number, number': function (size, min, max) {
119 const res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt)
120 return (size && size.isMatrix === true) ? matrix(res) : res
121 }
122 }),
123
124 pickRandom: typed({
125 'Array': function (possibles) {
126 return _pickRandom(possibles)
127 },
128 'Array, number | Array': function (possibles, arg2) {
129 let number, weights
130
131 if (Array.isArray(arg2)) {
132 weights = arg2
133 } else if (isNumber(arg2)) {
134 number = arg2
135 } else {
136 throw new TypeError('Invalid argument in function pickRandom')
137 }
138
139 return _pickRandom(possibles, number, weights)
140 },
141 'Array, number | Array, Array | number': function (possibles, arg2, arg3) {
142 let number, weights
143
144 if (Array.isArray(arg2)) {
145 weights = arg2
146 number = arg3
147 } else {
148 weights = arg3
149 number = arg2
150 }
151
152 if (!Array.isArray(weights) || !isNumber(number)) {
153 throw new TypeError('Invalid argument in function pickRandom')
154 }
155
156 return _pickRandom(possibles, number, weights)
157 }
158 })
159 }
160
161 function _pickRandom (possibles, number, weights) {
162 const single = (typeof number === 'undefined')
163
164 if (single) {
165 number = 1
166 }
167
168 if (type.isMatrix(possibles)) {
169 possibles = possibles.valueOf() // get Array
170 } else if (!Array.isArray(possibles)) {
171 throw new TypeError('Unsupported type of value in function pickRandom')
172 }
173
174 if (array.size(possibles).length > 1) {
175 throw new Error('Only one dimensional vectors supported')
176 }
177
178 let totalWeights = 0
179
180 if (typeof weights !== 'undefined') {
181 if (weights.length !== possibles.length) {
182 throw new Error('Weights must have the same length as possibles')
183 }
184
185 for (let i = 0, len = weights.length; i < len; i++) {
186 if (!isNumber(weights[i]) || weights[i] < 0) {
187 throw new Error('Weights must be an array of positive numbers')
188 }
189
190 totalWeights += weights[i]
191 }
192 }
193
194 const length = possibles.length
195
196 if (length === 0) {
197 return []
198 } else if (number >= length) {
199 return number > 1 ? possibles : possibles[0]
200 }
201
202 const result = []
203 let pick
204
205 while (result.length < number) {
206 if (typeof weights === 'undefined') {
207 pick = possibles[Math.floor(rng() * length)]
208 } else {
209 let randKey = rng() * totalWeights
210
211 for (let i = 0, len = possibles.length; i < len; i++) {
212 randKey -= weights[i]
213
214 if (randKey < 0) {
215 pick = possibles[i]
216 break
217 }
218 }
219 }
220
221 if (result.indexOf(pick) === -1) {
222 result.push(pick)
223 }
224 }
225
226 return single ? result[0] : result
227
228 // TODO: add support for multi dimensional matrices
229 }
230
231 function _random (min, max) {
232 return min + distribution() * (max - min)
233 }
234
235 function _randomInt (min, max) {
236 return Math.floor(min + distribution() * (max - min))
237 }
238
239 // This is a function for generating a random matrix recursively.
240 function _randomDataForMatrix (size, min, max, randFunc) {
241 const data = []
242 size = size.slice(0)
243
244 if (size.length > 1) {
245 for (let i = 0, length = size.shift(); i < length; i++) {
246 data.push(_randomDataForMatrix(size, min, max, randFunc))
247 }
248 } else {
249 for (let i = 0, length = size.shift(); i < length; i++) {
250 data.push(randFunc(min, max))
251 }
252 }
253
254 return data
255 }
256
257 return randFunctions
258 })(distribution)
259 }
260
261 // Each distribution is a function that takes no argument and when called returns
262 // a number between 0 and 1.
263 let distributions = {
264
265 uniform: function () {
266 return rng
267 },
268
269 // Implementation of normal distribution using Box-Muller transform
270 // ref : http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
271 // We take : mean = 0.5, standard deviation = 1/6
272 // so that 99.7% values are in [0, 1].
273 normal: function () {
274 return function () {
275 let u1
276 let u2
277 let picked = -1
278 // We reject values outside of the interval [0, 1]
279 // TODO: check if it is ok to do that?
280 while (picked < 0 || picked > 1) {
281 u1 = rng()
282 u2 = rng()
283 picked = 1 / 6 * Math.pow(-2 * Math.log(u1), 0.5) * Math.cos(2 * Math.PI * u2) + 0.5
284 }
285 return picked
286 }
287 }
288 }
289
290 distribution.toTex = undefined // use default template
291
292 return distribution
293}
294
295exports.name = 'distribution'
296exports.factory = factory