@danielsimonjr/mathts-parallel 0.1.1 → 0.2.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
@@ -3,11 +3,390 @@ import {
3
3
  MathWorkerPool,
4
4
  Transfer
5
5
  } from "@danielsimonjr/mathts-workerpool";
6
+
7
+ // src/strategies/chunk.ts
8
+ var DEFAULT_CHUNK_OPTIONS = {
9
+ minChunkSize: 1e3,
10
+ maxChunks: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
11
+ targetChunkSize: 0,
12
+ // 0 = auto
13
+ balanced: true
14
+ };
15
+ function calculateOptimalChunks(totalElements, options = {}) {
16
+ const { minChunkSize, maxChunks, targetChunkSize } = {
17
+ ...DEFAULT_CHUNK_OPTIONS,
18
+ ...options
19
+ };
20
+ if (totalElements === 0) return 0;
21
+ if (targetChunkSize > 0) {
22
+ return Math.min(maxChunks, Math.ceil(totalElements / targetChunkSize));
23
+ }
24
+ const maxPossibleChunks = Math.floor(totalElements / minChunkSize);
25
+ return Math.max(1, Math.min(maxChunks, maxPossibleChunks));
26
+ }
27
+ function chunkFloat64Array(data, options = {}) {
28
+ const { minChunkSize, balanced } = { ...DEFAULT_CHUNK_OPTIONS, ...options };
29
+ const totalElements = data.length;
30
+ if (totalElements === 0) {
31
+ return {
32
+ chunks: [],
33
+ chunkInfo: [],
34
+ totalElements: 0,
35
+ numChunks: 0
36
+ };
37
+ }
38
+ const numChunks = calculateOptimalChunks(totalElements, options);
39
+ if (numChunks <= 1) {
40
+ return {
41
+ chunks: [data],
42
+ chunkInfo: [
43
+ {
44
+ startIndex: 0,
45
+ endIndex: totalElements,
46
+ length: totalElements,
47
+ chunkIndex: 0
48
+ }
49
+ ],
50
+ totalElements,
51
+ numChunks: 1
52
+ };
53
+ }
54
+ const chunks = [];
55
+ const chunkInfo = [];
56
+ if (balanced) {
57
+ const baseChunkSize = Math.floor(totalElements / numChunks);
58
+ const remainder = totalElements % numChunks;
59
+ let currentIndex = 0;
60
+ for (let i = 0; i < numChunks; i++) {
61
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
62
+ const endIndex = currentIndex + thisChunkSize;
63
+ chunks.push(data.subarray(currentIndex, endIndex));
64
+ chunkInfo.push({
65
+ startIndex: currentIndex,
66
+ endIndex,
67
+ length: thisChunkSize,
68
+ chunkIndex: i
69
+ });
70
+ currentIndex = endIndex;
71
+ }
72
+ } else {
73
+ const chunkSize = Math.max(minChunkSize, Math.ceil(totalElements / numChunks));
74
+ let currentIndex = 0;
75
+ let chunkIndex = 0;
76
+ while (currentIndex < totalElements) {
77
+ const endIndex = Math.min(currentIndex + chunkSize, totalElements);
78
+ const thisChunkSize = endIndex - currentIndex;
79
+ chunks.push(data.subarray(currentIndex, endIndex));
80
+ chunkInfo.push({
81
+ startIndex: currentIndex,
82
+ endIndex,
83
+ length: thisChunkSize,
84
+ chunkIndex
85
+ });
86
+ currentIndex = endIndex;
87
+ chunkIndex++;
88
+ }
89
+ }
90
+ return {
91
+ chunks,
92
+ chunkInfo,
93
+ totalElements,
94
+ numChunks: chunks.length
95
+ };
96
+ }
97
+ function chunkArray(data, options = {}) {
98
+ const { balanced } = { ...DEFAULT_CHUNK_OPTIONS, ...options };
99
+ const totalElements = data.length;
100
+ if (totalElements === 0) {
101
+ return {
102
+ chunks: [],
103
+ chunkInfo: [],
104
+ totalElements: 0,
105
+ numChunks: 0
106
+ };
107
+ }
108
+ const numChunks = calculateOptimalChunks(totalElements, options);
109
+ if (numChunks <= 1) {
110
+ return {
111
+ chunks: [data],
112
+ chunkInfo: [
113
+ {
114
+ startIndex: 0,
115
+ endIndex: totalElements,
116
+ length: totalElements,
117
+ chunkIndex: 0
118
+ }
119
+ ],
120
+ totalElements,
121
+ numChunks: 1
122
+ };
123
+ }
124
+ const chunks = [];
125
+ const chunkInfo = [];
126
+ if (balanced) {
127
+ const baseChunkSize = Math.floor(totalElements / numChunks);
128
+ const remainder = totalElements % numChunks;
129
+ let currentIndex = 0;
130
+ for (let i = 0; i < numChunks; i++) {
131
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
132
+ const endIndex = currentIndex + thisChunkSize;
133
+ chunks.push(data.slice(currentIndex, endIndex));
134
+ chunkInfo.push({
135
+ startIndex: currentIndex,
136
+ endIndex,
137
+ length: thisChunkSize,
138
+ chunkIndex: i
139
+ });
140
+ currentIndex = endIndex;
141
+ }
142
+ } else {
143
+ const chunkSize = Math.ceil(totalElements / numChunks);
144
+ let currentIndex = 0;
145
+ let chunkIndex = 0;
146
+ while (currentIndex < totalElements) {
147
+ const endIndex = Math.min(currentIndex + chunkSize, totalElements);
148
+ const thisChunkSize = endIndex - currentIndex;
149
+ chunks.push(data.slice(currentIndex, endIndex));
150
+ chunkInfo.push({
151
+ startIndex: currentIndex,
152
+ endIndex,
153
+ length: thisChunkSize,
154
+ chunkIndex
155
+ });
156
+ currentIndex = endIndex;
157
+ chunkIndex++;
158
+ }
159
+ }
160
+ return {
161
+ chunks,
162
+ chunkInfo,
163
+ totalElements,
164
+ numChunks: chunks.length
165
+ };
166
+ }
167
+ function mergeFloat64Chunks(chunks) {
168
+ if (chunks.length === 0) return new Float64Array(0);
169
+ if (chunks.length === 1) return chunks[0];
170
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
171
+ const result = new Float64Array(totalLength);
172
+ let offset = 0;
173
+ for (const chunk of chunks) {
174
+ result.set(chunk, offset);
175
+ offset += chunk.length;
176
+ }
177
+ return result;
178
+ }
179
+ function mergeArrayChunks(chunks) {
180
+ if (chunks.length === 0) return [];
181
+ if (chunks.length === 1) return chunks[0];
182
+ return chunks.flat();
183
+ }
184
+ function shouldParallelize(elementCount, threshold = 1e4) {
185
+ return elementCount >= threshold;
186
+ }
187
+ function partitionRange(start, end, options = {}) {
188
+ const totalElements = end - start;
189
+ const numChunks = calculateOptimalChunks(totalElements, options);
190
+ if (numChunks <= 1) {
191
+ return [[start, end]];
192
+ }
193
+ const partitions = [];
194
+ const baseChunkSize = Math.floor(totalElements / numChunks);
195
+ const remainder = totalElements % numChunks;
196
+ let currentIndex = start;
197
+ for (let i = 0; i < numChunks; i++) {
198
+ const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
199
+ const endIndex = currentIndex + thisChunkSize;
200
+ partitions.push([currentIndex, endIndex]);
201
+ currentIndex = endIndex;
202
+ }
203
+ return partitions;
204
+ }
205
+ function partition2D(rows, cols, options = {}) {
206
+ const rowPartitions = partitionRange(0, rows, options);
207
+ return rowPartitions.map(([rowStart, rowEnd]) => [rowStart, rowEnd, 0, cols]);
208
+ }
209
+
210
+ // src/ops/bitwise.ts
211
+ function applyBinaryChunk(a, b, out, start, length, outStart, op) {
212
+ const end = start + length;
213
+ switch (op) {
214
+ case "bitAnd":
215
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] & b[i];
216
+ return;
217
+ case "bitOr":
218
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] | b[i];
219
+ return;
220
+ case "bitXor":
221
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] ^ b[i];
222
+ return;
223
+ case "leftShift":
224
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] << b[i];
225
+ return;
226
+ case "rightArithShift":
227
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >> b[i];
228
+ return;
229
+ case "rightLogShift":
230
+ for (let i = start; i < end; i++) out[outStart + (i - start)] = a[i] >>> b[i];
231
+ return;
232
+ }
233
+ }
234
+ function runBinary(a, b, op, options = {}) {
235
+ if (a.length !== b.length) {
236
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
237
+ }
238
+ const out = new Int32Array(a.length);
239
+ if (a.length === 0) return out;
240
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
241
+ const base = Math.floor(a.length / numChunks);
242
+ const remainder = a.length % numChunks;
243
+ let start = 0;
244
+ for (let c = 0; c < numChunks; c++) {
245
+ const len = base + (c < remainder ? 1 : 0);
246
+ if (len === 0) continue;
247
+ applyBinaryChunk(a, b, out, start, len, start, op);
248
+ start += len;
249
+ }
250
+ return out;
251
+ }
252
+ function bitAnd(a, b, options) {
253
+ return runBinary(a, b, "bitAnd", options);
254
+ }
255
+ function bitOr(a, b, options) {
256
+ return runBinary(a, b, "bitOr", options);
257
+ }
258
+ function bitXor(a, b, options) {
259
+ return runBinary(a, b, "bitXor", options);
260
+ }
261
+ function leftShift(a, b, options) {
262
+ if (typeof b === "number") return runBinaryScalar(a, b, "leftShift", options);
263
+ return runBinary(a, b, "leftShift", options);
264
+ }
265
+ function rightArithShift(a, b, options) {
266
+ if (typeof b === "number") return runBinaryScalar(a, b, "rightArithShift", options);
267
+ return runBinary(a, b, "rightArithShift", options);
268
+ }
269
+ function rightLogShift(a, b, options) {
270
+ if (typeof b === "number") return runBinaryScalar(a, b, "rightLogShift", options);
271
+ return runBinary(a, b, "rightLogShift", options);
272
+ }
273
+ function bitNot(a, options = {}) {
274
+ const out = new Int32Array(a.length);
275
+ if (a.length === 0) return out;
276
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
277
+ const base = Math.floor(a.length / numChunks);
278
+ const remainder = a.length % numChunks;
279
+ let start = 0;
280
+ for (let c = 0; c < numChunks; c++) {
281
+ const len = base + (c < remainder ? 1 : 0);
282
+ if (len === 0) continue;
283
+ const end = start + len;
284
+ for (let i = start; i < end; i++) out[i] = ~a[i];
285
+ start += len;
286
+ }
287
+ return out;
288
+ }
289
+ function runBinaryScalar(a, scalar, op, options = {}) {
290
+ const out = new Int32Array(a.length);
291
+ if (a.length === 0) return out;
292
+ const numChunks = Math.max(1, calculateOptimalChunks(a.length, options));
293
+ const base = Math.floor(a.length / numChunks);
294
+ const remainder = a.length % numChunks;
295
+ let start = 0;
296
+ for (let c = 0; c < numChunks; c++) {
297
+ const len = base + (c < remainder ? 1 : 0);
298
+ if (len === 0) continue;
299
+ const end = start + len;
300
+ switch (op) {
301
+ case "leftShift":
302
+ for (let i = start; i < end; i++) out[i] = a[i] << scalar;
303
+ break;
304
+ case "rightArithShift":
305
+ for (let i = start; i < end; i++) out[i] = a[i] >> scalar;
306
+ break;
307
+ case "rightLogShift":
308
+ for (let i = start; i < end; i++) out[i] = a[i] >>> scalar;
309
+ break;
310
+ // The non-shift ops never go through the scalar path — caller
311
+ // routes only the three shift ops here. Listed for exhaustiveness.
312
+ case "bitAnd":
313
+ for (let i = start; i < end; i++) out[i] = a[i] & scalar;
314
+ break;
315
+ case "bitOr":
316
+ for (let i = start; i < end; i++) out[i] = a[i] | scalar;
317
+ break;
318
+ case "bitXor":
319
+ for (let i = start; i < end; i++) out[i] = a[i] ^ scalar;
320
+ break;
321
+ }
322
+ start += len;
323
+ }
324
+ return out;
325
+ }
326
+
327
+ // src/ComputePool.ts
328
+ function resolveOpThreshold(t) {
329
+ if (t === "never") return Number.MAX_SAFE_INTEGER;
330
+ if (t === "always") return 0;
331
+ return t;
332
+ }
333
+ var DEFAULT_THRESHOLD_BY_OP = {
334
+ // element-wise: overhead dominates at all tested sizes
335
+ add: "never",
336
+ subtract: "never",
337
+ multiply: "never",
338
+ divide: "never",
339
+ pow: "never",
340
+ scale: "never",
341
+ abs: "never",
342
+ negate: "never",
343
+ exp: "never",
344
+ log: "never",
345
+ sin: "never",
346
+ cos: "never",
347
+ tan: "never",
348
+ sign: "never",
349
+ // tensor contraction: parallelize when contracted-axis volume >= 8K
350
+ tensordot: 8192,
351
+ // reductions: overhead dominates
352
+ sum: "never",
353
+ mean: "never",
354
+ variance: "never",
355
+ parallelStatProd: "never",
356
+ normalCDF: "never",
357
+ // signal: overhead dominates or break-even never reached
358
+ parallelFFT: "never",
359
+ parallelConv: "never",
360
+ fft2d: "never",
361
+ // geometry: overhead dominates
362
+ distanceMatrix: "never",
363
+ // special functions: parallel wins above these sizes
364
+ erfc: 1e5,
365
+ besselJ: 1e6,
366
+ spectrogram: 65536,
367
+ // linear algebra: parallel wins above these element counts
368
+ matmul: 4096,
369
+ // break-even at 64×64 (4,096 elements)
370
+ matrixPower: 9216,
371
+ // break-even at ~96×96
372
+ characteristicPolynomial: 9216,
373
+ // break-even at ~96×96
374
+ // hypothesis tests: element-wise reduction (chiSquare) and post-sort
375
+ // dot-product (KS/MW/SW). Threshold 4096 per Slice 3.10 spec.
376
+ chiSquareTest: 4096,
377
+ kolmogorovSmirnovTest: 4096,
378
+ mannWhitneyTest: 4096,
379
+ shapiroWilkTest: 4096,
380
+ // distribution batch sampling: per-sample cost (rejection/transcendental) amortises
381
+ // worker overhead above 100 000 samples (Slice 5.12).
382
+ sampleChunk: 1e5
383
+ };
6
384
  var DEFAULT_POOL_CONFIG = {
7
385
  enabled: true,
8
386
  minWorkers: 1,
9
387
  maxWorkers: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
10
388
  thresholdElements: 5e4,
389
+ thresholdByOp: DEFAULT_THRESHOLD_BY_OP,
11
390
  chunkSize: 1e4,
12
391
  workerType: "auto",
13
392
  workerIdleTimeout: 6e4,
@@ -34,6 +413,80 @@ function toWorkerConfig(config) {
34
413
  taskTimeout: config.taskTimeout
35
414
  };
36
415
  }
416
+ function _rowMajorStrides(shape) {
417
+ const strides = new Array(shape.length);
418
+ let stride = 1;
419
+ for (let i = shape.length - 1; i >= 0; i--) {
420
+ strides[i] = stride;
421
+ stride *= shape[i];
422
+ }
423
+ return strides;
424
+ }
425
+ function _flatIndex(idx, strides) {
426
+ let flat = 0;
427
+ for (let i = 0; i < idx.length; i++) flat += idx[i] * strides[i];
428
+ return flat;
429
+ }
430
+ function _forEachIndex(sizes, cb) {
431
+ const rank = sizes.length;
432
+ if (rank === 0) {
433
+ cb([]);
434
+ return;
435
+ }
436
+ const idx = new Array(rank).fill(0);
437
+ const total = sizes.reduce((a, b) => a * b, 1);
438
+ for (let n = 0; n < total; n++) {
439
+ cb(idx.slice());
440
+ for (let d = rank - 1; d >= 0; d--) {
441
+ idx[d]++;
442
+ if (idx[d] < sizes[d]) break;
443
+ idx[d] = 0;
444
+ }
445
+ }
446
+ }
447
+ function tensordotChunkKernel(a, aShape, b, bShape, axesA, axesB, resultShape, outStart, outEnd) {
448
+ const aStrides = _rowMajorStrides(aShape);
449
+ const bStrides = _rowMajorStrides(bShape);
450
+ const outStrides = _rowMajorStrides(resultShape);
451
+ const aRank = aShape.length;
452
+ const bRank = bShape.length;
453
+ const contractedAxesA = Array.from(axesA);
454
+ const contractedAxesB = Array.from(axesB);
455
+ const contractedSetA = new Set(contractedAxesA);
456
+ const contractedSetB = new Set(contractedAxesB);
457
+ const aFreeAxes = [];
458
+ const bFreeAxes = [];
459
+ for (let i = 0; i < aRank; i++) if (!contractedSetA.has(i)) aFreeAxes.push(i);
460
+ for (let i = 0; i < bRank; i++) if (!contractedSetB.has(i)) bFreeAxes.push(i);
461
+ const contractedSizes = contractedAxesA.map((ax) => aShape[ax]);
462
+ const chunkLen = outEnd - outStart;
463
+ const result = new Float64Array(chunkLen);
464
+ const outRank = resultShape.length;
465
+ const outIdx = new Array(outRank);
466
+ for (let outFlat = outStart; outFlat < outEnd; outFlat++) {
467
+ let rem = outFlat;
468
+ for (let d = 0; d < outRank; d++) {
469
+ outIdx[d] = Math.floor(rem / outStrides[d]);
470
+ rem -= outIdx[d] * outStrides[d];
471
+ }
472
+ const aFreeVals = outIdx.slice(0, aFreeAxes.length);
473
+ const bFreeVals = outIdx.slice(aFreeAxes.length);
474
+ let acc = 0;
475
+ _forEachIndex(contractedSizes, (contractVals) => {
476
+ const aIdx = new Array(aRank).fill(0);
477
+ for (let fi = 0; fi < aFreeAxes.length; fi++) aIdx[aFreeAxes[fi]] = aFreeVals[fi];
478
+ for (let ci = 0; ci < contractedAxesA.length; ci++)
479
+ aIdx[contractedAxesA[ci]] = contractVals[ci];
480
+ const bIdx = new Array(bRank).fill(0);
481
+ for (let fi = 0; fi < bFreeAxes.length; fi++) bIdx[bFreeAxes[fi]] = bFreeVals[fi];
482
+ for (let ci = 0; ci < contractedAxesB.length; ci++)
483
+ bIdx[contractedAxesB[ci]] = contractVals[ci];
484
+ acc += a[_flatIndex(aIdx, aStrides)] * b[_flatIndex(bIdx, bStrides)];
485
+ });
486
+ result[outFlat - outStart] = acc;
487
+ }
488
+ return result;
489
+ }
37
490
  var ComputePool = class {
38
491
  workerPool;
39
492
  config;
@@ -54,9 +507,22 @@ var ComputePool = class {
54
507
  return this.workerPool.isReady();
55
508
  }
56
509
  /**
57
- * Determine if operation should be parallelized
510
+ * Determine if operation should be parallelized.
511
+ *
512
+ * When `op` is supplied the per-op threshold map is consulted first;
513
+ * if the op is absent from the map the global `thresholdElements` fallback
514
+ * is used. When `op` is omitted the global threshold is always used (for
515
+ * backward-compatibility with existing callers that don't know the op name).
58
516
  */
59
- shouldParallelize(elementCount) {
517
+ shouldParallelize(elementCount, op) {
518
+ const byOp = this.config.thresholdByOp;
519
+ if (op !== void 0 && byOp !== void 0 && op in byOp) {
520
+ const raw = byOp[op];
521
+ if (raw !== void 0) {
522
+ const threshold = resolveOpThreshold(raw);
523
+ return this.config.enabled && elementCount >= threshold && this.workerPool.isReady();
524
+ }
525
+ }
60
526
  return this.workerPool.shouldParallelize(elementCount);
61
527
  }
62
528
  /**
@@ -78,6 +544,13 @@ var ComputePool = class {
78
544
  const result = await this.workerPool.sum(data);
79
545
  return toParallelResult(result);
80
546
  }
547
+ /**
548
+ * Parallel product of array elements
549
+ */
550
+ async prod(data) {
551
+ const result = await this.workerPool.prod(data);
552
+ return toParallelResult(result);
553
+ }
81
554
  /**
82
555
  * Parallel dot product
83
556
  */
@@ -188,6 +661,141 @@ var ComputePool = class {
188
661
  const result = await this.workerPool.unary(data, fn);
189
662
  return toParallelResult(result);
190
663
  }
664
+ /**
665
+ * Apply a caller-supplied unary numeric function in parallel.
666
+ *
667
+ * @param data - Input values
668
+ * @param fnSource - Source of a self-contained `(x: number) => number`
669
+ * expression. It must not reference free variables / closures, since it
670
+ * is eval'd in an isolated worker context. Used to parallelize element-wise
671
+ * math (special functions, distribution PDFs/CDFs).
672
+ */
673
+ async applyKernel(data, fnSource) {
674
+ const result = await this.workerPool.applyKernel(data, fnSource);
675
+ return toParallelResult(result);
676
+ }
677
+ /**
678
+ * Apply a caller-supplied binary numeric function in parallel.
679
+ *
680
+ * @param a - First operand array
681
+ * @param b - Second operand array (must match the length of `a`)
682
+ * @param fnSource - Source of a self-contained `(a: number, b: number) =>
683
+ * number` expression (no free variables / closures).
684
+ */
685
+ async applyKernel2(a, b, fnSource) {
686
+ const result = await this.workerPool.applyKernel2(a, b, fnSource);
687
+ return toParallelResult(result);
688
+ }
689
+ /**
690
+ * Fan-out Gauss-Legendre quadrature over `workerCount` sub-intervals.
691
+ *
692
+ * Partitions `[a, b]` into `workerCount` equal sub-domains and dispatches one
693
+ * `integrateChunk` worker task per sub-domain. Each task evaluates the
694
+ * integrand (supplied as a stringified closure) with the given GL nodes and
695
+ * weights and returns its partial integral. The partial sums are aggregated
696
+ * on the main thread.
697
+ *
698
+ * **Closure contract:** `fnSource` must be a self-contained `(x: number) =>
699
+ * number` expression — no free variables referencing outer scope, because it
700
+ * is eval'd inside an isolated worker context. Call-site validation (via
701
+ * `validateClosureSource`) should have already rejected unsafe closures before
702
+ * reaching this method.
703
+ *
704
+ * @param fnSource - Stringified integrand, e.g. `"(x) => Math.sin(x)"`
705
+ * @param a - Lower integration bound
706
+ * @param b - Upper integration bound
707
+ * @param workerCount - Number of sub-intervals / worker tasks
708
+ * @param nodes - GL nodes on [-1, 1]
709
+ * @param weights - Corresponding GL weights
710
+ * @returns Sum of partial integrals = approximate value of ∫ₐᵇ f(x) dx
711
+ */
712
+ async integrateFanOut(fnSource, a, b, workerCount, nodes, weights) {
713
+ const h = (b - a) / workerCount;
714
+ const tasks = [];
715
+ for (let k = 0; k < workerCount; k++) {
716
+ const subA = a + k * h;
717
+ const subB = a + (k + 1) * h;
718
+ tasks.push(
719
+ this.workerPool.exec("integrateChunk", [fnSource, subA, subB, nodes, weights])
720
+ );
721
+ }
722
+ const partials = await Promise.all(tasks);
723
+ return partials.reduce((acc, v) => acc + v, 0);
724
+ }
725
+ /**
726
+ * Fan-out distribution batch sampling across `workerCount` workers.
727
+ *
728
+ * Partitions `n` samples into `workerCount` equal chunks (last chunk absorbs
729
+ * the remainder) and dispatches one `sampleChunk` task per chunk. Each chunk
730
+ * uses an independent seed derived from `baseSeed` via SplitMix64-style
731
+ * hashing:
732
+ *
733
+ * `chunkSeed = (baseSeed ^ (chunkIdx * 0x9E3779B97F4A7C15)) >>> 0`
734
+ *
735
+ * This Fibonacci-hashing multiplier gives good avalanche: even baseSeed=0
736
+ * produces K distinct seeds, and adjacent chunk indices produce uncorrelated
737
+ * sequences. See the `sampleChunk` worker kernel for the full rationale.
738
+ *
739
+ * @param distName - One of 'normal', 'gamma', 'beta', 'studentT', 'exponential'
740
+ * @param params - Distribution parameters (order matches factory functions)
741
+ * @param n - Total number of samples to generate
742
+ * @param baseSeed - Base seed for reproducible output
743
+ * @param workerCount - Number of parallel worker tasks
744
+ * @returns Concatenated Float64Array of `n` samples
745
+ */
746
+ async distributionSampleFanOut(distName, params, n, baseSeed, workerCount) {
747
+ const SPLIT_MIX_CONST = 2654435769;
748
+ const baseChunkSize = Math.floor(n / workerCount);
749
+ const tasks = [];
750
+ for (let k = 0; k < workerCount; k++) {
751
+ const chunkCount = k < workerCount - 1 ? baseChunkSize : n - baseChunkSize * (workerCount - 1);
752
+ const chunkSeed = (baseSeed ^ (Math.imul(k, SPLIT_MIX_CONST) | 0)) >>> 0;
753
+ tasks.push(
754
+ this.workerPool.exec("sampleChunk", [distName, params, chunkSeed, chunkCount])
755
+ );
756
+ }
757
+ const buffers = await Promise.all(tasks);
758
+ const out = new Float64Array(n);
759
+ let offset = 0;
760
+ for (const buf of buffers) {
761
+ const chunk = new Float64Array(buf);
762
+ out.set(chunk, offset);
763
+ offset += chunk.length;
764
+ }
765
+ return out;
766
+ }
767
+ /**
768
+ * Compute a batch of independent radix-2 FFTs in parallel.
769
+ *
770
+ * `real` and `imag` each hold `frameCount` concatenated frames of
771
+ * `frameLength` samples (`frameLength` must be a power of two). Each frame
772
+ * is FFT'd independently and the frames are distributed across workers.
773
+ *
774
+ * Used to parallelize the embarrassingly-parallel FFT batches in
775
+ * `spectrogram` (one FFT per windowed frame) and `fft2d` (rows then columns).
776
+ *
777
+ * @param real - Concatenated real parts (`frameCount * frameLength` values)
778
+ * @param imag - Concatenated imaginary parts (same layout as `real`)
779
+ * @param frameCount - Number of independent frames
780
+ * @param frameLength - Samples per frame (power of two)
781
+ * @param inverse - Compute the inverse FFT when true
782
+ */
783
+ async fftBatch(real, imag, frameCount, frameLength, inverse = false) {
784
+ const result = await this.workerPool.fftBatch(real, imag, frameCount, frameLength, inverse);
785
+ return toParallelResult(result);
786
+ }
787
+ /**
788
+ * Compute an all-pairs Euclidean distance matrix in parallel.
789
+ *
790
+ * @param points - Flattened `n * dim` coordinate array (row-major)
791
+ * @param n - Number of points
792
+ * @param dim - Coordinate dimension
793
+ * @returns Flat row-major `n * n` distance matrix
794
+ */
795
+ async distanceMatrix(points, n, dim) {
796
+ const result = await this.workerPool.distanceMatrix(points, n, dim);
797
+ return toParallelResult(result);
798
+ }
191
799
  /**
192
800
  * Parallel absolute value
193
801
  */
@@ -298,49 +906,350 @@ var ComputePool = class {
298
906
  return this.elementwise(a, b, "multiply");
299
907
  }
300
908
  /**
301
- * Parallel element-wise division
909
+ * Parallel element-wise division
910
+ */
911
+ async divide(a, b) {
912
+ return this.elementwise(a, b, "divide");
913
+ }
914
+ /**
915
+ * Parallel element-wise exponentiation.
916
+ *
917
+ * `out[i] = a[i] ** b[i]`
918
+ *
919
+ * Delegates to `applyKernel2` with the `Math.pow` kernel. The default
920
+ * per-op threshold for `pow` is `'never'` (overhead dominates); the
921
+ * sequential path is taken unless the caller overrides `thresholdByOp.pow`.
922
+ */
923
+ async pow(a, b) {
924
+ if (a.length !== b.length) {
925
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
926
+ }
927
+ if (this.shouldParallelize(a.length, "pow")) {
928
+ return this.applyKernel2(a, b, "(a, b) => Math.pow(a, b)");
929
+ }
930
+ const start = performance.now();
931
+ const result = new Float64Array(a.length);
932
+ for (let i = 0; i < a.length; i++) {
933
+ result[i] = a[i] ** b[i];
934
+ }
935
+ return {
936
+ result,
937
+ duration: performance.now() - start,
938
+ chunks: 1,
939
+ parallelized: false
940
+ };
941
+ }
942
+ /**
943
+ * Parallel element-wise sign function.
944
+ *
945
+ * `out[i] = Math.sign(data[i])` → -1 | 0 | 1, with NaN → NaN (IEEE 754).
946
+ *
947
+ * Delegates to `applyKernel` with the `Math.sign` kernel. The default
948
+ * per-op threshold for `sign` is `'never'` (overhead dominates); the
949
+ * sequential path is taken unless the caller overrides `thresholdByOp.sign`.
950
+ */
951
+ async sign(data) {
952
+ if (this.shouldParallelize(data.length, "sign")) {
953
+ return this.applyKernel(data, "(x) => Math.sign(x)");
954
+ }
955
+ const start = performance.now();
956
+ const result = new Float64Array(data.length);
957
+ for (let i = 0; i < data.length; i++) {
958
+ result[i] = Math.sign(data[i]);
959
+ }
960
+ return {
961
+ result,
962
+ duration: performance.now() - start,
963
+ chunks: 1,
964
+ parallelized: false
965
+ };
966
+ }
967
+ /**
968
+ * General tensor contraction (tensordot) over specified axis pairs.
969
+ *
970
+ * Contracts axis `axesA[i]` of `a` with axis `axesB[i]` of `b` for each
971
+ * pair index `i`. The result shape is the non-contracted axes of `a` followed
972
+ * by the non-contracted axes of `b` (NumPy convention).
973
+ *
974
+ * Parallelization threshold: contracted-axis volume (product of contracted
975
+ * dimensions) >= 8 192. When parallelized, output elements are distributed
976
+ * in contiguous slices across workers via the `tensordotChunk` kernel.
977
+ *
978
+ * @param a - Flat row-major Float64Array for tensor A
979
+ * @param aShape - Shape of tensor A
980
+ * @param b - Flat row-major Float64Array for tensor B
981
+ * @param bShape - Shape of tensor B
982
+ * @param axesA - Axes of A to contract (same length as axesB)
983
+ * @param axesB - Axes of B to contract (same length as axesA)
984
+ * @returns `{ data: Float64Array; shape: number[] }` — flat result + result shape
985
+ */
986
+ async tensordot(a, aShape, b, bShape, axesA, axesB) {
987
+ if (axesA.length !== axesB.length) {
988
+ throw new Error(
989
+ `tensordot: axesA.length (${axesA.length}) !== axesB.length (${axesB.length})`
990
+ );
991
+ }
992
+ const aRank = aShape.length;
993
+ const bRank = bShape.length;
994
+ const aContracted = /* @__PURE__ */ new Set();
995
+ const bContracted = /* @__PURE__ */ new Set();
996
+ for (let i = 0; i < axesA.length; i++) {
997
+ const ax = axesA[i];
998
+ const bx = axesB[i];
999
+ if (!Number.isInteger(ax) || ax < 0 || ax >= aRank) {
1000
+ throw new Error(`tensordot: axesA[${i}]=${ax} is out of range for aShape (rank ${aRank})`);
1001
+ }
1002
+ if (!Number.isInteger(bx) || bx < 0 || bx >= bRank) {
1003
+ throw new Error(`tensordot: axesB[${i}]=${bx} is out of range for bShape (rank ${bRank})`);
1004
+ }
1005
+ if (aShape[ax] !== bShape[bx]) {
1006
+ throw new Error(
1007
+ `tensordot: dimension mismatch on contracted axes: aShape[${ax}]=${aShape[ax]} vs bShape[${bx}]=${bShape[bx]}`
1008
+ );
1009
+ }
1010
+ aContracted.add(ax);
1011
+ bContracted.add(bx);
1012
+ }
1013
+ const resultShape = [];
1014
+ for (let i = 0; i < aRank; i++) {
1015
+ if (!aContracted.has(i)) resultShape.push(aShape[i]);
1016
+ }
1017
+ for (let i = 0; i < bRank; i++) {
1018
+ if (!bContracted.has(i)) resultShape.push(bShape[i]);
1019
+ }
1020
+ const resultSize = resultShape.reduce((acc, d) => acc * d, 1);
1021
+ const contractedVolume = Array.from(axesA).reduce((acc, ax) => acc * aShape[ax], 1);
1022
+ const start = performance.now();
1023
+ if (this.shouldParallelize(contractedVolume, "tensordot") && resultSize > 0) {
1024
+ const chunkSize = this.config.chunkSize;
1025
+ const chunks = [];
1026
+ for (let i = 0; i < resultSize; i += chunkSize) {
1027
+ chunks.push([i, Math.min(i + chunkSize, resultSize)]);
1028
+ }
1029
+ const aShapeArr = Array.from(aShape);
1030
+ const bShapeArr = Array.from(bShape);
1031
+ const axesAArr = Array.from(axesA);
1032
+ const axesBArr = Array.from(axesB);
1033
+ const chunkResults = await Promise.all(
1034
+ chunks.map(
1035
+ ([outStart, outEnd]) => this.workerPool.exec("tensordotChunk", [
1036
+ a.buffer,
1037
+ aShapeArr,
1038
+ b.buffer,
1039
+ bShapeArr,
1040
+ axesAArr,
1041
+ axesBArr,
1042
+ resultShape,
1043
+ outStart,
1044
+ outEnd
1045
+ ])
1046
+ )
1047
+ );
1048
+ const data2 = new Float64Array(resultSize);
1049
+ let offset = 0;
1050
+ for (const buf of chunkResults) {
1051
+ const chunk = new Float64Array(buf);
1052
+ data2.set(chunk, offset);
1053
+ offset += chunk.length;
1054
+ }
1055
+ return {
1056
+ result: { data: data2, shape: resultShape },
1057
+ duration: performance.now() - start,
1058
+ chunks: chunks.length,
1059
+ parallelized: true
1060
+ };
1061
+ }
1062
+ const data = resultSize === 0 ? new Float64Array(0) : tensordotChunkKernel(a, aShape, b, bShape, axesA, axesB, resultShape, 0, resultSize);
1063
+ return {
1064
+ result: { data, shape: resultShape },
1065
+ duration: performance.now() - start,
1066
+ chunks: 1,
1067
+ parallelized: false
1068
+ };
1069
+ }
1070
+ /**
1071
+ * Compute mean in parallel (uses variance internally)
1072
+ */
1073
+ async mean(data) {
1074
+ const result = await this.variance(data);
1075
+ return {
1076
+ ...result,
1077
+ result: result.result.mean
1078
+ };
1079
+ }
1080
+ /**
1081
+ * Compute standard deviation in parallel
1082
+ */
1083
+ async std(data) {
1084
+ const result = await this.variance(data);
1085
+ return {
1086
+ ...result,
1087
+ result: result.result.std
1088
+ };
1089
+ }
1090
+ /**
1091
+ * Find minimum value in parallel
1092
+ */
1093
+ async min(data) {
1094
+ const result = await this.minMax(data);
1095
+ return {
1096
+ ...result,
1097
+ result: result.result.min
1098
+ };
1099
+ }
1100
+ /**
1101
+ * Find maximum value in parallel
1102
+ */
1103
+ async max(data) {
1104
+ const result = await this.minMax(data);
1105
+ return {
1106
+ ...result,
1107
+ result: result.result.max
1108
+ };
1109
+ }
1110
+ // =========================================================================
1111
+ // Bitwise Operations (Int32Array)
1112
+ // =========================================================================
1113
+ //
1114
+ // Above the elementwise threshold these dispatch to the `Int32Array`-keyed
1115
+ // worker kernels in `@danielsimonjr/mathts-workerpool` (`bitwiseChunk`,
1116
+ // `bitwiseScalarChunk`, `bitwiseNotChunk`). Below threshold they fall back
1117
+ // to the pure in-process drivers in `./ops/bitwise.ts`. The fallback path
1118
+ // uses those drivers verbatim so the algorithm lives in one place.
1119
+ /**
1120
+ * Element-wise bitwise AND on two `Int32Array`s.
1121
+ */
1122
+ async bitAnd(a, b) {
1123
+ if (a.length !== b.length) {
1124
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1125
+ }
1126
+ if (this.shouldParallelize(a.length)) {
1127
+ return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitAnd"));
1128
+ }
1129
+ const start = performance.now();
1130
+ const result = bitAnd(a, b);
1131
+ return {
1132
+ result,
1133
+ duration: performance.now() - start,
1134
+ chunks: 1,
1135
+ parallelized: false
1136
+ };
1137
+ }
1138
+ /**
1139
+ * Element-wise bitwise OR on two `Int32Array`s.
1140
+ */
1141
+ async bitOr(a, b) {
1142
+ if (a.length !== b.length) {
1143
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1144
+ }
1145
+ if (this.shouldParallelize(a.length)) {
1146
+ return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitOr"));
1147
+ }
1148
+ const start = performance.now();
1149
+ const result = bitOr(a, b);
1150
+ return {
1151
+ result,
1152
+ duration: performance.now() - start,
1153
+ chunks: 1,
1154
+ parallelized: false
1155
+ };
1156
+ }
1157
+ /**
1158
+ * Element-wise bitwise XOR on two `Int32Array`s.
302
1159
  */
303
- async divide(a, b) {
304
- return this.elementwise(a, b, "divide");
1160
+ async bitXor(a, b) {
1161
+ if (a.length !== b.length) {
1162
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1163
+ }
1164
+ if (this.shouldParallelize(a.length)) {
1165
+ return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitXor"));
1166
+ }
1167
+ const start = performance.now();
1168
+ const result = bitXor(a, b);
1169
+ return {
1170
+ result,
1171
+ duration: performance.now() - start,
1172
+ chunks: 1,
1173
+ parallelized: false
1174
+ };
305
1175
  }
306
1176
  /**
307
- * Compute mean in parallel (uses variance internally)
1177
+ * Unary bitwise NOT on an `Int32Array`.
308
1178
  */
309
- async mean(data) {
310
- const result = await this.variance(data);
1179
+ async bitNot(a) {
1180
+ if (this.shouldParallelize(a.length)) {
1181
+ return toParallelResult(await this.workerPool.bitwiseNot(a));
1182
+ }
1183
+ const start = performance.now();
1184
+ const result = bitNot(a);
311
1185
  return {
312
- ...result,
313
- result: result.result.mean
1186
+ result,
1187
+ duration: performance.now() - start,
1188
+ chunks: 1,
1189
+ parallelized: false
314
1190
  };
315
1191
  }
316
1192
  /**
317
- * Compute standard deviation in parallel
1193
+ * Element-wise left shift. `b` may be a per-element `Int32Array` of
1194
+ * shift counts or a single scalar `number` applied uniformly.
318
1195
  */
319
- async std(data) {
320
- const result = await this.variance(data);
1196
+ async leftShift(a, b) {
1197
+ if (typeof b !== "number" && a.length !== b.length) {
1198
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1199
+ }
1200
+ if (this.shouldParallelize(a.length)) {
1201
+ const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "leftShift") : await this.workerPool.bitwiseBinary(a, b, "leftShift");
1202
+ return toParallelResult(result2);
1203
+ }
1204
+ const start = performance.now();
1205
+ const result = leftShift(a, b);
321
1206
  return {
322
- ...result,
323
- result: result.result.std
1207
+ result,
1208
+ duration: performance.now() - start,
1209
+ chunks: 1,
1210
+ parallelized: false
324
1211
  };
325
1212
  }
326
1213
  /**
327
- * Find minimum value in parallel
1214
+ * Element-wise arithmetic (sign-preserving) right shift.
328
1215
  */
329
- async min(data) {
330
- const result = await this.minMax(data);
1216
+ async rightArithShift(a, b) {
1217
+ if (typeof b !== "number" && a.length !== b.length) {
1218
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1219
+ }
1220
+ if (this.shouldParallelize(a.length)) {
1221
+ const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightArithShift") : await this.workerPool.bitwiseBinary(a, b, "rightArithShift");
1222
+ return toParallelResult(result2);
1223
+ }
1224
+ const start = performance.now();
1225
+ const result = rightArithShift(a, b);
331
1226
  return {
332
- ...result,
333
- result: result.result.min
1227
+ result,
1228
+ duration: performance.now() - start,
1229
+ chunks: 1,
1230
+ parallelized: false
334
1231
  };
335
1232
  }
336
1233
  /**
337
- * Find maximum value in parallel
1234
+ * Element-wise logical (zero-filling) right shift. Results are stored
1235
+ * as `Int32Array`, so values ≥ 2^31 wrap into the negative range —
1236
+ * matching `(x >>> n) | 0` JavaScript semantics.
338
1237
  */
339
- async max(data) {
340
- const result = await this.minMax(data);
1238
+ async rightLogShift(a, b) {
1239
+ if (typeof b !== "number" && a.length !== b.length) {
1240
+ throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1241
+ }
1242
+ if (this.shouldParallelize(a.length)) {
1243
+ const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightLogShift") : await this.workerPool.bitwiseBinary(a, b, "rightLogShift");
1244
+ return toParallelResult(result2);
1245
+ }
1246
+ const start = performance.now();
1247
+ const result = rightLogShift(a, b);
341
1248
  return {
342
- ...result,
343
- result: result.result.max
1249
+ result,
1250
+ duration: performance.now() - start,
1251
+ chunks: 1,
1252
+ parallelized: false
344
1253
  };
345
1254
  }
346
1255
  /**
@@ -362,6 +1271,13 @@ var ComputePool = class {
362
1271
  getConfig() {
363
1272
  return { ...this.config };
364
1273
  }
1274
+ /**
1275
+ * Maximum number of workers configured for this pool.
1276
+ * Used by distribution sampling fan-out to determine chunk count.
1277
+ */
1278
+ get workerCount() {
1279
+ return this.config.maxWorkers;
1280
+ }
365
1281
  /**
366
1282
  * Get the underlying MathWorkerPool for advanced operations
367
1283
  */
@@ -566,209 +1482,6 @@ async function parallelCount(data, predicate, options = {}) {
566
1482
  };
567
1483
  }
568
1484
 
569
- // src/strategies/chunk.ts
570
- var DEFAULT_CHUNK_OPTIONS = {
571
- minChunkSize: 1e3,
572
- maxChunks: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
573
- targetChunkSize: 0,
574
- // 0 = auto
575
- balanced: true
576
- };
577
- function calculateOptimalChunks(totalElements, options = {}) {
578
- const { minChunkSize, maxChunks, targetChunkSize } = {
579
- ...DEFAULT_CHUNK_OPTIONS,
580
- ...options
581
- };
582
- if (totalElements === 0) return 0;
583
- if (targetChunkSize > 0) {
584
- return Math.min(maxChunks, Math.ceil(totalElements / targetChunkSize));
585
- }
586
- const maxPossibleChunks = Math.floor(totalElements / minChunkSize);
587
- return Math.max(1, Math.min(maxChunks, maxPossibleChunks));
588
- }
589
- function chunkFloat64Array(data, options = {}) {
590
- const { minChunkSize, balanced } = { ...DEFAULT_CHUNK_OPTIONS, ...options };
591
- const totalElements = data.length;
592
- if (totalElements === 0) {
593
- return {
594
- chunks: [],
595
- chunkInfo: [],
596
- totalElements: 0,
597
- numChunks: 0
598
- };
599
- }
600
- const numChunks = calculateOptimalChunks(totalElements, options);
601
- if (numChunks <= 1) {
602
- return {
603
- chunks: [data],
604
- chunkInfo: [
605
- {
606
- startIndex: 0,
607
- endIndex: totalElements,
608
- length: totalElements,
609
- chunkIndex: 0
610
- }
611
- ],
612
- totalElements,
613
- numChunks: 1
614
- };
615
- }
616
- const chunks = [];
617
- const chunkInfo = [];
618
- if (balanced) {
619
- const baseChunkSize = Math.floor(totalElements / numChunks);
620
- const remainder = totalElements % numChunks;
621
- let currentIndex = 0;
622
- for (let i = 0; i < numChunks; i++) {
623
- const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
624
- const endIndex = currentIndex + thisChunkSize;
625
- chunks.push(data.subarray(currentIndex, endIndex));
626
- chunkInfo.push({
627
- startIndex: currentIndex,
628
- endIndex,
629
- length: thisChunkSize,
630
- chunkIndex: i
631
- });
632
- currentIndex = endIndex;
633
- }
634
- } else {
635
- const chunkSize = Math.max(minChunkSize, Math.ceil(totalElements / numChunks));
636
- let currentIndex = 0;
637
- let chunkIndex = 0;
638
- while (currentIndex < totalElements) {
639
- const endIndex = Math.min(currentIndex + chunkSize, totalElements);
640
- const thisChunkSize = endIndex - currentIndex;
641
- chunks.push(data.subarray(currentIndex, endIndex));
642
- chunkInfo.push({
643
- startIndex: currentIndex,
644
- endIndex,
645
- length: thisChunkSize,
646
- chunkIndex
647
- });
648
- currentIndex = endIndex;
649
- chunkIndex++;
650
- }
651
- }
652
- return {
653
- chunks,
654
- chunkInfo,
655
- totalElements,
656
- numChunks: chunks.length
657
- };
658
- }
659
- function chunkArray(data, options = {}) {
660
- const { balanced } = { ...DEFAULT_CHUNK_OPTIONS, ...options };
661
- const totalElements = data.length;
662
- if (totalElements === 0) {
663
- return {
664
- chunks: [],
665
- chunkInfo: [],
666
- totalElements: 0,
667
- numChunks: 0
668
- };
669
- }
670
- const numChunks = calculateOptimalChunks(totalElements, options);
671
- if (numChunks <= 1) {
672
- return {
673
- chunks: [data],
674
- chunkInfo: [
675
- {
676
- startIndex: 0,
677
- endIndex: totalElements,
678
- length: totalElements,
679
- chunkIndex: 0
680
- }
681
- ],
682
- totalElements,
683
- numChunks: 1
684
- };
685
- }
686
- const chunks = [];
687
- const chunkInfo = [];
688
- if (balanced) {
689
- const baseChunkSize = Math.floor(totalElements / numChunks);
690
- const remainder = totalElements % numChunks;
691
- let currentIndex = 0;
692
- for (let i = 0; i < numChunks; i++) {
693
- const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
694
- const endIndex = currentIndex + thisChunkSize;
695
- chunks.push(data.slice(currentIndex, endIndex));
696
- chunkInfo.push({
697
- startIndex: currentIndex,
698
- endIndex,
699
- length: thisChunkSize,
700
- chunkIndex: i
701
- });
702
- currentIndex = endIndex;
703
- }
704
- } else {
705
- const chunkSize = Math.ceil(totalElements / numChunks);
706
- let currentIndex = 0;
707
- let chunkIndex = 0;
708
- while (currentIndex < totalElements) {
709
- const endIndex = Math.min(currentIndex + chunkSize, totalElements);
710
- const thisChunkSize = endIndex - currentIndex;
711
- chunks.push(data.slice(currentIndex, endIndex));
712
- chunkInfo.push({
713
- startIndex: currentIndex,
714
- endIndex,
715
- length: thisChunkSize,
716
- chunkIndex
717
- });
718
- currentIndex = endIndex;
719
- chunkIndex++;
720
- }
721
- }
722
- return {
723
- chunks,
724
- chunkInfo,
725
- totalElements,
726
- numChunks: chunks.length
727
- };
728
- }
729
- function mergeFloat64Chunks(chunks) {
730
- if (chunks.length === 0) return new Float64Array(0);
731
- if (chunks.length === 1) return chunks[0];
732
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
733
- const result = new Float64Array(totalLength);
734
- let offset = 0;
735
- for (const chunk of chunks) {
736
- result.set(chunk, offset);
737
- offset += chunk.length;
738
- }
739
- return result;
740
- }
741
- function mergeArrayChunks(chunks) {
742
- if (chunks.length === 0) return [];
743
- if (chunks.length === 1) return chunks[0];
744
- return chunks.flat();
745
- }
746
- function shouldParallelize(elementCount, threshold = 1e4) {
747
- return elementCount >= threshold;
748
- }
749
- function partitionRange(start, end, options = {}) {
750
- const totalElements = end - start;
751
- const numChunks = calculateOptimalChunks(totalElements, options);
752
- if (numChunks <= 1) {
753
- return [[start, end]];
754
- }
755
- const partitions = [];
756
- const baseChunkSize = Math.floor(totalElements / numChunks);
757
- const remainder = totalElements % numChunks;
758
- let currentIndex = start;
759
- for (let i = 0; i < numChunks; i++) {
760
- const thisChunkSize = baseChunkSize + (i < remainder ? 1 : 0);
761
- const endIndex = currentIndex + thisChunkSize;
762
- partitions.push([currentIndex, endIndex]);
763
- currentIndex = endIndex;
764
- }
765
- return partitions;
766
- }
767
- function partition2D(rows, cols, options = {}) {
768
- const rowPartitions = partitionRange(0, rows, options);
769
- return rowPartitions.map(([rowStart, rowEnd]) => [rowStart, rowEnd, 0, cols]);
770
- }
771
-
772
1485
  // src/strategies/threshold.ts
773
1486
  var DEFAULT_THRESHOLDS = {
774
1487
  matmul: 1e4,
@@ -897,12 +1610,17 @@ export {
897
1610
  DEFAULT_THRESHOLDS,
898
1611
  ThresholdDispatcher,
899
1612
  Transfer,
1613
+ bitAnd,
1614
+ bitNot,
1615
+ bitOr,
1616
+ bitXor,
900
1617
  calculateChunks,
901
1618
  calculateOptimalChunks,
902
1619
  chunkArray,
903
1620
  chunkFloat64Array,
904
1621
  computePool,
905
1622
  dispatch,
1623
+ leftShift,
906
1624
  mergeArrayChunks,
907
1625
  mergeFloat64Chunks,
908
1626
  parallelAbs,
@@ -947,6 +1665,9 @@ export {
947
1665
  parallelVariance,
948
1666
  partition2D,
949
1667
  partitionRange,
1668
+ resolveOpThreshold,
1669
+ rightArithShift,
1670
+ rightLogShift,
950
1671
  shouldParallelize as shouldChunkParallelize,
951
1672
  shouldParallelize2 as shouldParallelize,
952
1673
  thresholdDispatcher