mdarray_linalg/testing/eig/
mod.rs1use num_complex::{Complex, ComplexFloat};
2
3use approx::assert_relative_eq;
4
5use super::common::{naive_matmul, random_matrix};
6use crate::eig::EigDecomp;
7use crate::eig::SchurDecomp;
8use crate::{assert_complex_matrix_eq, assert_matrix_eq};
9use crate::{prelude::*, pretty_print};
10use mdarray::DTensor;
11
12fn test_eigen_reconstruction<T>(
13 a: &DTensor<T, 2>,
14 eigenvalues: &DTensor<Complex<T::Real>, 2>,
15 right_eigenvectors: &DTensor<Complex<T::Real>, 2>,
16) where
17 T: Default + std::fmt::Debug + ComplexFloat<Real = f64>,
18{
19 let (n, _) = *a.shape();
20
21 let x = T::default();
22
23 for i in 0..n {
24 let λ = eigenvalues[[0, i]];
25 let v = right_eigenvectors.view(.., i).to_owned();
26
27 let mut av = DTensor::<_, 1>::from_elem([n], Complex::new(x.re(), x.re()));
28 let mut λv = DTensor::<_, 1>::from_elem([n], Complex::new(x.re(), x.re()));
29
30 let norm: f64 = v.iter().map(|z| z.norm_sqr()).sum::<f64>().sqrt();
31 assert!(norm > 1e-12, "Null vector found");
32
33 for row in 0..n {
34 let mut sum = Complex::new(x.re(), x.re());
35 for col in 0..n {
36 sum += Complex::new(a[[row, col]].re(), a[[row, col]].im()) * v[[col]];
37 }
38 av[[row]] = sum;
39 λv[[row]] = λ * v[[row]];
40 }
41 for row in 0..n {
42 let diff = av[[row]] - λv[[row]];
43 assert_relative_eq!(diff.re(), 0.0, epsilon = 1e-10);
44 assert_relative_eq!(diff.im(), 0.0, epsilon = 1e-10);
45 }
46 }
47}
48
49pub fn test_non_square_matrix(bd: &impl Eig<f64>) {
50 let n = 3;
51 let m = 5;
52 let a = random_matrix(m, n);
53
54 let EigDecomp { .. } = bd
55 .eig(&mut a.clone())
56 .expect("Eigenvalue decomposition failed");
57}
58
59pub fn test_square_matrix(bd: &impl Eig<f64>) {
60 let n = 2;
61 let a = random_matrix(n, n);
62
63 let EigDecomp {
64 eigenvalues,
65 right_eigenvectors,
66 ..
67 } = bd
68 .eig(&mut a.clone())
69 .expect("Eigenvalue decomposition failed");
70
71 test_eigen_reconstruction(&a, &eigenvalues, &right_eigenvectors.unwrap());
72}
73
74pub fn test_eig_cplx_square_matrix(bd: &impl Eig<Complex<f64>>) {
75 let n = 4;
76 let a = DTensor::<Complex<f64>, 2>::from_fn([n, n], |i| {
77 Complex::new((i[0] + i[1]) as f64, (i[0] * i[1]) as f64)
78 });
79 println!("{a:?}");
80 let EigDecomp {
81 eigenvalues,
82 right_eigenvectors,
83 ..
84 } = bd
85 .eig(&mut a.clone())
86 .expect("Eigenvalue decomposition failed");
87 println!("{eigenvalues:?}");
88 println!("{right_eigenvectors:?}");
89
90 test_eigen_reconstruction(&a, &eigenvalues, &right_eigenvectors.unwrap());
91}
92
93pub fn test_eigen_reconstruction_full<T>(
153 a: &DTensor<T, 2>,
154 eigenvalues: &DTensor<Complex<T::Real>, 2>,
155 left_eigenvectors: &DTensor<Complex<T::Real>, 2>,
156 right_eigenvectors: &DTensor<Complex<T::Real>, 2>,
157) where
158 T: Default + std::fmt::Debug + ComplexFloat<Real = f64>,
159{
160 let (n, _) = *a.shape();
161 let x = T::default();
162
163 for i in 0..n {
164 let λ = eigenvalues[[0, i]];
165 let vr = right_eigenvectors.view(.., i).to_owned();
166 let vl = left_eigenvectors.view(.., i).to_owned();
167
168 let norm_r: f64 = vr.iter().map(|z| z.norm_sqr()).sum::<f64>().sqrt();
169 let norm_l: f64 = vl.iter().map(|z| z.norm_sqr()).sum::<f64>().sqrt();
170
171 assert!(norm_r > 1e-12, "Null right eigenvector found");
172 assert!(norm_l > 1e-12, "Null left eigenvector found");
173
174 for row in 0..n {
176 let mut sum = Complex::new(x.re(), x.re());
177 for col in 0..n {
178 sum += Complex::new(a[[row, col]].re(), a[[row, col]].im()) * vr[[col]];
179 }
180 let diff = sum - λ * vr[[row]];
181 assert_relative_eq!(diff.re(), 0.0, epsilon = 1e-10);
182 assert_relative_eq!(diff.im(), 0.0, epsilon = 1e-10);
183 }
184
185 for col in 0..n {
187 let mut sum = Complex::new(x.re(), x.re());
188 for row in 0..n {
189 sum += vl[[row]].conj() * Complex::new(a[[row, col]].re(), a[[row, col]].im());
190 }
191 let diff = sum - λ * vl[[col]].conj();
192 assert_relative_eq!(diff.re(), 0.0, epsilon = 1e-10);
193 assert_relative_eq!(diff.im(), 0.0, epsilon = 1e-10);
194 }
195 }
196}
197
198pub fn test_eig_values_only(bd: &impl Eig<f64>) {
199 let n = 3;
200 let a = random_matrix(n, n);
201
202 let EigDecomp {
203 eigenvalues,
204 left_eigenvectors,
205 right_eigenvectors,
206 } = bd
207 .eig_values(&mut a.clone())
208 .expect("Eigenvalues computation failed");
209
210 assert_eq!(*eigenvalues.shape(), (1, n));
212
213 assert!(left_eigenvectors.is_none());
215 assert!(right_eigenvectors.is_none());
216}
217
218pub fn test_eigh_symmetric(bd: &impl Eig<f64>) {
270 let n = 3;
271 let mut a = random_matrix(n, n);
272
273 for i in 0..n {
275 for j in 0..n {
276 let val = (a[[i, j]] + a[[j, i]]) / 2.0;
277 a[[i, j]] = val;
278 a[[j, i]] = val;
279 }
280 }
281
282 let mut a_clone = a.clone();
283 println!("{a_clone:?}");
284
285 let EigDecomp {
286 eigenvalues,
287 right_eigenvectors,
288 ..
289 } = bd
290 .eigs(&mut a_clone)
291 .expect("Hermitian eigenvalue decomposition failed");
292
293 println!("{a_clone:?}");
294 println!("{right_eigenvectors:?}");
295 println!("{eigenvalues:?}");
296
297 for i in 0..n {
299 assert_relative_eq!(eigenvalues[[0, i]].im(), 0.0, epsilon = 1e-10);
300 }
301
302 test_eigen_reconstruction(&a, &eigenvalues, &right_eigenvectors.unwrap());
303}
304
305pub fn test_eigh_complex_hermitian(bd: &impl Eig<Complex<f64>>) {
306 let n = 3;
307 let mut a = DTensor::<Complex<f64>, 2>::from_fn([n, n], |i| {
308 Complex::new((i[0] + i[1]) as f64, (i[0] * i[1]) as f64)
309 });
310
311 for i in 0..n {
313 for j in 0..n {
314 if i == j {
315 a[[i, j]] = Complex::new(a[[i, j]].re(), 0.0); } else {
317 a[[j, i]] = a[[i, j]].conj();
318 }
319 }
320 }
321 a[[0, 0]] = Complex::new(1., 0.);
322
323 pretty_print(&a);
325
326 let EigDecomp {
327 eigenvalues,
328 right_eigenvectors,
329 ..
330 } = bd
331 .eigh(&mut a.clone())
332 .expect("Complex Hermitian eigenvalue decomposition failed");
333
334 pretty_print(&right_eigenvectors.clone().unwrap());
335 println!("{eigenvalues:?}");
336
337 for i in 0..n {
339 assert_relative_eq!(eigenvalues[[0, i]].im(), 0.0, epsilon = 1e-10);
340 }
341
342 test_eigen_reconstruction(&a, &eigenvalues, &right_eigenvectors.unwrap());
343}
344
345pub fn test_eig_full_non_square(bd: &impl Eig<f64>) {
346 let n = 3;
347 let m = 5;
348 let a = random_matrix(m, n);
349
350 let EigDecomp { .. } = bd
351 .eig_full(&mut a.clone())
352 .expect("Full eigenvalue decomposition failed");
353}
354
355pub fn test_eig_values_non_square(bd: &impl Eig<f64>) {
356 let n = 3;
357 let m = 5;
358 let a = random_matrix(m, n);
359
360 let EigDecomp { .. } = bd
361 .eig_values(&mut a.clone())
362 .expect("Eigenvalues computation failed");
363}
364
365pub fn test_schur(bd: &impl Eig<f64>) {
366 let n = 4;
367 let a = random_matrix(n, n);
368
369 let SchurDecomp { t, z } = bd
370 .schur(&mut a.clone())
371 .expect("Schur decomposition failed");
372
373 assert_eq!(t.shape(), &(n, n));
374 assert_eq!(z.shape(), &(n, n));
375
376 let zt = z.transpose().to_tensor();
377
378 println!("{a:?}");
379 println!("{t:?}");
380 println!("{z:?}");
381
382 let reconstructed_tmp = naive_matmul(&z, &t);
383 let a_reconstructed = naive_matmul(&reconstructed_tmp, &zt);
384
385 assert_matrix_eq!(&a, &a_reconstructed);
386}
387
388pub fn test_schur_cplx(bd: &impl Eig<Complex<f64>>) {
389 let n = 4;
390 let a = random_matrix(n, n);
391 let b = random_matrix(n, n);
392
393 let c = DTensor::<Complex<f64>, 2>::from_fn([n, n], |i| {
394 Complex::new(a[[i[0], i[1]]], b[[i[0], i[1]]])
395 });
396
397 let SchurDecomp { t, z } = bd
398 .schur_complex(&mut c.clone())
399 .expect("Schur decomposition failed");
400
401 assert_eq!(t.shape(), &(n, n));
402 assert_eq!(z.shape(), &(n, n));
403
404 let mut zt = z.transpose().to_tensor();
405
406 for i in 0..n {
407 for j in 0..n {
408 zt[[i, j]] = zt[[i, j]].conj();
409 }
410 }
411
412 println!("{c:?}");
413 println!("{t:?}");
414 println!("{z:?}");
415
416 let c_reconstructed_tmp = naive_matmul(&z, &t);
417 let c_reconstructed = naive_matmul(&c_reconstructed_tmp, &zt);
418
419 println!("---------------------------------------------");
420 println!("{c:?}");
421 println!("{c_reconstructed:?}");
422
423 assert_complex_matrix_eq!(&c, &c_reconstructed);
424}