nalgebra/src/linalg/schur.rs

586 lines
19 KiB
Rust
Raw Normal View History

2021-06-18 15:45:37 +08:00
#![allow(clippy::suspicious_operation_groupings)]
#[cfg(feature = "serde-serialize-no-std")]
2018-10-22 13:00:10 +08:00
use serde::{Deserialize, Serialize};
use approx::AbsDiffEq;
use num_complex::Complex as NumComplex;
2020-03-21 19:16:46 +08:00
use simba::scalar::{ComplexField, RealField};
use std::cmp;
2019-03-23 21:29:07 +08:00
use crate::allocator::Allocator;
use crate::base::dimension::{Const, Dim, DimDiff, DimSub, Dynamic, U1, U2};
use crate::base::storage::Storage;
use crate::base::{DefaultAllocator, OMatrix, OVector, SquareMatrix, Unit, Vector2, Vector3};
2019-03-23 21:29:07 +08:00
use crate::geometry::Reflection;
2020-03-21 19:16:46 +08:00
use crate::linalg::givens::GivensRotation;
2019-03-23 21:29:07 +08:00
use crate::linalg::householder;
use crate::linalg::Hessenberg;
use crate::{Matrix, UninitVector};
use std::mem::MaybeUninit;
/// Schur decomposition of a square matrix.
///
2021-07-28 07:18:29 +08:00
/// If this is a real matrix, this will be a `RealField` Schur decomposition.
#[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde-serialize-no-std",
2021-04-11 17:00:38 +08:00
serde(bound(serialize = "DefaultAllocator: Allocator<T, D, D>,
OMatrix<T, D, D>: Serialize"))
)]
#[cfg_attr(
feature = "serde-serialize-no-std",
2021-04-11 17:00:38 +08:00
serde(bound(deserialize = "DefaultAllocator: Allocator<T, D, D>,
OMatrix<T, D, D>: Deserialize<'de>"))
)]
2021-07-30 01:33:45 +08:00
#[derive(Clone, Debug)]
pub struct Schur<T: ComplexField, D: Dim>
2020-04-06 00:49:48 +08:00
where
2021-04-11 17:00:38 +08:00
DefaultAllocator: Allocator<T, D, D>,
2018-02-02 19:26:35 +08:00
{
2021-04-11 17:00:38 +08:00
q: OMatrix<T, D, D>,
t: OMatrix<T, D, D>,
}
impl<T: ComplexField, D: Dim> Copy for Schur<T, D>
2018-02-02 19:26:35 +08:00
where
2021-04-11 17:00:38 +08:00
DefaultAllocator: Allocator<T, D, D>,
OMatrix<T, D, D>: Copy,
2020-03-21 19:16:46 +08:00
{
}
2021-04-11 17:00:38 +08:00
impl<T: ComplexField, D: Dim> Schur<T, D>
2018-02-02 19:26:35 +08:00
where
2020-03-21 19:16:46 +08:00
D: DimSub<U1>, // For Hessenberg.
2021-04-11 17:00:38 +08:00
DefaultAllocator: Allocator<T, D, DimDiff<D, U1>>
+ Allocator<T, DimDiff<D, U1>>
+ Allocator<T, D, D>
+ Allocator<T, D>,
2018-02-02 19:26:35 +08:00
{
/// Computes the Schur decomposition of a square matrix.
2021-04-11 17:00:38 +08:00
pub fn new(m: OMatrix<T, D, D>) -> Self {
Self::try_new(m, T::RealField::default_epsilon(), 0).unwrap()
}
/// Attempts to compute the Schur decomposition of a square matrix.
///
/// If only eigenvalues are needed, it is more efficient to call the matrix method
/// `.eigenvalues()` instead.
///
/// # Arguments
///
2018-09-24 12:48:42 +08:00
/// * `eps` tolerance used to determine when a value converged to 0.
/// * `max_niter` maximum total number of iterations performed by the algorithm. If this
/// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm
/// continues indefinitely until convergence.
2021-04-11 17:00:38 +08:00
pub fn try_new(m: OMatrix<T, D, D>, eps: T::RealField, max_niter: usize) -> Option<Self> {
let mut work = Matrix::zeros_generic(m.shape_generic().0, Const::<1>);
2020-10-11 16:57:26 +08:00
Self::do_decompose(m, &mut work, eps, max_niter, true)
.map(|(q, t)| Schur { q: q.unwrap(), t })
}
2018-02-02 19:26:35 +08:00
fn do_decompose(
2021-04-11 17:00:38 +08:00
mut m: OMatrix<T, D, D>,
work: &mut OVector<T, D>,
2021-04-11 17:00:38 +08:00
eps: T::RealField,
2018-02-02 19:26:35 +08:00
max_niter: usize,
compute_q: bool,
2021-04-11 17:00:38 +08:00
) -> Option<(Option<OMatrix<T, D, D>>, OMatrix<T, D, D>)> {
2018-02-02 19:26:35 +08:00
assert!(
m.is_square(),
"Unable to compute the eigenvectors and eigenvalues of a non-square matrix."
);
let dim = m.shape_generic().0;
2019-03-12 20:15:02 +08:00
// Specialization would make this easier.
if dim.value() == 0 {
2021-04-11 17:00:38 +08:00
let vecs = Some(OMatrix::from_element_generic(dim, dim, T::zero()));
let vals = OMatrix::from_element_generic(dim, dim, T::zero());
return Some((vecs, vals));
2018-02-02 19:26:35 +08:00
} else if dim.value() == 1 {
if compute_q {
2021-04-11 17:00:38 +08:00
let q = OMatrix::from_element_generic(dim, dim, T::one());
return Some((Some(q), m));
2018-02-02 19:26:35 +08:00
} else {
return Some((None, m));
}
2019-03-12 20:15:02 +08:00
} else if dim.value() == 2 {
return decompose_2x2(m, compute_q);
}
let amax_m = m.camax();
m.unscale_mut(amax_m.clone());
let hess = Hessenberg::new_with_workspace(m, work);
let mut q;
let mut t;
if compute_q {
2020-11-15 23:57:49 +08:00
// TODO: could we work without unpacking? Using only the internal representation of
// hessenberg decomposition.
let (vecs, vals) = hess.unpack();
q = Some(vecs);
t = vals;
2018-02-02 19:26:35 +08:00
} else {
q = None;
t = hess.unpack_h()
}
// Implicit double-shift QR method.
let mut niter = 0;
let (mut start, mut end) = Self::delimit_subproblem(&mut t, eps.clone(), dim.value() - 1);
while end != start {
let subdim = end - start + 1;
if subdim > 2 {
let m = end - 1;
let n = end;
let h11 = t[(start, start)].clone();
let h12 = t[(start, start + 1)].clone();
let h21 = t[(start + 1, start)].clone();
let h22 = t[(start + 1, start + 1)].clone();
let h32 = t[(start + 2, start + 1)].clone();
let hnn = t[(n, n)].clone();
let hmm = t[(m, m)].clone();
let hnm = t[(n, m)].clone();
let hmn = t[(m, n)].clone();
let tra = hnn.clone() + hmm.clone();
let det = hnn * hmm - hnm * hmn;
2018-02-02 19:26:35 +08:00
let mut axis = Vector3::new(
h11.clone() * h11.clone() + h12 * h21.clone() - tra.clone() * h11.clone() + det,
h21.clone() * (h11 + h22 - tra),
2018-02-02 19:26:35 +08:00
h21 * h32,
);
2018-02-02 19:26:35 +08:00
for k in start..n - 1 {
let (norm, not_zero) = householder::reflection_axis_mut(&mut axis);
if not_zero {
if k > start {
2020-10-11 17:23:05 +08:00
t[(k, k - 1)] = norm;
2021-04-11 17:00:38 +08:00
t[(k + 1, k - 1)] = T::zero();
t[(k + 2, k - 1)] = T::zero();
}
let refl = Reflection::new(Unit::new_unchecked(axis.clone()), T::zero());
{
let krows = cmp::min(k + 4, end + 1);
let mut work = work.rows_mut(0, krows);
refl.reflect(&mut t.generic_slice_mut(
(k, k),
(Const::<3>, Dynamic::new(dim.value() - k)),
));
2018-02-02 19:26:35 +08:00
refl.reflect_rows(
&mut t.generic_slice_mut((0, k), (Dynamic::new(krows), Const::<3>)),
2018-02-02 19:26:35 +08:00
&mut work,
);
}
if let Some(ref mut q) = q {
refl.reflect_rows(
&mut q.generic_slice_mut((0, k), (dim, Const::<3>)),
work,
);
}
}
axis.x = t[(k + 1, k)].clone();
axis.y = t[(k + 2, k)].clone();
if k < n - 2 {
axis.z = t[(k + 3, k)].clone();
}
}
let mut axis = Vector2::new(axis.x.clone(), axis.y.clone());
let (norm, not_zero) = householder::reflection_axis_mut(&mut axis);
if not_zero {
2021-04-11 17:00:38 +08:00
let refl = Reflection::new(Unit::new_unchecked(axis), T::zero());
t[(m, m - 1)] = norm;
2021-04-11 17:00:38 +08:00
t[(n, m - 1)] = T::zero();
{
let mut work = work.rows_mut(0, end + 1);
refl.reflect(&mut t.generic_slice_mut(
(m, m),
(Const::<2>, Dynamic::new(dim.value() - m)),
));
2018-02-02 19:26:35 +08:00
refl.reflect_rows(
&mut t.generic_slice_mut((0, m), (Dynamic::new(end + 1), Const::<2>)),
2018-02-02 19:26:35 +08:00
&mut work,
);
}
if let Some(ref mut q) = q {
refl.reflect_rows(
&mut q.generic_slice_mut((0, m), (dim, Const::<2>)),
work,
);
}
}
2018-02-02 19:26:35 +08:00
} else {
// Decouple the 2x2 block if it has real eigenvalues.
2021-04-11 17:00:38 +08:00
if let Some(rot) = compute_2x2_basis(&t.fixed_slice::<2, 2>(start, start)) {
let inv_rot = rot.inverse();
2018-02-02 19:26:35 +08:00
inv_rot.rotate(&mut t.generic_slice_mut(
(start, start),
(Const::<2>, Dynamic::new(dim.value() - start)),
2018-02-02 19:26:35 +08:00
));
2018-10-22 13:00:10 +08:00
rot.rotate_rows(
&mut t.generic_slice_mut((0, start), (Dynamic::new(end + 1), Const::<2>)),
2018-10-22 13:00:10 +08:00
);
2021-04-11 17:00:38 +08:00
t[(end, start)] = T::zero();
if let Some(ref mut q) = q {
rot.rotate_rows(&mut q.generic_slice_mut((0, start), (dim, Const::<2>)));
}
}
// Check if we reached the beginning of the matrix.
if end > 2 {
end -= 2;
2018-02-02 19:26:35 +08:00
} else {
break;
}
}
let sub = Self::delimit_subproblem(&mut t, eps.clone(), end);
start = sub.0;
2018-02-02 19:26:35 +08:00
end = sub.1;
niter += 1;
if niter == max_niter {
return None;
}
}
t.scale_mut(amax_m);
Some((q, t))
}
/// Computes the eigenvalues of the decomposed matrix.
fn do_eigenvalues(t: &OMatrix<T, D, D>, out: &mut OVector<T, D>) -> bool {
let dim = t.nrows();
let mut m = 0;
while m < dim - 1 {
let n = m + 1;
if t[(n, m)].is_zero() {
out[m] = t[(m, m)].clone();
m += 1;
2018-02-02 19:26:35 +08:00
} else {
// Complex eigenvalue.
return false;
}
}
if m == dim - 1 {
out[m] = t[(m, m)].clone();
}
true
}
/// Computes the complex eigenvalues of the decomposed matrix.
fn do_complex_eigenvalues(t: &OMatrix<T, D, D>, out: &mut UninitVector<NumComplex<T>, D>)
where
2021-04-11 17:00:38 +08:00
T: RealField,
DefaultAllocator: Allocator<NumComplex<T>, D>,
2020-03-21 19:16:46 +08:00
{
let dim = t.nrows();
let mut m = 0;
while m < dim - 1 {
let n = m + 1;
if t[(n, m)].is_zero() {
out[m] = MaybeUninit::new(NumComplex::new(t[(m, m)].clone(), T::zero()));
m += 1;
2018-02-02 19:26:35 +08:00
} else {
// Solve the 2x2 eigenvalue subproblem.
let hmm = t[(m, m)].clone();
let hnm = t[(n, m)].clone();
let hmn = t[(m, n)].clone();
let hnn = t[(n, n)].clone();
// NOTE: use the same algorithm as in compute_2x2_eigvals.
let val = (hmm.clone() - hnn.clone()) * crate::convert(0.5);
let discr = hnm * hmn + val.clone() * val;
// All 2x2 blocks have negative discriminant because we already decoupled those
// with positive eigenvalues.
2021-04-11 17:00:38 +08:00
let sqrt_discr = NumComplex::new(T::zero(), (-discr).sqrt());
let half_tra = (hnn + hmm) * crate::convert(0.5);
out[m] = MaybeUninit::new(
NumComplex::new(half_tra.clone(), T::zero()) + sqrt_discr.clone(),
);
out[m + 1] =
MaybeUninit::new(NumComplex::new(half_tra, T::zero()) - sqrt_discr.clone());
m += 2;
}
}
if m == dim - 1 {
out[m] = MaybeUninit::new(NumComplex::new(t[(m, m)].clone(), T::zero()));
}
}
2021-04-11 17:00:38 +08:00
fn delimit_subproblem(t: &mut OMatrix<T, D, D>, eps: T::RealField, end: usize) -> (usize, usize)
2018-02-02 19:26:35 +08:00
where
D: DimSub<U1>,
2021-04-11 17:00:38 +08:00
DefaultAllocator: Allocator<T, DimDiff<D, U1>>,
2018-02-02 19:26:35 +08:00
{
let mut n = end;
while n > 0 {
let m = n - 1;
if t[(n, m)].clone().norm1()
<= eps.clone() * (t[(n, n)].clone().norm1() + t[(m, m)].clone().norm1())
{
2021-04-11 17:00:38 +08:00
t[(n, m)] = T::zero();
2018-02-02 19:26:35 +08:00
} else {
break;
}
n -= 1;
}
if n == 0 {
return (0, 0);
}
let mut new_start = n - 1;
while new_start > 0 {
let m = new_start - 1;
let off_diag = t[(new_start, m)].clone();
2018-02-02 19:26:35 +08:00
if off_diag.is_zero()
|| off_diag.norm1()
<= eps.clone()
* (t[(new_start, new_start)].clone().norm1() + t[(m, m)].clone().norm1())
2018-02-02 19:26:35 +08:00
{
2021-04-11 17:00:38 +08:00
t[(new_start, m)] = T::zero();
break;
}
new_start -= 1;
}
(new_start, n)
}
/// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the
/// decomposed matrix equals `Q * T * Q.transpose()`.
2021-04-11 17:00:38 +08:00
pub fn unpack(self) -> (OMatrix<T, D, D>, OMatrix<T, D, D>) {
(self.q, self.t)
}
/// Computes the real eigenvalues of the decomposed matrix.
///
/// Return `None` if some eigenvalues are complex.
#[must_use]
2021-04-11 17:00:38 +08:00
pub fn eigenvalues(&self) -> Option<OVector<T, D>> {
let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>);
if Self::do_eigenvalues(&self.t, &mut out) {
Some(out)
2018-02-02 19:26:35 +08:00
} else {
None
}
}
/// Computes the complex eigenvalues of the decomposed matrix.
#[must_use]
2021-04-11 17:00:38 +08:00
pub fn complex_eigenvalues(&self) -> OVector<NumComplex<T>, D>
2020-03-21 19:16:46 +08:00
where
2021-04-11 17:00:38 +08:00
T: RealField,
DefaultAllocator: Allocator<NumComplex<T>, D>,
2020-03-21 19:16:46 +08:00
{
let mut out = Matrix::uninit(self.t.shape_generic().0, Const::<1>);
Self::do_complex_eigenvalues(&self.t, &mut out);
// Safety: out has been fully initialized by do_complex_eigenvalues.
2021-07-17 17:36:14 +08:00
unsafe { out.assume_init() }
}
}
2021-04-11 17:00:38 +08:00
fn decompose_2x2<T: ComplexField, D: Dim>(
mut m: OMatrix<T, D, D>,
2018-02-02 19:26:35 +08:00
compute_q: bool,
2021-04-11 17:00:38 +08:00
) -> Option<(Option<OMatrix<T, D, D>>, OMatrix<T, D, D>)>
2018-02-02 19:26:35 +08:00
where
2021-04-11 17:00:38 +08:00
DefaultAllocator: Allocator<T, D, D>,
2018-02-02 19:26:35 +08:00
{
let dim = m.shape_generic().0;
let mut q = None;
2021-04-11 17:00:38 +08:00
match compute_2x2_basis(&m.fixed_slice::<2, 2>(0, 0)) {
Some(rot) => {
2021-04-11 17:00:38 +08:00
let mut m = m.fixed_slice_mut::<2, 2>(0, 0);
let inv_rot = rot.inverse();
inv_rot.rotate(&mut m);
rot.rotate_rows(&mut m);
2021-04-11 17:00:38 +08:00
m[(1, 0)] = T::zero();
if compute_q {
// XXX: we have to build the matrix manually because
// rot.to_rotation_matrix().unwrap() causes an ICE.
2021-04-11 17:00:38 +08:00
let c = T::from_real(rot.c());
q = Some(OMatrix::from_column_slice_generic(
2018-02-02 19:26:35 +08:00
dim,
dim,
&[c.clone(), rot.s(), -rot.s().conjugate(), c],
2018-02-02 19:26:35 +08:00
));
}
2018-02-02 19:26:35 +08:00
}
2018-10-22 13:00:10 +08:00
None => {
if compute_q {
2021-04-11 17:00:38 +08:00
q = Some(OMatrix::identity_generic(dim, dim));
2018-10-22 13:00:10 +08:00
}
}
};
Some((q, m))
}
2021-04-11 17:00:38 +08:00
fn compute_2x2_eigvals<T: ComplexField, S: Storage<T, U2, U2>>(
m: &SquareMatrix<T, U2, S>,
) -> Option<(T, T)> {
// Solve the 2x2 eigenvalue subproblem.
let h00 = m[(0, 0)].clone();
let h10 = m[(1, 0)].clone();
let h01 = m[(0, 1)].clone();
let h11 = m[(1, 1)].clone();
2019-03-12 20:15:02 +08:00
// NOTE: this discriminant computation is more stable than the
// one based on the trace and determinant: 0.25 * tra * tra - det
2019-03-12 20:15:02 +08:00
// because it ensures positiveness for symmetric matrices.
let val = (h00.clone() - h11.clone()) * crate::convert(0.5);
let discr = h10 * h01 + val.clone() * val;
discr.try_sqrt().map(|sqrt_discr| {
2019-03-23 21:29:07 +08:00
let half_tra = (h00 + h11) * crate::convert(0.5);
(half_tra.clone() + sqrt_discr.clone(), half_tra - sqrt_discr)
})
}
// Computes the 2x2 transformation that upper-triangulates a 2x2 matrix with real eigenvalues.
/// Computes the singular vectors for a 2x2 matrix.
///
/// Returns `None` if the matrix has complex eigenvalues, or is upper-triangular. In both case,
/// the basis is the identity.
2021-04-11 17:00:38 +08:00
fn compute_2x2_basis<T: ComplexField, S: Storage<T, U2, U2>>(
m: &SquareMatrix<T, U2, S>,
) -> Option<GivensRotation<T>> {
let h10 = m[(1, 0)].clone();
if h10.is_zero() {
return None;
}
if let Some((eigval1, eigval2)) = compute_2x2_eigvals(m) {
let x1 = eigval1 - m[(1, 1)].clone();
let x2 = eigval2 - m[(1, 1)].clone();
2019-03-12 20:15:02 +08:00
// NOTE: Choose the one that yields a larger x component.
// This is necessary for numerical stability of the normalization of the complex
// number.
if x1.clone().norm1() > x2.clone().norm1() {
2019-03-18 18:23:19 +08:00
Some(GivensRotation::new(x1, h10).0)
2018-02-02 19:26:35 +08:00
} else {
2019-03-18 18:23:19 +08:00
Some(GivensRotation::new(x2, h10).0)
}
2018-02-02 19:26:35 +08:00
} else {
None
}
}
2021-04-11 17:00:38 +08:00
impl<T: ComplexField, D: Dim, S: Storage<T, D, D>> SquareMatrix<T, D, S>
2018-02-02 19:26:35 +08:00
where
2020-03-21 19:16:46 +08:00
D: DimSub<U1>, // For Hessenberg.
2021-04-11 17:00:38 +08:00
DefaultAllocator: Allocator<T, D, DimDiff<D, U1>>
+ Allocator<T, DimDiff<D, U1>>
+ Allocator<T, D, D>
+ Allocator<T, D>,
2018-02-02 19:26:35 +08:00
{
/// Computes the eigenvalues of this matrix.
#[must_use]
2021-04-11 17:00:38 +08:00
pub fn eigenvalues(&self) -> Option<OVector<T, D>> {
2018-02-02 19:26:35 +08:00
assert!(
self.is_square(),
"Unable to compute eigenvalues of a non-square matrix."
);
let mut work = Matrix::zeros_generic(self.shape_generic().0, Const::<1>);
2018-09-24 12:48:42 +08:00
// Special case for 2x2 matrices.
if self.nrows() == 2 {
2020-11-15 23:57:49 +08:00
// TODO: can we avoid this slicing
// (which is needed here just to transform D to U2)?
2021-04-11 17:00:38 +08:00
let me = self.fixed_slice::<2, 2>(0, 0);
return match compute_2x2_eigvals(&me) {
Some((a, b)) => {
work[0] = a;
work[1] = b;
Some(work)
2018-02-02 19:26:35 +08:00
}
None => None,
};
}
2020-11-15 23:57:49 +08:00
// TODO: add balancing?
let schur = Schur::do_decompose(
2018-02-02 19:26:35 +08:00
self.clone_owned(),
&mut work,
2021-04-11 17:00:38 +08:00
T::RealField::default_epsilon(),
2018-02-02 19:26:35 +08:00
0,
false,
2018-10-22 13:00:10 +08:00
)
.unwrap();
if Schur::do_eigenvalues(&schur.1, &mut work) {
Some(work)
2018-02-02 19:26:35 +08:00
} else {
None
}
}
/// Computes the eigenvalues of this matrix.
#[must_use]
2021-04-11 17:00:38 +08:00
pub fn complex_eigenvalues(&self) -> OVector<NumComplex<T>, D>
2020-11-15 23:57:49 +08:00
// TODO: add balancing?
2020-03-21 19:16:46 +08:00
where
2021-04-11 17:00:38 +08:00
T: RealField,
DefaultAllocator: Allocator<NumComplex<T>, D>,
2020-03-21 19:16:46 +08:00
{
let dim = self.shape_generic().0;
let mut work = Matrix::zeros_generic(dim, Const::<1>);
let schur = Schur::do_decompose(
2018-02-02 19:26:35 +08:00
self.clone_owned(),
&mut work,
2021-04-11 17:00:38 +08:00
T::default_epsilon(),
2018-02-02 19:26:35 +08:00
0,
false,
2018-10-22 13:00:10 +08:00
)
.unwrap();
let mut eig = Matrix::uninit(dim, Const::<1>);
Schur::do_complex_eigenvalues(&schur.1, &mut eig);
// Safety: eig has been fully initialized by do_complex_eigenvalues.
2021-07-17 17:36:14 +08:00
unsafe { eig.assume_init() }
}
}