@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
package/src/utils/stochastic.js
CHANGED
|
@@ -1,32 +1,92 @@
|
|
|
1
1
|
export const brownianMotion = (rng, steps, dt = 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
|
-
|
|
2
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
3
|
+
throw new TypeError('First argument must be RNG instance');
|
|
4
|
+
}
|
|
5
|
+
if (typeof steps !== 'number' || !Number.isInteger(steps)) {
|
|
6
|
+
throw new TypeError('steps must be an integer');
|
|
7
|
+
}
|
|
8
|
+
if (steps <= 0) {
|
|
9
|
+
throw new RangeError('steps must be positive');
|
|
10
|
+
}
|
|
11
|
+
if (typeof dt !== 'number') {
|
|
12
|
+
throw new TypeError('dt must be a number');
|
|
13
|
+
}
|
|
14
|
+
if (dt <= 0) {
|
|
15
|
+
throw new RangeError('dt must be positive');
|
|
16
|
+
}
|
|
17
|
+
const path = [0];
|
|
18
|
+
for (let i = 0; i < steps; i++) {
|
|
19
|
+
const drift = -0.5 * dt;
|
|
20
|
+
const diffusion = Math.sqrt(dt) * (rng.nextFloat() * 2 - 1);
|
|
21
|
+
const nextVal = path[i] + drift + diffusion;
|
|
22
|
+
path.push(nextVal);
|
|
23
|
+
}
|
|
24
|
+
return path;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const ornsteinUhlenbeck = (rng, steps, theta = 0.1, mu = 0, sigma = 1) => {
|
|
28
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
29
|
+
throw new TypeError('First argument must be RNG instance');
|
|
30
|
+
}
|
|
31
|
+
if (typeof steps !== 'number' || !Number.isInteger(steps)) {
|
|
32
|
+
throw new TypeError('steps must be an integer');
|
|
33
|
+
}
|
|
34
|
+
if (steps <= 0) {
|
|
35
|
+
throw new RangeError('steps must be positive');
|
|
36
|
+
}
|
|
37
|
+
if (typeof theta !== 'number') {
|
|
38
|
+
throw new TypeError('theta must be a number');
|
|
39
|
+
}
|
|
40
|
+
if (typeof mu !== 'number') {
|
|
41
|
+
throw new TypeError('mu must be a number');
|
|
42
|
+
}
|
|
43
|
+
if (typeof sigma !== 'number') {
|
|
44
|
+
throw new TypeError('sigma must be a number');
|
|
45
|
+
}
|
|
46
|
+
if (sigma <= 0) {
|
|
47
|
+
throw new RangeError('sigma must be positive');
|
|
48
|
+
}
|
|
49
|
+
const path = [mu];
|
|
50
|
+
for (let i = 0; i < steps; i++) {
|
|
51
|
+
const drift = theta * (mu - path[i]);
|
|
52
|
+
const diffusion = sigma * (rng.nextFloat() * 2 - 1);
|
|
53
|
+
const nextVal = path[i] + drift + diffusion;
|
|
54
|
+
path.push(nextVal);
|
|
55
|
+
}
|
|
56
|
+
return path;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const geometricBrownian = (rng, steps, mu = 0.05, sigma = 0.2, dt = 0.01) => {
|
|
60
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
61
|
+
throw new TypeError('First argument must be RNG instance');
|
|
62
|
+
}
|
|
63
|
+
if (typeof steps !== 'number' || !Number.isInteger(steps)) {
|
|
64
|
+
throw new TypeError('steps must be an integer');
|
|
65
|
+
}
|
|
66
|
+
if (steps <= 0) {
|
|
67
|
+
throw new RangeError('steps must be positive');
|
|
68
|
+
}
|
|
69
|
+
if (typeof mu !== 'number') {
|
|
70
|
+
throw new TypeError('mu must be a number');
|
|
71
|
+
}
|
|
72
|
+
if (typeof sigma !== 'number') {
|
|
73
|
+
throw new TypeError('sigma must be a number');
|
|
74
|
+
}
|
|
75
|
+
if (sigma <= 0) {
|
|
76
|
+
throw new RangeError('sigma must be positive');
|
|
77
|
+
}
|
|
78
|
+
if (typeof dt !== 'number') {
|
|
79
|
+
throw new TypeError('dt must be a number');
|
|
80
|
+
}
|
|
81
|
+
if (dt <= 0) {
|
|
82
|
+
throw new RangeError('dt must be positive');
|
|
83
|
+
}
|
|
84
|
+
const path = [1];
|
|
85
|
+
for (let i = 0; i < steps; i++) {
|
|
86
|
+
const drift = (mu - 0.5 * sigma * sigma) * dt;
|
|
87
|
+
const diffusion = sigma * Math.sqrt(dt) * (rng.nextFloat() * 2 - 1);
|
|
88
|
+
const nextVal = path[i] * Math.exp(drift + diffusion);
|
|
89
|
+
path.push(nextVal);
|
|
90
|
+
}
|
|
91
|
+
return path;
|
|
92
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export function stratifiedSample(r, data, strata, n) {
|
|
2
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
3
|
+
throw new TypeError('data must be a non-empty array');
|
|
4
|
+
}
|
|
5
|
+
if (typeof strata !== 'function') {
|
|
6
|
+
throw new TypeError('strata must be a function that returns stratum key');
|
|
7
|
+
}
|
|
8
|
+
if (typeof n !== 'number' || n < 1) {
|
|
9
|
+
throw new RangeError('n must be a positive integer');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const groups = new Map();
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < data.length; i++) {
|
|
15
|
+
const key = strata(data[i]);
|
|
16
|
+
if (!groups.has(key)) {
|
|
17
|
+
groups.set(key, []);
|
|
18
|
+
}
|
|
19
|
+
groups.get(key).push(data[i]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = [];
|
|
23
|
+
const groupCount = groups.size;
|
|
24
|
+
const samplesPerGroup = Math.floor(n / groupCount);
|
|
25
|
+
let remaining = n - samplesPerGroup * groupCount;
|
|
26
|
+
|
|
27
|
+
for (const [key, group] of groups) {
|
|
28
|
+
const k = samplesPerGroup + (remaining > 0 ? 1 : 0);
|
|
29
|
+
if (remaining > 0) remaining--;
|
|
30
|
+
|
|
31
|
+
if (k >= group.length) {
|
|
32
|
+
result.push(...group);
|
|
33
|
+
} else {
|
|
34
|
+
for (let i = 0; i < k; i++) {
|
|
35
|
+
const idx = r.nextInt(group.length);
|
|
36
|
+
result.push(group[idx]);
|
|
37
|
+
group[idx] = group[group.length - 1];
|
|
38
|
+
group.pop();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function stratifiedSampleProportional(r, data, strata, n) {
|
|
47
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
48
|
+
throw new TypeError('data must be a non-empty array');
|
|
49
|
+
}
|
|
50
|
+
if (typeof strata !== 'function') {
|
|
51
|
+
throw new TypeError('strata must be a function');
|
|
52
|
+
}
|
|
53
|
+
if (typeof n !== 'number' || n < 1) {
|
|
54
|
+
throw new RangeError('n must be a positive integer');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const groups = new Map();
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < data.length; i++) {
|
|
60
|
+
const key = strata(data[i]);
|
|
61
|
+
if (!groups.has(key)) {
|
|
62
|
+
groups.set(key, []);
|
|
63
|
+
}
|
|
64
|
+
groups.get(key).push(data[i]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const result = [];
|
|
68
|
+
const total = data.length;
|
|
69
|
+
|
|
70
|
+
for (const [key, group] of groups) {
|
|
71
|
+
const k = Math.round(n * group.length / total);
|
|
72
|
+
|
|
73
|
+
if (k >= group.length) {
|
|
74
|
+
result.push(...group);
|
|
75
|
+
} else if (k > 0) {
|
|
76
|
+
const copy = [...group];
|
|
77
|
+
for (let i = 0; i < k; i++) {
|
|
78
|
+
const idx = r.nextInt(copy.length);
|
|
79
|
+
result.push(copy[idx]);
|
|
80
|
+
copy[idx] = copy[copy.length - 1];
|
|
81
|
+
copy.pop();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function stratify(data, strata) {
|
|
90
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
91
|
+
throw new TypeError('data must be a non-empty array');
|
|
92
|
+
}
|
|
93
|
+
if (typeof strata !== 'function') {
|
|
94
|
+
throw new TypeError('strata must be a function');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const groups = new Map();
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < data.length; i++) {
|
|
100
|
+
const key = strata(data[i]);
|
|
101
|
+
if (!groups.has(key)) {
|
|
102
|
+
groups.set(key, []);
|
|
103
|
+
}
|
|
104
|
+
groups.get(key).push(data[i]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Object.fromEntries(groups);
|
|
108
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export function diff(data, order = 1) {
|
|
2
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
3
|
+
throw new TypeError('data must be non-empty array');
|
|
4
|
+
}
|
|
5
|
+
if (typeof order !== 'number' || order < 1) {
|
|
6
|
+
throw new RangeError('order must be positive integer');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let result = [...data];
|
|
10
|
+
|
|
11
|
+
for (let o = 0; o < order; o++) {
|
|
12
|
+
const prev = result;
|
|
13
|
+
result = new Array(prev.length - 1);
|
|
14
|
+
for (let i = 0; i < prev.length - 1; i++) {
|
|
15
|
+
result[i] = prev[i + 1] - prev[i];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function lag(data, lags = 1) {
|
|
23
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
24
|
+
throw new TypeError('data must be non-empty array');
|
|
25
|
+
}
|
|
26
|
+
if (typeof lags !== 'number' || lags < 1) {
|
|
27
|
+
throw new RangeError('lags must be positive integer');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = new Array(data.length);
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < data.length; i++) {
|
|
33
|
+
result[i] = i < lags ? null : data[i - lags];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function shift(data, periods = 1) {
|
|
40
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
41
|
+
throw new TypeError('data must be non-empty array');
|
|
42
|
+
}
|
|
43
|
+
if (typeof periods !== 'number') {
|
|
44
|
+
throw new RangeError('periods must be integer');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = new Array(data.length);
|
|
48
|
+
|
|
49
|
+
if (periods > 0) {
|
|
50
|
+
for (let i = 0; i < data.length; i++) {
|
|
51
|
+
result[i] = i < periods ? null : data[i - periods];
|
|
52
|
+
}
|
|
53
|
+
} else if (periods < 0) {
|
|
54
|
+
for (let i = 0; i < data.length; i++) {
|
|
55
|
+
result[i] = i < -periods ? null : data[i - periods];
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
return [...data];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function sma(data, window) {
|
|
65
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
66
|
+
throw new TypeError('data must be non-empty array');
|
|
67
|
+
}
|
|
68
|
+
if (typeof window !== 'number' || window < 1) {
|
|
69
|
+
throw new RangeError('window must be positive integer');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = new Array(data.length);
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < data.length; i++) {
|
|
75
|
+
if (i < window - 1) {
|
|
76
|
+
result[i] = null;
|
|
77
|
+
} else {
|
|
78
|
+
let sum = 0;
|
|
79
|
+
for (let j = i - window + 1; j <= i; j++) {
|
|
80
|
+
sum += data[j];
|
|
81
|
+
}
|
|
82
|
+
result[i] = sum / window;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function ema(data, window) {
|
|
90
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
91
|
+
throw new TypeError('data must be non-empty array');
|
|
92
|
+
}
|
|
93
|
+
if (typeof window !== 'number' || window < 1) {
|
|
94
|
+
throw new RangeError('window must be positive integer');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const alpha = 2 / (window + 1);
|
|
98
|
+
const result = new Array(data.length);
|
|
99
|
+
|
|
100
|
+
result[0] = data[0];
|
|
101
|
+
|
|
102
|
+
for (let i = 1; i < data.length; i++) {
|
|
103
|
+
result[i] = alpha * data[i] + (1 - alpha) * result[i - 1];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function acf(data, maxLag = 40) {
|
|
110
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
111
|
+
throw new TypeError('data must be non-empty array');
|
|
112
|
+
}
|
|
113
|
+
if (typeof maxLag !== 'number' || maxLag < 1) {
|
|
114
|
+
throw new RangeError('maxLag must be positive integer');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const mean = data.reduce((a, b) => a + b, 0) / data.length;
|
|
118
|
+
const centered = data.map(x => x - mean);
|
|
119
|
+
|
|
120
|
+
let c0 = 0;
|
|
121
|
+
for (let i = 0; i < data.length; i++) {
|
|
122
|
+
c0 += centered[i] * centered[i];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const result = new Array(Math.min(maxLag + 1, data.length));
|
|
126
|
+
result[0] = 1;
|
|
127
|
+
|
|
128
|
+
for (let k = 1; k < result.length; k++) {
|
|
129
|
+
let ck = 0;
|
|
130
|
+
for (let i = 0; i < data.length - k; i++) {
|
|
131
|
+
ck += centered[i] * centered[i + k];
|
|
132
|
+
}
|
|
133
|
+
result[k] = ck / c0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function pacf(data, maxLag = 40) {
|
|
140
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
141
|
+
throw new TypeError('data must be non-empty array');
|
|
142
|
+
}
|
|
143
|
+
if (typeof maxLag !== 'number' || maxLag < 1) {
|
|
144
|
+
throw new RangeError('maxLag must be positive integer');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const correlations = acf(data, maxLag);
|
|
148
|
+
const result = [1];
|
|
149
|
+
|
|
150
|
+
for (let k = 1; k < correlations.length; k++) {
|
|
151
|
+
let numerator = correlations[k];
|
|
152
|
+
|
|
153
|
+
for (let j = 1; j < k; j++) {
|
|
154
|
+
numerator -= result[j] * correlations[k - j];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let denominator = 1;
|
|
158
|
+
for (let j = 1; j < k; j++) {
|
|
159
|
+
denominator -= result[j] * correlations[j];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
result.push(numerator / denominator);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export function zscore(data) {
|
|
2
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
3
|
+
throw new TypeError('data must be non-empty array');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const mean = data.reduce((a, b) => a + b, 0) / data.length;
|
|
7
|
+
let variance = 0;
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < data.length; i++) {
|
|
10
|
+
variance += (data[i] - mean) ** 2;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const stddev = Math.sqrt(variance / data.length);
|
|
14
|
+
|
|
15
|
+
if (stddev === 0) {
|
|
16
|
+
throw new RangeError('cannot z-score data with zero variance');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return data.map(x => (x - mean) / stddev);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function standardize(data) {
|
|
23
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
24
|
+
throw new TypeError('data must be non-empty array');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const mean = data.reduce((a, b) => a + b, 0) / data.length;
|
|
28
|
+
return data.map(x => x - mean);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function minMaxScale(data, min = 0, max = 1) {
|
|
32
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
33
|
+
throw new TypeError('data must be non-empty array');
|
|
34
|
+
}
|
|
35
|
+
if (typeof min !== 'number' || typeof max !== 'number') {
|
|
36
|
+
throw new TypeError('min and max must be numbers');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const dataMin = Math.min(...data);
|
|
40
|
+
const dataMax = Math.max(...data);
|
|
41
|
+
const range = dataMax - dataMin;
|
|
42
|
+
|
|
43
|
+
if (range === 0) {
|
|
44
|
+
throw new RangeError('cannot scale data with zero range');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return data.map(x => ((x - dataMin) / range) * (max - min) + min);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function logTransform(data, base = Math.E) {
|
|
51
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
52
|
+
throw new TypeError('data must be non-empty array');
|
|
53
|
+
}
|
|
54
|
+
if (typeof base !== 'number' || base <= 0 || base === 1) {
|
|
55
|
+
throw new RangeError('base must be positive number != 1');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < data.length; i++) {
|
|
59
|
+
if (data[i] <= 0) {
|
|
60
|
+
throw new RangeError('all values must be positive');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const logBase = Math.log(base);
|
|
65
|
+
return data.map(x => Math.log(x) / logBase);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sqrtTransform(data) {
|
|
69
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
70
|
+
throw new TypeError('data must be non-empty array');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < data.length; i++) {
|
|
74
|
+
if (data[i] < 0) {
|
|
75
|
+
throw new RangeError('all values must be non-negative');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return data.map(x => Math.sqrt(x));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function rank(data) {
|
|
83
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
84
|
+
throw new TypeError('data must be non-empty array');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const indexed = data.map((val, i) => [val, i]);
|
|
88
|
+
indexed.sort((a, b) => a[0] - b[0]);
|
|
89
|
+
|
|
90
|
+
const ranks = new Array(data.length);
|
|
91
|
+
let i = 0;
|
|
92
|
+
|
|
93
|
+
while (i < indexed.length) {
|
|
94
|
+
let j = i;
|
|
95
|
+
while (j < indexed.length && indexed[j][0] === indexed[i][0]) {
|
|
96
|
+
j++;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const rank = (i + 1 + j) / 2;
|
|
100
|
+
for (let k = i; k < j; k++) {
|
|
101
|
+
ranks[indexed[k][1]] = rank;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
i = j;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return ranks;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function robustScale(data, center = 'median', scale = 'iqr') {
|
|
111
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
112
|
+
throw new TypeError('data must be non-empty array');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let centerVal;
|
|
116
|
+
if (center === 'median') {
|
|
117
|
+
const sorted = [...data].sort((a, b) => a - b);
|
|
118
|
+
centerVal = sorted.length % 2 === 0
|
|
119
|
+
? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
|
|
120
|
+
: sorted[Math.floor(sorted.length / 2)];
|
|
121
|
+
} else if (center === 'mean') {
|
|
122
|
+
centerVal = data.reduce((a, b) => a + b, 0) / data.length;
|
|
123
|
+
} else {
|
|
124
|
+
throw new RangeError('center must be "median" or "mean"');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let scaleVal;
|
|
128
|
+
if (scale === 'iqr') {
|
|
129
|
+
const sorted = [...data].sort((a, b) => a - b);
|
|
130
|
+
const q1 = sorted[Math.floor(sorted.length * 0.25)];
|
|
131
|
+
const q3 = sorted[Math.floor(sorted.length * 0.75)];
|
|
132
|
+
scaleVal = q3 - q1;
|
|
133
|
+
} else if (scale === 'mad') {
|
|
134
|
+
const centered = data.map(x => Math.abs(x - centerVal));
|
|
135
|
+
const sorted = [...centered].sort((a, b) => a - b);
|
|
136
|
+
scaleVal = sorted[Math.floor(sorted.length / 2)];
|
|
137
|
+
} else {
|
|
138
|
+
throw new RangeError('scale must be "iqr" or "mad"');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (scaleVal === 0) {
|
|
142
|
+
throw new RangeError('scale value is zero');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return data.map(x => (x - centerVal) / scaleVal);
|
|
146
|
+
}
|
package/test/comprehensive.js
CHANGED
|
@@ -61,10 +61,11 @@ console.log(` State restored correctly: ${val2 === val2_restored}`);
|
|
|
61
61
|
|
|
62
62
|
console.log('\n7. Generator Cloning');
|
|
63
63
|
const orig = deterministic(12345);
|
|
64
|
-
|
|
64
|
+
orig.floats(3);
|
|
65
65
|
const cloned = cloneGenerator(orig);
|
|
66
|
-
const
|
|
67
|
-
|
|
66
|
+
const origNext = orig.floats(3);
|
|
67
|
+
const clonedNext = cloned.floats(3);
|
|
68
|
+
console.log(` Clone has same sequence: ${JSON.stringify(origNext) === JSON.stringify(clonedNext)}`);
|
|
68
69
|
|
|
69
70
|
console.log('\n8. Weighted Sampling');
|
|
70
71
|
const items = ['A', 'B', 'C'];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { rng, binomial, geometric, normals, exponentials, sampleWithReplacement, permute, range, cycle, clz, ctz, popcountNum, reverseBits, setBit, clearBit, toggleBit } from '../src/index.js';
|
|
2
|
+
|
|
3
|
+
console.log('=== NEW FEATURES TEST ===\n');
|
|
4
|
+
|
|
5
|
+
const r = rng();
|
|
6
|
+
|
|
7
|
+
console.log('1. Binomial Distribution');
|
|
8
|
+
const binom = binomial(r, 20, 0.5);
|
|
9
|
+
console.log(` binomial(20, 0.5): ${binom} (between 0-20)`);
|
|
10
|
+
|
|
11
|
+
console.log('\n2. Geometric Distribution');
|
|
12
|
+
const geom = geometric(r, 0.3);
|
|
13
|
+
console.log(` geometric(0.3): ${geom} (number of trials)`);
|
|
14
|
+
|
|
15
|
+
console.log('\n3. Batch Normal Distribution (with caching)');
|
|
16
|
+
const normals_arr = normals(r, 5);
|
|
17
|
+
console.log(` normals(5): [${normals_arr.map(n => n.toFixed(2)).join(', ')}]`);
|
|
18
|
+
|
|
19
|
+
console.log('\n4. Batch Exponential Distribution');
|
|
20
|
+
const exps = exponentials(r, 4, 1);
|
|
21
|
+
console.log(` exponentials(4): [${exps.map(e => e.toFixed(2)).join(', ')}]`);
|
|
22
|
+
|
|
23
|
+
console.log('\n5. Sampling with Replacement');
|
|
24
|
+
const sampled = sampleWithReplacement(['A', 'B', 'C'], 8, r);
|
|
25
|
+
console.log(` sampleWithReplacement([A,B,C], 8): [${sampled.join(', ')}]`);
|
|
26
|
+
|
|
27
|
+
console.log('\n6. Permutation (alias for shuffle)');
|
|
28
|
+
const perm = permute([1, 2, 3, 4], r);
|
|
29
|
+
console.log(` permute([1,2,3,4]): [${perm.join(', ')}]`);
|
|
30
|
+
|
|
31
|
+
console.log('\n7. Range Generation');
|
|
32
|
+
const r1 = range(0, 5);
|
|
33
|
+
const r2 = range(10, 5, -1);
|
|
34
|
+
console.log(` range(0, 5): [${r1.join(', ')}]`);
|
|
35
|
+
console.log(` range(10, 5, -1): [${r2.join(', ')}]`);
|
|
36
|
+
|
|
37
|
+
console.log('\n8. Cycle (repeat array)');
|
|
38
|
+
const cyc = cycle(['X', 'Y'], 6);
|
|
39
|
+
console.log(` cycle([X, Y], 6): [${cyc.join(', ')}]`);
|
|
40
|
+
|
|
41
|
+
console.log('\n9. Bit Operations');
|
|
42
|
+
const val = 0b11010110n;
|
|
43
|
+
console.log(` Value: 0b11010110`);
|
|
44
|
+
console.log(` clz(): ${clz(val)}`);
|
|
45
|
+
console.log(` ctz(): ${ctz(val)}`);
|
|
46
|
+
console.log(` popcountNum(214): ${popcountNum(214)}`);
|
|
47
|
+
console.log(` reverseBits() [8-bit]: 0b${reverseBits(val, 8).toString(2).padStart(8, '0')}`);
|
|
48
|
+
console.log(` setBit(0b11010110, 0): 0b${setBit(val, 0).toString(2).padStart(8, '0')}`);
|
|
49
|
+
console.log(` clearBit(0b11010110, 1): 0b${clearBit(val, 1).toString(2).padStart(8, '0')}`);
|
|
50
|
+
console.log(` toggleBit(0b11010110, 4): 0b${toggleBit(val, 4).toString(2).padStart(8, '0')}`);
|
|
51
|
+
|
|
52
|
+
console.log('\n✓ All new features working correctly');
|
package/IMPROVEMENTS.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Improvements Made (Hour 2)
|
|
2
|
-
|
|
3
|
-
## Batch Operations
|
|
4
|
-
- Added `batch(count, fn)` - Execute custom function n times
|
|
5
|
-
- Added `floats(count)` - Generate array of floats
|
|
6
|
-
- Added `ints(count, max)` - Generate array of integers
|
|
7
|
-
- Added `bools(count, probability)` - Generate array of booleans
|
|
8
|
-
|
|
9
|
-
## RNG API Enhancements
|
|
10
|
-
- Added `range(min, max, step)` - Pick random value from stepped range
|
|
11
|
-
- Added `choice(arr)` - Pick random element from array
|
|
12
|
-
- Better error messages with TypeError vs RangeError distinctions
|
|
13
|
-
|
|
14
|
-
## Statistical Improvements
|
|
15
|
-
- Added `kolmogorovSmirnovTest(data)` - KS goodness-of-fit test
|
|
16
|
-
- Added `meanTest(data, expected)` - Test mean of distribution
|
|
17
|
-
- Added `varianceTest(data, expected)` - Test variance of distribution
|
|
18
|
-
- All tests return detailed result objects with pass/fail indicators
|
|
19
|
-
|
|
20
|
-
## Sampling Enhancements
|
|
21
|
-
- Improved `weightedPick()` with comprehensive validation
|
|
22
|
-
- Improved `weightedSample()` with type checking
|
|
23
|
-
- Improved `reservoirSample()` with better error handling
|
|
24
|
-
|
|
25
|
-
## Generator Optimizations
|
|
26
|
-
- Removed modulo bias in `nextInt()` - uses rejection sampling instead
|
|
27
|
-
- Applied to: PCG64, Xorshift64, Splitmix64, MT19937
|
|
28
|
-
- Ensures uniform distribution across all ranges
|
|
29
|
-
|
|
30
|
-
## Performance & Caching
|
|
31
|
-
- Added crypto cache in entropy source for performance
|
|
32
|
-
- Added `clearCryptoCache()` export for testing
|
|
33
|
-
- Better entropy mixing with proper BigInt operations
|
|
34
|
-
|
|
35
|
-
## Type Safety
|
|
36
|
-
- Added `IGenerator` interface for type consistency
|
|
37
|
-
- Added `GeneratorConstructor` type definition
|
|
38
|
-
- Added generic types to shuffle, pick, sample functions
|
|
39
|
-
- Added test result interfaces: `TestResult`, `KSTestResult`
|
|
40
|
-
- Complete TypeScript definitions for all new APIs
|
|
41
|
-
|
|
42
|
-
## Documentation
|
|
43
|
-
- Updated README with batch operations examples
|
|
44
|
-
- Added weighted sampling section
|
|
45
|
-
- Added statistical tests section
|
|
46
|
-
- Enhanced generator selection documentation
|
|
47
|
-
|
|
48
|
-
## Testing
|
|
49
|
-
- Added comprehensive test suite (test/comprehensive.js)
|
|
50
|
-
- Added advanced features test (test/advanced.js)
|
|
51
|
-
- All tests pass successfully
|
|
52
|
-
|
|
53
|
-
## Code Quality
|
|
54
|
-
- Total lines: 1107 → 1318 (+211)
|
|
55
|
-
- Files modified: 10
|
|
56
|
-
- Lines added/changed: 286
|
|
57
|
-
- Consistent error handling patterns
|
|
58
|
-
- Full backward compatibility maintained
|