@danielsimonjr/mathts-parallel 0.3.0 → 0.3.2

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.d.ts CHANGED
@@ -24,7 +24,7 @@ export { Transfer } from '@danielsimonjr/mathts-workerpool';
24
24
  * - Special / distribution functions (`erfc`, `besselJ`, `normalCDF`)
25
25
  * - Additional ops exposed via the typed functions layer
26
26
  */
27
- type OpName = 'add' | 'subtract' | 'multiply' | 'divide' | 'pow' | 'scale' | 'abs' | 'negate' | 'sqrt' | 'square' | 'exp' | 'log' | 'sin' | 'cos' | 'tan' | 'sign' | 'tensordot' | 'sum' | 'mean' | 'variance' | 'norm' | 'histogram' | 'dot' | 'distance' | 'parallelStatProd' | 'matmul' | 'outer' | 'matvec' | 'transpose' | 'matrixPower' | 'characteristicPolynomial' | 'parallelFFT' | 'parallelConv' | 'spectrogram' | 'fft2d' | 'distanceMatrix' | 'erfc' | 'besselJ' | 'normalCDF' | 'chiSquareTest' | 'kolmogorovSmirnovTest' | 'mannWhitneyTest' | 'shapiroWilkTest' | 'integrateChunk' | 'sampleChunk';
27
+ type OpName = 'add' | 'subtract' | 'multiply' | 'divide' | 'pow' | 'scale' | 'abs' | 'negate' | 'sqrt' | 'square' | 'bitAnd' | 'bitOr' | 'bitXor' | 'bitNot' | 'leftShift' | 'rightArithShift' | 'rightLogShift' | 'exp' | 'log' | 'sin' | 'cos' | 'tan' | 'sign' | 'tensordot' | 'sum' | 'mean' | 'min' | 'max' | 'minMax' | 'variance' | 'std' | 'norm' | 'histogram' | 'dot' | 'prod' | 'distance' | 'parallelStatProd' | 'matmul' | 'outer' | 'matvec' | 'transpose' | 'matrixPower' | 'characteristicPolynomial' | 'parallelFFT' | 'parallelConv' | 'spectrogram' | 'fft2d' | 'distanceMatrix' | 'erfc' | 'besselJ' | 'normalCDF' | 'chiSquareTest' | 'kolmogorovSmirnovTest' | 'mannWhitneyTest' | 'shapiroWilkTest' | 'integrateChunk' | 'sampleChunk';
28
28
  /**
29
29
  * Per-op threshold override. The value is the minimum element count required
30
30
  * before parallelizing the named operation. Special string aliases:
@@ -148,6 +148,15 @@ declare class ComputePool {
148
148
  * backward-compatibility with existing callers that don't know the op name).
149
149
  */
150
150
  shouldParallelize(elementCount: number, op?: OpName): boolean;
151
+ /**
152
+ * Wrap a sequentially-computed reduction result in the `ParallelResult`
153
+ * envelope. Used by the reduction methods below so that ops whose threshold
154
+ * says "don't parallelize at this size" (notably the memory-bound reductions
155
+ * pinned to `'never'`) compute inline instead of paying a worker round-trip —
156
+ * previously every reduction dispatched to the pool unconditionally, ignoring
157
+ * its threshold.
158
+ */
159
+ private seqResult;
151
160
  /**
152
161
  * Execute a method in the worker pool
153
162
  */
package/dist/index.js CHANGED
@@ -346,6 +346,13 @@ var DEFAULT_THRESHOLD_BY_OP = {
346
346
  cos: "never",
347
347
  tan: "never",
348
348
  sign: "never",
349
+ // Added 2026-07-02 (WS-2): these element-wise ops were absent from the map and
350
+ // silently defaulted to the 50 000 global threshold — an inconsistency with their
351
+ // siblings above (surfaced by parallel-pairing.md). Benchmarked (tools/benchmark/
352
+ // parallel/run.ts sqrt square) — memory-bound, speedup 0.12–0.65×, no persistent
353
+ // break-even → 'never', matching the class.
354
+ sqrt: "never",
355
+ square: "never",
349
356
  // tensor contraction: parallelize when contracted-axis volume >= 8K
350
357
  tensordot: 8192,
351
358
  // reductions: overhead dominates
@@ -354,6 +361,60 @@ var DEFAULT_THRESHOLD_BY_OP = {
354
361
  variance: "never",
355
362
  parallelStatProd: "never",
356
363
  normalCDF: "never",
364
+ // Added 2026-07-02 (WS-2): reductions that were absent from the map and defaulted
365
+ // to the global threshold; benchmarked memory-bound (0.09–1.19×, occasional noise
366
+ // spikes but no persistent win) → 'never'.
367
+ norm: "never",
368
+ dot: "never",
369
+ // Added 2026-07-02 (WS-2): `min`/`max` are now `OpName`s (added to the union) so
370
+ // they're tunable. Same memory-bound reduction profile as sum/mean/norm above →
371
+ // 'never'. Their `functions/` call sites previously dispatched to the worker pool
372
+ // unconditionally (no `shouldParallelize` gate), paying dispatch overhead on every
373
+ // call; they now respect this threshold (sequential) with a `shouldParallelize` gate.
374
+ min: "never",
375
+ max: "never",
376
+ // Added 2026-07-03 (WS-2, DGT sweep): the remaining memory-bound reductions that
377
+ // defaulted to the 50 000 global threshold and dispatched unconditionally at every
378
+ // `functions/` call site. Same class as sum/mean/min/max/norm/dot → 'never'. Now
379
+ // gated INSIDE the ComputePool reduction methods (inline sequential fallback), so
380
+ // ALL call sites honor the threshold without per-site edits. `std` derives from
381
+ // `variance`; `min`/`max` derive from `minMax`.
382
+ minMax: "never",
383
+ std: "never",
384
+ prod: "never",
385
+ distance: "never",
386
+ // Added 2026-07-04 (WS-2 completion): the five OpNames that were absent from
387
+ // this map and silently rode the untested 50 000 global default. Benchmarked
388
+ // (tools/benchmarks/ws2-missing-ops.mjs, medians of 9 interleaved reps):
389
+ // histogram 0.23-0.56x, transpose 0.01-0.28x, matvec 0.00-0.05x, outer
390
+ // 0.01-0.50x at every size up to 4.2M elements - memory-bound / copy-dominated,
391
+ // no break-even -> 'never'. Their ComputePool methods previously dispatched to
392
+ // the worker pool UNCONDITIONALLY (no shouldParallelize gate); they now gate
393
+ // with an inline sequential fallback like the reductions above.
394
+ histogram: "never",
395
+ transpose: "never",
396
+ matvec: "never",
397
+ outer: "never",
398
+ // integrateFanOut loses ~50-100x for cheap integrands (0.01-0.02x at 128-32768
399
+ // evals). The fan-out is CALLER-OPT-IN (integration.ts dispatches only when the
400
+ // caller passes workerCount > 1), so this entry documents the default posture;
401
+ // no shouldParallelize call consults it. Expensive (ms-scale) integrands are the
402
+ // one case where the fan-out pays off.
403
+ integrateChunk: "never",
404
+ // Added 2026-07-05 (WS-2 addendum): the bitwise family gated via a NAMELESS
405
+ // shouldParallelize(len) -> the untested global 50k. Benchmarked
406
+ // (tools/benchmarks/ws2-bitwise-ops.mjs, medians of 9 interleaved reps):
407
+ // bitAnd 0.07-0.15x, bitNot 0.06-0.09x, leftShift 0.04-0.07x at every size up
408
+ // to 4M elements - Int32Array element-wise is maximally memory-bound; the
409
+ // worker path loses 7-25x. All seven -> 'never'; the methods now pass their
410
+ // op name so these entries actually govern.
411
+ bitAnd: "never",
412
+ bitOr: "never",
413
+ bitXor: "never",
414
+ bitNot: "never",
415
+ leftShift: "never",
416
+ rightArithShift: "never",
417
+ rightLogShift: "never",
357
418
  // signal: overhead dominates or break-even never reached
358
419
  parallelFFT: "never",
359
420
  parallelConv: "never",
@@ -525,6 +586,17 @@ var ComputePool = class {
525
586
  }
526
587
  return this.workerPool.shouldParallelize(elementCount);
527
588
  }
589
+ /**
590
+ * Wrap a sequentially-computed reduction result in the `ParallelResult`
591
+ * envelope. Used by the reduction methods below so that ops whose threshold
592
+ * says "don't parallelize at this size" (notably the memory-bound reductions
593
+ * pinned to `'never'`) compute inline instead of paying a worker round-trip —
594
+ * previously every reduction dispatched to the pool unconditionally, ignoring
595
+ * its threshold.
596
+ */
597
+ seqResult(result) {
598
+ return { result, duration: 0, chunks: 1, parallelized: false };
599
+ }
528
600
  /**
529
601
  * Execute a method in the worker pool
530
602
  */
@@ -541,22 +613,35 @@ var ComputePool = class {
541
613
  * Parallel sum of array elements
542
614
  */
543
615
  async sum(data) {
544
- const result = await this.workerPool.sum(data);
545
- return toParallelResult(result);
616
+ if (!this.shouldParallelize(data.length, "sum")) {
617
+ let s = 0;
618
+ for (let i = 0; i < data.length; i++) s += data[i];
619
+ return this.seqResult(s);
620
+ }
621
+ return toParallelResult(await this.workerPool.sum(data));
546
622
  }
547
623
  /**
548
624
  * Parallel product of array elements
549
625
  */
550
626
  async prod(data) {
551
- const result = await this.workerPool.prod(data);
552
- return toParallelResult(result);
627
+ if (!this.shouldParallelize(data.length, "prod")) {
628
+ let p = 1;
629
+ for (let i = 0; i < data.length; i++) p *= data[i];
630
+ return this.seqResult(p);
631
+ }
632
+ return toParallelResult(await this.workerPool.prod(data));
553
633
  }
554
634
  /**
555
635
  * Parallel dot product
556
636
  */
557
637
  async dot(a, b) {
558
- const result = await this.workerPool.dot(a, b);
559
- return toParallelResult(result);
638
+ if (!this.shouldParallelize(a.length, "dot")) {
639
+ if (a.length !== b.length) throw new Error("dot: vectors must have the same length");
640
+ let s = 0;
641
+ for (let i = 0; i < a.length; i++) s += a[i] * b[i];
642
+ return this.seqResult(s);
643
+ }
644
+ return toParallelResult(await this.workerPool.dot(a, b));
560
645
  }
561
646
  /**
562
647
  * Parallel element-wise operation
@@ -589,6 +674,16 @@ var ComputePool = class {
589
674
  * Parallel matrix transpose
590
675
  */
591
676
  async transpose(data, rows, cols) {
677
+ if (!this.shouldParallelize(data.length, "transpose")) {
678
+ if (data.length !== rows * cols) {
679
+ throw new Error(`transpose: data length ${data.length} !== rows*cols (${rows * cols})`);
680
+ }
681
+ const out = new Float64Array(rows * cols);
682
+ for (let i = 0; i < rows; i++) {
683
+ for (let j = 0; j < cols; j++) out[j * rows + i] = data[i * cols + j];
684
+ }
685
+ return this.seqResult(out);
686
+ }
592
687
  const result = await this.workerPool.transpose(data, rows, cols);
593
688
  return toParallelResult(result);
594
689
  }
@@ -620,34 +715,89 @@ var ComputePool = class {
620
715
  * Find min and max values in parallel
621
716
  */
622
717
  async minMax(data) {
623
- const result = await this.workerPool.minMax(data);
624
- return toParallelResult(result);
718
+ if (!this.shouldParallelize(data.length, "minMax") && !this.shouldParallelize(data.length, "min")) {
719
+ let min = Infinity;
720
+ let max = -Infinity;
721
+ let minIdx = -1;
722
+ let maxIdx = -1;
723
+ for (let i = 0; i < data.length; i++) {
724
+ if (data[i] < min) {
725
+ min = data[i];
726
+ minIdx = i;
727
+ }
728
+ if (data[i] > max) {
729
+ max = data[i];
730
+ maxIdx = i;
731
+ }
732
+ }
733
+ return this.seqResult({ min, max, minIdx, maxIdx });
734
+ }
735
+ return toParallelResult(await this.workerPool.minMax(data));
625
736
  }
626
737
  /**
627
738
  * Compute variance, mean, and standard deviation in parallel
628
739
  */
629
740
  async variance(data) {
630
- const result = await this.workerPool.variance(data);
631
- return toParallelResult(result);
741
+ if (!this.shouldParallelize(data.length, "variance")) {
742
+ const n = data.length;
743
+ let mean = 0;
744
+ for (let i = 0; i < n; i++) mean += data[i];
745
+ mean = n > 0 ? mean / n : 0;
746
+ let acc = 0;
747
+ for (let i = 0; i < n; i++) acc += (data[i] - mean) ** 2;
748
+ const variance = n > 0 ? acc / n : 0;
749
+ return this.seqResult({ mean, variance, std: Math.sqrt(variance) });
750
+ }
751
+ return toParallelResult(await this.workerPool.variance(data));
632
752
  }
633
753
  /**
634
754
  * Compute norm (Euclidean length) in parallel
635
755
  */
636
756
  async norm(data) {
637
- const result = await this.workerPool.norm(data);
638
- return toParallelResult(result);
757
+ if (!this.shouldParallelize(data.length, "norm")) {
758
+ let s = 0;
759
+ for (let i = 0; i < data.length; i++) s += data[i] * data[i];
760
+ return this.seqResult(Math.sqrt(s));
761
+ }
762
+ return toParallelResult(await this.workerPool.norm(data));
639
763
  }
640
764
  /**
641
765
  * Compute Euclidean distance in parallel
642
766
  */
643
767
  async distance(a, b) {
644
- const result = await this.workerPool.distance(a, b);
645
- return toParallelResult(result);
768
+ if (!this.shouldParallelize(a.length, "distance")) {
769
+ if (a.length !== b.length) throw new Error("distance: vectors must have the same length");
770
+ let s = 0;
771
+ for (let i = 0; i < a.length; i++) s += (a[i] - b[i]) ** 2;
772
+ return this.seqResult(Math.sqrt(s));
773
+ }
774
+ return toParallelResult(await this.workerPool.distance(a, b));
646
775
  }
647
776
  /**
648
777
  * Compute histogram in parallel
649
778
  */
650
779
  async histogram(data, bins, min, max) {
780
+ if (!this.shouldParallelize(data.length, "histogram")) {
781
+ let lo = min;
782
+ let hi = max;
783
+ if (lo === void 0 || hi === void 0) {
784
+ lo = Infinity;
785
+ hi = -Infinity;
786
+ for (let i = 0; i < data.length; i++) {
787
+ if (data[i] < lo) lo = data[i];
788
+ if (data[i] > hi) hi = data[i];
789
+ }
790
+ }
791
+ const counts = new Array(bins).fill(0);
792
+ const width = (hi - lo) / bins || 1;
793
+ for (let i = 0; i < data.length; i++) {
794
+ let b = Math.floor((data[i] - lo) / width);
795
+ if (b >= bins) b = bins - 1;
796
+ if (b < 0) b = 0;
797
+ counts[b]++;
798
+ }
799
+ return this.seqResult(counts);
800
+ }
651
801
  const result = await this.workerPool.histogram(data, bins, min, max);
652
802
  return toParallelResult(result);
653
803
  }
@@ -857,6 +1007,21 @@ var ComputePool = class {
857
1007
  * Parallel matrix-vector multiplication
858
1008
  */
859
1009
  async matvec(matrix, rows, cols, vector) {
1010
+ if (!this.shouldParallelize(matrix.length, "matvec")) {
1011
+ if (matrix.length !== rows * cols) {
1012
+ throw new Error(`matvec: matrix length ${matrix.length} !== rows*cols (${rows * cols})`);
1013
+ }
1014
+ if (vector.length !== cols) {
1015
+ throw new Error(`matvec: vector length ${vector.length} !== cols (${cols})`);
1016
+ }
1017
+ const out = new Float64Array(rows);
1018
+ for (let i = 0; i < rows; i++) {
1019
+ let s = 0;
1020
+ for (let j = 0; j < cols; j++) s += matrix[i * cols + j] * vector[j];
1021
+ out[i] = s;
1022
+ }
1023
+ return this.seqResult(out);
1024
+ }
860
1025
  const result = await this.workerPool.matvec(matrix, rows, cols, vector);
861
1026
  return toParallelResult(result);
862
1027
  }
@@ -864,6 +1029,13 @@ var ComputePool = class {
864
1029
  * Parallel outer product
865
1030
  */
866
1031
  async outer(a, b) {
1032
+ if (!this.shouldParallelize(a.length * b.length, "outer")) {
1033
+ const out = new Float64Array(a.length * b.length);
1034
+ for (let i = 0; i < a.length; i++) {
1035
+ for (let j = 0; j < b.length; j++) out[i * b.length + j] = a[i] * b[j];
1036
+ }
1037
+ return this.seqResult(out);
1038
+ }
867
1039
  const result = await this.workerPool.outer(a, b);
868
1040
  return toParallelResult(result);
869
1041
  }
@@ -1123,7 +1295,7 @@ var ComputePool = class {
1123
1295
  if (a.length !== b.length) {
1124
1296
  throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1125
1297
  }
1126
- if (this.shouldParallelize(a.length)) {
1298
+ if (this.shouldParallelize(a.length, "bitAnd")) {
1127
1299
  return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitAnd"));
1128
1300
  }
1129
1301
  const start = performance.now();
@@ -1142,7 +1314,7 @@ var ComputePool = class {
1142
1314
  if (a.length !== b.length) {
1143
1315
  throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1144
1316
  }
1145
- if (this.shouldParallelize(a.length)) {
1317
+ if (this.shouldParallelize(a.length, "bitOr")) {
1146
1318
  return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitOr"));
1147
1319
  }
1148
1320
  const start = performance.now();
@@ -1161,7 +1333,7 @@ var ComputePool = class {
1161
1333
  if (a.length !== b.length) {
1162
1334
  throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1163
1335
  }
1164
- if (this.shouldParallelize(a.length)) {
1336
+ if (this.shouldParallelize(a.length, "bitXor")) {
1165
1337
  return toParallelResult(await this.workerPool.bitwiseBinary(a, b, "bitXor"));
1166
1338
  }
1167
1339
  const start = performance.now();
@@ -1177,7 +1349,7 @@ var ComputePool = class {
1177
1349
  * Unary bitwise NOT on an `Int32Array`.
1178
1350
  */
1179
1351
  async bitNot(a) {
1180
- if (this.shouldParallelize(a.length)) {
1352
+ if (this.shouldParallelize(a.length, "bitNot")) {
1181
1353
  return toParallelResult(await this.workerPool.bitwiseNot(a));
1182
1354
  }
1183
1355
  const start = performance.now();
@@ -1197,7 +1369,7 @@ var ComputePool = class {
1197
1369
  if (typeof b !== "number" && a.length !== b.length) {
1198
1370
  throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1199
1371
  }
1200
- if (this.shouldParallelize(a.length)) {
1372
+ if (this.shouldParallelize(a.length, "leftShift")) {
1201
1373
  const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "leftShift") : await this.workerPool.bitwiseBinary(a, b, "leftShift");
1202
1374
  return toParallelResult(result2);
1203
1375
  }
@@ -1217,7 +1389,7 @@ var ComputePool = class {
1217
1389
  if (typeof b !== "number" && a.length !== b.length) {
1218
1390
  throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1219
1391
  }
1220
- if (this.shouldParallelize(a.length)) {
1392
+ if (this.shouldParallelize(a.length, "rightArithShift")) {
1221
1393
  const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightArithShift") : await this.workerPool.bitwiseBinary(a, b, "rightArithShift");
1222
1394
  return toParallelResult(result2);
1223
1395
  }
@@ -1239,7 +1411,7 @@ var ComputePool = class {
1239
1411
  if (typeof b !== "number" && a.length !== b.length) {
1240
1412
  throw new Error(`Array lengths must match: ${a.length} vs ${b.length}`);
1241
1413
  }
1242
- if (this.shouldParallelize(a.length)) {
1414
+ if (this.shouldParallelize(a.length, "rightLogShift")) {
1243
1415
  const result2 = typeof b === "number" ? await this.workerPool.bitwiseScalar(a, b, "rightLogShift") : await this.workerPool.bitwiseBinary(a, b, "rightLogShift");
1244
1416
  return toParallelResult(result2);
1245
1417
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielsimonjr/mathts-parallel",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "WebWorker parallelization for MathTS computations",
5
5
  "author": "Daniel Simon Jr.",
6
6
  "license": "MIT",