russell_lab/matrix/
mat_eigen.rs

1use super::Matrix;
2use crate::{dgeev_data, dgeev_data_lr, to_i32, CcBool, StrError, Vector, C_FALSE, C_TRUE};
3
4extern "C" {
5    // Computes the eigenvalues and eigenvectors of a general matrix
6    // <https://www.netlib.org/lapack/explore-html/d9/d28/dgeev_8f.html>
7    fn c_dgeev(
8        calc_vl: CcBool,
9        calc_vr: CcBool,
10        n: *const i32,
11        a: *mut f64,
12        lda: *const i32,
13        wr: *mut f64,
14        wi: *mut f64,
15        vl: *mut f64,
16        ldvl: *const i32,
17        vr: *mut f64,
18        ldvr: *const i32,
19        work: *mut f64,
20        lwork: *const i32,
21        info: *mut i32,
22    );
23}
24
25/// (dgeev) Performs the eigen-decomposition of a square matrix
26///
27/// Computes the eigenvalues `l` and right eigenvectors `v`, such that:
28///
29/// ```text
30/// a ⋅ vj = lj ⋅ vj
31/// ```
32///
33/// where `lj` is the component j of `l` and `vj` is the column j of `v`.
34///
35/// See also: <https://www.netlib.org/lapack/explore-html/d9/d28/dgeev_8f.html>
36///
37/// # Output
38///
39/// * `l_real` -- (m) eigenvalues; real part
40/// * `l_imag` -- (m) eigenvalues; imaginary part
41/// * `v_real` -- (m,m) **right** eigenvectors (as columns); real part
42/// * `v_imag` -- (m,m) **right** eigenvectors (as columns); imaginary part
43///
44/// # Input
45///
46/// * `a` -- (m,m) general matrix (will be modified)
47///
48/// # Note
49///
50/// * The matrix `a` will be modified
51///
52/// # Similarity transformation
53///
54/// The eigen-decomposition leads to a similarity transformation like so:
55///
56/// ```text
57/// a = v⋅λ⋅v⁻¹
58/// ```
59///
60/// where `v` is a matrix whose columns are the m linearly independent eigenvectors of `a`,
61/// and `λ` is a matrix whose diagonal are the eigenvalues of `a`. Thus, the following is valid:
62///
63/// ```text
64/// a⋅v = v⋅λ
65/// ```
66///
67/// Let us define the error `err` as follows:
68///
69/// ```text
70/// err := a⋅v - v⋅λ
71/// ```
72///
73/// # Examples
74///
75/// ```
76/// use russell_lab::*;
77///
78/// fn main() -> Result<(), StrError> {
79///     // set matrix
80///     let data = [[2.0, 0.0, 0.0], [0.0, 3.0, 4.0], [0.0, 4.0, 9.0]];
81///     let mut a = Matrix::from(&data);
82///
83///     // allocate output arrays
84///     let m = a.nrow();
85///     let mut l_real = Vector::new(m);
86///     let mut l_imag = Vector::new(m);
87///     let mut v_real = Matrix::new(m, m);
88///     let mut v_imag = Matrix::new(m, m);
89///
90///     // perform the eigen-decomposition
91///     mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut a)?;
92///
93///     // check results
94///     assert_eq!(
95///         format!("{:.1}", l_real),
96///         "┌      ┐\n\
97///          │ 11.0 │\n\
98///          │  1.0 │\n\
99///          │  2.0 │\n\
100///          └      ┘"
101///     );
102///     assert_eq!(
103///         format!("{}", l_imag),
104///         "┌   ┐\n\
105///          │ 0 │\n\
106///          │ 0 │\n\
107///          │ 0 │\n\
108///          └   ┘"
109///     );
110///
111///     // check eigen-decomposition (similarity transformation) of a
112///     // symmetric matrix with real-only eigenvalues and eigenvectors
113///     let a_copy = Matrix::from(&data);
114///     let lam = Matrix::diagonal(l_real.as_data());
115///     let mut a_v = Matrix::new(m, m);
116///     let mut v_l = Matrix::new(m, m);
117///     let mut err = Matrix::filled(m, m, f64::MAX);
118///     mat_mat_mul(&mut a_v, 1.0, &a_copy, &v_real, 0.0)?;
119///     mat_mat_mul(&mut v_l, 1.0, &v_real, &lam, 0.0)?;
120///     mat_add(&mut err, 1.0, &a_v, -1.0, &v_l)?;
121///     approx_eq(mat_norm(&err, Norm::Max), 0.0, 1e-15);
122///     Ok(())
123/// }
124/// ```
125pub fn mat_eigen(
126    l_real: &mut Vector,
127    l_imag: &mut Vector,
128    v_real: &mut Matrix,
129    v_imag: &mut Matrix,
130    a: &mut Matrix,
131) -> Result<(), StrError> {
132    let (m, n) = a.dims();
133    if m != n {
134        return Err("matrix must be square");
135    }
136    if l_real.dim() != m || l_imag.dim() != m {
137        return Err("vectors are incompatible");
138    }
139    if v_real.nrow() != m || v_real.ncol() != m || v_imag.nrow() != m || v_imag.ncol() != m {
140        return Err("matrices are incompatible");
141    }
142    let m_i32 = to_i32(m);
143    let lda = m_i32;
144    let ldu = 1;
145    let ldv = m_i32;
146    const EXTRA: i32 = 1;
147    let lwork = 4 * m_i32 + EXTRA;
148    let mut u = vec![0.0; ldu as usize];
149    let mut v = vec![0.0; m * m];
150    let mut work = vec![0.0; lwork as usize];
151    let mut info = 0;
152    unsafe {
153        c_dgeev(
154            C_FALSE,
155            C_TRUE,
156            &m_i32,
157            a.as_mut_data().as_mut_ptr(),
158            &lda,
159            l_real.as_mut_data().as_mut_ptr(),
160            l_imag.as_mut_data().as_mut_ptr(),
161            u.as_mut_ptr(),
162            &ldu,
163            v.as_mut_ptr(),
164            &ldv,
165            work.as_mut_ptr(),
166            &lwork,
167            &mut info,
168        );
169    }
170    if info < 0 {
171        println!("LAPACK ERROR (dgeev): Argument #{} had an illegal value", -info);
172        return Err("LAPACK ERROR (dgeev): An argument had an illegal value");
173    } else if info > 0 {
174        println!("LAPACK ERROR (dgeev): The QR algorithm failed. Elements {}+1:N of l_real and l_imag contain eigenvalues which have converged", info-1);
175        return Err("LAPACK ERROR (dgeev): The QR algorithm failed to compute all the eigenvalues, and no eigenvectors have been computed");
176    }
177    dgeev_data(v_real.as_mut_data(), v_imag.as_mut_data(), l_imag.as_data(), &v)
178}
179
180/// Performs the eigen-decomposition of a square matrix (left and right)
181///
182/// Computes the eigenvalues `l` and left eigenvectors `u`, such that:
183///
184/// ```text
185/// ujᴴ ⋅ a = lj ⋅ ujᴴ
186/// ```
187///
188/// where `lj` is the component j of `l` and `ujᴴ` is the column j of `uᴴ`,
189/// with `uᴴ` being the conjugate-transpose of `u`.
190///
191/// Also, computes the right eigenvectors `v`, such that:
192///
193/// ```text
194/// a ⋅ vj = lj ⋅ vj
195/// ```
196///
197/// where `vj` is the column j of `v`.
198///
199/// See also: <https://www.netlib.org/lapack/explore-html/d9/d28/dgeev_8f.html>
200///
201/// # Output
202///
203/// * `l_real` -- (m) eigenvalues; real part
204/// * `l_imag` -- (m) eigenvalues; imaginary part
205/// * `u_real` -- (m,m) **left** eigenvectors (as columns); real part
206/// * `u_imag` -- (m,m) **left** eigenvectors (as columns); imaginary part
207/// * `v_real` -- (m,m) **right** eigenvectors (as columns); real part
208/// * `v_imag` -- (m,m) **right** eigenvectors (as columns); imaginary part
209///
210/// # Input
211///
212/// * `a` -- (m,m) general matrix (will be modified)
213///
214/// # Note
215///
216/// * The matrix `a` will be modified
217///
218/// # Examples
219///
220/// ```
221/// use russell_lab::*;
222///
223/// fn main() -> Result<(), StrError> {
224///     // set matrix
225///     let data = [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]];
226///     let mut a = Matrix::from(&data);
227///
228///     // allocate output arrays
229///     let m = a.nrow();
230///     let mut l_real = Vector::new(m);
231///     let mut l_imag = Vector::new(m);
232///     let mut u_real = Matrix::new(m, m);
233///     let mut u_imag = Matrix::new(m, m);
234///     let mut v_real = Matrix::new(m, m);
235///     let mut v_imag = Matrix::new(m, m);
236///
237///     // perform the eigen-decomposition
238///     mat_eigen_lr(
239///         &mut l_real,
240///         &mut l_imag,
241///         &mut u_real,
242///         &mut u_imag,
243///         &mut v_real,
244///         &mut v_imag,
245///         &mut a,
246///     )?;
247///
248///     // check results
249///     assert_eq!(
250///         format!("{:.3}", l_real),
251///         "┌        ┐\n\
252///          │ -0.500 │\n\
253///          │ -0.500 │\n\
254///          │  1.000 │\n\
255///          └        ┘"
256///     );
257///     assert_eq!(
258///         format!("{:.3}", l_imag),
259///         "┌        ┐\n\
260///          │  0.866 │\n\
261///          │ -0.866 │\n\
262///          │  0.000 │\n\
263///          └        ┘"
264///     );
265///
266///     // check the eigen-decomposition (similarity transformation)
267///     // ```text
268///     // a⋅v = v⋅λ
269///     // err := a⋅v - v⋅λ
270///     // ```
271///     let a = ComplexMatrix::from(&data);
272///     let mut v = ComplexMatrix::new(m, m);
273///     let mut d = ComplexVector::new(m);
274///     complex_mat_zip(&mut v, &v_real, &v_imag)?;
275///     complex_vec_zip(&mut d, &l_real, &l_imag)?;
276///     let lam = ComplexMatrix::diagonal(d.as_data());
277///     let mut a_v = ComplexMatrix::new(m, m);
278///     let mut v_l = ComplexMatrix::new(m, m);
279///     let mut err = ComplexMatrix::filled(m, m, cpx!(f64::MAX, f64::MAX));
280///     let one = cpx!(1.0, 0.0);
281///     let m_one = cpx!(-1.0, 0.0);
282///     let zero = cpx!(0.0, 0.0);
283///     complex_mat_mat_mul(&mut a_v, one, &a, &v, zero)?;
284///     complex_mat_mat_mul(&mut v_l, one, &v, &lam, zero)?;
285///     complex_mat_add(&mut err, one, &a_v, m_one, &v_l)?;
286///     approx_eq(complex_mat_norm(&err, Norm::Max), 0.0, 1e-15);
287///     Ok(())
288/// }
289/// ```
290pub fn mat_eigen_lr(
291    l_real: &mut Vector,
292    l_imag: &mut Vector,
293    u_real: &mut Matrix,
294    u_imag: &mut Matrix,
295    v_real: &mut Matrix,
296    v_imag: &mut Matrix,
297    a: &mut Matrix,
298) -> Result<(), StrError> {
299    let (m, n) = a.dims();
300    if m != n {
301        return Err("matrix must be square");
302    }
303    if l_real.dim() != m || l_imag.dim() != m {
304        return Err("vectors are incompatible");
305    }
306    if u_real.nrow() != m
307        || u_real.ncol() != m
308        || u_imag.nrow() != m
309        || u_imag.ncol() != m
310        || v_real.nrow() != m
311        || v_real.ncol() != m
312        || v_imag.nrow() != m
313        || v_imag.ncol() != m
314    {
315        return Err("matrices are incompatible");
316    }
317    let m_i32 = to_i32(m);
318    let lda = m_i32;
319    let ldu = m_i32;
320    let ldv = m_i32;
321    const EXTRA: i32 = 1;
322    let lwork = 4 * m_i32 + EXTRA;
323    let mut u = vec![0.0; m * m];
324    let mut v = vec![0.0; m * m];
325    let mut work = vec![0.0; lwork as usize];
326    let mut info = 0;
327    unsafe {
328        c_dgeev(
329            C_TRUE,
330            C_TRUE,
331            &m_i32,
332            a.as_mut_data().as_mut_ptr(),
333            &lda,
334            l_real.as_mut_data().as_mut_ptr(),
335            l_imag.as_mut_data().as_mut_ptr(),
336            u.as_mut_ptr(),
337            &ldu,
338            v.as_mut_ptr(),
339            &ldv,
340            work.as_mut_ptr(),
341            &lwork,
342            &mut info,
343        );
344    }
345    if info < 0 {
346        println!("LAPACK ERROR (dgeev): Argument #{} had an illegal value", -info);
347        return Err("LAPACK ERROR (dgeev): An argument had an illegal value");
348    } else if info > 0 {
349        println!("LAPACK ERROR (dgeev): The QR algorithm failed. Elements {}+1:N of l_real and l_imag contain eigenvalues which have converged", info-1);
350        return Err("LAPACK ERROR (dgeev): The QR algorithm failed to compute all the eigenvalues, and no eigenvectors have been computed");
351    }
352    dgeev_data_lr(
353        u_real.as_mut_data(),
354        u_imag.as_mut_data(),
355        v_real.as_mut_data(),
356        v_imag.as_mut_data(),
357        l_imag.as_data(),
358        &u,
359        &v,
360    )?;
361    Ok(())
362}
363
364////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
365
366#[cfg(test)]
367mod tests {
368    use super::{mat_eigen, mat_eigen_lr};
369    use crate::mat_approx_eq;
370    use crate::matrix::testing::{check_eigen, check_eigen_sym};
371    use crate::{vec_approx_eq, Matrix, Vector};
372
373    #[test]
374    fn mat_eigen_fails_on_non_square() {
375        let mut a = Matrix::new(3, 4);
376        let m = a.nrow();
377        let mut l_real = Vector::new(m);
378        let mut l_imag = Vector::new(m);
379        let mut v_real = Matrix::new(m, m);
380        let mut v_imag = Matrix::new(m, m);
381        assert_eq!(
382            mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut a),
383            Err("matrix must be square")
384        );
385    }
386
387    #[test]
388    fn mat_eigen_fails_on_wrong_dims() {
389        let mut a = Matrix::new(2, 2);
390        let m = a.nrow();
391        let mut l_real = Vector::new(m);
392        let mut l_imag = Vector::new(m);
393        let mut v_real = Matrix::new(m, m);
394        let mut v_imag = Matrix::new(m, m);
395        let mut l_real_wrong = Vector::new(m + 1);
396        let mut l_imag_wrong = Vector::new(m + 1);
397        let mut v_real_wrong = Matrix::new(m + 1, m);
398        let mut v_imag_wrong = Matrix::new(m, m + 1);
399        assert_eq!(
400            mat_eigen(&mut l_real_wrong, &mut l_imag, &mut v_real, &mut v_imag, &mut a),
401            Err("vectors are incompatible")
402        );
403        assert_eq!(
404            mat_eigen(&mut l_real, &mut l_imag_wrong, &mut v_real, &mut v_imag, &mut a),
405            Err("vectors are incompatible")
406        );
407        assert_eq!(
408            mat_eigen(&mut l_real, &mut l_imag, &mut v_real_wrong, &mut v_imag, &mut a),
409            Err("matrices are incompatible")
410        );
411        assert_eq!(
412            mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag_wrong, &mut a),
413            Err("matrices are incompatible")
414        );
415    }
416
417    #[test]
418    fn mat_eigen_lr_fails_on_non_square() {
419        let mut a = Matrix::new(3, 4);
420        let m = a.nrow();
421        let mut l_real = Vector::new(m);
422        let mut l_imag = Vector::new(m);
423        let mut u_real = Matrix::new(m, m);
424        let mut u_imag = Matrix::new(m, m);
425        let mut v_real = Matrix::new(m, m);
426        let mut v_imag = Matrix::new(m, m);
427        assert_eq!(
428            mat_eigen_lr(
429                &mut l_real,
430                &mut l_imag,
431                &mut u_real,
432                &mut u_imag,
433                &mut v_real,
434                &mut v_imag,
435                &mut a,
436            ),
437            Err("matrix must be square"),
438        );
439    }
440
441    #[test]
442    fn mat_eigen_lr_fails_on_wrong_dims() {
443        let mut a = Matrix::new(2, 2);
444        let m = a.nrow();
445        let mut l_real = Vector::new(m);
446        let mut l_imag = Vector::new(m);
447        let mut u_real = Matrix::new(m, m);
448        let mut u_imag = Matrix::new(m, m);
449        let mut v_real = Matrix::new(m, m);
450        let mut v_imag = Matrix::new(m, m);
451        let mut l_real_wrong = Vector::new(m + 1);
452        let mut l_imag_wrong = Vector::new(m + 1);
453        let mut u_real_wrong = Matrix::new(m + 1, m);
454        let mut u_imag_wrong = Matrix::new(m, m + 1);
455        let mut v_real_wrong = Matrix::new(m + 1, m);
456        let mut v_imag_wrong = Matrix::new(m, m + 1);
457        assert_eq!(
458            mat_eigen_lr(
459                &mut l_real_wrong,
460                &mut l_imag,
461                &mut u_real,
462                &mut u_imag,
463                &mut v_real,
464                &mut v_imag,
465                &mut a,
466            ),
467            Err("vectors are incompatible"),
468        );
469        assert_eq!(
470            mat_eigen_lr(
471                &mut l_real,
472                &mut l_imag_wrong,
473                &mut u_real,
474                &mut u_imag,
475                &mut v_real,
476                &mut v_imag,
477                &mut a,
478            ),
479            Err("vectors are incompatible"),
480        );
481        assert_eq!(
482            mat_eigen_lr(
483                &mut l_real,
484                &mut l_imag,
485                &mut u_real_wrong,
486                &mut u_imag,
487                &mut v_real,
488                &mut v_imag,
489                &mut a,
490            ),
491            Err("matrices are incompatible"),
492        );
493        assert_eq!(
494            mat_eigen_lr(
495                &mut l_real,
496                &mut l_imag,
497                &mut u_real,
498                &mut u_imag_wrong,
499                &mut v_real,
500                &mut v_imag,
501                &mut a,
502            ),
503            Err("matrices are incompatible"),
504        );
505        assert_eq!(
506            mat_eigen_lr(
507                &mut l_real,
508                &mut l_imag,
509                &mut u_real,
510                &mut u_imag,
511                &mut v_real_wrong,
512                &mut v_imag,
513                &mut a,
514            ),
515            Err("matrices are incompatible"),
516        );
517        assert_eq!(
518            mat_eigen_lr(
519                &mut l_real,
520                &mut l_imag,
521                &mut u_real,
522                &mut u_imag,
523                &mut v_real,
524                &mut v_imag_wrong,
525                &mut a,
526            ),
527            Err("matrices are incompatible"),
528        );
529    }
530
531    #[test]
532    fn mat_eigen_works() {
533        #[rustfmt::skip]
534        let data = [
535            [0.0, 1.0, 0.0],
536            [0.0, 0.0, 1.0],
537            [1.0, 0.0, 0.0],
538        ];
539        let mut a = Matrix::from(&data);
540        let m = a.nrow();
541        let mut l_real = Vector::new(m);
542        let mut l_imag = Vector::new(m);
543        let mut v_real = Matrix::new(m, m);
544        let mut v_imag = Matrix::new(m, m);
545        mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut a).unwrap();
546        let s3 = f64::sqrt(3.0);
547        let l_real_correct = &[-0.5, -0.5, 1.0];
548        let l_imag_correct = &[s3 / 2.0, -s3 / 2.0, 0.0];
549        vec_approx_eq(&l_real, l_real_correct, 1e-15);
550        vec_approx_eq(&l_imag, l_imag_correct, 1e-15);
551        check_eigen(&data, &v_real, &l_real, &v_imag, &l_imag, 1e-15);
552    }
553
554    #[test]
555    fn mat_eigen_repeated_eval_works() {
556        // rep: repeated eigenvalues
557        #[rustfmt::skip]
558        let data = [
559            [2.0, 0.0, 0.0, 0.0],
560            [1.0, 2.0, 0.0, 0.0],
561            [0.0, 1.0, 3.0, 0.0],
562            [0.0, 0.0, 1.0, 3.0],
563        ];
564        let mut a = Matrix::from(&data);
565        let m = a.nrow();
566        let mut l_real = Vector::new(m);
567        let mut l_imag = Vector::new(m);
568        let mut v_real = Matrix::new(m, m);
569        let mut v_imag = Matrix::new(m, m);
570        mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut a).unwrap();
571        let l_real_correct = &[3.0, 3.0, 2.0, 2.0];
572        let l_imag_correct = &[0.0, 0.0, 0.0, 0.0];
573        let os3 = 1.0 / f64::sqrt(3.0);
574        #[rustfmt::skip]
575        let v_real_correct = &[
576            [0.0,  0.0,  0.0,  0.0],
577            [0.0,  0.0,  os3, -os3],
578            [0.0,  0.0, -os3,  os3],
579            [1.0, -1.0,  os3, -os3],
580        ];
581        let v_imag_correct = Matrix::new(4, 4);
582        vec_approx_eq(&l_real, l_real_correct, 1e-15);
583        vec_approx_eq(&l_imag, l_imag_correct, 1e-15);
584        mat_approx_eq(&v_real, v_real_correct, 1e-15);
585        mat_approx_eq(&v_imag, &v_imag_correct, 1e-15);
586        check_eigen_sym(&data, &v_real, &l_real, 1e-15);
587    }
588
589    #[test]
590    fn mat_eigen_lr_works() {
591        #[rustfmt::skip]
592        let data = [
593            [0.0, 1.0, 0.0],
594            [0.0, 0.0, 1.0],
595            [1.0, 0.0, 0.0],
596        ];
597        let mut a = Matrix::from(&data);
598        let m = a.nrow();
599        let mut l_real = Vector::new(m);
600        let mut l_imag = Vector::new(m);
601        let mut u_real = Matrix::new(m, m);
602        let mut u_imag = Matrix::new(m, m);
603        let mut v_real = Matrix::new(m, m);
604        let mut v_imag = Matrix::new(m, m);
605        mat_eigen_lr(
606            &mut l_real,
607            &mut l_imag,
608            &mut u_real,
609            &mut u_imag,
610            &mut v_real,
611            &mut v_imag,
612            &mut a,
613        )
614        .unwrap();
615        let s3 = f64::sqrt(3.0);
616        let l_real_correct = &[-0.5, -0.5, 1.0];
617        let l_imag_correct = &[s3 / 2.0, -s3 / 2.0, 0.0];
618        vec_approx_eq(&l_real, l_real_correct, 1e-15);
619        vec_approx_eq(&l_imag, l_imag_correct, 1e-15);
620        check_eigen(&data, &v_real, &l_real, &v_imag, &l_imag, 1e-15);
621    }
622}