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