faer/linalg/evd/
mod.rs

1//! low level implementation of the eigenvalue decomposition of a square diagonalizable matrix.
2//!
3//! the eigenvalue decomposition of a square matrix $A$ of shape $(n, n)$ is a decomposition into
4//! two components $U$, $S$:
5//!
6//! - $U$ has shape $(n, n)$ and is invertible
7//! - $S$ has shape $(n, n)$ and is a diagonal matrix
8//! - and finally:
9//!
10//! $$A = U S U^{-1}$$
11//!
12//! if $A$ is self-adjoint, then $U$ can be made unitary ($U^{-1} = U^H$), and $S$ is real valued
13
14/// hessenberg decomposition
15pub mod hessenberg;
16#[doc(hidden)]
17pub mod schur;
18
19/// self-adjoint tridiagonalization
20pub mod tridiag;
21pub(crate) mod tridiag_evd;
22
23use crate::assert;
24use crate::internal_prelude::*;
25use hessenberg::HessenbergParams;
26use linalg::matmul::triangular::BlockStructure;
27use schur::SchurParams;
28use tridiag::TridiagParams;
29
30/// eigendecomposition error
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
32pub enum EvdError {
33	/// reached max iterations
34	NoConvergence,
35}
36
37/// schur to eigendecomposition conversion parameters
38#[derive(Clone, Copy, Debug)]
39pub struct EvdFromSchurParams {
40	/// threshold at which the implementation should stop recursing
41	pub recursion_threshold: usize,
42	#[doc(hidden)]
43	pub non_exhaustive: NonExhaustive,
44}
45
46/// eigendecomposition tuning parameters
47#[derive(Clone, Copy, Debug)]
48pub struct EvdParams {
49	/// hessenberg parameters
50	pub hessenberg: HessenbergParams,
51	/// schur from hessenberg conversion parameters
52	pub schur: SchurParams,
53	/// eigendecomposition from schur conversion parameters
54	pub evd_from_schur: EvdFromSchurParams,
55
56	#[doc(hidden)]
57	pub non_exhaustive: NonExhaustive,
58}
59
60/// self-adjoint eigendecomposition tuning parameters
61#[derive(Clone, Copy, Debug)]
62pub struct SelfAdjointEvdParams {
63	/// tridiagonalization parameters
64	pub tridiag: TridiagParams,
65	/// threshold at which the implementation should stop recursing
66	pub recursion_threshold: usize,
67
68	#[doc(hidden)]
69	pub non_exhaustive: NonExhaustive,
70}
71
72impl<T: ComplexField> Auto<T> for EvdParams {
73	fn auto() -> Self {
74		Self {
75			hessenberg: auto!(T),
76			schur: auto!(T),
77			evd_from_schur: auto!(T),
78			non_exhaustive: NonExhaustive(()),
79		}
80	}
81}
82impl<T: ComplexField> Auto<T> for EvdFromSchurParams {
83	fn auto() -> Self {
84		Self {
85			recursion_threshold: 64,
86			non_exhaustive: NonExhaustive(()),
87		}
88	}
89}
90
91impl<T: ComplexField> Auto<T> for SelfAdjointEvdParams {
92	fn auto() -> Self {
93		Self {
94			tridiag: auto!(T),
95			recursion_threshold: 128,
96			non_exhaustive: NonExhaustive(()),
97		}
98	}
99}
100
101/// whether the eigenvectors should be computed
102#[derive(Copy, Clone, Debug, PartialEq, Eq)]
103pub enum ComputeEigenvectors {
104	/// do not compute eigenvectors
105	No,
106	/// compute eigenvectors
107	Yes,
108}
109
110/// computes the size and alignment of the workspace required to compute a self-adjoint matrix's
111/// eigendecomposition
112#[math]
113pub fn self_adjoint_evd_scratch<T: ComplexField>(
114	dim: usize,
115	compute_u: ComputeEigenvectors,
116	par: Par,
117	params: Spec<SelfAdjointEvdParams, T>,
118) -> StackReq {
119	let n = dim;
120	let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n, n);
121
122	let prologue = StackReq::all_of(&[
123		temp_mat_scratch::<T>(n, n),
124		temp_mat_scratch::<T>(bs, n),
125		StackReq::any_of(&[tridiag::tridiag_in_place_scratch::<T>(n, par, params.tridiag.into())]),
126		temp_mat_scratch::<T::Real>(n, 1).array(2),
127	]);
128	if compute_u == ComputeEigenvectors::No {
129		return prologue;
130	}
131
132	StackReq::all_of(&[
133		prologue,
134		temp_mat_scratch::<T::Real>(n, if try_const! { T::IS_REAL } { 0 } else { n }).array(2),
135		StackReq::any_of(&[
136			if n < params.recursion_threshold {
137				StackReq::empty()
138			} else {
139				tridiag_evd::divide_and_conquer_scratch::<T>(n, par)
140			},
141			temp_mat_scratch::<T>(n, 1),
142			linalg::householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(n - 1, bs, n),
143		]),
144	])
145}
146
147/// computes the matrix $A$'s eigendecomposition, assuming it is self-adjoint
148///
149/// the eigenvalues are stored in $S$, and the eigenvectors in $U$ such that the eigenvalues are
150/// sorted in nondecreasing order
151///
152/// only the lower triangular half of $A$ is accessed
153#[math]
154pub fn self_adjoint_evd<T: ComplexField>(
155	A: MatRef<'_, T>,
156	s: DiagMut<'_, T>,
157	u: Option<MatMut<'_, T>>,
158	par: Par,
159	stack: &mut MemStack,
160	params: Spec<SelfAdjointEvdParams, T>,
161) -> Result<(), EvdError> {
162	let n = A.nrows();
163	assert!(all(A.nrows() == A.ncols(), s.dim() == n));
164	if let Some(u) = u.rb() {
165		assert!(all(u.nrows() == n, u.ncols() == n));
166	}
167	let s = s.column_vector_mut();
168
169	if n == 0 {
170		return Ok(());
171	}
172
173	#[cfg(feature = "perf-warn")]
174	if let Some(matrix) = u.rb() {
175		if matrix.row_stride().unsigned_abs() != 1 && crate::__perf_warn!(QR_WARN) {
176			if matrix.col_stride().unsigned_abs() == 1 {
177				log::warn!(target: "faer_perf", "EVD prefers column-major eigenvector matrix. Found row-major matrix.");
178			} else {
179				log::warn!(target: "faer_perf", "EVD prefers column-major eigenvector matrix. Found matrix with generic strides.");
180			}
181		}
182	}
183
184	let (mut trid, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
185	let mut trid = trid.as_mat_mut();
186
187	trid.copy_from_triangular_lower(A);
188
189	let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n, n);
190	let (mut householder, stack) = unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
191	let mut householder = householder.as_mat_mut();
192
193	{
194		tridiag::tridiag_in_place(trid.rb_mut(), householder.rb_mut(), par, stack, params.tridiag.into());
195	}
196
197	let trid = trid.rb();
198
199	let (mut diag, stack) = unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
200	let (mut offdiag, stack) = unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
201
202	let mut diag = diag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
203	let mut offdiag = offdiag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
204
205	for i in 0..n {
206		diag[i] = real(trid[(i, i)]);
207
208		if i + 1 < n {
209			if try_const! { T::IS_REAL } {
210				offdiag[i] = real(trid[(i + 1, i)]);
211			} else {
212				offdiag[i] = abs(trid[(i + 1, i)]);
213			}
214		} else {
215			offdiag[i] = zero();
216		}
217	}
218
219	let mut s = s;
220	let mut u = match u {
221		Some(u) => u,
222		None => {
223			tridiag_evd::qr_algorithm(diag.rb_mut(), offdiag.rb_mut(), None)?;
224			for i in 0..n {
225				s[i] = from_real(diag[i]);
226			}
227
228			return Ok(());
229		},
230	};
231
232	let (mut u_real, stack) = unsafe { temp_mat_uninit::<T::Real, _, _>(n, if T::IS_REAL { 0 } else { n }, stack) };
233	let mut u_real = u_real.as_mat_mut();
234	let mut u_evd = if try_const! { T::IS_REAL } {
235		unsafe { core::mem::transmute(u.rb_mut()) }
236	} else {
237		u_real.rb_mut()
238	};
239
240	if n < params.recursion_threshold {
241		tridiag_evd::qr_algorithm(diag.rb_mut(), offdiag.rb_mut(), Some(u_evd.rb_mut()))?;
242	} else {
243		tridiag_evd::divide_and_conquer::<T::Real>(diag.rb_mut(), offdiag.rb_mut(), u_evd.rb_mut(), par, stack, params.recursion_threshold)?;
244	}
245
246	if try_const! { !T::IS_REAL } {
247		let normalized = |x: T| {
248			if x == zero() { one() } else { mul_real(x, recip(abs(x))) }
249		};
250
251		let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
252		let mut scale = scale.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
253
254		let mut x = one::<T>();
255		scale[0] = one();
256
257		for i in 1..n {
258			x = normalized(trid[(i, i - 1)] * x);
259			scale[i] = copy(x);
260		}
261		for j in 0..n {
262			z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(|uz!(u, real, scale)| {
263				*u = mul_real(*scale, *real);
264			});
265		}
266	}
267
268	linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
269		trid.submatrix(1, 0, n - 1, n - 1),
270		householder.rb(),
271		Conj::No,
272		u.rb_mut().subrows_mut(1, n - 1),
273		par,
274		stack,
275	);
276
277	for i in 0..n {
278		s[i] = from_real(diag[i]);
279	}
280
281	Ok(())
282}
283
284/// computes the size and alignment of the workspace required to compute a self-adjoint matrix's
285/// pseudoinverse, given the eigendecomposition
286pub fn pseudoinverse_from_self_adjoint_evd_scratch<T: ComplexField>(dim: usize, par: Par) -> StackReq {
287	_ = par;
288	temp_mat_scratch::<T>(dim, dim).array(2)
289}
290
291/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition factors $S$ and $U$
292#[math]
293#[track_caller]
294pub fn pseudoinverse_from_self_adjoint_evd<T: ComplexField>(pinv: MatMut<'_, T>, s: ColRef<'_, T>, u: MatRef<'_, T>, par: Par, stack: &mut MemStack) {
295	pseudoinverse_from_self_adjoint_evd_with_tolerance(pinv, s, u, zero(), eps::<T::Real>() * from_f64::<T::Real>(u.ncols() as f64), par, stack);
296}
297
298/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition factors $S$ and
299/// $U$, and tolerance parameters for determining zero eigenvalues
300#[math]
301#[track_caller]
302pub fn pseudoinverse_from_self_adjoint_evd_with_tolerance<T: ComplexField>(
303	pinv: MatMut<'_, T>,
304	s: ColRef<'_, T>,
305	u: MatRef<'_, T>,
306	abs_tol: T::Real,
307	rel_tol: T::Real,
308	par: Par,
309	stack: &mut MemStack,
310) {
311	let mut pinv = pinv;
312	let n = u.ncols();
313
314	assert!(all(u.nrows() == n, u.ncols() == n, s.nrows() == n));
315
316	let smax = s.norm_max();
317	let tol = max(abs_tol, rel_tol * smax);
318
319	let (mut u_trunc, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
320	let (mut up_trunc, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
321
322	let mut u_trunc = u_trunc.as_mat_mut();
323	let mut up_trunc = up_trunc.as_mat_mut();
324	let mut len = 0;
325
326	for j in 0..n {
327		let x = absmax(s[j]);
328		if x > tol {
329			let p = recip(real(s[j]));
330			u_trunc.rb_mut().col_mut(len).copy_from(u.col(j));
331			z!(up_trunc.rb_mut().col_mut(len), u.col(j)).for_each(|uz!(dst, src)| *dst = mul_real(*src, p));
332
333			len += 1;
334		}
335	}
336
337	linalg::matmul::triangular::matmul(
338		pinv.rb_mut(),
339		BlockStructure::TriangularLower,
340		Accum::Replace,
341		up_trunc.rb(),
342		BlockStructure::Rectangular,
343		u_trunc.rb().adjoint(),
344		BlockStructure::Rectangular,
345		one(),
346		par,
347	);
348
349	for j in 0..n {
350		for i in 0..j {
351			pinv[(i, j)] = conj(pinv[(j, i)]);
352		}
353	}
354}
355
356#[math]
357fn dot2x1<T: RealField>(lhs0: RowRef<'_, T>, lhs1: RowRef<'_, T>, rhs: ColRef<'_, T>) -> (T, T) {
358	let n = rhs.nrows();
359	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
360
361	let mut acc00 = zero::<T>();
362	let mut acc01 = zero::<T>();
363	let mut acc10 = zero::<T>();
364	let mut acc11 = zero::<T>();
365
366	let n2 = n / 2 * 2;
367
368	let mut i = 0;
369	while i < n2 {
370		acc00 = acc00 + lhs0[i] * rhs[i];
371		acc10 = acc10 + lhs1[i] * rhs[i];
372
373		acc01 = acc01 + lhs0[i + 1] * rhs[i + 1];
374		acc11 = acc11 + lhs1[i + 1] * rhs[i + 1];
375
376		i += 2;
377	}
378	while i < n {
379		acc00 = acc00 + lhs0[i] * rhs[i];
380		acc10 = acc10 + lhs1[i] * rhs[i];
381		i += 1;
382	}
383
384	(acc00 + acc01, acc10 + acc11)
385}
386
387#[math]
388fn dot2x2<T: RealField>(lhs0: RowRef<'_, T>, lhs1: RowRef<'_, T>, rhs0: ColRef<'_, T>, rhs1: ColRef<'_, T>) -> (T, T, T, T) {
389	let n = rhs0.nrows();
390	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
391
392	let mut acc0 = zero::<T>();
393	let mut acc1 = zero::<T>();
394	let mut acc2 = zero::<T>();
395	let mut acc3 = zero::<T>();
396
397	let mut i = 0;
398	while i < n {
399		acc0 = acc0 + lhs0[i] * rhs0[i];
400		acc1 = acc1 + lhs1[i] * rhs0[i];
401		acc2 = acc2 + lhs0[i] * rhs1[i];
402		acc3 = acc3 + lhs1[i] * rhs1[i];
403
404		i += 1;
405	}
406
407	(acc0, acc1, acc2, acc3)
408}
409
410#[math]
411pub(crate) fn evd_from_real_schur_imp<T: RealField>(A: MatRef<'_, T>, V: MatMut<'_, T>, par: Par, params: EvdFromSchurParams) {
412	let one = one::<T>;
413	let zero = zero::<T>;
414
415	let mut V = V;
416	let n = A.nrows();
417
418	let mut norm = zero();
419
420	for j in 0..n {
421		for i in 0..Ord::min(j + 2, n) {
422			norm = norm + abs1(A[(i, j)]);
423		}
424	}
425
426	let mut k = n;
427	loop {
428		if k == 0 {
429			break;
430		}
431		k -= 1;
432
433		if k == 0 || A[(k, k - 1)] == zero() {
434			// real eigenvalue
435			let p = copy(A[(k, k)]);
436
437			// solve (A[:k, :k] - p I) X = -A[:k, k]
438			// form rhs
439			V[(k, k)] = one();
440			for i in 0..k {
441				V[(i, k)] = -A[(i, k)];
442			}
443
444			solve_real_shifted_upper_quasi_triangular_system(
445				A.submatrix(0, 0, k, k),
446				p,
447				V.rb_mut().subrows_mut(0, k).col_mut(k),
448				copy(norm),
449				par,
450				params,
451			);
452		} else {
453			// complex eigenvalue pair
454			let p = copy(A[(k, k)]);
455			let q = sqrt(abs(A[(k, k - 1)])) * sqrt(abs(A[(k - 1, k)]));
456
457			// solve (A[:k, :k] - (p + iq) I) X = rhs
458			// form rhs
459			if abs(A[(k - 1, k)]) >= abs(A[(k, k - 1)]) {
460				V[(k - 1, k - 1)] = one();
461				V[(k, k)] = q / A[(k - 1, k)];
462			} else {
463				V[(k - 1, k - 1)] = -q / A[(k, k - 1)];
464				V[(k, k)] = one();
465			}
466
467			V[(k - 1, k)] = zero();
468			V[(k, k - 1)] = zero();
469
470			for i in 0..k - 1 {
471				V[(i, k - 1)] = -V[(k - 1, k - 1)] * A[(i, k - 1)];
472				V[(i, k)] = -V[(k, k)] * A[(i, k)];
473			}
474
475			solve_cplx_shifted_upper_quasi_triangular_system(
476				A.submatrix(0, 0, k - 1, k - 1),
477				p,
478				q,
479				V.rb_mut().submatrix_mut(0, k - 1, k - 1, 2),
480				copy(norm),
481				par,
482				params,
483			);
484
485			k -= 1;
486		}
487	}
488}
489
490#[math]
491pub(crate) fn evd_from_cplx_schur_imp<T: ComplexField>(A: MatRef<'_, T>, conj_A: Conj, V: MatMut<'_, T>, par: Par, params: EvdFromSchurParams) {
492	let one = one::<T>;
493
494	let mut V = V;
495	let n = A.nrows();
496
497	let mut norm = zero::<T::Real>();
498
499	for j in 0..n {
500		for i in 0..j + 1 {
501			norm = norm + abs1(A[(i, j)]);
502		}
503	}
504
505	let mut k = n;
506	loop {
507		if k == 0 {
508			break;
509		}
510		k -= 1;
511
512		let p = if conj_A == Conj::Yes { conj(A[(k, k)]) } else { copy(A[(k, k)]) };
513
514		// solve (A[:k, :k] - p I) X = -A[:k, k]
515		// form rhs
516		V[(k, k)] = one();
517
518		if conj_A == Conj::Yes {
519			for i in 0..k {
520				V[(i, k)] = -conj(A[(i, k)]);
521			}
522		} else {
523			for i in 0..k {
524				V[(i, k)] = -A[(i, k)];
525			}
526		}
527
528		solve_shifted_upper_triangular_system(
529			A.submatrix(0, 0, k, k),
530			conj_A,
531			p,
532			V.rb_mut().subrows_mut(0, k).col_mut(k),
533			copy(norm),
534			par,
535			params,
536		);
537	}
538}
539
540#[math]
541fn solve_real_shifted_upper_quasi_triangular_system<T: RealField>(
542	A: MatRef<'_, T>,
543	p: T,
544	x: ColMut<'_, T>,
545	norm: T,
546	par: Par,
547	params: EvdFromSchurParams,
548) {
549	let n = A.nrows();
550
551	let one = one::<T>;
552	let zero = zero::<T>;
553
554	let eps = eps::<T>();
555
556	let mut x = x;
557
558	if par.degree() == 0 || n < params.recursion_threshold {
559		let mut i = n;
560		loop {
561			if i == 0 {
562				break;
563			}
564			i -= 1;
565
566			if i == 0 || A[(i, i - 1)] == zero() {
567				// 1x1 block
568				let dot = linalg::matmul::dot::inner_prod(A.row(i).subcols(i + 1, n - i - 1), Conj::No, x.rb().subrows(i + 1, n - i - 1), Conj::No);
569
570				x[i] = x[i] - dot;
571
572				let mut z = A[(i, i)] - p;
573
574				if abs(z) < eps * norm {
575					z = eps * norm;
576				}
577
578				if x[i] != zero() {
579					x[i] = x[i] / z;
580				}
581			} else {
582				let (dot0, dot1) = dot2x1(
583					A.row(i - 1).subcols(i + 1, n - i - 1),
584					A.row(i).subcols(i + 1, n - i - 1),
585					x.rb().subrows(i + 1, n - i - 1),
586				);
587
588				x[i - 1] = x[i - 1] - dot0;
589				x[i] = x[i] - dot1;
590
591				// solve
592				// [a b] [x0]   [r0]
593				// [c a]×[x1] = [r1]
594				//
595				//  [x0]   [a  -b] [r0]
596				//  [x1] = [-c  a]×[r1] / det
597				let a = A[(i, i)] - p;
598				let b = copy(A[(i - 1, i)]);
599				let c = copy(A[(i, i - 1)]);
600
601				let r0 = copy(x[i - 1]);
602				let r1 = copy(x[i]);
603
604				let inv_det = recip(abs2(a) - b * c);
605
606				x[i - 1] = (a * r0 - b * r1) * inv_det;
607				x[i] = (a * r1 - c * r0) * inv_det;
608
609				i -= 1;
610			}
611		}
612	} else {
613		let mut mid = n / 2;
614		if A[(mid, mid - 1)] != zero() {
615			mid -= 1;
616		}
617
618		let (A00, A01, _, A11) = A.split_at(mid, mid);
619		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
620
621		solve_real_shifted_upper_quasi_triangular_system(A11, copy(p), x1.rb_mut(), copy(norm), par, params);
622
623		linalg::matmul::matmul(x0.rb_mut().as_mat_mut(), Accum::Add, A01, x1.rb().as_mat(), -one(), par);
624
625		solve_real_shifted_upper_quasi_triangular_system(A00, p, x0.rb_mut(), norm, par, params);
626	}
627}
628
629#[math]
630fn solve_cplx_shifted_upper_quasi_triangular_system<T: RealField>(
631	A: MatRef<'_, T>,
632	p: T,
633	q: T,
634	x: MatMut<'_, T>,
635	norm: T,
636	par: Par,
637	params: EvdFromSchurParams,
638) {
639	let n = A.nrows();
640
641	let one = one::<T>;
642	let zero = zero::<T>;
643
644	let eps = eps::<T>();
645
646	let mut x = x;
647
648	if par.degree() == 0 || n < params.recursion_threshold {
649		let mut i = n;
650		loop {
651			if i == 0 {
652				break;
653			}
654			i -= 1;
655
656			if i == 0 || A[(i, i - 1)] == zero() {
657				// 1x1 block
658				let (re, im) = dot2x1(
659					x.rb().subrows(i + 1, n - i - 1).col(0).transpose(),
660					x.rb().subrows(i + 1, n - i - 1).col(1).transpose(),
661					A.row(i).subcols(i + 1, n - i - 1).transpose(),
662				);
663
664				x[(i, 0)] = x[(i, 0)] - re;
665				x[(i, 1)] = x[(i, 1)] - im;
666
667				let mut z_re = A[(i, i)] - p;
668				let mut z_im = -q;
669				let mut z = hypot(z_re, z_im);
670
671				if z < eps * norm {
672					z_re = eps * norm;
673					z_im = zero();
674					z = copy(z_re);
675				}
676
677				let z_re = (z_re / z) / z;
678				let z_im = (-z_im / z) / z;
679
680				let x_re = copy(x[(i, 0)]);
681				let x_im = copy(x[(i, 1)]);
682
683				x[(i, 0)] = x_re * z_re - x_im * z_im;
684				x[(i, 1)] = x_re * z_im + x_im * z_re;
685			} else {
686				let (re0, re1, im0, im1) = dot2x2(
687					A.row(i - 1).subcols(i + 1, n - i - 1),
688					A.row(i).subcols(i + 1, n - i - 1),
689					x.rb().col(0).subrows(i + 1, n - i - 1),
690					x.rb().col(1).subrows(i + 1, n - i - 1),
691				);
692
693				x[(i - 1, 0)] = x[(i - 1, 0)] - re0;
694				x[(i - 1, 1)] = x[(i - 1, 1)] - im0;
695				x[(i, 0)] = x[(i, 0)] - re1;
696				x[(i, 1)] = x[(i, 1)] - im1;
697
698				let a_re = A[(i, i)] - p;
699				let a_im = -q;
700				let b = copy(A[(i - 1, i)]);
701				let c = copy(A[(i, i - 1)]);
702
703				let r0_re = copy(x[(i - 1, 0)]);
704				let r0_im = copy(x[(i - 1, 1)]);
705				let r1_re = copy(x[(i, 0)]);
706				let r1_im = copy(x[(i, 1)]);
707
708				let mut z_re = abs2(a_re) - abs2(a_im) - b * c;
709				let mut z_im = mul_pow2(a_re * a_im, from_f64::<T>(2.0));
710				let mut z = hypot(z_re, z_im);
711
712				if z < eps * norm {
713					z_re = eps * norm;
714					z_im = zero();
715					z = copy(z_re);
716				}
717
718				let z_re = (z_re / z) / z;
719				let z_im = (-z_im / z) / z;
720
721				let x0_re = (a_re * r0_re - a_im * r0_im) - b * r1_re;
722				let x0_im = (a_re * r0_im + a_im * r0_re) - b * r1_im;
723
724				let x1_re = (a_re * r1_re - a_im * r1_im) - c * r0_re;
725				let x1_im = (a_re * r1_im + a_im * r1_re) - c * r0_im;
726
727				x[(i - 1, 0)] = x0_re * z_re - x0_im * z_im;
728				x[(i - 1, 1)] = x0_re * z_im + x0_im * z_re;
729
730				x[(i, 0)] = x1_re * z_re - x1_im * z_im;
731				x[(i, 1)] = x1_re * z_im + x1_im * z_re;
732
733				i -= 1;
734			}
735		}
736	} else {
737		let mut mid = n / 2;
738		if A[(mid, mid - 1)] != zero() {
739			mid -= 1;
740		}
741
742		let (A00, A01, _, A11) = A.split_at(mid, mid);
743		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
744
745		solve_cplx_shifted_upper_quasi_triangular_system(A11, copy(p), copy(q), x1.rb_mut(), copy(norm), par, params);
746
747		linalg::matmul::matmul(x0.rb_mut(), Accum::Add, A01, x1.rb(), -one(), par);
748
749		solve_cplx_shifted_upper_quasi_triangular_system(A00, p, q, x0.rb_mut(), norm, par, params);
750	}
751}
752
753#[math]
754fn solve_shifted_upper_triangular_system<T: ComplexField>(
755	A: MatRef<'_, T>,
756	conj_A: Conj,
757	p: T,
758	x: ColMut<'_, T>,
759	norm: T::Real,
760	par: Par,
761	params: EvdFromSchurParams,
762) {
763	let n = A.nrows();
764
765	let one = one::<T>;
766	let zero = zero::<T>;
767
768	let eps = eps::<T::Real>();
769
770	let mut x = x;
771
772	if par.degree() == 0 || n < params.recursion_threshold {
773		let mut i = n;
774		loop {
775			if i == 0 {
776				break;
777			}
778			i -= 1;
779
780			// 1x1 block
781			let dot = linalg::matmul::dot::inner_prod(A.row(i).subcols(i + 1, n - i - 1), conj_A, x.rb().subrows(i + 1, n - i - 1), Conj::No);
782
783			x[i] = x[i] - dot;
784
785			let mut z = if conj_A == Conj::Yes { conj(A[(i, i)]) } else { copy(A[(i, i)]) } - p;
786
787			if abs(z) < eps * norm {
788				z = from_real(eps * norm);
789			}
790
791			if x[i] != zero() {
792				x[i] = x[i] * recip(z);
793			}
794		}
795	} else {
796		let mid = n / 2;
797
798		let (A00, A01, _, A11) = A.split_at(mid, mid);
799		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
800
801		solve_shifted_upper_triangular_system(A11, conj_A, copy(p), x1.rb_mut(), copy(norm), par, params);
802
803		linalg::matmul::matmul_with_conj(x0.rb_mut().as_mat_mut(), Accum::Add, A01, conj_A, x1.rb().as_mat(), Conj::No, -one(), par);
804
805		solve_shifted_upper_triangular_system(A00, conj_A, p, x0.rb_mut(), norm, par, params);
806	}
807}
808
809/// computes the size and alignment of the workspace required to compute a matrix's
810/// eigendecomposition
811pub fn evd_scratch<T: ComplexField>(
812	dim: usize,
813	eigen_left: ComputeEigenvectors,
814	eigen_right: ComputeEigenvectors,
815	par: Par,
816	params: Spec<EvdParams, T>,
817) -> StackReq {
818	let n = dim;
819
820	if n == 0 {
821		return StackReq::EMPTY;
822	}
823
824	let compute_eigen = eigen_left == ComputeEigenvectors::Yes || eigen_right == ComputeEigenvectors::Yes;
825	let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n - 1, n - 1);
826
827	let H = temp_mat_scratch::<T>(n, n);
828	let X = H;
829	let Z = temp_mat_scratch::<T>(n, if compute_eigen { n } else { 0 });
830	let householder = temp_mat_scratch::<T>(bs, n);
831	let apply = linalg::householder::apply_block_householder_sequence_on_the_right_in_place_scratch::<T>(n - 1, bs, n - 1);
832
833	StackReq::all_of(&[
834		H,
835		Z,
836		StackReq::any_of(&[
837			householder.and(hessenberg::hessenberg_in_place_scratch::<T>(n, bs, par, params.hessenberg.into()).or(apply)),
838			schur::multishift_qr_scratch::<T>(n, n, compute_eigen, compute_eigen, par, params.schur),
839			X,
840		]),
841	])
842}
843
844#[math]
845fn evd_imp<T: ComplexField>(
846	A: MatRef<'_, T>,
847	s: ColMut<'_, T>,
848	s_im: Option<ColMut<'_, T>>,
849	u_left: Option<MatMut<'_, T>>,
850	u_right: Option<MatMut<'_, T>>,
851	par: Par,
852	stack: &mut MemStack,
853	params: EvdParams,
854) -> Result<(), EvdError> {
855	let n = A.nrows();
856
857	if n == 0 {
858		return Ok(());
859	}
860
861	for j in 0..n {
862		for i in 0..n {
863			if !is_finite(A[(i, j)]) {
864				return Err(EvdError::NoConvergence);
865			}
866		}
867	}
868
869	let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n - 1, n - 1);
870	let mut s = s;
871	let mut s_im = s_im;
872
873	let (mut H, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
874	let (mut Z, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, if u_left.is_some() || u_right.is_some() { n } else { 0 }, stack) };
875
876	let mut H = H.as_mat_mut();
877	let mut Z = if u_left.is_some() || u_right.is_some() {
878		Some(Z.as_mat_mut())
879	} else {
880		None
881	};
882
883	H.copy_from(A);
884
885	{
886		let (mut householder, stack) = unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
887		let mut householder = householder.as_mat_mut();
888
889		hessenberg::hessenberg_in_place(H.rb_mut(), householder.rb_mut(), par, stack, params.hessenberg.into());
890
891		if let Some(mut Z) = Z.rb_mut() {
892			Z.fill(zero());
893			Z.rb_mut().diagonal_mut().fill(one());
894
895			linalg::householder::apply_block_householder_sequence_on_the_right_in_place_with_conj(
896				H.rb().submatrix(1, 0, n - 1, n - 1),
897				householder.rb(),
898				Conj::No,
899				Z.rb_mut().submatrix_mut(1, 1, n - 1, n - 1),
900				par,
901				stack,
902			);
903		}
904
905		for j in 0..n {
906			for i in j + 2..n {
907				H[(i, j)] = zero();
908			}
909		}
910	}
911
912	if try_const! { T::IS_REAL } {
913		schur::real_schur::multishift_qr::<T::Real>(
914			unsafe { core::mem::transmute(Z.is_some()) },
915			unsafe { core::mem::transmute(H.rb_mut()) },
916			unsafe { core::mem::transmute(Z.rb_mut()) },
917			unsafe { core::mem::transmute(s.rb_mut()) },
918			unsafe { core::mem::transmute(s_im.rb_mut().unwrap()) },
919			0,
920			n,
921			par,
922			stack,
923			params.schur,
924		);
925	} else {
926		schur::complex_schur::multishift_qr::<T>(Z.is_some(), H.rb_mut(), Z.rb_mut(), s.rb_mut(), 0, n, par, stack, params.schur);
927	}
928
929	let H = H.rb();
930
931	if let (Some(mut u), Some(Z)) = (u_right, Z.rb()) {
932		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
933		let mut X = X.as_mat_mut();
934
935		if try_const! { T::IS_REAL } {
936			evd_from_real_schur_imp::<T::Real>(
937				unsafe { core::mem::transmute(H) },
938				unsafe { core::mem::transmute(X.rb_mut()) },
939				par,
940				params.evd_from_schur,
941			);
942		} else {
943			evd_from_cplx_schur_imp::<T>(H, Conj::No, X.rb_mut(), par, params.evd_from_schur);
944		}
945
946		linalg::matmul::triangular::matmul(
947			u.rb_mut(),
948			BlockStructure::Rectangular,
949			Accum::Replace,
950			Z,
951			BlockStructure::Rectangular,
952			X.rb(),
953			BlockStructure::TriangularUpper,
954			one(),
955			par,
956		);
957	}
958
959	if let (Some(mut u), Some(Z)) = (u_left, Z.rb()) {
960		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
961		let mut X = X.as_mat_mut().reverse_rows_mut();
962
963		if try_const! { T::IS_REAL } {
964			evd_from_real_schur_imp::<T::Real>(
965				unsafe { core::mem::transmute(H.transpose().reverse_rows_and_cols()) },
966				unsafe { core::mem::transmute(X.rb_mut()) },
967				par,
968				params.evd_from_schur,
969			);
970		} else {
971			evd_from_cplx_schur_imp::<T>(H.transpose().reverse_rows_and_cols(), Conj::Yes, X.rb_mut(), par, params.evd_from_schur);
972		}
973
974		linalg::matmul::triangular::matmul(
975			u.rb_mut(),
976			BlockStructure::Rectangular,
977			Accum::Replace,
978			Z,
979			BlockStructure::Rectangular,
980			X.rb().reverse_rows_and_cols(),
981			BlockStructure::TriangularLower,
982			one(),
983			par,
984		);
985	}
986
987	Ok(())
988}
989
990/// computes the matrix $A$'s eigendecomposition
991///
992/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the right eigenvectors in
993/// $U_R$
994#[track_caller]
995pub fn evd_cplx<T: RealField>(
996	A: MatRef<'_, Complex<T>>,
997	s: DiagMut<'_, Complex<T>>,
998	u_left: Option<MatMut<'_, Complex<T>>>,
999	u_right: Option<MatMut<'_, Complex<T>>>,
1000	par: Par,
1001	stack: &mut MemStack,
1002	params: Spec<EvdParams, Complex<T>>,
1003) -> Result<(), EvdError> {
1004	let n = A.nrows();
1005	assert!(all(A.nrows() == n, A.ncols() == n, s.dim() == n));
1006	if let Some(u) = u_left.rb() {
1007		assert!(all(u.nrows() == n, u.ncols() == n));
1008	}
1009	if let Some(u) = u_right.rb() {
1010		assert!(all(u.nrows() == n, u.ncols() == n));
1011	}
1012
1013	evd_imp(A, s.column_vector_mut(), None, u_left, u_right, par, stack, params.config)
1014}
1015
1016/// computes the matrix $A$'s eigendecomposition
1017///
1018/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the right eigenvectors in
1019/// $U_R$
1020#[track_caller]
1021pub fn evd_real<T: RealField>(
1022	A: MatRef<'_, T>,
1023	s_re: DiagMut<'_, T>,
1024	s_im: DiagMut<'_, T>,
1025	u_left: Option<MatMut<'_, T>>,
1026	u_right: Option<MatMut<'_, T>>,
1027	par: Par,
1028	stack: &mut MemStack,
1029	params: Spec<EvdParams, T>,
1030) -> Result<(), EvdError> {
1031	let n = A.nrows();
1032	assert!(all(A.nrows() == n, A.ncols() == n, s_re.dim() == n, s_im.dim() == n));
1033	if let Some(u) = u_left.rb() {
1034		assert!(all(u.nrows() == n, u.ncols() == n));
1035	}
1036	if let Some(u) = u_right.rb() {
1037		assert!(all(u.nrows() == n, u.ncols() == n));
1038	}
1039
1040	evd_imp(
1041		A,
1042		s_re.column_vector_mut(),
1043		Some(s_im.column_vector_mut()),
1044		u_left,
1045		u_right,
1046		par,
1047		stack,
1048		params.config,
1049	)
1050}
1051
1052#[cfg(test)]
1053mod general_tests {
1054	use super::*;
1055	use crate::assert;
1056	use crate::stats::prelude::*;
1057	use crate::utils::approx::*;
1058	use dyn_stack::MemBuffer;
1059
1060	fn test_cplx_evd(mat: MatRef<'_, c64>) {
1061		let n = mat.nrows();
1062		let params = Spec::new(EvdParams {
1063			hessenberg: auto!(c64),
1064			schur: auto!(c64),
1065			evd_from_schur: EvdFromSchurParams {
1066				recursion_threshold: 8,
1067				..auto!(c64)
1068			},
1069			..auto!(c64)
1070		});
1071
1072		use faer_traits::math_utils::*;
1073		let approx_eq = CwiseMat(ApproxEq::eps() * sqrt(&from_f64(8.0 * n as f64)));
1074
1075		let mut s = Mat::zeros(n, n);
1076		{
1077			let mut ul = Mat::zeros(n, n);
1078			let mut ur = Mat::zeros(n, n);
1079
1080			evd_cplx(
1081				mat.as_ref(),
1082				s.as_mut().diagonal_mut(),
1083				Some(ul.as_mut()),
1084				Some(ur.as_mut()),
1085				Par::Seq,
1086				MemStack::new(&mut MemBuffer::new(evd_scratch::<c64>(
1087					n,
1088					ComputeEigenvectors::Yes,
1089					ComputeEigenvectors::Yes,
1090					Par::Seq,
1091					params,
1092				))),
1093				params,
1094			)
1095			.unwrap();
1096
1097			assert!(&ur * &s ~ mat * &ur);
1098			assert!(&s * ul.adjoint() ~ ul.adjoint() * mat);
1099		}
1100	}
1101
1102	fn test_real_evd(mat: MatRef<'_, f64>) {
1103		let n = mat.nrows();
1104		let params = Spec::new(EvdParams {
1105			hessenberg: auto!(f64),
1106			schur: auto!(f64),
1107			evd_from_schur: EvdFromSchurParams {
1108				recursion_threshold: 8,
1109				..auto!(f64)
1110			},
1111			..auto!(f64)
1112		});
1113
1114		use faer_traits::math_utils::*;
1115		let approx_eq = CwiseMat(ApproxEq::<f64>::eps() * sqrt(&from_f64(8.0 * n as f64)));
1116
1117		let mut s_re = Diag::zeros(n);
1118		let mut s_im = Diag::zeros(n);
1119		{
1120			let mut ul = Mat::zeros(n, n);
1121			let mut ur = Mat::zeros(n, n);
1122
1123			evd_real(
1124				mat.as_ref(),
1125				s_re.as_mut(),
1126				s_im.as_mut(),
1127				Some(ul.as_mut()),
1128				Some(ur.as_mut()),
1129				Par::Seq,
1130				MemStack::new(&mut MemBuffer::new(evd_scratch::<f64>(
1131					n,
1132					ComputeEigenvectors::Yes,
1133					ComputeEigenvectors::Yes,
1134					Par::Seq,
1135					params,
1136				))),
1137				params,
1138			)
1139			.unwrap();
1140
1141			let mut i = 0;
1142			while i < n {
1143				if s_im[i] == 0.0 {
1144					let ur = ur.col(i);
1145					let ul = ul.col(i);
1146
1147					let s = Scale(s_re[i]);
1148
1149					assert!((&ur * s).as_mat() ~ (mat * &ur).as_mat());
1150					assert!((&ul.adjoint() * s).as_mat() ~ (&ul.adjoint() * mat).as_mat());
1151
1152					i += 1;
1153				} else {
1154					let re = ur.col(i);
1155					let im = ur.col(i + 1);
1156					let ur = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1157
1158					let re = ul.col(i);
1159					let im = ul.col(i + 1);
1160					let ul = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1161
1162					let mat = &Mat::from_fn(n, n, |i, j| c64::from(mat[(i, j)]));
1163
1164					let s = Scale(c64::new(s_re[i], s_im[i]));
1165
1166					let approx_eq = CwiseMat(ApproxEq::eps() * sqrt(&from_f64(8.0 * n as f64)));
1167
1168					assert!((ur * s).as_mat() ~ (mat * ur).as_mat());
1169					assert!((ul.adjoint() * s).as_mat() ~ (ul.adjoint() * mat).as_mat());
1170
1171					i += 2;
1172				}
1173			}
1174		}
1175	}
1176
1177	#[test]
1178	fn test_cplx() {
1179		let rng = &mut StdRng::seed_from_u64(1);
1180
1181		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1182			let mat = CwiseMatDistribution {
1183				nrows: n,
1184				ncols: n,
1185				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1186			}
1187			.rand::<Mat<c64>>(rng);
1188
1189			test_cplx_evd(mat.as_ref());
1190		}
1191	}
1192
1193	#[test]
1194	fn test_real() {
1195		let rng = &mut StdRng::seed_from_u64(0);
1196
1197		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1198			let mat = CwiseMatDistribution {
1199				nrows: n,
1200				ncols: n,
1201				dist: StandardNormal,
1202			}
1203			.rand::<Mat<f64>>(rng);
1204
1205			test_real_evd(mat.as_ref());
1206		}
1207	}
1208}
1209
1210#[cfg(test)]
1211mod self_adjoint_tests {
1212	use super::*;
1213	use crate::assert;
1214	use crate::stats::prelude::*;
1215	use crate::utils::approx::*;
1216	use dyn_stack::MemBuffer;
1217
1218	fn test_self_adjoint_evd<T: ComplexField>(mat: MatRef<'_, T>) {
1219		let n = mat.nrows();
1220		let params = Spec::new(SelfAdjointEvdParams {
1221			recursion_threshold: 8,
1222			..auto!(T)
1223		});
1224		use faer_traits::math_utils::*;
1225		let approx_eq = CwiseMat(ApproxEq::<T::Real>::eps() * sqrt(&from_f64(8.0 * n as f64)));
1226
1227		let mut s = Mat::zeros(n, n);
1228		{
1229			let mut u = Mat::zeros(n, n);
1230
1231			self_adjoint_evd(
1232				mat.as_ref(),
1233				s.as_mut().diagonal_mut(),
1234				Some(u.as_mut()),
1235				Par::Seq,
1236				MemStack::new(&mut MemBuffer::new(self_adjoint_evd_scratch::<T>(
1237					n,
1238					ComputeEigenvectors::Yes,
1239					Par::Seq,
1240					params,
1241				))),
1242				params,
1243			)
1244			.unwrap();
1245
1246			let reconstructed = &u * &s * u.adjoint();
1247			assert!(reconstructed ~ mat);
1248		}
1249
1250		{
1251			let mut s2 = Mat::zeros(n, n);
1252
1253			self_adjoint_evd(
1254				mat.as_ref(),
1255				s2.as_mut().diagonal_mut(),
1256				None,
1257				Par::Seq,
1258				MemStack::new(&mut MemBuffer::new(self_adjoint_evd_scratch::<T>(
1259					n,
1260					ComputeEigenvectors::No,
1261					Par::Seq,
1262					params,
1263				))),
1264				params,
1265			)
1266			.unwrap();
1267
1268			assert!(s2 ~ s);
1269		}
1270	}
1271
1272	#[test]
1273	fn test_real() {
1274		let rng = &mut StdRng::seed_from_u64(1);
1275
1276		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1277			let mat = CwiseMatDistribution {
1278				nrows: n,
1279				ncols: n,
1280				dist: StandardNormal,
1281			}
1282			.rand::<Mat<f64>>(rng);
1283			let mat = &mat + mat.adjoint();
1284
1285			test_self_adjoint_evd(mat.as_ref());
1286		}
1287	}
1288
1289	#[test]
1290	fn test_cplx() {
1291		let rng = &mut StdRng::seed_from_u64(1);
1292
1293		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1294			let mat = CwiseMatDistribution {
1295				nrows: n,
1296				ncols: n,
1297				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1298			}
1299			.rand::<Mat<c64>>(rng);
1300			let mat = &mat + mat.adjoint();
1301
1302			test_self_adjoint_evd(mat.as_ref());
1303		}
1304	}
1305
1306	#[test]
1307	fn test_special() {
1308		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1309			test_self_adjoint_evd(Mat::full(n, n, 0.0).as_ref());
1310			test_self_adjoint_evd(Mat::full(n, n, c64::ZERO).as_ref());
1311			test_self_adjoint_evd(Mat::full(n, n, 1.0).as_ref());
1312			test_self_adjoint_evd(Mat::full(n, n, c64::ONE).as_ref());
1313			test_self_adjoint_evd(Mat::<f64>::identity(n, n).as_ref());
1314			test_self_adjoint_evd(Mat::<c64>::identity(n, n).as_ref());
1315		}
1316	}
1317}