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}