@danielsimonjr/mathts-parallel 0.3.0 → 0.3.1

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' | '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,46 @@ 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",
357
404
  // signal: overhead dominates or break-even never reached
358
405
  parallelFFT: "never",
359
406
  parallelConv: "never",
@@ -525,6 +572,17 @@ var ComputePool = class {
525
572
  }
526
573
  return this.workerPool.shouldParallelize(elementCount);
527
574
  }
575
+ /**
576
+ * Wrap a sequentially-computed reduction result in the `ParallelResult`
577
+ * envelope. Used by the reduction methods below so that ops whose threshold
578
+ * says "don't parallelize at this size" (notably the memory-bound reductions
579
+ * pinned to `'never'`) compute inline instead of paying a worker round-trip —
580
+ * previously every reduction dispatched to the pool unconditionally, ignoring
581
+ * its threshold.
582
+ */
583
+ seqResult(result) {
584
+ return { result, duration: 0, chunks: 1, parallelized: false };
585
+ }
528
586
  /**
529
587
  * Execute a method in the worker pool
530
588
  */
@@ -541,22 +599,35 @@ var ComputePool = class {
541
599
  * Parallel sum of array elements
542
600
  */
543
601
  async sum(data) {
544
- const result = await this.workerPool.sum(data);
545
- return toParallelResult(result);
602
+ if (!this.shouldParallelize(data.length, "sum")) {
603
+ let s = 0;
604
+ for (let i = 0; i < data.length; i++) s += data[i];
605
+ return this.seqResult(s);
606
+ }
607
+ return toParallelResult(await this.workerPool.sum(data));
546
608
  }
547
609
  /**
548
610
  * Parallel product of array elements
549
611
  */
550
612
  async prod(data) {
551
- const result = await this.workerPool.prod(data);
552
- return toParallelResult(result);
613
+ if (!this.shouldParallelize(data.length, "prod")) {
614
+ let p = 1;
615
+ for (let i = 0; i < data.length; i++) p *= data[i];
616
+ return this.seqResult(p);
617
+ }
618
+ return toParallelResult(await this.workerPool.prod(data));
553
619
  }
554
620
  /**
555
621
  * Parallel dot product
556
622
  */
557
623
  async dot(a, b) {
558
- const result = await this.workerPool.dot(a, b);
559
- return toParallelResult(result);
624
+ if (!this.shouldParallelize(a.length, "dot")) {
625
+ if (a.length !== b.length) throw new Error("dot: vectors must have the same length");
626
+ let s = 0;
627
+ for (let i = 0; i < a.length; i++) s += a[i] * b[i];
628
+ return this.seqResult(s);
629
+ }
630
+ return toParallelResult(await this.workerPool.dot(a, b));
560
631
  }
561
632
  /**
562
633
  * Parallel element-wise operation
@@ -589,6 +660,16 @@ var ComputePool = class {
589
660
  * Parallel matrix transpose
590
661
  */
591
662
  async transpose(data, rows, cols) {
663
+ if (!this.shouldParallelize(data.length, "transpose")) {
664
+ if (data.length !== rows * cols) {
665
+ throw new Error(`transpose: data length ${data.length} !== rows*cols (${rows * cols})`);
666
+ }
667
+ const out = new Float64Array(rows * cols);
668
+ for (let i = 0; i < rows; i++) {
669
+ for (let j = 0; j < cols; j++) out[j * rows + i] = data[i * cols + j];
670
+ }
671
+ return this.seqResult(out);
672
+ }
592
673
  const result = await this.workerPool.transpose(data, rows, cols);
593
674
  return toParallelResult(result);
594
675
  }
@@ -620,34 +701,89 @@ var ComputePool = class {
620
701
  * Find min and max values in parallel
621
702
  */
622
703
  async minMax(data) {
623
- const result = await this.workerPool.minMax(data);
624
- return toParallelResult(result);
704
+ if (!this.shouldParallelize(data.length, "minMax") && !this.shouldParallelize(data.length, "min")) {
705
+ let min = Infinity;
706
+ let max = -Infinity;
707
+ let minIdx = -1;
708
+ let maxIdx = -1;
709
+ for (let i = 0; i < data.length; i++) {
710
+ if (data[i] < min) {
711
+ min = data[i];
712
+ minIdx = i;
713
+ }
714
+ if (data[i] > max) {
715
+ max = data[i];
716
+ maxIdx = i;
717
+ }
718
+ }
719
+ return this.seqResult({ min, max, minIdx, maxIdx });
720
+ }
721
+ return toParallelResult(await this.workerPool.minMax(data));
625
722
  }
626
723
  /**
627
724
  * Compute variance, mean, and standard deviation in parallel
628
725
  */
629
726
  async variance(data) {
630
- const result = await this.workerPool.variance(data);
631
- return toParallelResult(result);
727
+ if (!this.shouldParallelize(data.length, "variance")) {
728
+ const n = data.length;
729
+ let mean = 0;
730
+ for (let i = 0; i < n; i++) mean += data[i];
731
+ mean = n > 0 ? mean / n : 0;
732
+ let acc = 0;
733
+ for (let i = 0; i < n; i++) acc += (data[i] - mean) ** 2;
734
+ const variance = n > 0 ? acc / n : 0;
735
+ return this.seqResult({ mean, variance, std: Math.sqrt(variance) });
736
+ }
737
+ return toParallelResult(await this.workerPool.variance(data));
632
738
  }
633
739
  /**
634
740
  * Compute norm (Euclidean length) in parallel
635
741
  */
636
742
  async norm(data) {
637
- const result = await this.workerPool.norm(data);
638
- return toParallelResult(result);
743
+ if (!this.shouldParallelize(data.length, "norm")) {
744
+ let s = 0;
745
+ for (let i = 0; i < data.length; i++) s += data[i] * data[i];
746
+ return this.seqResult(Math.sqrt(s));
747
+ }
748
+ return toParallelResult(await this.workerPool.norm(data));
639
749
  }
640
750
  /**
641
751
  * Compute Euclidean distance in parallel
642
752
  */
643
753
  async distance(a, b) {
644
- const result = await this.workerPool.distance(a, b);
645
- return toParallelResult(result);
754
+ if (!this.shouldParallelize(a.length, "distance")) {
755
+ if (a.length !== b.length) throw new Error("distance: vectors must have the same length");
756
+ let s = 0;
757
+ for (let i = 0; i < a.length; i++) s += (a[i] - b[i]) ** 2;
758
+ return this.seqResult(Math.sqrt(s));
759
+ }
760
+ return toParallelResult(await this.workerPool.distance(a, b));
646
761
  }
647
762
  /**
648
763
  * Compute histogram in parallel
649
764
  */
650
765
  async histogram(data, bins, min, max) {
766
+ if (!this.shouldParallelize(data.length, "histogram")) {
767
+ let lo = min;
768
+ let hi = max;
769
+ if (lo === void 0 || hi === void 0) {
770
+ lo = Infinity;
771
+ hi = -Infinity;
772
+ for (let i = 0; i < data.length; i++) {
773
+ if (data[i] < lo) lo = data[i];
774
+ if (data[i] > hi) hi = data[i];
775
+ }
776
+ }
777
+ const counts = new Array(bins).fill(0);
778
+ const width = (hi - lo) / bins || 1;
779
+ for (let i = 0; i < data.length; i++) {
780
+ let b = Math.floor((data[i] - lo) / width);
781
+ if (b >= bins) b = bins - 1;
782
+ if (b < 0) b = 0;
783
+ counts[b]++;
784
+ }
785
+ return this.seqResult(counts);
786
+ }
651
787
  const result = await this.workerPool.histogram(data, bins, min, max);
652
788
  return toParallelResult(result);
653
789
  }
@@ -857,6 +993,21 @@ var ComputePool = class {
857
993
  * Parallel matrix-vector multiplication
858
994
  */
859
995
  async matvec(matrix, rows, cols, vector) {
996
+ if (!this.shouldParallelize(matrix.length, "matvec")) {
997
+ if (matrix.length !== rows * cols) {
998
+ throw new Error(`matvec: matrix length ${matrix.length} !== rows*cols (${rows * cols})`);
999
+ }
1000
+ if (vector.length !== cols) {
1001
+ throw new Error(`matvec: vector length ${vector.length} !== cols (${cols})`);
1002
+ }
1003
+ const out = new Float64Array(rows);
1004
+ for (let i = 0; i < rows; i++) {
1005
+ let s = 0;
1006
+ for (let j = 0; j < cols; j++) s += matrix[i * cols + j] * vector[j];
1007
+ out[i] = s;
1008
+ }
1009
+ return this.seqResult(out);
1010
+ }
860
1011
  const result = await this.workerPool.matvec(matrix, rows, cols, vector);
861
1012
  return toParallelResult(result);
862
1013
  }
@@ -864,6 +1015,13 @@ var ComputePool = class {
864
1015
  * Parallel outer product
865
1016
  */
866
1017
  async outer(a, b) {
1018
+ if (!this.shouldParallelize(a.length * b.length, "outer")) {
1019
+ const out = new Float64Array(a.length * b.length);
1020
+ for (let i = 0; i < a.length; i++) {
1021
+ for (let j = 0; j < b.length; j++) out[i * b.length + j] = a[i] * b[j];
1022
+ }
1023
+ return this.seqResult(out);
1024
+ }
867
1025
  const result = await this.workerPool.outer(a, b);
868
1026
  return toParallelResult(result);
869
1027
  }
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.1",
4
4
  "description": "WebWorker parallelization for MathTS computations",
5
5
  "author": "Daniel Simon Jr.",
6
6
  "license": "MIT",