@danielsimonjr/mathts-core 0.6.0 → 0.8.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.d.ts CHANGED
@@ -2445,6 +2445,116 @@ declare const math: {
2445
2445
  configure: (config: Partial<MathTSConfig>) => void;
2446
2446
  };
2447
2447
 
2448
+ /**
2449
+ * Numerically stable reduction primitives.
2450
+ *
2451
+ * Floating-point addition is not associative, so *how* you accumulate decides how much error you
2452
+ * carry. These are the algorithms the established numerical stack uses, and the reason it is
2453
+ * trusted.
2454
+ *
2455
+ * Measured on this repo's own reductions before these existed — 1e6 copies of `0.1`, exact answer
2456
+ * 100000:
2457
+ *
2458
+ * | accumulation | relative error |
2459
+ * | ------------------------------- | -------------- |
2460
+ * | naive `s += x` | **1.3e-11** |
2461
+ * | pairwise (this, = NumPy's algo) | **2.9e-16** |
2462
+ * | Neumaier compensated (`fsum`) | **0** (exact) |
2463
+ *
2464
+ * NumPy 2.3.4 reports 2.9e-16 on the same input — we are now at parity. Before this, we were
2465
+ * ~46,000× worse than NumPy on a bog-standard `sum`, and `mean`, `std`, `var` and every statistic
2466
+ * inherit that error.
2467
+ *
2468
+ * @packageDocumentation
2469
+ */
2470
+ /**
2471
+ * Sum with **pairwise (cascade) summation** — the algorithm behind `np.sum`.
2472
+ *
2473
+ * Naive accumulation lets the running total grow large while the addends stay small, so each
2474
+ * addition rounds off a little more of the total: error grows as **O(n)·ε**. Pairwise summation
2475
+ * adds numbers of *comparable magnitude* by recursively halving the range, so error grows as
2476
+ * **O(log n)·ε** — for n = 10⁶ that is the difference between ~1e-11 and ~1e-16.
2477
+ *
2478
+ * It costs the same number of additions as the naive loop. There is no speed/accuracy trade here;
2479
+ * the naive version is simply worse.
2480
+ *
2481
+ * Not exact: for that, use {@link neumaierSum}. Pairwise is the right default because it is free.
2482
+ */
2483
+ declare function pairwiseSum(xs: ArrayLike<number>, start?: number, end?: number): number;
2484
+ /**
2485
+ * Sum with **Neumaier compensation** — correctly rounded for practical purposes, the equivalent
2486
+ * of Python's `math.fsum`.
2487
+ *
2488
+ * Tracks the low-order bits that each addition throws away and folds them back in at the end. It
2489
+ * recovers information that pairwise summation cannot: `[1e16, 1, -1e16]` sums to **1** here,
2490
+ * while both naive and pairwise (and `np.sum`) give **0**, because the `1` is annihilated the
2491
+ * moment it meets `1e16`.
2492
+ *
2493
+ * ~2-4× slower than {@link pairwiseSum}, so it is opt-in rather than the default. Reach for it
2494
+ * when catastrophic cancellation is possible: near-zero results from large terms, long-running
2495
+ * accumulators, conservation checks.
2496
+ *
2497
+ * (Neumaier's variant, not classic Kahan: Kahan loses the compensation when the *addend* is
2498
+ * larger than the running sum, which is precisely the `1e16` case.)
2499
+ */
2500
+ declare function neumaierSum(xs: ArrayLike<number>): number;
2501
+ /**
2502
+ * Euclidean (2-)norm that does **not** overflow or underflow — BLAS's `dnrm2` scaling.
2503
+ *
2504
+ * The obvious `sqrt(Σxᵢ²)` squares before it adds, so it dies well inside the representable
2505
+ * range: `‖[1e200, 1e200, 1e200, 1e200]‖` overflows to `Infinity` (the true answer, 2e200, is
2506
+ * perfectly representable), and `‖[1e-200] × 4‖` **flushes to 0** — a silent wrong answer, which
2507
+ * is worse.
2508
+ *
2509
+ * NumPy has this bug too: `np.linalg.norm([1e200]*4)` returns `inf` with an overflow warning.
2510
+ * LAPACK does not, and neither do we.
2511
+ *
2512
+ * The fix is to carry a running scale: keep the largest magnitude seen out of the sum of squares,
2513
+ * so the accumulator only ever holds ratios in [0, 1].
2514
+ */
2515
+ declare function norm2(xs: ArrayLike<number>): number;
2516
+ /**
2517
+ * Dot product `Σ aᵢ·bᵢ` with **pairwise (cascade) summation** of the products — the accuracy of
2518
+ * {@link pairwiseSum} applied to a dot without materialising the product array.
2519
+ *
2520
+ * A naive `s += a[i]*b[i]` loop carries the same **O(n)·ε** error as a naive sum; measured against
2521
+ * an exact reference on an ill-conditioned dot (large mean × small factor, n = 10⁶) it is ~18×
2522
+ * worse than `np.dot`, which sums pairwise. This closes that gap — and, like {@link pairwiseSum},
2523
+ * costs the same number of flops as the naive loop.
2524
+ *
2525
+ * The two ranges are assumed the same length; callers guard that. Reads only `[start, end)`.
2526
+ */
2527
+ declare function pairwiseDot(a: ArrayLike<number>, b: ArrayLike<number>, start?: number, end?: number): number;
2528
+ /**
2529
+ * Euclidean distance `‖a − b‖₂` that does **not** overflow or underflow — {@link norm2}'s BLAS
2530
+ * `dnrm2` scaling applied to the elementwise difference.
2531
+ *
2532
+ * `distance` is just a 2-norm, so the obvious `sqrt(Σ(aᵢ−bᵢ)²)` inherits norm's pathology: it
2533
+ * squares before it adds, overflowing to `Infinity` and — the dangerous case — **flushing to a
2534
+ * silent 0** for small differences well inside the representable range. NumPy's `linalg.norm` has
2535
+ * the same bug; scaling on the largest *difference* seen avoids both. The scale tracks the residual
2536
+ * `a − b`, not the inputs, so it stays accurate even when `a` and `b` are individually huge.
2537
+ *
2538
+ * The two ranges are assumed the same length; callers guard that.
2539
+ */
2540
+ declare function scaledDistance(a: ArrayLike<number>, b: ArrayLike<number>): number;
2541
+ /**
2542
+ * Cumulative sum with **Neumaier compensation** — each prefix total is written to `out[i]` carrying
2543
+ * the low-order bits a naive running sum throws away.
2544
+ *
2545
+ * A cumulative sum is an inherently sequential prefix scan, so pairwise summation does not apply:
2546
+ * you need every intermediate total, not just the final one. `np.cumsum` therefore accumulates
2547
+ * naively and its tail drifts by **O(n)·ε** (relErr ~1.3e-11 over 10⁶ terms). Carrying a running
2548
+ * compensation costs a few extra flops per element, no extra memory, and makes every prefix exact
2549
+ * for practical purposes — a strict improvement over NumPy where accumulated drift matters (e.g.
2550
+ * integrating a signal).
2551
+ *
2552
+ * Writes `out[i]` for `i` in `[0, xs.length)`; `out` may be a `number[]` or a `Float64Array`.
2553
+ */
2554
+ declare function neumaierCumsum(xs: ArrayLike<number>, out: {
2555
+ [index: number]: number;
2556
+ }): void;
2557
+
2448
2558
  /**
2449
2559
  * @danielsimonjr/mathts-core - Core types and utilities for MathTS
2450
2560
  * @packageDocumentation
@@ -2452,4 +2562,4 @@ declare const math: {
2452
2562
 
2453
2563
  declare const VERSION = "0.1.0";
2454
2564
 
2455
- export { ALL_UNITS, BASE_UNITS, BEST_PREFIXES, BIGNUMBER_E, BIGNUMBER_LN10, BIGNUMBER_LN2, BIGNUMBER_NEG_ONE, BIGNUMBER_ONE, BIGNUMBER_PI, BIGNUMBER_TEN, BIGNUMBER_ZERO, type BackendType, BigNumber, type BigNumberConfig, COMPLEX_NEG_ONE, COMPLEX_ONE, COMPLEX_ZERO, Complex, type ConversionDef, DEFAULT_CONFIG, DERIVED_UNITS, DIMENSIONLESS, DUAL_UNARY_RULES, DimensionMismatchError, type Dimensions, Dual, type DualUnaryRule, type DualUnaryRuleName, E, FRACTION_HALF, FRACTION_NEG_ONE, FRACTION_ONE, FRACTION_QUARTER, FRACTION_THIRD, FRACTION_ZERO, type FactoryDependencies, type FactoryFunction, type FactoryImport, Fraction, FunctionRegistry, I, type IBigNumber, type IComplex, type IFraction, type IMatrix, LN10, LN2, LOG10E, LOG2E, MATHTS_CONVERSIONS, MATHTS_TYPES, type MathTSConfig, type MathTSValue, type MatrixBackend, type MatrixDimensions, type NumericScalar, type NumericType, PHI, PI, Range, type RangeForEachCallback, type RangeFormatOptions, type RangeJSON, type RangeMapCallback, type RoundingMode, SI_PREFIXES, SQRT1_2, SQRT2, type Scalar, TAU, type TypeDef, TypeRegistry, UNIT_ALIASES, type UnitDef, UnitParseError, VERSION, abs, addScalar, createFactory, createMathTSTyped, createRangeClass, createTypedFunction, dim, divideScalar, equal, fix, getPrefix, getUnitDef, initTypedWasm, isArray, isBigInt, isBigNumber, isBoolean, isComplex, isDenseMatrix, isDual, isFraction, isFunction, isMatrix, isNull, isNumber, isNumeric, isObject, isSparseMatrix, isString, isTypedWasmAvailable, isUndefined, isUnit, math, mathTyped, multiplyScalar, number, pow, registerNativeTypes, registry, round, subtractScalar };
2565
+ export { ALL_UNITS, BASE_UNITS, BEST_PREFIXES, BIGNUMBER_E, BIGNUMBER_LN10, BIGNUMBER_LN2, BIGNUMBER_NEG_ONE, BIGNUMBER_ONE, BIGNUMBER_PI, BIGNUMBER_TEN, BIGNUMBER_ZERO, type BackendType, BigNumber, type BigNumberConfig, COMPLEX_NEG_ONE, COMPLEX_ONE, COMPLEX_ZERO, Complex, type ConversionDef, DEFAULT_CONFIG, DERIVED_UNITS, DIMENSIONLESS, DUAL_UNARY_RULES, DimensionMismatchError, type Dimensions, Dual, type DualUnaryRule, type DualUnaryRuleName, E, FRACTION_HALF, FRACTION_NEG_ONE, FRACTION_ONE, FRACTION_QUARTER, FRACTION_THIRD, FRACTION_ZERO, type FactoryDependencies, type FactoryFunction, type FactoryImport, Fraction, FunctionRegistry, I, type IBigNumber, type IComplex, type IFraction, type IMatrix, LN10, LN2, LOG10E, LOG2E, MATHTS_CONVERSIONS, MATHTS_TYPES, type MathTSConfig, type MathTSValue, type MatrixBackend, type MatrixDimensions, type NumericScalar, type NumericType, PHI, PI, Range, type RangeForEachCallback, type RangeFormatOptions, type RangeJSON, type RangeMapCallback, type RoundingMode, SI_PREFIXES, SQRT1_2, SQRT2, type Scalar, TAU, type TypeDef, TypeRegistry, UNIT_ALIASES, type UnitDef, UnitParseError, VERSION, abs, addScalar, createFactory, createMathTSTyped, createRangeClass, createTypedFunction, dim, divideScalar, equal, fix, getPrefix, getUnitDef, initTypedWasm, isArray, isBigInt, isBigNumber, isBoolean, isComplex, isDenseMatrix, isDual, isFraction, isFunction, isMatrix, isNull, isNumber, isNumeric, isObject, isSparseMatrix, isString, isTypedWasmAvailable, isUndefined, isUnit, math, mathTyped, multiplyScalar, neumaierCumsum, neumaierSum, norm2, number, pairwiseDot, pairwiseSum, pow, registerNativeTypes, registry, round, scaledDistance, subtractScalar };
package/dist/index.js CHANGED
@@ -1186,6 +1186,129 @@ var math = {
1186
1186
  configure: (config) => registry.updateConfig(config)
1187
1187
  };
1188
1188
 
1189
+ // src/numeric/stable.ts
1190
+ var PAIRWISE_BLOCK = 128;
1191
+ function pairwiseSum(xs, start = 0, end = xs.length) {
1192
+ const n = end - start;
1193
+ if (n <= PAIRWISE_BLOCK) {
1194
+ let s0 = 0;
1195
+ let s1 = 0;
1196
+ let s2 = 0;
1197
+ let s3 = 0;
1198
+ let s4 = 0;
1199
+ let s5 = 0;
1200
+ let s6 = 0;
1201
+ let s7 = 0;
1202
+ let i = start;
1203
+ const limit = start + (n - n % 8);
1204
+ for (; i < limit; i += 8) {
1205
+ s0 += xs[i];
1206
+ s1 += xs[i + 1];
1207
+ s2 += xs[i + 2];
1208
+ s3 += xs[i + 3];
1209
+ s4 += xs[i + 4];
1210
+ s5 += xs[i + 5];
1211
+ s6 += xs[i + 6];
1212
+ s7 += xs[i + 7];
1213
+ }
1214
+ let tail = 0;
1215
+ for (; i < end; i++) tail += xs[i];
1216
+ return s0 + s1 + (s2 + s3) + (s4 + s5 + (s6 + s7)) + tail;
1217
+ }
1218
+ const half = (n >> 1) - (n >> 1) % 8;
1219
+ return pairwiseSum(xs, start, start + half) + pairwiseSum(xs, start + half, end);
1220
+ }
1221
+ function neumaierSum(xs) {
1222
+ let sum = 0;
1223
+ let c = 0;
1224
+ for (let i = 0; i < xs.length; i++) {
1225
+ const x = xs[i];
1226
+ const t = sum + x;
1227
+ c += Math.abs(sum) >= Math.abs(x) ? sum - t + x : x - t + sum;
1228
+ sum = t;
1229
+ }
1230
+ return sum + c;
1231
+ }
1232
+ function norm2(xs) {
1233
+ let scale = 0;
1234
+ let ssq = 1;
1235
+ for (let i = 0; i < xs.length; i++) {
1236
+ const x = xs[i];
1237
+ if (x === 0) continue;
1238
+ if (Number.isNaN(x)) return NaN;
1239
+ const ax = Math.abs(x);
1240
+ if (scale < ax) {
1241
+ const r = scale / ax;
1242
+ ssq = 1 + ssq * r * r;
1243
+ scale = ax;
1244
+ } else {
1245
+ const r = ax / scale;
1246
+ ssq += r * r;
1247
+ }
1248
+ }
1249
+ return scale === 0 ? 0 : scale * Math.sqrt(ssq);
1250
+ }
1251
+ function pairwiseDot(a, b, start = 0, end = a.length) {
1252
+ const n = end - start;
1253
+ if (n <= PAIRWISE_BLOCK) {
1254
+ let s0 = 0;
1255
+ let s1 = 0;
1256
+ let s2 = 0;
1257
+ let s3 = 0;
1258
+ let s4 = 0;
1259
+ let s5 = 0;
1260
+ let s6 = 0;
1261
+ let s7 = 0;
1262
+ let i = start;
1263
+ const limit = start + (n - n % 8);
1264
+ for (; i < limit; i += 8) {
1265
+ s0 += a[i] * b[i];
1266
+ s1 += a[i + 1] * b[i + 1];
1267
+ s2 += a[i + 2] * b[i + 2];
1268
+ s3 += a[i + 3] * b[i + 3];
1269
+ s4 += a[i + 4] * b[i + 4];
1270
+ s5 += a[i + 5] * b[i + 5];
1271
+ s6 += a[i + 6] * b[i + 6];
1272
+ s7 += a[i + 7] * b[i + 7];
1273
+ }
1274
+ let tail = 0;
1275
+ for (; i < end; i++) tail += a[i] * b[i];
1276
+ return s0 + s1 + (s2 + s3) + (s4 + s5 + (s6 + s7)) + tail;
1277
+ }
1278
+ const half = (n >> 1) - (n >> 1) % 8;
1279
+ return pairwiseDot(a, b, start, start + half) + pairwiseDot(a, b, start + half, end);
1280
+ }
1281
+ function scaledDistance(a, b) {
1282
+ let scale = 0;
1283
+ let ssq = 1;
1284
+ for (let i = 0; i < a.length; i++) {
1285
+ const d = a[i] - b[i];
1286
+ if (d === 0) continue;
1287
+ if (Number.isNaN(d)) return NaN;
1288
+ const ad = Math.abs(d);
1289
+ if (scale < ad) {
1290
+ const r = scale / ad;
1291
+ ssq = 1 + ssq * r * r;
1292
+ scale = ad;
1293
+ } else {
1294
+ const r = ad / scale;
1295
+ ssq += r * r;
1296
+ }
1297
+ }
1298
+ return scale === 0 ? 0 : scale * Math.sqrt(ssq);
1299
+ }
1300
+ function neumaierCumsum(xs, out) {
1301
+ let sum = 0;
1302
+ let c = 0;
1303
+ for (let i = 0; i < xs.length; i++) {
1304
+ const x = xs[i];
1305
+ const t = sum + x;
1306
+ c += Math.abs(sum) >= Math.abs(x) ? sum - t + x : x - t + sum;
1307
+ sum = t;
1308
+ out[i] = sum + c;
1309
+ }
1310
+ }
1311
+
1189
1312
  // src/index.ts
1190
1313
  var VERSION = "0.1.0";
1191
1314
  export {
@@ -1276,11 +1399,17 @@ export {
1276
1399
  math,
1277
1400
  mathTyped,
1278
1401
  multiplyScalar,
1402
+ neumaierCumsum,
1403
+ neumaierSum,
1404
+ norm2,
1279
1405
  number,
1406
+ pairwiseDot,
1407
+ pairwiseSum,
1280
1408
  pow,
1281
1409
  registerNativeTypes,
1282
1410
  registry,
1283
1411
  round,
1412
+ scaledDistance,
1284
1413
  subtractScalar,
1285
1414
  typed
1286
1415
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielsimonjr/mathts-core",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Core types, utilities, and configuration for MathTS",
5
5
  "author": "Daniel Simon Jr.",
6
6
  "license": "MIT",