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}