@danielsimonjr/mathts-parallel 0.6.10 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1885 +1,1292 @@
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, tensordotRange } 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
  };
10
+ function planChunks(totalElements, options = {}, unbalancedChunkMin = 0) {
11
+ const { balanced } = {
12
+ ...DEFAULT_CHUNK_OPTIONS,
13
+ ...options
14
+ };
15
+ if (totalElements === 0) return [];
16
+ const numChunks = calculateOptimalChunks(totalElements, options);
17
+ if (numChunks <= 1) return [{
18
+ startIndex: 0,
19
+ endIndex: totalElements,
20
+ length: totalElements,
21
+ chunkIndex: 0
22
+ }];
23
+ const chunkInfo = [];
24
+ if (balanced) {
25
+ const baseChunkSize = Math.floor(totalElements / numChunks);
26
+ const remainder = totalElements % numChunks;
27
+ let currentIndex = 0;
28
+ for (let i = 0; i < numChunks; i++) {
29
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
30
+ const endIndex = currentIndex + thisChunkSize;
31
+ chunkInfo.push({
32
+ startIndex: currentIndex,
33
+ endIndex,
34
+ length: thisChunkSize,
35
+ chunkIndex: i
36
+ });
37
+ currentIndex = endIndex;
38
+ }
39
+ } else {
40
+ const chunkSize = Math.max(unbalancedChunkMin, Math.ceil(totalElements / numChunks));
41
+ let currentIndex = 0;
42
+ let chunkIndex = 0;
43
+ while (currentIndex < totalElements) {
44
+ const endIndex = Math.min(currentIndex + chunkSize, totalElements);
45
+ const thisChunkSize = endIndex - currentIndex;
46
+ chunkInfo.push({
47
+ startIndex: currentIndex,
48
+ endIndex,
49
+ length: thisChunkSize,
50
+ chunkIndex
51
+ });
52
+ currentIndex = endIndex;
53
+ chunkIndex++;
54
+ }
55
+ }
56
+ return chunkInfo;
57
+ }
22
58
  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));
59
+ const { minChunkSize, maxChunks, targetChunkSize } = {
60
+ ...DEFAULT_CHUNK_OPTIONS,
61
+ ...options
62
+ };
63
+ if (totalElements === 0) return 0;
64
+ if (targetChunkSize > 0) return Math.min(maxChunks, Math.ceil(totalElements / targetChunkSize));
65
+ const maxPossibleChunks = Math.floor(totalElements / minChunkSize);
66
+ return Math.max(1, Math.min(maxChunks, maxPossibleChunks));
67
+ }
68
+ function assembleChunks(totalElements, options, minChunkSizeForPlan, whole, materialize) {
69
+ if (totalElements === 0) return {
70
+ chunks: [],
71
+ chunkInfo: [],
72
+ totalElements: 0,
73
+ numChunks: 0
74
+ };
75
+ const planned = calculateOptimalChunks(totalElements, options);
76
+ const chunkInfo = planChunks(totalElements, options, minChunkSizeForPlan);
77
+ if (planned <= 1) return {
78
+ chunks: [whole],
79
+ chunkInfo,
80
+ totalElements,
81
+ numChunks: 1
82
+ };
83
+ const chunks = chunkInfo.map((info) => materialize(info.startIndex, info.endIndex));
84
+ return {
85
+ chunks,
86
+ chunkInfo,
87
+ totalElements,
88
+ numChunks: chunks.length
89
+ };
33
90
  }
34
91
  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
- };
92
+ const { minChunkSize } = {
93
+ ...DEFAULT_CHUNK_OPTIONS,
94
+ ...options
95
+ };
96
+ return assembleChunks(data.length, options, minChunkSize, data, (start, end) => data.subarray(start, end));
103
97
  }
104
98
  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
- };
99
+ return assembleChunks(data.length, options, 0, data, (start, end) => data.slice(start, end));
173
100
  }
174
101
  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;
102
+ if (chunks.length === 0) return /* @__PURE__ */ new Float64Array(0);
103
+ if (chunks.length === 1) return chunks[0];
104
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
105
+ const result = new Float64Array(totalLength);
106
+ let offset = 0;
107
+ for (const chunk of chunks) {
108
+ result.set(chunk, offset);
109
+ offset += chunk.length;
110
+ }
111
+ return result;
185
112
  }
186
113
  function mergeArrayChunks(chunks) {
187
- if (chunks.length === 0) return [];
188
- if (chunks.length === 1) return chunks[0];
189
- return chunks.flat();
114
+ if (chunks.length === 0) return [];
115
+ if (chunks.length === 1) return chunks[0];
116
+ return chunks.flat();
190
117
  }
191
118
  function shouldParallelize(elementCount, threshold = 1e4) {
192
- return elementCount >= threshold;
119
+ return elementCount >= threshold;
193
120
  }
194
121
  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;
122
+ const totalElements = end - start;
123
+ const numChunks = calculateOptimalChunks(totalElements, options);
124
+ if (numChunks <= 1) return [[start, end]];
125
+ const partitions = [];
126
+ const baseChunkSize = Math.floor(totalElements / numChunks);
127
+ const remainder = totalElements % numChunks;
128
+ let currentIndex = start;
129
+ for (let i = 0; i < numChunks; i++) {
130
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
131
+ const endIndex = currentIndex + thisChunkSize;
132
+ partitions.push([currentIndex, endIndex]);
133
+ currentIndex = endIndex;
134
+ }
135
+ return partitions;
211
136
  }
212
137
  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
138
+ return partitionRange(0, rows, options).map(([rowStart, rowEnd]) => [
139
+ rowStart,
140
+ rowEnd,
141
+ 0,
142
+ cols
143
+ ]);
144
+ }
145
+ //#endregion
146
+ //#region src/ops/bitwise.ts
218
147
  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
- }
148
+ const end = start + length;
149
+ switch (op) {
150
+ case "bitAnd":
151
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] & b[i];
152
+ return;
153
+ case "bitOr":
154
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] | b[i];
155
+ return;
156
+ case "bitXor":
157
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] ^ b[i];
158
+ return;
159
+ case "leftShift":
160
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] << b[i];
161
+ return;
162
+ case "rightArithShift":
163
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >> b[i];
164
+ return;
165
+ case "rightLogShift":
166
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >>> b[i];
167
+ return;
168
+ }
240
169
  }
241
170
  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;
171
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
172
+ const out = new Int32Array(a.length);
173
+ if (a.length === 0) return out;
174
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
175
+ const base = Math.floor(a.length / numChunks);
176
+ const remainder = a.length % numChunks;
177
+ let start = 0;
178
+ for (let c = 0; c < numChunks; c++) {
179
+ const len = base + (c < remainder ? 1 : 0);
180
+ if (len === 0) continue;
181
+ applyBinaryChunk(a, b, out, start, len, start, op);
182
+ start += len;
183
+ }
184
+ return out;
258
185
  }
259
186
  function bitAnd(a, b, options) {
260
- return runBinary(a, b, "bitAnd", options);
187
+ return runBinary(a, b, "bitAnd", options);
261
188
  }
262
189
  function bitOr(a, b, options) {
263
- return runBinary(a, b, "bitOr", options);
190
+ return runBinary(a, b, "bitOr", options);
264
191
  }
265
192
  function bitXor(a, b, options) {
266
- return runBinary(a, b, "bitXor", options);
193
+ return runBinary(a, b, "bitXor", options);
267
194
  }
268
195
  function leftShift(a, b, options) {
269
- if (typeof b === "number") return runBinaryScalar(a, b, "leftShift", options);
270
- return runBinary(a, b, "leftShift", options);
196
+ if (typeof b === "number") return runBinaryScalar(a, b, "leftShift", options);
197
+ return runBinary(a, b, "leftShift", options);
271
198
  }
272
199
  function rightArithShift(a, b, options) {
273
- if (typeof b === "number") return runBinaryScalar(a, b, "rightArithShift", options);
274
- return runBinary(a, b, "rightArithShift", options);
200
+ if (typeof b === "number") return runBinaryScalar(a, b, "rightArithShift", options);
201
+ return runBinary(a, b, "rightArithShift", options);
275
202
  }
276
203
  function rightLogShift(a, b, options) {
277
- if (typeof b === "number") return runBinaryScalar(a, b, "rightLogShift", options);
278
- return runBinary(a, b, "rightLogShift", options);
204
+ if (typeof b === "number") return runBinaryScalar(a, b, "rightLogShift", options);
205
+ return runBinary(a, b, "rightLogShift", options);
279
206
  }
280
207
  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;
208
+ const out = new Int32Array(a.length);
209
+ if (a.length === 0) return out;
210
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
211
+ const base = Math.floor(a.length / numChunks);
212
+ const remainder = a.length % numChunks;
213
+ let start = 0;
214
+ for (let c = 0; c < numChunks; c++) {
215
+ const len = base + (c < remainder ? 1 : 0);
216
+ if (len === 0) continue;
217
+ const end = start + len;
218
+ for (let i = start; i < end; i++) out[i] = ~a[i];
219
+ start += len;
220
+ }
221
+ return out;
295
222
  }
296
223
  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
224
+ const out = new Int32Array(a.length);
225
+ if (a.length === 0) return out;
226
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
227
+ const base = Math.floor(a.length / numChunks);
228
+ const remainder = a.length % numChunks;
229
+ let start = 0;
230
+ for (let c = 0; c < numChunks; c++) {
231
+ const len = base + (c < remainder ? 1 : 0);
232
+ if (len === 0) continue;
233
+ const end = start + len;
234
+ switch (op) {
235
+ case "leftShift":
236
+ for (let i = start; i < end; i++) out[i] = a[i] << scalar;
237
+ break;
238
+ case "rightArithShift":
239
+ for (let i = start; i < end; i++) out[i] = a[i] >> scalar;
240
+ break;
241
+ case "rightLogShift":
242
+ for (let i = start; i < end; i++) out[i] = a[i] >>> scalar;
243
+ break;
244
+ case "bitAnd":
245
+ for (let i = start; i < end; i++) out[i] = a[i] & scalar;
246
+ break;
247
+ case "bitOr":
248
+ for (let i = start; i < end; i++) out[i] = a[i] | scalar;
249
+ break;
250
+ case "bitXor": for (let i = start; i < end; i++) out[i] = a[i] ^ scalar;
251
+ }
252
+ start += len;
253
+ }
254
+ return out;
255
+ }
256
+ //#endregion
257
+ //#region src/ComputePool.ts
335
258
  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
259
+ if (t === "never") return Number.MAX_SAFE_INTEGER;
260
+ if (t === "always") return 0;
261
+ return t;
262
+ }
263
+ const DEFAULT_THRESHOLD_BY_OP = {
264
+ add: "never",
265
+ subtract: "never",
266
+ multiply: "never",
267
+ divide: "never",
268
+ pow: "never",
269
+ scale: "never",
270
+ abs: "never",
271
+ negate: "never",
272
+ exp: "never",
273
+ log: "never",
274
+ sin: "never",
275
+ cos: "never",
276
+ tan: "never",
277
+ sign: "never",
278
+ sqrt: "never",
279
+ square: "never",
280
+ tensordot: 8192,
281
+ sum: "never",
282
+ mean: "never",
283
+ variance: "never",
284
+ parallelStatProd: "never",
285
+ normalCDF: "never",
286
+ norm: "never",
287
+ dot: "never",
288
+ min: "never",
289
+ max: "never",
290
+ minMax: "never",
291
+ std: "never",
292
+ prod: "never",
293
+ distance: "never",
294
+ histogram: "never",
295
+ transpose: "never",
296
+ matvec: "never",
297
+ outer: "never",
298
+ integrateChunk: "never",
299
+ bitAnd: "never",
300
+ bitOr: "never",
301
+ bitXor: "never",
302
+ bitNot: "never",
303
+ leftShift: "never",
304
+ rightArithShift: "never",
305
+ rightLogShift: "never",
306
+ parallelFFT: "never",
307
+ parallelConv: "never",
308
+ fft2d: "never",
309
+ distanceMatrix: "never",
310
+ erfc: 1e5,
311
+ besselJ: 1e6,
312
+ spectrogram: 65536,
313
+ matmul: 4096,
314
+ matrixPower: 9216,
315
+ characteristicPolynomial: 9216,
316
+ chiSquareTest: 4096,
317
+ kolmogorovSmirnovTest: 4096,
318
+ mannWhitneyTest: 4096,
319
+ shapiroWilkTest: 4096,
320
+ sampleChunk: 1e5
456
321
  };
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
322
+ const DEFAULT_POOL_CONFIG = {
323
+ enabled: true,
324
+ minWorkers: 1,
325
+ maxWorkers: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
326
+ thresholdElements: 5e4,
327
+ thresholdByOp: DEFAULT_THRESHOLD_BY_OP,
328
+ chunkSize: 1e4,
329
+ workerType: "auto",
330
+ workerIdleTimeout: 6e4,
331
+ taskTimeout: 3e5
468
332
  };
469
333
  function mergeThresholds(base, override) {
470
- return override === void 0 ? base : { ...base, ...override };
334
+ return override === void 0 ? base : {
335
+ ...base,
336
+ ...override
337
+ };
471
338
  }
472
339
  function toParallelResult(result) {
473
- return {
474
- result: result.result,
475
- duration: result.duration,
476
- chunks: result.chunks,
477
- parallelized: result.parallelized
478
- };
340
+ return {
341
+ result: result.result,
342
+ duration: result.duration,
343
+ chunks: result.chunks,
344
+ parallelized: result.parallelized
345
+ };
479
346
  }
480
347
  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
- };
491
- }
492
- 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;
500
- }
501
- 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;
505
- }
506
- 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
- }
522
- }
523
- 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;
348
+ return {
349
+ enabled: config.enabled,
350
+ minWorkers: config.minWorkers,
351
+ maxWorkers: config.maxWorkers,
352
+ parallelThreshold: config.thresholdElements,
353
+ chunkSize: config.chunkSize,
354
+ workerType: config.workerType,
355
+ idleTimeout: config.workerIdleTimeout,
356
+ taskTimeout: config.taskTimeout
357
+ };
565
358
  }
566
359
  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
- }
360
+ workerPool;
361
+ config;
362
+ constructor(config = {}) {
363
+ this.config = {
364
+ ...DEFAULT_POOL_CONFIG,
365
+ ...config,
366
+ thresholdByOp: mergeThresholds(DEFAULT_POOL_CONFIG.thresholdByOp, config.thresholdByOp)
367
+ };
368
+ this.workerPool = new MathWorkerPool(toWorkerConfig(this.config));
369
+ }
370
+ async initialize() {
371
+ await this.workerPool.initialize();
372
+ }
373
+ isReady() {
374
+ return this.workerPool.isReady();
375
+ }
376
+ shouldParallelize(elementCount, op) {
377
+ const byOp = this.config.thresholdByOp;
378
+ if (op !== void 0 && byOp !== void 0 && op in byOp) {
379
+ const raw = byOp[op];
380
+ if (raw !== void 0) {
381
+ const threshold = resolveOpThreshold(raw);
382
+ return this.config.enabled && elementCount >= threshold && this.workerPool.isReady();
383
+ }
384
+ }
385
+ return this.workerPool.shouldParallelize(elementCount);
386
+ }
387
+ seqResult(result) {
388
+ return {
389
+ result,
390
+ duration: 0,
391
+ chunks: 1,
392
+ parallelized: false
393
+ };
394
+ }
395
+ async gatedBinary(a, b, options) {
396
+ if (!this.shouldParallelize(a.length, options.op)) {
397
+ if (a.length !== b.length) throw new Error(`${options.op}: vectors must have the same length`);
398
+ return this.seqResult(options.sequential(a, b));
399
+ }
400
+ return toParallelResult(await options.worker(a, b));
401
+ }
402
+ async bitwiseBinary(a, b, options) {
403
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
404
+ if (this.shouldParallelize(a.length, options.op)) return toParallelResult(await this.workerPool.bitwiseBinary(a, b, options.op));
405
+ const start = performance.now();
406
+ return {
407
+ result: options.fallback(a, b),
408
+ duration: performance.now() - start,
409
+ chunks: 1,
410
+ parallelized: false
411
+ };
412
+ }
413
+ async bitwiseShift(a, b, options) {
414
+ if (typeof b !== "number" && a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
415
+ if (this.shouldParallelize(a.length, options.op)) return toParallelResult(typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, options.op) : await this.workerPool.bitwiseBinary(a, b, options.op));
416
+ const start = performance.now();
417
+ return {
418
+ result: options.fallback(a, b),
419
+ duration: performance.now() - start,
420
+ chunks: 1,
421
+ parallelized: false
422
+ };
423
+ }
424
+ async exec(method, params, options) {
425
+ return this.workerPool.exec(method, params, options);
426
+ }
427
+ stats() {
428
+ return this.workerPool.stats();
429
+ }
430
+ async sum(data) {
431
+ if (!this.shouldParallelize(data.length, "sum")) return this.seqResult(pairwiseSum(data));
432
+ return toParallelResult(await this.workerPool.sum(data));
433
+ }
434
+ async prod(data) {
435
+ if (!this.shouldParallelize(data.length, "prod")) {
436
+ let p = 1;
437
+ for (let i = 0; i < data.length; i++) p *= data[i];
438
+ return this.seqResult(p);
439
+ }
440
+ return toParallelResult(await this.workerPool.prod(data));
441
+ }
442
+ async dot(a, b) {
443
+ return this.gatedBinary(a, b, {
444
+ op: "dot",
445
+ sequential: pairwiseDot,
446
+ worker: (x, y) => this.workerPool.dot(x, y)
447
+ });
448
+ }
449
+ poolOptions(elementCount, op) {
450
+ return this.shouldParallelize(elementCount, op) ? { forceParallel: true } : { forceSequential: true };
451
+ }
452
+ async elementwise(a, b, op) {
453
+ return toParallelResult(await this.workerPool.elementwise(a, b, op, this.poolOptions(a.length, op)));
454
+ }
455
+ async scale(data, scalar) {
456
+ return toParallelResult(await this.workerPool.scale(data, scalar, this.poolOptions(data.length, "scale")));
457
+ }
458
+ async matmul(a, aRows, aCols, b, bCols) {
459
+ return toParallelResult(await this.workerPool.matmul(a, aRows, aCols, b, bCols, this.poolOptions(aRows * bCols, "matmul")));
460
+ }
461
+ async transpose(data, rows, cols) {
462
+ if (!this.shouldParallelize(data.length, "transpose")) {
463
+ if (data.length !== rows * cols) throw new Error(`transpose: data length ${data.length} !== rows*cols (${rows * cols})`);
464
+ const out = new Float64Array(rows * cols);
465
+ for (let i = 0; i < rows; i++) for (let j = 0; j < cols; j++) out[j * rows + i] = data[i * cols + j];
466
+ return this.seqResult(out);
467
+ }
468
+ return toParallelResult(await this.workerPool.transpose(data, rows, cols));
469
+ }
470
+ async map(data, fn) {
471
+ return toParallelResult(await this.workerPool.map(data, fn));
472
+ }
473
+ async reduce(data, fn, initial) {
474
+ return toParallelResult(await this.workerPool.reduce(data, fn, initial));
475
+ }
476
+ async filter(data, predicate) {
477
+ return toParallelResult(await this.workerPool.filter(data, predicate));
478
+ }
479
+ async minMax(data) {
480
+ if (!this.shouldParallelize(data.length, "minMax") && !this.shouldParallelize(data.length, "min")) {
481
+ let min = Infinity;
482
+ let max = -Infinity;
483
+ let minIdx = -1;
484
+ let maxIdx = -1;
485
+ for (let i = 0; i < data.length; i++) {
486
+ if (data[i] < min) {
487
+ min = data[i];
488
+ minIdx = i;
489
+ }
490
+ if (data[i] > max) {
491
+ max = data[i];
492
+ maxIdx = i;
493
+ }
494
+ }
495
+ return this.seqResult({
496
+ min,
497
+ max,
498
+ minIdx,
499
+ maxIdx
500
+ });
501
+ }
502
+ return toParallelResult(await this.workerPool.minMax(data));
503
+ }
504
+ async variance(data) {
505
+ if (!this.shouldParallelize(data.length, "variance")) {
506
+ const n = data.length;
507
+ const mean = n > 0 ? pairwiseSum(data) / n : 0;
508
+ const variance = n > 0 ? sumSquaredDeviations(data) / n : 0;
509
+ return this.seqResult({
510
+ mean,
511
+ variance,
512
+ std: Math.sqrt(variance)
513
+ });
514
+ }
515
+ return toParallelResult(await this.workerPool.variance(data));
516
+ }
517
+ async norm(data) {
518
+ if (!this.shouldParallelize(data.length, "norm")) return this.seqResult(norm2(data));
519
+ return toParallelResult(await this.workerPool.norm(data));
520
+ }
521
+ async distance(a, b) {
522
+ return this.gatedBinary(a, b, {
523
+ op: "distance",
524
+ sequential: scaledDistance,
525
+ worker: (x, y) => this.workerPool.distance(x, y)
526
+ });
527
+ }
528
+ async histogram(data, bins, min, max) {
529
+ if (!this.shouldParallelize(data.length, "histogram")) {
530
+ let lo = min;
531
+ let hi = max;
532
+ if (lo === void 0 || hi === void 0) {
533
+ lo = Infinity;
534
+ hi = -Infinity;
535
+ for (let i = 0; i < data.length; i++) {
536
+ if (data[i] < lo) lo = data[i];
537
+ if (data[i] > hi) hi = data[i];
538
+ }
539
+ }
540
+ const counts = new Array(bins).fill(0);
541
+ const width = (hi - lo) / bins || 1;
542
+ for (let i = 0; i < data.length; i++) {
543
+ let b = Math.floor((data[i] - lo) / width);
544
+ if (b >= bins) b = bins - 1;
545
+ if (b < 0) b = 0;
546
+ counts[b]++;
547
+ }
548
+ return this.seqResult(counts);
549
+ }
550
+ return toParallelResult(await this.workerPool.histogram(data, bins, min, max));
551
+ }
552
+ async unary(data, fn) {
553
+ return toParallelResult(await this.workerPool.unary(data, fn, this.poolOptions(data.length, fn)));
554
+ }
555
+ async applyKernel(data, fnSource) {
556
+ return toParallelResult(await this.workerPool.applyKernel(data, fnSource));
557
+ }
558
+ async applyKernel2(a, b, fnSource) {
559
+ return toParallelResult(await this.workerPool.applyKernel2(a, b, fnSource));
560
+ }
561
+ async integrateFanOut(fnSource, a, b, workerCount, nodes, weights) {
562
+ const h = (b - a) / workerCount;
563
+ const tasks = [];
564
+ for (let k = 0; k < workerCount; k++) {
565
+ const subA = a + k * h;
566
+ const subB = a + (k + 1) * h;
567
+ tasks.push(this.workerPool.exec("integrateChunk", [
568
+ fnSource,
569
+ subA,
570
+ subB,
571
+ nodes,
572
+ weights
573
+ ]));
574
+ }
575
+ return (await Promise.all(tasks)).reduce((acc, v) => acc + v, 0);
576
+ }
577
+ async distributionSampleFanOut(distName, params, n, baseSeed, workerCount) {
578
+ const SPLIT_MIX_CONST = 2654435769;
579
+ const baseChunkSize = Math.floor(n / workerCount);
580
+ const tasks = [];
581
+ for (let k = 0; k < workerCount; k++) {
582
+ const chunkCount = k < workerCount - 1 ? baseChunkSize : n - baseChunkSize * (workerCount - 1);
583
+ const chunkSeed = (baseSeed ^ (Math.imul(k, SPLIT_MIX_CONST) | 0)) >>> 0;
584
+ tasks.push(this.workerPool.exec("sampleChunk", [
585
+ distName,
586
+ params,
587
+ chunkSeed,
588
+ chunkCount
589
+ ]));
590
+ }
591
+ const buffers = await Promise.all(tasks);
592
+ const out = new Float64Array(n);
593
+ let offset = 0;
594
+ for (const buf of buffers) {
595
+ const chunk = new Float64Array(buf);
596
+ out.set(chunk, offset);
597
+ offset += chunk.length;
598
+ }
599
+ return out;
600
+ }
601
+ async fftBatch(real, imag, frameCount, frameLength, inverse = false) {
602
+ return toParallelResult(await this.workerPool.fftBatch(real, imag, frameCount, frameLength, inverse));
603
+ }
604
+ async distanceMatrix(points, n, dim) {
605
+ return toParallelResult(await this.workerPool.distanceMatrix(points, n, dim));
606
+ }
607
+ async abs(data) {
608
+ return this.unary(data, "abs");
609
+ }
610
+ async sqrt(data) {
611
+ return this.unary(data, "sqrt");
612
+ }
613
+ async exp(data) {
614
+ return this.unary(data, "exp");
615
+ }
616
+ async log(data) {
617
+ return this.unary(data, "log");
618
+ }
619
+ async sin(data) {
620
+ return this.unary(data, "sin");
621
+ }
622
+ async cos(data) {
623
+ return this.unary(data, "cos");
624
+ }
625
+ async tan(data) {
626
+ return this.unary(data, "tan");
627
+ }
628
+ async negate(data) {
629
+ return this.unary(data, "negate");
630
+ }
631
+ async square(data) {
632
+ return this.unary(data, "square");
633
+ }
634
+ async matvec(matrix, rows, cols, vector) {
635
+ if (!this.shouldParallelize(matrix.length, "matvec")) {
636
+ if (matrix.length !== rows * cols) throw new Error(`matvec: matrix length ${matrix.length} !== rows*cols (${rows * cols})`);
637
+ if (vector.length !== cols) throw new Error(`matvec: vector length ${vector.length} !== cols (${cols})`);
638
+ const out = new Float64Array(rows);
639
+ for (let i = 0; i < rows; i++) {
640
+ let s = 0;
641
+ for (let j = 0; j < cols; j++) s += matrix[i * cols + j] * vector[j];
642
+ out[i] = s;
643
+ }
644
+ return this.seqResult(out);
645
+ }
646
+ return toParallelResult(await this.workerPool.matvec(matrix, rows, cols, vector));
647
+ }
648
+ async outer(a, b) {
649
+ if (!this.shouldParallelize(a.length * b.length, "outer")) {
650
+ const out = new Float64Array(a.length * b.length);
651
+ 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];
652
+ return this.seqResult(out);
653
+ }
654
+ return toParallelResult(await this.workerPool.outer(a, b));
655
+ }
656
+ async find(data, predicate) {
657
+ return toParallelResult(await this.workerPool.find(data, predicate));
658
+ }
659
+ async sort(data, compare) {
660
+ return toParallelResult(await this.workerPool.sort(data, compare));
661
+ }
662
+ async add(a, b) {
663
+ return this.elementwise(a, b, "add");
664
+ }
665
+ async subtract(a, b) {
666
+ return this.elementwise(a, b, "subtract");
667
+ }
668
+ async multiply(a, b) {
669
+ return this.elementwise(a, b, "multiply");
670
+ }
671
+ async divide(a, b) {
672
+ return this.elementwise(a, b, "divide");
673
+ }
674
+ async pow(a, b) {
675
+ if (a.length !== b.length) throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
676
+ if (this.shouldParallelize(a.length, "pow")) return this.applyKernel2(a, b, "(a, b) => Math.pow(a, b)");
677
+ const start = performance.now();
678
+ const result = new Float64Array(a.length);
679
+ for (let i = 0; i < a.length; i++) result[i] = a[i] ** b[i];
680
+ return {
681
+ result,
682
+ duration: performance.now() - start,
683
+ chunks: 1,
684
+ parallelized: false
685
+ };
686
+ }
687
+ async sign(data) {
688
+ if (this.shouldParallelize(data.length, "sign")) return this.applyKernel(data, "(x) => Math.sign(x)");
689
+ const start = performance.now();
690
+ const result = new Float64Array(data.length);
691
+ for (let i = 0; i < data.length; i++) result[i] = Math.sign(data[i]);
692
+ return {
693
+ result,
694
+ duration: performance.now() - start,
695
+ chunks: 1,
696
+ parallelized: false
697
+ };
698
+ }
699
+ async tensordot(a, aShape, b, bShape, axesA, axesB) {
700
+ if (axesA.length !== axesB.length) throw new Error(`tensordot: axesA.length (${axesA.length}) !== axesB.length (${axesB.length})`);
701
+ const aRank = aShape.length;
702
+ const bRank = bShape.length;
703
+ const aContracted = /* @__PURE__ */ new Set();
704
+ const bContracted = /* @__PURE__ */ new Set();
705
+ for (let i = 0; i < axesA.length; i++) {
706
+ const ax = axesA[i];
707
+ const bx = axesB[i];
708
+ if (!Number.isInteger(ax) || ax < 0 || ax >= aRank) throw new Error(`tensordot: axesA[${i}]=${ax} is out of range for aShape (rank ${aRank})`);
709
+ if (!Number.isInteger(bx) || bx < 0 || bx >= bRank) throw new Error(`tensordot: axesB[${i}]=${bx} is out of range for bShape (rank ${bRank})`);
710
+ if (aShape[ax] !== bShape[bx]) throw new Error(`tensordot: dimension mismatch on contracted axes: aShape[${ax}]=${aShape[ax]} vs bShape[${bx}]=${bShape[bx]}`);
711
+ aContracted.add(ax);
712
+ bContracted.add(bx);
713
+ }
714
+ const resultShape = [];
715
+ for (let i = 0; i < aRank; i++) if (!aContracted.has(i)) resultShape.push(aShape[i]);
716
+ for (let i = 0; i < bRank; i++) if (!bContracted.has(i)) resultShape.push(bShape[i]);
717
+ const resultSize = resultShape.reduce((acc, d) => acc * d, 1);
718
+ const contractedVolume = Array.from(axesA).reduce((acc, ax) => acc * aShape[ax], 1);
719
+ const start = performance.now();
720
+ if (this.shouldParallelize(contractedVolume, "tensordot") && resultSize > 0) {
721
+ const chunkSize = this.config.chunkSize;
722
+ const chunks = [];
723
+ for (let i = 0; i < resultSize; i += chunkSize) chunks.push([i, Math.min(i + chunkSize, resultSize)]);
724
+ const aShapeArr = Array.from(aShape);
725
+ const bShapeArr = Array.from(bShape);
726
+ const axesAArr = Array.from(axesA);
727
+ const axesBArr = Array.from(axesB);
728
+ const chunkResults = await Promise.all(chunks.map(([outStart, outEnd]) => this.workerPool.exec("tensordotChunk", [
729
+ a.buffer,
730
+ aShapeArr,
731
+ b.buffer,
732
+ bShapeArr,
733
+ axesAArr,
734
+ axesBArr,
735
+ resultShape,
736
+ outStart,
737
+ outEnd
738
+ ])));
739
+ const data = new Float64Array(resultSize);
740
+ let offset = 0;
741
+ for (const buf of chunkResults) {
742
+ const chunk = new Float64Array(buf);
743
+ data.set(chunk, offset);
744
+ offset += chunk.length;
745
+ }
746
+ return {
747
+ result: {
748
+ data,
749
+ shape: resultShape
750
+ },
751
+ duration: performance.now() - start,
752
+ chunks: chunks.length,
753
+ parallelized: true
754
+ };
755
+ }
756
+ return {
757
+ result: {
758
+ data: resultSize === 0 ? /* @__PURE__ */ new Float64Array(0) : tensordotRange(a, aShape, b, bShape, axesA, axesB, resultShape, 0, resultSize),
759
+ shape: resultShape
760
+ },
761
+ duration: performance.now() - start,
762
+ chunks: 1,
763
+ parallelized: false
764
+ };
765
+ }
766
+ async mean(data) {
767
+ const result = await this.variance(data);
768
+ return {
769
+ ...result,
770
+ result: result.result.mean
771
+ };
772
+ }
773
+ async std(data) {
774
+ const result = await this.variance(data);
775
+ return {
776
+ ...result,
777
+ result: result.result.std
778
+ };
779
+ }
780
+ async min(data) {
781
+ const result = await this.minMax(data);
782
+ return {
783
+ ...result,
784
+ result: result.result.min
785
+ };
786
+ }
787
+ async max(data) {
788
+ const result = await this.minMax(data);
789
+ return {
790
+ ...result,
791
+ result: result.result.max
792
+ };
793
+ }
794
+ async bitAnd(a, b) {
795
+ return this.bitwiseBinary(a, b, {
796
+ op: "bitAnd",
797
+ fallback: bitAnd
798
+ });
799
+ }
800
+ async bitOr(a, b) {
801
+ return this.bitwiseBinary(a, b, {
802
+ op: "bitOr",
803
+ fallback: bitOr
804
+ });
805
+ }
806
+ async bitXor(a, b) {
807
+ return this.bitwiseBinary(a, b, {
808
+ op: "bitXor",
809
+ fallback: bitXor
810
+ });
811
+ }
812
+ async bitNot(a) {
813
+ if (this.shouldParallelize(a.length, "bitNot")) return toParallelResult(await this.workerPool.bitwiseNot(a));
814
+ const start = performance.now();
815
+ return {
816
+ result: bitNot(a),
817
+ duration: performance.now() - start,
818
+ chunks: 1,
819
+ parallelized: false
820
+ };
821
+ }
822
+ async leftShift(a, b) {
823
+ return this.bitwiseShift(a, b, {
824
+ op: "leftShift",
825
+ fallback: leftShift
826
+ });
827
+ }
828
+ async rightArithShift(a, b) {
829
+ return this.bitwiseShift(a, b, {
830
+ op: "rightArithShift",
831
+ fallback: rightArithShift
832
+ });
833
+ }
834
+ async rightLogShift(a, b) {
835
+ return this.bitwiseShift(a, b, {
836
+ op: "rightLogShift",
837
+ fallback: rightLogShift
838
+ });
839
+ }
840
+ async terminate(force = false) {
841
+ await this.workerPool.terminate(force);
842
+ }
843
+ updateConfig(config) {
844
+ this.config = {
845
+ ...this.config,
846
+ ...config,
847
+ thresholdByOp: mergeThresholds(this.config.thresholdByOp, config.thresholdByOp)
848
+ };
849
+ this.workerPool.updateConfig(toWorkerConfig(this.config));
850
+ }
851
+ getConfig() {
852
+ return { ...this.config };
853
+ }
854
+ get workerCount() {
855
+ return this.config.maxWorkers;
856
+ }
857
+ getWorkerPool() {
858
+ return this.workerPool;
859
+ }
860
+ };
861
+ const computePool = new ComputePool();
862
+ //#endregion
863
+ //#region src/WorkerPool.ts
864
+ var WorkerPool = class {
865
+ workers = [];
866
+ availableWorkers = [];
867
+ taskQueue = [];
868
+ activeTasks = /* @__PURE__ */ new Map();
869
+ maxWorkers;
870
+ workerScript;
871
+ isNode;
872
+ terminated = false;
873
+ constructor(workerScript, maxWorkers) {
874
+ this.workerScript = workerScript;
875
+ this.maxWorkers = maxWorkers || this.getOptimalWorkerCount();
876
+ this.isNode = typeof process !== "undefined" && process.versions?.node !== void 0;
877
+ this.initialize();
878
+ }
879
+ getOptimalWorkerCount() {
880
+ if (typeof navigator !== "undefined" && navigator.hardwareConcurrency) return Math.max(2, navigator.hardwareConcurrency - 1);
881
+ return 4;
882
+ }
883
+ async initialize() {
884
+ for (let i = 0; i < this.maxWorkers; i++) {
885
+ const worker = await this.createWorker();
886
+ if (this.terminated) {
887
+ worker.terminate();
888
+ return;
889
+ }
890
+ this.workers.push(worker);
891
+ this.availableWorkers.push(worker);
892
+ this.processQueue();
893
+ }
894
+ }
895
+ async createWorker() {
896
+ let worker;
897
+ if (this.isNode) {
898
+ const { Worker: NodeWorker } = await import("worker_threads");
899
+ const nodeWorker = new NodeWorker(this.workerScript.startsWith("file://") ? new URL(this.workerScript) : this.workerScript);
900
+ nodeWorker.on("message", (data) => {
901
+ this.handleWorkerMessage(worker, data);
902
+ });
903
+ nodeWorker.on("error", (error) => {
904
+ this.handleWorkerError(worker, { message: error.message });
905
+ });
906
+ worker = nodeWorker;
907
+ } else {
908
+ worker = new Worker(this.workerScript, { type: "module" });
909
+ worker.onmessage = (event) => {
910
+ this.handleWorkerMessage(worker, event.data);
911
+ };
912
+ worker.onerror = (error) => {
913
+ this.handleWorkerError(worker, error);
914
+ };
915
+ }
916
+ return worker;
917
+ }
918
+ handleWorkerMessage(worker, message) {
919
+ const task = this.activeTasks.get(message.id);
920
+ if (!task) return;
921
+ this.activeTasks.delete(message.id);
922
+ if (task.timeoutHandle) {
923
+ clearTimeout(task.timeoutHandle);
924
+ task.timeoutHandle = void 0;
925
+ }
926
+ if (message.type === "result") task.resolve(message.data);
927
+ else if (message.type === "error") task.reject(new Error(message.error || "Worker error"));
928
+ this.availableWorkers.push(worker);
929
+ this.processQueue();
930
+ }
931
+ handleWorkerError(worker, error) {
932
+ console.error("Worker error:", error);
933
+ for (const [id, task] of this.activeTasks) if (this.workers.includes(worker)) {
934
+ this.activeTasks.delete(id);
935
+ this.clearTaskTimer(task);
936
+ task.reject(/* @__PURE__ */ new Error(`Worker error: ${error.message}`));
937
+ }
938
+ }
939
+ processQueue() {
940
+ while (this.taskQueue.length > 0 && this.availableWorkers.length > 0) {
941
+ const task = this.taskQueue.shift();
942
+ const worker = this.availableWorkers.shift();
943
+ this.executeTask(worker, task);
944
+ }
945
+ }
946
+ executeTask(worker, task) {
947
+ this.activeTasks.set(task.id, task);
948
+ task.worker = worker;
949
+ const message = {
950
+ id: task.id,
951
+ type: "task",
952
+ data: task.data
953
+ };
954
+ if (task.transferables && task.transferables.length > 0) worker.postMessage(message, task.transferables);
955
+ else worker.postMessage(message);
956
+ }
957
+ async handleTaskTimeout(task, timeoutMs) {
958
+ const queuedAt = this.taskQueue.indexOf(task);
959
+ if (queuedAt >= 0) {
960
+ this.taskQueue.splice(queuedAt, 1);
961
+ task.reject(/* @__PURE__ */ new Error(`Worker task timed out after ${timeoutMs}ms`));
962
+ return;
963
+ }
964
+ if (!this.activeTasks.has(task.id)) return;
965
+ this.activeTasks.delete(task.id);
966
+ const dead = task.worker;
967
+ if (dead) {
968
+ try {
969
+ dead.terminate();
970
+ } catch {}
971
+ this.workers = this.workers.filter((w) => w !== dead);
972
+ this.availableWorkers = this.availableWorkers.filter((w) => w !== dead);
973
+ try {
974
+ const replacement = await this.createWorker();
975
+ if (this.terminated) {
976
+ replacement.terminate();
977
+ task.reject(/* @__PURE__ */ new Error(`Worker task timed out after ${timeoutMs}ms`));
978
+ return;
979
+ }
980
+ this.workers.push(replacement);
981
+ this.availableWorkers.push(replacement);
982
+ this.processQueue();
983
+ } catch (err) {
984
+ console.error("WorkerPool: failed to spawn replacement worker:", err);
985
+ }
986
+ }
987
+ task.reject(/* @__PURE__ */ new Error(`Worker task timed out after ${timeoutMs}ms`));
988
+ }
989
+ async execute(data, transferables, timeoutMs) {
990
+ return new Promise((resolve, reject) => {
991
+ if (this.terminated) {
992
+ reject(/* @__PURE__ */ new Error("WorkerPool terminated"));
993
+ return;
994
+ }
995
+ const task = {
996
+ id: this.generateTaskId(),
997
+ data,
998
+ resolve: (value) => resolve(value),
999
+ reject,
1000
+ transferables
1001
+ };
1002
+ if (typeof timeoutMs === "number" && timeoutMs > 0) task.timeoutHandle = setTimeout(() => {
1003
+ this.handleTaskTimeout(task, timeoutMs);
1004
+ }, timeoutMs);
1005
+ if (this.availableWorkers.length > 0) {
1006
+ const worker = this.availableWorkers.shift();
1007
+ this.executeTask(worker, task);
1008
+ } else this.taskQueue.push(task);
1009
+ });
1010
+ }
1011
+ generateTaskId() {
1012
+ return `task_${Date.now()}_${globalThis.crypto.randomUUID()}`;
1013
+ }
1014
+ async terminate() {
1015
+ this.terminated = true;
1016
+ for (const task of this.taskQueue) {
1017
+ this.clearTaskTimer(task);
1018
+ task.reject(/* @__PURE__ */ new Error("WorkerPool terminated"));
1019
+ }
1020
+ this.taskQueue = [];
1021
+ for (const task of this.activeTasks.values()) {
1022
+ this.clearTaskTimer(task);
1023
+ task.reject(/* @__PURE__ */ new Error("WorkerPool terminated"));
1024
+ }
1025
+ this.activeTasks.clear();
1026
+ for (const worker of this.workers) worker.terminate();
1027
+ this.workers = [];
1028
+ this.availableWorkers = [];
1029
+ }
1030
+ clearTaskTimer(task) {
1031
+ if (task.timeoutHandle !== void 0) {
1032
+ clearTimeout(task.timeoutHandle);
1033
+ task.timeoutHandle = void 0;
1034
+ }
1035
+ }
1036
+ get activeTaskCount() {
1037
+ return this.activeTasks.size;
1038
+ }
1039
+ get queuedTaskCount() {
1040
+ return this.taskQueue.length;
1041
+ }
1042
+ get workerCount() {
1043
+ return this.workers.length;
1044
+ }
1493
1045
  };
1494
- var computePool = new ComputePool();
1495
-
1496
- // src/operations/matmul.ts
1046
+ //#endregion
1047
+ //#region src/operations/matmul.ts
1497
1048
  async function parallelMatmul(a, aRows, aCols, b, bCols, options = {}) {
1498
- const pool = options.pool ?? computePool;
1499
- return pool.matmul(a, aRows, aCols, b, bCols);
1049
+ return (options.pool ?? computePool).matmul(a, aRows, aCols, b, bCols);
1500
1050
  }
1501
1051
  async function parallelMatvec(matrix, rows, cols, vector, options = {}) {
1502
- const pool = options.pool ?? computePool;
1503
- return pool.matvec(matrix, rows, cols, vector);
1052
+ return (options.pool ?? computePool).matvec(matrix, rows, cols, vector);
1504
1053
  }
1505
1054
  async function parallelTranspose(data, rows, cols, options = {}) {
1506
- const pool = options.pool ?? computePool;
1507
- return pool.transpose(data, rows, cols);
1055
+ return (options.pool ?? computePool).transpose(data, rows, cols);
1508
1056
  }
1509
1057
  async function parallelOuter(a, b, options = {}) {
1510
- const pool = options.pool ?? computePool;
1511
- return pool.outer(a, b);
1058
+ return (options.pool ?? computePool).outer(a, b);
1512
1059
  }
1513
1060
  async function parallelDot(a, b, options = {}) {
1514
- const pool = options.pool ?? computePool;
1515
- return pool.dot(a, b);
1061
+ return (options.pool ?? computePool).dot(a, b);
1516
1062
  }
1517
-
1518
- // src/operations/elementwise.ts
1063
+ //#endregion
1064
+ //#region src/operations/elementwise.ts
1519
1065
  async function parallelAdd(a, b, options = {}) {
1520
- const pool = options.pool ?? computePool;
1521
- return pool.add(a, b);
1066
+ return (options.pool ?? computePool).add(a, b);
1522
1067
  }
1523
1068
  async function parallelSubtract(a, b, options = {}) {
1524
- const pool = options.pool ?? computePool;
1525
- return pool.subtract(a, b);
1069
+ return (options.pool ?? computePool).subtract(a, b);
1526
1070
  }
1527
1071
  async function parallelMultiply(a, b, options = {}) {
1528
- const pool = options.pool ?? computePool;
1529
- return pool.multiply(a, b);
1072
+ return (options.pool ?? computePool).multiply(a, b);
1530
1073
  }
1531
1074
  async function parallelDivide(a, b, options = {}) {
1532
- const pool = options.pool ?? computePool;
1533
- return pool.divide(a, b);
1075
+ return (options.pool ?? computePool).divide(a, b);
1534
1076
  }
1535
1077
  async function parallelScale(data, scalar, options = {}) {
1536
- const pool = options.pool ?? computePool;
1537
- return pool.scale(data, scalar);
1078
+ return (options.pool ?? computePool).scale(data, scalar);
1538
1079
  }
1539
1080
  async function parallelAbs(data, options = {}) {
1540
- const pool = options.pool ?? computePool;
1541
- return pool.abs(data);
1081
+ return (options.pool ?? computePool).abs(data);
1542
1082
  }
1543
1083
  async function parallelNegate(data, options = {}) {
1544
- const pool = options.pool ?? computePool;
1545
- return pool.negate(data);
1084
+ return (options.pool ?? computePool).negate(data);
1546
1085
  }
1547
1086
  async function parallelSquare(data, options = {}) {
1548
- const pool = options.pool ?? computePool;
1549
- return pool.square(data);
1087
+ return (options.pool ?? computePool).square(data);
1550
1088
  }
1551
1089
  async function parallelSqrt(data, options = {}) {
1552
- const pool = options.pool ?? computePool;
1553
- return pool.sqrt(data);
1090
+ return (options.pool ?? computePool).sqrt(data);
1554
1091
  }
1555
1092
  async function parallelExp(data, options = {}) {
1556
- const pool = options.pool ?? computePool;
1557
- return pool.exp(data);
1093
+ return (options.pool ?? computePool).exp(data);
1558
1094
  }
1559
1095
  async function parallelLog(data, options = {}) {
1560
- const pool = options.pool ?? computePool;
1561
- return pool.log(data);
1096
+ return (options.pool ?? computePool).log(data);
1562
1097
  }
1563
1098
  async function parallelSin(data, options = {}) {
1564
- const pool = options.pool ?? computePool;
1565
- return pool.sin(data);
1099
+ return (options.pool ?? computePool).sin(data);
1566
1100
  }
1567
1101
  async function parallelCos(data, options = {}) {
1568
- const pool = options.pool ?? computePool;
1569
- return pool.cos(data);
1102
+ return (options.pool ?? computePool).cos(data);
1570
1103
  }
1571
1104
  async function parallelTan(data, options = {}) {
1572
- const pool = options.pool ?? computePool;
1573
- return pool.tan(data);
1105
+ return (options.pool ?? computePool).tan(data);
1574
1106
  }
1575
1107
  async function parallelElementwise(a, b, op, options = {}) {
1576
- const pool = options.pool ?? computePool;
1577
- return pool.elementwise(a, b, op);
1108
+ return (options.pool ?? computePool).elementwise(a, b, op);
1578
1109
  }
1579
1110
  async function parallelUnary(data, fn, options = {}) {
1580
- const pool = options.pool ?? computePool;
1581
- return pool.unary(data, fn);
1111
+ return (options.pool ?? computePool).unary(data, fn);
1582
1112
  }
1583
-
1584
- // src/operations/reduce.ts
1113
+ //#endregion
1114
+ //#region src/operations/reduce.ts
1585
1115
  async function parallelSum(data, options = {}) {
1586
- const pool = options.pool ?? computePool;
1587
- return pool.sum(data);
1116
+ return (options.pool ?? computePool).sum(data);
1588
1117
  }
1589
1118
  async function parallelMean(data, options = {}) {
1590
- const pool = options.pool ?? computePool;
1591
- return pool.mean(data);
1119
+ return (options.pool ?? computePool).mean(data);
1592
1120
  }
1593
1121
  async function parallelMin(data, options = {}) {
1594
- const pool = options.pool ?? computePool;
1595
- return pool.min(data);
1122
+ return (options.pool ?? computePool).min(data);
1596
1123
  }
1597
1124
  async function parallelMax(data, options = {}) {
1598
- const pool = options.pool ?? computePool;
1599
- return pool.max(data);
1125
+ return (options.pool ?? computePool).max(data);
1600
1126
  }
1601
1127
  async function parallelMinMax(data, options = {}) {
1602
- const pool = options.pool ?? computePool;
1603
- return pool.minMax(data);
1128
+ return (options.pool ?? computePool).minMax(data);
1604
1129
  }
1605
1130
  async function parallelVariance(data, options = {}) {
1606
- const pool = options.pool ?? computePool;
1607
- return pool.variance(data);
1131
+ return (options.pool ?? computePool).variance(data);
1608
1132
  }
1609
1133
  async function parallelStd(data, options = {}) {
1610
- const pool = options.pool ?? computePool;
1611
- return pool.std(data);
1134
+ return (options.pool ?? computePool).std(data);
1612
1135
  }
1613
1136
  async function parallelNorm(data, options = {}) {
1614
- const pool = options.pool ?? computePool;
1615
- return pool.norm(data);
1137
+ return (options.pool ?? computePool).norm(data);
1616
1138
  }
1617
1139
  async function parallelDistance(a, b, options = {}) {
1618
- const pool = options.pool ?? computePool;
1619
- return pool.distance(a, b);
1140
+ return (options.pool ?? computePool).distance(a, b);
1620
1141
  }
1621
1142
  async function parallelHistogram(data, bins, min, max, options = {}) {
1622
- const pool = options.pool ?? computePool;
1623
- return pool.histogram(data, bins, min, max);
1143
+ return (options.pool ?? computePool).histogram(data, bins, min, max);
1624
1144
  }
1625
1145
  async function parallelReduce(data, fn, initial, options = {}) {
1626
- const pool = options.pool ?? computePool;
1627
- return pool.reduce(data, fn, initial);
1146
+ return (options.pool ?? computePool).reduce(data, fn, initial);
1628
1147
  }
1629
-
1630
- // src/operations/map.ts
1148
+ //#endregion
1149
+ //#region src/operations/map.ts
1631
1150
  async function parallelMap(data, fn, options = {}) {
1632
- const pool = options.pool ?? computePool;
1633
- return pool.map(data, fn);
1151
+ return (options.pool ?? computePool).map(data, fn);
1634
1152
  }
1635
1153
  async function parallelFilter(data, predicate, options = {}) {
1636
- const pool = options.pool ?? computePool;
1637
- return pool.filter(data, predicate);
1154
+ return (options.pool ?? computePool).filter(data, predicate);
1638
1155
  }
1639
1156
  async function parallelFind(data, predicate, options = {}) {
1640
- const pool = options.pool ?? computePool;
1641
- return pool.find(data, predicate);
1157
+ return (options.pool ?? computePool).find(data, predicate);
1642
1158
  }
1643
1159
  async function parallelSort(data, compare, options = {}) {
1644
- const pool = options.pool ?? computePool;
1645
- return pool.sort(data, compare);
1160
+ return (options.pool ?? computePool).sort(data, compare);
1646
1161
  }
1647
1162
  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
- };
1163
+ const result = await (options.pool ?? computePool).map(data, (item) => {
1164
+ fn(item);
1165
+ });
1166
+ return {
1167
+ ...result,
1168
+ result: result.result
1169
+ };
1657
1170
  }
1658
1171
  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
- };
1172
+ const findResult = await (options.pool ?? computePool).find(data, predicate);
1173
+ return {
1174
+ ...findResult,
1175
+ result: findResult.result.found
1176
+ };
1665
1177
  }
1666
1178
  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
- };
1179
+ const findResult = await (options.pool ?? computePool).find(data, (item) => !predicate(item));
1180
+ return {
1181
+ ...findResult,
1182
+ result: !findResult.result.found
1183
+ };
1673
1184
  }
1674
1185
  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
1186
+ const pool = options.pool ?? computePool;
1187
+ if (!predicate) return {
1188
+ result: data.length,
1189
+ duration: 0,
1190
+ chunks: 1,
1191
+ parallelized: false
1192
+ };
1193
+ const filterResult = await pool.filter(data, predicate);
1194
+ return {
1195
+ ...filterResult,
1196
+ result: filterResult.result.length
1197
+ };
1198
+ }
1199
+ //#endregion
1200
+ //#region src/strategies/threshold.ts
1692
1201
  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
1202
+ const raw = DEFAULT_THRESHOLD_BY_OP[op];
1203
+ return typeof raw === "number" ? raw : fallback;
1204
+ }
1205
+ const DEFAULT_THRESHOLDS = {
1206
+ matmul: opThreshold("matmul", 4096),
1207
+ elementwise: 5e4,
1208
+ reduce: 1e5,
1209
+ map: 1e4,
1210
+ sort: 5e3,
1211
+ decomposition: 2500,
1212
+ general: 5e4
1711
1213
  };
1712
1214
  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
- }
1215
+ thresholds;
1216
+ pool;
1217
+ constructor(thresholds = {}, pool) {
1218
+ this.thresholds = {
1219
+ ...DEFAULT_THRESHOLDS,
1220
+ ...thresholds
1221
+ };
1222
+ this.pool = pool ?? computePool;
1223
+ }
1224
+ getThreshold(category) {
1225
+ return this.thresholds[category];
1226
+ }
1227
+ setThresholds(thresholds) {
1228
+ this.thresholds = {
1229
+ ...this.thresholds,
1230
+ ...thresholds
1231
+ };
1232
+ }
1233
+ getThresholds() {
1234
+ return { ...this.thresholds };
1235
+ }
1236
+ dispatch(elementCount, category = "general") {
1237
+ const threshold = this.thresholds[category];
1238
+ if (!this.pool.isReady()) return {
1239
+ mode: "sequential",
1240
+ reason: "Worker pool not initialized",
1241
+ threshold,
1242
+ elementCount
1243
+ };
1244
+ if (!this.pool.getConfig().enabled) return {
1245
+ mode: "sequential",
1246
+ reason: "Parallel processing disabled",
1247
+ threshold,
1248
+ elementCount
1249
+ };
1250
+ if (elementCount < threshold) return {
1251
+ mode: "sequential",
1252
+ reason: `Element count (${elementCount}) below threshold (${threshold})`,
1253
+ threshold,
1254
+ elementCount
1255
+ };
1256
+ return {
1257
+ mode: "parallel",
1258
+ reason: `Element count (${elementCount}) exceeds threshold (${threshold})`,
1259
+ threshold,
1260
+ elementCount
1261
+ };
1262
+ }
1263
+ shouldParallelize(elementCount, category = "general") {
1264
+ return this.dispatch(elementCount, category).mode === "parallel";
1265
+ }
1266
+ calculateChunks(elementCount, category = "general") {
1267
+ if (this.dispatch(elementCount, category).mode === "sequential") return 1;
1268
+ const stats = this.pool.stats();
1269
+ const workerCount = Math.max(1, stats.totalWorkers);
1270
+ switch (category) {
1271
+ case "matmul":
1272
+ case "decomposition": return workerCount;
1273
+ case "elementwise":
1274
+ case "map": return Math.min(workerCount * 2, Math.ceil(elementCount / 1e4));
1275
+ case "reduce": return Math.min(workerCount, Math.ceil(elementCount / 5e4));
1276
+ case "sort": return Math.min(workerCount, 8);
1277
+ default: return workerCount;
1278
+ }
1279
+ }
1806
1280
  };
1807
- var thresholdDispatcher = new ThresholdDispatcher();
1808
- function shouldParallelize2(elementCount, category = "general") {
1809
- return thresholdDispatcher.shouldParallelize(elementCount, category);
1281
+ const thresholdDispatcher = new ThresholdDispatcher();
1282
+ function shouldParallelize$1(elementCount, category = "general") {
1283
+ return thresholdDispatcher.shouldParallelize(elementCount, category);
1810
1284
  }
1811
1285
  function dispatch(elementCount, category = "general") {
1812
- return thresholdDispatcher.dispatch(elementCount, category);
1286
+ return thresholdDispatcher.dispatch(elementCount, category);
1813
1287
  }
1814
1288
  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
- };
1289
+ return thresholdDispatcher.calculateChunks(elementCount, category);
1290
+ }
1291
+ //#endregion
1292
+ export { ComputePool, DEFAULT_POOL_CONFIG, DEFAULT_THRESHOLDS, DEFAULT_THRESHOLD_BY_OP, ThresholdDispatcher, Transfer, WorkerPool, 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 };