1 #[cfg(feature = "serde-serialize")]
2 use serde::{Deserialize, Serialize};
3 
4 use approx::AbsDiffEq;
5 use alga::general::{ComplexField, RealField};
6 use num_complex::Complex as NumComplex;
7 use std::cmp;
8 
9 use crate::allocator::Allocator;
10 use crate::base::dimension::{Dim, DimDiff, DimSub, Dynamic, U1, U2, U3};
11 use crate::base::storage::Storage;
12 use crate::base::{DefaultAllocator, MatrixN, SquareMatrix, Unit, Vector2, Vector3, VectorN};
13 
14 use crate::geometry::Reflection;
15 use crate::linalg::householder;
16 use crate::linalg::Hessenberg;
17 use crate::linalg::givens::GivensRotation;
18 
19 /// Schur decomposition of a square matrix.
20 ///
21 /// If this is a real matrix, this will be a RealField Schur decomposition.
22 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
23 #[cfg_attr(
24     feature = "serde-serialize",
25     serde(bound(
26         serialize = "DefaultAllocator: Allocator<N, D, D>,
27          MatrixN<N, D>: Serialize"
28     ))
29 )]
30 #[cfg_attr(
31     feature = "serde-serialize",
32     serde(bound(
33         deserialize = "DefaultAllocator: Allocator<N, D, D>,
34          MatrixN<N, D>: Deserialize<'de>"
35     ))
36 )]
37 #[derive(Clone, Debug)]
38 pub struct Schur<N: ComplexField, D: Dim>
39 where DefaultAllocator: Allocator<N, D, D>
40 {
41     q: MatrixN<N, D>,
42     t: MatrixN<N, D>,
43 }
44 
45 impl<N: ComplexField, D: Dim> Copy for Schur<N, D>
46 where
47     DefaultAllocator: Allocator<N, D, D>,
48     MatrixN<N, D>: Copy,
49 {}
50 
51 impl<N: ComplexField, D: Dim> Schur<N, D>
52 where
53     D: DimSub<U1>,                                   // For Hessenberg.
54     DefaultAllocator: Allocator<N, D, DimDiff<D, U1>>
55         + Allocator<N, DimDiff<D, U1>>
56         + Allocator<N, D, D>
57         + Allocator<N, D>,
58 {
59     /// Computes the Schur decomposition of a square matrix.
new(m: MatrixN<N, D>) -> Self60     pub fn new(m: MatrixN<N, D>) -> Self {
61         Self::try_new(m, N::RealField::default_epsilon(), 0).unwrap()
62     }
63 
64     /// Attempts to compute the Schur decomposition of a square matrix.
65     ///
66     /// If only eigenvalues are needed, it is more efficient to call the matrix method
67     /// `.eigenvalues()` instead.
68     ///
69     /// # Arguments
70     ///
71     /// * `eps`       − tolerance used to determine when a value converged to 0.
72     /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this
73     /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm
74     /// continues indefinitely until convergence.
try_new(m: MatrixN<N, D>, eps: N::RealField, max_niter: usize) -> Option<Self>75     pub fn try_new(m: MatrixN<N, D>, eps: N::RealField, max_niter: usize) -> Option<Self> {
76         let mut work = unsafe { VectorN::new_uninitialized_generic(m.data.shape().0, U1) };
77 
78         Self::do_decompose(m, &mut work, eps, max_niter, true).map(|(q, t)| Schur {
79             q: q.unwrap(),
80             t: t,
81         })
82     }
83 
do_decompose( mut m: MatrixN<N, D>, work: &mut VectorN<N, D>, eps: N::RealField, max_niter: usize, compute_q: bool, ) -> Option<(Option<MatrixN<N, D>>, MatrixN<N, D>)>84     fn do_decompose(
85         mut m: MatrixN<N, D>,
86         work: &mut VectorN<N, D>,
87         eps: N::RealField,
88         max_niter: usize,
89         compute_q: bool,
90     ) -> Option<(Option<MatrixN<N, D>>, MatrixN<N, D>)>
91     {
92         assert!(
93             m.is_square(),
94             "Unable to compute the eigenvectors and eigenvalues of a non-square matrix."
95         );
96 
97         let dim = m.data.shape().0;
98 
99         // Specialization would make this easier.
100         if dim.value() == 0 {
101             let vecs = Some(MatrixN::from_element_generic(dim, dim, N::zero()));
102             let vals = MatrixN::from_element_generic(dim, dim, N::zero());
103             return Some((vecs, vals));
104         } else if dim.value() == 1 {
105             if compute_q {
106                 let q = MatrixN::from_element_generic(dim, dim, N::one());
107                 return Some((Some(q), m));
108             } else {
109                 return Some((None, m));
110             }
111         } else if dim.value() == 2 {
112             return decompose_2x2(m, compute_q);
113         }
114 
115         let amax_m = m.camax();
116         m.unscale_mut(amax_m);
117 
118         let hess = Hessenberg::new_with_workspace(m, work);
119         let mut q;
120         let mut t;
121 
122         if compute_q {
123             // FIXME: could we work without unpacking? Using only the internal representation of
124             // hessenberg decomposition.
125             let (vecs, vals) = hess.unpack();
126             q = Some(vecs);
127             t = vals;
128         } else {
129             q = None;
130             t = hess.unpack_h()
131         }
132 
133         // Implicit double-shift QR method.
134         let mut niter = 0;
135         let (mut start, mut end) = Self::delimit_subproblem(&mut t, eps, dim.value() - 1);
136 
137         while end != start {
138             let subdim = end - start + 1;
139 
140             if subdim > 2 {
141                 let m = end - 1;
142                 let n = end;
143 
144                 let h11 = t[(start + 0, start + 0)];
145                 let h12 = t[(start + 0, start + 1)];
146                 let h21 = t[(start + 1, start + 0)];
147                 let h22 = t[(start + 1, start + 1)];
148                 let h32 = t[(start + 2, start + 1)];
149 
150                 let hnn = t[(n, n)];
151                 let hmm = t[(m, m)];
152                 let hnm = t[(n, m)];
153                 let hmn = t[(m, n)];
154 
155                 let tra = hnn + hmm;
156                 let det = hnn * hmm - hnm * hmn;
157 
158                 let mut axis = Vector3::new(
159                     h11 * h11 + h12 * h21 - tra * h11 + det,
160                     h21 * (h11 + h22 - tra),
161                     h21 * h32,
162                 );
163 
164                 for k in start..n - 1 {
165                     let (norm, not_zero) = householder::reflection_axis_mut(&mut axis);
166 
167                     if not_zero {
168                         if k > start {
169                             t[(k + 0, k - 1)] = norm;
170                             t[(k + 1, k - 1)] = N::zero();
171                             t[(k + 2, k - 1)] = N::zero();
172                         }
173 
174                         let refl = Reflection::new(Unit::new_unchecked(axis), N::zero());
175 
176                         {
177                             let krows = cmp::min(k + 4, end + 1);
178                             let mut work = work.rows_mut(0, krows);
179                             refl.reflect(
180                                 &mut t
181                                     .generic_slice_mut((k, k), (U3, Dynamic::new(dim.value() - k))),
182                             );
183                             refl.reflect_rows(
184                                 &mut t.generic_slice_mut((0, k), (Dynamic::new(krows), U3)),
185                                 &mut work,
186                             );
187                         }
188 
189                         if let Some(ref mut q) = q {
190                             refl.reflect_rows(&mut q.generic_slice_mut((0, k), (dim, U3)), work);
191                         }
192                     }
193 
194                     axis.x = t[(k + 1, k)];
195                     axis.y = t[(k + 2, k)];
196 
197                     if k < n - 2 {
198                         axis.z = t[(k + 3, k)];
199                     }
200                 }
201 
202                 let mut axis = Vector2::new(axis.x, axis.y);
203                 let (norm, not_zero) = householder::reflection_axis_mut(&mut axis);
204 
205                 if not_zero {
206                     let refl = Reflection::new(Unit::new_unchecked(axis), N::zero());
207 
208                     t[(m, m - 1)] = norm;
209                     t[(n, m - 1)] = N::zero();
210 
211                     {
212                         let mut work = work.rows_mut(0, end + 1);
213                         refl.reflect(
214                             &mut t.generic_slice_mut((m, m), (U2, Dynamic::new(dim.value() - m))),
215                         );
216                         refl.reflect_rows(
217                             &mut t.generic_slice_mut((0, m), (Dynamic::new(end + 1), U2)),
218                             &mut work,
219                         );
220                     }
221 
222                     if let Some(ref mut q) = q {
223                         refl.reflect_rows(&mut q.generic_slice_mut((0, m), (dim, U2)), work);
224                     }
225                 }
226             } else {
227                 // Decouple the 2x2 block if it has real eigenvalues.
228                 if let Some(rot) = compute_2x2_basis(&t.fixed_slice::<U2, U2>(start, start)) {
229                     let inv_rot = rot.inverse();
230                     inv_rot.rotate(&mut t.generic_slice_mut(
231                         (start, start),
232                         (U2, Dynamic::new(dim.value() - start)),
233                     ));
234                     rot.rotate_rows(
235                         &mut t.generic_slice_mut((0, start), (Dynamic::new(end + 1), U2)),
236                     );
237                     t[(end, start)] = N::zero();
238 
239                     if let Some(ref mut q) = q {
240                         rot.rotate_rows(&mut q.generic_slice_mut((0, start), (dim, U2)));
241                     }
242                 }
243 
244                 // Check if we reached the beginning of the matrix.
245                 if end > 2 {
246                     end -= 2;
247                 } else {
248                     break;
249                 }
250             }
251 
252             let sub = Self::delimit_subproblem(&mut t, eps, end);
253 
254             start = sub.0;
255             end = sub.1;
256 
257             niter += 1;
258             if niter == max_niter {
259                 return None;
260             }
261         }
262 
263         t.scale_mut(amax_m);
264 
265         Some((q, t))
266     }
267 
268     /// Computes the eigenvalues of the decomposed matrix.
do_eigenvalues(t: &MatrixN<N, D>, out: &mut VectorN<N, D>) -> bool269     fn do_eigenvalues(t: &MatrixN<N, D>, out: &mut VectorN<N, D>) -> bool {
270         let dim = t.nrows();
271         let mut m = 0;
272 
273         while m < dim - 1 {
274             let n = m + 1;
275 
276             if t[(n, m)].is_zero() {
277                 out[m] = t[(m, m)];
278                 m += 1;
279             } else {
280                 // Complex eigenvalue.
281                 return false;
282             }
283         }
284 
285         if m == dim - 1 {
286             out[m] = t[(m, m)];
287         }
288 
289         true
290     }
291 
292     /// Computes the complex eigenvalues of the decomposed matrix.
do_complex_eigenvalues(t: &MatrixN<N, D>, out: &mut VectorN<NumComplex<N>, D>) where N: RealField, DefaultAllocator: Allocator<NumComplex<N>, D>293     fn do_complex_eigenvalues(t: &MatrixN<N, D>, out: &mut VectorN<NumComplex<N>, D>)
294     where N: RealField,
295           DefaultAllocator: Allocator<NumComplex<N>, D> {
296         let dim = t.nrows();
297         let mut m = 0;
298 
299         while m < dim - 1 {
300             let n = m + 1;
301 
302             if t[(n, m)].is_zero() {
303                 out[m] = NumComplex::new(t[(m, m)], N::zero());
304                 m += 1;
305             } else {
306                 // Solve the 2x2 eigenvalue subproblem.
307                 let hmm = t[(m, m)];
308                 let hnm = t[(n, m)];
309                 let hmn = t[(m, n)];
310                 let hnn = t[(n, n)];
311 
312                 let tra = hnn + hmm;
313                 let det = hnn * hmm - hnm * hmn;
314                 let discr = tra * tra * crate::convert(0.25) - det;
315 
316                 // All 2x2 blocks have negative discriminant because we already decoupled those
317                 // with positive eigenvalues..
318                 let sqrt_discr = NumComplex::new(N::zero(), (-discr).sqrt());
319 
320                 out[m] = NumComplex::new(tra * crate::convert(0.5), N::zero()) + sqrt_discr;
321                 out[m + 1] = NumComplex::new(tra * crate::convert(0.5), N::zero()) - sqrt_discr;
322 
323                 m += 2;
324             }
325         }
326 
327         if m == dim - 1 {
328             out[m] = NumComplex::new(t[(m, m)], N::zero());
329         }
330     }
331 
delimit_subproblem(t: &mut MatrixN<N, D>, eps: N::RealField, end: usize) -> (usize, usize) where D: DimSub<U1>, DefaultAllocator: Allocator<N, DimDiff<D, U1>>,332     fn delimit_subproblem(t: &mut MatrixN<N, D>, eps: N::RealField, end: usize) -> (usize, usize)
333     where
334         D: DimSub<U1>,
335         DefaultAllocator: Allocator<N, DimDiff<D, U1>>,
336     {
337         let mut n = end;
338 
339         while n > 0 {
340             let m = n - 1;
341 
342             if t[(n, m)].norm1() <= eps * (t[(n, n)].norm1() + t[(m, m)].norm1()) {
343                 t[(n, m)] = N::zero();
344             } else {
345                 break;
346             }
347 
348             n -= 1;
349         }
350 
351         if n == 0 {
352             return (0, 0);
353         }
354 
355         let mut new_start = n - 1;
356         while new_start > 0 {
357             let m = new_start - 1;
358 
359             let off_diag = t[(new_start, m)];
360             if off_diag.is_zero()
361                 || off_diag.norm1() <= eps * (t[(new_start, new_start)].norm1() + t[(m, m)].norm1())
362             {
363                 t[(new_start, m)] = N::zero();
364                 break;
365             }
366 
367             new_start -= 1;
368         }
369 
370         (new_start, n)
371     }
372 
373     /// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the
374     /// decomposed matrix equals `Q * T * Q.transpose()`.
unpack(self) -> (MatrixN<N, D>, MatrixN<N, D>)375     pub fn unpack(self) -> (MatrixN<N, D>, MatrixN<N, D>) {
376         (self.q, self.t)
377     }
378 
379     /// Computes the real eigenvalues of the decomposed matrix.
380     ///
381     /// Return `None` if some eigenvalues are complex.
eigenvalues(&self) -> Option<VectorN<N, D>>382     pub fn eigenvalues(&self) -> Option<VectorN<N, D>> {
383         let mut out = unsafe { VectorN::new_uninitialized_generic(self.t.data.shape().0, U1) };
384         if Self::do_eigenvalues(&self.t, &mut out) {
385             Some(out)
386         } else {
387             None
388         }
389     }
390 
391     /// Computes the complex eigenvalues of the decomposed matrix.
complex_eigenvalues(&self) -> VectorN<NumComplex<N>, D> where N: RealField, DefaultAllocator: Allocator<NumComplex<N>, D>392     pub fn complex_eigenvalues(&self) -> VectorN<NumComplex<N>, D>
393     where N: RealField,
394           DefaultAllocator: Allocator<NumComplex<N>, D> {
395         let mut out = unsafe { VectorN::new_uninitialized_generic(self.t.data.shape().0, U1) };
396         Self::do_complex_eigenvalues(&self.t, &mut out);
397         out
398     }
399 }
400 
decompose_2x2<N: ComplexField, D: Dim>( mut m: MatrixN<N, D>, compute_q: bool, ) -> Option<(Option<MatrixN<N, D>>, MatrixN<N, D>)> where DefaultAllocator: Allocator<N, D, D>,401 fn decompose_2x2<N: ComplexField, D: Dim>(
402     mut m: MatrixN<N, D>,
403     compute_q: bool,
404 ) -> Option<(Option<MatrixN<N, D>>, MatrixN<N, D>)>
405 where
406     DefaultAllocator: Allocator<N, D, D>,
407 {
408     let dim = m.data.shape().0;
409     let mut q = None;
410     match compute_2x2_basis(&m.fixed_slice::<U2, U2>(0, 0)) {
411         Some(rot) => {
412             let mut m = m.fixed_slice_mut::<U2, U2>(0, 0);
413             let inv_rot = rot.inverse();
414             inv_rot.rotate(&mut m);
415             rot.rotate_rows(&mut m);
416 
417             if compute_q {
418                 // XXX: we have to build the matrix manually because
419                 // rot.to_rotation_matrix().unwrap() causes an ICE.
420                 let c = N::from_real(rot.c());
421                 q = Some(MatrixN::from_column_slice_generic(
422                     dim,
423                     dim,
424                     &[c, rot.s(), -rot.s().conjugate(), c],
425                 ));
426             }
427         }
428         None => {
429             if compute_q {
430                 q = Some(MatrixN::identity_generic(dim, dim));
431             }
432         }
433     };
434 
435     Some((q, m))
436 }
437 
compute_2x2_eigvals<N: ComplexField, S: Storage<N, U2, U2>>( m: &SquareMatrix<N, U2, S>, ) -> Option<(N, N)>438 fn compute_2x2_eigvals<N: ComplexField, S: Storage<N, U2, U2>>(
439     m: &SquareMatrix<N, U2, S>,
440 ) -> Option<(N, N)> {
441     // Solve the 2x2 eigenvalue subproblem.
442     let h00 = m[(0, 0)];
443     let h10 = m[(1, 0)];
444     let h01 = m[(0, 1)];
445     let h11 = m[(1, 1)];
446 
447     // NOTE: this discriminant computation is more stable than the
448     // one based on the trace and determinant: 0.25 * tra * tra - det
449     // because it ensures positiveness for symmetric matrices.
450     let val = (h00 - h11) * crate::convert(0.5);
451     let discr = h10 * h01 + val * val;
452 
453     discr.try_sqrt().map(|sqrt_discr| {
454         let half_tra = (h00 + h11) * crate::convert(0.5);
455         (half_tra + sqrt_discr, half_tra - sqrt_discr)
456     })
457 }
458 
459 // Computes the 2x2 transformation that upper-triangulates a 2x2 matrix with real eigenvalues.
460 /// Computes the singular vectors for a 2x2 matrix.
461 ///
462 /// Returns `None` if the matrix has complex eigenvalues, or is upper-triangular. In both case,
463 /// the basis is the identity.
compute_2x2_basis<N: ComplexField, S: Storage<N, U2, U2>>( m: &SquareMatrix<N, U2, S>, ) -> Option<GivensRotation<N>>464 fn compute_2x2_basis<N: ComplexField, S: Storage<N, U2, U2>>(
465     m: &SquareMatrix<N, U2, S>,
466 ) -> Option<GivensRotation<N>> {
467     let h10 = m[(1, 0)];
468 
469     if h10.is_zero() {
470         return None;
471     }
472 
473     if let Some((eigval1, eigval2)) = compute_2x2_eigvals(m) {
474         let x1 = eigval1 - m[(1, 1)];
475         let x2 = eigval2 - m[(1, 1)];
476 
477         // NOTE: Choose the one that yields a larger x component.
478         // This is necessary for numerical stability of the normalization of the complex
479         // number.
480         if x1.norm1() > x2.norm1() {
481             Some(GivensRotation::new(x1, h10).0)
482         } else {
483             Some(GivensRotation::new(x2, h10).0)
484         }
485     } else {
486         None
487     }
488 }
489 
490 impl<N: ComplexField, D: Dim, S: Storage<N, D, D>> SquareMatrix<N, D, S>
491 where
492     D: DimSub<U1>,                                   // For Hessenberg.
493     DefaultAllocator: Allocator<N, D, DimDiff<D, U1>>
494         + Allocator<N, DimDiff<D, U1>>
495         + Allocator<N, D, D>
496         + Allocator<N, D>,
497 {
498     /// Computes the Schur decomposition of a square matrix.
schur(self) -> Schur<N, D>499     pub fn schur(self) -> Schur<N, D> {
500         Schur::new(self.into_owned())
501     }
502 
503     /// Attempts to compute the Schur decomposition of a square matrix.
504     ///
505     /// If only eigenvalues are needed, it is more efficient to call the matrix method
506     /// `.eigenvalues()` instead.
507     ///
508     /// # Arguments
509     ///
510     /// * `eps`       − tolerance used to determine when a value converged to 0.
511     /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this
512     /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm
513     /// continues indefinitely until convergence.
try_schur(self, eps: N::RealField, max_niter: usize) -> Option<Schur<N, D>>514     pub fn try_schur(self, eps: N::RealField, max_niter: usize) -> Option<Schur<N, D>> {
515         Schur::try_new(self.into_owned(), eps, max_niter)
516     }
517 
518     /// Computes the eigenvalues of this matrix.
eigenvalues(&self) -> Option<VectorN<N, D>>519     pub fn eigenvalues(&self) -> Option<VectorN<N, D>> {
520         assert!(
521             self.is_square(),
522             "Unable to compute eigenvalues of a non-square matrix."
523         );
524 
525         let mut work = unsafe { VectorN::new_uninitialized_generic(self.data.shape().0, U1) };
526 
527         // Special case for 2x2 matrices.
528         if self.nrows() == 2 {
529             // FIXME: can we avoid this slicing
530             // (which is needed here just to transform D to U2)?
531             let me = self.fixed_slice::<U2, U2>(0, 0);
532             return match compute_2x2_eigvals(&me) {
533                 Some((a, b)) => {
534                     work[0] = a;
535                     work[1] = b;
536                     Some(work)
537                 }
538                 None => None,
539             };
540         }
541 
542         // FIXME: add balancing?
543         let schur = Schur::do_decompose(
544             self.clone_owned(),
545             &mut work,
546             N::RealField::default_epsilon(),
547             0,
548             false,
549         )
550         .unwrap();
551         if Schur::do_eigenvalues(&schur.1, &mut work) {
552             Some(work)
553         } else {
554             None
555         }
556     }
557 
558     /// Computes the eigenvalues of this matrix.
complex_eigenvalues(&self) -> VectorN<NumComplex<N>, D> where N: RealField, DefaultAllocator: Allocator<NumComplex<N>, D>559     pub fn complex_eigenvalues(&self) -> VectorN<NumComplex<N>, D>
560     // FIXME: add balancing?
561     where N: RealField,
562           DefaultAllocator: Allocator<NumComplex<N>, D> {
563         let dim = self.data.shape().0;
564         let mut work = unsafe { VectorN::new_uninitialized_generic(dim, U1) };
565 
566         let schur = Schur::do_decompose(
567             self.clone_owned(),
568             &mut work,
569             N::default_epsilon(),
570             0,
571             false,
572         )
573         .unwrap();
574         let mut eig = unsafe { VectorN::new_uninitialized_generic(dim, U1) };
575         Schur::do_complex_eigenvalues(&schur.1, &mut eig);
576         eig
577     }
578 }
579