1 | 'use strict'
|
2 |
|
3 | const ArgumentsError = require('../../error/ArgumentsError')
|
4 | const isCollection = require('../../utils/collection/isCollection')
|
5 | const isNumber = require('../../utils/number').isNumber
|
6 |
|
7 |
|
8 |
|
9 | function factory (type, config, load, typed, math) {
|
10 | const matrix = load(require('../../type/matrix/function/matrix'))
|
11 | const array = require('../../utils/array')
|
12 |
|
13 |
|
14 | const rng = load(require('./seededRNG'))
|
15 |
|
16 | |
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
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 |
|
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 |
|
56 | if (isCollection(arg1)) {
|
57 | size = arg1
|
58 | } else {
|
59 | max = arg1
|
60 | }
|
61 | } else if (arguments.length === 2) {
|
62 |
|
63 | if (isCollection(arg1)) {
|
64 | size = arg1
|
65 | max = arg2
|
66 | } else {
|
67 | min = arg1
|
68 | max = arg2
|
69 | }
|
70 | } else {
|
71 |
|
72 | size = arg1
|
73 | min = arg2
|
74 | max = arg3
|
75 | }
|
76 |
|
77 |
|
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()
|
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 |
|
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 |
|
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 |
|
262 |
|
263 | let distributions = {
|
264 |
|
265 | uniform: function () {
|
266 | return rng
|
267 | },
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 | normal: function () {
|
274 | return function () {
|
275 | let u1
|
276 | let u2
|
277 | let picked = -1
|
278 |
|
279 |
|
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
|
291 |
|
292 | return distribution
|
293 | }
|
294 |
|
295 | exports.name = 'distribution'
|
296 | exports.factory = factory
|