nalgebra_lapack/
schur.rs

1#[cfg(feature = "serde-serialize")]
2use serde::{Deserialize, Serialize};
3
4use num::Zero;
5use num_complex::Complex;
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 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> + Allocator<D>,
21         OVector<T, D>: Serialize,
22         OMatrix<T, D, D>: Serialize"))
23)]
24#[cfg_attr(
25    feature = "serde-serialize",
26    serde(bound(deserialize = "DefaultAllocator: Allocator<D, D> + Allocator<D>,
27         OVector<T, D>: Deserialize<'de>,
28         OMatrix<T, D, D>: Deserialize<'de>"))
29)]
30#[derive(Clone, Debug)]
31pub struct Schur<T: Scalar, D: Dim>
32where
33    DefaultAllocator: Allocator<D> + Allocator<D, D>,
34{
35    re: OVector<T, D>,
36    im: OVector<T, D>,
37    t: OMatrix<T, D, D>,
38    q: OMatrix<T, D, D>,
39}
40
41impl<T: Scalar + Copy, D: Dim> Copy for Schur<T, D>
42where
43    DefaultAllocator: Allocator<D, D> + Allocator<D>,
44    OMatrix<T, D, D>: Copy,
45    OVector<T, D>: Copy,
46{
47}
48
49impl<T: SchurScalar + RealField, D: Dim> Schur<T, D>
50where
51    DefaultAllocator: Allocator<D, D> + Allocator<D>,
52{
53    /// Computes the eigenvalues and real Schur form of the matrix `m`.
54    ///
55    /// Panics if the method did not converge.
56    pub fn new(m: OMatrix<T, D, D>) -> Self {
57        Self::try_new(m).expect("Schur decomposition: convergence failed.")
58    }
59
60    /// Computes the eigenvalues and real Schur form of the matrix `m`.
61    ///
62    /// Returns `None` if the method did not converge.
63    pub fn try_new(mut m: OMatrix<T, D, D>) -> Option<Self> {
64        assert!(
65            m.is_square(),
66            "Unable to compute the eigenvalue decomposition of a non-square matrix."
67        );
68
69        let (nrows, ncols) = m.shape_generic();
70        let n = nrows.value();
71
72        let lda = n as i32;
73
74        let mut info = 0;
75
76        let mut wr = Matrix::zeros_generic(nrows, Const::<1>);
77        let mut wi = Matrix::zeros_generic(nrows, Const::<1>);
78        let mut q = Matrix::zeros_generic(nrows, ncols);
79        // Placeholders:
80        let mut bwork = [0i32];
81        let mut unused = 0;
82
83        let lwork = T::xgees_work_size(
84            b'V',
85            //@note(geo-ant): this interface is bad because we could pass
86            // 'S' here, but we cannot give a function to SELECT, which
87            // isn't exposed here.
88            // https://www.netlib.org/lapack/explore-html/d5/d38/group__gees.html
89            b'N',
90            n as i32,
91            m.as_mut_slice(),
92            lda,
93            &mut unused,
94            wr.as_mut_slice(),
95            wi.as_mut_slice(),
96            q.as_mut_slice(),
97            n as i32,
98            &mut bwork,
99            &mut info,
100        );
101        lapack_check!(info);
102
103        let mut work = vec![T::zero(); lwork as usize];
104
105        T::xgees(
106            b'V',
107            b'N',
108            n as i32,
109            m.as_mut_slice(),
110            lda,
111            &mut unused,
112            wr.as_mut_slice(),
113            wi.as_mut_slice(),
114            q.as_mut_slice(),
115            n as i32,
116            &mut work,
117            lwork,
118            &mut bwork,
119            &mut info,
120        );
121        lapack_check!(info);
122
123        Some(Schur {
124            re: wr,
125            im: wi,
126            t: m,
127            q,
128        })
129    }
130
131    /// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the
132    /// decomposed matrix equals `Q * T * Q.transpose()`.
133    pub fn unpack(self) -> (OMatrix<T, D, D>, OMatrix<T, D, D>) {
134        (self.q, self.t)
135    }
136
137    /// Computes the real eigenvalues of the decomposed matrix.
138    ///
139    /// Return `None` if some eigenvalues are complex.
140    #[must_use]
141    pub fn eigenvalues(&self) -> Option<OVector<T, D>> {
142        if self.im.iter().all(|e| e.is_zero()) {
143            Some(self.re.clone())
144        } else {
145            None
146        }
147    }
148
149    /// Computes the complex eigenvalues of the decomposed matrix.
150    #[must_use]
151    pub fn complex_eigenvalues(&self) -> OVector<Complex<T>, D>
152    where
153        DefaultAllocator: Allocator<D>,
154    {
155        let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>);
156
157        for i in 0..out.len() {
158            out[i] = Complex::new(self.re[i].clone(), self.im[i].clone())
159        }
160
161        out
162    }
163}
164
165/*
166 *
167 * Lapack functions dispatch.
168 *
169 */
170/// Trait implemented by scalars for which Lapack implements the RealField Schur decomposition.
171pub trait SchurScalar: Scalar {
172    #[allow(missing_docs)]
173    fn xgees(
174        jobvs: u8,
175        sort: u8,
176        //@note(geo-ant) see other comments in this file on this method
177        // select: ???
178        n: i32,
179        a: &mut [Self],
180        lda: i32,
181        sdim: &mut i32,
182        wr: &mut [Self],
183        wi: &mut [Self],
184        vs: &mut [Self],
185        ldvs: i32,
186        work: &mut [Self],
187        lwork: i32,
188        bwork: &mut [i32],
189        info: &mut i32,
190    );
191
192    #[allow(missing_docs)]
193    fn xgees_work_size(
194        jobvs: u8,
195        sort: u8,
196        // select: ???
197        n: i32,
198        a: &mut [Self],
199        lda: i32,
200        sdim: &mut i32,
201        wr: &mut [Self],
202        wi: &mut [Self],
203        vs: &mut [Self],
204        ldvs: i32,
205        bwork: &mut [i32],
206        info: &mut i32,
207    ) -> i32;
208}
209
210macro_rules! real_eigensystem_scalar_impl (
211    ($N: ty, $xgees: path) => (
212        impl SchurScalar for $N {
213            #[inline]
214            fn xgees(jobvs:  u8,
215                     sort:   u8,
216                     //@note(geo-ant) see the documentation for xGEES here:
217                     // https://www.netlib.org/lapack/explore-html/d5/d38/group__gees.html
218                     // If we pass a sort argument of b'S', then we must offer a
219                     // SELECT implementation.
220                     // select: ???
221                     n:      i32,
222                     a:      &mut [$N],
223                     lda:    i32,
224                     sdim:   &mut i32,
225                     wr:     &mut [$N],
226                     wi:     &mut [$N],
227                     vs:     &mut [$N],
228                     ldvs:   i32,
229                     work:   &mut [$N],
230                     lwork:  i32,
231                     bwork:  &mut [i32],
232                     info:   &mut i32) {
233                unsafe { $xgees(jobvs, sort, None, n, a, lda, sdim, wr, wi, vs, ldvs, work, lwork, bwork, info); }
234            }
235
236
237            #[inline]
238            fn xgees_work_size(jobvs:  u8,
239                               sort:   u8,
240                               // select: ???
241                               n:      i32,
242                               a:      &mut [$N],
243                               lda:    i32,
244                               sdim:   &mut i32,
245                               wr:     &mut [$N],
246                               wi:     &mut [$N],
247                               vs:     &mut [$N],
248                               ldvs:   i32,
249                               bwork:  &mut [i32],
250                               info:   &mut i32)
251                               -> i32 {
252                let mut work = [ Zero::zero() ];
253                let lwork    = -1 as i32;
254
255                unsafe { $xgees(jobvs, sort, None, n, a, lda, sdim, wr, wi, vs, ldvs, &mut work, lwork, bwork, info); }
256                ComplexHelper::real_part(work[0]) as i32
257            }
258        }
259    )
260);
261
262real_eigensystem_scalar_impl!(f32, lapack::sgees);
263real_eigensystem_scalar_impl!(f64, lapack::dgees);