@danielsimonjr/mathts-matrix 0.1.13 → 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.d.ts CHANGED
@@ -1125,7 +1125,7 @@ declare function getCachedFeatures(): WasmFeatures | null;
1125
1125
  * WASM Matrix Backend (AssemblyScript)
1126
1126
  *
1127
1127
  * Implements MatrixBackend using the AssemblyScript-compiled WebAssembly
1128
- * module (`lib/wasm/mathts-as.wasm`, ~42 KB). Falls back to JSBackend when
1128
+ * module (`dist/wasm/mathts-as.wasm`, ~65 KB). Falls back to JSBackend when
1129
1129
  * the WASM module is unavailable or for small matrices.
1130
1130
  *
1131
1131
  * ABI:
@@ -1178,7 +1178,7 @@ declare function getCachedFeatures(): WasmFeatures | null;
1178
1178
  interface WASMBackendConfig {
1179
1179
  /** Minimum elements to use WASM (default: 100) */
1180
1180
  minElements?: number;
1181
- /** Path to WASM file (defaults to the AS artifact, `lib/wasm/mathts-as.wasm`) */
1181
+ /** Path to WASM file (defaults to the packaged AS artifact, `dist/wasm/mathts-as.wasm`) */
1182
1182
  wasmPath?: string;
1183
1183
  /** Enable SIMD code paths if the AS module exposes them (currently a no-op
1184
1184
  * — AS export surface does not include `simd*` ops; kept for API stability). */
@@ -1203,11 +1203,7 @@ declare class WASMBackend implements MatrixBackend {
1203
1203
  initialize(): Promise<void>;
1204
1204
  private doInitialize;
1205
1205
  /**
1206
- * Resolve the path to the AssemblyScript artifact (`lib/wasm/mathts-as.wasm`).
1207
- *
1208
- * On Windows, `URL.pathname` returns "/C:/foo/bar.wasm" which fs.readFile
1209
- * interprets as drive-relative ("C:\C:\foo\..."). `fileURLToPath` does the
1210
- * platform-correct conversion.
1206
+ * Resolve the path to the AssemblyScript artifact (`dist/wasm/mathts-as.wasm`).
1211
1207
  */
1212
1208
  private resolveAsWasmPath;
1213
1209
  /**
@@ -2478,18 +2474,17 @@ interface SVDOptions {
2478
2474
  maxIterations?: number;
2479
2475
  /** Convergence tolerance */
2480
2476
  tolerance?: number;
2481
- /** Whether to compute full U and V matrices */
2477
+ /**
2478
+ * Complete the thin factor to a square orthonormal basis (U → m×m for tall
2479
+ * inputs, V → n×n for wide ones), numpy `full_matrices=True` style, so the
2480
+ * rectangular-Σ reconstruction `A = U·Σ·Vᵀ` holds. Default `false` = thin
2481
+ * factors (the library's long-standing behavior; the option was previously
2482
+ * accepted but ignored — B-4).
2483
+ */
2482
2484
  fullMatrices?: boolean;
2483
2485
  /** Threshold for rank determination */
2484
2486
  rankTolerance?: number;
2485
2487
  }
2486
- /**
2487
- * Compute SVD of a matrix
2488
- *
2489
- * @param matrix - Input matrix (m x n)
2490
- * @param options - Computation options
2491
- * @returns SVD decomposition
2492
- */
2493
2488
  declare function svd(matrix: number[][] | Float64Array, options?: SVDOptions): SVDResult;
2494
2489
  /**
2495
2490
  * Compute only singular values (faster than full SVD)
package/dist/index.js CHANGED
@@ -2481,22 +2481,17 @@ var WASMBackend = class {
2481
2481
  }
2482
2482
  }
2483
2483
  /**
2484
- * Resolve the path to the AssemblyScript artifact (`lib/wasm/mathts-as.wasm`).
2485
- *
2486
- * On Windows, `URL.pathname` returns "/C:/foo/bar.wasm" which fs.readFile
2487
- * interprets as drive-relative ("C:\C:\foo\..."). `fileURLToPath` does the
2488
- * platform-correct conversion.
2484
+ * Resolve the path to the AssemblyScript artifact (`dist/wasm/mathts-as.wasm`).
2489
2485
  */
2490
2486
  async resolveAsWasmPath() {
2491
2487
  const isNode = typeof process !== "undefined" && process.versions?.node !== void 0;
2488
+ const { resolvePackagedWasm, defaultWasmLocation } = await import("./resolve-VJX3YTS6.js");
2492
2489
  if (isNode) {
2493
- const { resolvePackagedWasm } = await import("./resolve-JRHDDNVQ.js");
2494
2490
  const found = await resolvePackagedWasm(import.meta.url, "mathts-as.wasm");
2495
2491
  if (found) return found;
2496
- const { fileURLToPath } = await import("url");
2497
- return fileURLToPath(new URL(`../../../lib/wasm/mathts-as.wasm`, import.meta.url));
2492
+ return defaultWasmLocation(import.meta.url, "mathts-as.wasm");
2498
2493
  }
2499
- return new URL(`../../../lib/wasm/mathts-as.wasm`, import.meta.url).href;
2494
+ return defaultWasmLocation(import.meta.url, "mathts-as.wasm", { browser: true });
2500
2495
  }
2501
2496
  /**
2502
2497
  * Compile + instantiate the AssemblyScript WASM artifact. Each
@@ -5483,27 +5478,22 @@ var WasmLoader = class _WasmLoader {
5483
5478
  * (`mathts-as.wasm`); the legacy second toolchain was removed in the
5484
5479
  * WASM-backend migration (complete 2026-06-26).
5485
5480
  *
5486
- * The path is resolved relative to this source file's location so it is
5487
- * CWD-independent. This file lives at:
5488
- * <repo-root>/matrix/src/backends/WasmLoader.ts
5489
- * so the repo root is three directories up, and the artifact is at
5490
- * <repo-root>/lib/wasm/mathts-as.wasm
5491
- *
5492
- * Both branches use `new URL(relative, import.meta.url)` to resolve a
5493
- * file: URL. In Node we convert via `fileURLToPath` (which correctly
5494
- * strips the leading slash on Windows so the drive letter is not
5495
- * doubled); in the browser we keep the full `.href` for fetch().
5481
+ * Resolution is package-relative and CWD-independent: the packaged artifact
5482
+ * (`dist/wasm/`) is preferred via `resolvePackagedWasm`; when absent, the
5483
+ * canonical expected location is returned so the missing-binary warning is
5484
+ * actionable (see `defaultWasmLocation` in wasm/resolve.ts B-3).
5496
5485
  */
5497
5486
  async getDefaultWasmPath() {
5498
5487
  const wasmFile = "mathts-as.wasm";
5499
5488
  if (this.isNode) {
5500
- const { resolvePackagedWasm } = await import("./resolve-JRHDDNVQ.js");
5489
+ const { resolvePackagedWasm } = await import("./resolve-VJX3YTS6.js");
5501
5490
  const found = await resolvePackagedWasm(import.meta.url, wasmFile);
5502
5491
  if (found) return found;
5503
- const { fileURLToPath } = await import("url");
5504
- return fileURLToPath(new URL(`../../../lib/wasm/${wasmFile}`, import.meta.url));
5492
+ const { defaultWasmLocation: defaultWasmLocation2 } = await import("./resolve-VJX3YTS6.js");
5493
+ return defaultWasmLocation2(import.meta.url, wasmFile);
5505
5494
  }
5506
- return new URL(`../../../lib/wasm/${wasmFile}`, import.meta.url).href;
5495
+ const { defaultWasmLocation } = await import("./resolve-VJX3YTS6.js");
5496
+ return defaultWasmLocation(import.meta.url, wasmFile, { browser: true });
5507
5497
  }
5508
5498
  async loadNodeWasm(path, totalStart) {
5509
5499
  const fs = await import("fs");
@@ -6544,11 +6534,110 @@ function handleZero(d, e, _U, V, zeroIdx, isDiagonal, _tolerance) {
6544
6534
  }
6545
6535
  }
6546
6536
  }
6537
+ function jacobiSVD(A) {
6538
+ const m = A.length;
6539
+ const n = A[0].length;
6540
+ const W = A.map((row2) => row2.slice());
6541
+ const V = eye(n);
6542
+ const EPS = 1e-15;
6543
+ const MAX_SWEEPS = 60;
6544
+ for (let sweep = 0; sweep < MAX_SWEEPS; sweep++) {
6545
+ let rotated = false;
6546
+ for (let p = 0; p < n - 1; p++) {
6547
+ for (let q = p + 1; q < n; q++) {
6548
+ let alpha = 0;
6549
+ let beta = 0;
6550
+ let gamma = 0;
6551
+ for (let i = 0; i < m; i++) {
6552
+ alpha += W[i][p] * W[i][p];
6553
+ gamma += W[i][q] * W[i][q];
6554
+ beta += W[i][p] * W[i][q];
6555
+ }
6556
+ if (Math.abs(beta) <= EPS * Math.sqrt(alpha * gamma) || alpha === 0 && gamma === 0) {
6557
+ continue;
6558
+ }
6559
+ rotated = true;
6560
+ const zeta = (gamma - alpha) / (2 * beta);
6561
+ const sign = zeta >= 0 ? 1 : -1;
6562
+ const t = sign / (Math.abs(zeta) + Math.sqrt(1 + zeta * zeta));
6563
+ const c = 1 / Math.sqrt(1 + t * t);
6564
+ const s = c * t;
6565
+ for (let i = 0; i < m; i++) {
6566
+ const wip = W[i][p];
6567
+ const wiq = W[i][q];
6568
+ W[i][p] = c * wip - s * wiq;
6569
+ W[i][q] = s * wip + c * wiq;
6570
+ }
6571
+ for (let i = 0; i < n; i++) {
6572
+ const vip = V[i][p];
6573
+ const viq = V[i][q];
6574
+ V[i][p] = c * vip - s * viq;
6575
+ V[i][q] = s * vip + c * viq;
6576
+ }
6577
+ }
6578
+ }
6579
+ if (!rotated) break;
6580
+ }
6581
+ const d = new Array(n).fill(0);
6582
+ const U = Array.from({ length: m }, () => new Array(n).fill(0));
6583
+ for (let j = 0; j < n; j++) {
6584
+ let norm4 = 0;
6585
+ for (let i = 0; i < m; i++) norm4 += W[i][j] * W[i][j];
6586
+ norm4 = Math.sqrt(norm4);
6587
+ d[j] = norm4;
6588
+ if (norm4 > 1e-300) {
6589
+ for (let i = 0; i < m; i++) U[i][j] = W[i][j] / norm4;
6590
+ }
6591
+ }
6592
+ for (let j = 0; j < n; j++) {
6593
+ if (d[j] > 1e-300) continue;
6594
+ const col = new Array(m).fill(0);
6595
+ for (let seed = 0; seed < m; seed++) {
6596
+ col.fill(0);
6597
+ col[seed] = 1;
6598
+ for (let c = 0; c < n; c++) {
6599
+ if (c === j) continue;
6600
+ let dot = 0;
6601
+ for (let i = 0; i < m; i++) dot += col[i] * U[i][c];
6602
+ for (let i = 0; i < m; i++) col[i] -= dot * U[i][c];
6603
+ }
6604
+ let nrm = 0;
6605
+ for (let i = 0; i < m; i++) nrm += col[i] * col[i];
6606
+ nrm = Math.sqrt(nrm);
6607
+ if (nrm > 1e-8) {
6608
+ for (let i = 0; i < m; i++) U[i][j] = col[i] / nrm;
6609
+ break;
6610
+ }
6611
+ }
6612
+ }
6613
+ return { d, U, V };
6614
+ }
6615
+ function completeOrthonormalBasis(cols, m) {
6616
+ const k = cols[0]?.length ?? 0;
6617
+ const basis = Array.from({ length: k }, (_, j) => cols.map((row2) => row2[j]));
6618
+ for (let cand = 0; cand < m && basis.length < m; cand++) {
6619
+ const v = Array.from({ length: m }, (_, i) => i === cand ? 1 : 0);
6620
+ for (let pass = 0; pass < 2; pass++) {
6621
+ for (const b of basis) {
6622
+ let dot = 0;
6623
+ for (let i = 0; i < m; i++) dot += v[i] * b[i];
6624
+ for (let i = 0; i < m; i++) v[i] -= dot * b[i];
6625
+ }
6626
+ }
6627
+ let norm4 = 0;
6628
+ for (let i = 0; i < m; i++) norm4 += v[i] * v[i];
6629
+ norm4 = Math.sqrt(norm4);
6630
+ if (norm4 > 0.1) {
6631
+ basis.push(v.map((x) => x / norm4));
6632
+ }
6633
+ }
6634
+ return Array.from({ length: m }, (_, i) => basis.map((b) => b[i]));
6635
+ }
6547
6636
  function svd(matrix2, options = {}) {
6548
6637
  const {
6549
6638
  maxIterations = DEFAULT_MAX_ITERATIONS,
6550
6639
  tolerance = DEFAULT_TOLERANCE,
6551
- fullMatrices: _fullMatrices = true,
6640
+ fullMatrices = false,
6552
6641
  rankTolerance = 1e-10
6553
6642
  } = options;
6554
6643
  let A;
@@ -6615,19 +6704,29 @@ function svd(matrix2, options = {}) {
6615
6704
  }
6616
6705
  svdStep(d, e, U, V, start, end);
6617
6706
  }
6618
- for (let i = 0; i < d.length; i++) {
6619
- if (d[i] < 0) {
6620
- d[i] = -d[i];
6621
- for (let j = 0; j < V.length; j++) {
6622
- V[j][i] = -V[j][i];
6623
- }
6624
- }
6625
- }
6626
- const indices = Array.from({ length: d.length }, (_, i) => i);
6627
- indices.sort((a, b) => d[b] - d[a]);
6628
- const sortedS = indices.map((i) => d[i]);
6629
- const sortedU = U.map((row2) => indices.map((i) => row2[i]));
6630
- const sortedV = V.map((row2) => indices.map((i) => row2[i]));
6707
+ let dFinal = d;
6708
+ let uFinal = U;
6709
+ let vFinal = V;
6710
+ const maxAbsD = d.reduce((mx, x) => Math.max(mx, Math.abs(x)), 0);
6711
+ if (maxAbsD > 0 && d.some((x) => Math.abs(x) <= rankTolerance * maxAbsD)) {
6712
+ const jac = jacobiSVD(A);
6713
+ dFinal = jac.d;
6714
+ uFinal = jac.U;
6715
+ vFinal = jac.V;
6716
+ }
6717
+ for (let i = 0; i < dFinal.length; i++) {
6718
+ if (dFinal[i] < 0) {
6719
+ dFinal[i] = -dFinal[i];
6720
+ for (let j = 0; j < vFinal.length; j++) {
6721
+ vFinal[j][i] = -vFinal[j][i];
6722
+ }
6723
+ }
6724
+ }
6725
+ const indices = Array.from({ length: dFinal.length }, (_, i) => i);
6726
+ indices.sort((a, b) => dFinal[b] - dFinal[a]);
6727
+ const sortedS = indices.map((i) => dFinal[i]);
6728
+ const sortedU = uFinal.map((row2) => indices.map((i) => row2[i]));
6729
+ const sortedV = vFinal.map((row2) => indices.map((i) => row2[i]));
6631
6730
  const maxS = sortedS[0] || 0;
6632
6731
  let rank = 0;
6633
6732
  for (const s of sortedS) {
@@ -6639,12 +6738,12 @@ function svd(matrix2, options = {}) {
6639
6738
  return {
6640
6739
  U: sortedV,
6641
6740
  S: sortedS,
6642
- V: sortedU,
6741
+ V: fullMatrices && sortedU.length > (sortedU[0]?.length ?? 0) ? completeOrthonormalBasis(sortedU, sortedU.length) : sortedU,
6643
6742
  rank
6644
6743
  };
6645
6744
  }
6646
6745
  return {
6647
- U: sortedU,
6746
+ U: fullMatrices && sortedU.length > (sortedU[0]?.length ?? 0) ? completeOrthonormalBasis(sortedU, sortedU.length) : sortedU,
6648
6747
  S: sortedS,
6649
6748
  V: sortedV,
6650
6749
  rank
@@ -7111,8 +7210,7 @@ function hessenbergReduce(A) {
7111
7210
  applyHouseholderRight(Q, v, beta, 0, k + 1);
7112
7211
  }
7113
7212
  }
7114
- for (let i = 0; i < n; i++)
7115
- for (let j = 0; j < i - 1; j++) H[i][j] = 0;
7213
+ for (let i = 0; i < n; i++) for (let j = 0; j < i - 1; j++) H[i][j] = 0;
7116
7214
  return { H, Q };
7117
7215
  }
7118
7216
  function givensParams(a, b) {
@@ -7217,6 +7315,57 @@ function qrStepDouble(H, Q, start, end) {
7217
7315
  applyGivensRight(H, gc, gs, end - 1, end, 0);
7218
7316
  applyGivensRight(Q, gc, gs, end - 1, end, 0);
7219
7317
  }
7318
+ function qrStepSingleShift(H, Q, start, end, shift) {
7319
+ let x = H[start][start] - shift;
7320
+ let y = H[start + 1][start];
7321
+ for (let k = start; k < end; k++) {
7322
+ const { c: gc, s: gs } = givensParams(x, y);
7323
+ applyGivensLeft(H, gc, gs, k, k + 1, Math.max(0, k - 1));
7324
+ applyGivensRight(H, gc, gs, k, k + 1, 0);
7325
+ applyGivensRight(Q, gc, gs, k, k + 1, 0);
7326
+ if (k < end - 1) {
7327
+ x = H[k + 1][k];
7328
+ y = H[k + 2][k];
7329
+ }
7330
+ }
7331
+ }
7332
+ function exceptionalShift(H, start, end) {
7333
+ const sub1 = Math.abs(H[end][end - 1]);
7334
+ const sub2 = end - 1 > start ? Math.abs(H[end - 1][end - 2]) : 0;
7335
+ return H[end][end] + 0.75 * (sub1 + sub2);
7336
+ }
7337
+ function standardize2x2Block(H, Q, p) {
7338
+ const q = p + 1;
7339
+ const a = H[p][p];
7340
+ const b = H[p][q];
7341
+ const c = H[q][p];
7342
+ const d = H[q][q];
7343
+ if (c === 0) return;
7344
+ const half = (a - d) / 2;
7345
+ const disc = half * half + b * c;
7346
+ if (disc < 0) return;
7347
+ const z = Math.sqrt(disc);
7348
+ const lambda = (a + d) / 2 + (half >= 0 ? z : -z);
7349
+ const r0 = Math.hypot(a - lambda, b);
7350
+ const r1 = Math.hypot(c, d - lambda);
7351
+ let u0;
7352
+ let u1;
7353
+ if (r0 >= r1) {
7354
+ u0 = b;
7355
+ u1 = lambda - a;
7356
+ } else {
7357
+ u0 = lambda - d;
7358
+ u1 = c;
7359
+ }
7360
+ const norm4 = Math.hypot(u0, u1);
7361
+ if (norm4 === 0) return;
7362
+ const cs = u0 / norm4;
7363
+ const sn = -u1 / norm4;
7364
+ applyGivensLeft(H, cs, sn, p, q, 0);
7365
+ applyGivensRight(H, cs, sn, p, q, 0);
7366
+ applyGivensRight(Q, cs, sn, p, q, 0);
7367
+ H[q][p] = 0;
7368
+ }
7220
7369
  function schurRaw(A, maxIterations, tolerance) {
7221
7370
  const n = A.length;
7222
7371
  if (n === 1) {
@@ -7225,7 +7374,10 @@ function schurRaw(A, maxIterations, tolerance) {
7225
7374
  const { H, Q } = hessenbergReduce(A);
7226
7375
  let end = n - 1;
7227
7376
  let iter = 0;
7377
+ let iterSinceDeflation = 0;
7378
+ const EXCEPTIONAL_EVERY = 10;
7228
7379
  while (end > 0 && iter < maxIterations) {
7380
+ const endBefore = end;
7229
7381
  let start = end;
7230
7382
  while (start > 0) {
7231
7383
  const scale2 = Math.abs(H[start - 1][start - 1]) + Math.abs(H[start][start]);
@@ -7238,14 +7390,16 @@ function schurRaw(A, maxIterations, tolerance) {
7238
7390
  if (start === end) {
7239
7391
  end--;
7240
7392
  } else if (start === end - 1) {
7393
+ standardize2x2Block(H, Q, start);
7241
7394
  end -= 2;
7395
+ } else if (iterSinceDeflation > 0 && iterSinceDeflation % EXCEPTIONAL_EVERY === 0) {
7396
+ qrStepSingleShift(H, Q, start, end, exceptionalShift(H, start, end));
7397
+ } else if (end - start >= 2) {
7398
+ qrStepDouble(H, Q, start, end);
7242
7399
  } else {
7243
- if (end - start >= 2) {
7244
- qrStepDouble(H, Q, start, end);
7245
- } else {
7246
- qrStepSingle(H, Q, start, end);
7247
- }
7400
+ qrStepSingle(H, Q, start, end);
7248
7401
  }
7402
+ iterSinceDeflation = end < endBefore ? 0 : iterSinceDeflation + 1;
7249
7403
  iter++;
7250
7404
  }
7251
7405
  return { H, Q };
@@ -19,6 +19,25 @@ async function resolvePackagedWasm(metaUrl, wasmFile) {
19
19
  }
20
20
  return null;
21
21
  }
22
+ async function defaultWasmLocation(metaUrl, wasmFile, opts) {
23
+ if (opts?.browser) {
24
+ return new URL(`./wasm/${wasmFile}`, metaUrl).href;
25
+ }
26
+ const { fileURLToPath } = await import("url");
27
+ const { dirname, join } = await import("path");
28
+ const { existsSync } = await import("fs");
29
+ let dir = dirname(fileURLToPath(metaUrl));
30
+ for (let depth = 0; depth < 8; depth++) {
31
+ if (existsSync(join(dir, "package.json"))) {
32
+ return join(dir, "dist", "wasm", wasmFile);
33
+ }
34
+ const parent = dirname(dir);
35
+ if (parent === dir) break;
36
+ dir = parent;
37
+ }
38
+ return join(dirname(fileURLToPath(metaUrl)), "wasm", wasmFile);
39
+ }
22
40
  export {
41
+ defaultWasmLocation,
23
42
  resolvePackagedWasm
24
43
  };
Binary file
@@ -1,3 +1,3 @@
1
1
  {
2
- "mathts-as.wasm": "sha384-eSck1dw9Rtu8oUAQ/0GMpC1L4bhAPNVS72581QGAyd1xDlRvonQT/XA2RZjpxghB"
2
+ "mathts-as.wasm": "sha384-q4RGhtVhsLmeCfG5lYpL70f51laygEpfcKGuxKWFnoC9iSXZPXMUEYE9V8W8gYZu"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielsimonjr/mathts-matrix",
3
- "version": "0.1.13",
3
+ "version": "0.2.0",
4
4
  "description": "Matrix operations for MathTS with WASM/WebGPU backend support",
5
5
  "author": "Daniel Simon Jr.",
6
6
  "license": "MIT",
@@ -32,8 +32,8 @@
32
32
  "build:prod": "tsup src/index.ts --format esm --dts --clean --minify --treeshake && node scripts/copy-wasm.mjs"
33
33
  },
34
34
  "dependencies": {
35
- "@danielsimonjr/mathts-core": "^0.3.0",
36
- "@danielsimonjr/mathts-parallel": "^0.3.0"
35
+ "@danielsimonjr/mathts-core": "^0.5.0",
36
+ "@danielsimonjr/mathts-parallel": "^0.3.2"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^25.5.2",