2017-08-14 01:53:04 +08:00
|
|
|
|
#[cfg(feature = "serde-serialize")]
|
2018-10-22 13:00:10 +08:00
|
|
|
|
use serde::{Deserialize, Serialize};
|
2017-08-14 01:53:04 +08:00
|
|
|
|
|
2019-03-03 02:33:49 +08:00
|
|
|
|
use approx::AbsDiffEq;
|
2020-03-21 19:16:46 +08:00
|
|
|
|
use num::Zero;
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
2019-03-23 21:29:07 +08:00
|
|
|
|
use crate::allocator::Allocator;
|
|
|
|
|
use crate::base::{DefaultAllocator, Matrix2, MatrixN, SquareMatrix, Vector2, VectorN};
|
|
|
|
|
use crate::dimension::{Dim, DimDiff, DimSub, U1, U2};
|
|
|
|
|
use crate::storage::Storage;
|
2020-03-21 19:16:46 +08:00
|
|
|
|
use simba::scalar::ComplexField;
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
2019-03-23 21:29:07 +08:00
|
|
|
|
use crate::linalg::givens::GivensRotation;
|
|
|
|
|
use crate::linalg::SymmetricTridiagonal;
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
2017-08-14 01:53:04 +08:00
|
|
|
|
/// Eigendecomposition of a symmetric matrix.
|
|
|
|
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
2018-05-19 23:15:15 +08:00
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "serde-serialize",
|
2020-03-21 19:16:46 +08:00
|
|
|
|
serde(bound(serialize = "DefaultAllocator: Allocator<N, D, D> +
|
2019-03-25 18:21:41 +08:00
|
|
|
|
Allocator<N::RealField, D>,
|
|
|
|
|
VectorN<N::RealField, D>: Serialize,
|
2020-03-21 19:16:46 +08:00
|
|
|
|
MatrixN<N, D>: Serialize"))
|
2018-05-19 23:15:15 +08:00
|
|
|
|
)]
|
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "serde-serialize",
|
2020-03-21 19:16:46 +08:00
|
|
|
|
serde(bound(deserialize = "DefaultAllocator: Allocator<N, D, D> +
|
2019-03-25 18:21:41 +08:00
|
|
|
|
Allocator<N::RealField, D>,
|
|
|
|
|
VectorN<N::RealField, D>: Deserialize<'de>,
|
2020-03-21 19:16:46 +08:00
|
|
|
|
MatrixN<N, D>: Deserialize<'de>"))
|
2018-05-19 23:15:15 +08:00
|
|
|
|
)]
|
2017-08-14 01:53:00 +08:00
|
|
|
|
#[derive(Clone, Debug)]
|
2019-03-25 18:19:36 +08:00
|
|
|
|
pub struct SymmetricEigen<N: ComplexField, D: Dim>
|
2019-03-25 18:21:41 +08:00
|
|
|
|
where DefaultAllocator: Allocator<N, D, D> + Allocator<N::RealField, D>
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
2017-08-03 01:37:44 +08:00
|
|
|
|
/// The eigenvectors of the decomposed matrix.
|
|
|
|
|
pub eigenvectors: MatrixN<N, D>,
|
|
|
|
|
|
|
|
|
|
/// The unsorted eigenvalues of the decomposed matrix.
|
2019-03-25 18:21:41 +08:00
|
|
|
|
pub eigenvalues: VectorN<N::RealField, D>,
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-25 18:19:36 +08:00
|
|
|
|
impl<N: ComplexField, D: Dim> Copy for SymmetricEigen<N, D>
|
2018-02-02 19:26:35 +08:00
|
|
|
|
where
|
2019-03-25 18:21:41 +08:00
|
|
|
|
DefaultAllocator: Allocator<N, D, D> + Allocator<N::RealField, D>,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
MatrixN<N, D>: Copy,
|
2019-03-25 18:21:41 +08:00
|
|
|
|
VectorN<N::RealField, D>: Copy,
|
2020-03-21 19:16:46 +08:00
|
|
|
|
{
|
|
|
|
|
}
|
2017-08-14 01:53:00 +08:00
|
|
|
|
|
2019-03-25 18:19:36 +08:00
|
|
|
|
impl<N: ComplexField, D: Dim> SymmetricEigen<N, D>
|
2019-03-25 18:21:41 +08:00
|
|
|
|
where DefaultAllocator: Allocator<N, D, D> + Allocator<N::RealField, D>
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
2017-08-03 01:37:44 +08:00
|
|
|
|
/// Computes the eigendecomposition of the given symmetric matrix.
|
|
|
|
|
///
|
2017-08-14 01:53:04 +08:00
|
|
|
|
/// Only the lower-triangular parts (including its diagonal) of `m` is read.
|
2017-08-03 01:37:44 +08:00
|
|
|
|
pub fn new(m: MatrixN<N, D>) -> Self
|
2018-02-02 19:26:35 +08:00
|
|
|
|
where
|
|
|
|
|
D: DimSub<U1>,
|
2020-03-21 19:16:46 +08:00
|
|
|
|
DefaultAllocator: Allocator<N, DimDiff<D, U1>> + Allocator<N::RealField, DimDiff<D, U1>>,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
2019-03-25 18:21:41 +08:00
|
|
|
|
Self::try_new(m, N::RealField::default_epsilon(), 0).unwrap()
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Computes the eigendecomposition of the given symmetric matrix with user-specified
|
|
|
|
|
/// convergence parameters.
|
|
|
|
|
///
|
2017-08-14 01:53:04 +08:00
|
|
|
|
/// Only the lower-triangular part (including its diagonal) of `m` is read.
|
2017-08-03 01:37:44 +08:00
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2017-08-14 01:53:04 +08:00
|
|
|
|
/// * `eps` − tolerance used to determine when a value converged to 0.
|
2017-08-03 01:37:44 +08:00
|
|
|
|
/// * `max_niter` − maximum total number of iterations performed by the algorithm. If this
|
|
|
|
|
/// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm
|
|
|
|
|
/// continues indefinitely until convergence.
|
2019-03-25 18:21:41 +08:00
|
|
|
|
pub fn try_new(m: MatrixN<N, D>, eps: N::RealField, max_niter: usize) -> Option<Self>
|
2018-02-02 19:26:35 +08:00
|
|
|
|
where
|
|
|
|
|
D: DimSub<U1>,
|
2020-03-21 19:16:46 +08:00
|
|
|
|
DefaultAllocator: Allocator<N, DimDiff<D, U1>> + Allocator<N::RealField, DimDiff<D, U1>>,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
|
|
|
|
Self::do_decompose(m, true, eps, max_niter).map(|(vals, vecs)| SymmetricEigen {
|
|
|
|
|
eigenvectors: vecs.unwrap(),
|
|
|
|
|
eigenvalues: vals,
|
2017-08-06 23:04:40 +08:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-02 19:26:35 +08:00
|
|
|
|
fn do_decompose(
|
|
|
|
|
mut m: MatrixN<N, D>,
|
|
|
|
|
eigenvectors: bool,
|
2019-03-25 18:21:41 +08:00
|
|
|
|
eps: N::RealField,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
max_niter: usize,
|
2019-03-25 18:21:41 +08:00
|
|
|
|
) -> Option<(VectorN<N::RealField, D>, Option<MatrixN<N, D>>)>
|
2018-02-02 19:26:35 +08:00
|
|
|
|
where
|
|
|
|
|
D: DimSub<U1>,
|
2020-03-21 19:16:46 +08:00
|
|
|
|
DefaultAllocator: Allocator<N, DimDiff<D, U1>> + Allocator<N::RealField, DimDiff<D, U1>>,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
|
|
|
|
assert!(
|
|
|
|
|
m.is_square(),
|
|
|
|
|
"Unable to compute the eigendecomposition of a non-square matrix."
|
|
|
|
|
);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let dim = m.nrows();
|
2019-03-03 02:33:49 +08:00
|
|
|
|
let m_amax = m.camax();
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
|
|
|
|
if !m_amax.is_zero() {
|
2019-03-03 02:33:49 +08:00
|
|
|
|
m.unscale_mut(m_amax);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-06 23:04:40 +08:00
|
|
|
|
let (mut q, mut diag, mut off_diag);
|
|
|
|
|
|
|
|
|
|
if eigenvectors {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let res = SymmetricTridiagonal::new(m).unpack();
|
|
|
|
|
q = Some(res.0);
|
|
|
|
|
diag = res.1;
|
|
|
|
|
off_diag = res.2;
|
|
|
|
|
} else {
|
|
|
|
|
let res = SymmetricTridiagonal::new(m).unpack_tridiagonal();
|
|
|
|
|
q = None;
|
|
|
|
|
diag = res.0;
|
|
|
|
|
off_diag = res.1;
|
2017-08-06 23:04:40 +08:00
|
|
|
|
}
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
|
|
|
|
if dim == 1 {
|
2019-03-03 02:33:49 +08:00
|
|
|
|
diag.scale_mut(m_amax);
|
2017-08-06 23:04:40 +08:00
|
|
|
|
return Some((diag, q));
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut niter = 0;
|
|
|
|
|
let (mut start, mut end) = Self::delimit_subproblem(&diag, &mut off_diag, dim - 1, eps);
|
|
|
|
|
|
|
|
|
|
while end != start {
|
|
|
|
|
let subdim = end - start + 1;
|
|
|
|
|
|
|
|
|
|
if subdim > 2 {
|
|
|
|
|
let m = end - 1;
|
|
|
|
|
let n = end;
|
|
|
|
|
|
|
|
|
|
let mut v = Vector2::new(
|
|
|
|
|
diag[start] - wilkinson_shift(diag[m], diag[n], off_diag[m]),
|
2018-02-02 19:26:35 +08:00
|
|
|
|
off_diag[start],
|
|
|
|
|
);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
2018-02-02 19:26:35 +08:00
|
|
|
|
for i in start..n {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let j = i + 1;
|
|
|
|
|
|
2019-03-03 02:33:49 +08:00
|
|
|
|
if let Some((rot, norm)) = GivensRotation::cancel_y(&v) {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
if i > start {
|
|
|
|
|
// Not the first iteration.
|
|
|
|
|
off_diag[i - 1] = norm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mii = diag[i];
|
|
|
|
|
let mjj = diag[j];
|
|
|
|
|
let mij = off_diag[i];
|
|
|
|
|
|
2019-03-03 02:33:49 +08:00
|
|
|
|
let cc = rot.c() * rot.c();
|
2019-03-19 21:22:59 +08:00
|
|
|
|
let ss = rot.s() * rot.s();
|
|
|
|
|
let cs = rot.c() * rot.s();
|
|
|
|
|
|
2019-03-23 21:29:07 +08:00
|
|
|
|
let b = cs * crate::convert(2.0) * mij;
|
2019-03-19 21:22:59 +08:00
|
|
|
|
|
|
|
|
|
diag[i] = (cc * mii + ss * mjj) - b;
|
|
|
|
|
diag[j] = (ss * mii + cc * mjj) + b;
|
|
|
|
|
off_diag[i] = cs * (mii - mjj) + mij * (cc - ss);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
|
|
|
|
if i != n - 1 {
|
|
|
|
|
v.x = off_diag[i];
|
2019-03-03 02:33:49 +08:00
|
|
|
|
v.y = -rot.s() * off_diag[i + 1];
|
2019-03-19 21:22:59 +08:00
|
|
|
|
off_diag[i + 1] *= rot.c();
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-06 23:04:40 +08:00
|
|
|
|
if let Some(ref mut q) = q {
|
2019-03-19 21:22:59 +08:00
|
|
|
|
let rot = GivensRotation::new_unchecked(rot.c(), N::from_real(rot.s()));
|
2017-08-06 23:04:40 +08:00
|
|
|
|
rot.inverse().rotate_rows(&mut q.fixed_columns_mut::<U2>(i));
|
|
|
|
|
}
|
2018-02-02 19:26:35 +08:00
|
|
|
|
} else {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-23 21:13:00 +08:00
|
|
|
|
if off_diag[m].norm1() <= eps * (diag[m].norm1() + diag[n].norm1()) {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
end -= 1;
|
|
|
|
|
}
|
2018-02-02 19:26:35 +08:00
|
|
|
|
} else if subdim == 2 {
|
|
|
|
|
let m = Matrix2::new(
|
2020-03-21 19:16:46 +08:00
|
|
|
|
diag[start],
|
|
|
|
|
off_diag[start].conjugate(),
|
|
|
|
|
off_diag[start],
|
|
|
|
|
diag[start + 1],
|
2018-02-02 19:26:35 +08:00
|
|
|
|
);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let eigvals = m.eigenvalues().unwrap();
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let basis = Vector2::new(eigvals.x - diag[start + 1], off_diag[start]);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
|
|
|
|
diag[start + 0] = eigvals[0];
|
|
|
|
|
diag[start + 1] = eigvals[1];
|
|
|
|
|
|
2017-08-06 23:04:40 +08:00
|
|
|
|
if let Some(ref mut q) = q {
|
2019-03-18 18:23:19 +08:00
|
|
|
|
if let Some((rot, _)) = GivensRotation::try_new(basis.x, basis.y, eps) {
|
2019-03-19 21:22:59 +08:00
|
|
|
|
let rot = GivensRotation::new_unchecked(rot.c(), N::from_real(rot.s()));
|
2017-08-06 23:04:40 +08:00
|
|
|
|
rot.rotate_rows(&mut q.fixed_columns_mut::<U2>(start));
|
|
|
|
|
}
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
end -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-24 12:48:42 +08:00
|
|
|
|
// Re-delimit the subproblem in case some decoupling occurred.
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let sub = Self::delimit_subproblem(&diag, &mut off_diag, end, eps);
|
|
|
|
|
|
|
|
|
|
start = sub.0;
|
2018-02-02 19:26:35 +08:00
|
|
|
|
end = sub.1;
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
|
|
|
|
niter += 1;
|
|
|
|
|
if niter == max_niter {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-03 02:33:49 +08:00
|
|
|
|
diag.scale_mut(m_amax);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
2017-08-06 23:04:40 +08:00
|
|
|
|
Some((diag, q))
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-02 19:26:35 +08:00
|
|
|
|
fn delimit_subproblem(
|
2019-03-25 18:21:41 +08:00
|
|
|
|
diag: &VectorN<N::RealField, D>,
|
|
|
|
|
off_diag: &mut VectorN<N::RealField, DimDiff<D, U1>>,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
end: usize,
|
2019-03-25 18:21:41 +08:00
|
|
|
|
eps: N::RealField,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
) -> (usize, usize)
|
|
|
|
|
where
|
|
|
|
|
D: DimSub<U1>,
|
2019-03-25 18:21:41 +08:00
|
|
|
|
DefaultAllocator: Allocator<N::RealField, DimDiff<D, U1>>,
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let mut n = end;
|
|
|
|
|
|
|
|
|
|
while n > 0 {
|
|
|
|
|
let m = n - 1;
|
|
|
|
|
|
2019-03-23 21:13:00 +08:00
|
|
|
|
if off_diag[m].norm1() > eps * (diag[n].norm1() + diag[m].norm1()) {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n == 0 {
|
|
|
|
|
return (0, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut new_start = n - 1;
|
|
|
|
|
while new_start > 0 {
|
|
|
|
|
let m = new_start - 1;
|
|
|
|
|
|
2018-02-02 19:26:35 +08:00
|
|
|
|
if off_diag[m].is_zero()
|
2019-03-23 21:13:00 +08:00
|
|
|
|
|| off_diag[m].norm1() <= eps * (diag[new_start].norm1() + diag[m].norm1())
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
2019-03-25 18:21:41 +08:00
|
|
|
|
off_diag[m] = N::RealField::zero();
|
2017-08-03 01:37:44 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_start -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(new_start, n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Rebuild the original matrix.
|
|
|
|
|
///
|
|
|
|
|
/// This is useful if some of the eigenvalues have been manually modified.
|
|
|
|
|
pub fn recompose(&self) -> MatrixN<N, D> {
|
|
|
|
|
let mut u_t = self.eigenvectors.clone();
|
2018-02-02 19:26:35 +08:00
|
|
|
|
for i in 0..self.eigenvalues.len() {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let val = self.eigenvalues[i];
|
2019-03-19 21:22:59 +08:00
|
|
|
|
u_t.column_mut(i).scale_mut(val);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
2019-03-23 21:13:00 +08:00
|
|
|
|
u_t.adjoint_mut();
|
2017-08-03 01:37:44 +08:00
|
|
|
|
&self.eigenvectors * u_t
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Computes the wilkinson shift, i.e., the 2x2 symmetric matrix eigenvalue to its tailing
|
|
|
|
|
/// component `tnn`.
|
|
|
|
|
///
|
|
|
|
|
/// The inputs are interpreted as the 2x2 matrix:
|
|
|
|
|
/// tmm tmn
|
|
|
|
|
/// tmn tnn
|
2019-03-25 18:19:36 +08:00
|
|
|
|
pub fn wilkinson_shift<N: ComplexField>(tmm: N, tnn: N, tmn: N) -> N {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let sq_tmn = tmn * tmn;
|
|
|
|
|
if !sq_tmn.is_zero() {
|
2018-09-24 12:48:42 +08:00
|
|
|
|
// We have the guarantee that the denominator won't be zero.
|
2019-03-23 21:29:07 +08:00
|
|
|
|
let d = (tmm - tnn) * crate::convert(0.5);
|
2017-08-03 01:37:44 +08:00
|
|
|
|
tnn - sq_tmn / (d + d.signum() * (d * d + sq_tmn).sqrt())
|
2018-02-02 19:26:35 +08:00
|
|
|
|
} else {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
tnn
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-06 23:04:40 +08:00
|
|
|
|
/*
|
|
|
|
|
*
|
|
|
|
|
* Computations of eigenvalues for symmetric matrices.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2019-03-25 18:19:36 +08:00
|
|
|
|
impl<N: ComplexField, D: DimSub<U1>, S: Storage<N, D, D>> SquareMatrix<N, D, S>
|
2020-03-21 19:16:46 +08:00
|
|
|
|
where DefaultAllocator: Allocator<N, D, D>
|
|
|
|
|
+ Allocator<N, DimDiff<D, U1>>
|
|
|
|
|
+ Allocator<N::RealField, D>
|
|
|
|
|
+ Allocator<N::RealField, DimDiff<D, U1>>
|
2018-02-02 19:26:35 +08:00
|
|
|
|
{
|
2017-08-14 01:52:46 +08:00
|
|
|
|
/// Computes the eigendecomposition of this symmetric matrix.
|
|
|
|
|
///
|
2017-08-14 01:53:04 +08:00
|
|
|
|
/// Only the lower-triangular part (including the diagonal) of `m` is read.
|
2017-08-14 01:52:46 +08:00
|
|
|
|
pub fn symmetric_eigen(self) -> SymmetricEigen<N, D> {
|
|
|
|
|
SymmetricEigen::new(self.into_owned())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Computes the eigendecomposition of the given symmetric matrix with user-specified
|
|
|
|
|
/// convergence parameters.
|
|
|
|
|
///
|
2017-08-14 01:53:04 +08:00
|
|
|
|
/// Only the lower-triangular part (including the diagonal) of `m` is read.
|
2017-08-14 01:52:46 +08:00
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2017-08-14 01:53:04 +08:00
|
|
|
|
/// * `eps` − tolerance used to determine when a value converged to 0.
|
2017-08-14 01:52:46 +08:00
|
|
|
|
/// * `max_niter` − maximum total number of iterations performed by the algorithm. If this
|
|
|
|
|
/// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm
|
|
|
|
|
/// continues indefinitely until convergence.
|
2020-03-21 19:16:46 +08:00
|
|
|
|
pub fn try_symmetric_eigen(
|
|
|
|
|
self,
|
|
|
|
|
eps: N::RealField,
|
|
|
|
|
max_niter: usize,
|
|
|
|
|
) -> Option<SymmetricEigen<N, D>>
|
|
|
|
|
{
|
2017-08-14 01:52:46 +08:00
|
|
|
|
SymmetricEigen::try_new(self.into_owned(), eps, max_niter)
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-06 23:04:40 +08:00
|
|
|
|
/// Computes the eigenvalues of this symmetric matrix.
|
|
|
|
|
///
|
|
|
|
|
/// Only the lower-triangular part of the matrix is read.
|
2019-03-25 18:21:41 +08:00
|
|
|
|
pub fn symmetric_eigenvalues(&self) -> VectorN<N::RealField, D> {
|
2020-03-21 19:16:46 +08:00
|
|
|
|
SymmetricEigen::do_decompose(
|
|
|
|
|
self.clone_owned(),
|
|
|
|
|
false,
|
|
|
|
|
N::RealField::default_epsilon(),
|
|
|
|
|
0,
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.0
|
2017-08-06 23:04:40 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
2019-03-23 21:29:07 +08:00
|
|
|
|
use crate::base::Matrix2;
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
|
|
|
|
fn expected_shift(m: Matrix2<f64>) -> f64 {
|
|
|
|
|
let vals = m.eigenvalues().unwrap();
|
|
|
|
|
|
|
|
|
|
if (vals.x - m.m22).abs() < (vals.y - m.m22).abs() {
|
|
|
|
|
vals.x
|
|
|
|
|
} else {
|
|
|
|
|
vals.y
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wilkinson_shift_random() {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
for _ in 0..1000 {
|
2017-08-03 01:37:44 +08:00
|
|
|
|
let m = Matrix2::new_random();
|
|
|
|
|
let m = m * m.transpose();
|
|
|
|
|
|
|
|
|
|
let expected = expected_shift(m);
|
|
|
|
|
let computed = super::wilkinson_shift(m.m11, m.m22, m.m12);
|
|
|
|
|
assert!(relative_eq!(expected, computed, epsilon = 1.0e-7));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wilkinson_shift_zero() {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let m = Matrix2::new(0.0, 0.0, 0.0, 0.0);
|
|
|
|
|
assert!(relative_eq!(
|
|
|
|
|
expected_shift(m),
|
|
|
|
|
super::wilkinson_shift(m.m11, m.m22, m.m12)
|
|
|
|
|
));
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wilkinson_shift_zero_diagonal() {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let m = Matrix2::new(0.0, 42.0, 42.0, 0.0);
|
|
|
|
|
assert!(relative_eq!(
|
|
|
|
|
expected_shift(m),
|
|
|
|
|
super::wilkinson_shift(m.m11, m.m22, m.m12)
|
|
|
|
|
));
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wilkinson_shift_zero_off_diagonal() {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let m = Matrix2::new(42.0, 0.0, 0.0, 64.0);
|
|
|
|
|
assert!(relative_eq!(
|
|
|
|
|
expected_shift(m),
|
|
|
|
|
super::wilkinson_shift(m.m11, m.m22, m.m12)
|
|
|
|
|
));
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wilkinson_shift_zero_trace() {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let m = Matrix2::new(42.0, 20.0, 20.0, -42.0);
|
|
|
|
|
assert!(relative_eq!(
|
|
|
|
|
expected_shift(m),
|
|
|
|
|
super::wilkinson_shift(m.m11, m.m22, m.m12)
|
|
|
|
|
));
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wilkinson_shift_zero_diag_diff_and_zero_off_diagonal() {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let m = Matrix2::new(42.0, 0.0, 0.0, 42.0);
|
|
|
|
|
assert!(relative_eq!(
|
|
|
|
|
expected_shift(m),
|
|
|
|
|
super::wilkinson_shift(m.m11, m.m22, m.m12)
|
|
|
|
|
));
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wilkinson_shift_zero_det() {
|
2018-02-02 19:26:35 +08:00
|
|
|
|
let m = Matrix2::new(2.0, 4.0, 4.0, 8.0);
|
|
|
|
|
assert!(relative_eq!(
|
|
|
|
|
expected_shift(m),
|
|
|
|
|
super::wilkinson_shift(m.m11, m.m22, m.m12)
|
|
|
|
|
));
|
2017-08-03 01:37:44 +08:00
|
|
|
|
}
|
|
|
|
|
}
|