mathhook_core/matrices/
eigenvalues.rs

1//! Eigenvalue and eigenvector computation
2//!
3//! This module provides methods for computing eigenvalues and eigenvectors
4//! of matrices, including both real and complex cases, characteristic polynomials,
5//! and matrix functions using eigendecomposition.
6
7pub mod characteristic;
8pub mod computation;
9pub mod eigenvalues_tests;
10pub mod power_methods;
11
12use crate::core::expression::Expression;
13use crate::core::symbol::Symbol;
14use crate::matrices::eigenvalues::characteristic::CharacteristicPolynomial;
15use crate::matrices::types::*;
16use crate::matrices::unified::Matrix;
17use crate::simplify::Simplify;
18
19/// Eigenvalue and eigenvector operations trait
20///
21/// This trait provides a unified interface for all eigenvalue-related computations.
22/// Methods return Options to handle cases where computation is not possible.
23pub trait EigenOperations {
24    /// Compute eigenvalues and eigenvectors
25    ///
26    /// Returns eigenvalues and corresponding eigenvectors for real matrices.
27    /// For matrices with complex eigenvalues, use `complex_eigen_decomposition`.
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// use mathhook_core::matrices::Matrix;
33    /// use mathhook_core::matrices::eigenvalues::EigenOperations;
34    /// use mathhook_core::Expression;
35    ///
36    /// let matrix = Matrix::diagonal(vec![
37    ///     Expression::integer(2),
38    ///     Expression::integer(3)
39    /// ]);
40    ///
41    /// let eigen = matrix.eigen_decomposition().unwrap();
42    /// assert_eq!(eigen.eigenvalues.len(), 2);
43    /// ```
44    fn eigen_decomposition(&self) -> Option<EigenDecomposition>;
45
46    /// Compute complex eigenvalues and eigenvectors
47    ///
48    /// Handles matrices that may have complex eigenvalues and eigenvectors.
49    fn complex_eigen_decomposition(&self) -> Option<ComplexEigenDecomposition>;
50
51    /// Compute only eigenvalues (faster than full decomposition)
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use mathhook_core::matrices::Matrix;
57    /// use mathhook_core::matrices::eigenvalues::EigenOperations;
58    /// use mathhook_core::Expression;
59    ///
60    /// let matrix = Matrix::diagonal(vec![
61    ///     Expression::integer(2),
62    ///     Expression::integer(3)
63    /// ]);
64    ///
65    /// let eigenvalues = matrix.eigenvalues();
66    /// assert_eq!(eigenvalues.len(), 2);
67    /// assert_eq!(eigenvalues[0], Expression::integer(2));
68    /// assert_eq!(eigenvalues[1], Expression::integer(3));
69    /// ```
70    fn eigenvalues(&self) -> Vec<Expression>;
71
72    /// Compute characteristic polynomial det(A - λI)
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use mathhook_core::matrices::Matrix;
78    /// use mathhook_core::matrices::eigenvalues::EigenOperations;
79    /// use mathhook_core::Expression;
80    ///
81    /// let matrix = Matrix::diagonal(vec![
82    ///     Expression::integer(2),
83    ///     Expression::integer(3)
84    /// ]);
85    ///
86    /// let poly = matrix.characteristic_polynomial();
87    /// assert_eq!(poly.degree(), 2);
88    /// ```
89    fn characteristic_polynomial(&self) -> CharacteristicPolynomial;
90
91    /// Get the trace (sum of eigenvalues)
92    fn trace(&self) -> Expression;
93
94    /// Get the determinant (product of eigenvalues)
95    fn determinant_via_eigenvalues(&self) -> Expression;
96
97    /// Check if matrix is diagonalizable
98    fn is_diagonalizable(&self) -> bool;
99
100    /// Compute matrix power using eigendecomposition
101    ///
102    /// For diagonalizable matrices, computes A^n = P D^n P^(-1)
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use mathhook_core::matrices::Matrix;
108    /// use mathhook_core::matrices::eigenvalues::EigenOperations;
109    /// use mathhook_core::Expression;
110    ///
111    /// let matrix = Matrix::diagonal(vec![
112    ///     Expression::integer(2),
113    ///     Expression::integer(3)
114    /// ]);
115    ///
116    /// let power = matrix.matrix_power_eigen(3).unwrap();
117    /// // Returns diag([8, 27])
118    /// ```
119    fn matrix_power_eigen(&self, n: i64) -> Option<Matrix>;
120
121    /// Compute matrix exponential using eigendecomposition
122    fn matrix_exponential(&self) -> Option<Matrix>;
123
124    /// Compute matrix logarithm using eigendecomposition
125    fn matrix_logarithm(&self) -> Option<Matrix>;
126
127    /// Compute matrix square root using eigendecomposition
128    fn matrix_sqrt(&self) -> Option<Matrix>;
129
130    /// Check if matrix is nilpotent
131    fn is_nilpotent(&self) -> bool;
132}
133
134/// Implementation of EigenOperations trait for Matrix
135impl EigenOperations for Matrix {
136    fn eigen_decomposition(&self) -> Option<EigenDecomposition> {
137        // Call inherent method from computation module
138        // Rust resolves this correctly because it's defined as impl Matrix {...}
139        Matrix::eigen_decomposition(self)
140    }
141
142    fn complex_eigen_decomposition(&self) -> Option<ComplexEigenDecomposition> {
143        // Call inherent method from computation module
144        Matrix::complex_eigen_decomposition(self)
145    }
146
147    fn eigenvalues(&self) -> Vec<Expression> {
148        // Call inherent method from computation module
149        Matrix::eigenvalues(self)
150    }
151
152    fn characteristic_polynomial(&self) -> CharacteristicPolynomial {
153        // Construct characteristic polynomial from eigenvalues
154        // For a matrix with eigenvalues λ₁, λ₂, ..., λₙ:
155        // char_poly(λ) = (λ - λ₁)(λ - λ₂)...(λ - λₙ)
156        let eigenvals = Matrix::eigenvalues(self);
157        let lambda = Symbol::scalar("lambda");
158
159        if eigenvals.is_empty() {
160            return CharacteristicPolynomial::new(vec![Expression::integer(0)], lambda);
161        }
162
163        // Start with (λ - λ₁)
164        let mut poly = CharacteristicPolynomial::new(
165            vec![
166                Expression::mul(vec![Expression::integer(-1), eigenvals[0].clone()]),
167                Expression::integer(1),
168            ],
169            lambda.clone(),
170        );
171
172        // Multiply by (λ - λᵢ) for remaining eigenvalues
173        for eigenval in eigenvals.iter().skip(1) {
174            let factor = CharacteristicPolynomial::new(
175                vec![
176                    Expression::mul(vec![Expression::integer(-1), eigenval.clone()]),
177                    Expression::integer(1),
178                ],
179                lambda.clone(),
180            );
181            poly = poly.multiply(&factor);
182        }
183
184        poly
185    }
186
187    fn trace(&self) -> Expression {
188        // Call inherent method from unified/operations.rs
189        // Note: Matrix has a trace() inherent method, not from this trait
190        Matrix::trace(self)
191    }
192
193    fn determinant_via_eigenvalues(&self) -> Expression {
194        // Determinant equals product of eigenvalues
195        let eigenvals = Matrix::eigenvalues(self);
196        if eigenvals.is_empty() {
197            Expression::integer(0)
198        } else {
199            Expression::mul(eigenvals).simplify()
200        }
201    }
202
203    fn is_diagonalizable(&self) -> bool {
204        // Call inherent method from computation module
205        Matrix::is_diagonalizable(self)
206    }
207
208    fn matrix_power_eigen(&self, n: i64) -> Option<Matrix> {
209        // Call inherent method from power_methods module
210        Matrix::matrix_power_eigen(self, n)
211    }
212
213    fn matrix_exponential(&self) -> Option<Matrix> {
214        // Call inherent method from power_methods module
215        // Note: The inherent method is named matrix_exponential_eigen()
216        Matrix::matrix_exponential_eigen(self)
217    }
218
219    fn matrix_logarithm(&self) -> Option<Matrix> {
220        // Call inherent method from power_methods module
221        // Note: The inherent method is named matrix_logarithm_eigen()
222        Matrix::matrix_logarithm_eigen(self)
223    }
224
225    fn matrix_sqrt(&self) -> Option<Matrix> {
226        // Call inherent method from power_methods module
227        // Note: The inherent method is named matrix_sqrt_eigen()
228        Matrix::matrix_sqrt_eigen(self)
229    }
230
231    fn is_nilpotent(&self) -> bool {
232        // Matrix is nilpotent if all eigenvalues are zero
233        let eigenvals = Matrix::eigenvalues(self);
234        eigenvals.iter().all(|val| val.is_zero())
235    }
236}