blob: 1f52e43f1be822443887f11835f161be7adbb621 [file] [log] [blame]
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));
}
}