|
@@ -0,0 +1,292 @@
|
|
|
+/**
|
|
|
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
|
+ *
|
|
|
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
|
|
|
+ */
|
|
|
+
|
|
|
+import Matrix from './matrix';
|
|
|
+
|
|
|
+// svd method adapted from http://inspirit.github.io/jsfeat/ MIT Eugene Zatepyakin
|
|
|
+
|
|
|
+export function swap(A: Helpers.NumberArray, i0: number, i1: number, t: number) {
|
|
|
+ t = A[i0]
|
|
|
+ A[i0] = A[i1]
|
|
|
+ A[i1] = t
|
|
|
+}
|
|
|
+
|
|
|
+export function hypot(a: number, b: number) {
|
|
|
+ a = Math.abs(a)
|
|
|
+ b = Math.abs(b)
|
|
|
+ if (a > b) {
|
|
|
+ b /= a
|
|
|
+ return a * Math.sqrt(1.0 + b * b)
|
|
|
+ }
|
|
|
+ if (b > 0) {
|
|
|
+ a /= b
|
|
|
+ return b * Math.sqrt(1.0 + a * a)
|
|
|
+ }
|
|
|
+ return 0.0
|
|
|
+}
|
|
|
+
|
|
|
+const EPSILON = 0.0000001192092896
|
|
|
+const FLT_MIN = 1E-37
|
|
|
+
|
|
|
+export function JacobiSVDImpl(At: Helpers.NumberArray, astep: number, _W: Helpers.NumberArray, Vt: Helpers.NumberArray, vstep: number, m: number, n: number, n1: number) {
|
|
|
+ const eps = EPSILON * 2.0
|
|
|
+ const minval = FLT_MIN
|
|
|
+ let i = 0
|
|
|
+ let j = 0
|
|
|
+ let k = 0
|
|
|
+ let iter = 0
|
|
|
+ const maxIter = Math.max(m, 30)
|
|
|
+ let Ai = 0
|
|
|
+ let Aj = 0
|
|
|
+ let Vi = 0
|
|
|
+ let Vj = 0
|
|
|
+ let changed = 0
|
|
|
+ let c = 0.0
|
|
|
+ let s = 0.0
|
|
|
+ let t = 0.0
|
|
|
+ let t0 = 0.0
|
|
|
+ let t1 = 0.0
|
|
|
+ let sd = 0.0
|
|
|
+ let beta = 0.0
|
|
|
+ let gamma = 0.0
|
|
|
+ let delta = 0.0
|
|
|
+ let a = 0.0
|
|
|
+ let p = 0.0
|
|
|
+ let b = 0.0
|
|
|
+ let seed = 0x1234
|
|
|
+ let val = 0.0
|
|
|
+ let val0 = 0.0
|
|
|
+ let asum = 0.0
|
|
|
+
|
|
|
+ const W = new Float64Array(n << 3)
|
|
|
+
|
|
|
+ for (; i < n; i++) {
|
|
|
+ for (k = 0, sd = 0; k < m; k++) {
|
|
|
+ t = At[i * astep + k]
|
|
|
+ sd += t * t
|
|
|
+ }
|
|
|
+ W[i] = sd
|
|
|
+
|
|
|
+ if (Vt) {
|
|
|
+ for (k = 0; k < n; k++) {
|
|
|
+ Vt[i * vstep + k] = 0
|
|
|
+ }
|
|
|
+ Vt[i * vstep + i] = 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (; iter < maxIter; iter++) {
|
|
|
+ changed = 0
|
|
|
+
|
|
|
+ for (i = 0; i < n - 1; i++) {
|
|
|
+ for (j = i + 1; j < n; j++) {
|
|
|
+ Ai = (i * astep) | 0
|
|
|
+ Aj = (j * astep) | 0
|
|
|
+ a = W[i]
|
|
|
+ p = 0
|
|
|
+ b = W[j]
|
|
|
+
|
|
|
+ k = 2
|
|
|
+ p += At[Ai] * At[Aj]
|
|
|
+ p += At[Ai + 1] * At[Aj + 1]
|
|
|
+
|
|
|
+ for (; k < m; k++) { p += At[Ai + k] * At[Aj + k] }
|
|
|
+
|
|
|
+ if (Math.abs(p) <= eps * Math.sqrt(a * b)) continue
|
|
|
+
|
|
|
+ p *= 2.0
|
|
|
+ beta = a - b
|
|
|
+ gamma = hypot(p, beta)
|
|
|
+ if (beta < 0) {
|
|
|
+ delta = (gamma - beta) * 0.5
|
|
|
+ s = Math.sqrt(delta / gamma)
|
|
|
+ c = (p / (gamma * s * 2.0))
|
|
|
+ } else {
|
|
|
+ c = Math.sqrt((gamma + beta) / (gamma * 2.0))
|
|
|
+ s = (p / (gamma * c * 2.0))
|
|
|
+ }
|
|
|
+
|
|
|
+ a = 0.0
|
|
|
+ b = 0.0
|
|
|
+
|
|
|
+ k = 2 // unroll
|
|
|
+ t0 = c * At[Ai] + s * At[Aj]
|
|
|
+ t1 = -s * At[Ai] + c * At[Aj]
|
|
|
+ At[Ai] = t0; At[Aj] = t1
|
|
|
+ a += t0 * t0; b += t1 * t1
|
|
|
+
|
|
|
+ t0 = c * At[Ai + 1] + s * At[Aj + 1]
|
|
|
+ t1 = -s * At[Ai + 1] + c * At[Aj + 1]
|
|
|
+ At[Ai + 1] = t0; At[Aj + 1] = t1
|
|
|
+ a += t0 * t0; b += t1 * t1
|
|
|
+
|
|
|
+ for (; k < m; k++) {
|
|
|
+ t0 = c * At[Ai + k] + s * At[Aj + k]
|
|
|
+ t1 = -s * At[Ai + k] + c * At[Aj + k]
|
|
|
+ At[Ai + k] = t0; At[Aj + k] = t1
|
|
|
+
|
|
|
+ a += t0 * t0; b += t1 * t1
|
|
|
+ }
|
|
|
+
|
|
|
+ W[i] = a
|
|
|
+ W[j] = b
|
|
|
+
|
|
|
+ changed = 1
|
|
|
+
|
|
|
+ if (Vt) {
|
|
|
+ Vi = (i * vstep) | 0
|
|
|
+ Vj = (j * vstep) | 0
|
|
|
+
|
|
|
+ k = 2
|
|
|
+ t0 = c * Vt[Vi] + s * Vt[Vj]
|
|
|
+ t1 = -s * Vt[Vi] + c * Vt[Vj]
|
|
|
+ Vt[Vi] = t0; Vt[Vj] = t1
|
|
|
+
|
|
|
+ t0 = c * Vt[Vi + 1] + s * Vt[Vj + 1]
|
|
|
+ t1 = -s * Vt[Vi + 1] + c * Vt[Vj + 1]
|
|
|
+ Vt[Vi + 1] = t0; Vt[Vj + 1] = t1
|
|
|
+
|
|
|
+ for (; k < n; k++) {
|
|
|
+ t0 = c * Vt[Vi + k] + s * Vt[Vj + k]
|
|
|
+ t1 = -s * Vt[Vi + k] + c * Vt[Vj + k]
|
|
|
+ Vt[Vi + k] = t0; Vt[Vj + k] = t1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (changed === 0) break
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < n; i++) {
|
|
|
+ for (k = 0, sd = 0; k < m; k++) {
|
|
|
+ t = At[i * astep + k]
|
|
|
+ sd += t * t
|
|
|
+ }
|
|
|
+ W[i] = Math.sqrt(sd)
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < n - 1; i++) {
|
|
|
+ j = i
|
|
|
+ for (k = i + 1; k < n; k++) {
|
|
|
+ if (W[j] < W[k]) { j = k }
|
|
|
+ }
|
|
|
+ if (i !== j) {
|
|
|
+ swap(W, i, j, sd)
|
|
|
+ if (Vt) {
|
|
|
+ for (k = 0; k < m; k++) {
|
|
|
+ swap(At, i * astep + k, j * astep + k, t)
|
|
|
+ }
|
|
|
+
|
|
|
+ for (k = 0; k < n; k++) {
|
|
|
+ swap(Vt, i * vstep + k, j * vstep + k, t)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < n; i++) {
|
|
|
+ _W[i] = W[i]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!Vt) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < n1; i++) {
|
|
|
+ sd = i < n ? W[i] : 0
|
|
|
+
|
|
|
+ while (sd <= minval) {
|
|
|
+ // if we got a zero singular value, then in order to get the corresponding left singular vector
|
|
|
+ // we generate a random vector, project it to the previously computed left singular vectors,
|
|
|
+ // subtract the projection and normalize the difference.
|
|
|
+ val0 = (1.0 / m)
|
|
|
+ for (k = 0; k < m; k++) {
|
|
|
+ seed = (seed * 214013 + 2531011)
|
|
|
+ val = (((seed >> 16) & 0x7fff) & 256) !== 0 ? val0 : -val0
|
|
|
+ At[i * astep + k] = val
|
|
|
+ }
|
|
|
+ for (iter = 0; iter < 2; iter++) {
|
|
|
+ for (j = 0; j < i; j++) {
|
|
|
+ sd = 0
|
|
|
+ for (k = 0; k < m; k++) {
|
|
|
+ sd += At[i * astep + k] * At[j * astep + k]
|
|
|
+ }
|
|
|
+ asum = 0.0
|
|
|
+ for (k = 0; k < m; k++) {
|
|
|
+ t = (At[i * astep + k] - sd * At[j * astep + k])
|
|
|
+ At[i * astep + k] = t
|
|
|
+ asum += Math.abs(t)
|
|
|
+ }
|
|
|
+ asum = asum ? 1.0 / asum : 0
|
|
|
+ for (k = 0; k < m; k++) {
|
|
|
+ At[i * astep + k] *= asum
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sd = 0
|
|
|
+ for (k = 0; k < m; k++) {
|
|
|
+ t = At[i * astep + k]
|
|
|
+ sd += t * t
|
|
|
+ }
|
|
|
+ sd = Math.sqrt(sd)
|
|
|
+ }
|
|
|
+
|
|
|
+ s = (1.0 / sd)
|
|
|
+ for (k = 0; k < m; k++) {
|
|
|
+ At[i * astep + k] *= s
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function svd(A: Matrix, W: Matrix, U: Matrix, V: Matrix) {
|
|
|
+ let at = 0
|
|
|
+ let i = 0
|
|
|
+ const _m = A.rows
|
|
|
+ const _n = A.cols
|
|
|
+ let m = _m
|
|
|
+ let n = _n
|
|
|
+
|
|
|
+ if (m < n) {
|
|
|
+ at = 1
|
|
|
+ i = m
|
|
|
+ m = n
|
|
|
+ n = i
|
|
|
+ }
|
|
|
+
|
|
|
+ const amt = Matrix.create(m, m)
|
|
|
+ const wmt = Matrix.create(1, n)
|
|
|
+ const vmt = Matrix.create(n, n)
|
|
|
+
|
|
|
+ if (at === 0) {
|
|
|
+ Matrix.transpose(amt, A)
|
|
|
+ } else {
|
|
|
+ for (i = 0; i < _n * _m; i++) {
|
|
|
+ amt.data[i] = A.data[i]
|
|
|
+ }
|
|
|
+ for (; i < n * m; i++) {
|
|
|
+ amt.data[i] = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ JacobiSVDImpl(amt.data, m, wmt.data, vmt.data, n, m, n, m)
|
|
|
+
|
|
|
+ if (W) {
|
|
|
+ for (i = 0; i < n; i++) {
|
|
|
+ W.data[i] = wmt.data[i]
|
|
|
+ }
|
|
|
+ for (; i < _n; i++) {
|
|
|
+ W.data[i] = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (at === 0) {
|
|
|
+ if (U) Matrix.transpose(U, amt)
|
|
|
+ if (V) Matrix.transpose(V, vmt)
|
|
|
+ } else {
|
|
|
+ if (U) Matrix.transpose(U, vmt)
|
|
|
+ if (V) Matrix.transpose(V, amt)
|
|
|
+ }
|
|
|
+}
|