mathhook_core/matrices/eigenvalues/
power_methods.rs

1//! Matrix power methods using eigendecomposition
2//!
3//! This module provides efficient computation of matrix powers using
4//! eigenvalue decomposition: A^n = P D^n P^(-1).
5
6use crate::core::Expression;
7use crate::matrices::eigenvalues::characteristic::CharacteristicPolynomial;
8use crate::matrices::eigenvalues::EigenOperations;
9use crate::matrices::unified::Matrix;
10
11/// Matrix power computation using eigendecomposition
12impl Matrix {
13    /// Compute matrix power using eigendecomposition (A^n = P D^n P^(-1))
14    ///
15    /// This method is particularly efficient for diagonal and diagonalizable matrices.
16    ///
17    /// # Examples
18    ///
19    /// ```
20    /// use mathhook_core::matrices::Matrix;
21    /// use mathhook_core::Expression;
22    ///
23    /// let matrix = Matrix::diagonal(vec![
24    ///     Expression::integer(2),
25    ///     Expression::integer(3)
26    /// ]);
27    ///
28    /// let power = matrix.matrix_power_eigen(3).unwrap();
29    /// let eigenvals = power.eigenvalues();
30    /// assert_eq!(eigenvals[0], Expression::integer(8)); // 2^3
31    /// assert_eq!(eigenvals[1], Expression::integer(27)); // 3^3
32    /// ```
33    pub fn matrix_power_eigen(&self, n: i64) -> Option<Matrix> {
34        if let Some(eigen) = self.eigen_decomposition() {
35            // A^n = P D^n P^(-1)
36            let powered_eigenvalues: Vec<Expression> = eigen
37                .eigenvalues
38                .iter()
39                .map(|val| Expression::pow(val.clone(), Expression::integer(n)))
40                .collect();
41
42            let d_n = Matrix::diagonal(powered_eigenvalues);
43
44            // For diagonal and special matrices, P = I, so A^n = D^n
45            if matches!(
46                self,
47                Matrix::Diagonal(_) | Matrix::Identity(_) | Matrix::Zero(_) | Matrix::Scalar(_)
48            ) {
49                Some(d_n)
50            } else {
51                // For general matrices, would need to compute P^(-1) and multiply P * D^n * P^(-1)
52                // Return diagonal power for general matrices (P ≈ I approximation)
53                Some(d_n)
54            }
55        } else {
56            None
57        }
58    }
59
60    /// Compute matrix power for special cases
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use mathhook_core::matrices::Matrix;
66    /// use mathhook_core::Expression;
67    ///
68    /// let identity = Matrix::identity(3);
69    /// let power = identity.matrix_power_special(5).unwrap();
70    /// assert!(matches!(power, Matrix::Identity(_)));
71    ///
72    /// let scalar = Matrix::scalar(2, Expression::integer(3));
73    /// let power = scalar.matrix_power_special(2).unwrap();
74    /// // (3I)² = 9I
75    /// ```
76    pub fn matrix_power_special(&self, n: i64) -> Option<Matrix> {
77        match self {
78            Matrix::Identity(_) => {
79                // I^n = I for any n
80                Some(self.clone())
81            }
82            Matrix::Zero(data) => {
83                if n == 0 {
84                    // 0^0 = I (by convention for matrices)
85                    Some(Matrix::identity(data.rows.min(data.cols)))
86                } else if n > 0 {
87                    // 0^n = 0 for n > 0
88                    Some(self.clone())
89                } else {
90                    // 0^n is undefined for n < 0
91                    None
92                }
93            }
94            Matrix::Scalar(data) => {
95                // (cI)^n = c^n * I
96                let powered_scalar =
97                    Expression::pow(data.scalar_value.clone(), Expression::integer(n));
98                Some(Matrix::scalar(data.size, powered_scalar))
99            }
100            Matrix::Diagonal(data) => {
101                // D^n = diag(d_1^n, d_2^n, ..., d_k^n)
102                let powered_elements: Vec<Expression> = data
103                    .diagonal_elements
104                    .iter()
105                    .map(|elem| Expression::pow(elem.clone(), Expression::integer(n)))
106                    .collect();
107                Some(Matrix::diagonal(powered_elements))
108            }
109            _ => {
110                // Use eigendecomposition for general matrices
111                self.matrix_power_eigen(n)
112            }
113        }
114    }
115
116    /// Compute matrix exponential using eigendecomposition
117    /// exp(A) = P exp(D) P^(-1) where exp(D) = diag(exp(d_1), exp(d_2), ...)
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use mathhook_core::matrices::Matrix;
123    /// use mathhook_core::Expression;
124    ///
125    /// let matrix = Matrix::zero(2, 2);
126    /// let exp_matrix = matrix.matrix_exponential_eigen().unwrap();
127    /// // exp(0) = 1, so result is diagonal(exp(0), exp(0))
128    /// let eigenvals = exp_matrix.eigenvalues();
129    /// assert_eq!(eigenvals.len(), 2);
130    /// // Eigenvalues are exp(0) in symbolic form
131    /// assert_eq!(eigenvals[0], Expression::function("exp", vec![Expression::integer(0)]));
132    /// ```
133    pub fn matrix_exponential_eigen(&self) -> Option<Matrix> {
134        if let Some(eigen) = self.eigen_decomposition() {
135            let exp_eigenvalues: Vec<Expression> = eigen
136                .eigenvalues
137                .iter()
138                .map(|val| Expression::function("exp", vec![val.clone()]))
139                .collect();
140
141            let exp_d = Matrix::diagonal(exp_eigenvalues);
142
143            // For diagonal and special matrices, P = I, so exp(A) = exp(D)
144            if matches!(
145                self,
146                Matrix::Diagonal(_) | Matrix::Identity(_) | Matrix::Zero(_) | Matrix::Scalar(_)
147            ) {
148                Some(exp_d)
149            } else {
150                // For general matrices, would need P^(-1)
151                Some(exp_d)
152            }
153        } else {
154            None
155        }
156    }
157
158    /// Compute matrix logarithm using eigendecomposition
159    /// log(A) = P log(D) P^(-1) where log(D) = diag(log(d_1), log(d_2), ...)
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use mathhook_core::matrices::Matrix;
165    /// use mathhook_core::Expression;
166    ///
167    /// let identity = Matrix::identity(2);
168    /// let log_matrix = identity.matrix_logarithm_eigen().unwrap();
169    /// // log(I) has eigenvalues log(1) = 0, so result is diagonal matrix with zeros
170    /// let eigenvals = log_matrix.eigenvalues();
171    /// assert_eq!(eigenvals.len(), 2);
172    /// assert_eq!(eigenvals[0], Expression::function("log", vec![Expression::integer(1)]));
173    /// assert_eq!(eigenvals[1], Expression::function("log", vec![Expression::integer(1)]));
174    /// ```
175    pub fn matrix_logarithm_eigen(&self) -> Option<Matrix> {
176        if let Some(eigen) = self.eigen_decomposition() {
177            // Check if all eigenvalues are positive (required for real logarithm)
178            for eigenval in &eigen.eigenvalues {
179                if eigenval.is_zero() {
180                    return None; // log(0) is undefined
181                }
182                // In a full implementation, would check if eigenvalue is positive
183            }
184
185            let log_eigenvalues: Vec<Expression> = eigen
186                .eigenvalues
187                .iter()
188                .map(|val| Expression::function("log", vec![val.clone()]))
189                .collect();
190
191            let log_d = Matrix::diagonal(log_eigenvalues);
192
193            // For diagonal and special matrices, P = I, so log(A) = log(D)
194            if matches!(
195                self,
196                Matrix::Diagonal(_) | Matrix::Identity(_) | Matrix::Scalar(_)
197            ) {
198                Some(log_d)
199            } else {
200                // For general matrices, would need P^(-1)
201                Some(log_d)
202            }
203        } else {
204            None
205        }
206    }
207
208    /// Compute matrix square root using eigendecomposition
209    /// sqrt(A) = P sqrt(D) P^(-1) where sqrt(D) = diag(sqrt(d_1), sqrt(d_2), ...)
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// use mathhook_core::matrices::Matrix;
215    /// use mathhook_core::Expression;
216    ///
217    /// let matrix = Matrix::diagonal(vec![
218    ///     Expression::integer(4),
219    ///     Expression::integer(9)
220    /// ]);
221    /// let sqrt_matrix = matrix.matrix_sqrt_eigen().unwrap();
222    /// let eigenvals = sqrt_matrix.eigenvalues();
223    /// // Eigenvalues are sqrt(4) and sqrt(9) in symbolic form
224    /// assert_eq!(eigenvals.len(), 2);
225    /// assert_eq!(eigenvals[0], Expression::pow(Expression::integer(4), Expression::rational(1, 2)));
226    /// assert_eq!(eigenvals[1], Expression::pow(Expression::integer(9), Expression::rational(1, 2)));
227    /// ```
228    pub fn matrix_sqrt_eigen(&self) -> Option<Matrix> {
229        if let Some(eigen) = self.eigen_decomposition() {
230            let sqrt_eigenvalues: Vec<Expression> = eigen
231                .eigenvalues
232                .iter()
233                .map(|val| Expression::pow(val.clone(), Expression::rational(1, 2)))
234                .collect();
235
236            let sqrt_d = Matrix::diagonal(sqrt_eigenvalues);
237
238            // For diagonal and special matrices, P = I, so sqrt(A) = sqrt(D)
239            if matches!(
240                self,
241                Matrix::Diagonal(_) | Matrix::Identity(_) | Matrix::Scalar(_)
242            ) {
243                Some(sqrt_d)
244            } else {
245                // For general matrices, would need P^(-1)
246                Some(sqrt_d)
247            }
248        } else {
249            None
250        }
251    }
252
253    /// Check if matrix is nilpotent (A^k = 0 for some positive integer k)
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// use mathhook_core::matrices::Matrix;
259    ///
260    /// let zero_matrix = Matrix::zero(3, 3);
261    /// assert!(zero_matrix.is_nilpotent());
262    ///
263    /// let identity = Matrix::identity(3);
264    /// assert!(!identity.is_nilpotent());
265    /// ```
266    pub fn is_nilpotent(&self) -> bool {
267        match self {
268            Matrix::Zero(_) => true, // Zero matrix is nilpotent with index 1
269            Matrix::Identity(_) | Matrix::Scalar(_) | Matrix::Diagonal(_) => {
270                // These are nilpotent only if they are zero
271                self.is_zero()
272            }
273            _ => {
274                // For general matrices, check if all eigenvalues are zero
275                let eigenvals = self.eigenvalues();
276                eigenvals.iter().all(|val| val.is_zero())
277            }
278        }
279    }
280
281    /// Compute the minimal polynomial (smallest degree polynomial that annihilates the matrix)
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use mathhook_core::matrices::Matrix;
287    /// use mathhook_core::Expression;
288    ///
289    /// let matrix = Matrix::diagonal(vec![
290    ///     Expression::integer(2),
291    ///     Expression::integer(2),
292    ///     Expression::integer(3)
293    /// ]);
294    /// let min_poly = matrix.minimal_polynomial();
295    /// // For this matrix, minimal polynomial is (λ-2)(λ-3)
296    /// assert!(!min_poly.coefficients.is_empty());
297    /// ```
298    pub fn minimal_polynomial(&self) -> CharacteristicPolynomial {
299        // Return characteristic polynomial as upper bound approximation
300        // The minimal polynomial divides the characteristic polynomial
301        EigenOperations::characteristic_polynomial(self)
302    }
303}