#[cfg(feature = "serde-serialize")] use serde::{Deserialize, Serialize}; use num::Zero; use num_complex::Complex; use simba::scalar::RealField; use crate::ComplexHelper; use na::allocator::Allocator; use na::dimension::{Const, Dim}; use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; /// Eigendecomposition of a real square matrix with real or complex eigenvalues. #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize", serde( bound(serialize = "DefaultAllocator: Allocator + Allocator, OVector: Serialize, OMatrix: Serialize") ) )] #[cfg_attr( feature = "serde-serialize", serde( bound(deserialize = "DefaultAllocator: Allocator + Allocator, OVector: Serialize, OMatrix: Deserialize<'de>") ) )] #[derive(Clone, Debug)] pub struct Eigen where DefaultAllocator: Allocator + Allocator, { /// The real parts of eigenvalues of the decomposed matrix. pub eigenvalues_re: OVector, /// The imaginary parts of the eigenvalues of the decomposed matrix. pub eigenvalues_im: OVector, /// The (right) eigenvectors of the decomposed matrix. pub eigenvectors: Option>, /// The left eigenvectors of the decomposed matrix. pub left_eigenvectors: Option>, } impl Copy for Eigen where DefaultAllocator: Allocator + Allocator, OVector: Copy, OMatrix: Copy, { } impl Eigen where DefaultAllocator: Allocator + Allocator, { /// Computes the eigenvalues and eigenvectors of the square matrix `m`. /// /// If `eigenvectors` is `false` then, the eigenvectors are not computed explicitly. pub fn new( mut m: OMatrix, left_eigenvectors: bool, eigenvectors: bool, ) -> Option> { assert!( m.is_square(), "Unable to compute the eigenvalue decomposition of a non-square matrix." ); let ljob = if left_eigenvectors { b'V' } else { b'N' }; let rjob = if eigenvectors { b'V' } else { b'N' }; let (nrows, ncols) = m.shape_generic(); let n = nrows.value(); let lda = n as i32; // TODO: avoid the initialization? let mut wr = Matrix::zeros_generic(nrows, Const::<1>); // TODO: Tap into the workspace. let mut wi = Matrix::zeros_generic(nrows, Const::<1>); let mut info = 0; let mut placeholder1 = [T::zero()]; let mut placeholder2 = [T::zero()]; let lwork = T::xgeev_work_size( ljob, rjob, n as i32, m.as_mut_slice(), lda, wr.as_mut_slice(), wi.as_mut_slice(), &mut placeholder1, n as i32, &mut placeholder2, n as i32, &mut info, ); lapack_check!(info); let mut work = vec![T::zero(); lwork as usize]; let mut vl = if left_eigenvectors { Some(Matrix::zeros_generic(nrows, ncols)) } else { None }; let mut vr = if eigenvectors { Some(Matrix::zeros_generic(nrows, ncols)) } else { None }; let vl_ref = vl .as_mut() .map(|m| m.as_mut_slice()) .unwrap_or(&mut placeholder1); let vr_ref = vr .as_mut() .map(|m| m.as_mut_slice()) .unwrap_or(&mut placeholder2); T::xgeev( ljob, rjob, n as i32, m.as_mut_slice(), lda, wr.as_mut_slice(), wi.as_mut_slice(), vl_ref, if left_eigenvectors { n as i32 } else { 1 }, vr_ref, if eigenvectors { n as i32 } else { 1 }, &mut work, lwork, &mut info, ); lapack_check!(info); Some(Self { eigenvalues_re: wr, eigenvalues_im: wi, left_eigenvectors: vl, eigenvectors: vr, }) } /// Returns `true` if all the eigenvalues are real. pub fn eigenvalues_are_real(&self) -> bool { self.eigenvalues_im.iter().all(|e| e.is_zero()) } /// The determinant of the decomposed matrix. #[inline] #[must_use] pub fn determinant(&self) -> Complex { let mut det: Complex = na::one(); for (re, im) in self.eigenvalues_re.iter().zip(self.eigenvalues_im.iter()) { det *= Complex::new(re.clone(), im.clone()); } det } } /* * * Lapack functions dispatch. * */ /// Trait implemented by scalar type for which Lapack function exist to compute the /// eigendecomposition. pub trait EigenScalar: Scalar { #[allow(missing_docs)] fn xgeev( jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, wr: &mut [Self], wi: &mut [Self], vl: &mut [Self], ldvl: i32, vr: &mut [Self], ldvr: i32, work: &mut [Self], lwork: i32, info: &mut i32, ); #[allow(missing_docs)] fn xgeev_work_size( jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, wr: &mut [Self], wi: &mut [Self], vl: &mut [Self], ldvl: i32, vr: &mut [Self], ldvr: i32, info: &mut i32, ) -> i32; } macro_rules! real_eigensystem_scalar_impl ( ($N: ty, $xgeev: path) => ( impl EigenScalar for $N { #[inline] fn xgeev(jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, wr: &mut [Self], wi: &mut [Self], vl: &mut [Self], ldvl: i32, vr: &mut [Self], ldvr: i32, work: &mut [Self], lwork: i32, info: &mut i32) { unsafe { $xgeev(jobvl, jobvr, n, a, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork, info) } } #[inline] fn xgeev_work_size(jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, wr: &mut [Self], wi: &mut [Self], vl: &mut [Self], ldvl: i32, vr: &mut [Self], ldvr: i32, info: &mut i32) -> i32 { let mut work = [ Zero::zero() ]; let lwork = -1 as i32; unsafe { $xgeev(jobvl, jobvr, n, a, lda, wr, wi, vl, ldvl, vr, ldvr, &mut work, lwork, info) }; ComplexHelper::real_part(work[0]) as i32 } } ) ); real_eigensystem_scalar_impl!(f32, lapack::sgeev); real_eigensystem_scalar_impl!(f64, lapack::dgeev); //// TODO: decomposition of complex matrix and matrices with complex eigenvalues. // eigensystem_complex_impl!(f32, lapack::cgeev); // eigensystem_complex_impl!(f64, lapack::zgeev);