nalgebra_lapack/
symmetric_eigen.rs

1#[cfg(feature = "serde-serialize")]
2use serde::{Deserialize, Serialize};
3
4use num::Zero;
5use std::ops::MulAssign;
6
7use simba::scalar::RealField;
8
9use crate::ComplexHelper;
10use na::allocator::Allocator;
11use na::dimension::{Const, Dim};
12use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar};
13
14use lapack;
15
16/// Eigendecomposition of a real square symmetric matrix with real eigenvalues.
17#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
18#[cfg_attr(
19    feature = "serde-serialize",
20    serde(bound(serialize = "DefaultAllocator: Allocator<D, D> +
21                           Allocator<D>,
22         OVector<T, D>: Serialize,
23         OMatrix<T, D, D>: Serialize"))
24)]
25#[cfg_attr(
26    feature = "serde-serialize",
27    serde(bound(deserialize = "DefaultAllocator: Allocator<D, D> +
28                           Allocator<D>,
29         OVector<T, D>: Deserialize<'de>,
30         OMatrix<T, D, D>: Deserialize<'de>"))
31)]
32#[derive(Clone, Debug)]
33pub struct SymmetricEigen<T: Scalar, D: Dim>
34where
35    DefaultAllocator: Allocator<D> + Allocator<D, D>,
36{
37    /// The eigenvectors of the decomposed matrix.
38    pub eigenvectors: OMatrix<T, D, D>,
39
40    /// The unsorted eigenvalues of the decomposed matrix.
41    pub eigenvalues: OVector<T, D>,
42}
43
44impl<T: Scalar + Copy, D: Dim> Copy for SymmetricEigen<T, D>
45where
46    DefaultAllocator: Allocator<D, D> + Allocator<D>,
47    OMatrix<T, D, D>: Copy,
48    OVector<T, D>: Copy,
49{
50}
51
52impl<T: SymmetricEigenScalar + RealField, D: Dim> SymmetricEigen<T, D>
53where
54    DefaultAllocator: Allocator<D, D> + Allocator<D>,
55{
56    /// Computes the eigenvalues and eigenvectors of the symmetric matrix `m`.
57    ///
58    /// Only the lower-triangular part of `m` is read. If `eigenvectors` is `false` then, the
59    /// eigenvectors are not computed explicitly. Panics if the method did not converge.
60    pub fn new(m: OMatrix<T, D, D>) -> Self {
61        let (vals, vecs) =
62            Self::do_decompose(m, true).expect("SymmetricEigen: convergence failure.");
63        Self {
64            eigenvalues: vals,
65            eigenvectors: vecs.unwrap(),
66        }
67    }
68
69    /// Computes the eigenvalues and eigenvectors of the symmetric matrix `m`.
70    ///
71    /// Only the lower-triangular part of `m` is read. If `eigenvectors` is `false` then, the
72    /// eigenvectors are not computed explicitly. Returns `None` if the method did not converge.
73    pub fn try_new(m: OMatrix<T, D, D>) -> Option<Self> {
74        Self::do_decompose(m, true).map(|(vals, vecs)| SymmetricEigen {
75            eigenvalues: vals,
76            eigenvectors: vecs.unwrap(),
77        })
78    }
79
80    fn do_decompose(
81        mut m: OMatrix<T, D, D>,
82        eigenvectors: bool,
83    ) -> Option<(OVector<T, D>, Option<OMatrix<T, D, D>>)> {
84        assert!(
85            m.is_square(),
86            "Unable to compute the eigenvalue decomposition of a non-square matrix."
87        );
88
89        let jobz = if eigenvectors { b'V' } else { b'T' };
90
91        let nrows = m.shape_generic().0;
92        let n = nrows.value();
93
94        let lda = n as i32;
95
96        let mut values = Matrix::zeros_generic(nrows, Const::<1>);
97        let mut info = 0;
98
99        let lwork = T::xsyev_work_size(jobz, b'L', n as i32, m.as_mut_slice(), lda, &mut info);
100        lapack_check!(info);
101
102        let mut work = vec![T::zero(); lwork as usize];
103
104        T::xsyev(
105            jobz,
106            b'L',
107            n as i32,
108            m.as_mut_slice(),
109            lda,
110            values.as_mut_slice(),
111            &mut work,
112            lwork,
113            &mut info,
114        );
115        lapack_check!(info);
116
117        let vectors = if eigenvectors { Some(m) } else { None };
118        Some((values, vectors))
119    }
120
121    /// Computes only the eigenvalues of the input matrix.
122    ///
123    /// Panics if the method does not converge.
124    pub fn eigenvalues(m: OMatrix<T, D, D>) -> OVector<T, D> {
125        Self::do_decompose(m, false)
126            .expect("SymmetricEigen eigenvalues: convergence failure.")
127            .0
128    }
129
130    /// Computes only the eigenvalues of the input matrix.
131    ///
132    /// Returns `None` if the method does not converge.
133    pub fn try_eigenvalues(m: OMatrix<T, D, D>) -> Option<OVector<T, D>> {
134        Self::do_decompose(m, false).map(|res| res.0)
135    }
136
137    /// The determinant of the decomposed matrix.
138    #[inline]
139    #[must_use]
140    pub fn determinant(&self) -> T {
141        let mut det = T::one();
142        for e in self.eigenvalues.iter() {
143            det *= e.clone();
144        }
145
146        det
147    }
148
149    /// Rebuild the original matrix.
150    ///
151    /// This is useful if some of the eigenvalues have been manually modified.
152    #[must_use]
153    pub fn recompose(&self) -> OMatrix<T, D, D> {
154        let mut u_t = self.eigenvectors.clone();
155        for i in 0..self.eigenvalues.len() {
156            let val = self.eigenvalues[i].clone();
157            u_t.column_mut(i).mul_assign(val);
158        }
159        u_t.transpose_mut();
160        &self.eigenvectors * u_t
161    }
162}
163
164/*
165 *
166 * Lapack functions dispatch.
167 *
168 */
169/// Trait implemented by scalars for which Lapack implements the eigendecomposition of symmetric
170/// real matrices.
171pub trait SymmetricEigenScalar: Scalar {
172    #[allow(missing_docs)]
173    fn xsyev(
174        jobz: u8,
175        uplo: u8,
176        n: i32,
177        a: &mut [Self],
178        lda: i32,
179        w: &mut [Self],
180        work: &mut [Self],
181        lwork: i32,
182        info: &mut i32,
183    );
184    #[allow(missing_docs)]
185    fn xsyev_work_size(jobz: u8, uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32)
186    -> i32;
187}
188
189macro_rules! real_eigensystem_scalar_impl (
190    ($N: ty, $xsyev: path) => (
191        impl SymmetricEigenScalar for $N {
192            #[inline]
193            fn xsyev(jobz: u8, uplo: u8, n: i32, a: &mut [Self], lda: i32, w: &mut [Self], work: &mut [Self],
194                     lwork: i32, info: &mut i32) {
195                unsafe { $xsyev(jobz, uplo, n, a, lda, w, work, lwork, info) }
196            }
197
198
199            #[inline]
200            fn xsyev_work_size(jobz: u8, uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32) -> i32 {
201                let mut work = [ Zero::zero() ];
202                let mut w    = [ Zero::zero() ];
203                let lwork    = -1 as i32;
204
205                unsafe { $xsyev(jobz, uplo, n, a, lda, &mut w, &mut work, lwork, info); }
206                ComplexHelper::real_part(work[0]) as i32
207            }
208        }
209    )
210);
211
212real_eigensystem_scalar_impl!(f32, lapack::ssyev);
213real_eigensystem_scalar_impl!(f64, lapack::dsyev);