@cloudglides/nox 1.1.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/example/package.json +1 -1
- package/example/src/App.css +38 -7
- package/example/src/App.jsx +328 -25
- package/package.json +7 -6
- package/pnpm-workspace.yaml +3 -0
- package/src/core.browser.js +37 -4
- package/src/core.js +37 -4
- package/src/generators/logistic.js +30 -25
- package/src/generators/mixer.js +7 -7
- package/src/generators/mt19937.js +10 -7
- package/src/generators/pcg64.js +23 -12
- package/src/generators/splitmix64.js +12 -6
- package/src/generators/tent.js +12 -7
- package/src/generators/xorshift64.js +6 -3
- package/src/index.browser.js +168 -1
- package/src/index.d.ts +68 -4
- package/src/index.js +169 -1
- package/src/rng.browser.js +5 -6
- package/src/rng.js +95 -94
- package/src/utils/arrays.js +149 -0
- package/src/utils/bits.js +146 -21
- package/src/utils/categorical.js +68 -31
- package/src/utils/clustering.js +143 -0
- package/src/utils/combinatorics.js +113 -69
- package/src/utils/confidence.js +145 -0
- package/src/utils/decomposition.js +204 -0
- package/src/utils/diagnostics.js +309 -0
- package/src/utils/distributions-advanced.js +122 -0
- package/src/utils/distributions-extra.js +102 -11
- package/src/utils/distributions-special.js +77 -20
- package/src/utils/distributions.js +99 -35
- package/src/utils/effects.js +172 -0
- package/src/utils/entropy.browser.js +29 -26
- package/src/utils/entropy.js +18 -8
- package/src/utils/helpers.js +64 -0
- package/src/utils/hypothesis.js +167 -0
- package/src/utils/integration.js +137 -0
- package/src/utils/interpolation.js +221 -0
- package/src/utils/logistic.js +91 -0
- package/src/utils/matrix.js +242 -0
- package/src/utils/noise.js +36 -22
- package/src/utils/odesolvers.js +176 -0
- package/src/utils/optimization.js +215 -0
- package/src/utils/polynomial.js +136 -0
- package/src/utils/precomputed.js +166 -0
- package/src/utils/probability.js +199 -0
- package/src/utils/pvalues.js +142 -0
- package/src/utils/regression.js +170 -0
- package/src/utils/resampling.js +112 -0
- package/src/utils/rootfinding.js +158 -0
- package/src/utils/sampling.js +86 -77
- package/src/utils/seed.js +10 -4
- package/src/utils/seeding.js +24 -12
- package/src/utils/sequence.js +116 -32
- package/src/utils/state.js +48 -36
- package/src/utils/statistics.js +64 -2
- package/src/utils/stochastic.js +91 -31
- package/src/utils/stratified.js +108 -0
- package/src/utils/timeseries.js +166 -0
- package/src/utils/transforms.js +146 -0
- package/test/comprehensive.js +4 -3
- package/test/new-features.js +52 -0
- package/IMPROVEMENTS.md +0 -58
- package/PERFORMANCE.md +0 -69
- package/example/pnpm-lock.yaml +0 -1006
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
export function gradientDescent(f, df, x0, learningRate = 0.01, iterations = 100, tol = 1e-6) {
|
|
2
|
+
if (typeof f !== 'function' || typeof df !== 'function') {
|
|
3
|
+
throw new TypeError('f and df must be functions');
|
|
4
|
+
}
|
|
5
|
+
if (typeof x0 !== 'number') {
|
|
6
|
+
throw new TypeError('x0 must be number');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let x = x0;
|
|
10
|
+
const history = [x];
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < iterations; i++) {
|
|
13
|
+
const grad = df(x);
|
|
14
|
+
const xNew = x - learningRate * grad;
|
|
15
|
+
|
|
16
|
+
if (Math.abs(xNew - x) < tol) {
|
|
17
|
+
history.push(xNew);
|
|
18
|
+
return { x: xNew, iterations: i + 1, value: f(xNew), history };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
x = xNew;
|
|
22
|
+
history.push(x);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { x, iterations, value: f(x), history };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function momentumDescent(f, df, x0, learningRate = 0.01, momentum = 0.9, iterations = 100, tol = 1e-6) {
|
|
29
|
+
if (typeof f !== 'function' || typeof df !== 'function') {
|
|
30
|
+
throw new TypeError('f and df must be functions');
|
|
31
|
+
}
|
|
32
|
+
if (typeof x0 !== 'number') {
|
|
33
|
+
throw new TypeError('x0 must be number');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let x = x0;
|
|
37
|
+
let velocity = 0;
|
|
38
|
+
const history = [x];
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < iterations; i++) {
|
|
41
|
+
const grad = df(x);
|
|
42
|
+
velocity = momentum * velocity + learningRate * grad;
|
|
43
|
+
const xNew = x - velocity;
|
|
44
|
+
|
|
45
|
+
if (Math.abs(xNew - x) < tol) {
|
|
46
|
+
history.push(xNew);
|
|
47
|
+
return { x: xNew, iterations: i + 1, value: f(xNew), history };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
x = xNew;
|
|
51
|
+
history.push(x);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { x, iterations, value: f(x), history };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function adam(f, df, x0, learningRate = 0.001, iterations = 100, beta1 = 0.9, beta2 = 0.999, eps = 1e-8, tol = 1e-6) {
|
|
58
|
+
if (typeof f !== 'function' || typeof df !== 'function') {
|
|
59
|
+
throw new TypeError('f and df must be functions');
|
|
60
|
+
}
|
|
61
|
+
if (typeof x0 !== 'number') {
|
|
62
|
+
throw new TypeError('x0 must be number');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let x = x0;
|
|
66
|
+
let m = 0;
|
|
67
|
+
let v = 0;
|
|
68
|
+
const history = [x];
|
|
69
|
+
|
|
70
|
+
for (let t = 1; t <= iterations; t++) {
|
|
71
|
+
const grad = df(x);
|
|
72
|
+
|
|
73
|
+
m = beta1 * m + (1 - beta1) * grad;
|
|
74
|
+
v = beta2 * v + (1 - beta2) * grad * grad;
|
|
75
|
+
|
|
76
|
+
const mHat = m / (1 - Math.pow(beta1, t));
|
|
77
|
+
const vHat = v / (1 - Math.pow(beta2, t));
|
|
78
|
+
|
|
79
|
+
const xNew = x - learningRate * mHat / (Math.sqrt(vHat) + eps);
|
|
80
|
+
|
|
81
|
+
if (Math.abs(xNew - x) < tol) {
|
|
82
|
+
history.push(xNew);
|
|
83
|
+
return { x: xNew, iterations: t, value: f(xNew), history };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
x = xNew;
|
|
87
|
+
history.push(x);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { x, iterations, value: f(x), history };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function simplex(f, vertices, maxIter = 1000, tol = 1e-6) {
|
|
94
|
+
if (typeof f !== 'function') {
|
|
95
|
+
throw new TypeError('f must be function');
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(vertices) || vertices.length < 2) {
|
|
98
|
+
throw new TypeError('vertices must be array of points');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const n = vertices[0].length;
|
|
102
|
+
let currentVertices = vertices.map(v => [...v]);
|
|
103
|
+
|
|
104
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
105
|
+
const values = currentVertices.map(f);
|
|
106
|
+
|
|
107
|
+
const sorted = currentVertices.map((v, i) => [v, values[i]])
|
|
108
|
+
.sort((a, b) => a[1] - b[1]);
|
|
109
|
+
|
|
110
|
+
if (sorted[sorted.length - 1][1] - sorted[0][1] < tol) {
|
|
111
|
+
return { min: sorted[0][0], value: sorted[0][1], iterations: iter };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const best = sorted.slice(0, n).map(x => x[0]);
|
|
115
|
+
const worst = sorted[sorted.length - 1][0];
|
|
116
|
+
|
|
117
|
+
const centroid = new Array(n);
|
|
118
|
+
for (let i = 0; i < n; i++) {
|
|
119
|
+
centroid[i] = best.reduce((sum, v) => sum + v[i], 0) / n;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const reflected = centroid.map((x, i) => x + (x - worst[i]));
|
|
123
|
+
const reflectedValue = f(reflected);
|
|
124
|
+
|
|
125
|
+
if (reflectedValue < sorted[0][1]) {
|
|
126
|
+
currentVertices = [...best, reflected];
|
|
127
|
+
} else if (reflectedValue < sorted[n - 1][1]) {
|
|
128
|
+
currentVertices = [...best, reflected];
|
|
129
|
+
} else {
|
|
130
|
+
const contracted = centroid.map((x, i) => x + 0.5 * (worst[i] - x));
|
|
131
|
+
currentVertices = [...best, contracted];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const values = currentVertices.map(f);
|
|
136
|
+
const minIdx = values.indexOf(Math.min(...values));
|
|
137
|
+
return { min: currentVertices[minIdx], value: values[minIdx], iterations: maxIter };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function brent(f, a, b, tol = 1e-6, maxIter = 100) {
|
|
141
|
+
if (typeof f !== 'function') {
|
|
142
|
+
throw new TypeError('f must be function');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let fa = f(a);
|
|
146
|
+
let fb = f(b);
|
|
147
|
+
|
|
148
|
+
if (fa * fb > 0) {
|
|
149
|
+
throw new RangeError('f(a) and f(b) must have opposite signs');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (Math.abs(fa) < Math.abs(fb)) {
|
|
153
|
+
[a, b] = [b, a];
|
|
154
|
+
[fa, fb] = [fb, fa];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let c = a;
|
|
158
|
+
let fc = fa;
|
|
159
|
+
let d = b - a;
|
|
160
|
+
let e = d;
|
|
161
|
+
|
|
162
|
+
for (let i = 0; i < maxIter; i++) {
|
|
163
|
+
if (Math.abs(fc) < Math.abs(fb)) {
|
|
164
|
+
a = b;
|
|
165
|
+
b = c;
|
|
166
|
+
c = a;
|
|
167
|
+
fa = fb;
|
|
168
|
+
fb = fc;
|
|
169
|
+
fc = fa;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const tol1 = tol * Math.abs(b) + 1e-12;
|
|
173
|
+
const xm = 0.5 * (c - b);
|
|
174
|
+
|
|
175
|
+
if (Math.abs(xm) <= tol1 || Math.abs(fb) < 1e-14) {
|
|
176
|
+
return { root: b, iterations: i + 1, residual: Math.abs(fb) };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (Math.abs(e) >= tol1 && Math.abs(fa) > Math.abs(fb)) {
|
|
180
|
+
let s = fb / fa;
|
|
181
|
+
let p, q;
|
|
182
|
+
|
|
183
|
+
if (Math.abs(a - c) < 1e-14) {
|
|
184
|
+
p = 2 * xm * s;
|
|
185
|
+
q = 1 - s;
|
|
186
|
+
} else {
|
|
187
|
+
q = fa / fc;
|
|
188
|
+
const r = fb / fc;
|
|
189
|
+
p = s * (2 * xm * q * (q - r) - (b - a) * (r - 1));
|
|
190
|
+
q = (q - 1) * (r - 1) * (s - 1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (p > 0) q = -q;
|
|
194
|
+
else p = -p;
|
|
195
|
+
|
|
196
|
+
if (2 * p < 3 * xm * q - Math.abs(tol1 * q) && p < Math.abs(0.5 * e * q)) {
|
|
197
|
+
e = d;
|
|
198
|
+
d = p / q;
|
|
199
|
+
} else {
|
|
200
|
+
d = xm;
|
|
201
|
+
e = d;
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
d = xm;
|
|
205
|
+
e = d;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
a = b;
|
|
209
|
+
fa = fb;
|
|
210
|
+
b += Math.abs(d) > tol1 ? d : (xm > 0 ? tol1 : -tol1);
|
|
211
|
+
fb = f(b);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { root: b, iterations: maxIter, residual: Math.abs(fb) };
|
|
215
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export class PolynomialRegression {
|
|
2
|
+
constructor(x, y, degree = 2) {
|
|
3
|
+
if (!Array.isArray(x) || !Array.isArray(y)) {
|
|
4
|
+
throw new TypeError('x and y must be arrays');
|
|
5
|
+
}
|
|
6
|
+
if (x.length !== y.length || x.length < 2) {
|
|
7
|
+
throw new RangeError('x and y must have same length >= 2');
|
|
8
|
+
}
|
|
9
|
+
if (typeof degree !== 'number' || degree < 1) {
|
|
10
|
+
throw new RangeError('degree must be positive integer');
|
|
11
|
+
}
|
|
12
|
+
if (x.length <= degree) {
|
|
13
|
+
throw new RangeError('need at least degree+1 data points');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const n = x.length;
|
|
17
|
+
this.degree = degree;
|
|
18
|
+
|
|
19
|
+
const X = x.map(xi => {
|
|
20
|
+
const row = [1];
|
|
21
|
+
for (let d = 1; d <= degree; d++) {
|
|
22
|
+
row.push(Math.pow(xi, d));
|
|
23
|
+
}
|
|
24
|
+
return row;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const XtX = matmul(transpose(X), X);
|
|
28
|
+
const Xty = matmul(transpose(X), y.map(yi => [yi]));
|
|
29
|
+
|
|
30
|
+
this.coefficients = solve(XtX, Xty).flat();
|
|
31
|
+
|
|
32
|
+
const yPred = x.map(xi => {
|
|
33
|
+
let pred = this.coefficients[0];
|
|
34
|
+
for (let d = 1; d <= degree; d++) {
|
|
35
|
+
pred += this.coefficients[d] * Math.pow(xi, d);
|
|
36
|
+
}
|
|
37
|
+
return pred;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const meanY = y.reduce((a, b) => a + b, 0) / n;
|
|
41
|
+
let ssRes = 0, ssTot = 0;
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < n; i++) {
|
|
44
|
+
ssRes += (y[i] - yPred[i]) ** 2;
|
|
45
|
+
ssTot += (y[i] - meanY) ** 2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.rSquared = 1 - ssRes / ssTot;
|
|
49
|
+
this.adjRSquared = 1 - (1 - this.rSquared) * (n - 1) / (n - degree - 1);
|
|
50
|
+
this.rmse = Math.sqrt(ssRes / n);
|
|
51
|
+
this.predictions = yPred;
|
|
52
|
+
this.residuals = y.map((yi, i) => yi - yPred[i]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
predict(x) {
|
|
56
|
+
if (typeof x === 'number') {
|
|
57
|
+
let pred = this.coefficients[0];
|
|
58
|
+
for (let d = 1; d <= this.degree; d++) {
|
|
59
|
+
pred += this.coefficients[d] * Math.pow(x, d);
|
|
60
|
+
}
|
|
61
|
+
return pred;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(x)) {
|
|
65
|
+
return x.map(xi => {
|
|
66
|
+
let pred = this.coefficients[0];
|
|
67
|
+
for (let d = 1; d <= this.degree; d++) {
|
|
68
|
+
pred += this.coefficients[d] * Math.pow(xi, d);
|
|
69
|
+
}
|
|
70
|
+
return pred;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw new TypeError('x must be number or array');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function transpose(matrix) {
|
|
79
|
+
const result = [];
|
|
80
|
+
for (let j = 0; j < matrix[0].length; j++) {
|
|
81
|
+
result[j] = [];
|
|
82
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
83
|
+
result[j].push(matrix[i][j]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function matmul(A, B) {
|
|
90
|
+
const result = [];
|
|
91
|
+
for (let i = 0; i < A.length; i++) {
|
|
92
|
+
result[i] = [];
|
|
93
|
+
for (let j = 0; j < B[0].length; j++) {
|
|
94
|
+
let sum = 0;
|
|
95
|
+
for (let k = 0; k < A[0].length; k++) {
|
|
96
|
+
sum += A[i][k] * B[k][j];
|
|
97
|
+
}
|
|
98
|
+
result[i][j] = sum;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function solve(A, b) {
|
|
105
|
+
const n = A.length;
|
|
106
|
+
const aug = A.map((row, i) => [...row, b[i][0]]);
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < n; i++) {
|
|
109
|
+
let maxRow = i;
|
|
110
|
+
for (let k = i + 1; k < n; k++) {
|
|
111
|
+
if (Math.abs(aug[k][i]) > Math.abs(aug[maxRow][i])) {
|
|
112
|
+
maxRow = k;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
[aug[i], aug[maxRow]] = [aug[maxRow], aug[i]];
|
|
117
|
+
|
|
118
|
+
for (let k = i + 1; k < n; k++) {
|
|
119
|
+
const factor = aug[k][i] / aug[i][i];
|
|
120
|
+
for (let j = i; j < n + 1; j++) {
|
|
121
|
+
aug[k][j] -= factor * aug[i][j];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const x = new Array(n);
|
|
127
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
128
|
+
x[i] = aug[i][n];
|
|
129
|
+
for (let j = i + 1; j < n; j++) {
|
|
130
|
+
x[i] -= aug[i][j] * x[j];
|
|
131
|
+
}
|
|
132
|
+
x[i] /= aug[i][i];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return x.map(xi => [xi]);
|
|
136
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export class WeightedDistribution {
|
|
2
|
+
constructor(items, weights) {
|
|
3
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
4
|
+
throw new TypeError('items must be non-empty array');
|
|
5
|
+
}
|
|
6
|
+
if (!Array.isArray(weights) || weights.length !== items.length) {
|
|
7
|
+
throw new TypeError('weights length must match items');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
this.items = items;
|
|
11
|
+
this.cumsum = new Array(items.length);
|
|
12
|
+
let total = 0;
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < weights.length; i++) {
|
|
15
|
+
if (typeof weights[i] !== 'number' || weights[i] < 0) {
|
|
16
|
+
throw new RangeError('all weights must be non-negative numbers');
|
|
17
|
+
}
|
|
18
|
+
total += weights[i];
|
|
19
|
+
this.cumsum[i] = total;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (total <= 0) {
|
|
23
|
+
throw new RangeError('weights must sum to positive value');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.total = total;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pick(rng) {
|
|
30
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
31
|
+
throw new TypeError('rng must be an RNG instance');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const rand = rng.nextFloat() * this.total;
|
|
35
|
+
let left = 0, right = this.cumsum.length;
|
|
36
|
+
|
|
37
|
+
while (left < right) {
|
|
38
|
+
const mid = (left + right) >> 1;
|
|
39
|
+
if (rand < this.cumsum[mid]) {
|
|
40
|
+
right = mid;
|
|
41
|
+
} else {
|
|
42
|
+
left = mid + 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return this.items[left];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
sample(rng, n) {
|
|
50
|
+
if (typeof n !== 'number' || n < 1) {
|
|
51
|
+
throw new RangeError('n must be positive integer');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = new Array(n);
|
|
55
|
+
for (let i = 0; i < n; i++) {
|
|
56
|
+
result[i] = this.pick(rng);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class CategoricalDistribution {
|
|
63
|
+
constructor(probabilities) {
|
|
64
|
+
if (!Array.isArray(probabilities) || probabilities.length === 0) {
|
|
65
|
+
throw new TypeError('probabilities must be non-empty array');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.cumsum = new Array(probabilities.length);
|
|
69
|
+
let total = 0;
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < probabilities.length; i++) {
|
|
72
|
+
if (typeof probabilities[i] !== 'number' || probabilities[i] < 0) {
|
|
73
|
+
throw new RangeError('all probabilities must be non-negative');
|
|
74
|
+
}
|
|
75
|
+
total += probabilities[i];
|
|
76
|
+
this.cumsum[i] = total;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (Math.abs(total - 1) > 1e-10) {
|
|
80
|
+
for (let i = 0; i < this.cumsum.length; i++) {
|
|
81
|
+
this.cumsum[i] /= total;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.n = probabilities.length;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
sample(rng) {
|
|
89
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
90
|
+
throw new TypeError('rng must be an RNG instance');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const u = rng.nextFloat();
|
|
94
|
+
let left = 0, right = this.cumsum.length;
|
|
95
|
+
|
|
96
|
+
while (left < right) {
|
|
97
|
+
const mid = (left + right) >> 1;
|
|
98
|
+
if (u < this.cumsum[mid]) {
|
|
99
|
+
right = mid;
|
|
100
|
+
} else {
|
|
101
|
+
left = mid + 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return left;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
samples(rng, n) {
|
|
109
|
+
if (typeof n !== 'number' || n < 1) {
|
|
110
|
+
throw new RangeError('n must be positive integer');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result = new Array(n);
|
|
114
|
+
for (let i = 0; i < n; i++) {
|
|
115
|
+
result[i] = this.sample(rng);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export class NormalDistribution {
|
|
122
|
+
constructor(mean = 0, stddev = 1) {
|
|
123
|
+
if (typeof mean !== 'number') {
|
|
124
|
+
throw new TypeError('mean must be number');
|
|
125
|
+
}
|
|
126
|
+
if (typeof stddev !== 'number' || stddev <= 0) {
|
|
127
|
+
throw new RangeError('stddev must be positive');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.mean = mean;
|
|
131
|
+
this.stddev = stddev;
|
|
132
|
+
this.hasSpare = false;
|
|
133
|
+
this.spare = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
sample(rng) {
|
|
137
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
138
|
+
throw new TypeError('rng must be an RNG instance');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (this.hasSpare) {
|
|
142
|
+
this.hasSpare = false;
|
|
143
|
+
return this.spare * this.stddev + this.mean;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.hasSpare = true;
|
|
147
|
+
const u1 = rng.nextFloat();
|
|
148
|
+
const u2 = rng.nextFloat();
|
|
149
|
+
const mag = Math.sqrt(-2 * Math.log(u1 > 1e-10 ? u1 : 1e-10));
|
|
150
|
+
|
|
151
|
+
this.spare = Math.sin(2 * Math.PI * u2) * mag;
|
|
152
|
+
return Math.cos(2 * Math.PI * u2) * mag * this.stddev + this.mean;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
samples(rng, n) {
|
|
156
|
+
if (typeof n !== 'number' || n < 1) {
|
|
157
|
+
throw new RangeError('n must be positive integer');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const result = new Array(n);
|
|
161
|
+
for (let i = 0; i < n; i++) {
|
|
162
|
+
result[i] = this.sample(rng);
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
export function cdf(x, distribution = 'uniform', ...params) {
|
|
2
|
+
if (typeof x !== 'number') {
|
|
3
|
+
throw new TypeError('x must be a number');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
switch (distribution.toLowerCase()) {
|
|
7
|
+
case 'uniform': {
|
|
8
|
+
const [min = 0, max = 1] = params;
|
|
9
|
+
if (x < min) return 0;
|
|
10
|
+
if (x > max) return 1;
|
|
11
|
+
return (x - min) / (max - min);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
case 'normal': {
|
|
15
|
+
const [mu = 0, sigma = 1] = params;
|
|
16
|
+
const z = (x - mu) / sigma;
|
|
17
|
+
return 0.5 * (1 + erf(z / Math.sqrt(2)));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
case 'exponential': {
|
|
21
|
+
const [lambda = 1] = params;
|
|
22
|
+
if (x < 0) return 0;
|
|
23
|
+
return 1 - Math.exp(-lambda * x);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
case 'poisson': {
|
|
27
|
+
const [lambda = 1] = params;
|
|
28
|
+
if (x < 0) return 0;
|
|
29
|
+
const k = Math.floor(x);
|
|
30
|
+
let sum = 0;
|
|
31
|
+
const expNeg = Math.exp(-lambda);
|
|
32
|
+
let term = expNeg;
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i <= k; i++) {
|
|
35
|
+
sum += term;
|
|
36
|
+
if (i < k) term *= lambda / (i + 1);
|
|
37
|
+
}
|
|
38
|
+
return sum;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case 'beta': {
|
|
42
|
+
const [a = 1, b = 1] = params;
|
|
43
|
+
if (x <= 0) return 0;
|
|
44
|
+
if (x >= 1) return 1;
|
|
45
|
+
return incompleteBeta(x, a, b);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
default:
|
|
49
|
+
throw new RangeError(`unknown distribution: ${distribution}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function ppf(p, distribution = 'uniform', ...params) {
|
|
54
|
+
if (typeof p !== 'number' || p < 0 || p > 1) {
|
|
55
|
+
throw new RangeError('p must be in [0, 1]');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
switch (distribution.toLowerCase()) {
|
|
59
|
+
case 'uniform': {
|
|
60
|
+
const [min = 0, max = 1] = params;
|
|
61
|
+
return min + p * (max - min);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case 'normal': {
|
|
65
|
+
const [mu = 0, sigma = 1] = params;
|
|
66
|
+
return mu + sigma * invNormal(p);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case 'exponential': {
|
|
70
|
+
const [lambda = 1] = params;
|
|
71
|
+
if (p === 0) return 0;
|
|
72
|
+
if (p === 1) return Infinity;
|
|
73
|
+
return -Math.log(1 - p) / lambda;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case 'beta': {
|
|
77
|
+
const [a = 1, b = 1] = params;
|
|
78
|
+
if (p === 0) return 0;
|
|
79
|
+
if (p === 1) return 1;
|
|
80
|
+
return invIncompleteBeta(p, a, b);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
throw new RangeError(`unknown distribution: ${distribution}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function sf(x, distribution = 'uniform', ...params) {
|
|
89
|
+
if (typeof x !== 'number') {
|
|
90
|
+
throw new TypeError('x must be a number');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return 1 - cdf(x, distribution, ...params);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function erf(x) {
|
|
97
|
+
const a1 = 0.254829592;
|
|
98
|
+
const a2 = -0.284496736;
|
|
99
|
+
const a3 = 1.421413741;
|
|
100
|
+
const a4 = -1.453152027;
|
|
101
|
+
const a5 = 1.061405429;
|
|
102
|
+
const p = 0.3275911;
|
|
103
|
+
|
|
104
|
+
const sign = x < 0 ? -1 : 1;
|
|
105
|
+
const absX = Math.abs(x);
|
|
106
|
+
const t = 1 / (1 + p * absX);
|
|
107
|
+
const t2 = t * t;
|
|
108
|
+
const t3 = t2 * t;
|
|
109
|
+
const t4 = t3 * t;
|
|
110
|
+
const t5 = t4 * t;
|
|
111
|
+
|
|
112
|
+
return sign * (1 - (a1 * t + a2 * t2 + a3 * t3 + a4 * t4 + a5 * t5) * Math.exp(-absX * absX));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function invNormal(p) {
|
|
116
|
+
if (p <= 0.5) {
|
|
117
|
+
const t = Math.sqrt(-2 * Math.log(p));
|
|
118
|
+
const c0 = 2.515517;
|
|
119
|
+
const c1 = 0.802853;
|
|
120
|
+
const c2 = 0.010328;
|
|
121
|
+
const d1 = 1.432788;
|
|
122
|
+
const d2 = 0.189269;
|
|
123
|
+
const d3 = 0.001308;
|
|
124
|
+
return -(t - (c0 + c1 * t + c2 * t * t) / (1 + d1 * t + d2 * t * t + d3 * t * t * t));
|
|
125
|
+
} else {
|
|
126
|
+
const t = Math.sqrt(-2 * Math.log(1 - p));
|
|
127
|
+
const c0 = 2.515517;
|
|
128
|
+
const c1 = 0.802853;
|
|
129
|
+
const c2 = 0.010328;
|
|
130
|
+
const d1 = 1.432788;
|
|
131
|
+
const d2 = 0.189269;
|
|
132
|
+
const d3 = 0.001308;
|
|
133
|
+
return t - (c0 + c1 * t + c2 * t * t) / (1 + d1 * t + d2 * t * t + d3 * t * t * t);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function incompleteBeta(x, a, b) {
|
|
138
|
+
const maxIterations = 100;
|
|
139
|
+
const epsilon = 1e-10;
|
|
140
|
+
|
|
141
|
+
let sum = 1;
|
|
142
|
+
let term = 1;
|
|
143
|
+
|
|
144
|
+
for (let i = 1; i <= maxIterations; i++) {
|
|
145
|
+
term *= (a + b - 1) * x / ((a + i - 1) * (1 - x / (a + i)));
|
|
146
|
+
const nextSum = sum + term;
|
|
147
|
+
|
|
148
|
+
if (Math.abs(nextSum - sum) < epsilon) {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
sum = nextSum;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return sum * Math.exp(a * Math.log(x) + b * Math.log(1 - x) - logBeta(a, b)) / a;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function invIncompleteBeta(p, a, b) {
|
|
158
|
+
let x = 0.5;
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < 20; i++) {
|
|
161
|
+
const fx = incompleteBeta(x, a, b) - p;
|
|
162
|
+
const absX = Math.log(x / (1 - x));
|
|
163
|
+
const denom = (Math.exp(a * absX - logBeta(a, b)) * Math.exp(b * Math.log(1 - x))) / (a + b);
|
|
164
|
+
const dx = fx / denom;
|
|
165
|
+
|
|
166
|
+
x = Math.max(0.001, Math.min(0.999, x - dx));
|
|
167
|
+
|
|
168
|
+
if (Math.abs(dx) < 1e-10) break;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return x;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function logBeta(a, b) {
|
|
175
|
+
return logGamma(a) + logGamma(b) - logGamma(a + b);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function logGamma(x) {
|
|
179
|
+
const coefficients = [
|
|
180
|
+
676.5203681218851,
|
|
181
|
+
-1259.1392167224028,
|
|
182
|
+
771.32342877765313,
|
|
183
|
+
-176.61502916214059,
|
|
184
|
+
12.507343278686905,
|
|
185
|
+
-0.13857109526572012,
|
|
186
|
+
9.9843695780195716e-6,
|
|
187
|
+
1.5056327351493116e-7
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const g = 7;
|
|
191
|
+
const z = x - 1;
|
|
192
|
+
let sum = 0.99999999999980993;
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i < coefficients.length; i++) {
|
|
195
|
+
sum += coefficients[i] / (z + i + 1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return 0.5 * Math.log(2 * Math.PI) + (z + 0.5) * Math.log(z + g + 0.5) - (z + g + 0.5) + Math.log(sum);
|
|
199
|
+
}
|