| use matrix::{Matrix, BaseMatrix, BaseMatrixMut, MatrixSlice, MatrixSliceMut}; |
| use error::{Error, ErrorKind}; |
| |
| use std::any::Any; |
| use std::cmp; |
| use std::vec::*; |
| |
| use libnum::{Float, Signed}; |
| |
| /// Ensures that all singular values in the given singular value decomposition |
| /// are non-negative, making necessary corrections to the singular vectors. |
| /// |
| /// The SVD is represented by matrices `(b, u, v)`, where `b` is the diagonal matrix |
| /// containing the singular values, `u` is the matrix of left singular vectors |
| /// and v is the matrix of right singular vectors. |
| fn correct_svd_signs<T>(mut b: Matrix<T>, |
| mut u: Matrix<T>, |
| mut v: Matrix<T>) |
| -> (Matrix<T>, Matrix<T>, Matrix<T>) |
| where T: Any + Float + Signed |
| { |
| |
| // When correcting the signs of the singular vectors, we can choose |
| // to correct EITHER u or v. We make the choice depending on which matrix has the |
| // least number of rows. Later we will need to multiply all elements in columns by |
| // -1, which might be significantly faster in corner cases if we pick the matrix |
| // with the least amount of rows. |
| { |
| let ref mut shortest_matrix = if u.rows() <= v.rows() { &mut u } else { &mut v }; |
| let column_length = shortest_matrix.rows(); |
| let num_singular_values = cmp::min(b.rows(), b.cols()); |
| |
| for i in 0..num_singular_values { |
| if b[[i, i]] < T::zero() { |
| // Swap sign of singular value and column in u |
| b[[i, i]] = b[[i, i]].abs(); |
| |
| // Access the column as a slice and flip sign |
| let mut column = shortest_matrix.sub_slice_mut([0, i], column_length, 1); |
| column *= -T::one(); |
| } |
| } |
| } |
| (b, u, v) |
| } |
| |
| fn sort_svd<T>(mut b: Matrix<T>, |
| mut u: Matrix<T>, |
| mut v: Matrix<T>) |
| -> (Matrix<T>, Matrix<T>, Matrix<T>) |
| where T: Any + Float + Signed |
| { |
| |
| assert!(u.cols() == b.cols() && b.cols() == v.cols()); |
| |
| // This unfortunately incurs two allocations since we have no (simple) |
| // way to iterate over a matrix diagonal, only to copy it into a new Vector |
| let mut indexed_sorted_values: Vec<_> = b.diag().cloned().enumerate().collect(); |
| |
| // Sorting a vector of indices simultaneously with the singular values |
| // gives us a mapping between old and new (final) column indices. |
| indexed_sorted_values.sort_by(|&(_, ref x), &(_, ref y)| { |
| x.partial_cmp(y) |
| .expect("All singular values should be finite, and thus sortable.") |
| .reverse() |
| }); |
| |
| // Set the diagonal elements of the singular value matrix |
| for (i, &(_, value)) in indexed_sorted_values.iter().enumerate() { |
| b[[i, i]] = value; |
| } |
| |
| // Assuming N columns, the simultaneous sorting of indices and singular values yields |
| // a set of N (i, j) pairs which correspond to columns which must be swapped. However, |
| // for any (i, j) in this set, there is also (j, i). Keeping both of these would make us |
| // swap the columns back and forth, so we must remove the duplicates. We can avoid |
| // any further sorting or hashsets or similar by noting that we can simply |
| // remove any (i, j) for which j >= i. This also removes (i, i) pairs, |
| // i.e. columns that don't need to be swapped. |
| let swappable_pairs = indexed_sorted_values.into_iter() |
| .enumerate() |
| .map(|(new_index, (old_index, _))| (old_index, new_index)) |
| .filter(|&(old_index, new_index)| old_index < new_index); |
| |
| for (old_index, new_index) in swappable_pairs { |
| u.swap_cols(old_index, new_index); |
| v.swap_cols(old_index, new_index); |
| } |
| |
| (b, u, v) |
| } |
| |
| impl<T: Any + Float + Signed> Matrix<T> { |
| /// Singular Value Decomposition |
| /// |
| /// Computes the SVD using the Golub-Reinsch algorithm. |
| /// |
| /// Returns Σ, U, V, such that `self` = U Σ V<sup>T</sup>. Σ is a diagonal matrix whose elements |
| /// correspond to the non-negative singular values of the matrix. The singular values are ordered in |
| /// non-increasing order. U and V have orthonormal columns, and each column represents the |
| /// left and right singular vectors for the corresponding singular value in Σ, respectively. |
| /// |
| /// If `self` has M rows and N columns, the dimensions of the returned matrices |
| /// are as follows. |
| /// |
| /// If M >= N: |
| /// |
| /// - `Σ`: N x N |
| /// - `U`: M x N |
| /// - `V`: N x N |
| /// |
| /// If M < N: |
| /// |
| /// - `Σ`: M x M |
| /// - `U`: M x M |
| /// - `V`: N x M |
| /// |
| /// Note: This version of the SVD is sometimes referred to as the 'economy SVD'. |
| /// |
| /// # Failures |
| /// |
| /// This function may fail in some cases. The current decomposition whilst being |
| /// efficient is fairly basic. Hopefully the algorithm can be made not to fail in the near future. |
| pub fn svd(self) -> Result<(Matrix<T>, Matrix<T>, Matrix<T>), Error> { |
| let (b, u, v) = try!(self.svd_unordered()); |
| Ok(sort_svd(b, u, v)) |
| } |
| |
| fn svd_unordered(self) -> Result<(Matrix<T>, Matrix<T>, Matrix<T>), Error> { |
| let (b, u, v) = try!(self.svd_golub_reinsch()); |
| |
| // The Golub-Reinsch implementation sometimes spits out negative singular values, |
| // so we need to correct these. |
| Ok(correct_svd_signs(b, u, v)) |
| } |
| |
| fn svd_golub_reinsch(mut self) -> Result<(Matrix<T>, Matrix<T>, Matrix<T>), Error> { |
| let mut flipped = false; |
| |
| // The algorithm assumes rows > cols. If this is not the case we transpose and fix later. |
| if self.cols > self.rows { |
| self = self.transpose(); |
| flipped = true; |
| } |
| |
| let eps = T::from(3.0).unwrap() * T::epsilon(); |
| let n = self.cols; |
| |
| // Get the bidiagonal decomposition |
| let (mut b, mut u, mut v) = try!(self.bidiagonal_decomp() |
| .map_err(|_| Error::new(ErrorKind::DecompFailure, "Could not compute SVD."))); |
| |
| loop { |
| // Values to count the size of lower diagonal block |
| let mut q = 0; |
| let mut on_lower = true; |
| |
| // Values to count top block |
| let mut p = 0; |
| let mut on_middle = false; |
| |
| // Iterate through and hard set the super diag if converged |
| for i in (0..n - 1).rev() { |
| let (b_ii, b_sup_diag, diag_abs_sum): (T, T, T); |
| unsafe { |
| b_ii = *b.get_unchecked([i, i]); |
| b_sup_diag = b.get_unchecked([i, i + 1]).abs(); |
| diag_abs_sum = eps * (b_ii.abs() + b.get_unchecked([i + 1, i + 1]).abs()); |
| } |
| if b_sup_diag <= diag_abs_sum { |
| // Adjust q or p to define boundaries of sup-diagonal box |
| if on_lower { |
| q += 1; |
| } else if on_middle { |
| on_middle = false; |
| p = i + 1; |
| } |
| unsafe { |
| *b.get_unchecked_mut([i, i + 1]) = T::zero(); |
| } |
| } else { |
| if on_lower { |
| // No longer on the lower diagonal |
| on_middle = true; |
| on_lower = false; |
| } |
| } |
| } |
| |
| // We have converged! |
| if q == n - 1 { |
| break; |
| } |
| |
| // Zero off diagonals if needed. |
| for i in p..n - q - 1 { |
| let (b_ii, b_sup_diag): (T, T); |
| unsafe { |
| b_ii = *b.get_unchecked([i, i]); |
| b_sup_diag = *b.get_unchecked([i, i + 1]); |
| } |
| |
| if b_ii.abs() < eps { |
| let (c, s) = Matrix::<T>::givens_rot(b_ii, b_sup_diag); |
| let givens = Matrix::new(2, 2, vec![c, s, -s, c]); |
| let b_i = MatrixSliceMut::from_matrix(&mut b, [i, i], 1, 2); |
| let zerod_line = &b_i * givens; |
| |
| b_i.set_to(zerod_line.as_slice()); |
| } |
| } |
| |
| // Apply Golub-Kahan svd step |
| unsafe { |
| try!(Matrix::<T>::golub_kahan_svd_step(&mut b, &mut u, &mut v, p, q) |
| .map_err(|_| Error::new(ErrorKind::DecompFailure, "Could not compute SVD."))); |
| } |
| } |
| |
| if flipped { |
| Ok((b.transpose(), v, u)) |
| } else { |
| Ok((b, u, v)) |
| } |
| |
| } |
| |
| /// This function is unsafe as it makes assumptions about the dimensions |
| /// of the inputs matrices and does not check them. As a result if misused |
| /// this function can call `get_unchecked` on invalid indices. |
| unsafe fn golub_kahan_svd_step(b: &mut Matrix<T>, |
| u: &mut Matrix<T>, |
| v: &mut Matrix<T>, |
| p: usize, |
| q: usize) |
| -> Result<(), Error> { |
| let n = b.rows(); |
| |
| // C is the lower, right 2x2 square of aTa, where a is the |
| // middle block of b (between p and n-q). |
| // |
| // Computed as xTx + yTy, where y is the bottom 2x2 block of a |
| // and x are the two columns above it within a. |
| let c: Matrix<T>; |
| { |
| let y = MatrixSlice::from_matrix(&b, [n - q - 2, n - q - 2], 2, 2).into_matrix(); |
| if n - q - p - 2 > 0 { |
| let x = MatrixSlice::from_matrix(&b, [p, n - q - 2], n - q - p - 2, 2); |
| c = x.into_matrix().transpose() * x + y.transpose() * y; |
| } else { |
| c = y.transpose() * y; |
| } |
| } |
| |
| let c_eigs = try!(c.clone().eigenvalues()); |
| |
| // Choose eigenvalue closes to c[1,1]. |
| let lambda: T; |
| if (c_eigs[0] - *c.get_unchecked([1, 1])).abs() < |
| (c_eigs[1] - *c.get_unchecked([1, 1])).abs() { |
| lambda = c_eigs[0]; |
| } else { |
| lambda = c_eigs[1]; |
| } |
| |
| let b_pp = *b.get_unchecked([p, p]); |
| let mut alpha = (b_pp * b_pp) - lambda; |
| let mut beta = b_pp * *b.get_unchecked([p, p + 1]); |
| for k in p..n - q - 1 { |
| // Givens rot on columns k and k + 1 |
| let (c, s) = Matrix::<T>::givens_rot(alpha, beta); |
| let givens_mat = Matrix::new(2, 2, vec![c, s, -s, c]); |
| |
| { |
| // Pick the rows from b to be zerod. |
| let b_block = MatrixSliceMut::from_matrix(b, |
| [k.saturating_sub(1), k], |
| cmp::min(3, n - k.saturating_sub(1)), |
| 2); |
| let transformed = &b_block * &givens_mat; |
| b_block.set_to(transformed.as_slice()); |
| |
| let v_block = MatrixSliceMut::from_matrix(v, [0, k], n, 2); |
| let transformed = &v_block * &givens_mat; |
| v_block.set_to(transformed.as_slice()); |
| } |
| |
| alpha = *b.get_unchecked([k, k]); |
| beta = *b.get_unchecked([k + 1, k]); |
| |
| let (c, s) = Matrix::<T>::givens_rot(alpha, beta); |
| let givens_mat = Matrix::new(2, 2, vec![c, -s, s, c]); |
| |
| { |
| // Pick the columns from b to be zerod. |
| let b_block = MatrixSliceMut::from_matrix(b, [k, k], 2, cmp::min(3, n - k)); |
| let transformed = &givens_mat * &b_block; |
| b_block.set_to(transformed.as_slice()); |
| |
| let m = u.rows(); |
| let u_block = MatrixSliceMut::from_matrix(u, [0, k], m, 2); |
| let transformed = &u_block * givens_mat.transpose(); |
| u_block.set_to(transformed.as_slice()); |
| } |
| |
| if k + 2 < n - q { |
| alpha = *b.get_unchecked([k, k + 1]); |
| beta = *b.get_unchecked([k, k + 2]); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use matrix::{Matrix, BaseMatrix}; |
| use vector::Vector; |
| use super::sort_svd; |
| |
| fn validate_svd(mat: &Matrix<f64>, b: &Matrix<f64>, u: &Matrix<f64>, v: &Matrix<f64>) { |
| // b is diagonal (the singular values) |
| for (idx, row) in b.row_iter().enumerate() { |
| assert!(!row.iter().take(idx).any(|&x| x > 1e-10)); |
| assert!(!row.iter().skip(idx + 1).any(|&x| x > 1e-10)); |
| // Assert non-negativity of diagonal elements |
| assert!(row[idx] >= 0.0); |
| } |
| |
| let recovered = u * b * v.transpose(); |
| |
| assert_eq!(recovered.rows(), mat.rows()); |
| assert_eq!(recovered.cols(), mat.cols()); |
| |
| assert!(!mat.data() |
| .iter() |
| .zip(recovered.data().iter()) |
| .any(|(&x, &y)| (x - y).abs() > 1e-10)); |
| |
| // The transposition is due to the fact that there does not exist |
| // any column iterators at the moment, and we need to simultaneously iterate |
| // over the columns. Once they do exist, we should rewrite |
| // the below iterators to use iter_cols() or whatever instead. |
| let ref u_transposed = u.transpose(); |
| let ref v_transposed = v.transpose(); |
| let ref mat_transposed = mat.transpose(); |
| |
| let mut singular_triplets = u_transposed.row_iter().zip(b.diag()).zip(v_transposed.row_iter()) |
| // chained zipping results in nested tuple. Flatten it. |
| .map(|((u_col, singular_value), v_col)| (Vector::new(u_col.raw_slice()), singular_value, Vector::new(v_col.raw_slice()))); |
| |
| assert!(singular_triplets.by_ref() |
| // For a matrix M, each singular value σ and left and right singular vectors u and v respectively |
| // satisfy M v = σ u, so we take the difference |
| .map(|(ref u, sigma, ref v)| mat * v - u * sigma) |
| .flat_map(|v| v.into_vec().into_iter()) |
| .all(|x| x.abs() < 1e-10)); |
| |
| assert!(singular_triplets.by_ref() |
| // For a matrix M, each singular value σ and left and right singular vectors u and v respectively |
| // satisfy M_transposed u = σ v, so we take the difference |
| .map(|(ref u, sigma, ref v)| mat_transposed * u - v * sigma) |
| .flat_map(|v| v.into_vec().into_iter()) |
| .all(|x| x.abs() < 1e-10)); |
| } |
| |
| #[test] |
| fn test_sort_svd() { |
| let u = matrix![1.0, 2.0, 3.0; |
| 4.0, 5.0, 6.0]; |
| let b = matrix![4.0, 0.0, 0.0; |
| 0.0, 8.0, 0.0; |
| 0.0, 0.0, 2.0]; |
| let v = matrix![21.0, 22.0, 23.0; |
| 24.0, 25.0, 26.0; |
| 27.0, 28.0, 29.0]; |
| |
| let (b, u, v) = sort_svd(b, u, v); |
| |
| assert_eq!(b.data(), &vec![8.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 2.0]); |
| assert_eq!(u.data(), &vec![2.0, 1.0, 3.0, 5.0, 4.0, 6.0]); |
| assert_eq!(v.data(), |
| &vec![22.0, 21.0, 23.0, 25.0, 24.0, 26.0, 28.0, 27.0, 29.0]); |
| |
| } |
| |
| #[test] |
| fn test_svd_tall_matrix() { |
| // Note: This matrix is not arbitrary. It has been constructed specifically so that |
| // the "natural" order of the singular values it not sorted by default. |
| let mat = matrix![3.61833700244349288, -3.28382346228211697, 1.97968027781346501, -0.41869628192662156; |
| 3.96046289599926427, 0.70730060716580723, -2.80552479438772817, -1.45283286109873933; |
| 1.44435028724617442, 1.27749196276785826, -1.09858397535426366, -0.03159619816434689; |
| 1.13455445826500667, 0.81521390274755756, 3.99123446373437263, -2.83025703359666192; |
| -3.30895752093770579, -0.04979044289857298, 3.03248594516832792, 3.85962479743330977]; |
| let (b, u, v) = mat.clone().svd().unwrap(); |
| |
| let expected_values = vec![8.0, 6.0, 4.0, 2.0]; |
| |
| validate_svd(&mat, &b, &u, &v); |
| |
| // Assert the singular values are what we expect |
| assert!(expected_values.iter() |
| .zip(b.diag()) |
| .all(|(expected, actual)| (expected - actual).abs() < 1e-14)); |
| } |
| |
| #[test] |
| fn test_svd_short_matrix() { |
| // Note: This matrix is not arbitrary. It has been constructed specifically so that |
| // the "natural" order of the singular values it not sorted by default. |
| let mat = matrix![3.61833700244349288, 3.96046289599926427, 1.44435028724617442, 1.13455445826500645, -3.30895752093770579; |
| -3.28382346228211697, 0.70730060716580723, 1.27749196276785826, 0.81521390274755756, -0.04979044289857298; |
| 1.97968027781346545, -2.80552479438772817, -1.09858397535426366, 3.99123446373437263, 3.03248594516832792; |
| -0.41869628192662156, -1.45283286109873933, -0.03159619816434689, -2.83025703359666192, 3.85962479743330977]; |
| let (b, u, v) = mat.clone().svd().unwrap(); |
| |
| let expected_values = vec![8.0, 6.0, 4.0, 2.0]; |
| |
| validate_svd(&mat, &b, &u, &v); |
| |
| // Assert the singular values are what we expect |
| assert!(expected_values.iter() |
| .zip(b.diag()) |
| .all(|(expected, actual)| (expected - actual).abs() < 1e-14)); |
| } |
| |
| #[test] |
| fn test_svd_square_matrix() { |
| let mat = matrix![1.0, 2.0, 3.0, 4.0, 5.0; |
| 2.0, 4.0, 1.0, 2.0, 1.0; |
| 3.0, 1.0, 7.0, 1.0, 1.0; |
| 4.0, 2.0, 1.0, -1.0, 3.0; |
| 5.0, 1.0, 1.0, 3.0, 2.0]; |
| |
| let expected_values = vec![12.1739747429271112, |
| 5.2681047320525831, |
| 4.4942269799769843, |
| 2.9279675877385123, |
| 2.8758200827412224]; |
| |
| let (b, u, v) = mat.clone().svd().unwrap(); |
| validate_svd(&mat, &b, &u, &v); |
| |
| // Assert the singular values are what we expect |
| assert!(expected_values.iter() |
| .zip(b.diag()) |
| .all(|(expected, actual)| (expected - actual).abs() < 1e-12)); |
| } |
| } |