Skip to main content

mdarray_linalg/
eig.rs

1//! Eigenvalue, eigenvector, and Schur decomposition utilities for general and self-adjoint matrices
2//!
3//! ```rust,ignore
4//! use mdarray_linalg::prelude::*;
5//! use mdarray_linalg_backend::Backend;
6//!
7//! // ----- Eigenvalue decomposition -----
8//! // Note: we must clone `a` here because decomposition routines destroy the input.
9//! let bd = Backend::default();
10//! let EigDecomp { eigenvalues, right_eigenvectors, .. } = bd
11//!     .eig(&mut a.clone())
12//!     .expect("Eigenvalue decomposition failed");
13//!
14//! // Or...
15//! let EigDecomp { eigenvalues: lambda, right_eigenvectors: Some(v), .. } = bd
16//!     .eig(&mut a.clone())
17//!     .expect("Eigenvalue decomposition failed");
18//!
19//! // Full decomposition with left and right eigenvectors.
20//! let EigDecomp { eigenvalues, left_eigenvectors, right_eigenvectors } = bd
21//!     .eig_full(&mut a.clone())
22//!     .expect("Full eigenvalue decomposition failed");
23//! let left = left_eigenvectors.expect("Left eigenvectors were not computed");
24//! let right = right_eigenvectors.expect("Right eigenvectors were not computed");
25//!
26//! // ----- Schur decomposition -----
27//! // A = Z * T * Z^H, with `Z^H` reducing to `Z^T` for real Schur decompositions.
28//! let SchurDecomp { t, z } = bd
29//!     .schur(&mut a.clone())
30//!     .expect("Schur decomposition failed");
31//!
32//! // Reconstruct A from the decomposition with the conjugate transpose Z^H:
33//! // A ≈ Z * T * Z^H
34//! ```
35
36use mdarray::{Array, Dense, Dim, Layout, Slice};
37use thiserror::Error;
38
39/// Error types related to eigenvalue decomposition
40#[derive(Debug, Error)]
41pub enum EigError {
42    #[error("Backend error code: {0}")]
43    BackendError(i32),
44
45    #[error("Backend failed to converge: {iterations} iterations exceeded")]
46    BackendDidNotConverge { iterations: i32 },
47
48    #[error("Matrix must be square for eigenvalue decomposition")]
49    NotSquareMatrix,
50}
51
52/// Holds the results of a general eigenvalue decomposition.
53///
54/// The scalar type `S` is the backend's spectral scalar for the input matrix
55/// scalar. For a real matrix this is typically a complex scalar, while for a
56/// complex matrix it is usually the matrix scalar itself.
57pub struct EigDecomp<S, D0: Dim, D1: Dim> {
58    pub eigenvalues: Array<S, (D0,)>,
59    pub left_eigenvectors: Option<Array<S, (D0, D1)>>,
60    pub right_eigenvectors: Option<Array<S, (D0, D1)>>,
61}
62
63/// Holds the results of a self-adjoint eigenvalue decomposition.
64///
65/// Self-adjoint eigenvalues are real, while eigenvectors live in the input
66/// matrix scalar field.
67pub struct EighDecomp<T, R, D0: Dim, D1: Dim> {
68    pub eigenvalues: Array<R, (D0,)>,
69    pub eigenvectors: Array<T, (D0, D1)>,
70}
71
72/// Error types related to Schur decomposition
73#[derive(Debug, Error)]
74pub enum SchurError {
75    #[error("Backend error code: {0}")]
76    BackendError(i32),
77
78    #[error("Backend failed to converge: {iterations} iterations exceeded")]
79    BackendDidNotConverge { iterations: i32 },
80
81    #[error("Matrix must be square for Schur decomposition")]
82    NotSquareMatrix,
83}
84
85/// Holds the results of a Schur decomposition: A = Z * T * Z^H
86/// where Z is unitary and T is upper-triangular (complex) or quasi-upper triangular (real)
87pub struct SchurDecomp<T, D0: Dim, D1: Dim> {
88    /// Schur form T (upper-triangular for complex, quasi-upper triangular for real)
89    pub t: Array<T, (D0, D1)>,
90    /// Unitary Schur transformation matrix Z
91    pub z: Array<T, (D0, D1)>,
92}
93
94/// Eigenvalue decomposition operations of general and self-adjoint matrices.
95///
96/// Backends choose the spectral scalar model through associated types.
97/// General eigendecompositions and complex Schur decompositions use
98/// [`Self::SpectralScalar`], while self-adjoint eigendecompositions use
99/// [`Self::RealScalar`] for eigenvalues and the input scalar `T` for
100/// eigenvectors.
101pub trait Eig<T, D0: Dim, D1: Dim> {
102    /// Spectral scalar type used for general eigenvalues/eigenvectors and complex Schur decompositions.
103    type SpectralScalar;
104
105    /// Real scalar type used for self-adjoint eigenvalues.
106    type RealScalar;
107
108    /// Compute eigenvalues and right eigenvectors with new allocated matrices.
109    /// The matrix `A` satisfies: `A * v = λ * v` where v are the right eigenvectors.
110    fn eig<L: Layout>(
111        &self,
112        a: &mut Slice<T, (D0, D1), L>,
113    ) -> Result<EigDecomp<Self::SpectralScalar, D0, D1>, EigError>;
114
115    /// Compute eigenvalues and both left/right eigenvectors with new allocated matrices.
116    /// The matrix A satisfies: `A * vr = λ * vr` and `vl^H * A = λ * vl^H`.
117    fn eig_full<L: Layout>(
118        &self,
119        a: &mut Slice<T, (D0, D1), L>,
120    ) -> Result<EigDecomp<Self::SpectralScalar, D0, D1>, EigError>;
121
122    /// Compute only eigenvalues with a newly allocated vector.
123    fn eig_values<L: Layout>(
124        &self,
125        a: &mut Slice<T, (D0, D1), L>,
126    ) -> Result<Array<Self::SpectralScalar, (D0,)>, EigError>;
127
128    /// Compute eigenvalues and eigenvectors of a self-adjoint matrix.
129    fn eigh<L: Layout>(
130        &self,
131        a: &mut Slice<T, (D0, D1), L>,
132    ) -> Result<EighDecomp<T, Self::RealScalar, D0, D1>, EigError>;
133
134    /// Compute Schur decomposition over the input scalar field.
135    fn schur<L: Layout>(
136        &self,
137        a: &mut Slice<T, (D0, D1), L>,
138    ) -> Result<SchurDecomp<T, D0, D1>, SchurError>;
139
140    /// Compute Schur decomposition overwriting existing matrices.
141    fn schur_write<L: Layout>(
142        &self,
143        a: &mut Slice<T, (D0, D1), L>,
144        t: &mut Slice<T, (D0, D1), Dense>,
145        z: &mut Slice<T, (D0, D1), Dense>,
146    ) -> Result<(), SchurError>;
147
148    /// Compute Schur decomposition over the spectral scalar field.
149    fn schur_complex<L: Layout>(
150        &self,
151        a: &mut Slice<T, (D0, D1), L>,
152    ) -> Result<SchurDecomp<Self::SpectralScalar, D0, D1>, SchurError>;
153
154    /// Compute Schur decomposition over the spectral scalar field, overwriting existing matrices.
155    fn schur_complex_write<L: Layout>(
156        &self,
157        a: &mut Slice<T, (D0, D1), L>,
158        t: &mut Slice<Self::SpectralScalar, (D0, D1), Dense>,
159        z: &mut Slice<Self::SpectralScalar, (D0, D1), Dense>,
160    ) -> Result<(), SchurError>;
161}