#[cfg(feature = "serde-serialize")] use serde::{Deserialize, Serialize}; use num::Signed; use std::cmp; use na::allocator::Allocator; use na::dimension::{Const, Dim, DimMin, DimMinimum, U1}; use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; /// The SVD decomposition of a general matrix. #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize", serde(bound(serialize = "DefaultAllocator: Allocator> + Allocator + Allocator, OMatrix: Serialize, OMatrix: Serialize, OVector>: Serialize")) )] #[cfg_attr( feature = "serde-serialize", serde(bound(serialize = "DefaultAllocator: Allocator> + Allocator + Allocator, OMatrix: Deserialize<'de>, OMatrix: Deserialize<'de>, OVector>: Deserialize<'de>")) )] #[derive(Clone, Debug)] pub struct SVD, C: Dim> where DefaultAllocator: Allocator + Allocator> + Allocator, { /// The left-singular vectors `U` of this SVD. pub u: OMatrix, // TODO: should be OMatrix> /// The right-singular vectors `V^t` of this SVD. pub vt: OMatrix, // TODO: should be OMatrix, C> /// The singular values of this SVD. pub singular_values: OVector>, } impl, C: Dim> Copy for SVD where DefaultAllocator: Allocator + Allocator + Allocator>, OMatrix: Copy, OMatrix: Copy, OVector>: Copy, { } /// Trait implemented by floats (`f32`, `f64`) and complex floats (`Complex`, `Complex`) /// supported by the Singular Value Decompotition. pub trait SVDScalar, C: Dim>: Scalar where DefaultAllocator: Allocator + Allocator + Allocator> + Allocator, { /// Computes the SVD decomposition of `m`. fn compute(m: OMatrix) -> Option>; } impl, R: DimMin, C: Dim> SVD where DefaultAllocator: Allocator + Allocator + Allocator> + Allocator, { /// Computes the Singular Value Decomposition of `matrix`. pub fn new(m: OMatrix) -> Option { T::compute(m) } } macro_rules! svd_impl( ($t: ty, $lapack_func: path) => ( impl SVDScalar for $t where R: DimMin, DefaultAllocator: Allocator<$t, R, C> + Allocator<$t, R, R> + Allocator<$t, C, C> + Allocator<$t, DimMinimum> { fn compute(mut m: OMatrix<$t, R, C>) -> Option> { let (nrows, ncols) = m.shape_generic(); if nrows.value() == 0 || ncols.value() == 0 { return None; } let job = b'A'; let lda = nrows.value() as i32; let mut u = Matrix::zeros_generic(nrows, nrows); let mut s = Matrix::zeros_generic(nrows.min(ncols), Const::<1>); let mut vt = Matrix::zeros_generic(ncols, ncols); let ldu = nrows.value(); let ldvt = ncols.value(); let mut work = [ 0.0 ]; let mut lwork = -1 as i32; let mut info = 0; let mut iwork = vec![0; 8 * cmp::min(nrows.value(), ncols.value())]; unsafe { $lapack_func(job, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), lda, &mut s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), ldvt as i32, &mut work, lwork, &mut iwork, &mut info); } lapack_check!(info); lwork = work[0] as i32; let mut work = vec![0.0; lwork as usize]; unsafe { $lapack_func(job, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), lda, &mut s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), ldvt as i32, &mut work, lwork, &mut iwork, &mut info); } lapack_check!(info); Some(SVD { u: u, singular_values: s, vt: vt }) } } impl, C: Dim> SVD<$t, R, C> // TODO: All those bounds… where DefaultAllocator: Allocator<$t, R, C> + Allocator<$t, C, R> + Allocator<$t, U1, R> + Allocator<$t, U1, C> + Allocator<$t, R, R> + Allocator<$t, DimMinimum> + Allocator<$t, DimMinimum, R> + Allocator<$t, DimMinimum, C> + Allocator<$t, R, DimMinimum> + Allocator<$t, C, C> { /// Reconstructs the matrix from its decomposition. /// /// Useful if some components (e.g. some singular values) of this decomposition have /// been manually changed by the user. #[inline] pub fn recompose(self) -> OMatrix<$t, R, C> { let nrows = self.u.shape_generic().0; let ncols = self.vt.shape_generic().1; let min_nrows_ncols = nrows.min(ncols); let mut res: OMatrix<_, R, C> = Matrix::zeros_generic(nrows, ncols); { let mut sres = res.generic_view_mut((0, 0), (min_nrows_ncols, ncols)); sres.copy_from(&self.vt.rows_generic(0, min_nrows_ncols)); for i in 0 .. min_nrows_ncols.value() { let eigval = self.singular_values[i]; let mut row = sres.row_mut(i); row *= eigval; } } self.u * res } /// Computes the pseudo-inverse of the decomposed matrix. /// /// All singular value below epsilon will be set to zero instead of being inverted. #[inline] #[must_use] pub fn pseudo_inverse(&self, epsilon: $t) -> OMatrix<$t, C, R> { let nrows = self.u.shape_generic().0; let ncols = self.vt.shape_generic().1; let min_nrows_ncols = nrows.min(ncols); let mut res: OMatrix<_, C, R> = Matrix::zeros_generic(ncols, nrows); { let mut sres = res.generic_view_mut((0, 0), (min_nrows_ncols, nrows)); self.u.columns_generic(0, min_nrows_ncols).transpose_to(&mut sres); for i in 0 .. min_nrows_ncols.value() { let eigval = self.singular_values[i]; let mut row = sres.row_mut(i); if eigval.abs() > epsilon { row /= eigval } else { row.fill(0.0); } } } self.vt.tr_mul(&res) } /// The rank of the decomposed matrix. /// /// This is the number of singular values that are not too small (i.e. greater than /// the given `epsilon`). #[inline] #[must_use] pub fn rank(&self, epsilon: $t) -> usize { let mut i = 0; for e in self.singular_values.as_slice().iter() { if e.abs() > epsilon { i += 1; } } i } // TODO: add methods to retrieve the null-space and column-space? (Respectively // corresponding to the zero and non-zero singular values). } ); ); /* macro_rules! svd_complex_impl( ($name: ident, $t: ty, $lapack_func: path) => ( impl SVDScalar for Complex<$t> { fn compute(mut m: Matrix<$t, R, C, S>) -> Option> Option<(OMatrix, R, S::Alloc>, OVector<$t, DimMinimum, S::Alloc>, OMatrix, C, S::Alloc>)> where R: DimMin, S: ContiguousStorage, R, C>, S::Alloc: OwnedAllocator, R, C, S> + Allocator, R, R> + Allocator, C, C> + Allocator<$t, DimMinimum> { let (nrows, ncols) = m.shape_generic(); if nrows.value() == 0 || ncols.value() == 0 { return None; } let jobu = b'A'; let jobvt = b'A'; let lda = nrows.value() as i32; let min_nrows_ncols = nrows.min(ncols); let mut u = Matrix::zeros_generic(nrows, nrows); let mut s = Matrix::zeros_generic(min_nrows_ncols, U1); let mut vt = Matrix::zeros_generic(ncols, ncols); let ldu = nrows.value(); let ldvt = ncols.value(); let mut work = [ Complex::new(0.0, 0.0) ]; let mut lwork = -1 as i32; let mut rwork = vec![ 0.0; (5 * min_nrows_ncols.value()) ]; let mut info = 0; $lapack_func(jobu, jobvt, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), lda, s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), ldvt as i32, &mut work, lwork, &mut rwork, &mut info); lapack_check!(info); lwork = work[0].re as i32; let mut work = vec![Complex::new(0.0, 0.0); lwork as usize]; $lapack_func(jobu, jobvt, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), lda, s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), ldvt as i32, &mut work, lwork, &mut rwork, &mut info); lapack_check!(info); Some((u, s, vt)) } ); ); */ svd_impl!(f32, lapack::sgesdd); svd_impl!(f64, lapack::dgesdd); // svd_complex_impl!(lapack_svd_complex_f32, f32, lapack::cgesvd); // svd_complex_impl!(lapack_svd_complex_f64, f64, lapack::zgesvd);