@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 +111 -1
- package/dist/index.js +129 -0
- package/package.json +1 -1
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
|
};
|