Start fixing SVD.

This commit is contained in:
sebcrozet 2019-03-18 11:23:19 +01:00
parent 010c009cff
commit e4748c69ce
13 changed files with 248 additions and 206 deletions

View File

@ -61,10 +61,10 @@ impl<N: Complex, D: Dim, S: Storage<N, D>> Reflection<N, D, S> {
} }
} }
/// Applies the reflection to the rows of `rhs`. /// Applies the reflection to the rows of `lhs`.
pub fn reflect_rows<R2: Dim, C2: Dim, S2, S3>( pub fn reflect_rows<R2: Dim, C2: Dim, S2, S3>(
&self, &self,
rhs: &mut Matrix<N, R2, C2, S2>, lhs: &mut Matrix<N, R2, C2, S2>,
work: &mut Vector<N, R2, S3>, work: &mut Vector<N, R2, S3>,
) where ) where
S2: StorageMut<N, R2, C2>, S2: StorageMut<N, R2, C2>,
@ -72,13 +72,13 @@ impl<N: Complex, D: Dim, S: Storage<N, D>> Reflection<N, D, S> {
ShapeConstraint: DimEq<C2, D> + AreMultipliable<R2, C2, D, U1>, ShapeConstraint: DimEq<C2, D> + AreMultipliable<R2, C2, D, U1>,
DefaultAllocator: Allocator<N, D> DefaultAllocator: Allocator<N, D>
{ {
rhs.mul_to(&self.axis, work); lhs.mul_to(&self.axis, work);
if !self.bias.is_zero() { if !self.bias.is_zero() {
work.add_scalar_mut(-self.bias); work.add_scalar_mut(-self.bias);
} }
let m_two: N = ::convert(-2.0f64); let m_two: N = ::convert(-2.0f64);
rhs.ger(m_two, &work, &self.axis.conjugate(), N::one()); lhs.ger(m_two, &work, &self.axis.conjugate(), N::one());
} }
} }

View File

@ -49,9 +49,9 @@ where
// contiguous. This prevents some useless copies. // contiguous. This prevents some useless copies.
uv: MatrixMN<N, R, C>, uv: MatrixMN<N, R, C>,
/// The diagonal elements of the decomposed matrix. /// The diagonal elements of the decomposed matrix.
pub diagonal: VectorN<N, DimMinimum<R, C>>, diagonal: VectorN<N, DimMinimum<R, C>>,
/// The off-diagonal elements of the decomposed matrix. /// The off-diagonal elements of the decomposed matrix.
pub off_diagonal: VectorN<N, DimDiff<DimMinimum<R, C>, U1>>, off_diagonal: VectorN<N, DimDiff<DimMinimum<R, C>, U1>>,
upper_diagonal: bool, upper_diagonal: bool,
} }
@ -143,9 +143,9 @@ where
Bidiagonal { Bidiagonal {
uv: matrix, uv: matrix,
diagonal: diagonal, diagonal,
off_diagonal: off_diagonal, off_diagonal,
upper_diagonal: upper_diagonal, upper_diagonal,
} }
} }
@ -231,7 +231,7 @@ where
res res
} }
/// Computes the orthogonal matrix `V` of this `U * D * V` decomposition. /// Computes the orthogonal matrix `V_t` of this `U * D * V_t` decomposition.
pub fn v_t(&self) -> MatrixMN<N, DimMinimum<R, C>, C> pub fn v_t(&self) -> MatrixMN<N, DimMinimum<R, C>, C>
where DefaultAllocator: Allocator<N, DimMinimum<R, C>, C> { where DefaultAllocator: Allocator<N, DimMinimum<R, C>, C> {
let (nrows, ncols) = self.uv.data.shape(); let (nrows, ncols) = self.uv.data.shape();
@ -258,13 +258,13 @@ where
} }
/// The diagonal part of this decomposed matrix. /// The diagonal part of this decomposed matrix.
pub fn diagonal(&self) -> &VectorN<N, DimMinimum<R, C>> { pub fn diagonal(&self) -> VectorN<N, DimMinimum<R, C>> {
&self.diagonal self.diagonal.map(|e| N::from_real(e.modulus()))
} }
/// The off-diagonal part of this decomposed matrix. /// The off-diagonal part of this decomposed matrix.
pub fn off_diagonal(&self) -> &VectorN<N, DimDiff<DimMinimum<R, C>, U1>> { pub fn off_diagonal(&self) -> VectorN<N, DimDiff<DimMinimum<R, C>, U1>> {
&self.off_diagonal self.off_diagonal.map(|e| N::from_real(e.modulus()))
} }
#[doc(hidden)] #[doc(hidden)]

View File

@ -1,7 +1,7 @@
//! Construction of givens rotations. //! Construction of givens rotations.
use alga::general::{Complex, Real}; use alga::general::{Complex, Real};
use num::Zero; use num::{Zero, One};
use num_complex::Complex as NumComplex; use num_complex::Complex as NumComplex;
use base::dimension::{Dim, U2}; use base::dimension::{Dim, U2};
@ -12,60 +12,37 @@ use base::{Vector, Matrix};
use geometry::UnitComplex; use geometry::UnitComplex;
/// A Givens rotation. /// A Givens rotation.
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub struct GivensRotation<N: Complex> { pub struct GivensRotation<N: Complex> {
// FIXME: c should be a `N::Real`. c: N::Real,
c: N,
s: N s: N
} }
// XXX: remove this
/// Computes the rotation `R` required such that the `y` component of `R * v` is zero.
///
/// Returns `None` if no rotation is needed (i.e. if `v.y == 0`). Otherwise, this returns the norm
/// of `v` and the rotation `r` such that `R * v = [ |v|, 0.0 ]^t` where `|v|` is the norm of `v`.
pub fn cancel_y<N: Real, S: Storage<N, U2>>(v: &Vector<N, U2, S>) -> Option<(UnitComplex<N>, N)> {
if !v[1].is_zero() {
let c = NumComplex::new(v[0], -v[1]);
Some(UnitComplex::from_complex_and_get(c))
} else {
None
}
}
// XXX: remove this
/// Computes the rotation `R` required such that the `x` component of `R * v` is zero.
///
/// Returns `None` if no rotation is needed (i.e. if `v.x == 0`). Otherwise, this returns the norm
/// of `v` and the rotation `r` such that `R * v = [ 0.0, |v| ]^t` where `|v|` is the norm of `v`.
pub fn cancel_x<N: Real, S: Storage<N, U2>>(v: &Vector<N, U2, S>) -> Option<(UnitComplex<N>, N)> {
if !v[0].is_zero() {
let c = NumComplex::new(v[1], v[0]);
Some(UnitComplex::from_complex_and_get(c))
} else {
None
}
}
// Matrix = UnitComplex * Matrix // Matrix = UnitComplex * Matrix
impl<N: Complex> GivensRotation<N> { impl<N: Complex> GivensRotation<N> {
/// The Givents rotation that does nothing.
pub fn identity() -> Self {
Self {
c: N::Real::one(),
s: N::zero()
}
}
/// Initializes a Givens rotation from its non-normalized cosine an sine components. /// Initializes a Givens rotation from its non-normalized cosine an sine components.
pub fn new(c: N, s: N) -> Self { pub fn new(c: N, s: N) -> (Self, N) {
let res = Self::try_new(c, s, N::Real::zero()).unwrap(); Self::try_new(c, s, N::Real::zero()).unwrap()
println!("The rot: {:?}", res);
res
} }
/// Initializes a Givens rotation form its non-normalized cosine an sine components. /// Initializes a Givens rotation form its non-normalized cosine an sine components.
pub fn try_new(c: N, s: N, eps: N::Real) -> Option<Self> { pub fn try_new(c: N, s: N, eps: N::Real) -> Option<(Self, N)> {
let (mod0, sign0) = c.to_exp(); let (mod0, sign0) = c.to_exp();
let denom = (mod0 * mod0 + s.modulus_squared()).sqrt(); let denom = (mod0 * mod0 + s.modulus_squared()).sqrt();
if denom > eps { if denom > eps {
let c = N::from_real(mod0 / denom); let norm = sign0.scale(denom);
let s = s / sign0.scale(denom); let c = mod0 / denom;
Some(Self { c, s }) let s = s / norm;
Some((Self { c, s }, norm))
} else { } else {
None None
} }
@ -79,8 +56,8 @@ impl<N: Complex> GivensRotation<N> {
if !v[1].is_zero() { if !v[1].is_zero() {
let (mod0, sign0) = v[0].to_exp(); let (mod0, sign0) = v[0].to_exp();
let denom = (mod0 * mod0 + v[1].modulus_squared()).sqrt(); let denom = (mod0 * mod0 + v[1].modulus_squared()).sqrt();
let c = N::from_real(mod0 / denom); let c = mod0 / denom;
let s = (sign0 * v[1].conjugate()).unscale(-denom); let s = -v[1] / sign0.scale(denom);
let r = sign0.scale(denom); let r = sign0.scale(denom);
Some((Self { c, s }, r)) Some((Self { c, s }, r))
} else { } else {
@ -94,11 +71,11 @@ impl<N: Complex> GivensRotation<N> {
/// of `v` and the rotation `r` such that `R * v = [ 0.0, |v| ]^t` where `|v|` is the norm of `v`. /// of `v` and the rotation `r` such that `R * v = [ 0.0, |v| ]^t` where `|v|` is the norm of `v`.
pub fn cancel_x<S: Storage<N, U2>>(v: &Vector<N, U2, S>) -> Option<(Self, N)> { pub fn cancel_x<S: Storage<N, U2>>(v: &Vector<N, U2, S>) -> Option<(Self, N)> {
if !v[0].is_zero() { if !v[0].is_zero() {
let (mod0, sign0) = v[0].to_exp(); let (mod1, sign1) = v[1].to_exp();
let denom = (mod0 * mod0 + v[1].modulus_squared()).sqrt(); let denom = (mod1 * mod1 + v[0].modulus_squared()).sqrt();
let c = N::from_real(mod0 / denom); let c = mod1 / denom;
let s = (sign0 * v[1].conjugate()).unscale(denom); let s = (v[0].conjugate() * sign1).unscale(denom);
let r = sign0.scale(denom); let r = sign1.scale(denom);
Some((Self { c, s }, r)) Some((Self { c, s }, r))
} else { } else {
None None
@ -106,7 +83,7 @@ impl<N: Complex> GivensRotation<N> {
} }
/// The cos part of this roration. /// The cos part of this roration.
pub fn c(&self) -> N { pub fn c(&self) -> N::Real {
self.c self.c
} }
@ -140,8 +117,8 @@ impl<N: Complex> GivensRotation<N> {
let a = *rhs.get_unchecked((0, j)); let a = *rhs.get_unchecked((0, j));
let b = *rhs.get_unchecked((1, j)); let b = *rhs.get_unchecked((1, j));
*rhs.get_unchecked_mut((0, j)) = c * a + -s.conjugate() * b; *rhs.get_unchecked_mut((0, j)) = a.scale(c) - s.conjugate() * b;
*rhs.get_unchecked_mut((1, j)) = s * a + c * b; *rhs.get_unchecked_mut((1, j)) = s * a + b.scale(c);
} }
} }
} }
@ -167,8 +144,8 @@ impl<N: Complex> GivensRotation<N> {
let a = *lhs.get_unchecked((j, 0)); let a = *lhs.get_unchecked((j, 0));
let b = *lhs.get_unchecked((j, 1)); let b = *lhs.get_unchecked((j, 1));
*lhs.get_unchecked_mut((j, 0)) = c * a + s * b; *lhs.get_unchecked_mut((j, 0)) = a.scale(c) + s * b;
*lhs.get_unchecked_mut((j, 1)) = -s.conjugate() * a + c * b; *lhs.get_unchecked_mut((j, 1)) = -s.conjugate() * a + b.scale(c);
} }
} }
} }

View File

@ -23,20 +23,20 @@ pub fn reflection_axis_mut<N: Complex, D: Dim, S: StorageMut<N, D>>(
let reflection_norm = reflection_sq_norm.sqrt(); let reflection_norm = reflection_sq_norm.sqrt();
let factor; let factor;
let scaled_norm; let signed_norm;
unsafe { unsafe {
let (modulus, exp) = column.vget_unchecked(0).to_exp(); let (modulus, sign) = column.vget_unchecked(0).to_exp();
scaled_norm = exp.scale(reflection_norm); signed_norm = sign.scale(reflection_norm);
factor = (reflection_sq_norm + modulus * reflection_norm) * ::convert(2.0); factor = (reflection_sq_norm + modulus * reflection_norm) * ::convert(2.0);
*column.vget_unchecked_mut(0) += scaled_norm; *column.vget_unchecked_mut(0) += signed_norm;
}; };
if !factor.is_zero() { if !factor.is_zero() {
column.unscale_mut(factor.sqrt()); column.unscale_mut(factor.sqrt());
(-scaled_norm, true) (-signed_norm, true)
} else { } else {
(-scaled_norm, false) (-signed_norm, false)
} }
} }
@ -67,7 +67,7 @@ pub fn clear_column_unchecked<N: Complex, R: Dim, C: Dim>(
} }
} }
/// Uses an hoseholder reflection to zero out the `irow`-th row, ending before the `shift + 1`-th /// Uses an householder reflection to zero out the `irow`-th row, ending before the `shift + 1`-th
/// superdiagonal element. /// superdiagonal element.
#[doc(hidden)] #[doc(hidden)]
pub fn clear_row_unchecked<N: Complex, R: Dim, C: Dim>( pub fn clear_row_unchecked<N: Complex, R: Dim, C: Dim>(
@ -85,6 +85,7 @@ pub fn clear_row_unchecked<N: Complex, R: Dim, C: Dim>(
axis.tr_copy_from(&top.columns_range(irow + shift..)); axis.tr_copy_from(&top.columns_range(irow + shift..));
let (reflection_norm, not_zero) = reflection_axis_mut(&mut axis); let (reflection_norm, not_zero) = reflection_axis_mut(&mut axis);
axis.conjugate_mut(); // So that reflect_rows actually cancels the first row.
*diag_elt = reflection_norm; *diag_elt = reflection_norm;
if not_zero { if not_zero {

View File

@ -30,6 +30,6 @@ pub use self::lu::*;
pub use self::permutation_sequence::*; pub use self::permutation_sequence::*;
pub use self::qr::*; pub use self::qr::*;
pub use self::schur::*; pub use self::schur::*;
pub use self::svd::*; //pub use self::svd::*;
pub use self::symmetric_eigen::*; pub use self::symmetric_eigen::*;
pub use self::symmetric_tridiagonal::*; pub use self::symmetric_tridiagonal::*;

View File

@ -417,10 +417,11 @@ where
if compute_q { if compute_q {
// XXX: we have to build the matrix manually because // XXX: we have to build the matrix manually because
// rot.to_rotation_matrix().unwrap() causes an ICE. // rot.to_rotation_matrix().unwrap() causes an ICE.
let c = N::from_real(rot.c());
q = Some(MatrixN::from_column_slice_generic( q = Some(MatrixN::from_column_slice_generic(
dim, dim,
dim, dim,
&[rot.c(), rot.s(), -rot.s().conjugate(), rot.c()], &[c, rot.s(), -rot.s().conjugate(), c],
)); ));
} }
} }
@ -479,9 +480,9 @@ fn compute_2x2_basis<N: Complex, S: Storage<N, U2, U2>>(
// This is necessary for numerical stability of the normalization of the complex // This is necessary for numerical stability of the normalization of the complex
// number. // number.
if x1.modulus() > x2.modulus() { if x1.modulus() > x2.modulus() {
Some(GivensRotation::new(x1, h10)) Some(GivensRotation::new(x1, h10).0)
} else { } else {
Some(GivensRotation::new(x2, h10)) Some(GivensRotation::new(x2, h10).0)
} }
} else { } else {
None None

View File

@ -1,20 +1,22 @@
#[cfg(feature = "serde-serialize")] #[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use num_complex::Complex; use num_complex::Complex as NumComplex;
use num::Zero;
use std::ops::MulAssign; use std::ops::MulAssign;
use approx::AbsDiffEq;
use alga::general::Real; use alga::general::Complex;
use allocator::Allocator; use allocator::Allocator;
use base::{DefaultAllocator, Matrix, Matrix2x3, MatrixMN, Vector2, VectorN}; use base::{DefaultAllocator, Matrix, Matrix2x3, MatrixMN, Vector2, VectorN};
use constraint::{SameNumberOfRows, ShapeConstraint}; use constraint::{SameNumberOfRows, ShapeConstraint};
use dimension::{Dim, DimDiff, DimMin, DimMinimum, DimSub, U1, U2}; use dimension::{Dim, DimDiff, DimMin, DimMinimum, DimSub, U1, U2};
use storage::Storage; use storage::Storage;
use geometry::UnitComplex;
use linalg::givens; use linalg::givens;
use linalg::symmetric_eigen; use linalg::symmetric_eigen;
use linalg::Bidiagonal; use linalg::Bidiagonal;
use linalg::givens::GivensRotation;
/// Singular Value Decomposition of a general matrix. /// Singular Value Decomposition of a general matrix.
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
@ -43,7 +45,7 @@ use linalg::Bidiagonal;
)) ))
)] )]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SVD<N: Real, R: DimMin<C>, C: Dim> pub struct SVD<N: Complex, R: DimMin<C>, C: Dim>
where DefaultAllocator: Allocator<N, DimMinimum<R, C>, C> where DefaultAllocator: Allocator<N, DimMinimum<R, C>, C>
+ Allocator<N, R, DimMinimum<R, C>> + Allocator<N, R, DimMinimum<R, C>>
+ Allocator<N, DimMinimum<R, C>> + Allocator<N, DimMinimum<R, C>>
@ -52,11 +54,13 @@ where DefaultAllocator: Allocator<N, DimMinimum<R, C>, C>
pub u: Option<MatrixMN<N, R, DimMinimum<R, C>>>, pub u: Option<MatrixMN<N, R, DimMinimum<R, C>>>,
/// The right-singular vectors `V^t` of this SVD. /// The right-singular vectors `V^t` of this SVD.
pub v_t: Option<MatrixMN<N, DimMinimum<R, C>, C>>, pub v_t: Option<MatrixMN<N, DimMinimum<R, C>, C>>,
// FIXME: this should a vector of N::Real
// because singular values are necessarily real.
/// The singular values of this SVD. /// The singular values of this SVD.
pub singular_values: VectorN<N, DimMinimum<R, C>>, pub singular_values: VectorN<N, DimMinimum<R, C>>,
} }
impl<N: Real, R: DimMin<C>, C: Dim> Copy for SVD<N, R, C> impl<N: Complex, R: DimMin<C>, C: Dim> Copy for SVD<N, R, C>
where where
DefaultAllocator: Allocator<N, DimMinimum<R, C>, C> DefaultAllocator: Allocator<N, DimMinimum<R, C>, C>
+ Allocator<N, R, DimMinimum<R, C>> + Allocator<N, R, DimMinimum<R, C>>
@ -66,7 +70,7 @@ where
VectorN<N, DimMinimum<R, C>>: Copy, VectorN<N, DimMinimum<R, C>>: Copy,
{} {}
impl<N: Real, R: DimMin<C>, C: Dim> SVD<N, R, C> impl<N: Complex, R: DimMin<C>, C: Dim> SVD<N, R, C>
where where
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal. DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
DefaultAllocator: Allocator<N, R, C> DefaultAllocator: Allocator<N, R, C>
@ -79,7 +83,7 @@ where
{ {
/// Computes the Singular Value Decomposition of `matrix` using implicit shift. /// Computes the Singular Value Decomposition of `matrix` using implicit shift.
pub fn new(matrix: MatrixMN<N, R, C>, compute_u: bool, compute_v: bool) -> Self { pub fn new(matrix: MatrixMN<N, R, C>, compute_u: bool, compute_v: bool) -> Self {
Self::try_new(matrix, compute_u, compute_v, N::default_epsilon(), 0).unwrap() Self::try_new(matrix, compute_u, compute_v, N::Real::default_epsilon(), 0).unwrap()
} }
/// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift. /// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift.
@ -96,7 +100,7 @@ where
mut matrix: MatrixMN<N, R, C>, mut matrix: MatrixMN<N, R, C>,
compute_u: bool, compute_u: bool,
compute_v: bool, compute_v: bool,
eps: N, eps: N::Real,
max_niter: usize, max_niter: usize,
) -> Option<Self> ) -> Option<Self>
{ {
@ -108,18 +112,20 @@ where
let min_nrows_ncols = nrows.min(ncols); let min_nrows_ncols = nrows.min(ncols);
let dim = min_nrows_ncols.value(); let dim = min_nrows_ncols.value();
let m_amax = matrix.amax(); let m_amax = matrix.camax();
if !m_amax.is_zero() { if !m_amax.is_zero() {
matrix /= m_amax; matrix.unscale_mut(m_amax);
} }
let mut b = Bidiagonal::new(matrix); let mut b = Bidiagonal::new(matrix);
let mut u = if compute_u { Some(b.u()) } else { None }; let mut u = if compute_u { Some(b.u()) } else { None };
let mut v_t = if compute_v { Some(b.v_t()) } else { None }; let mut v_t = if compute_v { Some(b.v_t()) } else { None };
let mut diagonal = b.diagonal();
let mut off_diagonal = b.off_diagonal();
let mut niter = 0; let mut niter = 0;
let (mut start, mut end) = Self::delimit_subproblem(&mut b, &mut u, &mut v_t, dim - 1, eps); let (mut start, mut end) = Self::delimit_subproblem(&mut diagonal, &mut off_diagonal, &mut u, &mut v_t, b.is_upper_diagonal(), dim - 1, eps);
while end != start { while end != start {
let subdim = end - start + 1; let subdim = end - start + 1;
@ -131,19 +137,19 @@ where
let mut vec; let mut vec;
{ {
let dm = b.diagonal[m]; let dm = diagonal[m];
let dn = b.diagonal[n]; let dn = diagonal[n];
let fm = b.off_diagonal[m]; let fm = off_diagonal[m];
let tmm = dm * dm + b.off_diagonal[m - 1] * b.off_diagonal[m - 1]; let tmm = dm * dm + off_diagonal[m - 1] * off_diagonal[m - 1];
let tmn = dm * fm; let tmn = dm * fm;
let tnn = dn * dn + fm * fm; let tnn = dn * dn + fm * fm;
let shift = symmetric_eigen::wilkinson_shift(tmm, tnn, tmn); let shift = symmetric_eigen::wilkinson_shift(tmm, tnn, tmn);
vec = Vector2::new( vec = Vector2::new(
b.diagonal[start] * b.diagonal[start] - shift, diagonal[start] * diagonal[start] - shift,
b.diagonal[start] * b.off_diagonal[start], diagonal[start] * off_diagonal[start],
); );
} }
@ -151,31 +157,31 @@ where
let m12 = if k == n - 1 { let m12 = if k == n - 1 {
N::zero() N::zero()
} else { } else {
b.off_diagonal[k + 1] off_diagonal[k + 1]
}; };
let mut subm = Matrix2x3::new( let mut subm = Matrix2x3::new(
b.diagonal[k], diagonal[k],
b.off_diagonal[k], off_diagonal[k],
N::zero(), N::zero(),
N::zero(), N::zero(),
b.diagonal[k + 1], diagonal[k + 1],
m12, m12,
); );
if let Some((rot1, norm1)) = givens::cancel_y(&vec) { if let Some((rot1, norm1)) = GivensRotation::cancel_y(&vec) {
rot1.conjugate() rot1.inverse()
.rotate_rows(&mut subm.fixed_columns_mut::<U2>(0)); .rotate_rows(&mut subm.fixed_columns_mut::<U2>(0));
if k > start { if k > start {
// This is not the first iteration. // This is not the first iteration.
b.off_diagonal[k - 1] = norm1; off_diagonal[k - 1] = norm1;
} }
let v = Vector2::new(subm[(0, 0)], subm[(1, 0)]); let v = Vector2::new(subm[(0, 0)], subm[(1, 0)]);
// FIXME: does the case `v.y == 0` ever happen? // FIXME: does the case `v.y == 0` ever happen?
let (rot2, norm2) = let (rot2, norm2) =
givens::cancel_y(&v).unwrap_or((UnitComplex::identity(), subm[(0, 0)])); GivensRotation::cancel_y(&v).unwrap_or((GivensRotation::identity(), subm[(0, 0)]));
rot2.rotate(&mut subm.fixed_columns_mut::<U2>(1)); rot2.rotate(&mut subm.fixed_columns_mut::<U2>(1));
subm[(0, 0)] = norm2; subm[(0, 0)] = norm2;
@ -197,12 +203,12 @@ where
} }
} }
b.diagonal[k + 0] = subm[(0, 0)]; diagonal[k + 0] = subm[(0, 0)];
b.diagonal[k + 1] = subm[(1, 1)]; diagonal[k + 1] = subm[(1, 1)];
b.off_diagonal[k + 0] = subm[(0, 1)]; off_diagonal[k + 0] = subm[(0, 1)];
if k != n - 1 { if k != n - 1 {
b.off_diagonal[k + 1] = subm[(1, 2)]; off_diagonal[k + 1] = subm[(1, 2)];
} }
vec.x = subm[(0, 1)]; vec.x = subm[(0, 1)];
@ -214,16 +220,16 @@ where
} else if subdim == 2 { } else if subdim == 2 {
// Solve the remaining 2x2 subproblem. // Solve the remaining 2x2 subproblem.
let (u2, s, v2) = Self::compute_2x2_uptrig_svd( let (u2, s, v2) = Self::compute_2x2_uptrig_svd(
b.diagonal[start], diagonal[start],
b.off_diagonal[start], off_diagonal[start],
b.diagonal[start + 1], diagonal[start + 1],
compute_u && b.is_upper_diagonal() || compute_v && !b.is_upper_diagonal(), compute_u && b.is_upper_diagonal() || compute_v && !b.is_upper_diagonal(),
compute_v && b.is_upper_diagonal() || compute_u && !b.is_upper_diagonal(), compute_v && b.is_upper_diagonal() || compute_u && !b.is_upper_diagonal(),
); );
b.diagonal[start + 0] = s[0]; diagonal[start + 0] = s[0];
b.diagonal[start + 1] = s[1]; diagonal[start + 1] = s[1];
b.off_diagonal[start] = N::zero(); off_diagonal[start] = N::zero();
if let Some(ref mut u) = u { if let Some(ref mut u) = u {
let rot = if b.is_upper_diagonal() { let rot = if b.is_upper_diagonal() {
@ -247,7 +253,7 @@ where
} }
// Re-delimit the subproblem in case some decoupling occurred. // Re-delimit the subproblem in case some decoupling occurred.
let sub = Self::delimit_subproblem(&mut b, &mut u, &mut v_t, end, eps); let sub = Self::delimit_subproblem(&mut diagonal, &mut off_diagonal, &mut u, &mut v_t, b.is_upper_diagonal(), end, eps);
start = sub.0; start = sub.0;
end = sub.1; end = sub.1;
@ -257,24 +263,25 @@ where
} }
} }
b.diagonal *= m_amax; diagonal.scale_mut(m_amax);
// Ensure all singular value are non-negative. // Ensure all singular value are non-negative.
for i in 0..dim { for i in 0..dim {
let sval = b.diagonal[i]; let sval = diagonal[i];
if sval < N::zero() { let (modulus, sign) = sval.to_exp();
b.diagonal[i] = -sval; if modulus != N::Real::zero() {
diagonal[i] = N::from_real(modulus);
if let Some(ref mut u) = u { if let Some(ref mut u) = u {
u.column_mut(i).neg_mut(); u.column_mut(i).mul_assign(sign);
} }
} }
} }
Some(Self { Some(Self {
u: u, u,
v_t: v_t, v_t,
singular_values: b.diagonal, singular_values: diagonal,
}) })
} }
@ -287,10 +294,10 @@ where
m22: N, m22: N,
compute_u: bool, compute_u: bool,
compute_v: bool, compute_v: bool,
) -> (Option<UnitComplex<N>>, Vector2<N>, Option<UnitComplex<N>>) ) -> (Option<GivensRotation<N>>, Vector2<N>, Option<GivensRotation<N>>)
{ {
let two: N = ::convert(2.0f64); let two: N::Real = ::convert(2.0f64);
let half: N = ::convert(0.5f64); let half: N::Real = ::convert(0.5f64);
let denom = (m11 + m22).hypot(m12) + (m11 - m22).hypot(m12); let denom = (m11 + m22).hypot(m12) + (m11 - m22).hypot(m12);
@ -298,24 +305,29 @@ where
// This prevents cancellation issues when constructing the vector `csv` below. If we chose // This prevents cancellation issues when constructing the vector `csv` below. If we chose
// otherwise, we would have v1 ~= m11 when m12 is small. This would cause catastrophic // otherwise, we would have v1 ~= m11 when m12 is small. This would cause catastrophic
// cancellation on `v1 * v1 - m11 * m11` below. // cancellation on `v1 * v1 - m11 * m11` below.
let v1 = two * m11 * m22 / denom; let mut v1 = (m11 * m22).scale(two / denom);
let v2 = half * denom; let mut v2 = N::from_real(half * denom);
let mut u = None; let mut u = None;
let mut v_t = None; let mut v_t = None;
if compute_u || compute_v { if compute_u || compute_v {
let csv = Vector2::new(m11 * m12, v1 * v1 - m11 * m11).normalize(); let (csv, sgn_v) = GivensRotation::new(m11 * m12, v1 * v1 - m11 * m11);
v1 *= sgn_v;
v2 *= sgn_v;
if compute_v { if compute_v {
v_t = Some(UnitComplex::new_unchecked(Complex::new(csv.x, csv.y))); v_t = Some(csv);
} }
if compute_u { if compute_u {
let cu = (m11 * csv.x + m12 * csv.y) / v1; let cu = (m11.scale(csv.c()) + m12 * csv.s()) / v1;
let su = (m22 * csv.y) / v1; let su = (m22 * csv.s()) / v1;
let (csu, sgn_u) = GivensRotation::new(cu, su);
u = Some(UnitComplex::new_unchecked(Complex::new(cu, su))); v1 *= sgn_u;
v2 *= sgn_u;
u = Some(csu);
} }
} }
@ -338,11 +350,13 @@ where
*/ */
fn delimit_subproblem( fn delimit_subproblem(
b: &mut Bidiagonal<N, R, C>, diagonal: &mut VectorN<N, DimMinimum<R, C>>,
off_diagonal: &mut VectorN<N, DimDiff<DimMinimum<R, C>, U1>>,
u: &mut Option<MatrixMN<N, R, DimMinimum<R, C>>>, u: &mut Option<MatrixMN<N, R, DimMinimum<R, C>>>,
v_t: &mut Option<MatrixMN<N, DimMinimum<R, C>, C>>, v_t: &mut Option<MatrixMN<N, DimMinimum<R, C>, C>>,
is_upper_diagonal: bool,
end: usize, end: usize,
eps: N, eps: N::Real,
) -> (usize, usize) ) -> (usize, usize)
{ {
let mut n = end; let mut n = end;
@ -350,20 +364,20 @@ where
while n > 0 { while n > 0 {
let m = n - 1; let m = n - 1;
if b.off_diagonal[m].is_zero() if off_diagonal[m].is_zero()
|| b.off_diagonal[m].abs() <= eps * (b.diagonal[n].abs() + b.diagonal[m].abs()) || off_diagonal[m].modulus() <= eps * (diagonal[n].modulus() + diagonal[m].modulus())
{ {
b.off_diagonal[m] = N::zero(); off_diagonal[m] = N::zero();
} else if b.diagonal[m].abs() <= eps { } else if diagonal[m].modulus() <= eps {
b.diagonal[m] = N::zero(); diagonal[m] = N::zero();
Self::cancel_horizontal_off_diagonal_elt(b, u, v_t, m, m + 1); Self::cancel_horizontal_off_diagonal_elt(diagonal, off_diagonal, u, v_t, is_upper_diagonal, m, m + 1);
if m != 0 { if m != 0 {
Self::cancel_vertical_off_diagonal_elt(b, u, v_t, m - 1); Self::cancel_vertical_off_diagonal_elt(diagonal, off_diagonal, u, v_t, is_upper_diagonal, m - 1);
} }
} else if b.diagonal[n].abs() <= eps { } else if diagonal[n].modulus() <= eps {
b.diagonal[n] = N::zero(); diagonal[n] = N::zero();
Self::cancel_vertical_off_diagonal_elt(b, u, v_t, m); Self::cancel_vertical_off_diagonal_elt(diagonal, off_diagonal, u, v_t, is_upper_diagonal, m);
} else { } else {
break; break;
} }
@ -379,18 +393,18 @@ where
while new_start > 0 { while new_start > 0 {
let m = new_start - 1; let m = new_start - 1;
if b.off_diagonal[m].abs() <= eps * (b.diagonal[new_start].abs() + b.diagonal[m].abs()) if off_diagonal[m].modulus() <= eps * (diagonal[new_start].modulus() + diagonal[m].modulus())
{ {
b.off_diagonal[m] = N::zero(); off_diagonal[m] = N::zero();
break; break;
} }
// FIXME: write a test that enters this case. // FIXME: write a test that enters this case.
else if b.diagonal[m].abs() <= eps { else if diagonal[m].modulus() <= eps {
b.diagonal[m] = N::zero(); diagonal[m] = N::zero();
Self::cancel_horizontal_off_diagonal_elt(b, u, v_t, m, n); Self::cancel_horizontal_off_diagonal_elt(diagonal, off_diagonal, u, v_t, is_upper_diagonal, m, n);
if m != 0 { if m != 0 {
Self::cancel_vertical_off_diagonal_elt(b, u, v_t, m - 1); Self::cancel_vertical_off_diagonal_elt(diagonal, off_diagonal, u, v_t, is_upper_diagonal, m - 1);
} }
break; break;
} }
@ -403,21 +417,23 @@ where
// Cancels the i-th off-diagonal element using givens rotations. // Cancels the i-th off-diagonal element using givens rotations.
fn cancel_horizontal_off_diagonal_elt( fn cancel_horizontal_off_diagonal_elt(
b: &mut Bidiagonal<N, R, C>, diagonal: &mut VectorN<N, DimMinimum<R, C>>,
off_diagonal: &mut VectorN<N, DimDiff<DimMinimum<R, C>, U1>>,
u: &mut Option<MatrixMN<N, R, DimMinimum<R, C>>>, u: &mut Option<MatrixMN<N, R, DimMinimum<R, C>>>,
v_t: &mut Option<MatrixMN<N, DimMinimum<R, C>, C>>, v_t: &mut Option<MatrixMN<N, DimMinimum<R, C>, C>>,
is_upper_diagonal: bool,
i: usize, i: usize,
end: usize, end: usize,
) )
{ {
let mut v = Vector2::new(b.off_diagonal[i], b.diagonal[i + 1]); let mut v = Vector2::new(off_diagonal[i], diagonal[i + 1]);
b.off_diagonal[i] = N::zero(); off_diagonal[i] = N::zero();
for k in i..end { for k in i..end {
if let Some((rot, norm)) = givens::cancel_x(&v) { if let Some((rot, norm)) = GivensRotation::cancel_x(&v) {
b.diagonal[k + 1] = norm; diagonal[k + 1] = norm;
if b.is_upper_diagonal() { if is_upper_diagonal {
if let Some(ref mut u) = *u { if let Some(ref mut u) = *u {
rot.inverse() rot.inverse()
.rotate_rows(&mut u.fixed_columns_with_step_mut::<U2>(i, k - i)); .rotate_rows(&mut u.fixed_columns_with_step_mut::<U2>(i, k - i));
@ -427,9 +443,9 @@ where
} }
if k + 1 != end { if k + 1 != end {
v.x = -rot.sin_angle() * b.off_diagonal[k + 1]; v.x = -rot.s() * off_diagonal[k + 1];
v.y = b.diagonal[k + 2]; v.y = diagonal[k + 2];
b.off_diagonal[k + 1] *= rot.cos_angle(); off_diagonal[k + 1] = off_diagonal[k + 1].scale(rot.c());
} }
} else { } else {
break; break;
@ -439,20 +455,22 @@ where
// Cancels the i-th off-diagonal element using givens rotations. // Cancels the i-th off-diagonal element using givens rotations.
fn cancel_vertical_off_diagonal_elt( fn cancel_vertical_off_diagonal_elt(
b: &mut Bidiagonal<N, R, C>, diagonal: &mut VectorN<N, DimMinimum<R, C>>,
off_diagonal: &mut VectorN<N, DimDiff<DimMinimum<R, C>, U1>>,
u: &mut Option<MatrixMN<N, R, DimMinimum<R, C>>>, u: &mut Option<MatrixMN<N, R, DimMinimum<R, C>>>,
v_t: &mut Option<MatrixMN<N, DimMinimum<R, C>, C>>, v_t: &mut Option<MatrixMN<N, DimMinimum<R, C>, C>>,
is_upper_diagonal: bool,
i: usize, i: usize,
) )
{ {
let mut v = Vector2::new(b.diagonal[i], b.off_diagonal[i]); let mut v = Vector2::new(diagonal[i], off_diagonal[i]);
b.off_diagonal[i] = N::zero(); off_diagonal[i] = N::zero();
for k in (0..i + 1).rev() { for k in (0..i + 1).rev() {
if let Some((rot, norm)) = givens::cancel_y(&v) { if let Some((rot, norm)) = GivensRotation::cancel_y(&v) {
b.diagonal[k] = norm; diagonal[k] = norm;
if b.is_upper_diagonal() { if is_upper_diagonal {
if let Some(ref mut v_t) = *v_t { if let Some(ref mut v_t) = *v_t {
rot.rotate(&mut v_t.fixed_rows_with_step_mut::<U2>(k, i - k)); rot.rotate(&mut v_t.fixed_rows_with_step_mut::<U2>(k, i - k));
} }
@ -462,9 +480,9 @@ where
} }
if k > 0 { if k > 0 {
v.x = b.diagonal[k - 1]; v.x = diagonal[k - 1];
v.y = rot.sin_angle() * b.off_diagonal[k - 1]; v.y = rot.s() * off_diagonal[k - 1];
b.off_diagonal[k - 1] *= rot.cos_angle(); off_diagonal[k - 1] = off_diagonal[k - 1].scale(rot.c());
} }
} else { } else {
break; break;
@ -474,12 +492,12 @@ where
/// Computes the rank of the decomposed matrix, i.e., the number of singular values greater /// Computes the rank of the decomposed matrix, i.e., the number of singular values greater
/// than `eps`. /// than `eps`.
pub fn rank(&self, eps: N) -> usize { pub fn rank(&self, eps: N::Real) -> usize {
assert!( assert!(
eps >= N::zero(), eps >= N::Real::zero(),
"SVD rank: the epsilon must be non-negative." "SVD rank: the epsilon must be non-negative."
); );
self.singular_values.iter().filter(|e| **e > eps).count() self.singular_values.iter().filter(|e| e.asum() > eps).count()
} }
/// Rebuild the original matrix. /// Rebuild the original matrix.
@ -507,18 +525,18 @@ where
/// Any singular value smaller than `eps` is assumed to be zero. /// Any singular value smaller than `eps` is assumed to be zero.
/// Returns `Err` if the right- and left- singular vectors have not /// Returns `Err` if the right- and left- singular vectors have not
/// been computed at construction-time. /// been computed at construction-time.
pub fn pseudo_inverse(mut self, eps: N) -> Result<MatrixMN<N, C, R>, &'static str> pub fn pseudo_inverse(mut self, eps: N::Real) -> Result<MatrixMN<N, C, R>, &'static str>
where where
DefaultAllocator: Allocator<N, C, R>, DefaultAllocator: Allocator<N, C, R>,
{ {
if eps < N::zero() { if eps < N::Real::zero() {
Err("SVD pseudo inverse: the epsilon must be non-negative.") Err("SVD pseudo inverse: the epsilon must be non-negative.")
} }
else { else {
for i in 0..self.singular_values.len() { for i in 0..self.singular_values.len() {
let val = self.singular_values[i]; let val = self.singular_values[i];
if val > eps { if val.asum() > eps {
self.singular_values[i] = N::one() / val; self.singular_values[i] = N::one() / val;
} else { } else {
self.singular_values[i] = N::zero(); self.singular_values[i] = N::zero();
@ -537,14 +555,14 @@ where
pub fn solve<R2: Dim, C2: Dim, S2>( pub fn solve<R2: Dim, C2: Dim, S2>(
&self, &self,
b: &Matrix<N, R2, C2, S2>, b: &Matrix<N, R2, C2, S2>,
eps: N, eps: N::Real,
) -> Result<MatrixMN<N, C, C2>, &'static str> ) -> Result<MatrixMN<N, C, C2>, &'static str>
where where
S2: Storage<N, R2, C2>, S2: Storage<N, R2, C2>,
DefaultAllocator: Allocator<N, C, C2> + Allocator<N, DimMinimum<R, C>, C2>, DefaultAllocator: Allocator<N, C, C2> + Allocator<N, DimMinimum<R, C>, C2>,
ShapeConstraint: SameNumberOfRows<R, R2>, ShapeConstraint: SameNumberOfRows<R, R2>,
{ {
if eps < N::zero() { if eps < N::Real::zero() {
Err("SVD solve: the epsilon must be non-negative.") Err("SVD solve: the epsilon must be non-negative.")
} }
else { else {
@ -557,7 +575,7 @@ where
for i in 0..self.singular_values.len() { for i in 0..self.singular_values.len() {
let val = self.singular_values[i]; let val = self.singular_values[i];
if val > eps { if val.asum() > eps {
col[i] /= val; col[i] /= val;
} else { } else {
col[i] = N::zero(); col[i] = N::zero();
@ -575,7 +593,7 @@ where
} }
} }
impl<N: Real, R: DimMin<C>, C: Dim, S: Storage<N, R, C>> Matrix<N, R, C, S> impl<N: Complex, R: DimMin<C>, C: Dim, S: Storage<N, R, C>> Matrix<N, R, C, S>
where where
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal. DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
DefaultAllocator: Allocator<N, R, C> DefaultAllocator: Allocator<N, R, C>
@ -605,7 +623,7 @@ where
self, self,
compute_u: bool, compute_u: bool,
compute_v: bool, compute_v: bool,
eps: N, eps: N::Real,
max_niter: usize, max_niter: usize,
) -> Option<SVD<N, R, C>> ) -> Option<SVD<N, R, C>>
{ {
@ -620,7 +638,7 @@ where
/// Computes the rank of this matrix. /// Computes the rank of this matrix.
/// ///
/// All singular values below `eps` are considered equal to 0. /// All singular values below `eps` are considered equal to 0.
pub fn rank(&self, eps: N) -> usize { pub fn rank(&self, eps: N::Real) -> usize {
let svd = SVD::new(self.clone_owned(), false, false); let svd = SVD::new(self.clone_owned(), false, false);
svd.rank(eps) svd.rank(eps)
} }
@ -628,7 +646,7 @@ where
/// Computes the pseudo-inverse of this matrix. /// Computes the pseudo-inverse of this matrix.
/// ///
/// All singular values below `eps` are considered equal to 0. /// All singular values below `eps` are considered equal to 0.
pub fn pseudo_inverse(self, eps: N) -> Result<MatrixMN<N, C, R>, &'static str> pub fn pseudo_inverse(self, eps: N::Real) -> Result<MatrixMN<N, C, R>, &'static str>
where where
DefaultAllocator: Allocator<N, C, R>, DefaultAllocator: Allocator<N, C, R>,
{ {

View File

@ -1,7 +1,7 @@
#[cfg(feature = "serde-serialize")] #[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use num::Zero; use num::{Zero, One};
use num_complex::Complex as NumComplex; use num_complex::Complex as NumComplex;
use approx::AbsDiffEq; use approx::AbsDiffEq;
use std::ops::MulAssign; use std::ops::MulAssign;
@ -119,6 +119,8 @@ where DefaultAllocator: Allocator<N, D, D> + Allocator<N, D>
q = Some(res.0); q = Some(res.0);
diag = res.1; diag = res.1;
off_diag = res.2; off_diag = res.2;
println!("Tridiagonalization q: {:.5?}", q);
} else { } else {
let res = SymmetricTridiagonal::new(m).unpack_tridiagonal(); let res = SymmetricTridiagonal::new(m).unpack_tridiagonal();
q = None; q = None;
@ -150,6 +152,7 @@ where DefaultAllocator: Allocator<N, D, D> + Allocator<N, D>
let j = i + 1; let j = i + 1;
if let Some((rot, norm)) = GivensRotation::cancel_y(&v) { if let Some((rot, norm)) = GivensRotation::cancel_y(&v) {
println!("Canceling: {:.5?} with norm: {:.5?}", rot, norm);
if i > start { if i > start {
// Not the first iteration. // Not the first iteration.
off_diag[i - 1] = norm; off_diag[i - 1] = norm;
@ -160,19 +163,33 @@ where DefaultAllocator: Allocator<N, D, D> + Allocator<N, D>
let mij = off_diag[i]; let mij = off_diag[i];
let cc = rot.c() * rot.c(); let cc = rot.c() * rot.c();
let ss = rot.s() * rot.s().conjugate(); let ss = rot.s().modulus_squared(); // rot.s() * rot.s().conjugate()
let cs = rot.c() * rot.s(); let cs = rot.s().scale(rot.c());
let b = cs * mij.conjugate() + cs.conjugate() * mij; // b = cs * mij.conjugate() + cs.conjugate() * mij
let b = N::from_real((cs * mij.conjugate()).real() * ::convert(2.0));
diag[i] = (cc * mii + ss * mjj) - b; diag[i] = (mii.scale(cc) + mjj.scale(ss)) - b;
diag[j] = (ss * mii + cc * mjj) + b; diag[j] = (mii.scale(ss) + mjj.scale(cc)) + b;
off_diag[i] = cs * (mii - mjj) + mij * cc - mij.conjugate() * rot.s() * rot.s(); off_diag[i] = cs * (mii - mjj) + mij.scale(cc) - mij.conjugate() * rot.s() * rot.s();
let mut mat = Matrix2::new(
mii, mij.conjugate(),
mij, mjj);
println!("The mat before rotate: {:.5}", mat);
println!("The v before rotate: {:.5?}", v);
rot.rotate(&mut mat);
rot.inverse().rotate_rows(&mut mat);
let mut v2 = v.clone();
rot.rotate(&mut v2);
println!("The v: {:.5?}", v2);
println!("The mat: {:.5}", mat);
println!("Its components: {:.5}, {:.5}, {:.5}", diag[i], diag[j], off_diag[i]);
if i != n - 1 { if i != n - 1 {
v.x = off_diag[i]; v.x = off_diag[i];
v.y = -rot.s() * off_diag[i + 1]; v.y = -rot.s() * off_diag[i + 1];
off_diag[i + 1] *= rot.c(); off_diag[i + 1] = off_diag[i + 1].scale(rot.c());
} }
if let Some(ref mut q) = q { if let Some(ref mut q) = q {
@ -197,12 +214,12 @@ where DefaultAllocator: Allocator<N, D, D> + Allocator<N, D>
diag[start + 0] = eigvals[0]; diag[start + 0] = eigvals[0];
diag[start + 1] = eigvals[1]; diag[start + 1] = eigvals[1];
println!("Eigvals: {:?}", eigvals); println!("Eigvals: {:.5?}", eigvals);
println!("m: {}", m); println!("m: {:.5}", m);
println!("Curr q: {:?}", q); println!("Curr q: {:.5?}", q);
if let Some(ref mut q) = q { if let Some(ref mut q) = q {
if let Some(rot) = GivensRotation::try_new(basis.x, basis.y, eps) { if let Some((rot, _)) = GivensRotation::try_new(basis.x, basis.y, eps) {
rot.rotate_rows(&mut q.fixed_columns_mut::<U2>(start)); rot.rotate_rows(&mut q.fixed_columns_mut::<U2>(start));
} }
} }

View File

@ -83,6 +83,7 @@ where DefaultAllocator: Allocator<N, D, D> + Allocator<N, DimDiff<D, U1>>
m.ger_symm(-N::one(), &p, &axis.conjugate(), N::one()); m.ger_symm(-N::one(), &p, &axis.conjugate(), N::one());
m.ger_symm(-N::one(), &axis, &p.conjugate(), N::one()); m.ger_symm(-N::one(), &axis, &p.conjugate(), N::one());
m.ger_symm(dot * ::convert(2.0), &axis, &axis.conjugate(), N::one()); m.ger_symm(dot * ::convert(2.0), &axis, &axis.conjugate(), N::one());
println!("The m: {}", m);
} }
} }

View File

@ -41,6 +41,7 @@ quickcheck! {
relative_eq!(m, &u * d * &v_t, epsilon = 1.0e-7) relative_eq!(m, &u * d * &v_t, epsilon = 1.0e-7)
} }
fn bidiagonal_static_square(m: Matrix4<RandComplex<f64>>) -> bool { fn bidiagonal_static_square(m: Matrix4<RandComplex<f64>>) -> bool {
let m = m.map(|e| e.0); let m = m.map(|e| e.0);
let bidiagonal = m.bidiagonalize(); let bidiagonal = m.bidiagonalize();

View File

@ -9,10 +9,9 @@ mod quickcheck_tests {
use std::cmp; use std::cmp;
quickcheck! { quickcheck! {
/*
fn symmetric_eigen(n: usize) -> bool { fn symmetric_eigen(n: usize) -> bool {
let n = cmp::max(1, cmp::min(n, 10)); let n = cmp::max(1, cmp::min(n, 10));
let m = DMatrix::<RandComplex<f64>>::new_random(n, n).map(|e| e.0); let m = DMatrix::<RandComplex<f64>>::new_random(n, n).map(|e| e.0).hermitian_part();
let eig = m.clone().symmetric_eigen(); let eig = m.clone().symmetric_eigen();
let recomp = eig.recompose(); let recomp = eig.recompose();
@ -23,7 +22,7 @@ mod quickcheck_tests {
fn symmetric_eigen_singular(n: usize) -> bool { fn symmetric_eigen_singular(n: usize) -> bool {
let n = cmp::max(1, cmp::min(n, 10)); let n = cmp::max(1, cmp::min(n, 10));
let mut m = DMatrix::<RandComplex<f64>>::new_random(n, n).map(|e| e.0); let mut m = DMatrix::<RandComplex<f64>>::new_random(n, n).map(|e| e.0).hermitian_part();
m.row_mut(n / 2).fill(na::zero()); m.row_mut(n / 2).fill(na::zero());
m.column_mut(n / 2).fill(na::zero()); m.column_mut(n / 2).fill(na::zero());
let eig = m.clone().symmetric_eigen(); let eig = m.clone().symmetric_eigen();
@ -35,7 +34,7 @@ mod quickcheck_tests {
} }
fn symmetric_eigen_static_square_4x4(m: Matrix4<RandComplex<f64>>) -> bool { fn symmetric_eigen_static_square_4x4(m: Matrix4<RandComplex<f64>>) -> bool {
let m = m.map(|e| e.0); let m = m.map(|e| e.0).hermitian_part();
let eig = m.symmetric_eigen(); let eig = m.symmetric_eigen();
let recomp = eig.recompose(); let recomp = eig.recompose();
@ -43,7 +42,6 @@ mod quickcheck_tests {
relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5)
} }
*/
fn symmetric_eigen_static_square_3x3(m: Matrix3<RandComplex<f64>>) -> bool { fn symmetric_eigen_static_square_3x3(m: Matrix3<RandComplex<f64>>) -> bool {
let m = m.map(|e| e.0).hermitian_part(); let m = m.map(|e| e.0).hermitian_part();
@ -57,7 +55,6 @@ mod quickcheck_tests {
relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5)
} }
/*
fn symmetric_eigen_static_square_2x2(m: Matrix2<RandComplex<f64>>) -> bool { fn symmetric_eigen_static_square_2x2(m: Matrix2<RandComplex<f64>>) -> bool {
let m = m.map(|e| e.0).hermitian_part(); let m = m.map(|e| e.0).hermitian_part();
let eig = m.symmetric_eigen(); let eig = m.symmetric_eigen();
@ -68,11 +65,9 @@ mod quickcheck_tests {
relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5)
} }
*/
} }
} }
/*
// Test proposed on the issue #176 of rulinalg. // Test proposed on the issue #176 of rulinalg.
#[test] #[test]
fn symmetric_eigen_singular_24x24() { fn symmetric_eigen_singular_24x24() {
@ -115,7 +110,7 @@ fn symmetric_eigen_singular_24x24() {
recomp.lower_triangle(), recomp.lower_triangle(),
epsilon = 1.0e-5 epsilon = 1.0e-5
)); ));
}*/ }
// #[cfg(feature = "arbitrary")] // #[cfg(feature = "arbitrary")]
// quickcheck! { // quickcheck! {

View File

@ -7,8 +7,11 @@ mod quickcheck_tests {
DMatrix, DVector, Matrix2, Matrix2x5, Matrix3, Matrix3x5, Matrix4, Matrix5x2, Matrix5x3, DMatrix, DVector, Matrix2, Matrix2x5, Matrix3, Matrix3x5, Matrix4, Matrix5x2, Matrix5x3,
}; };
use std::cmp; use std::cmp;
use core::helper::{RandScalar, RandComplex};
quickcheck! { quickcheck! {
/*
fn svd(m: DMatrix<f64>) -> bool { fn svd(m: DMatrix<f64>) -> bool {
if m.len() > 0 { if m.len() > 0 {
let svd = m.clone().svd(true, true); let svd = m.clone().svd(true, true);
@ -68,6 +71,7 @@ mod quickcheck_tests {
relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5) relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5)
} }
fn svd_static_square(m: Matrix4<f64>) -> bool { fn svd_static_square(m: Matrix4<f64>) -> bool {
let svd = m.svd(true, true); let svd = m.svd(true, true);
let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap());
@ -78,18 +82,27 @@ mod quickcheck_tests {
u.is_orthogonal(1.0e-5) && u.is_orthogonal(1.0e-5) &&
v_t.is_orthogonal(1.0e-5) v_t.is_orthogonal(1.0e-5)
} }
*/
fn svd_static_square_2x2(m: Matrix2<f64>) -> bool {
fn svd_static_square_2x2(m: Matrix2<RandComplex<f64>>) -> bool {
let m = m.map(|e| e.0);
let svd = m.svd(true, true); let svd = m.svd(true, true);
let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap());
let ds = Matrix2::from_diagonal(&s); let ds = Matrix2::from_diagonal(&s);
s.iter().all(|e| *e >= 0.0) && println!("u, s, v_t: {}{}{}", u, s, v_t);
println!("m: {}", m);
println!("recomp: {}", u * ds * v_t);
println!("uu_t, vv_t: {}{}", u * u.conjugate_transpose(), v_t.conjugate_transpose() * v_t);
s.iter().all(|e| e.re >= 0.0) &&
relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5) && relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5) &&
u.is_orthogonal(1.0e-5) && u.is_orthogonal(1.0e-5) &&
v_t.is_orthogonal(1.0e-5) v_t.is_orthogonal(1.0e-5)
} }
/*
fn svd_pseudo_inverse(m: DMatrix<f64>) -> bool { fn svd_pseudo_inverse(m: DMatrix<f64>) -> bool {
if m.len() > 0 { if m.len() > 0 {
let svd = m.clone().svd(true, true); let svd = m.clone().svd(true, true);
@ -140,9 +153,11 @@ mod quickcheck_tests {
true true
} }
*/
} }
} }
/*
// Test proposed on the issue #176 of rulinalg. // Test proposed on the issue #176 of rulinalg.
#[test] #[test]
fn svd_singular() { fn svd_singular() {
@ -342,3 +357,5 @@ fn svd_err() {
assert_eq!(Err("SVD recomposition: U and V^t have not been computed."), svd.clone().recompose()); assert_eq!(Err("SVD recomposition: U and V^t have not been computed."), svd.clone().recompose());
assert_eq!(Err("SVD pseudo inverse: the epsilon must be non-negative."), svd.clone().pseudo_inverse(-1.0)); assert_eq!(Err("SVD pseudo inverse: the epsilon must be non-negative."), svd.clone().pseudo_inverse(-1.0));
} }
*/

View File

@ -15,6 +15,20 @@ quickcheck! {
relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-7) relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-7)
} }
fn symm_tridiagonal_singular(n: usize) -> bool {
let n = cmp::max(1, cmp::min(n, 4));
let mut m = DMatrix::<RandComplex<f64>>::new_random(n, n).map(|e| e.0).hermitian_part();
m.row_mut(n / 2).fill(na::zero());
m.column_mut(n / 2).fill(na::zero());
let tri = m.clone().symmetric_tridiagonalize();
println!("Tri: {:?}", tri);
let recomp = tri.recompose();
println!("Recomp: {:?}", recomp);
relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-7)
}
fn symm_tridiagonal_static_square(m: Matrix4<RandComplex<f64>>) -> bool { fn symm_tridiagonal_static_square(m: Matrix4<RandComplex<f64>>) -> bool {
let m = m.map(|e| e.0).hermitian_part(); let m = m.map(|e| e.0).hermitian_part();
let tri = m.symmetric_tridiagonalize(); let tri = m.symmetric_tridiagonalize();