//! # SVD Decomposition //! //! Any _m_ by _n_ matrix \\(A\\) can be factored into: //! //! \\[A = U \Sigma V^T\\] //! //! Where columns of \\(U\\) are eigenvectors of \\(AA^T\\) (left-singular vectors of _A_), //! \\(V\\) are eigenvectors of \\(A^TA\\) (right-singular vectors of _A_), //! and the diagonal values in the \\(\Sigma\\) matrix are known as the singular values of the original matrix. //! //! Example: //! ``` //! use smartcore::linalg::basic::matrix::DenseMatrix; //! use smartcore::linalg::traits::svd::*; //! //! let A = DenseMatrix::from_2d_array(&[ //! &[0.9, 0.4, 0.7], //! &[0.4, 0.5, 0.3], //! &[0.7, 0.3, 0.8] //! ]).unwrap(); //! //! let svd = A.svd().unwrap(); //! let u: DenseMatrix = svd.U; //! let v: DenseMatrix = svd.V; //! let s: Vec = svd.s; //! ``` //! //! ## References: //! * ["Linear Algebra and Its Applications", Gilbert Strang, 5th ed., 6.3 Singular Value Decomposition](https://www.academia.edu/32459792/_Strang_G_Linear_algebra_and_its_applications_4_5881001_PDF) //! * ["Numerical Recipes: The Art of Scientific Computing", Press W.H., Teukolsky S.A., Vetterling W.T, Flannery B.P, 3rd ed., 2.6 Singular Value Decomposition](http://numerical.recipes/) //! //! //! #![allow(non_snake_case)] use crate::error::Failed; use crate::linalg::basic::arrays::Array2; use crate::numbers::basenum::Number; use crate::numbers::realnum::RealNumber; use std::fmt::Debug; /// Results of SVD decomposition #[derive(Debug, Clone)] pub struct SVD> { /// Left-singular vectors of _A_ pub U: M, /// Right-singular vectors of _A_ pub V: M, /// Singular values of the original matrix pub s: Vec, m: usize, n: usize, /// Tolerance tol: T, } impl> SVD { /// Diagonal matrix with singular values pub fn S(&self) -> M { let mut s = M::zeros(self.U.shape().1, self.V.shape().0); for i in 0..self.s.len() { s.set((i, i), self.s[i]); } s } } /// Trait that implements SVD decomposition routine for any matrix. pub trait SVDDecomposable: Array2 { /// Solves Ax = b. Overrides original matrix in the process. fn svd_solve_mut(self, b: Self) -> Result { self.svd_mut().and_then(|svd| svd.solve(b)) } /// Solves Ax = b fn svd_solve(&self, b: Self) -> Result { self.svd().and_then(|svd| svd.solve(b)) } /// Compute the SVD decomposition of a matrix. fn svd(&self) -> Result, Failed> { self.clone().svd_mut() } /// Compute the SVD decomposition of a matrix. The input matrix /// will be used for factorization. fn svd_mut(self) -> Result, Failed> { let mut U = self; let (m, n) = U.shape(); let (mut l, mut nm) = (0usize, 0usize); let (mut anorm, mut g, mut scale) = (T::zero(), T::zero(), T::zero()); let mut v = Self::zeros(n, n); let mut w = vec![T::zero(); n]; let mut rv1 = vec![T::zero(); n]; for i in 0..n { l = i + 2; rv1[i] = scale * g; g = T::zero(); let mut s = T::zero(); scale = T::zero(); if i < m { for k in i..m { scale += U.get((k, i)).abs(); } if scale.abs() > T::epsilon() { for k in i..m { U.div_element_mut((k, i), scale); s += *U.get((k, i)) * *U.get((k, i)); } let mut f = *U.get((i, i)); g = -::copysign(s.sqrt(), f); let h = f * g - s; U.set((i, i), f - g); for j in l - 1..n { s = T::zero(); for k in i..m { s += *U.get((k, i)) * *U.get((k, j)); } f = s / h; for k in i..m { U.add_element_mut((k, j), f * *U.get((k, i))); } } for k in i..m { U.mul_element_mut((k, i), scale); } } } w[i] = scale * g; g = T::zero(); let mut s = T::zero(); scale = T::zero(); if i < m && i + 1 != n { for k in l - 1..n { scale += U.get((i, k)).abs(); } if scale.abs() > T::epsilon() { for k in l - 1..n { U.div_element_mut((i, k), scale); s += *U.get((i, k)) * *U.get((i, k)); } let f = *U.get((i, l - 1)); g = -::copysign(s.sqrt(), f); let h = f * g - s; U.set((i, l - 1), f - g); for (k, rv1_k) in rv1.iter_mut().enumerate().take(n).skip(l - 1) { *rv1_k = *U.get((i, k)) / h; } for j in l - 1..m { s = T::zero(); for k in l - 1..n { s += *U.get((j, k)) * *U.get((i, k)); } for (k, rv1_k) in rv1.iter().enumerate().take(n).skip(l - 1) { U.add_element_mut((j, k), s * (*rv1_k)); } } for k in l - 1..n { U.mul_element_mut((i, k), scale); } } } anorm = T::max(anorm, w[i].abs() + rv1[i].abs()); } for i in (0..n).rev() { if i < n - 1 { if g != T::zero() { for j in l..n { v.set((j, i), (*U.get((i, j)) / *U.get((i, l))) / g); } for j in l..n { let mut s = T::zero(); for k in l..n { s += *U.get((i, k)) * *v.get((k, j)); } for k in l..n { v.add_element_mut((k, j), s * *v.get((k, i))); } } } for j in l..n { v.set((i, j), T::zero()); v.set((j, i), T::zero()); } } v.set((i, i), T::one()); g = rv1[i]; l = i; } for i in (0..usize::min(m, n)).rev() { l = i + 1; g = w[i]; for j in l..n { U.set((i, j), T::zero()); } if g.abs() > T::epsilon() { g = T::one() / g; for j in l..n { let mut s = T::zero(); for k in l..m { s += *U.get((k, i)) * *U.get((k, j)); } let f = (s / *U.get((i, i))) * g; for k in i..m { U.add_element_mut((k, j), f * *U.get((k, i))); } } for j in i..m { U.mul_element_mut((j, i), g); } } else { for j in i..m { U.set((j, i), T::zero()); } } U.add_element_mut((i, i), T::one()); } for k in (0..n).rev() { for iteration in 0..30 { let mut flag = true; l = k; while l != 0 { if l == 0 || rv1[l].abs() <= T::epsilon() * anorm { flag = false; break; } nm = l - 1; if w[nm].abs() <= T::epsilon() * anorm { break; } l -= 1; } if flag { let mut c = T::zero(); let mut s = T::one(); for i in l..k + 1 { let f = s * rv1[i]; rv1[i] = c * rv1[i]; if f.abs() <= T::epsilon() * anorm { break; } g = w[i]; let mut h = f.hypot(g); w[i] = h; h = T::one() / h; c = g * h; s = -f * h; for j in 0..m { let y = *U.get((j, nm)); let z = *U.get((j, i)); U.set((j, nm), y * c + z * s); U.set((j, i), z * c - y * s); } } } let z = w[k]; if l == k { if z < T::zero() { w[k] = -z; for j in 0..n { v.set((j, k), -*v.get((j, k))); } } break; } if iteration == 29 { panic!("no convergence in 30 iterations"); } let mut x = w[l]; nm = k - 1; let mut y = w[nm]; g = rv1[nm]; let mut h = rv1[k]; let mut f = ((y - z) * (y + z) + (g - h) * (g + h)) / (T::two() * h * y); g = f.hypot(T::one()); f = ((x - z) * (x + z) + h * ((y / (f + ::copysign(g, f))) - h)) / x; let mut c = T::one(); let mut s = T::one(); for j in l..=nm { let i = j + 1; g = rv1[i]; y = w[i]; h = s * g; g = c * g; let mut z = f.hypot(h); rv1[j] = z; c = f / z; s = h / z; f = x * c + g * s; g = g * c - x * s; h = y * s; y *= c; for jj in 0..n { x = *v.get((jj, j)); z = *v.get((jj, i)); v.set((jj, j), x * c + z * s); v.set((jj, i), z * c - x * s); } z = f.hypot(h); w[j] = z; if z.abs() > T::epsilon() { z = T::one() / z; c = f * z; s = h * z; } f = c * g + s * y; x = c * y - s * g; for jj in 0..m { y = *U.get((jj, j)); z = *U.get((jj, i)); U.set((jj, j), y * c + z * s); U.set((jj, i), z * c - y * s); } } rv1[l] = T::zero(); rv1[k] = f; w[k] = x; } } let mut inc = 1usize; let mut su = vec![T::zero(); m]; let mut sv = vec![T::zero(); n]; loop { inc *= 3; inc += 1; if inc > n { break; } } loop { inc /= 3; for i in inc..n { let sw = w[i]; for (k, su_k) in su.iter_mut().enumerate().take(m) { *su_k = *U.get((k, i)); } for (k, sv_k) in sv.iter_mut().enumerate().take(n) { *sv_k = *v.get((k, i)); } let mut j = i; while w[j - inc] < sw { w[j] = w[j - inc]; for k in 0..m { U.set((k, j), *U.get((k, j - inc))); } for k in 0..n { v.set((k, j), *v.get((k, j - inc))); } j -= inc; if j < inc { break; } } w[j] = sw; for (k, su_k) in su.iter().enumerate().take(m) { U.set((k, j), *su_k); } for (k, sv_k) in sv.iter().enumerate().take(n) { v.set((k, j), *sv_k); } } if inc <= 1 { break; } } for k in 0..n { let mut s = 0.; for i in 0..m { if U.get((i, k)) < &T::zero() { s += 1.; } } for j in 0..n { if v.get((j, k)) < &T::zero() { s += 1.; } } if s > (m + n) as f64 / 2. { for i in 0..m { U.set((i, k), -*U.get((i, k))); } for j in 0..n { v.set((j, k), -*v.get((j, k))); } } } Ok(SVD::new(U, v, w)) } } impl> SVD { pub(crate) fn new(U: M, V: M, s: Vec) -> SVD { let m = U.shape().0; let n = V.shape().0; let tol = T::half() * (T::from(m + n).unwrap() + T::one()).sqrt() * s[0] * T::epsilon(); SVD { U, V, s, m, n, tol } } pub(crate) fn solve(&self, mut b: M) -> Result { let p = b.shape().1; if self.U.shape().0 != b.shape().0 { panic!( "Dimensions do not agree. U.nrows should equal b.nrows but is {}, {}", self.U.shape().0, b.shape().0 ); } for k in 0..p { let mut tmp = vec![T::zero(); self.n]; for (j, tmp_j) in tmp.iter_mut().enumerate().take(self.n) { let mut r = T::zero(); if self.s[j] > self.tol { for i in 0..self.m { r += *self.U.get((i, j)) * *b.get((i, k)); } r /= self.s[j]; } *tmp_j = r; } for j in 0..self.n { let mut r = T::zero(); for (jj, tmp_jj) in tmp.iter().enumerate().take(self.n) { r += *self.V.get((j, jj)) * (*tmp_jj); } b.set((j, k), r); } } Ok(b) } } #[cfg(test)] mod tests { use super::*; use crate::linalg::basic::matrix::DenseMatrix; use approx::relative_eq; #[cfg_attr( all(target_arch = "wasm32", not(target_os = "wasi")), wasm_bindgen_test::wasm_bindgen_test )] #[test] fn decompose_symmetric() { let A = DenseMatrix::from_2d_array(&[ &[0.9000, 0.4000, 0.7000], &[0.4000, 0.5000, 0.3000], &[0.7000, 0.3000, 0.8000], ]) .unwrap(); let s: Vec = vec![1.7498382, 0.3165784, 0.1335834]; let U = DenseMatrix::from_2d_array(&[ &[0.6881997, -0.07121225, 0.7220180], &[0.3700456, 0.89044952, -0.2648886], &[0.6240573, -0.44947578, -0.639158], ]) .unwrap(); let V = DenseMatrix::from_2d_array(&[ &[0.6881997, -0.07121225, 0.7220180], &[0.3700456, 0.89044952, -0.2648886], &[0.6240573, -0.44947578, -0.6391588], ]) .unwrap(); let svd = A.svd().unwrap(); assert!(relative_eq!(V.abs(), svd.V.abs(), epsilon = 1e-4)); assert!(relative_eq!(U.abs(), svd.U.abs(), epsilon = 1e-4)); for (i, s_i) in s.iter().enumerate() { assert!((s_i - svd.s[i]).abs() < 1e-4); } } #[cfg_attr( all(target_arch = "wasm32", not(target_os = "wasi")), wasm_bindgen_test::wasm_bindgen_test )] #[test] fn decompose_asymmetric() { let A = DenseMatrix::from_2d_array(&[ &[ 1.19720880, -1.8391378, 0.3019585, -1.1165701, -1.7210814, 0.4918882, -0.04247433, ], &[ 0.06605075, 1.0315583, 0.8294362, -0.3646043, -1.6038017, -0.9188110, -0.63760340, ], &[ -1.02637715, 1.0747931, -0.8089055, -0.4726863, -0.2064826, -0.3325532, 0.17966051, ], &[ -1.45817729, -0.8942353, 0.3459245, 1.5068363, -2.0180708, -0.3696350, -1.19575563, ], &[ -0.07318103, -0.2783787, 1.2237598, 0.1995332, 0.2545336, -0.1392502, -1.88207227, ], &[ 0.88248425, -0.9360321, 0.1393172, 0.1393281, -0.3277873, -0.5553013, 1.63805985, ], &[ 0.12641406, -0.8710055, -0.2712301, 0.2296515, 1.1781535, -0.2158704, -0.27529472, ], ]) .unwrap(); let s: Vec = vec![ 3.8589375, 3.4396766, 2.6487176, 2.2317399, 1.5165054, 0.8109055, 0.2706515, ]; let U = DenseMatrix::from_2d_array(&[ &[ -0.3082776, 0.77676231, 0.01330514, 0.23231424, -0.47682758, 0.13927109, 0.02640713, ], &[ -0.4013477, -0.09112050, 0.48754440, 0.47371793, 0.40636608, 0.24600706, -0.37796295, ], &[ 0.0599719, -0.31406586, 0.45428229, -0.08071283, -0.38432597, 0.57320261, 0.45673993, ], &[ -0.7694214, -0.12681435, -0.05536793, -0.62189972, -0.02075522, -0.01724911, -0.03681864, ], &[ -0.3319069, -0.17984404, -0.54466777, 0.45335157, 0.19377726, 0.12333423, 0.55003852, ], &[ 0.1259351, 0.49087824, 0.16349687, -0.32080176, 0.64828744, 0.20643772, 0.38812467, ], &[ 0.1491884, 0.01768604, -0.47884363, -0.14108924, 0.03922507, 0.73034065, -0.43965505, ], ]) .unwrap(); let V = DenseMatrix::from_2d_array(&[ &[ -0.2122609, -0.54650056, 0.08071332, -0.43239135, -0.2925067, 0.1414550, 0.59769207, ], &[ -0.1943605, 0.63132116, -0.54059857, -0.37089970, -0.1363031, 0.2892641, 0.17774114, ], &[ 0.3031265, -0.06182488, 0.18579097, -0.38606409, -0.5364911, 0.2983466, -0.58642548, ], &[ 0.1844063, 0.24425278, 0.25923756, 0.59043765, -0.4435443, 0.3959057, 0.37019098, ], &[ -0.7164205, 0.30694911, 0.58264743, -0.07458095, -0.1142140, -0.1311972, -0.13124764, ], &[ -0.1103067, -0.10633600, 0.18257905, -0.03638501, 0.5722925, 0.7784398, -0.09153611, ], &[ -0.5156083, -0.36573746, -0.47613340, 0.41342817, -0.2659765, 0.1654796, -0.32346758, ], ]) .unwrap(); let svd = A.svd().unwrap(); assert!(relative_eq!(V.abs(), svd.V.abs(), epsilon = 1e-4)); assert!(relative_eq!(U.abs(), svd.U.abs(), epsilon = 1e-4)); for (i, s_i) in s.iter().enumerate() { assert!((s_i - svd.s[i]).abs() < 1e-4); } } #[cfg_attr( all(target_arch = "wasm32", not(target_os = "wasi")), wasm_bindgen_test::wasm_bindgen_test )] #[test] fn solve() { let a = DenseMatrix::from_2d_array(&[&[0.9, 0.4, 0.7], &[0.4, 0.5, 0.3], &[0.7, 0.3, 0.8]]) .unwrap(); let b = DenseMatrix::from_2d_array(&[&[0.5, 0.2], &[0.5, 0.8], &[0.5, 0.3]]).unwrap(); let expected_w = DenseMatrix::from_2d_array(&[&[-0.20, -1.28], &[0.87, 2.22], &[0.47, 0.66]]).unwrap(); let w = a.svd_solve_mut(b).unwrap(); assert!(relative_eq!(w, expected_w, epsilon = 1e-2)); } #[cfg_attr( all(target_arch = "wasm32", not(target_os = "wasi")), wasm_bindgen_test::wasm_bindgen_test )] #[test] fn decompose_restore() { let a = DenseMatrix::from_2d_array(&[&[1.0, 2.0, 3.0, 4.0], &[5.0, 6.0, 7.0, 8.0]]).unwrap(); let svd = a.svd().unwrap(); let u: &DenseMatrix = &svd.U; //U let v: &DenseMatrix = &svd.V; // V let s: &DenseMatrix = &svd.S(); // Sigma let a_hat = u.matmul(s).matmul(&v.transpose()); assert!(relative_eq!(a, a_hat, epsilon = 1e-3)); } }