Add na::eigen_qr that performs an eigendecomposition using the qr algorithm.

This commit is contained in:
Sébastien Crozet 2014-08-16 14:53:25 +02:00
parent 663f8b3ccb
commit 314f0c0043
3 changed files with 96 additions and 38 deletions

View File

@ -1,6 +1,6 @@
use std::num::{Zero, Float}; use std::num::{Zero, Float};
use traits::operations::Transpose; use traits::operations::{Transpose, ApproxEq};
use traits::structure::{ColSlice, Eye, Indexable}; use traits::structure::{ColSlice, Eye, Indexable, Diag};
use traits::geometry::Norm; use traits::geometry::Norm;
use std::cmp::min; use std::cmp::min;
@ -16,12 +16,16 @@ pub fn householder_matrix<N: Float,
V: Indexable<uint, N>> V: Indexable<uint, N>>
(dim: uint, start: uint, vec: V) -> M { (dim: uint, start: uint, vec: V) -> M {
let mut qk : M = Eye::new_identity(dim); let mut qk : M = Eye::new_identity(dim);
let stop = start + vec.shape(); let subdim = vec.shape();
assert!(stop <= dim);
let stop = subdim + start;
assert!(dim >= stop);
for j in range(start, stop) { for j in range(start, stop) {
for i in range(start, stop) { for i in range(start, stop) {
unsafe { unsafe {
let vv = vec.unsafe_at(i) * vec.unsafe_at(j); let vv = vec.unsafe_at(i - start) * vec.unsafe_at(j - start);
let qkij = qk.unsafe_at((i, j)); let qkij = qk.unsafe_at((i, j));
qk.unsafe_set((i, j), qkij - vv - vv); qk.unsafe_set((i, j), qkij - vv - vv);
} }
@ -60,11 +64,70 @@ pub fn qr<N: Float,
let x = v.unsafe_at(0); let x = v.unsafe_at(0);
v.unsafe_set(0, x - alpha); v.unsafe_set(0, x - alpha);
} }
let _ = v.normalize(); if !v.normalize().is_zero() {
let qk: M = householder_matrix(rows, 0, v); let qk: M = householder_matrix(rows, ite, v);
r = qk * r; r = qk * r;
q = q * Transpose::transpose_cpy(&qk); q = q * Transpose::transpose_cpy(&qk);
} }
}
(q, r) (q, r)
} }
/// Eigendecomposition of a square matrix using the qr algorithm.
pub fn eigen_qr<N: Float,
V: Indexable<uint, N> + Norm<N>,
V2: Zero,
M: Clone + Eye + ColSlice<V> + Transpose
+ Indexable<(uint, uint), N> + Mul<M, M>
+ Diag<V2> + ApproxEq<N> + Add<M, M>
+ Sub<M, M>>
(m: &M, eps: &N, niter: uint) -> (M, V2) {
let (rows, cols) = m.shape();
assert!(rows == cols, "The matrix being decomposed must be square.");
let mut eigenvectors: M = Eye::new_identity(rows);
let mut eigenvalues = m.clone();
let mut shifter: M = Eye::new_identity(rows);
let mut iter = 0u;
for _ in range(0, niter) {
let mut stop = true;
for j in range(0, cols) {
for i in range(0, j) {
if unsafe { eigenvalues.unsafe_at((i, j)) }.abs() >= *eps {
stop = false;
break;
}
}
for i in range(j + 1, rows) {
if unsafe { eigenvalues.unsafe_at((i, j)) }.abs() >= *eps {
stop = false;
break;
}
}
}
if stop {
break;
}
iter = iter + 1;
// FIXME: This is a very naive implementation.
let shift = unsafe { eigenvalues.unsafe_at((rows - 1, rows - 1)) };
for i in range(0, rows) {
unsafe { shifter.unsafe_set((i, i), shift.clone()) }
}
let (q, r) = qr(&(eigenvalues - shifter));
eigenvalues = r * q + shifter;
eigenvectors = eigenvectors * q;
}
(eigenvectors, eigenvalues.diag())
}

View File

@ -37,34 +37,28 @@ macro_rules! test_qr_impl(
); );
) )
macro_rules! test_eigen_qr_impl( // NOTE: deactivated untile we get a better convergence rate.
($t: ty) => { // macro_rules! test_eigen_qr_impl(
for _ in range(0u, 10000) { // ($t: ty) => {
let randmat : $t = random(); // for _ in range(0u, 10000) {
// let randmat : $t = random();
let (eigenvalues, eigenvectors) = na::eigen_qr(&randmat, &Float::epsilon(), 1000); // // Make it symetric so that we can recompose the matrix to test at the end.
// let randmat = na::transpose(&randmat) * randmat;
// FIXME: provide a method to initialize a matrix from its diagonal! //
let diag: $t = na::zero(); // let (eigenvectors, eigenvalues) = na::eigen_qr(&randmat, &Float::epsilon(), 100);
//
for i in range(0, na::dim::<$t>()) { // let diag: $t = Diag::from_diag(&eigenvalues);
diag.set((i, i), eigenvalues.at(i)); //
} // let recomp = eigenvectors * diag * na::transpose(&eigenvectors);
//
let recomp = na::transpose(&eigenvectors) * diag * eigenvectors; // println!("eigenvalues: {}", eigenvalues);
// println!(" mat: {}", randmat);
println!("mat: {}", randmat); // println!("recomp: {}", recomp);
println!("eigenvectors: {}", eigenvectors); //
println!("eigenvalues: {}", eigenvalues); // assert!(na::approx_eq_eps(&randmat, &recomp, &1.0e-2));
println!("recomp: {}", recomp); // }
// }
assert!(false); // )
fail!("what!");
assert!(na::approx_eq(&randmat, &recomp));
}
}
)
#[test] #[test]
fn test_transpose_mat1() { fn test_transpose_mat1() {
@ -295,6 +289,7 @@ fn test_qr_mat6() {
test_qr_impl!(Mat6<f64>); test_qr_impl!(Mat6<f64>);
} }
// NOTE: deactivated untile we get a better convergence rate.
// #[test] // #[test]
// fn test_eigen_qr_mat1() { // fn test_eigen_qr_mat1() {
// test_eigen_qr_impl!(Mat1<f64>); // test_eigen_qr_impl!(Mat1<f64>);

View File

@ -144,7 +144,7 @@ pub trait Indexable<Index, Res> {
/// Swaps the `i`-th element of `self` with its `j`-th element. /// Swaps the `i`-th element of `self` with its `j`-th element.
fn swap(&mut self, i: Index, j: Index); fn swap(&mut self, i: Index, j: Index);
/// Returns the shape of the iterable range /// Returns the shape of the iterable range.
fn shape(&self) -> Index; fn shape(&self) -> Index;
/// Reads the `i`-th element of `self`. /// Reads the `i`-th element of `self`.