UNPKG

ml-matrix

Version:

Matrix manipulation and computation library

514 lines (479 loc) 15.2 kB
'use strict'; var Matrix = require('../matrix'); var util = require('./util'); var hypotenuse = util.hypotenuse; var getFilled2DArray = util.getFilled2DArray; // https://github.com/lutzroeder/Mapack/blob/master/Source/SingularValueDecomposition.cs function SingularValueDecomposition(value, options) { if (!(this instanceof SingularValueDecomposition)) { return new SingularValueDecomposition(value, options); } value = Matrix.checkMatrix(value); options = options || {}; var m = value.rows, n = value.columns, nu = Math.min(m, n); var wantu = true, wantv = true; if (options.computeLeftSingularVectors === false) wantu = false; if (options.computeRightSingularVectors === false) wantv = false; var autoTranspose = options.autoTranspose === true; var swapped = false; var a; if (m < n) { if (!autoTranspose) { a = value.clone(); console.warn('Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose'); } else { a = value.transpose(); m = a.rows; n = a.columns; swapped = true; var aux = wantu; wantu = wantv; wantv = aux; } } else { a = value.clone(); } var s = new Array(Math.min(m + 1, n)), U = getFilled2DArray(m, nu, 0), V = getFilled2DArray(n, n, 0), e = new Array(n), work = new Array(m); var nct = Math.min(m - 1, n); var nrt = Math.max(0, Math.min(n - 2, m)); var i, j, k, p, t, ks, f, cs, sn, max, kase, scale, sp, spm1, epm1, sk, ek, b, c, shift, g; for (k = 0, max = Math.max(nct, nrt); k < max; k++) { if (k < nct) { s[k] = 0; for (i = k; i < m; i++) { s[k] = hypotenuse(s[k], a[i][k]); } if (s[k] !== 0) { if (a[k][k] < 0) { s[k] = -s[k]; } for (i = k; i < m; i++) { a[i][k] /= s[k]; } a[k][k] += 1; } s[k] = -s[k]; } for (j = k + 1; j < n; j++) { if ((k < nct) && (s[k] !== 0)) { t = 0; for (i = k; i < m; i++) { t += a[i][k] * a[i][j]; } t = -t / a[k][k]; for (i = k; i < m; i++) { a[i][j] += t * a[i][k]; } } e[j] = a[k][j]; } if (wantu && (k < nct)) { for (i = k; i < m; i++) { U[i][k] = a[i][k]; } } if (k < nrt) { e[k] = 0; for (i = k + 1; i < n; i++) { e[k] = hypotenuse(e[k], e[i]); } if (e[k] !== 0) { if (e[k + 1] < 0) e[k] = -e[k]; for (i = k + 1; i < n; i++) { e[i] /= e[k]; } e[k + 1] += 1; } e[k] = -e[k]; if ((k + 1 < m) && (e[k] !== 0)) { for (i = k + 1; i < m; i++) { work[i] = 0; } for (j = k + 1; j < n; j++) { for (i = k + 1; i < m; i++) { work[i] += e[j] * a[i][j]; } } for (j = k + 1; j < n; j++) { t = -e[j] / e[k + 1]; for (i = k + 1; i < m; i++) { a[i][j] += t * work[i]; } } } if (wantv) { for (i = k + 1; i < n; i++) { V[i][k] = e[i]; } } } } p = Math.min(n, m + 1); if (nct < n) { s[nct] = a[nct][nct]; } if (m < p) { s[p - 1] = 0; } if (nrt + 1 < p) { e[nrt] = a[nrt][p - 1]; } e[p - 1] = 0; if (wantu) { for (j = nct; j < nu; j++) { for (i = 0; i < m; i++) { U[i][j] = 0; } U[j][j] = 1; } for (k = nct - 1; k >= 0; k--) { if (s[k] !== 0) { for (j = k + 1; j < nu; j++) { t = 0; for (i = k; i < m; i++) { t += U[i][k] * U[i][j]; } t = -t / U[k][k]; for (i = k; i < m; i++) { U[i][j] += t * U[i][k]; } } for (i = k; i < m; i++) { U[i][k] = -U[i][k]; } U[k][k] = 1 + U[k][k]; for (i = 0; i < k - 1; i++) { U[i][k] = 0; } } else { for (i = 0; i < m; i++) { U[i][k] = 0; } U[k][k] = 1; } } } if (wantv) { for (k = n - 1; k >= 0; k--) { if ((k < nrt) && (e[k] !== 0)) { for (j = k + 1; j < n; j++) { t = 0; for (i = k + 1; i < n; i++) { t += V[i][k] * V[i][j]; } t = -t / V[k + 1][k]; for (i = k + 1; i < n; i++) { V[i][j] += t * V[i][k]; } } } for (i = 0; i < n; i++) { V[i][k] = 0; } V[k][k] = 1; } } var pp = p - 1, iter = 0, eps = Math.pow(2, -52); while (p > 0) { for (k = p - 2; k >= -1; k--) { if (k === -1) { break; } if (Math.abs(e[k]) <= eps * (Math.abs(s[k]) + Math.abs(s[k + 1]))) { e[k] = 0; break; } } if (k === p - 2) { kase = 4; } else { for (ks = p - 1; ks >= k; ks--) { if (ks === k) { break; } t = (ks !== p ? Math.abs(e[ks]) : 0) + (ks !== k + 1 ? Math.abs(e[ks - 1]) : 0); if (Math.abs(s[ks]) <= eps * t) { s[ks] = 0; break; } } if (ks === k) { kase = 3; } else if (ks === p - 1) { kase = 1; } else { kase = 2; k = ks; } } k++; switch (kase) { case 1: { f = e[p - 2]; e[p - 2] = 0; for (j = p - 2; j >= k; j--) { t = hypotenuse(s[j], f); cs = s[j] / t; sn = f / t; s[j] = t; if (j !== k) { f = -sn * e[j - 1]; e[j - 1] = cs * e[j - 1]; } if (wantv) { for (i = 0; i < n; i++) { t = cs * V[i][j] + sn * V[i][p - 1]; V[i][p - 1] = -sn * V[i][j] + cs * V[i][p - 1]; V[i][j] = t; } } } break; } case 2 : { f = e[k - 1]; e[k - 1] = 0; for (j = k; j < p; j++) { t = hypotenuse(s[j], f); cs = s[j] / t; sn = f / t; s[j] = t; f = -sn * e[j]; e[j] = cs * e[j]; if (wantu) { for (i = 0; i < m; i++) { t = cs * U[i][j] + sn * U[i][k - 1]; U[i][k - 1] = -sn * U[i][j] + cs * U[i][k - 1]; U[i][j] = t; } } } break; } case 3 : { scale = Math.max(Math.max(Math.max(Math.max(Math.abs(s[p - 1]), Math.abs(s[p - 2])), Math.abs(e[p - 2])), Math.abs(s[k])), Math.abs(e[k])); sp = s[p - 1] / scale; spm1 = s[p - 2] / scale; epm1 = e[p - 2] / scale; sk = s[k] / scale; ek = e[k] / scale; b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2; c = (sp * epm1) * (sp * epm1); shift = 0; if ((b !== 0) || (c !== 0)) { shift = Math.sqrt(b * b + c); if (b < 0) { shift = -shift; } shift = c / (b + shift); } f = (sk + sp) * (sk - sp) + shift; g = sk * ek; for (j = k; j < p - 1; j++) { t = hypotenuse(f, g); cs = f / t; sn = g / t; if (j !== k) { e[j - 1] = t; } f = cs * s[j] + sn * e[j]; e[j] = cs * e[j] - sn * s[j]; g = sn * s[j + 1]; s[j + 1] = cs * s[j + 1]; if (wantv) { for (i = 0; i < n; i++) { t = cs * V[i][j] + sn * V[i][j + 1]; V[i][j + 1] = -sn * V[i][j] + cs * V[i][j + 1]; V[i][j] = t; } } t = hypotenuse(f, g); cs = f / t; sn = g / t; s[j] = t; f = cs * e[j] + sn * s[j + 1]; s[j + 1] = -sn * e[j] + cs * s[j + 1]; g = sn * e[j + 1]; e[j + 1] = cs * e[j + 1]; if (wantu && (j < m - 1)) { for (i = 0; i < m; i++) { t = cs * U[i][j] + sn * U[i][j + 1]; U[i][j + 1] = -sn * U[i][j] + cs * U[i][j + 1]; U[i][j] = t; } } } e[p - 2] = f; iter = iter + 1; break; } case 4: { if (s[k] <= 0) { s[k] = (s[k] < 0 ? -s[k] : 0); if (wantv) { for (i = 0; i <= pp; i++) { V[i][k] = -V[i][k]; } } } while (k < pp) { if (s[k] >= s[k + 1]) { break; } t = s[k]; s[k] = s[k + 1]; s[k + 1] = t; if (wantv && (k < n - 1)) { for (i = 0; i < n; i++) { t = V[i][k + 1]; V[i][k + 1] = V[i][k]; V[i][k] = t; } } if (wantu && (k < m - 1)) { for (i = 0; i < m; i++) { t = U[i][k + 1]; U[i][k + 1] = U[i][k]; U[i][k] = t; } } k++; } iter = 0; p--; break; } } } if (swapped) { var tmp = V; V = U; U = tmp; } this.m = m; this.n = n; this.s = s; this.U = U; this.V = V; } SingularValueDecomposition.prototype = { get condition() { return this.s[0] / this.s[Math.min(this.m, this.n) - 1]; }, get norm2() { return this.s[0]; }, get rank() { var eps = Math.pow(2, -52), tol = Math.max(this.m, this.n) * this.s[0] * eps, r = 0, s = this.s; for (var i = 0, ii = s.length; i < ii; i++) { if (s[i] > tol) { r++; } } return r; }, get diagonal() { return this.s; }, // https://github.com/accord-net/framework/blob/development/Sources/Accord.Math/Decompositions/SingularValueDecomposition.cs get threshold() { return (Math.pow(2, -52) / 2) * Math.max(this.m, this.n) * this.s[0]; }, get leftSingularVectors() { if (!Matrix.isMatrix(this.U)) { this.U = new Matrix(this.U); } return this.U; }, get rightSingularVectors() { if (!Matrix.isMatrix(this.V)) { this.V = new Matrix(this.V); } return this.V; }, get diagonalMatrix() { return Matrix.diag(this.s); }, solve: function (value) { var Y = value, e = this.threshold, scols = this.s.length, Ls = Matrix.zeros(scols, scols), i; for (i = 0; i < scols; i++) { if (Math.abs(this.s[i]) <= e) { Ls[i][i] = 0; } else { Ls[i][i] = 1 / this.s[i]; } } var U = this.U; var V = this.rightSingularVectors; var VL = V.mmul(Ls), vrows = V.rows, urows = U.length, VLU = Matrix.zeros(vrows, urows), j, k, sum; for (i = 0; i < vrows; i++) { for (j = 0; j < urows; j++) { sum = 0; for (k = 0; k < scols; k++) { sum += VL[i][k] * U[j][k]; } VLU[i][j] = sum; } } return VLU.mmul(Y); }, solveForDiagonal: function (value) { return this.solve(Matrix.diag(value)); }, inverse: function () { var V = this.V; var e = this.threshold, vrows = V.length, vcols = V[0].length, X = new Matrix(vrows, this.s.length), i, j; for (i = 0; i < vrows; i++) { for (j = 0; j < vcols; j++) { if (Math.abs(this.s[j]) > e) { X[i][j] = V[i][j] / this.s[j]; } else { X[i][j] = 0; } } } var U = this.U; var urows = U.length, ucols = U[0].length, Y = new Matrix(vrows, urows), k, sum; for (i = 0; i < vrows; i++) { for (j = 0; j < urows; j++) { sum = 0; for (k = 0; k < ucols; k++) { sum += X[i][k] * U[j][k]; } Y[i][j] = sum; } } return Y; } }; module.exports = SingularValueDecomposition;