@cloudglides/nox 1.1.5 → 2.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.
Files changed (58) hide show
  1. package/README.md +9 -9
  2. package/example/src/App.css +89 -84
  3. package/example/src/App.jsx +375 -151
  4. package/package.json +7 -6
  5. package/src/core.browser.js +10 -2
  6. package/src/core.js +32 -4
  7. package/src/generators/logistic.js +30 -25
  8. package/src/generators/mixer.js +7 -7
  9. package/src/generators/mt19937.js +10 -7
  10. package/src/generators/pcg64.js +23 -12
  11. package/src/generators/splitmix64.js +12 -6
  12. package/src/generators/tent.js +12 -7
  13. package/src/generators/xorshift64.js +6 -3
  14. package/src/index.d.ts +68 -4
  15. package/src/index.js +154 -2
  16. package/src/rng.browser.js +21 -10
  17. package/src/rng.js +95 -82
  18. package/src/utils/arrays.js +149 -0
  19. package/src/utils/bits.js +146 -21
  20. package/src/utils/categorical.js +68 -31
  21. package/src/utils/combinatorics.js +113 -69
  22. package/src/utils/confidence.js +145 -0
  23. package/src/utils/decomposition.js +204 -0
  24. package/src/utils/distributions-advanced.js +122 -0
  25. package/src/utils/distributions-extra.js +102 -11
  26. package/src/utils/distributions-special.js +77 -20
  27. package/src/utils/distributions.js +99 -35
  28. package/src/utils/effects.js +172 -0
  29. package/src/utils/entropy.browser.js +29 -26
  30. package/src/utils/entropy.js +18 -8
  31. package/src/utils/helpers.js +64 -0
  32. package/src/utils/hypothesis.js +167 -0
  33. package/src/utils/integration.js +137 -0
  34. package/src/utils/interpolation.js +221 -0
  35. package/src/utils/matrix.js +242 -0
  36. package/src/utils/noise.js +36 -22
  37. package/src/utils/odesolvers.js +176 -0
  38. package/src/utils/optimization.js +215 -0
  39. package/src/utils/precomputed.js +166 -0
  40. package/src/utils/probability.js +199 -0
  41. package/src/utils/regression.js +170 -0
  42. package/src/utils/resampling.js +112 -0
  43. package/src/utils/rootfinding.js +158 -0
  44. package/src/utils/sampling.js +86 -77
  45. package/src/utils/seed.js +10 -4
  46. package/src/utils/seeding.js +24 -12
  47. package/src/utils/sequence.js +116 -32
  48. package/src/utils/state.js +48 -36
  49. package/src/utils/statistics.js +64 -2
  50. package/src/utils/stochastic.js +91 -31
  51. package/src/utils/stratified.js +108 -0
  52. package/src/utils/timeseries.js +166 -0
  53. package/src/utils/transforms.js +146 -0
  54. package/test/comprehensive-new.js +126 -0
  55. package/test/comprehensive.js +4 -3
  56. package/test/error-handling.js +49 -0
  57. package/test/new-features.js +52 -0
  58. package/IMPROVEMENTS.md +0 -58
package/src/rng.js CHANGED
@@ -28,17 +28,16 @@ export class RNG {
28
28
  }
29
29
 
30
30
  int(min, max) {
31
- if (typeof min !== 'number' || typeof max !== 'number') {
32
- throw new TypeError('int() requires two number arguments');
33
- }
34
- if (!Number.isInteger(min) || !Number.isInteger(max)) {
35
- throw new TypeError('int() arguments must be integers');
36
- }
37
- if (min > max) [min, max] = [max, min];
38
- const range = max - min + 1;
39
- let val = Math.floor(this.nextFloat() * range);
40
- return val + min;
41
- }
31
+ if (typeof min !== 'number' || typeof max !== 'number') {
32
+ throw new TypeError('int() requires two number arguments');
33
+ }
34
+ if (!Number.isInteger(min) || !Number.isInteger(max)) {
35
+ throw new TypeError('int() arguments must be integers');
36
+ }
37
+ if (min > max) [min, max] = [max, min];
38
+ const range = max - min + 1;
39
+ return min + this.nextInt(range);
40
+ }
42
41
 
43
42
  bool(probability = 0.5) {
44
43
  if (typeof probability !== 'number') {
@@ -55,13 +54,13 @@ export class RNG {
55
54
  throw new TypeError('min, max, and step must be numbers');
56
55
  }
57
56
  if (!Number.isInteger(min) || !Number.isInteger(max) || !Number.isInteger(step)) {
58
- throw new Error('min, max, and step must be integers');
57
+ throw new TypeError('min, max, and step must be integers');
59
58
  }
60
59
  if (step <= 0) {
61
- throw new Error('step must be positive');
60
+ throw new RangeError('step must be positive');
62
61
  }
63
62
  if (min > max) {
64
- throw new Error('min must be less than or equal to max');
63
+ throw new RangeError('min must be less than or equal to max');
65
64
  }
66
65
 
67
66
  const count = Math.floor((max - min) / step) + 1;
@@ -70,79 +69,93 @@ export class RNG {
70
69
  }
71
70
 
72
71
  choice(arr) {
73
- if (!Array.isArray(arr)) {
74
- throw new TypeError('choice() requires array');
75
- }
76
- const len = arr.length;
77
- if (len === 0) {
78
- throw new TypeError('choice() requires non-empty array');
79
- }
80
- return arr[this.nextInt(len)];
81
- }
72
+ if (!Array.isArray(arr)) {
73
+ throw new TypeError('choice() requires array');
74
+ }
75
+ const len = arr.length;
76
+ if (len === 0) {
77
+ throw new RangeError('choice() requires non-empty array');
78
+ }
79
+ return arr[this.nextInt(len)];
80
+ }
82
81
 
83
82
  batch(count, fn) {
84
- if (typeof count !== 'number' || !Number.isInteger(count)) {
85
- throw new TypeError('count must be an integer');
86
- }
87
- if (count <= 0) {
88
- return [];
89
- }
90
- if (typeof fn !== 'function') {
91
- throw new TypeError('fn must be a function');
92
- }
93
-
94
- const result = new Array(count);
95
- for (let i = 0; i < count; i++) {
96
- result[i] = fn(this, i);
97
- }
98
- return result;
99
- }
83
+ if (typeof count !== 'number' || !Number.isInteger(count)) {
84
+ throw new TypeError('count must be an integer');
85
+ }
86
+ if (count < 0) {
87
+ throw new RangeError('count must be non-negative');
88
+ }
89
+ if (count === 0) {
90
+ return [];
91
+ }
92
+ if (typeof fn !== 'function') {
93
+ throw new TypeError('fn must be a function');
94
+ }
95
+
96
+ const result = new Array(count);
97
+ for (let i = 0; i < count; i++) {
98
+ result[i] = fn(this, i);
99
+ }
100
+ return result;
101
+ }
100
102
 
101
103
  floats(count) {
102
- if (typeof count !== 'number' || !Number.isInteger(count)) {
103
- throw new TypeError('count must be an integer');
104
- }
105
- if (count <= 0) {
106
- return [];
107
- }
108
-
109
- const result = new Array(count);
110
- for (let i = 0; i < count; i++) {
111
- result[i] = this.nextFloat();
112
- }
113
- return result;
114
- }
115
-
116
- ints(count, max = 2147483647) {
117
- if (typeof count !== 'number' || !Number.isInteger(count)) {
118
- throw new TypeError('count must be an integer');
119
- }
120
- if (count <= 0) {
121
- return [];
122
- }
123
-
124
- const result = new Array(count);
125
- for (let i = 0; i < count; i++) {
126
- result[i] = this.nextInt(max);
127
- }
128
- return result;
129
- }
130
-
131
- bools(count, probability = 0.5) {
132
- if (typeof count !== 'number' || !Number.isInteger(count)) {
133
- throw new TypeError('count must be an integer');
134
- }
135
- if (count <= 0) {
136
- return [];
137
- }
138
-
139
- const result = new Array(count);
140
- for (let i = 0; i < count; i++) {
141
- result[i] = this.bool(probability);
142
- }
143
- return result;
144
- }
104
+ if (typeof count !== 'number' || !Number.isInteger(count)) {
105
+ throw new TypeError('count must be an integer');
106
+ }
107
+ if (count < 0) {
108
+ throw new RangeError('count must be non-negative');
109
+ }
110
+ if (count === 0) {
111
+ return [];
112
+ }
113
+
114
+ const result = new Array(count);
115
+ for (let i = 0; i < count; i++) {
116
+ result[i] = this.nextFloat();
117
+ }
118
+ return result;
119
+ }
120
+
121
+ ints(count, max = 2147483647) {
122
+ if (typeof count !== 'number' || !Number.isInteger(count)) {
123
+ throw new TypeError('count must be an integer');
124
+ }
125
+ if (count < 0) {
126
+ throw new RangeError('count must be non-negative');
127
+ }
128
+ if (count === 0) {
129
+ return [];
130
+ }
131
+
132
+ const result = new Array(count);
133
+ for (let i = 0; i < count; i++) {
134
+ result[i] = this.nextInt(max);
135
+ }
136
+ return result;
137
+ }
138
+
139
+ bools(count, probability = 0.5) {
140
+ if (typeof count !== 'number' || !Number.isInteger(count)) {
141
+ throw new TypeError('count must be an integer');
142
+ }
143
+ if (count < 0) {
144
+ throw new RangeError('count must be non-negative');
145
+ }
146
+ if (count === 0) {
147
+ return [];
148
+ }
149
+
150
+ const result = new Array(count);
151
+ for (let i = 0; i < count; i++) {
152
+ result[i] = this.bool(probability);
153
+ }
154
+ return result;
155
+ }
145
156
  }
146
157
 
147
158
  export const rng = () => new RNG();
148
159
 
160
+
161
+
@@ -0,0 +1,149 @@
1
+ export function sum(arr) {
2
+ if (!Array.isArray(arr)) {
3
+ throw new TypeError('arr must be array');
4
+ }
5
+ let total = 0;
6
+ for (let i = 0; i < arr.length; i++) {
7
+ total += arr[i];
8
+ }
9
+ return total;
10
+ }
11
+
12
+ export function mean(arr) {
13
+ if (!Array.isArray(arr) || arr.length === 0) {
14
+ throw new TypeError('arr must be non-empty array');
15
+ }
16
+ return sum(arr) / arr.length;
17
+ }
18
+
19
+ export function min(arr) {
20
+ if (!Array.isArray(arr) || arr.length === 0) {
21
+ throw new TypeError('arr must be non-empty array');
22
+ }
23
+ let minVal = arr[0];
24
+ for (let i = 1; i < arr.length; i++) {
25
+ if (arr[i] < minVal) minVal = arr[i];
26
+ }
27
+ return minVal;
28
+ }
29
+
30
+ export function max(arr) {
31
+ if (!Array.isArray(arr) || arr.length === 0) {
32
+ throw new TypeError('arr must be non-empty array');
33
+ }
34
+ let maxVal = arr[0];
35
+ for (let i = 1; i < arr.length; i++) {
36
+ if (arr[i] > maxVal) maxVal = arr[i];
37
+ }
38
+ return maxVal;
39
+ }
40
+
41
+ export function unique(arr) {
42
+ if (!Array.isArray(arr)) {
43
+ throw new TypeError('arr must be array');
44
+ }
45
+ const seen = new Set();
46
+ const result = [];
47
+ for (let i = 0; i < arr.length; i++) {
48
+ if (!seen.has(arr[i])) {
49
+ seen.add(arr[i]);
50
+ result.push(arr[i]);
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+
56
+ export function chunk(arr, size) {
57
+ if (!Array.isArray(arr)) {
58
+ throw new TypeError('arr must be array');
59
+ }
60
+ if (typeof size !== 'number' || size < 1) {
61
+ throw new RangeError('size must be positive integer');
62
+ }
63
+
64
+ const result = [];
65
+ for (let i = 0; i < arr.length; i += size) {
66
+ result.push(arr.slice(i, i + size));
67
+ }
68
+ return result;
69
+ }
70
+
71
+ export function flatten(arr, depth = Infinity) {
72
+ if (!Array.isArray(arr)) {
73
+ throw new TypeError('arr must be array');
74
+ }
75
+ if (typeof depth !== 'number' || depth < 0) {
76
+ throw new RangeError('depth must be non-negative number');
77
+ }
78
+
79
+ if (depth === 0) return arr;
80
+
81
+ const result = [];
82
+ for (let i = 0; i < arr.length; i++) {
83
+ if (Array.isArray(arr[i]) && depth > 0) {
84
+ result.push(...flatten(arr[i], depth - 1));
85
+ } else {
86
+ result.push(arr[i]);
87
+ }
88
+ }
89
+ return result;
90
+ }
91
+
92
+ export function zip(...arrays) {
93
+ if (arrays.length === 0) {
94
+ throw new TypeError('at least one array required');
95
+ }
96
+
97
+ for (let i = 0; i < arrays.length; i++) {
98
+ if (!Array.isArray(arrays[i])) {
99
+ throw new TypeError(`argument ${i} must be array`);
100
+ }
101
+ }
102
+
103
+ const minLen = Math.min(...arrays.map(a => a.length));
104
+ const result = new Array(minLen);
105
+
106
+ for (let i = 0; i < minLen; i++) {
107
+ result[i] = arrays.map(a => a[i]);
108
+ }
109
+ return result;
110
+ }
111
+
112
+ export function transpose(matrix) {
113
+ if (!Array.isArray(matrix) || matrix.length === 0) {
114
+ throw new TypeError('matrix must be non-empty 2D array');
115
+ }
116
+
117
+ const cols = matrix[0].length;
118
+ const result = new Array(cols);
119
+
120
+ for (let j = 0; j < cols; j++) {
121
+ result[j] = new Array(matrix.length);
122
+ for (let i = 0; i < matrix.length; i++) {
123
+ result[j][i] = matrix[i][j];
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+
129
+ export function partition(arr, predicate) {
130
+ if (!Array.isArray(arr)) {
131
+ throw new TypeError('arr must be array');
132
+ }
133
+ if (typeof predicate !== 'function') {
134
+ throw new TypeError('predicate must be function');
135
+ }
136
+
137
+ const truthy = [];
138
+ const falsy = [];
139
+
140
+ for (let i = 0; i < arr.length; i++) {
141
+ if (predicate(arr[i], i)) {
142
+ truthy.push(arr[i]);
143
+ } else {
144
+ falsy.push(arr[i]);
145
+ }
146
+ }
147
+
148
+ return [truthy, falsy];
149
+ }
package/src/utils/bits.js CHANGED
@@ -1,25 +1,150 @@
1
1
  export const rotateBits = (val, shift) => {
2
- const mask = BigInt(64);
3
- shift = BigInt(shift) % mask;
4
- return (val << shift) | (val >> (mask - shift));
5
- };
2
+ if (typeof val !== 'bigint') {
3
+ throw new TypeError('val must be bigint');
4
+ }
5
+ if (typeof shift !== 'number' && typeof shift !== 'bigint') {
6
+ throw new TypeError('shift must be number or bigint');
7
+ }
8
+ const mask = BigInt(64);
9
+ shift = BigInt(shift) % mask;
10
+ return (val << shift) | (val >> (mask - shift));
11
+ };
12
+
13
+ export const extractBits = (val, start, length) => {
14
+ if (typeof val !== 'bigint') {
15
+ throw new TypeError('val must be bigint');
16
+ }
17
+ if (typeof start !== 'number' || !Number.isInteger(start)) {
18
+ throw new TypeError('start must be an integer');
19
+ }
20
+ if (start < 0) {
21
+ throw new RangeError('start must be non-negative');
22
+ }
23
+ if (typeof length !== 'number' || !Number.isInteger(length)) {
24
+ throw new TypeError('length must be an integer');
25
+ }
26
+ if (length <= 0) {
27
+ throw new RangeError('length must be positive');
28
+ }
29
+ const mask = (BigInt(1) << BigInt(length)) - BigInt(1);
30
+ return (val >> BigInt(start)) & mask;
31
+ };
32
+
33
+ export const hammingWeight = (val) => {
34
+ if (typeof val !== 'bigint') {
35
+ throw new TypeError('val must be bigint');
36
+ }
37
+ let count = 0;
38
+ let v = val < 0n ? -val : val;
39
+ while (v > 0n) {
40
+ v = v & (v - 1n);
41
+ count++;
42
+ }
43
+ return count;
44
+ };
45
+
46
+ export const bitRange = (val, min, max) => {
47
+ if (typeof val !== 'bigint') {
48
+ throw new TypeError('val must be bigint');
49
+ }
50
+ if (typeof min !== 'number' || !Number.isInteger(min)) {
51
+ throw new TypeError('min must be an integer');
52
+ }
53
+ if (min < 0) {
54
+ throw new RangeError('min must be non-negative');
55
+ }
56
+ if (typeof max !== 'number' || !Number.isInteger(max)) {
57
+ throw new TypeError('max must be an integer');
58
+ }
59
+ if (max <= min) {
60
+ throw new RangeError('max must be greater than min');
61
+ }
62
+ const shift = BigInt(min);
63
+ const mask = (BigInt(1) << BigInt(max - min)) - BigInt(1);
64
+ return (val >> shift) & mask;
65
+ };
6
66
 
7
- export const extractBits = (val, start, length) => {
8
- const mask = (BigInt(1) << BigInt(length)) - BigInt(1);
9
- return (val >> BigInt(start)) & mask;
10
- };
67
+ export const popcountNum = (val) => {
68
+ if (typeof val !== 'number' || !Number.isInteger(val) || val < 0) {
69
+ throw new TypeError('val must be a non-negative integer');
70
+ }
71
+ let count = 0;
72
+ let v = val >>> 0;
73
+ while (v) {
74
+ count += v & 1;
75
+ v >>>= 1;
76
+ }
77
+ return count;
78
+ };
11
79
 
12
- export const hammingWeight = (val) => {
13
- let count = 0;
14
- while (val > 0n) {
15
- count += val & 1n ? 1 : 0;
16
- val >>= 1n;
17
- }
18
- return count;
19
- };
80
+ export const clz = (val) => {
81
+ if (typeof val !== 'bigint') {
82
+ throw new TypeError('val must be bigint');
83
+ }
84
+ if (val === 0n) return 64;
85
+ let count = 0;
86
+ let mask = 1n << 63n;
87
+ while ((val & mask) === 0n) {
88
+ count++;
89
+ mask >>= 1n;
90
+ }
91
+ return count;
92
+ };
20
93
 
21
- export const bitRange = (val, min, max) => {
22
- const shift = BigInt(min);
23
- const mask = (BigInt(1) << BigInt(max - min)) - BigInt(1);
24
- return (val >> shift) & mask;
25
- };
94
+ export const ctz = (val) => {
95
+ if (typeof val !== 'bigint') {
96
+ throw new TypeError('val must be bigint');
97
+ }
98
+ if (val === 0n) return 64;
99
+ let count = 0;
100
+ let mask = 1n;
101
+ while ((val & mask) === 0n) {
102
+ count++;
103
+ mask <<= 1n;
104
+ }
105
+ return count;
106
+ };
107
+
108
+ export const reverseBits = (val, width = 64) => {
109
+ if (typeof val !== 'bigint') {
110
+ throw new TypeError('val must be bigint');
111
+ }
112
+ if (typeof width !== 'number' || !Number.isInteger(width) || width <= 0) {
113
+ throw new TypeError('width must be a positive integer');
114
+ }
115
+ let result = 0n;
116
+ for (let i = 0; i < width; i++) {
117
+ result = (result << 1n) | ((val >> BigInt(i)) & 1n);
118
+ }
119
+ return result;
120
+ };
121
+
122
+ export const setBit = (val, pos) => {
123
+ if (typeof val !== 'bigint') {
124
+ throw new TypeError('val must be bigint');
125
+ }
126
+ if (typeof pos !== 'number' || !Number.isInteger(pos) || pos < 0) {
127
+ throw new TypeError('pos must be a non-negative integer');
128
+ }
129
+ return val | (1n << BigInt(pos));
130
+ };
131
+
132
+ export const clearBit = (val, pos) => {
133
+ if (typeof val !== 'bigint') {
134
+ throw new TypeError('val must be bigint');
135
+ }
136
+ if (typeof pos !== 'number' || !Number.isInteger(pos) || pos < 0) {
137
+ throw new TypeError('pos must be a non-negative integer');
138
+ }
139
+ return val & ~(1n << BigInt(pos));
140
+ };
141
+
142
+ export const toggleBit = (val, pos) => {
143
+ if (typeof val !== 'bigint') {
144
+ throw new TypeError('val must be bigint');
145
+ }
146
+ if (typeof pos !== 'number' || !Number.isInteger(pos) || pos < 0) {
147
+ throw new TypeError('pos must be a non-negative integer');
148
+ }
149
+ return val ^ (1n << BigInt(pos));
150
+ };
@@ -1,12 +1,21 @@
1
1
  export const categorical = (rng, categories, probabilities) => {
2
- if (!categories || !probabilities) throw new Error('Categories and probabilities required');
3
- if (categories.length === 0) throw new Error('Categories cannot be empty');
4
- if (probabilities.length !== categories.length) {
5
- throw new Error('Probabilities and categories must have same length');
6
- }
2
+ if (!rng || typeof rng.nextFloat !== 'function') {
3
+ throw new TypeError('First argument must be RNG instance');
4
+ }
5
+ if (!Array.isArray(categories) || !Array.isArray(probabilities)) {
6
+ throw new TypeError('Categories and probabilities must be arrays');
7
+ }
8
+ if (categories.length === 0) {
9
+ throw new RangeError('Categories cannot be empty');
10
+ }
11
+ if (probabilities.length !== categories.length) {
12
+ throw new RangeError('Probabilities and categories must have same length');
13
+ }
7
14
 
8
- const total = probabilities.reduce((a, b) => a + b, 0);
9
- if (total <= 0) throw new Error('Probabilities must sum to positive value');
15
+ const total = probabilities.reduce((a, b) => a + b, 0);
16
+ if (total <= 0) {
17
+ throw new RangeError('Probabilities must sum to positive value');
18
+ }
10
19
 
11
20
  const normalized = probabilities.map(p => p / total);
12
21
 
@@ -24,34 +33,62 @@ export const categorical = (rng, categories, probabilities) => {
24
33
  };
25
34
 
26
35
  export const multinomial = (rng, n, categories, probabilities) => {
27
- const result = {};
28
- for (const cat of categories) {
29
- result[cat] = 0;
30
- }
36
+ if (!rng || typeof rng.nextFloat !== 'function') {
37
+ throw new TypeError('First argument must be RNG instance');
38
+ }
39
+ if (typeof n !== 'number' || !Number.isInteger(n)) {
40
+ throw new TypeError('n must be an integer');
41
+ }
42
+ if (n <= 0) {
43
+ throw new RangeError('n must be positive');
44
+ }
45
+ if (!Array.isArray(categories) || categories.length === 0) {
46
+ throw new RangeError('categories must be a non-empty array');
47
+ }
48
+ if (!Array.isArray(probabilities) || probabilities.length === 0) {
49
+ throw new RangeError('probabilities must be a non-empty array');
50
+ }
51
+ if (probabilities.length !== categories.length) {
52
+ throw new RangeError('probabilities and categories must have same length');
53
+ }
54
+ const result = {};
55
+ for (const cat of categories) {
56
+ result[cat] = 0;
57
+ }
31
58
 
32
- for (let i = 0; i < n; i++) {
33
- const pick = categorical(rng, categories, probabilities);
34
- result[pick]++;
35
- }
59
+ for (let i = 0; i < n; i++) {
60
+ const pick = categorical(rng, categories, probabilities);
61
+ result[pick]++;
62
+ }
36
63
 
37
- return result;
38
- };
64
+ return result;
65
+ };
39
66
 
40
67
  export const categorical2D = (rng, matrix) => {
41
- const rows = matrix.length;
42
- const cols = matrix[0].length;
43
-
44
- const total = matrix.flat().reduce((a, b) => a + b, 0);
45
- let rand = rng.nextFloat() * total;
68
+ if (!rng || typeof rng.nextFloat !== 'function') {
69
+ throw new TypeError('First argument must be RNG instance');
70
+ }
71
+ if (!Array.isArray(matrix) || matrix.length === 0) {
72
+ throw new RangeError('matrix must be non-empty array');
73
+ }
74
+ const rows = matrix.length;
75
+ const cols = matrix[0].length;
46
76
 
47
- for (let i = 0; i < rows; i++) {
48
- for (let j = 0; j < cols; j++) {
49
- rand -= matrix[i][j];
50
- if (rand <= 0) {
51
- return [i, j];
52
- }
77
+ const flat = matrix.flat();
78
+ const total = flat.reduce((a, b) => a + b, 0);
79
+ if (total <= 0) {
80
+ throw new RangeError('Matrix values must sum to positive');
53
81
  }
54
- }
82
+
83
+ let cumsum = 0;
84
+ const rand = rng.nextFloat() * total;
55
85
 
56
- return [rows - 1, cols - 1];
57
- };
86
+ for (let idx = 0; idx < flat.length; idx++) {
87
+ cumsum += flat[idx];
88
+ if (rand < cumsum) {
89
+ return [Math.floor(idx / cols), idx % cols];
90
+ }
91
+ }
92
+
93
+ return [rows - 1, cols - 1];
94
+ };