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
JavaScript
;
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));
}
}