amari_functional/spectral/
eigenvalue.rs1use crate::error::{FunctionalError, Result};
7use crate::operator::{LinearOperator, MatrixOperator};
8use amari_core::Multivector;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct Eigenvalue {
13 pub value: f64,
15 pub multiplicity: Option<usize>,
17}
18
19impl Eigenvalue {
20 pub fn new(value: f64) -> Self {
22 Self {
23 value,
24 multiplicity: None,
25 }
26 }
27
28 pub fn with_multiplicity(value: f64, multiplicity: usize) -> Self {
30 Self {
31 value,
32 multiplicity: Some(multiplicity),
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct Eigenpair<V> {
40 pub eigenvalue: Eigenvalue,
42 pub eigenvector: V,
44}
45
46impl<V> Eigenpair<V> {
47 pub fn new(eigenvalue: f64, eigenvector: V) -> Self {
49 Self {
50 eigenvalue: Eigenvalue::new(eigenvalue),
51 eigenvector,
52 }
53 }
54}
55
56pub fn power_method<const P: usize, const Q: usize, const R: usize>(
72 matrix: &MatrixOperator<P, Q, R>,
73 initial: Option<&Multivector<P, Q, R>>,
74 max_iterations: usize,
75 tolerance: f64,
76) -> Result<Eigenpair<Multivector<P, Q, R>>> {
77 let n = MatrixOperator::<P, Q, R>::DIM;
78
79 let mut v = if let Some(init) = initial {
81 init.clone()
82 } else {
83 let coeffs: Vec<f64> = (0..n).map(|_| 1.0 / (n as f64).sqrt()).collect();
85 Multivector::<P, Q, R>::from_slice(&coeffs)
86 };
87
88 let mut eigenvalue = 0.0;
89
90 for iter in 0..max_iterations {
91 let av = matrix.apply(&v)?;
93
94 let v_coeffs = v.to_vec();
96 let av_coeffs = av.to_vec();
97 let numerator: f64 = v_coeffs
98 .iter()
99 .zip(av_coeffs.iter())
100 .map(|(a, b)| a * b)
101 .sum();
102 let denominator: f64 = v_coeffs.iter().map(|x| x * x).sum();
103
104 if denominator.abs() < 1e-15 {
105 return Err(FunctionalError::numerical_instability(
106 "power method",
107 "Vector norm became zero",
108 ));
109 }
110
111 let new_eigenvalue = numerator / denominator;
112
113 if (new_eigenvalue - eigenvalue).abs() < tolerance && iter > 0 {
115 return Ok(Eigenpair::new(new_eigenvalue, v));
116 }
117 eigenvalue = new_eigenvalue;
118
119 let norm: f64 = av_coeffs.iter().map(|x| x * x).sum::<f64>().sqrt();
121 if norm < 1e-15 {
122 return Err(FunctionalError::numerical_instability(
123 "power method",
124 "Zero vector encountered",
125 ));
126 }
127 v = &av * (1.0 / norm);
128 }
129
130 Ok(Eigenpair::new(eigenvalue, v))
131}
132
133pub fn inverse_iteration<const P: usize, const Q: usize, const R: usize>(
149 matrix: &MatrixOperator<P, Q, R>,
150 shift: f64,
151 initial: Option<&Multivector<P, Q, R>>,
152 max_iterations: usize,
153 tolerance: f64,
154) -> Result<Eigenpair<Multivector<P, Q, R>>> {
155 let n = MatrixOperator::<P, Q, R>::DIM;
156
157 let identity = MatrixOperator::<P, Q, R>::identity();
159 let shifted = matrix.add(&identity.scale(-shift))?;
160
161 let mut v = if let Some(init) = initial {
165 init.clone()
166 } else {
167 let coeffs: Vec<f64> = (0..n).map(|_| 1.0 / (n as f64).sqrt()).collect();
168 Multivector::<P, Q, R>::from_slice(&coeffs)
169 };
170
171 for _ in 0..max_iterations {
172 let w = solve_linear_system(&shifted, &v, 50, tolerance)?;
174
175 let w_coeffs = w.to_vec();
177 let norm: f64 = w_coeffs.iter().map(|x| x * x).sum::<f64>().sqrt();
178 if norm < 1e-15 {
179 break;
180 }
181 v = &w * (1.0 / norm);
182 }
183
184 let av = matrix.apply(&v)?;
186 let v_coeffs = v.to_vec();
187 let av_coeffs = av.to_vec();
188 let numerator: f64 = v_coeffs
189 .iter()
190 .zip(av_coeffs.iter())
191 .map(|(a, b)| a * b)
192 .sum();
193 let denominator: f64 = v_coeffs.iter().map(|x| x * x).sum();
194
195 let eigenvalue = if denominator.abs() < 1e-15 {
196 shift
197 } else {
198 numerator / denominator
199 };
200
201 Ok(Eigenpair::new(eigenvalue, v))
202}
203
204pub fn compute_eigenvalues<const P: usize, const Q: usize, const R: usize>(
218 matrix: &MatrixOperator<P, Q, R>,
219 max_iterations: usize,
220 tolerance: f64,
221) -> Result<Vec<Eigenvalue>> {
222 if !matrix.is_symmetric(tolerance) {
223 return Err(FunctionalError::invalid_parameters(
224 "compute_eigenvalues requires a symmetric matrix",
225 ));
226 }
227
228 let n = matrix.rows();
229 let mut a = matrix.clone();
230
231 for _ in 0..max_iterations {
233 let mut max_val = 0.0;
235 let mut p = 0;
236 let mut q = 1;
237
238 for i in 0..n {
239 for j in (i + 1)..n {
240 let val = a.get(i, j).abs();
241 if val > max_val {
242 max_val = val;
243 p = i;
244 q = j;
245 }
246 }
247 }
248
249 if max_val < tolerance {
251 break;
252 }
253
254 let app = a.get(p, p);
256 let aqq = a.get(q, q);
257 let apq = a.get(p, q);
258
259 let theta = if (aqq - app).abs() < 1e-15 {
260 std::f64::consts::FRAC_PI_4
261 } else {
262 0.5 * ((2.0 * apq) / (aqq - app)).atan()
263 };
264
265 let c = theta.cos();
266 let s = theta.sin();
267
268 a = apply_jacobi_rotation(&a, p, q, c, s)?;
270 }
271
272 let mut eigenvalues: Vec<Eigenvalue> = (0..n).map(|i| Eigenvalue::new(a.get(i, i))).collect();
274
275 eigenvalues.sort_by(|a, b| {
277 b.value
278 .abs()
279 .partial_cmp(&a.value.abs())
280 .unwrap_or(std::cmp::Ordering::Equal)
281 });
282
283 Ok(eigenvalues)
284}
285
286fn apply_jacobi_rotation<const P: usize, const Q: usize, const R: usize>(
288 a: &MatrixOperator<P, Q, R>,
289 p: usize,
290 q: usize,
291 c: f64,
292 s: f64,
293) -> Result<MatrixOperator<P, Q, R>> {
294 let n = a.rows();
295 let mut result = a.clone();
296
297 for i in 0..n {
298 if i != p && i != q {
299 let aip = a.get(i, p);
300 let aiq = a.get(i, q);
301 result.set(i, p, c * aip - s * aiq);
302 result.set(p, i, c * aip - s * aiq);
303 result.set(i, q, s * aip + c * aiq);
304 result.set(q, i, s * aip + c * aiq);
305 }
306 }
307
308 let app = a.get(p, p);
309 let aqq = a.get(q, q);
310 let apq = a.get(p, q);
311
312 result.set(p, p, c * c * app - 2.0 * c * s * apq + s * s * aqq);
313 result.set(q, q, s * s * app + 2.0 * c * s * apq + c * c * aqq);
314 result.set(p, q, 0.0);
315 result.set(q, p, 0.0);
316
317 Ok(result)
318}
319
320fn solve_linear_system<const P: usize, const Q: usize, const R: usize>(
322 a: &MatrixOperator<P, Q, R>,
323 b: &Multivector<P, Q, R>,
324 max_iterations: usize,
325 _tolerance: f64,
326) -> Result<Multivector<P, Q, R>> {
327 let n = a.rows();
328 let b_coeffs = b.to_vec();
329 let mut x = vec![0.0; n];
330
331 for _ in 0..max_iterations {
332 for i in 0..n {
333 let mut sum = b_coeffs[i];
334 for j in 0..n {
335 if i != j {
336 sum -= a.get(i, j) * x[j];
337 }
338 }
339 let aii = a.get(i, i);
340 if aii.abs() > 1e-15 {
341 x[i] = sum / aii;
342 }
343 }
344 }
345
346 Ok(Multivector::<P, Q, R>::from_slice(&x))
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_eigenvalue_creation() {
355 let e = Eigenvalue::new(3.0);
356 assert!((e.value - 3.0).abs() < 1e-10);
357 assert!(e.multiplicity.is_none());
358
359 let e2 = Eigenvalue::with_multiplicity(2.0, 2);
360 assert_eq!(e2.multiplicity, Some(2));
361 }
362
363 #[test]
364 fn test_power_method_identity() {
365 let id: MatrixOperator<2, 0, 0> = MatrixOperator::identity();
366 let result = power_method(&id, None, 100, 1e-10).unwrap();
367
368 assert!((result.eigenvalue.value - 1.0).abs() < 0.1);
370 }
371
372 #[test]
373 fn test_power_method_diagonal() {
374 let diag: MatrixOperator<2, 0, 0> =
376 MatrixOperator::diagonal(&[4.0, 3.0, 2.0, 1.0]).unwrap();
377 let result = power_method(&diag, None, 100, 1e-10).unwrap();
378
379 assert!((result.eigenvalue.value - 4.0).abs() < 0.1);
381 }
382
383 #[test]
384 fn test_jacobi_eigenvalues_identity() {
385 let id: MatrixOperator<2, 0, 0> = MatrixOperator::identity();
386 let eigenvalues = compute_eigenvalues(&id, 100, 1e-10).unwrap();
387
388 for e in &eigenvalues {
390 assert!((e.value - 1.0).abs() < 1e-8);
391 }
392 }
393
394 #[test]
395 fn test_jacobi_eigenvalues_diagonal() {
396 let diag: MatrixOperator<2, 0, 0> =
397 MatrixOperator::diagonal(&[4.0, 3.0, 2.0, 1.0]).unwrap();
398 let eigenvalues = compute_eigenvalues(&diag, 100, 1e-10).unwrap();
399
400 assert!((eigenvalues[0].value - 4.0).abs() < 1e-8);
402 assert!((eigenvalues[1].value - 3.0).abs() < 1e-8);
403 assert!((eigenvalues[2].value - 2.0).abs() < 1e-8);
404 assert!((eigenvalues[3].value - 1.0).abs() < 1e-8);
405 }
406
407 #[test]
408 fn test_eigenpair_creation() {
409 let v = Multivector::<2, 0, 0>::from_slice(&[1.0, 0.0, 0.0, 0.0]);
410 let pair = Eigenpair::new(2.0, v);
411 assert!((pair.eigenvalue.value - 2.0).abs() < 1e-10);
412 }
413}