-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
90 lines (76 loc) · 2.48 KB
/
index.js
File metadata and controls
90 lines (76 loc) · 2.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
'use strict'
module.exports = function zipfian (min, max, skew, rng) {
// Type checking is important here to prevent infinite loops in the algorithm.
if (!Number.isInteger(min)) {
throw new TypeError('The first argument "min" must be an integer')
} else if (!Number.isInteger(max)) {
throw new TypeError('The second argument "max" must be an integer')
} else if (max < min) {
throw new RangeError('The second argument "max" must be >= the "min" argument')
} else if (!Number.isFinite(skew)) {
throw new TypeError('The third argument "skew" must be a number')
}
const n = max - min + 1
const flip = skew < 0
const sampler = new ZipfRejectionInversionSampler(n, Math.abs(skew))
rng = rng || Math.random
return function sample () {
const k = sampler.sample(rng) - 1
return flip ? max - k : min + k
}
}
// A port of Apache Commons Math's ZipfRejectionInversionSampler.
// See Apache Commons Math v3.6.1 for the (annotated) source of this.
class ZipfRejectionInversionSampler {
constructor (numberOfElements, exponent) {
this.numberOfElements = numberOfElements
this.exponent = exponent
this.hIntegralX1 = this._hIntegral(1.5) - 1
this.hIntegralNumberOfElements = this._hIntegral(numberOfElements + 0.5)
this.s = 2 - this._hIntegralInverse(this._hIntegral(2.5) - this._h(2))
}
// Returns an integer in the range [1,N]
sample (rng) {
while (true) {
const u = this.hIntegralNumberOfElements + rng() * (this.hIntegralX1 - this.hIntegralNumberOfElements)
const x = this._hIntegralInverse(u)
let k = (x + 0.5) | 0
if (k < 1) {
k = 1
} else if (k > this.numberOfElements) {
k = this.numberOfElements
}
if ((k - x <= this.s) || (u >= this._hIntegral(k + 0.5) - this._h(k))) {
return k
}
}
}
_hIntegral (x) {
const logX = Math.log(x)
return helper2((1 - this.exponent) * logX) * logX
}
_h (x) {
return Math.exp(-this.exponent * Math.log(x))
}
_hIntegralInverse (x) {
let t = x * (1 - this.exponent)
if (t < -1) t = -1
return Math.exp(helper1(t) * x)
}
}
function helper1 (x) {
// TODO (perf): avoid Math.abs()
if (Math.abs(x) > 1e-8) {
return Math.log1p(x) / x
} else {
return 1 - x * ((1 / 2) - x * ((1 / 3) - x * (1 / 4)))
}
}
function helper2 (x) {
// TODO (perf): avoid Math.abs()
if (Math.abs(x) > 1e-8) {
return Math.expm1(x) / x
} else {
return 1 + x * (1 / 2) * (1 + x * (1 / 3) * (1 + x * (1 / 4)))
}
}