UNPKG

svd.ts

Version:

A lightweight, isomorphic TypeScript implementation for computing Singular Value Decomposition (SVD) of matrix in Node.js and browsers.

76 lines (75 loc) 2.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.computeSVD = computeSVD; exports.reconstructFromSVD = reconstructFromSVD; const math_1 = require("./math"); /** Power iteration to find top k singular vectors/values */ function computeSVD(options) { const { A, k, maxIterations = 100, convergenceThreshold = 1e-6, initialVector = 'one', } = options; const m = A.length, n = A[0].length; let Ak = A.map(row => row.slice()); const U = []; const S = []; const V = []; for (let comp = 0; comp < k; ++comp) { let v = initialVector === 'one' ? Array(n).fill(1) : Array(n) .fill(0) .map(() => Math.random() - 0.5); v = (0, math_1.normalize)(v); let s = 0, s_prev = 0; for (let iter = 0; iter < maxIterations; ++iter) { // Gram-Schmidt orthogonalization for (let prev = 0; prev < comp; ++prev) { const proj = (0, math_1.dot)(v, V[prev]); for (let i = 0; i < v.length; ++i) { v[i] -= proj * V[prev][i]; } } v = (0, math_1.normalize)(v); // u = normalize(Ak * v) let u = (0, math_1.matVecMul)(Ak, v); u = (0, math_1.normalize)(u); // v = normalize(Ak^T * u) v = (0, math_1.matVecMul)((0, math_1.transpose)(Ak), u); s_prev = s; s = (0, math_1.norm)(v); v = (0, math_1.normalize)(v); if (Math.abs(s - s_prev) < convergenceThreshold) break; } // Singular value const uvec = (0, math_1.matVecMul)(Ak, v); if (Math.abs(s) > 1e-12) { U.push(uvec.map(x => x / s)); } else { U.push(Array(uvec.length).fill(0)); } S.push(s); V.push(v); // Deflate Ak (remove rank-1 component) const uvT = (0, math_1.outer)(U[comp], V[comp]); Ak = (0, math_1.matSub)(Ak, uvT.map(row => row.map(x => x * s))); } return { U: (0, math_1.transpose)(U), S, V, }; } /** A ≈ U * S * V^T (rank-k truncated SVD) */ function reconstructFromSVD(svd) { const { U, S, V } = svd; const Sdiag = (0, math_1.diag)(S); const US = (0, math_1.matMul)(U, Sdiag); if (V.length === US[0].length) { // If V.rows === k, V is (k, n): use directly return (0, math_1.matMul)(US, V); } else { // If V.rows === n, V is (n, k): transpose return (0, math_1.matMul)(US, (0, math_1.transpose)(V)); } }