rulinalg/matrix/decomposition/
eigen.rs

1use matrix::{Matrix, MatrixSliceMut, BaseMatrix, BaseMatrixMut};
2use norm::Euclidean;
3use error::{Error, ErrorKind};
4
5use std::cmp;
6use std::any::Any;
7
8use libnum::{Float, Signed};
9use libnum::{cast, abs};
10
11impl<T: Any + Float + Signed> Matrix<T> {
12    fn balance_matrix(&mut self) {
13        let n = self.rows();
14        let radix = T::one() + T::one();
15
16        debug_assert!(n == self.cols(),
17                      "Matrix must be square to produce balance matrix.");
18
19        let mut d = Matrix::<T>::identity(n);
20        let mut converged = false;
21
22        while !converged {
23            converged = true;
24
25            for i in 0..n {
26                let mut c = self.select_cols(&[i]).norm(Euclidean);
27                let mut r = self.select_rows(&[i]).norm(Euclidean);
28
29                let s = c * c + r * r;
30                let mut f = T::one();
31
32                while c < r / radix {
33                    c = c * radix;
34                    r = r / radix;
35                    f = f * radix;
36                }
37
38                while c >= r * radix {
39                    c = c / radix;
40                    r = r * radix;
41                    f = f / radix;
42                }
43
44                if (c * c + r * r) < cast::<f64, T>(0.95).unwrap() * s {
45                    converged = false;
46                    d.data[i * (self.cols + 1)] = f * d.data[i * (self.cols + 1)];
47
48                    for j in 0..n {
49                        self.data[j * self.cols + i] = f * self.data[j * self.cols + i];
50                        self.data[i * self.cols + j] = self.data[i * self.cols + j] / f;
51                    }
52                }
53            }
54        }
55    }
56
57    fn direct_2_by_2_eigenvalues(&self) -> Result<Vec<T>, Error> {
58        // The characteristic polynomial of a 2x2 matrix A is
59        // λ² − (a₁₁ + a₂₂)λ + (a₁₁a₂₂ − a₁₂a₂₁);
60        // the quadratic formula suffices.
61        let tr = self.data[0] + self.data[3];
62        let det = self.data[0] * self.data[3] - self.data[1] * self.data[2];
63
64        let two = T::one() + T::one();
65        let four = two + two;
66
67        let discr = tr * tr - four * det;
68
69        if discr < T::zero() {
70            Err(Error::new(ErrorKind::DecompFailure,
71                           "Matrix has complex eigenvalues. Currently unsupported, sorry!"))
72        } else {
73            let discr_root = discr.sqrt();
74            Ok(vec![(tr - discr_root) / two, (tr + discr_root) / two])
75        }
76
77    }
78
79    fn francis_shift_eigenvalues(self) -> Result<Vec<T>, Error> {
80        let n = self.rows();
81        debug_assert!(n > 2,
82                      "Francis shift only works on matrices greater than 2x2.");
83        debug_assert!(n == self.cols, "Matrix must be square for Francis shift.");
84
85        let mut h = try!(self
86            .upper_hessenberg()
87            .map_err(|_| Error::new(ErrorKind::DecompFailure, "Could not compute eigenvalues.")));
88        h.balance_matrix();
89
90        // The final index of the active matrix
91        let mut p = n - 1;
92
93        let eps = cast::<f64, T>(1e-20).expect("Failed to cast value for convergence check.");
94
95        while p > 1 {
96            let q = p - 1;
97            let s = h[[q, q]] + h[[p, p]];
98            let t = h[[q, q]] * h[[p, p]] - h[[q, p]] * h[[p, q]];
99
100            let mut x = h[[0, 0]] * h[[0, 0]] + h[[0, 1]] * h[[1, 0]] - h[[0, 0]] * s + t;
101            let mut y = h[[1, 0]] * (h[[0, 0]] + h[[1, 1]] - s);
102            let mut z = h[[1, 0]] * h[[2, 1]];
103
104            for k in 0..p - 1 {
105                let r = cmp::max(1, k) - 1;
106
107                let householder = try!(Matrix::make_householder(&[x, y, z]).map_err(|_| {
108                    Error::new(ErrorKind::DecompFailure, "Could not compute eigenvalues.")
109                }));
110
111                {
112                    // Apply householder transformation to block (on the left)
113                    let h_block = MatrixSliceMut::from_matrix(&mut h, [k, r], 3, n - r);
114                    let transformed = &householder * &h_block;
115                    h_block.set_to(transformed.as_slice());
116                }
117
118                let r = cmp::min(k + 4, p + 1);
119
120                {
121                    // Apply householder transformation to the block (on the right)
122                    let h_block = MatrixSliceMut::from_matrix(&mut h, [0, k], r, 3);
123                    let transformed = &h_block * householder.transpose();
124                    h_block.set_to(transformed.as_slice());
125                }
126
127                x = h[[k + 1, k]];
128                y = h[[k + 2, k]];
129
130                if k < p - 2 {
131                    z = h[[k + 3, k]];
132                }
133            }
134
135            let (c, s) = Matrix::givens_rot(x, y);
136            let givens_mat = Matrix::new(2, 2, vec![c, -s, s, c]);
137
138            {
139                // Apply Givens rotation to the block (on the left)
140                let h_block = MatrixSliceMut::from_matrix(&mut h, [q, p - 2], 2, n - p + 2);
141                let transformed = &givens_mat * &h_block;
142                h_block.set_to(transformed.as_slice());
143            }
144
145            {
146                // Apply Givens rotation to block (on the right)
147                let h_block = MatrixSliceMut::from_matrix(&mut h, [0, q], p + 1, 2);
148                let transformed = &h_block * givens_mat.transpose();
149                h_block.set_to(transformed.as_slice());
150            }
151
152            // Check for convergence
153            if abs(h[[p, q]]) < eps * (abs(h[[q, q]]) + abs(h[[p, p]])) {
154                h.data[p * h.cols + q] = T::zero();
155                p -= 1;
156            } else if abs(h[[p - 1, q - 1]]) < eps * (abs(h[[q - 1, q - 1]]) + abs(h[[q, q]])) {
157                h.data[(p - 1) * h.cols + q - 1] = T::zero();
158                p -= 2;
159            }
160        }
161
162        Ok(h.diag().cloned().collect::<Vec<_>>())
163    }
164
165    /// Eigenvalues of a square matrix.
166    ///
167    /// Returns a Vec of eigenvalues.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// use rulinalg::matrix::Matrix;
173    ///
174    /// let a = Matrix::new(4,4, (1..17).map(|v| v as f64).collect::<Vec<f64>>());
175    /// let e = a.eigenvalues().expect("We should be able to compute these eigenvalues!");
176    /// println!("{:?}", e);
177    /// ```
178    ///
179    /// # Panics
180    ///
181    /// - The matrix is not square.
182    ///
183    /// # Failures
184    ///
185    /// - Eigenvalues cannot be computed.
186    pub fn eigenvalues(self) -> Result<Vec<T>, Error> {
187        let n = self.rows();
188        assert!(n == self.cols,
189                "Matrix must be square for eigenvalue computation.");
190
191        match n {
192            1 => Ok(vec![self.data[0]]),
193            2 => self.direct_2_by_2_eigenvalues(),
194            _ => self.francis_shift_eigenvalues(),
195        }
196    }
197
198    fn direct_2_by_2_eigendecomp(&self) -> Result<(Vec<T>, Matrix<T>), Error> {
199        let eigenvalues = try!(self.direct_2_by_2_eigenvalues());
200        // Thanks to
201        // http://www.math.harvard.edu/archive/21b_fall_04/exhibits/2dmatrices/index.html
202        // for this characterization—
203        if self.data[2] != T::zero() {
204            let decomp_data = vec![eigenvalues[0] - self.data[3],
205                                   eigenvalues[1] - self.data[3],
206                                   self.data[2],
207                                   self.data[2]];
208            Ok((eigenvalues, Matrix::new(2, 2, decomp_data)))
209        } else if self.data[1] != T::zero() {
210            let decomp_data = vec![self.data[1],
211                                   self.data[1],
212                                   eigenvalues[0] - self.data[0],
213                                   eigenvalues[1] - self.data[0]];
214            Ok((eigenvalues, Matrix::new(2, 2, decomp_data)))
215        } else {
216            Ok((eigenvalues, Matrix::new(2, 2, vec![T::one(), T::zero(), T::zero(), T::one()])))
217        }
218    }
219
220    fn francis_shift_eigendecomp(self) -> Result<(Vec<T>, Matrix<T>), Error> {
221        let n = self.rows();
222        debug_assert!(n > 2,
223                      "Francis shift only works on matrices greater than 2x2.");
224        debug_assert!(n == self.cols, "Matrix must be square for Francis shift.");
225
226        let (u, mut h) = try!(self.upper_hess_decomp().map_err(|_| {
227            Error::new(ErrorKind::DecompFailure,
228                       "Could not compute eigen decomposition.")
229        }));
230        h.balance_matrix();
231        let mut transformation = Matrix::identity(n);
232
233        // The final index of the active matrix
234        let mut p = n - 1;
235
236        let eps = cast::<f64, T>(1e-20).expect("Failed to cast value for convergence check.");
237
238        while p > 1 {
239            let q = p - 1;
240            let s = h[[q, q]] + h[[p, p]];
241            let t = h[[q, q]] * h[[p, p]] - h[[q, p]] * h[[p, q]];
242
243            let mut x = h[[0, 0]] * h[[0, 0]] + h[[0, 1]] * h[[1, 0]] - h[[0, 0]] * s + t;
244            let mut y = h[[1, 0]] * (h[[0, 0]] + h[[1, 1]] - s);
245            let mut z = h[[1, 0]] * h[[2, 1]];
246
247            for k in 0..p - 1 {
248                let r = cmp::max(1, k) - 1;
249
250                let householder = try!(Matrix::make_householder(&[x, y, z]).map_err(|_| {
251                    Error::new(ErrorKind::DecompFailure,
252                               "Could not compute eigen decomposition.")
253                }));
254
255                {
256                    // Apply householder transformation to block (on the left)
257                    let h_block = MatrixSliceMut::from_matrix(&mut h, [k, r], 3, n - r);
258                    let transformed = &householder * &h_block;
259                    h_block.set_to(transformed.as_slice());
260                }
261
262                let r = cmp::min(k + 4, p + 1);
263
264                {
265                    // Apply householder transformation to the block (on the right)
266                    let h_block = MatrixSliceMut::from_matrix(&mut h, [0, k], r, 3);
267                    let transformed = &h_block * householder.transpose();
268                    h_block.set_to(transformed.as_slice());
269                }
270
271                {
272                    // Update the transformation matrix
273                    let trans_block =
274                        MatrixSliceMut::from_matrix(&mut transformation, [0, k], n, 3);
275                    let transformed = &trans_block * householder.transpose();
276                    trans_block.set_to(transformed.as_slice());
277                }
278
279                x = h[[k + 1, k]];
280                y = h[[k + 2, k]];
281
282                if k < p - 2 {
283                    z = h[[k + 3, k]];
284                }
285            }
286
287            let (c, s) = Matrix::givens_rot(x, y);
288            let givens_mat = Matrix::new(2, 2, vec![c, -s, s, c]);
289
290            {
291                // Apply Givens rotation to the block (on the left)
292                let h_block = MatrixSliceMut::from_matrix(&mut h, [q, p - 2], 2, n - p + 2);
293                let transformed = &givens_mat * &h_block;
294                h_block.set_to(transformed.as_slice());
295            }
296
297            {
298                // Apply Givens rotation to block (on the right)
299                let h_block = MatrixSliceMut::from_matrix(&mut h, [0, q], p + 1, 2);
300                let transformed = &h_block * givens_mat.transpose();
301                h_block.set_to(transformed.as_slice());
302            }
303
304            {
305                // Update the transformation matrix
306                let trans_block = MatrixSliceMut::from_matrix(&mut transformation, [0, q], n, 2);
307                let transformed = &trans_block * givens_mat.transpose();
308                trans_block.set_to(transformed.as_slice());
309            }
310
311            // Check for convergence
312            if abs(h[[p, q]]) < eps * (abs(h[[q, q]]) + abs(h[[p, p]])) {
313                h.data[p * h.cols + q] = T::zero();
314                p -= 1;
315            } else if abs(h[[p - 1, q - 1]]) < eps * (abs(h[[q - 1, q - 1]]) + abs(h[[q, q]])) {
316                h.data[(p - 1) * h.cols + q - 1] = T::zero();
317                p -= 2;
318            }
319        }
320
321        Ok((h.diag().cloned().collect::<Vec<_>>(), u * transformation))
322    }
323
324    /// Eigendecomposition of a square matrix.
325    ///
326    /// Returns a Vec of eigenvalues, and a matrix with eigenvectors as the columns.
327    ///
328    /// The eigenvectors are only gauranteed to be correct if the matrix is real-symmetric.
329    ///
330    /// # Examples
331    ///
332    /// ```
333    /// # #[macro_use] extern crate rulinalg; fn main() {
334    /// use rulinalg::matrix::Matrix;
335    ///
336    /// let a = matrix![3., 2., 4.;
337    ///                 2., 0., 2.;
338    ///                 4., 2., 3.];
339    ///
340    /// let (e, m) = a.eigendecomp().expect("We should be able to compute this eigendecomp!");
341    /// println!("{:?}", e);
342    /// println!("{:?}", m.data());
343    /// # }
344    /// ```
345    ///
346    /// # Panics
347    ///
348    /// - The matrix is not square.
349    ///
350    /// # Failures
351    ///
352    /// - The eigen decomposition can not be computed.
353    pub fn eigendecomp(self) -> Result<(Vec<T>, Matrix<T>), Error> {
354        let n = self.rows();
355        assert!(n == self.cols, "Matrix must be square for eigendecomp.");
356
357        match n {
358            1 => Ok((vec![self.data[0]], Matrix::ones(1, 1))),
359            2 => self.direct_2_by_2_eigendecomp(),
360            _ => self.francis_shift_eigendecomp(),
361        }
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use matrix::Matrix;
368
369    #[test]
370    fn test_1_by_1_matrix_eigenvalues() {
371        let a = Matrix::ones(1, 1) * 3.;
372        assert_eq!(vec![3.], a.eigenvalues().unwrap());
373    }
374
375    #[test]
376    fn test_2_by_2_matrix_eigenvalues() {
377        let a = matrix![1., 2.; 3., 4.];
378        // characteristic polynomial is λ² − 5λ − 2 = 0
379        assert_eq!(vec![(5. - (33.0f32).sqrt()) / 2., (5. + (33.0f32).sqrt()) / 2.],
380                   a.eigenvalues().unwrap());
381    }
382
383    #[test]
384    fn test_2_by_2_matrix_zeros_eigenvalues() {
385        let a = Matrix::zeros(2, 2);
386        // characteristic polynomial is λ² = 0
387        assert_eq!(vec![0.0, 0.0], a.eigenvalues().unwrap());
388    }
389
390    #[test]
391    fn test_2_by_2_matrix_complex_eigenvalues() {
392        // This test currently fails - complex eigenvalues would be nice though!
393        let a = matrix![1., -3.; 1., 1.];
394        // characteristic polynomial is λ² − λ + 4 = 0
395
396        // Decomposition will fail
397        assert!(a.eigenvalues().is_err());
398    }
399
400    #[test]
401    fn test_2_by_2_matrix_eigendecomp() {
402        let a = matrix![20., 4.; 20., 16.];
403        let (eigenvals, eigenvecs) = a.clone().eigendecomp().unwrap();
404
405        let lambda_1 = eigenvals[0];
406        let lambda_2 = eigenvals[1];
407
408        let v1 = vector![eigenvecs[[0, 0]], eigenvecs[[1, 0]]];
409        let v2 = vector![eigenvecs[[0, 1]], eigenvecs[[1, 1]]];
410
411        let epsilon = 0.00001;
412        assert!((&a * &v1 - &v1 * lambda_1).into_vec().iter().all(|&c| c < epsilon));
413        assert!((&a * &v2 - &v2 * lambda_2).into_vec().iter().all(|&c| c < epsilon));
414    }
415
416    #[test]
417    fn test_3_by_3_eigenvals() {
418        let a = matrix![17f64, 22., 27.;
419                        22., 29., 36.;
420                        27., 36., 45.];
421
422        let eigs = a.clone().eigenvalues().unwrap();
423
424        let eig_1 = 90.4026;
425        let eig_2 = 0.5973;
426        let eig_3 = 0.0;
427
428        assert!(eigs.iter().any(|x| (x - eig_1).abs() < 1e-4));
429        assert!(eigs.iter().any(|x| (x - eig_2).abs() < 1e-4));
430        assert!(eigs.iter().any(|x| (x - eig_3).abs() < 1e-4));
431    }
432
433    #[test]
434    fn test_5_by_5_eigenvals() {
435        let a = matrix![1f64, 2.0, 3.0, 4.0, 5.0;
436                        2.0, 4.0, 1.0, 2.0, 1.0;
437                        3.0, 1.0, 7.0, 1.0, 1.0;
438                        4.0, 2.0, 1.0, -1.0, 3.0;
439                        5.0, 1.0, 1.0, 3.0, 2.0];
440
441        let eigs = a.eigenvalues().unwrap();
442
443        let eig_1 = 12.174;
444        let eig_2 = 5.2681;
445        let eig_3 = -4.4942;
446        let eig_4 = 2.9279;
447        let eig_5 = -2.8758;
448
449        assert!(eigs.iter().any(|x| (x - eig_1).abs() < 1e-4));
450        assert!(eigs.iter().any(|x| (x - eig_2).abs() < 1e-4));
451        assert!(eigs.iter().any(|x| (x - eig_3).abs() < 1e-4));
452        assert!(eigs.iter().any(|x| (x - eig_4).abs() < 1e-4));
453        assert!(eigs.iter().any(|x| (x - eig_5).abs() < 1e-4));
454    }
455
456    #[test]
457    #[should_panic]
458    fn test_non_square_eigenvalues() {
459        let a: Matrix<f64> = Matrix::ones(2, 3);
460
461        let _ = a.eigenvalues();
462    }
463
464    #[test]
465    #[should_panic]
466    fn test_non_square_eigendecomp() {
467        let a: Matrix<f64> = Matrix::ones(2, 3);
468
469        let _ = a.eigendecomp();
470    }
471}