1use crate::{
3 Matrix, MatrixView, SolverError, Tolerance
4};
5use crate::numerics::{
6 checked, norm_iter, symmetric, zeros
7};
8#[derive(Clone, Copy, Debug)]
9pub struct EigenOptions {
10 pub tolerance: Tolerance,
11 pub symmetry_tolerance: Tolerance,
12 pub max_sweeps: usize,
13}
14impl Default for EigenOptions {
15 fn default()->Self {
16 Self{
17 tolerance: Tolerance{
18 absolute: 0.0, relative: 1e-12
19 },
20 symmetry_tolerance: Tolerance::default(), max_sweeps: 64
21 }
22 }
23}
24#[derive(Clone, Debug)]
25pub struct SymmetricEigen {
26 pub values: Vec<f64>, pub vectors: Matrix, pub sweeps: usize,
28 pub off_diagonal_norm: f64,
30}
31pub fn symmetric_eigen(a: MatrixView<'_>, options: EigenOptions)->Result<SymmetricEigen, SolverError> {
35 let n=a.square()?;
36 symmetric(a, options.symmetry_tolerance)?;
37 options.tolerance.validate()?;
38 if options.max_sweeps==0 || (options.tolerance.absolute==0.0 && options.tolerance.relative==0.0) {
39 return Err(SolverError::InvalidOption("eigen tolerance and sweep budget must be positive"));
40 }
41 let scale=a.max_abs();
42 let mut v=Matrix::identity(n)?;
43 if scale==0.0 {
44 return Ok(SymmetricEigen{
45 values: zeros(n)?, vectors: v, sweeps: 0, off_diagonal_norm: 0.0
46 });
47 }
48 let mut w=a.to_owned()?;
49 for x in &mut w.data {
50 *x/=scale;
51 }
52 for i in 0..n {
54 for j in 0..i {
55 w.data[j*n+i]=w.data[i*n+j];
56 }
57 }
58 let norm=norm_iter(w.data.iter().copied())?;
59 let cutoff=(options.tolerance.absolute/scale).max(options.tolerance.relative*norm);
60 for sweep in 0..=options.max_sweeps {
61 let off=norm_iter((0..n).flat_map(|i|(0..i).map(move|j|(i, j)))
62 .map(|(i, j)|w.data[i*n+j]))?*2.0f64.sqrt();
63 if off<=cutoff {
64 let mut order: Vec<usize>=(0..n).collect();
65 order.sort_by(|&i, &j|w.data[i*n+i].total_cmp(&w.data[j*n+j]));
66 let mut values=zeros(n)?;
67 let mut vectors=Matrix::zeros(n, n)?;
68 for (column, &old) in order.iter().enumerate() {
69 values[column]=checked(w.data[old*n+old]*scale, "eigenvalue rescale")?;
70 for i in 0..n {
71 vectors.data[i*n+column]=v.data[i*n+old];
72 }
73 }
74 return Ok(SymmetricEigen{
75 values, vectors, sweeps: sweep,
76 off_diagonal_norm: checked(off*scale, "eigen residual rescale")?
77 });
78 }
79 if sweep==options.max_sweeps {
80 return Err(SolverError::NonConvergence{
81 iterations: sweep, residual: checked(off*scale, "eigen residual rescale")?
82 });
83 }
84 for p in 0..n {
85 for q in p+1..n {
86 let apq=w.data[p*n+q];
87 if apq==0.0 {
88 continue;
89 }
90 let delta=(w.data[q*n+q]-w.data[p*n+p])*0.5;
91 let t=if delta==0.0 {
92 1.0
93 } else {
94 apq/(delta+delta.hypot(apq).copysign(delta))
95 };
96 let c=1.0/(1.0+t*t).sqrt();
97 let s=t*c;
98 let app=w.data[p*n+p];
99 let aqq=w.data[q*n+q];
100 w.data[p*n+p]=checked(app-t*apq, "Jacobi diagonal")?;
101 w.data[q*n+q]=checked(aqq+t*apq, "Jacobi diagonal")?;
102 w.data[p*n+q]=0.0;
103 w.data[q*n+p]=0.0;
104 for k in 0..n {
105 if k!=p && k!=q {
106 let x=w.data[k*n+p];
107 let y=w.data[k*n+q];
108 let xp=checked(c*x-s*y, "Jacobi rotation")?;
109 let yq=checked(s*x+c*y, "Jacobi rotation")?;
110 w.data[k*n+p]=xp;
111 w.data[p*n+k]=xp;
112 w.data[k*n+q]=yq;
113 w.data[q*n+k]=yq;
114 }
115 }
116 for k in 0..n {
117 let x=v.data[k*n+p];
118 let y=v.data[k*n+q];
119 v.data[k*n+p]=c*x-s*y;
120 v.data[k*n+q]=s*x+c*y;
121 }
122 }
123 }
124 }
125 Err(SolverError::Arithmetic("unreachable eigen iteration state"))
126}