@danielsimonjr/mathts-parallel 0.6.10 → 0.6.11

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 (3) hide show
  1. package/dist/index.js +1093 -1744
  2. package/dist/matrix.worker.js +88 -110
  3. package/package.json +17 -6
package/dist/index.js CHANGED
@@ -1,1885 +1,1234 @@
1
- // src/ComputePool.ts
2
- import {
3
- pairwiseSum,
4
- norm2,
5
- pairwiseDot,
6
- scaledDistance,
7
- sumSquaredDeviations
8
- } from "@danielsimonjr/mathts-core";
9
- import {
10
- MathWorkerPool,
11
- Transfer
12
- } from "@danielsimonjr/mathts-workerpool";
13
-
14
- // src/strategies/chunk.ts
15
- var DEFAULT_CHUNK_OPTIONS = {
16
- minChunkSize: 1e3,
17
- maxChunks: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
18
- targetChunkSize: 0,
19
- // 0 = auto
20
- balanced: true
1
+ import { norm2, pairwiseDot, pairwiseSum, scaledDistance, sumSquaredDeviations } from "@danielsimonjr/mathts-core";
2
+ import { MathWorkerPool, Transfer } from "@danielsimonjr/mathts-workerpool";
3
+ //#region src/strategies/chunk.ts
4
+ const DEFAULT_CHUNK_OPTIONS = {
5
+ minChunkSize: 1e3,
6
+ maxChunks: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
7
+ targetChunkSize: 0,
8
+ balanced: true
21
9
  };
22
10
  function calculateOptimalChunks(totalElements, options = {}) {
23
- const { minChunkSize, maxChunks, targetChunkSize } = {
24
- ...DEFAULT_CHUNK_OPTIONS,
25
- ...options
26
- };
27
- if (totalElements === 0) return 0;
28
- if (targetChunkSize > 0) {
29
- return Math.min(maxChunks, Math.ceil(totalElements / targetChunkSize));
30
- }
31
- const maxPossibleChunks = Math.floor(totalElements / minChunkSize);
32
- return Math.max(1, Math.min(maxChunks, maxPossibleChunks));
11
+ const { minChunkSize, maxChunks, targetChunkSize } = {
12
+ ...DEFAULT_CHUNK_OPTIONS,
13
+ ...options
14
+ };
15
+ if (totalElements === 0) return 0;
16
+ if (targetChunkSize > 0) return Math.min(maxChunks, Math.ceil(totalElements / targetChunkSize));
17
+ const maxPossibleChunks = Math.floor(totalElements / minChunkSize);
18
+ return Math.max(1, Math.min(maxChunks, maxPossibleChunks));
33
19
  }
34
20
  function chunkFloat64Array(data, options = {}) {
35
- const { minChunkSize, balanced } = { ...DEFAULT_CHUNK_OPTIONS, ...options };
36
- const totalElements = data.length;
37
- if (totalElements === 0) {
38
- return {
39
- chunks: [],
40
- chunkInfo: [],
41
- totalElements: 0,
42
- numChunks: 0
43
- };
44
- }
45
- const numChunks = calculateOptimalChunks(totalElements, options);
46
- if (numChunks <= 1) {
47
- return {
48
- chunks: [data],
49
- chunkInfo: [
50
- {
51
- startIndex: 0,
52
- endIndex: totalElements,
53
- length: totalElements,
54
- chunkIndex: 0
55
- }
56
- ],
57
- totalElements,
58
- numChunks: 1
59
- };
60
- }
61
- const chunks = [];
62
- const chunkInfo = [];
63
- if (balanced) {
64
- const baseChunkSize = Math.floor(totalElements / numChunks);
65
- const remainder = totalElements % numChunks;
66
- let currentIndex = 0;
67
- for (let i = 0; i < numChunks; i++) {
68
- const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
69
- const endIndex = currentIndex + thisChunkSize;
70
- chunks.push(data.subarray(currentIndex, endIndex));
71
- chunkInfo.push({
72
- startIndex: currentIndex,
73
- endIndex,
74
- length: thisChunkSize,
75
- chunkIndex: i
76
- });
77
- currentIndex = endIndex;
78
- }
79
- } else {
80
- const chunkSize = Math.max(minChunkSize, Math.ceil(totalElements / numChunks));
81
- let currentIndex = 0;
82
- let chunkIndex = 0;
83
- while (currentIndex < totalElements) {
84
- const endIndex = Math.min(currentIndex + chunkSize, totalElements);
85
- const thisChunkSize = endIndex - currentIndex;
86
- chunks.push(data.subarray(currentIndex, endIndex));
87
- chunkInfo.push({
88
- startIndex: currentIndex,
89
- endIndex,
90
- length: thisChunkSize,
91
- chunkIndex
92
- });
93
- currentIndex = endIndex;
94
- chunkIndex++;
95
- }
96
- }
97
- return {
98
- chunks,
99
- chunkInfo,
100
- totalElements,
101
- numChunks: chunks.length
102
- };
21
+ const { minChunkSize, balanced } = {
22
+ ...DEFAULT_CHUNK_OPTIONS,
23
+ ...options
24
+ };
25
+ const totalElements = data.length;
26
+ if (totalElements === 0) return {
27
+ chunks: [],
28
+ chunkInfo: [],
29
+ totalElements: 0,
30
+ numChunks: 0
31
+ };
32
+ const numChunks = calculateOptimalChunks(totalElements, options);
33
+ if (numChunks <= 1) return {
34
+ chunks: [data],
35
+ chunkInfo: [{
36
+ startIndex: 0,
37
+ endIndex: totalElements,
38
+ length: totalElements,
39
+ chunkIndex: 0
40
+ }],
41
+ totalElements,
42
+ numChunks: 1
43
+ };
44
+ const chunks = [];
45
+ const chunkInfo = [];
46
+ if (balanced) {
47
+ const baseChunkSize = Math.floor(totalElements / numChunks);
48
+ const remainder = totalElements % numChunks;
49
+ let currentIndex = 0;
50
+ for (let i = 0; i < numChunks; i++) {
51
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
52
+ const endIndex = currentIndex + thisChunkSize;
53
+ chunks.push(data.subarray(currentIndex, endIndex));
54
+ chunkInfo.push({
55
+ startIndex: currentIndex,
56
+ endIndex,
57
+ length: thisChunkSize,
58
+ chunkIndex: i
59
+ });
60
+ currentIndex = endIndex;
61
+ }
62
+ } else {
63
+ const chunkSize = Math.max(minChunkSize, Math.ceil(totalElements / numChunks));
64
+ let currentIndex = 0;
65
+ let chunkIndex = 0;
66
+ while (currentIndex < totalElements) {
67
+ const endIndex = Math.min(currentIndex + chunkSize, totalElements);
68
+ const thisChunkSize = endIndex - currentIndex;
69
+ chunks.push(data.subarray(currentIndex, endIndex));
70
+ chunkInfo.push({
71
+ startIndex: currentIndex,
72
+ endIndex,
73
+ length: thisChunkSize,
74
+ chunkIndex
75
+ });
76
+ currentIndex = endIndex;
77
+ chunkIndex++;
78
+ }
79
+ }
80
+ return {
81
+ chunks,
82
+ chunkInfo,
83
+ totalElements,
84
+ numChunks: chunks.length
85
+ };
103
86
  }
104
87
  function chunkArray(data, options = {}) {
105
- const { balanced } = { ...DEFAULT_CHUNK_OPTIONS, ...options };
106
- const totalElements = data.length;
107
- if (totalElements === 0) {
108
- return {
109
- chunks: [],
110
- chunkInfo: [],
111
- totalElements: 0,
112
- numChunks: 0
113
- };
114
- }
115
- const numChunks = calculateOptimalChunks(totalElements, options);
116
- if (numChunks <= 1) {
117
- return {
118
- chunks: [data],
119
- chunkInfo: [
120
- {
121
- startIndex: 0,
122
- endIndex: totalElements,
123
- length: totalElements,
124
- chunkIndex: 0
125
- }
126
- ],
127
- totalElements,
128
- numChunks: 1
129
- };
130
- }
131
- const chunks = [];
132
- const chunkInfo = [];
133
- if (balanced) {
134
- const baseChunkSize = Math.floor(totalElements / numChunks);
135
- const remainder = totalElements % numChunks;
136
- let currentIndex = 0;
137
- for (let i = 0; i < numChunks; i++) {
138
- const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
139
- const endIndex = currentIndex + thisChunkSize;
140
- chunks.push(data.slice(currentIndex, endIndex));
141
- chunkInfo.push({
142
- startIndex: currentIndex,
143
- endIndex,
144
- length: thisChunkSize,
145
- chunkIndex: i
146
- });
147
- currentIndex = endIndex;
148
- }
149
- } else {
150
- const chunkSize = Math.ceil(totalElements / numChunks);
151
- let currentIndex = 0;
152
- let chunkIndex = 0;
153
- while (currentIndex < totalElements) {
154
- const endIndex = Math.min(currentIndex + chunkSize, totalElements);
155
- const thisChunkSize = endIndex - currentIndex;
156
- chunks.push(data.slice(currentIndex, endIndex));
157
- chunkInfo.push({
158
- startIndex: currentIndex,
159
- endIndex,
160
- length: thisChunkSize,
161
- chunkIndex
162
- });
163
- currentIndex = endIndex;
164
- chunkIndex++;
165
- }
166
- }
167
- return {
168
- chunks,
169
- chunkInfo,
170
- totalElements,
171
- numChunks: chunks.length
172
- };
88
+ const { balanced } = {
89
+ ...DEFAULT_CHUNK_OPTIONS,
90
+ ...options
91
+ };
92
+ const totalElements = data.length;
93
+ if (totalElements === 0) return {
94
+ chunks: [],
95
+ chunkInfo: [],
96
+ totalElements: 0,
97
+ numChunks: 0
98
+ };
99
+ const numChunks = calculateOptimalChunks(totalElements, options);
100
+ if (numChunks <= 1) return {
101
+ chunks: [data],
102
+ chunkInfo: [{
103
+ startIndex: 0,
104
+ endIndex: totalElements,
105
+ length: totalElements,
106
+ chunkIndex: 0
107
+ }],
108
+ totalElements,
109
+ numChunks: 1
110
+ };
111
+ const chunks = [];
112
+ const chunkInfo = [];
113
+ if (balanced) {
114
+ const baseChunkSize = Math.floor(totalElements / numChunks);
115
+ const remainder = totalElements % numChunks;
116
+ let currentIndex = 0;
117
+ for (let i = 0; i < numChunks; i++) {
118
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
119
+ const endIndex = currentIndex + thisChunkSize;
120
+ chunks.push(data.slice(currentIndex, endIndex));
121
+ chunkInfo.push({
122
+ startIndex: currentIndex,
123
+ endIndex,
124
+ length: thisChunkSize,
125
+ chunkIndex: i
126
+ });
127
+ currentIndex = endIndex;
128
+ }
129
+ } else {
130
+ const chunkSize = Math.ceil(totalElements / numChunks);
131
+ let currentIndex = 0;
132
+ let chunkIndex = 0;
133
+ while (currentIndex < totalElements) {
134
+ const endIndex = Math.min(currentIndex + chunkSize, totalElements);
135
+ const thisChunkSize = endIndex - currentIndex;
136
+ chunks.push(data.slice(currentIndex, endIndex));
137
+ chunkInfo.push({
138
+ startIndex: currentIndex,
139
+ endIndex,
140
+ length: thisChunkSize,
141
+ chunkIndex
142
+ });
143
+ currentIndex = endIndex;
144
+ chunkIndex++;
145
+ }
146
+ }
147
+ return {
148
+ chunks,
149
+ chunkInfo,
150
+ totalElements,
151
+ numChunks: chunks.length
152
+ };
173
153
  }
174
154
  function mergeFloat64Chunks(chunks) {
175
- if (chunks.length === 0) return new Float64Array(0);
176
- if (chunks.length === 1) return chunks[0];
177
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
178
- const result = new Float64Array(totalLength);
179
- let offset = 0;
180
- for (const chunk of chunks) {
181
- result.set(chunk, offset);
182
- offset += chunk.length;
183
- }
184
- return result;
155
+ if (chunks.length === 0) return /* @__PURE__ */ new Float64Array(0);
156
+ if (chunks.length === 1) return chunks[0];
157
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
158
+ const result = new Float64Array(totalLength);
159
+ let offset = 0;
160
+ for (const chunk of chunks) {
161
+ result.set(chunk, offset);
162
+ offset += chunk.length;
163
+ }
164
+ return result;
185
165
  }
186
166
  function mergeArrayChunks(chunks) {
187
- if (chunks.length === 0) return [];
188
- if (chunks.length === 1) return chunks[0];
189
- return chunks.flat();
167
+ if (chunks.length === 0) return [];
168
+ if (chunks.length === 1) return chunks[0];
169
+ return chunks.flat();
190
170
  }
191
171
  function shouldParallelize(elementCount, threshold = 1e4) {
192
- return elementCount >= threshold;
172
+ return elementCount >= threshold;
193
173
  }
194
174
  function partitionRange(start, end, options = {}) {
195
- const totalElements = end - start;
196
- const numChunks = calculateOptimalChunks(totalElements, options);
197
- if (numChunks <= 1) {
198
- return [[start, end]];
199
- }
200
- const partitions = [];
201
- const baseChunkSize = Math.floor(totalElements / numChunks);
202
- const remainder = totalElements % numChunks;
203
- let currentIndex = start;
204
- for (let i = 0; i < numChunks; i++) {
205
- const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
206
- const endIndex = currentIndex + thisChunkSize;
207
- partitions.push([currentIndex, endIndex]);
208
- currentIndex = endIndex;
209
- }
210
- return partitions;
175
+ const totalElements = end - start;
176
+ const numChunks = calculateOptimalChunks(totalElements, options);
177
+ if (numChunks <= 1) return [[start, end]];
178
+ const partitions = [];
179
+ const baseChunkSize = Math.floor(totalElements / numChunks);
180
+ const remainder = totalElements % numChunks;
181
+ let currentIndex = start;
182
+ for (let i = 0; i < numChunks; i++) {
183
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
184
+ const endIndex = currentIndex + thisChunkSize;
185
+ partitions.push([currentIndex, endIndex]);
186
+ currentIndex = endIndex;
187
+ }
188
+ return partitions;
211
189
  }
212
190
  function partition2D(rows, cols, options = {}) {
213
- const rowPartitions = partitionRange(0, rows, options);
214
- return rowPartitions.map(([rowStart, rowEnd]) => [rowStart, rowEnd, 0, cols]);
215
- }
216
-
217
- // src/ops/bitwise.ts
191
+ return partitionRange(0, rows, options).map(([rowStart, rowEnd]) => [
192
+ rowStart,
193
+ rowEnd,
194
+ 0,
195
+ cols
196
+ ]);
197
+ }
198
+ //#endregion
199
+ //#region src/ops/bitwise.ts
218
200
  function applyBinaryChunk(a, b, out, start, length, outStart, op) {
219
- const end = start + length;
220
- switch (op) {
221
- case "bitAnd":
222
- for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] & b[i];
223
- return;
224
- case "bitOr":
225
- for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] | b[i];
226
- return;
227
- case "bitXor":
228
- for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] ^ b[i];
229
- return;
230
- case "leftShift":
231
- for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] << b[i];
232
- return;
233
- case "rightArithShift":
234
- for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >> b[i];
235
- return;
236
- case "rightLogShift":
237
- for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >>> b[i];
238
- return;
239
- }
201
+ const end = start + length;
202
+ switch (op) {
203
+ case "bitAnd":
204
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] & b[i];
205
+ return;
206
+ case "bitOr":
207
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] | b[i];
208
+ return;
209
+ case "bitXor":
210
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] ^ b[i];
211
+ return;
212
+ case "leftShift":
213
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] << b[i];
214
+ return;
215
+ case "rightArithShift":
216
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >> b[i];
217
+ return;
218
+ case "rightLogShift":
219
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >>> b[i];
220
+ return;
221
+ }
240
222
  }
241
223
  function runBinary(a, b, op, options = {}) {
242
- if (a.length !== b.length) {
243
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
244
- }
245
- const out = new Int32Array(a.length);
246
- if (a.length === 0) return out;
247
- const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
248
- const base = Math.floor(a.length / numChunks);
249
- const remainder = a.length % numChunks;
250
- let start = 0;
251
- for (let c = 0; c < numChunks; c++) {
252
- const len = base + (c < remainder ? 1 : 0);
253
- if (len === 0) continue;
254
- applyBinaryChunk(a, b, out, start, len, start, op);
255
- start += len;
256
- }
257
- return out;
224
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
225
+ const out = new Int32Array(a.length);
226
+ if (a.length === 0) return out;
227
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
228
+ const base = Math.floor(a.length / numChunks);
229
+ const remainder = a.length % numChunks;
230
+ let start = 0;
231
+ for (let c = 0; c < numChunks; c++) {
232
+ const len = base + (c < remainder ? 1 : 0);
233
+ if (len === 0) continue;
234
+ applyBinaryChunk(a, b, out, start, len, start, op);
235
+ start += len;
236
+ }
237
+ return out;
258
238
  }
259
239
  function bitAnd(a, b, options) {
260
- return runBinary(a, b, "bitAnd", options);
240
+ return runBinary(a, b, "bitAnd", options);
261
241
  }
262
242
  function bitOr(a, b, options) {
263
- return runBinary(a, b, "bitOr", options);
243
+ return runBinary(a, b, "bitOr", options);
264
244
  }
265
245
  function bitXor(a, b, options) {
266
- return runBinary(a, b, "bitXor", options);
246
+ return runBinary(a, b, "bitXor", options);
267
247
  }
268
248
  function leftShift(a, b, options) {
269
- if (typeof b === "number") return runBinaryScalar(a, b, "leftShift", options);
270
- return runBinary(a, b, "leftShift", options);
249
+ if (typeof b === "number") return runBinaryScalar(a, b, "leftShift", options);
250
+ return runBinary(a, b, "leftShift", options);
271
251
  }
272
252
  function rightArithShift(a, b, options) {
273
- if (typeof b === "number") return runBinaryScalar(a, b, "rightArithShift", options);
274
- return runBinary(a, b, "rightArithShift", options);
253
+ if (typeof b === "number") return runBinaryScalar(a, b, "rightArithShift", options);
254
+ return runBinary(a, b, "rightArithShift", options);
275
255
  }
276
256
  function rightLogShift(a, b, options) {
277
- if (typeof b === "number") return runBinaryScalar(a, b, "rightLogShift", options);
278
- return runBinary(a, b, "rightLogShift", options);
257
+ if (typeof b === "number") return runBinaryScalar(a, b, "rightLogShift", options);
258
+ return runBinary(a, b, "rightLogShift", options);
279
259
  }
280
260
  function bitNot(a, options = {}) {
281
- const out = new Int32Array(a.length);
282
- if (a.length === 0) return out;
283
- const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
284
- const base = Math.floor(a.length / numChunks);
285
- const remainder = a.length % numChunks;
286
- let start = 0;
287
- for (let c = 0; c < numChunks; c++) {
288
- const len = base + (c < remainder ? 1 : 0);
289
- if (len === 0) continue;
290
- const end = start + len;
291
- for (let i = start; i < end; i++) out[i] = ~a[i];
292
- start += len;
293
- }
294
- return out;
261
+ const out = new Int32Array(a.length);
262
+ if (a.length === 0) return out;
263
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
264
+ const base = Math.floor(a.length / numChunks);
265
+ const remainder = a.length % numChunks;
266
+ let start = 0;
267
+ for (let c = 0; c < numChunks; c++) {
268
+ const len = base + (c < remainder ? 1 : 0);
269
+ if (len === 0) continue;
270
+ const end = start + len;
271
+ for (let i = start; i < end; i++) out[i] = ~a[i];
272
+ start += len;
273
+ }
274
+ return out;
295
275
  }
296
276
  function runBinaryScalar(a, scalar, op, options = {}) {
297
- const out = new Int32Array(a.length);
298
- if (a.length === 0) return out;
299
- const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
300
- const base = Math.floor(a.length / numChunks);
301
- const remainder = a.length % numChunks;
302
- let start = 0;
303
- for (let c = 0; c < numChunks; c++) {
304
- const len = base + (c < remainder ? 1 : 0);
305
- if (len === 0) continue;
306
- const end = start + len;
307
- switch (op) {
308
- case "leftShift":
309
- for (let i = start; i < end; i++) out[i] = a[i] << scalar;
310
- break;
311
- case "rightArithShift":
312
- for (let i = start; i < end; i++) out[i] = a[i] >> scalar;
313
- break;
314
- case "rightLogShift":
315
- for (let i = start; i < end; i++) out[i] = a[i] >>> scalar;
316
- break;
317
- // The non-shift ops never go through the scalar path — caller
318
- // routes only the three shift ops here. Listed for exhaustiveness.
319
- case "bitAnd":
320
- for (let i = start; i < end; i++) out[i] = a[i] & scalar;
321
- break;
322
- case "bitOr":
323
- for (let i = start; i < end; i++) out[i] = a[i] | scalar;
324
- break;
325
- case "bitXor":
326
- for (let i = start; i < end; i++) out[i] = a[i] ^ scalar;
327
- break;
328
- }
329
- start += len;
330
- }
331
- return out;
332
- }
333
-
334
- // src/ComputePool.ts
277
+ const out = new Int32Array(a.length);
278
+ if (a.length === 0) return out;
279
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
280
+ const base = Math.floor(a.length / numChunks);
281
+ const remainder = a.length % numChunks;
282
+ let start = 0;
283
+ for (let c = 0; c < numChunks; c++) {
284
+ const len = base + (c < remainder ? 1 : 0);
285
+ if (len === 0) continue;
286
+ const end = start + len;
287
+ switch (op) {
288
+ case "leftShift":
289
+ for (let i = start; i < end; i++) out[i] = a[i] << scalar;
290
+ break;
291
+ case "rightArithShift":
292
+ for (let i = start; i < end; i++) out[i] = a[i] >> scalar;
293
+ break;
294
+ case "rightLogShift":
295
+ for (let i = start; i < end; i++) out[i] = a[i] >>> scalar;
296
+ break;
297
+ case "bitAnd":
298
+ for (let i = start; i < end; i++) out[i] = a[i] & scalar;
299
+ break;
300
+ case "bitOr":
301
+ for (let i = start; i < end; i++) out[i] = a[i] | scalar;
302
+ break;
303
+ case "bitXor": for (let i = start; i < end; i++) out[i] = a[i] ^ scalar;
304
+ }
305
+ start += len;
306
+ }
307
+ return out;
308
+ }
309
+ //#endregion
310
+ //#region src/ComputePool.ts
335
311
  function resolveOpThreshold(t) {
336
- if (t === "never") return Number.MAX_SAFE_INTEGER;
337
- if (t === "always") return 0;
338
- return t;
339
- }
340
- var DEFAULT_THRESHOLD_BY_OP = {
341
- // element-wise: overhead dominates at all tested sizes
342
- add: "never",
343
- subtract: "never",
344
- multiply: "never",
345
- divide: "never",
346
- pow: "never",
347
- scale: "never",
348
- abs: "never",
349
- negate: "never",
350
- exp: "never",
351
- log: "never",
352
- sin: "never",
353
- cos: "never",
354
- tan: "never",
355
- sign: "never",
356
- // Added 2026-07-02 (WS-2): these element-wise ops were absent from the map and
357
- // silently defaulted to the 50 000 global threshold — an inconsistency with their
358
- // siblings above (surfaced by parallel-pairing.md). Benchmarked (tools/benchmark/
359
- // parallel/run.ts sqrt square) — memory-bound, speedup 0.12–0.65×, no persistent
360
- // break-even → 'never', matching the class.
361
- sqrt: "never",
362
- square: "never",
363
- // tensor contraction: parallelize when contracted-axis volume >= 8K
364
- tensordot: 8192,
365
- // reductions: overhead dominates
366
- sum: "never",
367
- mean: "never",
368
- variance: "never",
369
- parallelStatProd: "never",
370
- normalCDF: "never",
371
- // Added 2026-07-02 (WS-2): reductions that were absent from the map and defaulted
372
- // to the global threshold; benchmarked memory-bound (0.09–1.19×, occasional noise
373
- // spikes but no persistent win) → 'never'.
374
- norm: "never",
375
- // ACCURACY NOTE: `dot`/`distance` staying 'never' is also what keeps them stable. The
376
- // sequential fallbacks use core's pairwiseDot / scaledDistance, but the worker kernels
377
- // (dotChunk / distanceChunk in workerpool) still accumulate naively. Do NOT tune these off
378
- // 'never' without giving those kernels the same pairwise/scaled accumulation first, or large
379
- // inputs — the regime where O(n)·ε is worst — would silently revert to naive accuracy.
380
- dot: "never",
381
- // Added 2026-07-02 (WS-2): `min`/`max` are now `OpName`s (added to the union) so
382
- // they're tunable. Same memory-bound reduction profile as sum/mean/norm above →
383
- // 'never'. Their `functions/` call sites previously dispatched to the worker pool
384
- // unconditionally (no `shouldParallelize` gate), paying dispatch overhead on every
385
- // call; they now respect this threshold (sequential) with a `shouldParallelize` gate.
386
- min: "never",
387
- max: "never",
388
- // Added 2026-07-03 (WS-2, DGT sweep): the remaining memory-bound reductions that
389
- // defaulted to the 50 000 global threshold and dispatched unconditionally at every
390
- // `functions/` call site. Same class as sum/mean/min/max/norm/dot → 'never'. Now
391
- // gated INSIDE the ComputePool reduction methods (inline sequential fallback), so
392
- // ALL call sites honor the threshold without per-site edits. `std` derives from
393
- // `variance`; `min`/`max` derive from `minMax`.
394
- minMax: "never",
395
- std: "never",
396
- prod: "never",
397
- distance: "never",
398
- // Added 2026-07-04 (WS-2 completion): the five OpNames that were absent from
399
- // this map and silently rode the untested 50 000 global default. Benchmarked
400
- // (tools/benchmarks/ws2-missing-ops.mjs, medians of 9 interleaved reps):
401
- // histogram 0.23-0.56x, transpose 0.01-0.28x, matvec 0.00-0.05x, outer
402
- // 0.01-0.50x at every size up to 4.2M elements - memory-bound / copy-dominated,
403
- // no break-even -> 'never'. Their ComputePool methods previously dispatched to
404
- // the worker pool UNCONDITIONALLY (no shouldParallelize gate); they now gate
405
- // with an inline sequential fallback like the reductions above.
406
- histogram: "never",
407
- transpose: "never",
408
- matvec: "never",
409
- outer: "never",
410
- // integrateFanOut loses ~50-100x for cheap integrands (0.01-0.02x at 128-32768
411
- // evals). The fan-out is CALLER-OPT-IN (integration.ts dispatches only when the
412
- // caller passes workerCount > 1), so this entry documents the default posture;
413
- // no shouldParallelize call consults it. Expensive (ms-scale) integrands are the
414
- // one case where the fan-out pays off.
415
- integrateChunk: "never",
416
- // Added 2026-07-05 (WS-2 addendum): the bitwise family gated via a NAMELESS
417
- // shouldParallelize(len) -> the untested global 50k. Benchmarked
418
- // (tools/benchmarks/ws2-bitwise-ops.mjs, medians of 9 interleaved reps):
419
- // bitAnd 0.07-0.15x, bitNot 0.06-0.09x, leftShift 0.04-0.07x at every size up
420
- // to 4M elements - Int32Array element-wise is maximally memory-bound; the
421
- // worker path loses 7-25x. All seven -> 'never'; the methods now pass their
422
- // op name so these entries actually govern.
423
- bitAnd: "never",
424
- bitOr: "never",
425
- bitXor: "never",
426
- bitNot: "never",
427
- leftShift: "never",
428
- rightArithShift: "never",
429
- rightLogShift: "never",
430
- // signal: overhead dominates or break-even never reached
431
- parallelFFT: "never",
432
- parallelConv: "never",
433
- fft2d: "never",
434
- // geometry: overhead dominates
435
- distanceMatrix: "never",
436
- // special functions: parallel wins above these sizes
437
- erfc: 1e5,
438
- besselJ: 1e6,
439
- spectrogram: 65536,
440
- // linear algebra: parallel wins above these element counts
441
- matmul: 4096,
442
- // break-even at 64×64 (4,096 elements)
443
- matrixPower: 9216,
444
- // break-even at ~96×96
445
- characteristicPolynomial: 9216,
446
- // break-even at ~96×96
447
- // hypothesis tests: element-wise reduction (chiSquare) and post-sort
448
- // dot-product (KS/MW/SW). Threshold 4096 per Slice 3.10 spec.
449
- chiSquareTest: 4096,
450
- kolmogorovSmirnovTest: 4096,
451
- mannWhitneyTest: 4096,
452
- shapiroWilkTest: 4096,
453
- // distribution batch sampling: per-sample cost (rejection/transcendental) amortises
454
- // worker overhead above 100 000 samples (Slice 5.12).
455
- sampleChunk: 1e5
312
+ if (t === "never") return Number.MAX_SAFE_INTEGER;
313
+ if (t === "always") return 0;
314
+ return t;
315
+ }
316
+ const DEFAULT_THRESHOLD_BY_OP = {
317
+ add: "never",
318
+ subtract: "never",
319
+ multiply: "never",
320
+ divide: "never",
321
+ pow: "never",
322
+ scale: "never",
323
+ abs: "never",
324
+ negate: "never",
325
+ exp: "never",
326
+ log: "never",
327
+ sin: "never",
328
+ cos: "never",
329
+ tan: "never",
330
+ sign: "never",
331
+ sqrt: "never",
332
+ square: "never",
333
+ tensordot: 8192,
334
+ sum: "never",
335
+ mean: "never",
336
+ variance: "never",
337
+ parallelStatProd: "never",
338
+ normalCDF: "never",
339
+ norm: "never",
340
+ dot: "never",
341
+ min: "never",
342
+ max: "never",
343
+ minMax: "never",
344
+ std: "never",
345
+ prod: "never",
346
+ distance: "never",
347
+ histogram: "never",
348
+ transpose: "never",
349
+ matvec: "never",
350
+ outer: "never",
351
+ integrateChunk: "never",
352
+ bitAnd: "never",
353
+ bitOr: "never",
354
+ bitXor: "never",
355
+ bitNot: "never",
356
+ leftShift: "never",
357
+ rightArithShift: "never",
358
+ rightLogShift: "never",
359
+ parallelFFT: "never",
360
+ parallelConv: "never",
361
+ fft2d: "never",
362
+ distanceMatrix: "never",
363
+ erfc: 1e5,
364
+ besselJ: 1e6,
365
+ spectrogram: 65536,
366
+ matmul: 4096,
367
+ matrixPower: 9216,
368
+ characteristicPolynomial: 9216,
369
+ chiSquareTest: 4096,
370
+ kolmogorovSmirnovTest: 4096,
371
+ mannWhitneyTest: 4096,
372
+ shapiroWilkTest: 4096,
373
+ sampleChunk: 1e5
456
374
  };
457
- var DEFAULT_POOL_CONFIG = {
458
- enabled: true,
459
- minWorkers: 1,
460
- maxWorkers: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
461
- thresholdElements: 5e4,
462
- thresholdByOp: DEFAULT_THRESHOLD_BY_OP,
463
- chunkSize: 1e4,
464
- workerType: "auto",
465
- workerIdleTimeout: 6e4,
466
- taskTimeout: 3e5
467
- // 5 minutes
375
+ const DEFAULT_POOL_CONFIG = {
376
+ enabled: true,
377
+ minWorkers: 1,
378
+ maxWorkers: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
379
+ thresholdElements: 5e4,
380
+ thresholdByOp: DEFAULT_THRESHOLD_BY_OP,
381
+ chunkSize: 1e4,
382
+ workerType: "auto",
383
+ workerIdleTimeout: 6e4,
384
+ taskTimeout: 3e5
468
385
  };
469
386
  function mergeThresholds(base, override) {
470
- return override === void 0 ? base : { ...base, ...override };
387
+ return override === void 0 ? base : {
388
+ ...base,
389
+ ...override
390
+ };
471
391
  }
472
392
  function toParallelResult(result) {
473
- return {
474
- result: result.result,
475
- duration: result.duration,
476
- chunks: result.chunks,
477
- parallelized: result.parallelized
478
- };
393
+ return {
394
+ result: result.result,
395
+ duration: result.duration,
396
+ chunks: result.chunks,
397
+ parallelized: result.parallelized
398
+ };
479
399
  }
480
400
  function toWorkerConfig(config) {
481
- return {
482
- enabled: config.enabled,
483
- minWorkers: config.minWorkers,
484
- maxWorkers: config.maxWorkers,
485
- parallelThreshold: config.thresholdElements,
486
- chunkSize: config.chunkSize,
487
- workerType: config.workerType,
488
- idleTimeout: config.workerIdleTimeout,
489
- taskTimeout: config.taskTimeout
490
- };
401
+ return {
402
+ enabled: config.enabled,
403
+ minWorkers: config.minWorkers,
404
+ maxWorkers: config.maxWorkers,
405
+ parallelThreshold: config.thresholdElements,
406
+ chunkSize: config.chunkSize,
407
+ workerType: config.workerType,
408
+ idleTimeout: config.workerIdleTimeout,
409
+ taskTimeout: config.taskTimeout
410
+ };
491
411
  }
492
412
  function _rowMajorStrides(shape) {
493
- const strides = new Array(shape.length);
494
- let stride = 1;
495
- for (let i = shape.length - 1; i >= 0; i--) {
496
- strides[i] = stride;
497
- stride *= shape[i];
498
- }
499
- return strides;
413
+ const strides = new Array(shape.length);
414
+ let stride = 1;
415
+ for (let i = shape.length - 1; i >= 0; i--) {
416
+ strides[i] = stride;
417
+ stride *= shape[i];
418
+ }
419
+ return strides;
500
420
  }
501
421
  function _flatIndex(idx, strides) {
502
- let flat = 0;
503
- for (let i = 0; i < idx.length; i++) flat += idx[i] * strides[i];
504
- return flat;
422
+ let flat = 0;
423
+ for (let i = 0; i < idx.length; i++) flat += idx[i] * strides[i];
424
+ return flat;
505
425
  }
506
426
  function _forEachIndex(sizes, cb) {
507
- const rank = sizes.length;
508
- if (rank === 0) {
509
- cb([]);
510
- return;
511
- }
512
- const idx = new Array(rank).fill(0);
513
- const total = sizes.reduce((a, b) => a * b, 1);
514
- for (let n = 0; n < total; n++) {
515
- cb(idx.slice());
516
- for (let d = rank - 1; d >= 0; d--) {
517
- idx[d]++;
518
- if (idx[d] < sizes[d]) break;
519
- idx[d] = 0;
520
- }
521
- }
427
+ const rank = sizes.length;
428
+ if (rank === 0) {
429
+ cb([]);
430
+ return;
431
+ }
432
+ const idx = new Array(rank).fill(0);
433
+ const total = sizes.reduce((a, b) => a * b, 1);
434
+ for (let n = 0; n < total; n++) {
435
+ cb(idx.slice());
436
+ for (let d = rank - 1; d >= 0; d--) {
437
+ idx[d]++;
438
+ if (idx[d] < sizes[d]) break;
439
+ idx[d] = 0;
440
+ }
441
+ }
522
442
  }
523
443
  function tensordotChunkKernel(a, aShape, b, bShape, axesA, axesB, resultShape, outStart, outEnd) {
524
- const aStrides = _rowMajorStrides(aShape);
525
- const bStrides = _rowMajorStrides(bShape);
526
- const outStrides = _rowMajorStrides(resultShape);
527
- const aRank = aShape.length;
528
- const bRank = bShape.length;
529
- const contractedAxesA = Array.from(axesA);
530
- const contractedAxesB = Array.from(axesB);
531
- const contractedSetA = new Set(contractedAxesA);
532
- const contractedSetB = new Set(contractedAxesB);
533
- const aFreeAxes = [];
534
- const bFreeAxes = [];
535
- for (let i = 0; i < aRank; i++) if (!contractedSetA.has(i)) aFreeAxes.push(i);
536
- for (let i = 0; i < bRank; i++) if (!contractedSetB.has(i)) bFreeAxes.push(i);
537
- const contractedSizes = contractedAxesA.map((ax) => aShape[ax]);
538
- const chunkLen = outEnd - outStart;
539
- const result = new Float64Array(chunkLen);
540
- const outRank = resultShape.length;
541
- const outIdx = new Array(outRank);
542
- for (let outFlat = outStart; outFlat < outEnd; outFlat++) {
543
- let rem = outFlat;
544
- for (let d = 0; d < outRank; d++) {
545
- outIdx[d] = Math.floor(rem / outStrides[d]);
546
- rem -= outIdx[d] * outStrides[d];
547
- }
548
- const aFreeVals = outIdx.slice(0, aFreeAxes.length);
549
- const bFreeVals = outIdx.slice(aFreeAxes.length);
550
- let acc = 0;
551
- _forEachIndex(contractedSizes, (contractVals) => {
552
- const aIdx = new Array(aRank).fill(0);
553
- for (let fi = 0; fi < aFreeAxes.length; fi++) aIdx[aFreeAxes[fi]] = aFreeVals[fi];
554
- for (let ci = 0; ci < contractedAxesA.length; ci++)
555
- aIdx[contractedAxesA[ci]] = contractVals[ci];
556
- const bIdx = new Array(bRank).fill(0);
557
- for (let fi = 0; fi < bFreeAxes.length; fi++) bIdx[bFreeAxes[fi]] = bFreeVals[fi];
558
- for (let ci = 0; ci < contractedAxesB.length; ci++)
559
- bIdx[contractedAxesB[ci]] = contractVals[ci];
560
- acc += a[_flatIndex(aIdx, aStrides)] * b[_flatIndex(bIdx, bStrides)];
561
- });
562
- result[outFlat - outStart] = acc;
563
- }
564
- return result;
444
+ const aStrides = _rowMajorStrides(aShape);
445
+ const bStrides = _rowMajorStrides(bShape);
446
+ const outStrides = _rowMajorStrides(resultShape);
447
+ const aRank = aShape.length;
448
+ const bRank = bShape.length;
449
+ const contractedAxesA = Array.from(axesA);
450
+ const contractedAxesB = Array.from(axesB);
451
+ const contractedSetA = new Set(contractedAxesA);
452
+ const contractedSetB = new Set(contractedAxesB);
453
+ const aFreeAxes = [];
454
+ const bFreeAxes = [];
455
+ for (let i = 0; i < aRank; i++) if (!contractedSetA.has(i)) aFreeAxes.push(i);
456
+ for (let i = 0; i < bRank; i++) if (!contractedSetB.has(i)) bFreeAxes.push(i);
457
+ const contractedSizes = contractedAxesA.map((ax) => aShape[ax]);
458
+ const chunkLen = outEnd - outStart;
459
+ const result = new Float64Array(chunkLen);
460
+ const outRank = resultShape.length;
461
+ const outIdx = new Array(outRank);
462
+ for (let outFlat = outStart; outFlat < outEnd; outFlat++) {
463
+ let rem = outFlat;
464
+ for (let d = 0; d < outRank; d++) {
465
+ outIdx[d] = Math.floor(rem / outStrides[d]);
466
+ rem -= outIdx[d] * outStrides[d];
467
+ }
468
+ const aFreeVals = outIdx.slice(0, aFreeAxes.length);
469
+ const bFreeVals = outIdx.slice(aFreeAxes.length);
470
+ let acc = 0;
471
+ _forEachIndex(contractedSizes, (contractVals) => {
472
+ const aIdx = new Array(aRank).fill(0);
473
+ for (let fi = 0; fi < aFreeAxes.length; fi++) aIdx[aFreeAxes[fi]] = aFreeVals[fi];
474
+ for (let ci = 0; ci < contractedAxesA.length; ci++) aIdx[contractedAxesA[ci]] = contractVals[ci];
475
+ const bIdx = new Array(bRank).fill(0);
476
+ for (let fi = 0; fi < bFreeAxes.length; fi++) bIdx[bFreeAxes[fi]] = bFreeVals[fi];
477
+ for (let ci = 0; ci < contractedAxesB.length; ci++) bIdx[contractedAxesB[ci]] = contractVals[ci];
478
+ acc += a[_flatIndex(aIdx, aStrides)] * b[_flatIndex(bIdx, bStrides)];
479
+ });
480
+ result[outFlat - outStart] = acc;
481
+ }
482
+ return result;
565
483
  }
566
484
  var ComputePool = class {
567
- workerPool;
568
- config;
569
- constructor(config = {}) {
570
- this.config = {
571
- ...DEFAULT_POOL_CONFIG,
572
- ...config,
573
- thresholdByOp: mergeThresholds(DEFAULT_POOL_CONFIG.thresholdByOp, config.thresholdByOp)
574
- };
575
- this.workerPool = new MathWorkerPool(toWorkerConfig(this.config));
576
- }
577
- /**
578
- * Initialize the worker pool
579
- */
580
- async initialize() {
581
- await this.workerPool.initialize();
582
- }
583
- /**
584
- * Check if pool is ready
585
- */
586
- isReady() {
587
- return this.workerPool.isReady();
588
- }
589
- /**
590
- * Determine if operation should be parallelized.
591
- *
592
- * When `op` is supplied the per-op threshold map is consulted first;
593
- * if the op is absent from the map the global `thresholdElements` fallback
594
- * is used. When `op` is omitted the global threshold is always used (for
595
- * backward-compatibility with existing callers that don't know the op name).
596
- */
597
- shouldParallelize(elementCount, op) {
598
- const byOp = this.config.thresholdByOp;
599
- if (op !== void 0 && byOp !== void 0 && op in byOp) {
600
- const raw = byOp[op];
601
- if (raw !== void 0) {
602
- const threshold = resolveOpThreshold(raw);
603
- return this.config.enabled && elementCount >= threshold && this.workerPool.isReady();
604
- }
605
- }
606
- return this.workerPool.shouldParallelize(elementCount);
607
- }
608
- /**
609
- * Wrap a sequentially-computed reduction result in the `ParallelResult`
610
- * envelope. Used by the reduction methods below so that ops whose threshold
611
- * says "don't parallelize at this size" (notably the memory-bound reductions
612
- * pinned to `'never'`) compute inline instead of paying a worker round-trip —
613
- * previously every reduction dispatched to the pool unconditionally, ignoring
614
- * its threshold.
615
- */
616
- seqResult(result) {
617
- return { result, duration: 0, chunks: 1, parallelized: false };
618
- }
619
- /**
620
- * Execute a method in the worker pool
621
- */
622
- async exec(method, params, options) {
623
- return this.workerPool.exec(method, params, options);
624
- }
625
- /**
626
- * Get pool statistics
627
- */
628
- stats() {
629
- return this.workerPool.stats();
630
- }
631
- /**
632
- * Parallel sum of array elements
633
- */
634
- async sum(data) {
635
- if (!this.shouldParallelize(data.length, "sum")) {
636
- return this.seqResult(pairwiseSum(data));
637
- }
638
- return toParallelResult(await this.workerPool.sum(data));
639
- }
640
- /**
641
- * Parallel product of array elements
642
- */
643
- async prod(data) {
644
- if (!this.shouldParallelize(data.length, "prod")) {
645
- let p = 1;
646
- for (let i = 0; i < data.length; i++) p *= data[i];
647
- return this.seqResult(p);
648
- }
649
- return toParallelResult(await this.workerPool.prod(data));
650
- }
651
- /**
652
- * Parallel dot product
653
- */
654
- async dot(a, b) {
655
- if (!this.shouldParallelize(a.length, "dot")) {
656
- if (a.length !== b.length) throw new Error("dot: vectors must have the same length");
657
- return this.seqResult(pairwiseDot(a, b));
658
- }
659
- return toParallelResult(await this.workerPool.dot(a, b));
660
- }
661
- /**
662
- * Pool options that apply `op`'s `thresholdByOp` entry to a call the pool decides itself.
663
- *
664
- * The worker pool's methods check only the global `thresholdElements`, so a method that
665
- * forwarded without options let its entry govern nothing: `sin: 'never'` still went to the
666
- * workers at 50,000 elements once the pool was initialized, and `matmul: 4_096` did not
667
- * parallelize below 50,000. The bitwise family had the same bug (WS-2 addendum).
668
- * `shouldParallelize` requires a ready pool, so `forceParallel` never reaches an idle one.
669
- */
670
- poolOptions(elementCount, op) {
671
- return this.shouldParallelize(elementCount, op) ? { forceParallel: true } : { forceSequential: true };
672
- }
673
- /**
674
- * Parallel element-wise operation
675
- */
676
- async elementwise(a, b, op) {
677
- const result = await this.workerPool.elementwise(a, b, op, this.poolOptions(a.length, op));
678
- return toParallelResult(result);
679
- }
680
- /**
681
- * Parallel scale operation
682
- */
683
- async scale(data, scalar) {
684
- const result = await this.workerPool.scale(
685
- data,
686
- scalar,
687
- this.poolOptions(data.length, "scale")
688
- );
689
- return toParallelResult(result);
690
- }
691
- /**
692
- * Parallel matrix multiplication
693
- *
694
- * @param a - First matrix as flat Float64Array (row-major)
695
- * @param aRows - Number of rows in A
696
- * @param aCols - Number of columns in A
697
- * @param b - Second matrix as flat Float64Array (row-major)
698
- * @param bCols - Number of columns in B
699
- */
700
- async matmul(a, aRows, aCols, b, bCols) {
701
- const result = await this.workerPool.matmul(
702
- a,
703
- aRows,
704
- aCols,
705
- b,
706
- bCols,
707
- this.poolOptions(aRows * bCols, "matmul")
708
- );
709
- return toParallelResult(result);
710
- }
711
- /**
712
- * Parallel matrix transpose
713
- */
714
- async transpose(data, rows, cols) {
715
- if (!this.shouldParallelize(data.length, "transpose")) {
716
- if (data.length !== rows * cols) {
717
- throw new Error(`transpose: data length ${data.length} !== rows*cols (${rows * cols})`);
718
- }
719
- const out = new Float64Array(rows * cols);
720
- for (let i = 0; i < rows; i++) {
721
- for (let j = 0; j < cols; j++) out[j * rows + i] = data[i * cols + j];
722
- }
723
- return this.seqResult(out);
724
- }
725
- const result = await this.workerPool.transpose(data, rows, cols);
726
- return toParallelResult(result);
727
- }
728
- /**
729
- * Parallel map operation
730
- */
731
- async map(data, fn) {
732
- const result = await this.workerPool.map(data, fn);
733
- return toParallelResult(result);
734
- }
735
- /**
736
- * Parallel reduce operation
737
- */
738
- async reduce(data, fn, initial) {
739
- const result = await this.workerPool.reduce(data, fn, initial);
740
- return toParallelResult(result);
741
- }
742
- /**
743
- * Parallel filter operation
744
- */
745
- async filter(data, predicate) {
746
- const result = await this.workerPool.filter(data, predicate);
747
- return toParallelResult(result);
748
- }
749
- // =========================================================================
750
- // Statistical Operations
751
- // =========================================================================
752
- /**
753
- * Find min and max values in parallel
754
- */
755
- async minMax(data) {
756
- if (!this.shouldParallelize(data.length, "minMax") && !this.shouldParallelize(data.length, "min")) {
757
- let min = Infinity;
758
- let max = -Infinity;
759
- let minIdx = -1;
760
- let maxIdx = -1;
761
- for (let i = 0; i < data.length; i++) {
762
- if (data[i] < min) {
763
- min = data[i];
764
- minIdx = i;
765
- }
766
- if (data[i] > max) {
767
- max = data[i];
768
- maxIdx = i;
769
- }
770
- }
771
- return this.seqResult({ min, max, minIdx, maxIdx });
772
- }
773
- return toParallelResult(await this.workerPool.minMax(data));
774
- }
775
- /**
776
- * Compute variance, mean, and standard deviation in parallel
777
- */
778
- async variance(data) {
779
- if (!this.shouldParallelize(data.length, "variance")) {
780
- const n = data.length;
781
- const mean = n > 0 ? pairwiseSum(data) / n : 0;
782
- const variance = n > 0 ? sumSquaredDeviations(data) / n : 0;
783
- return this.seqResult({ mean, variance, std: Math.sqrt(variance) });
784
- }
785
- return toParallelResult(await this.workerPool.variance(data));
786
- }
787
- /**
788
- * Compute norm (Euclidean length) in parallel
789
- */
790
- async norm(data) {
791
- if (!this.shouldParallelize(data.length, "norm")) {
792
- return this.seqResult(norm2(data));
793
- }
794
- return toParallelResult(await this.workerPool.norm(data));
795
- }
796
- /**
797
- * Compute Euclidean distance in parallel
798
- */
799
- async distance(a, b) {
800
- if (!this.shouldParallelize(a.length, "distance")) {
801
- if (a.length !== b.length) throw new Error("distance: vectors must have the same length");
802
- return this.seqResult(scaledDistance(a, b));
803
- }
804
- return toParallelResult(await this.workerPool.distance(a, b));
805
- }
806
- /**
807
- * Compute histogram in parallel
808
- */
809
- async histogram(data, bins, min, max) {
810
- if (!this.shouldParallelize(data.length, "histogram")) {
811
- let lo = min;
812
- let hi = max;
813
- if (lo === void 0 || hi === void 0) {
814
- lo = Infinity;
815
- hi = -Infinity;
816
- for (let i = 0; i < data.length; i++) {
817
- if (data[i] < lo) lo = data[i];
818
- if (data[i] > hi) hi = data[i];
819
- }
820
- }
821
- const counts = new Array(bins).fill(0);
822
- const width = (hi - lo) / bins || 1;
823
- for (let i = 0; i < data.length; i++) {
824
- let b = Math.floor((data[i] - lo) / width);
825
- if (b >= bins) b = bins - 1;
826
- if (b < 0) b = 0;
827
- counts[b]++;
828
- }
829
- return this.seqResult(counts);
830
- }
831
- const result = await this.workerPool.histogram(data, bins, min, max);
832
- return toParallelResult(result);
833
- }
834
- // =========================================================================
835
- // Unary Operations
836
- // =========================================================================
837
- /**
838
- * Apply unary function in parallel
839
- */
840
- async unary(data, fn) {
841
- const result = await this.workerPool.unary(data, fn, this.poolOptions(data.length, fn));
842
- return toParallelResult(result);
843
- }
844
- /**
845
- * Apply a caller-supplied unary numeric function in parallel.
846
- *
847
- * @param data - Input values
848
- * @param fnSource - Source of a self-contained `(x: number) => number`
849
- * expression. It must not reference free variables / closures, since it
850
- * is eval'd in an isolated worker context. Used to parallelize element-wise
851
- * math (special functions, distribution PDFs/CDFs).
852
- */
853
- async applyKernel(data, fnSource) {
854
- const result = await this.workerPool.applyKernel(data, fnSource);
855
- return toParallelResult(result);
856
- }
857
- /**
858
- * Apply a caller-supplied binary numeric function in parallel.
859
- *
860
- * @param a - First operand array
861
- * @param b - Second operand array (must match the length of `a`)
862
- * @param fnSource - Source of a self-contained `(a: number, b: number) =>
863
- * number` expression (no free variables / closures).
864
- */
865
- async applyKernel2(a, b, fnSource) {
866
- const result = await this.workerPool.applyKernel2(a, b, fnSource);
867
- return toParallelResult(result);
868
- }
869
- /**
870
- * Fan-out Gauss-Legendre quadrature over `workerCount` sub-intervals.
871
- *
872
- * Partitions `[a, b]` into `workerCount` equal sub-domains and dispatches one
873
- * `integrateChunk` worker task per sub-domain. Each task evaluates the
874
- * integrand (supplied as a stringified closure) with the given GL nodes and
875
- * weights and returns its partial integral. The partial sums are aggregated
876
- * on the main thread.
877
- *
878
- * **Closure contract:** `fnSource` must be a self-contained `(x: number) =>
879
- * number` expression — no free variables referencing outer scope, because it
880
- * is eval'd inside an isolated worker context. Call-site validation (via
881
- * `validateClosureSource`) should have already rejected unsafe closures before
882
- * reaching this method.
883
- *
884
- * @param fnSource - Stringified integrand, e.g. `"(x) => Math.sin(x)"`
885
- * @param a - Lower integration bound
886
- * @param b - Upper integration bound
887
- * @param workerCount - Number of sub-intervals / worker tasks
888
- * @param nodes - GL nodes on [-1, 1]
889
- * @param weights - Corresponding GL weights
890
- * @returns Sum of partial integrals = approximate value of ∫ₐᵇ f(x) dx
891
- */
892
- async integrateFanOut(fnSource, a, b, workerCount, nodes, weights) {
893
- const h = (b - a) / workerCount;
894
- const tasks = [];
895
- for (let k = 0; k < workerCount; k++) {
896
- const subA = a + k * h;
897
- const subB = a + (k + 1) * h;
898
- tasks.push(
899
- this.workerPool.exec("integrateChunk", [fnSource, subA, subB, nodes, weights])
900
- );
901
- }
902
- const partials = await Promise.all(tasks);
903
- return partials.reduce((acc, v) => acc + v, 0);
904
- }
905
- /**
906
- * Fan-out distribution batch sampling across `workerCount` workers.
907
- *
908
- * Partitions `n` samples into `workerCount` equal chunks (last chunk absorbs
909
- * the remainder) and dispatches one `sampleChunk` task per chunk. Each chunk
910
- * uses an independent seed derived from `baseSeed` via SplitMix64-style
911
- * hashing:
912
- *
913
- * `chunkSeed = (baseSeed ^ (chunkIdx * 0x9E3779B97F4A7C15)) >>> 0`
914
- *
915
- * This Fibonacci-hashing multiplier gives good avalanche: even baseSeed=0
916
- * produces K distinct seeds, and adjacent chunk indices produce uncorrelated
917
- * sequences. See the `sampleChunk` worker kernel for the full rationale.
918
- *
919
- * @param distName - One of 'normal', 'gamma', 'beta', 'studentT', 'exponential'
920
- * @param params - Distribution parameters (order matches factory functions)
921
- * @param n - Total number of samples to generate
922
- * @param baseSeed - Base seed for reproducible output
923
- * @param workerCount - Number of parallel worker tasks
924
- * @returns Concatenated Float64Array of `n` samples
925
- */
926
- async distributionSampleFanOut(distName, params, n, baseSeed, workerCount) {
927
- const SPLIT_MIX_CONST = 2654435769;
928
- const baseChunkSize = Math.floor(n / workerCount);
929
- const tasks = [];
930
- for (let k = 0; k < workerCount; k++) {
931
- const chunkCount = k < workerCount - 1 ? baseChunkSize : n - baseChunkSize * (workerCount - 1);
932
- const chunkSeed = (baseSeed ^ (Math.imul(k, SPLIT_MIX_CONST) | 0)) >>> 0;
933
- tasks.push(
934
- this.workerPool.exec("sampleChunk", [distName, params, chunkSeed, chunkCount])
935
- );
936
- }
937
- const buffers = await Promise.all(tasks);
938
- const out = new Float64Array(n);
939
- let offset = 0;
940
- for (const buf of buffers) {
941
- const chunk = new Float64Array(buf);
942
- out.set(chunk, offset);
943
- offset += chunk.length;
944
- }
945
- return out;
946
- }
947
- /**
948
- * Compute a batch of independent radix-2 FFTs in parallel.
949
- *
950
- * `real` and `imag` each hold `frameCount` concatenated frames of
951
- * `frameLength` samples (`frameLength` must be a power of two). Each frame
952
- * is FFT'd independently and the frames are distributed across workers.
953
- *
954
- * Used to parallelize the embarrassingly-parallel FFT batches in
955
- * `spectrogram` (one FFT per windowed frame) and `fft2d` (rows then columns).
956
- *
957
- * @param real - Concatenated real parts (`frameCount * frameLength` values)
958
- * @param imag - Concatenated imaginary parts (same layout as `real`)
959
- * @param frameCount - Number of independent frames
960
- * @param frameLength - Samples per frame (power of two)
961
- * @param inverse - Compute the inverse FFT when true
962
- */
963
- async fftBatch(real, imag, frameCount, frameLength, inverse = false) {
964
- const result = await this.workerPool.fftBatch(real, imag, frameCount, frameLength, inverse);
965
- return toParallelResult(result);
966
- }
967
- /**
968
- * Compute an all-pairs Euclidean distance matrix in parallel.
969
- *
970
- * @param points - Flattened `n * dim` coordinate array (row-major)
971
- * @param n - Number of points
972
- * @param dim - Coordinate dimension
973
- * @returns Flat row-major `n * n` distance matrix
974
- */
975
- async distanceMatrix(points, n, dim) {
976
- const result = await this.workerPool.distanceMatrix(points, n, dim);
977
- return toParallelResult(result);
978
- }
979
- /**
980
- * Parallel absolute value
981
- */
982
- async abs(data) {
983
- return this.unary(data, "abs");
984
- }
985
- /**
986
- * Parallel square root
987
- */
988
- async sqrt(data) {
989
- return this.unary(data, "sqrt");
990
- }
991
- /**
992
- * Parallel exponential
993
- */
994
- async exp(data) {
995
- return this.unary(data, "exp");
996
- }
997
- /**
998
- * Parallel natural log
999
- */
1000
- async log(data) {
1001
- return this.unary(data, "log");
1002
- }
1003
- /**
1004
- * Parallel sine
1005
- */
1006
- async sin(data) {
1007
- return this.unary(data, "sin");
1008
- }
1009
- /**
1010
- * Parallel cosine
1011
- */
1012
- async cos(data) {
1013
- return this.unary(data, "cos");
1014
- }
1015
- /**
1016
- * Parallel tangent
1017
- */
1018
- async tan(data) {
1019
- return this.unary(data, "tan");
1020
- }
1021
- /**
1022
- * Parallel negation
1023
- */
1024
- async negate(data) {
1025
- return this.unary(data, "negate");
1026
- }
1027
- /**
1028
- * Parallel square
1029
- */
1030
- async square(data) {
1031
- return this.unary(data, "square");
1032
- }
1033
- // =========================================================================
1034
- // Additional Matrix Operations
1035
- // =========================================================================
1036
- /**
1037
- * Parallel matrix-vector multiplication
1038
- */
1039
- async matvec(matrix, rows, cols, vector) {
1040
- if (!this.shouldParallelize(matrix.length, "matvec")) {
1041
- if (matrix.length !== rows * cols) {
1042
- throw new Error(`matvec: matrix length ${matrix.length} !== rows*cols (${rows * cols})`);
1043
- }
1044
- if (vector.length !== cols) {
1045
- throw new Error(`matvec: vector length ${vector.length} !== cols (${cols})`);
1046
- }
1047
- const out = new Float64Array(rows);
1048
- for (let i = 0; i < rows; i++) {
1049
- let s = 0;
1050
- for (let j = 0; j < cols; j++) s += matrix[i * cols + j] * vector[j];
1051
- out[i] = s;
1052
- }
1053
- return this.seqResult(out);
1054
- }
1055
- const result = await this.workerPool.matvec(matrix, rows, cols, vector);
1056
- return toParallelResult(result);
1057
- }
1058
- /**
1059
- * Parallel outer product
1060
- */
1061
- async outer(a, b) {
1062
- if (!this.shouldParallelize(a.length * b.length, "outer")) {
1063
- const out = new Float64Array(a.length * b.length);
1064
- for (let i = 0; i < a.length; i++) {
1065
- for (let j = 0; j < b.length; j++) out[i * b.length + j] = a[i] * b[j];
1066
- }
1067
- return this.seqResult(out);
1068
- }
1069
- const result = await this.workerPool.outer(a, b);
1070
- return toParallelResult(result);
1071
- }
1072
- // =========================================================================
1073
- // Search and Sort Operations
1074
- // =========================================================================
1075
- /**
1076
- * Parallel find operation
1077
- */
1078
- async find(data, predicate) {
1079
- const result = await this.workerPool.find(data, predicate);
1080
- return toParallelResult(result);
1081
- }
1082
- /**
1083
- * Parallel sort operation
1084
- */
1085
- async sort(data, compare) {
1086
- const result = await this.workerPool.sort(data, compare);
1087
- return toParallelResult(result);
1088
- }
1089
- // =========================================================================
1090
- // Convenience Methods
1091
- // =========================================================================
1092
- /**
1093
- * Parallel addition of two arrays
1094
- */
1095
- async add(a, b) {
1096
- return this.elementwise(a, b, "add");
1097
- }
1098
- /**
1099
- * Parallel subtraction of two arrays
1100
- */
1101
- async subtract(a, b) {
1102
- return this.elementwise(a, b, "subtract");
1103
- }
1104
- /**
1105
- * Parallel element-wise multiplication
1106
- */
1107
- async multiply(a, b) {
1108
- return this.elementwise(a, b, "multiply");
1109
- }
1110
- /**
1111
- * Parallel element-wise division
1112
- */
1113
- async divide(a, b) {
1114
- return this.elementwise(a, b, "divide");
1115
- }
1116
- /**
1117
- * Parallel element-wise exponentiation.
1118
- *
1119
- * `out[i] = a[i] ** b[i]`
1120
- *
1121
- * Delegates to `applyKernel2` with the `Math.pow` kernel. The default
1122
- * per-op threshold for `pow` is `'never'` (overhead dominates); the
1123
- * sequential path is taken unless the caller overrides `thresholdByOp.pow`.
1124
- */
1125
- async pow(a, b) {
1126
- if (a.length !== b.length) {
1127
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1128
- }
1129
- if (this.shouldParallelize(a.length, "pow")) {
1130
- return this.applyKernel2(a, b, "(a, b) => Math.pow(a, b)");
1131
- }
1132
- const start = performance.now();
1133
- const result = new Float64Array(a.length);
1134
- for (let i = 0; i < a.length; i++) {
1135
- result[i] = a[i] ** b[i];
1136
- }
1137
- return {
1138
- result,
1139
- duration: performance.now() - start,
1140
- chunks: 1,
1141
- parallelized: false
1142
- };
1143
- }
1144
- /**
1145
- * Parallel element-wise sign function.
1146
- *
1147
- * `out[i] = Math.sign(data[i])` → -1 | 0 | 1, with NaN → NaN (IEEE 754).
1148
- *
1149
- * Delegates to `applyKernel` with the `Math.sign` kernel. The default
1150
- * per-op threshold for `sign` is `'never'` (overhead dominates); the
1151
- * sequential path is taken unless the caller overrides `thresholdByOp.sign`.
1152
- */
1153
- async sign(data) {
1154
- if (this.shouldParallelize(data.length, "sign")) {
1155
- return this.applyKernel(data, "(x) => Math.sign(x)");
1156
- }
1157
- const start = performance.now();
1158
- const result = new Float64Array(data.length);
1159
- for (let i = 0; i < data.length; i++) {
1160
- result[i] = Math.sign(data[i]);
1161
- }
1162
- return {
1163
- result,
1164
- duration: performance.now() - start,
1165
- chunks: 1,
1166
- parallelized: false
1167
- };
1168
- }
1169
- /**
1170
- * General tensor contraction (tensordot) over specified axis pairs.
1171
- *
1172
- * Contracts axis `axesA[i]` of `a` with axis `axesB[i]` of `b` for each
1173
- * pair index `i`. The result shape is the non-contracted axes of `a` followed
1174
- * by the non-contracted axes of `b` (NumPy convention).
1175
- *
1176
- * Parallelization threshold: contracted-axis volume (product of contracted
1177
- * dimensions) >= 8 192. When parallelized, output elements are distributed
1178
- * in contiguous slices across workers via the `tensordotChunk` kernel.
1179
- *
1180
- * @param a - Flat row-major Float64Array for tensor A
1181
- * @param aShape - Shape of tensor A
1182
- * @param b - Flat row-major Float64Array for tensor B
1183
- * @param bShape - Shape of tensor B
1184
- * @param axesA - Axes of A to contract (same length as axesB)
1185
- * @param axesB - Axes of B to contract (same length as axesA)
1186
- * @returns `{ data: Float64Array; shape: number[] }` — flat result + result shape
1187
- */
1188
- async tensordot(a, aShape, b, bShape, axesA, axesB) {
1189
- if (axesA.length !== axesB.length) {
1190
- throw new Error(
1191
- `tensordot: axesA.length (${axesA.length}) !== axesB.length (${axesB.length})`
1192
- );
1193
- }
1194
- const aRank = aShape.length;
1195
- const bRank = bShape.length;
1196
- const aContracted = /* @__PURE__ */ new Set();
1197
- const bContracted = /* @__PURE__ */ new Set();
1198
- for (let i = 0; i < axesA.length; i++) {
1199
- const ax = axesA[i];
1200
- const bx = axesB[i];
1201
- if (!Number.isInteger(ax) || ax < 0 || ax >= aRank) {
1202
- throw new Error(`tensordot: axesA[${i}]=${ax} is out of range for aShape (rank ${aRank})`);
1203
- }
1204
- if (!Number.isInteger(bx) || bx < 0 || bx >= bRank) {
1205
- throw new Error(`tensordot: axesB[${i}]=${bx} is out of range for bShape (rank ${bRank})`);
1206
- }
1207
- if (aShape[ax] !== bShape[bx]) {
1208
- throw new Error(
1209
- `tensordot: dimension mismatch on contracted axes: aShape[${ax}]=${aShape[ax]} vs bShape[${bx}]=${bShape[bx]}`
1210
- );
1211
- }
1212
- aContracted.add(ax);
1213
- bContracted.add(bx);
1214
- }
1215
- const resultShape = [];
1216
- for (let i = 0; i < aRank; i++) {
1217
- if (!aContracted.has(i)) resultShape.push(aShape[i]);
1218
- }
1219
- for (let i = 0; i < bRank; i++) {
1220
- if (!bContracted.has(i)) resultShape.push(bShape[i]);
1221
- }
1222
- const resultSize = resultShape.reduce((acc, d) => acc * d, 1);
1223
- const contractedVolume = Array.from(axesA).reduce((acc, ax) => acc * aShape[ax], 1);
1224
- const start = performance.now();
1225
- if (this.shouldParallelize(contractedVolume, "tensordot") && resultSize > 0) {
1226
- const chunkSize = this.config.chunkSize;
1227
- const chunks = [];
1228
- for (let i = 0; i < resultSize; i += chunkSize) {
1229
- chunks.push([i, Math.min(i + chunkSize, resultSize)]);
1230
- }
1231
- const aShapeArr = Array.from(aShape);
1232
- const bShapeArr = Array.from(bShape);
1233
- const axesAArr = Array.from(axesA);
1234
- const axesBArr = Array.from(axesB);
1235
- const chunkResults = await Promise.all(
1236
- chunks.map(
1237
- ([outStart, outEnd]) => this.workerPool.exec("tensordotChunk", [
1238
- a.buffer,
1239
- aShapeArr,
1240
- b.buffer,
1241
- bShapeArr,
1242
- axesAArr,
1243
- axesBArr,
1244
- resultShape,
1245
- outStart,
1246
- outEnd
1247
- ])
1248
- )
1249
- );
1250
- const data2 = new Float64Array(resultSize);
1251
- let offset = 0;
1252
- for (const buf of chunkResults) {
1253
- const chunk = new Float64Array(buf);
1254
- data2.set(chunk, offset);
1255
- offset += chunk.length;
1256
- }
1257
- return {
1258
- result: { data: data2, shape: resultShape },
1259
- duration: performance.now() - start,
1260
- chunks: chunks.length,
1261
- parallelized: true
1262
- };
1263
- }
1264
- const data = resultSize === 0 ? new Float64Array(0) : tensordotChunkKernel(a, aShape, b, bShape, axesA, axesB, resultShape, 0, resultSize);
1265
- return {
1266
- result: { data, shape: resultShape },
1267
- duration: performance.now() - start,
1268
- chunks: 1,
1269
- parallelized: false
1270
- };
1271
- }
1272
- /**
1273
- * Compute mean in parallel (uses variance internally)
1274
- */
1275
- async mean(data) {
1276
- const result = await this.variance(data);
1277
- return {
1278
- ...result,
1279
- result: result.result.mean
1280
- };
1281
- }
1282
- /**
1283
- * Compute standard deviation in parallel
1284
- */
1285
- async std(data) {
1286
- const result = await this.variance(data);
1287
- return {
1288
- ...result,
1289
- result: result.result.std
1290
- };
1291
- }
1292
- /**
1293
- * Find minimum value in parallel
1294
- */
1295
- async min(data) {
1296
- const result = await this.minMax(data);
1297
- return {
1298
- ...result,
1299
- result: result.result.min
1300
- };
1301
- }
1302
- /**
1303
- * Find maximum value in parallel
1304
- */
1305
- async max(data) {
1306
- const result = await this.minMax(data);
1307
- return {
1308
- ...result,
1309
- result: result.result.max
1310
- };
1311
- }
1312
- // =========================================================================
1313
- // Bitwise Operations (Int32Array)
1314
- // =========================================================================
1315
- //
1316
- // Above the elementwise threshold these dispatch to the `Int32Array`-keyed
1317
- // worker kernels in `@danielsimonjr/mathts-workerpool` (`bitwiseChunk`,
1318
- // `bitwiseScalarChunk`, `bitwiseNotChunk`). Below threshold they fall back
1319
- // to the pure in-process drivers in `./ops/bitwise.ts`. The fallback path
1320
- // uses those drivers verbatim so the algorithm lives in one place.
1321
- /**
1322
- * Element-wise bitwise AND on two `Int32Array`s.
1323
- */
1324
- async bitAnd(a, b) {
1325
- if (a.length !== b.length) {
1326
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1327
- }
1328
- if (this.shouldParallelize(a.length, "bitAnd")) {
1329
- return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitAnd"));
1330
- }
1331
- const start = performance.now();
1332
- const result = bitAnd(a, b);
1333
- return {
1334
- result,
1335
- duration: performance.now() - start,
1336
- chunks: 1,
1337
- parallelized: false
1338
- };
1339
- }
1340
- /**
1341
- * Element-wise bitwise OR on two `Int32Array`s.
1342
- */
1343
- async bitOr(a, b) {
1344
- if (a.length !== b.length) {
1345
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1346
- }
1347
- if (this.shouldParallelize(a.length, "bitOr")) {
1348
- return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitOr"));
1349
- }
1350
- const start = performance.now();
1351
- const result = bitOr(a, b);
1352
- return {
1353
- result,
1354
- duration: performance.now() - start,
1355
- chunks: 1,
1356
- parallelized: false
1357
- };
1358
- }
1359
- /**
1360
- * Element-wise bitwise XOR on two `Int32Array`s.
1361
- */
1362
- async bitXor(a, b) {
1363
- if (a.length !== b.length) {
1364
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1365
- }
1366
- if (this.shouldParallelize(a.length, "bitXor")) {
1367
- return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitXor"));
1368
- }
1369
- const start = performance.now();
1370
- const result = bitXor(a, b);
1371
- return {
1372
- result,
1373
- duration: performance.now() - start,
1374
- chunks: 1,
1375
- parallelized: false
1376
- };
1377
- }
1378
- /**
1379
- * Unary bitwise NOT on an `Int32Array`.
1380
- */
1381
- async bitNot(a) {
1382
- if (this.shouldParallelize(a.length, "bitNot")) {
1383
- return toParallelResult(await this.workerPool.bitwiseNot(a));
1384
- }
1385
- const start = performance.now();
1386
- const result = bitNot(a);
1387
- return {
1388
- result,
1389
- duration: performance.now() - start,
1390
- chunks: 1,
1391
- parallelized: false
1392
- };
1393
- }
1394
- /**
1395
- * Element-wise left shift. `b` may be a per-element `Int32Array` of
1396
- * shift counts or a single scalar `number` applied uniformly.
1397
- */
1398
- async leftShift(a, b) {
1399
- if (typeof b !== "number" && a.length !== b.length) {
1400
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1401
- }
1402
- if (this.shouldParallelize(a.length, "leftShift")) {
1403
- const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "leftShift") : await this.workerPool.bitwiseBinary(a, b, "leftShift");
1404
- return toParallelResult(result2);
1405
- }
1406
- const start = performance.now();
1407
- const result = leftShift(a, b);
1408
- return {
1409
- result,
1410
- duration: performance.now() - start,
1411
- chunks: 1,
1412
- parallelized: false
1413
- };
1414
- }
1415
- /**
1416
- * Element-wise arithmetic (sign-preserving) right shift.
1417
- */
1418
- async rightArithShift(a, b) {
1419
- if (typeof b !== "number" && a.length !== b.length) {
1420
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1421
- }
1422
- if (this.shouldParallelize(a.length, "rightArithShift")) {
1423
- const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightArithShift") : await this.workerPool.bitwiseBinary(a, b, "rightArithShift");
1424
- return toParallelResult(result2);
1425
- }
1426
- const start = performance.now();
1427
- const result = rightArithShift(a, b);
1428
- return {
1429
- result,
1430
- duration: performance.now() - start,
1431
- chunks: 1,
1432
- parallelized: false
1433
- };
1434
- }
1435
- /**
1436
- * Element-wise logical (zero-filling) right shift. Results are stored
1437
- * as `Int32Array`, so values ≥ 2^31 wrap into the negative range —
1438
- * matching `(x >>> n) | 0` JavaScript semantics.
1439
- */
1440
- async rightLogShift(a, b) {
1441
- if (typeof b !== "number" && a.length !== b.length) {
1442
- throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1443
- }
1444
- if (this.shouldParallelize(a.length, "rightLogShift")) {
1445
- const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightLogShift") : await this.workerPool.bitwiseBinary(a, b, "rightLogShift");
1446
- return toParallelResult(result2);
1447
- }
1448
- const start = performance.now();
1449
- const result = rightLogShift(a, b);
1450
- return {
1451
- result,
1452
- duration: performance.now() - start,
1453
- chunks: 1,
1454
- parallelized: false
1455
- };
1456
- }
1457
- /**
1458
- * Terminate the worker pool
1459
- */
1460
- async terminate(force = false) {
1461
- await this.workerPool.terminate(force);
1462
- }
1463
- /**
1464
- * Update configuration
1465
- */
1466
- updateConfig(config) {
1467
- this.config = {
1468
- ...this.config,
1469
- ...config,
1470
- thresholdByOp: mergeThresholds(this.config.thresholdByOp, config.thresholdByOp)
1471
- };
1472
- this.workerPool.updateConfig(toWorkerConfig(this.config));
1473
- }
1474
- /**
1475
- * Get current configuration
1476
- */
1477
- getConfig() {
1478
- return { ...this.config };
1479
- }
1480
- /**
1481
- * Maximum number of workers configured for this pool.
1482
- * Used by distribution sampling fan-out to determine chunk count.
1483
- */
1484
- get workerCount() {
1485
- return this.config.maxWorkers;
1486
- }
1487
- /**
1488
- * Get the underlying MathWorkerPool for advanced operations
1489
- */
1490
- getWorkerPool() {
1491
- return this.workerPool;
1492
- }
485
+ workerPool;
486
+ config;
487
+ constructor(config = {}) {
488
+ this.config = {
489
+ ...DEFAULT_POOL_CONFIG,
490
+ ...config,
491
+ thresholdByOp: mergeThresholds(DEFAULT_POOL_CONFIG.thresholdByOp, config.thresholdByOp)
492
+ };
493
+ this.workerPool = new MathWorkerPool(toWorkerConfig(this.config));
494
+ }
495
+ async initialize() {
496
+ await this.workerPool.initialize();
497
+ }
498
+ isReady() {
499
+ return this.workerPool.isReady();
500
+ }
501
+ shouldParallelize(elementCount, op) {
502
+ const byOp = this.config.thresholdByOp;
503
+ if (op !== void 0 && byOp !== void 0 && op in byOp) {
504
+ const raw = byOp[op];
505
+ if (raw !== void 0) {
506
+ const threshold = resolveOpThreshold(raw);
507
+ return this.config.enabled && elementCount >= threshold && this.workerPool.isReady();
508
+ }
509
+ }
510
+ return this.workerPool.shouldParallelize(elementCount);
511
+ }
512
+ seqResult(result) {
513
+ return {
514
+ result,
515
+ duration: 0,
516
+ chunks: 1,
517
+ parallelized: false
518
+ };
519
+ }
520
+ async exec(method, params, options) {
521
+ return this.workerPool.exec(method, params, options);
522
+ }
523
+ stats() {
524
+ return this.workerPool.stats();
525
+ }
526
+ async sum(data) {
527
+ if (!this.shouldParallelize(data.length, "sum")) return this.seqResult(pairwiseSum(data));
528
+ return toParallelResult(await this.workerPool.sum(data));
529
+ }
530
+ async prod(data) {
531
+ if (!this.shouldParallelize(data.length, "prod")) {
532
+ let p = 1;
533
+ for (let i = 0; i < data.length; i++) p *= data[i];
534
+ return this.seqResult(p);
535
+ }
536
+ return toParallelResult(await this.workerPool.prod(data));
537
+ }
538
+ async dot(a, b) {
539
+ if (!this.shouldParallelize(a.length, "dot")) {
540
+ if (a.length !== b.length) throw new Error("dot: vectors must have the same length");
541
+ return this.seqResult(pairwiseDot(a, b));
542
+ }
543
+ return toParallelResult(await this.workerPool.dot(a, b));
544
+ }
545
+ poolOptions(elementCount, op) {
546
+ return this.shouldParallelize(elementCount, op) ? { forceParallel: true } : { forceSequential: true };
547
+ }
548
+ async elementwise(a, b, op) {
549
+ return toParallelResult(await this.workerPool.elementwise(a, b, op, this.poolOptions(a.length, op)));
550
+ }
551
+ async scale(data, scalar) {
552
+ return toParallelResult(await this.workerPool.scale(data, scalar, this.poolOptions(data.length, "scale")));
553
+ }
554
+ async matmul(a, aRows, aCols, b, bCols) {
555
+ return toParallelResult(await this.workerPool.matmul(a, aRows, aCols, b, bCols, this.poolOptions(aRows * bCols, "matmul")));
556
+ }
557
+ async transpose(data, rows, cols) {
558
+ if (!this.shouldParallelize(data.length, "transpose")) {
559
+ if (data.length !== rows * cols) throw new Error(`transpose: data length ${data.length} !== rows*cols (${rows * cols})`);
560
+ const out = new Float64Array(rows * cols);
561
+ for (let i = 0; i < rows; i++) for (let j = 0; j < cols; j++) out[j * rows + i] = data[i * cols + j];
562
+ return this.seqResult(out);
563
+ }
564
+ return toParallelResult(await this.workerPool.transpose(data, rows, cols));
565
+ }
566
+ async map(data, fn) {
567
+ return toParallelResult(await this.workerPool.map(data, fn));
568
+ }
569
+ async reduce(data, fn, initial) {
570
+ return toParallelResult(await this.workerPool.reduce(data, fn, initial));
571
+ }
572
+ async filter(data, predicate) {
573
+ return toParallelResult(await this.workerPool.filter(data, predicate));
574
+ }
575
+ async minMax(data) {
576
+ if (!this.shouldParallelize(data.length, "minMax") && !this.shouldParallelize(data.length, "min")) {
577
+ let min = Infinity;
578
+ let max = -Infinity;
579
+ let minIdx = -1;
580
+ let maxIdx = -1;
581
+ for (let i = 0; i < data.length; i++) {
582
+ if (data[i] < min) {
583
+ min = data[i];
584
+ minIdx = i;
585
+ }
586
+ if (data[i] > max) {
587
+ max = data[i];
588
+ maxIdx = i;
589
+ }
590
+ }
591
+ return this.seqResult({
592
+ min,
593
+ max,
594
+ minIdx,
595
+ maxIdx
596
+ });
597
+ }
598
+ return toParallelResult(await this.workerPool.minMax(data));
599
+ }
600
+ async variance(data) {
601
+ if (!this.shouldParallelize(data.length, "variance")) {
602
+ const n = data.length;
603
+ const mean = n > 0 ? pairwiseSum(data) / n : 0;
604
+ const variance = n > 0 ? sumSquaredDeviations(data) / n : 0;
605
+ return this.seqResult({
606
+ mean,
607
+ variance,
608
+ std: Math.sqrt(variance)
609
+ });
610
+ }
611
+ return toParallelResult(await this.workerPool.variance(data));
612
+ }
613
+ async norm(data) {
614
+ if (!this.shouldParallelize(data.length, "norm")) return this.seqResult(norm2(data));
615
+ return toParallelResult(await this.workerPool.norm(data));
616
+ }
617
+ async distance(a, b) {
618
+ if (!this.shouldParallelize(a.length, "distance")) {
619
+ if (a.length !== b.length) throw new Error("distance: vectors must have the same length");
620
+ return this.seqResult(scaledDistance(a, b));
621
+ }
622
+ return toParallelResult(await this.workerPool.distance(a, b));
623
+ }
624
+ async histogram(data, bins, min, max) {
625
+ if (!this.shouldParallelize(data.length, "histogram")) {
626
+ let lo = min;
627
+ let hi = max;
628
+ if (lo === void 0 || hi === void 0) {
629
+ lo = Infinity;
630
+ hi = -Infinity;
631
+ for (let i = 0; i < data.length; i++) {
632
+ if (data[i] < lo) lo = data[i];
633
+ if (data[i] > hi) hi = data[i];
634
+ }
635
+ }
636
+ const counts = new Array(bins).fill(0);
637
+ const width = (hi - lo) / bins || 1;
638
+ for (let i = 0; i < data.length; i++) {
639
+ let b = Math.floor((data[i] - lo) / width);
640
+ if (b >= bins) b = bins - 1;
641
+ if (b < 0) b = 0;
642
+ counts[b]++;
643
+ }
644
+ return this.seqResult(counts);
645
+ }
646
+ return toParallelResult(await this.workerPool.histogram(data, bins, min, max));
647
+ }
648
+ async unary(data, fn) {
649
+ return toParallelResult(await this.workerPool.unary(data, fn, this.poolOptions(data.length, fn)));
650
+ }
651
+ async applyKernel(data, fnSource) {
652
+ return toParallelResult(await this.workerPool.applyKernel(data, fnSource));
653
+ }
654
+ async applyKernel2(a, b, fnSource) {
655
+ return toParallelResult(await this.workerPool.applyKernel2(a, b, fnSource));
656
+ }
657
+ async integrateFanOut(fnSource, a, b, workerCount, nodes, weights) {
658
+ const h = (b - a) / workerCount;
659
+ const tasks = [];
660
+ for (let k = 0; k < workerCount; k++) {
661
+ const subA = a + k * h;
662
+ const subB = a + (k + 1) * h;
663
+ tasks.push(this.workerPool.exec("integrateChunk", [
664
+ fnSource,
665
+ subA,
666
+ subB,
667
+ nodes,
668
+ weights
669
+ ]));
670
+ }
671
+ return (await Promise.all(tasks)).reduce((acc, v) => acc + v, 0);
672
+ }
673
+ async distributionSampleFanOut(distName, params, n, baseSeed, workerCount) {
674
+ const SPLIT_MIX_CONST = 2654435769;
675
+ const baseChunkSize = Math.floor(n / workerCount);
676
+ const tasks = [];
677
+ for (let k = 0; k < workerCount; k++) {
678
+ const chunkCount = k < workerCount - 1 ? baseChunkSize : n - baseChunkSize * (workerCount - 1);
679
+ const chunkSeed = (baseSeed ^ (Math.imul(k, SPLIT_MIX_CONST) | 0)) >>> 0;
680
+ tasks.push(this.workerPool.exec("sampleChunk", [
681
+ distName,
682
+ params,
683
+ chunkSeed,
684
+ chunkCount
685
+ ]));
686
+ }
687
+ const buffers = await Promise.all(tasks);
688
+ const out = new Float64Array(n);
689
+ let offset = 0;
690
+ for (const buf of buffers) {
691
+ const chunk = new Float64Array(buf);
692
+ out.set(chunk, offset);
693
+ offset += chunk.length;
694
+ }
695
+ return out;
696
+ }
697
+ async fftBatch(real, imag, frameCount, frameLength, inverse = false) {
698
+ return toParallelResult(await this.workerPool.fftBatch(real, imag, frameCount, frameLength, inverse));
699
+ }
700
+ async distanceMatrix(points, n, dim) {
701
+ return toParallelResult(await this.workerPool.distanceMatrix(points, n, dim));
702
+ }
703
+ async abs(data) {
704
+ return this.unary(data, "abs");
705
+ }
706
+ async sqrt(data) {
707
+ return this.unary(data, "sqrt");
708
+ }
709
+ async exp(data) {
710
+ return this.unary(data, "exp");
711
+ }
712
+ async log(data) {
713
+ return this.unary(data, "log");
714
+ }
715
+ async sin(data) {
716
+ return this.unary(data, "sin");
717
+ }
718
+ async cos(data) {
719
+ return this.unary(data, "cos");
720
+ }
721
+ async tan(data) {
722
+ return this.unary(data, "tan");
723
+ }
724
+ async negate(data) {
725
+ return this.unary(data, "negate");
726
+ }
727
+ async square(data) {
728
+ return this.unary(data, "square");
729
+ }
730
+ async matvec(matrix, rows, cols, vector) {
731
+ if (!this.shouldParallelize(matrix.length, "matvec")) {
732
+ if (matrix.length !== rows * cols) throw new Error(`matvec: matrix length ${matrix.length} !== rows*cols (${rows * cols})`);
733
+ if (vector.length !== cols) throw new Error(`matvec: vector length ${vector.length} !== cols (${cols})`);
734
+ const out = new Float64Array(rows);
735
+ for (let i = 0; i < rows; i++) {
736
+ let s = 0;
737
+ for (let j = 0; j < cols; j++) s += matrix[i * cols + j] * vector[j];
738
+ out[i] = s;
739
+ }
740
+ return this.seqResult(out);
741
+ }
742
+ return toParallelResult(await this.workerPool.matvec(matrix, rows, cols, vector));
743
+ }
744
+ async outer(a, b) {
745
+ if (!this.shouldParallelize(a.length * b.length, "outer")) {
746
+ const out = new Float64Array(a.length * b.length);
747
+ for (let i = 0; i < a.length; i++) for (let j = 0; j < b.length; j++) out[i * b.length + j] = a[i] * b[j];
748
+ return this.seqResult(out);
749
+ }
750
+ return toParallelResult(await this.workerPool.outer(a, b));
751
+ }
752
+ async find(data, predicate) {
753
+ return toParallelResult(await this.workerPool.find(data, predicate));
754
+ }
755
+ async sort(data, compare) {
756
+ return toParallelResult(await this.workerPool.sort(data, compare));
757
+ }
758
+ async add(a, b) {
759
+ return this.elementwise(a, b, "add");
760
+ }
761
+ async subtract(a, b) {
762
+ return this.elementwise(a, b, "subtract");
763
+ }
764
+ async multiply(a, b) {
765
+ return this.elementwise(a, b, "multiply");
766
+ }
767
+ async divide(a, b) {
768
+ return this.elementwise(a, b, "divide");
769
+ }
770
+ async pow(a, b) {
771
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
772
+ if (this.shouldParallelize(a.length, "pow")) return this.applyKernel2(a, b, "(a, b) => Math.pow(a, b)");
773
+ const start = performance.now();
774
+ const result = new Float64Array(a.length);
775
+ for (let i = 0; i < a.length; i++) result[i] = a[i] ** b[i];
776
+ return {
777
+ result,
778
+ duration: performance.now() - start,
779
+ chunks: 1,
780
+ parallelized: false
781
+ };
782
+ }
783
+ async sign(data) {
784
+ if (this.shouldParallelize(data.length, "sign")) return this.applyKernel(data, "(x) => Math.sign(x)");
785
+ const start = performance.now();
786
+ const result = new Float64Array(data.length);
787
+ for (let i = 0; i < data.length; i++) result[i] = Math.sign(data[i]);
788
+ return {
789
+ result,
790
+ duration: performance.now() - start,
791
+ chunks: 1,
792
+ parallelized: false
793
+ };
794
+ }
795
+ async tensordot(a, aShape, b, bShape, axesA, axesB) {
796
+ if (axesA.length !== axesB.length) throw new Error(`tensordot: axesA.length (${axesA.length}) !== axesB.length (${axesB.length})`);
797
+ const aRank = aShape.length;
798
+ const bRank = bShape.length;
799
+ const aContracted = /* @__PURE__ */ new Set();
800
+ const bContracted = /* @__PURE__ */ new Set();
801
+ for (let i = 0; i < axesA.length; i++) {
802
+ const ax = axesA[i];
803
+ const bx = axesB[i];
804
+ if (!Number.isInteger(ax) || ax < 0 || ax >= aRank) throw new Error(`tensordot: axesA[${i}]=${ax} is out of range for aShape (rank ${aRank})`);
805
+ if (!Number.isInteger(bx) || bx < 0 || bx >= bRank) throw new Error(`tensordot: axesB[${i}]=${bx} is out of range for bShape (rank ${bRank})`);
806
+ if (aShape[ax] !== bShape[bx]) throw new Error(`tensordot: dimension mismatch on contracted axes: aShape[${ax}]=${aShape[ax]} vs bShape[${bx}]=${bShape[bx]}`);
807
+ aContracted.add(ax);
808
+ bContracted.add(bx);
809
+ }
810
+ const resultShape = [];
811
+ for (let i = 0; i < aRank; i++) if (!aContracted.has(i)) resultShape.push(aShape[i]);
812
+ for (let i = 0; i < bRank; i++) if (!bContracted.has(i)) resultShape.push(bShape[i]);
813
+ const resultSize = resultShape.reduce((acc, d) => acc * d, 1);
814
+ const contractedVolume = Array.from(axesA).reduce((acc, ax) => acc * aShape[ax], 1);
815
+ const start = performance.now();
816
+ if (this.shouldParallelize(contractedVolume, "tensordot") && resultSize > 0) {
817
+ const chunkSize = this.config.chunkSize;
818
+ const chunks = [];
819
+ for (let i = 0; i < resultSize; i += chunkSize) chunks.push([i, Math.min(i + chunkSize, resultSize)]);
820
+ const aShapeArr = Array.from(aShape);
821
+ const bShapeArr = Array.from(bShape);
822
+ const axesAArr = Array.from(axesA);
823
+ const axesBArr = Array.from(axesB);
824
+ const chunkResults = await Promise.all(chunks.map(([outStart, outEnd]) => this.workerPool.exec("tensordotChunk", [
825
+ a.buffer,
826
+ aShapeArr,
827
+ b.buffer,
828
+ bShapeArr,
829
+ axesAArr,
830
+ axesBArr,
831
+ resultShape,
832
+ outStart,
833
+ outEnd
834
+ ])));
835
+ const data = new Float64Array(resultSize);
836
+ let offset = 0;
837
+ for (const buf of chunkResults) {
838
+ const chunk = new Float64Array(buf);
839
+ data.set(chunk, offset);
840
+ offset += chunk.length;
841
+ }
842
+ return {
843
+ result: {
844
+ data,
845
+ shape: resultShape
846
+ },
847
+ duration: performance.now() - start,
848
+ chunks: chunks.length,
849
+ parallelized: true
850
+ };
851
+ }
852
+ return {
853
+ result: {
854
+ data: resultSize === 0 ? /* @__PURE__ */ new Float64Array(0) : tensordotChunkKernel(a, aShape, b, bShape, axesA, axesB, resultShape, 0, resultSize),
855
+ shape: resultShape
856
+ },
857
+ duration: performance.now() - start,
858
+ chunks: 1,
859
+ parallelized: false
860
+ };
861
+ }
862
+ async mean(data) {
863
+ const result = await this.variance(data);
864
+ return {
865
+ ...result,
866
+ result: result.result.mean
867
+ };
868
+ }
869
+ async std(data) {
870
+ const result = await this.variance(data);
871
+ return {
872
+ ...result,
873
+ result: result.result.std
874
+ };
875
+ }
876
+ async min(data) {
877
+ const result = await this.minMax(data);
878
+ return {
879
+ ...result,
880
+ result: result.result.min
881
+ };
882
+ }
883
+ async max(data) {
884
+ const result = await this.minMax(data);
885
+ return {
886
+ ...result,
887
+ result: result.result.max
888
+ };
889
+ }
890
+ async bitAnd(a, b) {
891
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
892
+ if (this.shouldParallelize(a.length, "bitAnd")) return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitAnd"));
893
+ const start = performance.now();
894
+ return {
895
+ result: bitAnd(a, b),
896
+ duration: performance.now() - start,
897
+ chunks: 1,
898
+ parallelized: false
899
+ };
900
+ }
901
+ async bitOr(a, b) {
902
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
903
+ if (this.shouldParallelize(a.length, "bitOr")) return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitOr"));
904
+ const start = performance.now();
905
+ return {
906
+ result: bitOr(a, b),
907
+ duration: performance.now() - start,
908
+ chunks: 1,
909
+ parallelized: false
910
+ };
911
+ }
912
+ async bitXor(a, b) {
913
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
914
+ if (this.shouldParallelize(a.length, "bitXor")) return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitXor"));
915
+ const start = performance.now();
916
+ return {
917
+ result: bitXor(a, b),
918
+ duration: performance.now() - start,
919
+ chunks: 1,
920
+ parallelized: false
921
+ };
922
+ }
923
+ async bitNot(a) {
924
+ if (this.shouldParallelize(a.length, "bitNot")) return toParallelResult(await this.workerPool.bitwiseNot(a));
925
+ const start = performance.now();
926
+ return {
927
+ result: bitNot(a),
928
+ duration: performance.now() - start,
929
+ chunks: 1,
930
+ parallelized: false
931
+ };
932
+ }
933
+ async leftShift(a, b) {
934
+ if (typeof b !== "number" && a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
935
+ if (this.shouldParallelize(a.length, "leftShift")) return toParallelResult(typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "leftShift") : await this.workerPool.bitwiseBinary(a, b, "leftShift"));
936
+ const start = performance.now();
937
+ return {
938
+ result: leftShift(a, b),
939
+ duration: performance.now() - start,
940
+ chunks: 1,
941
+ parallelized: false
942
+ };
943
+ }
944
+ async rightArithShift(a, b) {
945
+ if (typeof b !== "number" && a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
946
+ if (this.shouldParallelize(a.length, "rightArithShift")) return toParallelResult(typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightArithShift") : await this.workerPool.bitwiseBinary(a, b, "rightArithShift"));
947
+ const start = performance.now();
948
+ return {
949
+ result: rightArithShift(a, b),
950
+ duration: performance.now() - start,
951
+ chunks: 1,
952
+ parallelized: false
953
+ };
954
+ }
955
+ async rightLogShift(a, b) {
956
+ if (typeof b !== "number" && a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
957
+ if (this.shouldParallelize(a.length, "rightLogShift")) return toParallelResult(typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightLogShift") : await this.workerPool.bitwiseBinary(a, b, "rightLogShift"));
958
+ const start = performance.now();
959
+ return {
960
+ result: rightLogShift(a, b),
961
+ duration: performance.now() - start,
962
+ chunks: 1,
963
+ parallelized: false
964
+ };
965
+ }
966
+ async terminate(force = false) {
967
+ await this.workerPool.terminate(force);
968
+ }
969
+ updateConfig(config) {
970
+ this.config = {
971
+ ...this.config,
972
+ ...config,
973
+ thresholdByOp: mergeThresholds(this.config.thresholdByOp, config.thresholdByOp)
974
+ };
975
+ this.workerPool.updateConfig(toWorkerConfig(this.config));
976
+ }
977
+ getConfig() {
978
+ return { ...this.config };
979
+ }
980
+ get workerCount() {
981
+ return this.config.maxWorkers;
982
+ }
983
+ getWorkerPool() {
984
+ return this.workerPool;
985
+ }
1493
986
  };
1494
- var computePool = new ComputePool();
1495
-
1496
- // src/operations/matmul.ts
987
+ const computePool = new ComputePool();
988
+ //#endregion
989
+ //#region src/operations/matmul.ts
1497
990
  async function parallelMatmul(a, aRows, aCols, b, bCols, options = {}) {
1498
- const pool = options.pool ?? computePool;
1499
- return pool.matmul(a, aRows, aCols, b, bCols);
991
+ return (options.pool ?? computePool).matmul(a, aRows, aCols, b, bCols);
1500
992
  }
1501
993
  async function parallelMatvec(matrix, rows, cols, vector, options = {}) {
1502
- const pool = options.pool ?? computePool;
1503
- return pool.matvec(matrix, rows, cols, vector);
994
+ return (options.pool ?? computePool).matvec(matrix, rows, cols, vector);
1504
995
  }
1505
996
  async function parallelTranspose(data, rows, cols, options = {}) {
1506
- const pool = options.pool ?? computePool;
1507
- return pool.transpose(data, rows, cols);
997
+ return (options.pool ?? computePool).transpose(data, rows, cols);
1508
998
  }
1509
999
  async function parallelOuter(a, b, options = {}) {
1510
- const pool = options.pool ?? computePool;
1511
- return pool.outer(a, b);
1000
+ return (options.pool ?? computePool).outer(a, b);
1512
1001
  }
1513
1002
  async function parallelDot(a, b, options = {}) {
1514
- const pool = options.pool ?? computePool;
1515
- return pool.dot(a, b);
1003
+ return (options.pool ?? computePool).dot(a, b);
1516
1004
  }
1517
-
1518
- // src/operations/elementwise.ts
1005
+ //#endregion
1006
+ //#region src/operations/elementwise.ts
1519
1007
  async function parallelAdd(a, b, options = {}) {
1520
- const pool = options.pool ?? computePool;
1521
- return pool.add(a, b);
1008
+ return (options.pool ?? computePool).add(a, b);
1522
1009
  }
1523
1010
  async function parallelSubtract(a, b, options = {}) {
1524
- const pool = options.pool ?? computePool;
1525
- return pool.subtract(a, b);
1011
+ return (options.pool ?? computePool).subtract(a, b);
1526
1012
  }
1527
1013
  async function parallelMultiply(a, b, options = {}) {
1528
- const pool = options.pool ?? computePool;
1529
- return pool.multiply(a, b);
1014
+ return (options.pool ?? computePool).multiply(a, b);
1530
1015
  }
1531
1016
  async function parallelDivide(a, b, options = {}) {
1532
- const pool = options.pool ?? computePool;
1533
- return pool.divide(a, b);
1017
+ return (options.pool ?? computePool).divide(a, b);
1534
1018
  }
1535
1019
  async function parallelScale(data, scalar, options = {}) {
1536
- const pool = options.pool ?? computePool;
1537
- return pool.scale(data, scalar);
1020
+ return (options.pool ?? computePool).scale(data, scalar);
1538
1021
  }
1539
1022
  async function parallelAbs(data, options = {}) {
1540
- const pool = options.pool ?? computePool;
1541
- return pool.abs(data);
1023
+ return (options.pool ?? computePool).abs(data);
1542
1024
  }
1543
1025
  async function parallelNegate(data, options = {}) {
1544
- const pool = options.pool ?? computePool;
1545
- return pool.negate(data);
1026
+ return (options.pool ?? computePool).negate(data);
1546
1027
  }
1547
1028
  async function parallelSquare(data, options = {}) {
1548
- const pool = options.pool ?? computePool;
1549
- return pool.square(data);
1029
+ return (options.pool ?? computePool).square(data);
1550
1030
  }
1551
1031
  async function parallelSqrt(data, options = {}) {
1552
- const pool = options.pool ?? computePool;
1553
- return pool.sqrt(data);
1032
+ return (options.pool ?? computePool).sqrt(data);
1554
1033
  }
1555
1034
  async function parallelExp(data, options = {}) {
1556
- const pool = options.pool ?? computePool;
1557
- return pool.exp(data);
1035
+ return (options.pool ?? computePool).exp(data);
1558
1036
  }
1559
1037
  async function parallelLog(data, options = {}) {
1560
- const pool = options.pool ?? computePool;
1561
- return pool.log(data);
1038
+ return (options.pool ?? computePool).log(data);
1562
1039
  }
1563
1040
  async function parallelSin(data, options = {}) {
1564
- const pool = options.pool ?? computePool;
1565
- return pool.sin(data);
1041
+ return (options.pool ?? computePool).sin(data);
1566
1042
  }
1567
1043
  async function parallelCos(data, options = {}) {
1568
- const pool = options.pool ?? computePool;
1569
- return pool.cos(data);
1044
+ return (options.pool ?? computePool).cos(data);
1570
1045
  }
1571
1046
  async function parallelTan(data, options = {}) {
1572
- const pool = options.pool ?? computePool;
1573
- return pool.tan(data);
1047
+ return (options.pool ?? computePool).tan(data);
1574
1048
  }
1575
1049
  async function parallelElementwise(a, b, op, options = {}) {
1576
- const pool = options.pool ?? computePool;
1577
- return pool.elementwise(a, b, op);
1050
+ return (options.pool ?? computePool).elementwise(a, b, op);
1578
1051
  }
1579
1052
  async function parallelUnary(data, fn, options = {}) {
1580
- const pool = options.pool ?? computePool;
1581
- return pool.unary(data, fn);
1053
+ return (options.pool ?? computePool).unary(data, fn);
1582
1054
  }
1583
-
1584
- // src/operations/reduce.ts
1055
+ //#endregion
1056
+ //#region src/operations/reduce.ts
1585
1057
  async function parallelSum(data, options = {}) {
1586
- const pool = options.pool ?? computePool;
1587
- return pool.sum(data);
1058
+ return (options.pool ?? computePool).sum(data);
1588
1059
  }
1589
1060
  async function parallelMean(data, options = {}) {
1590
- const pool = options.pool ?? computePool;
1591
- return pool.mean(data);
1061
+ return (options.pool ?? computePool).mean(data);
1592
1062
  }
1593
1063
  async function parallelMin(data, options = {}) {
1594
- const pool = options.pool ?? computePool;
1595
- return pool.min(data);
1064
+ return (options.pool ?? computePool).min(data);
1596
1065
  }
1597
1066
  async function parallelMax(data, options = {}) {
1598
- const pool = options.pool ?? computePool;
1599
- return pool.max(data);
1067
+ return (options.pool ?? computePool).max(data);
1600
1068
  }
1601
1069
  async function parallelMinMax(data, options = {}) {
1602
- const pool = options.pool ?? computePool;
1603
- return pool.minMax(data);
1070
+ return (options.pool ?? computePool).minMax(data);
1604
1071
  }
1605
1072
  async function parallelVariance(data, options = {}) {
1606
- const pool = options.pool ?? computePool;
1607
- return pool.variance(data);
1073
+ return (options.pool ?? computePool).variance(data);
1608
1074
  }
1609
1075
  async function parallelStd(data, options = {}) {
1610
- const pool = options.pool ?? computePool;
1611
- return pool.std(data);
1076
+ return (options.pool ?? computePool).std(data);
1612
1077
  }
1613
1078
  async function parallelNorm(data, options = {}) {
1614
- const pool = options.pool ?? computePool;
1615
- return pool.norm(data);
1079
+ return (options.pool ?? computePool).norm(data);
1616
1080
  }
1617
1081
  async function parallelDistance(a, b, options = {}) {
1618
- const pool = options.pool ?? computePool;
1619
- return pool.distance(a, b);
1082
+ return (options.pool ?? computePool).distance(a, b);
1620
1083
  }
1621
1084
  async function parallelHistogram(data, bins, min, max, options = {}) {
1622
- const pool = options.pool ?? computePool;
1623
- return pool.histogram(data, bins, min, max);
1085
+ return (options.pool ?? computePool).histogram(data, bins, min, max);
1624
1086
  }
1625
1087
  async function parallelReduce(data, fn, initial, options = {}) {
1626
- const pool = options.pool ?? computePool;
1627
- return pool.reduce(data, fn, initial);
1088
+ return (options.pool ?? computePool).reduce(data, fn, initial);
1628
1089
  }
1629
-
1630
- // src/operations/map.ts
1090
+ //#endregion
1091
+ //#region src/operations/map.ts
1631
1092
  async function parallelMap(data, fn, options = {}) {
1632
- const pool = options.pool ?? computePool;
1633
- return pool.map(data, fn);
1093
+ return (options.pool ?? computePool).map(data, fn);
1634
1094
  }
1635
1095
  async function parallelFilter(data, predicate, options = {}) {
1636
- const pool = options.pool ?? computePool;
1637
- return pool.filter(data, predicate);
1096
+ return (options.pool ?? computePool).filter(data, predicate);
1638
1097
  }
1639
1098
  async function parallelFind(data, predicate, options = {}) {
1640
- const pool = options.pool ?? computePool;
1641
- return pool.find(data, predicate);
1099
+ return (options.pool ?? computePool).find(data, predicate);
1642
1100
  }
1643
1101
  async function parallelSort(data, compare, options = {}) {
1644
- const pool = options.pool ?? computePool;
1645
- return pool.sort(data, compare);
1102
+ return (options.pool ?? computePool).sort(data, compare);
1646
1103
  }
1647
1104
  async function parallelForEach(data, fn, options = {}) {
1648
- const pool = options.pool ?? computePool;
1649
- const result = await pool.map(data, (item) => {
1650
- fn(item);
1651
- return void 0;
1652
- });
1653
- return {
1654
- ...result,
1655
- result: result.result
1656
- };
1105
+ const result = await (options.pool ?? computePool).map(data, (item) => {
1106
+ fn(item);
1107
+ });
1108
+ return {
1109
+ ...result,
1110
+ result: result.result
1111
+ };
1657
1112
  }
1658
1113
  async function parallelSome(data, predicate, options = {}) {
1659
- const pool = options.pool ?? computePool;
1660
- const findResult = await pool.find(data, predicate);
1661
- return {
1662
- ...findResult,
1663
- result: findResult.result.found
1664
- };
1114
+ const findResult = await (options.pool ?? computePool).find(data, predicate);
1115
+ return {
1116
+ ...findResult,
1117
+ result: findResult.result.found
1118
+ };
1665
1119
  }
1666
1120
  async function parallelEvery(data, predicate, options = {}) {
1667
- const pool = options.pool ?? computePool;
1668
- const findResult = await pool.find(data, (item) => !predicate(item));
1669
- return {
1670
- ...findResult,
1671
- result: !findResult.result.found
1672
- };
1121
+ const findResult = await (options.pool ?? computePool).find(data, (item) => !predicate(item));
1122
+ return {
1123
+ ...findResult,
1124
+ result: !findResult.result.found
1125
+ };
1673
1126
  }
1674
1127
  async function parallelCount(data, predicate, options = {}) {
1675
- const pool = options.pool ?? computePool;
1676
- if (!predicate) {
1677
- return {
1678
- result: data.length,
1679
- duration: 0,
1680
- chunks: 1,
1681
- parallelized: false
1682
- };
1683
- }
1684
- const filterResult = await pool.filter(data, predicate);
1685
- return {
1686
- ...filterResult,
1687
- result: filterResult.result.length
1688
- };
1689
- }
1690
-
1691
- // src/strategies/threshold.ts
1128
+ const pool = options.pool ?? computePool;
1129
+ if (!predicate) return {
1130
+ result: data.length,
1131
+ duration: 0,
1132
+ chunks: 1,
1133
+ parallelized: false
1134
+ };
1135
+ const filterResult = await pool.filter(data, predicate);
1136
+ return {
1137
+ ...filterResult,
1138
+ result: filterResult.result.length
1139
+ };
1140
+ }
1141
+ //#endregion
1142
+ //#region src/strategies/threshold.ts
1692
1143
  function opThreshold(op, fallback) {
1693
- const raw = DEFAULT_THRESHOLD_BY_OP[op];
1694
- return typeof raw === "number" ? raw : fallback;
1695
- }
1696
- var DEFAULT_THRESHOLDS = {
1697
- matmul: opThreshold("matmul", 4096),
1698
- // canonical: DEFAULT_THRESHOLD_BY_OP.matmul
1699
- elementwise: 5e4,
1700
- // ~50K elements (memory-bound; ComputePool ops mostly 'never')
1701
- reduce: 1e5,
1702
- // ~100K elements
1703
- map: 1e4,
1704
- // ~10K elements
1705
- sort: 5e3,
1706
- // ~5K elements
1707
- decomposition: 2500,
1708
- // ~50x50 matrix (no single canonical op; heuristic)
1709
- general: 5e4
1710
- // Default ~50K
1144
+ const raw = DEFAULT_THRESHOLD_BY_OP[op];
1145
+ return typeof raw === "number" ? raw : fallback;
1146
+ }
1147
+ const DEFAULT_THRESHOLDS = {
1148
+ matmul: opThreshold("matmul", 4096),
1149
+ elementwise: 5e4,
1150
+ reduce: 1e5,
1151
+ map: 1e4,
1152
+ sort: 5e3,
1153
+ decomposition: 2500,
1154
+ general: 5e4
1711
1155
  };
1712
1156
  var ThresholdDispatcher = class {
1713
- thresholds;
1714
- pool;
1715
- constructor(thresholds = {}, pool) {
1716
- this.thresholds = { ...DEFAULT_THRESHOLDS, ...thresholds };
1717
- this.pool = pool ?? computePool;
1718
- }
1719
- /**
1720
- * Get threshold for a specific operation category
1721
- */
1722
- getThreshold(category) {
1723
- return this.thresholds[category];
1724
- }
1725
- /**
1726
- * Update thresholds
1727
- */
1728
- setThresholds(thresholds) {
1729
- this.thresholds = { ...this.thresholds, ...thresholds };
1730
- }
1731
- /**
1732
- * Get current threshold configuration
1733
- */
1734
- getThresholds() {
1735
- return { ...this.thresholds };
1736
- }
1737
- /**
1738
- * Determine execution mode based on operation and data size
1739
- */
1740
- dispatch(elementCount, category = "general") {
1741
- const threshold = this.thresholds[category];
1742
- const poolReady = this.pool.isReady();
1743
- if (!poolReady) {
1744
- return {
1745
- mode: "sequential",
1746
- reason: "Worker pool not initialized",
1747
- threshold,
1748
- elementCount
1749
- };
1750
- }
1751
- const config = this.pool.getConfig();
1752
- if (!config.enabled) {
1753
- return {
1754
- mode: "sequential",
1755
- reason: "Parallel processing disabled",
1756
- threshold,
1757
- elementCount
1758
- };
1759
- }
1760
- if (elementCount < threshold) {
1761
- return {
1762
- mode: "sequential",
1763
- reason: `Element count (${elementCount}) below threshold (${threshold})`,
1764
- threshold,
1765
- elementCount
1766
- };
1767
- }
1768
- return {
1769
- mode: "parallel",
1770
- reason: `Element count (${elementCount}) exceeds threshold (${threshold})`,
1771
- threshold,
1772
- elementCount
1773
- };
1774
- }
1775
- /**
1776
- * Simple boolean check for parallel execution
1777
- */
1778
- shouldParallelize(elementCount, category = "general") {
1779
- return this.dispatch(elementCount, category).mode === "parallel";
1780
- }
1781
- /**
1782
- * Calculate optimal chunk count based on operation and data size
1783
- */
1784
- calculateChunks(elementCount, category = "general") {
1785
- const dispatch2 = this.dispatch(elementCount, category);
1786
- if (dispatch2.mode === "sequential") {
1787
- return 1;
1788
- }
1789
- const stats = this.pool.stats();
1790
- const workerCount = Math.max(1, stats.totalWorkers);
1791
- switch (category) {
1792
- case "matmul":
1793
- case "decomposition":
1794
- return workerCount;
1795
- case "elementwise":
1796
- case "map":
1797
- return Math.min(workerCount * 2, Math.ceil(elementCount / 1e4));
1798
- case "reduce":
1799
- return Math.min(workerCount, Math.ceil(elementCount / 5e4));
1800
- case "sort":
1801
- return Math.min(workerCount, 8);
1802
- default:
1803
- return workerCount;
1804
- }
1805
- }
1157
+ thresholds;
1158
+ pool;
1159
+ constructor(thresholds = {}, pool) {
1160
+ this.thresholds = {
1161
+ ...DEFAULT_THRESHOLDS,
1162
+ ...thresholds
1163
+ };
1164
+ this.pool = pool ?? computePool;
1165
+ }
1166
+ getThreshold(category) {
1167
+ return this.thresholds[category];
1168
+ }
1169
+ setThresholds(thresholds) {
1170
+ this.thresholds = {
1171
+ ...this.thresholds,
1172
+ ...thresholds
1173
+ };
1174
+ }
1175
+ getThresholds() {
1176
+ return { ...this.thresholds };
1177
+ }
1178
+ dispatch(elementCount, category = "general") {
1179
+ const threshold = this.thresholds[category];
1180
+ if (!this.pool.isReady()) return {
1181
+ mode: "sequential",
1182
+ reason: "Worker pool not initialized",
1183
+ threshold,
1184
+ elementCount
1185
+ };
1186
+ if (!this.pool.getConfig().enabled) return {
1187
+ mode: "sequential",
1188
+ reason: "Parallel processing disabled",
1189
+ threshold,
1190
+ elementCount
1191
+ };
1192
+ if (elementCount < threshold) return {
1193
+ mode: "sequential",
1194
+ reason: `Element count (${elementCount}) below threshold (${threshold})`,
1195
+ threshold,
1196
+ elementCount
1197
+ };
1198
+ return {
1199
+ mode: "parallel",
1200
+ reason: `Element count (${elementCount}) exceeds threshold (${threshold})`,
1201
+ threshold,
1202
+ elementCount
1203
+ };
1204
+ }
1205
+ shouldParallelize(elementCount, category = "general") {
1206
+ return this.dispatch(elementCount, category).mode === "parallel";
1207
+ }
1208
+ calculateChunks(elementCount, category = "general") {
1209
+ if (this.dispatch(elementCount, category).mode === "sequential") return 1;
1210
+ const stats = this.pool.stats();
1211
+ const workerCount = Math.max(1, stats.totalWorkers);
1212
+ switch (category) {
1213
+ case "matmul":
1214
+ case "decomposition": return workerCount;
1215
+ case "elementwise":
1216
+ case "map": return Math.min(workerCount * 2, Math.ceil(elementCount / 1e4));
1217
+ case "reduce": return Math.min(workerCount, Math.ceil(elementCount / 5e4));
1218
+ case "sort": return Math.min(workerCount, 8);
1219
+ default: return workerCount;
1220
+ }
1221
+ }
1806
1222
  };
1807
- var thresholdDispatcher = new ThresholdDispatcher();
1808
- function shouldParallelize2(elementCount, category = "general") {
1809
- return thresholdDispatcher.shouldParallelize(elementCount, category);
1223
+ const thresholdDispatcher = new ThresholdDispatcher();
1224
+ function shouldParallelize$1(elementCount, category = "general") {
1225
+ return thresholdDispatcher.shouldParallelize(elementCount, category);
1810
1226
  }
1811
1227
  function dispatch(elementCount, category = "general") {
1812
- return thresholdDispatcher.dispatch(elementCount, category);
1228
+ return thresholdDispatcher.dispatch(elementCount, category);
1813
1229
  }
1814
1230
  function calculateChunks(elementCount, category = "general") {
1815
- return thresholdDispatcher.calculateChunks(elementCount, category);
1816
- }
1817
- export {
1818
- ComputePool,
1819
- DEFAULT_POOL_CONFIG,
1820
- DEFAULT_THRESHOLDS,
1821
- DEFAULT_THRESHOLD_BY_OP,
1822
- ThresholdDispatcher,
1823
- Transfer,
1824
- bitAnd,
1825
- bitNot,
1826
- bitOr,
1827
- bitXor,
1828
- calculateChunks,
1829
- calculateOptimalChunks,
1830
- chunkArray,
1831
- chunkFloat64Array,
1832
- computePool,
1833
- dispatch,
1834
- leftShift,
1835
- mergeArrayChunks,
1836
- mergeFloat64Chunks,
1837
- parallelAbs,
1838
- parallelAdd,
1839
- parallelCos,
1840
- parallelCount,
1841
- parallelDistance,
1842
- parallelDivide,
1843
- parallelDot,
1844
- parallelElementwise,
1845
- parallelEvery,
1846
- parallelExp,
1847
- parallelFilter,
1848
- parallelFind,
1849
- parallelForEach,
1850
- parallelHistogram,
1851
- parallelLog,
1852
- parallelMap,
1853
- parallelMatmul,
1854
- parallelMatvec,
1855
- parallelMax,
1856
- parallelMean,
1857
- parallelMin,
1858
- parallelMinMax,
1859
- parallelMultiply,
1860
- parallelNegate,
1861
- parallelNorm,
1862
- parallelOuter,
1863
- parallelReduce,
1864
- parallelScale,
1865
- parallelSin,
1866
- parallelSome,
1867
- parallelSort,
1868
- parallelSqrt,
1869
- parallelSquare,
1870
- parallelStd,
1871
- parallelSubtract,
1872
- parallelSum,
1873
- parallelTan,
1874
- parallelTranspose,
1875
- parallelUnary,
1876
- parallelVariance,
1877
- partition2D,
1878
- partitionRange,
1879
- resolveOpThreshold,
1880
- rightArithShift,
1881
- rightLogShift,
1882
- shouldParallelize as shouldChunkParallelize,
1883
- shouldParallelize2 as shouldParallelize,
1884
- thresholdDispatcher
1885
- };
1231
+ return thresholdDispatcher.calculateChunks(elementCount, category);
1232
+ }
1233
+ //#endregion
1234
+ export { ComputePool, DEFAULT_POOL_CONFIG, DEFAULT_THRESHOLDS, DEFAULT_THRESHOLD_BY_OP, ThresholdDispatcher, Transfer, bitAnd, bitNot, bitOr, bitXor, calculateChunks, calculateOptimalChunks, chunkArray, chunkFloat64Array, computePool, dispatch, leftShift, mergeArrayChunks, mergeFloat64Chunks, parallelAbs, parallelAdd, parallelCos, parallelCount, parallelDistance, parallelDivide, parallelDot, parallelElementwise, parallelEvery, parallelExp, parallelFilter, parallelFind, parallelForEach, parallelHistogram, parallelLog, parallelMap, parallelMatmul, parallelMatvec, parallelMax, parallelMean, parallelMin, parallelMinMax, parallelMultiply, parallelNegate, parallelNorm, parallelOuter, parallelReduce, parallelScale, parallelSin, parallelSome, parallelSort, parallelSqrt, parallelSquare, parallelStd, parallelSubtract, parallelSum, parallelTan, parallelTranspose, parallelUnary, parallelVariance, partition2D, partitionRange, resolveOpThreshold, rightArithShift, rightLogShift, shouldParallelize as shouldChunkParallelize, shouldParallelize$1 as shouldParallelize, thresholdDispatcher };