Skip to main content

faer/linalg/evd/
mod.rs

1//! low level implementation of the eigenvalue decomposition of a square
2//! diagonalizable matrix.
3//!
4//! the eigenvalue decomposition of a square matrix $A$ of shape $(n, n)$ is a
5//! decomposition into two components $U$, $S$:
6//!
7//! - $U$ has shape $(n, n)$ and is invertible
8//! - $S$ has shape $(n, n)$ and is a diagonal matrix
9//! - and finally:
10//!
11//! $$A = U S U^{-1}$$
12//!
13//! if $A$ is self-adjoint, then $U$ can be made unitary ($U^{-1} = U^H$), and
14//! $S$ is real valued
15/// hessenberg decomposition
16pub mod hessenberg;
17#[doc(hidden)]
18pub mod schur;
19/// self-adjoint tridiagonalization
20pub mod tridiag;
21pub(crate) mod tridiag_evd;
22use crate::assert;
23use crate::internal_prelude::*;
24use hessenberg::HessenbergParams;
25use linalg::matmul::triangular::BlockStructure;
26pub use schur::SchurParams;
27use tridiag::TridiagParams;
28/// eigendecomposition error
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub enum EvdError {
31	/// reached max iterations
32	NoConvergence,
33}
34/// schur to eigendecomposition conversion parameters
35#[derive(Clone, Copy, Debug)]
36pub struct EvdFromSchurParams {
37	/// threshold at which the implementation should stop recursing
38	pub recursion_threshold: usize,
39	#[doc(hidden)]
40	pub non_exhaustive: NonExhaustive,
41}
42/// eigendecomposition tuning parameters
43#[derive(Clone, Copy, Debug)]
44pub struct EvdParams {
45	/// hessenberg parameters
46	pub hessenberg: HessenbergParams,
47	/// schur from hessenberg conversion parameters
48	pub schur: SchurParams,
49	/// eigendecomposition from schur conversion parameters
50	pub evd_from_schur: EvdFromSchurParams,
51	#[doc(hidden)]
52	pub non_exhaustive: NonExhaustive,
53}
54/// self-adjoint eigendecomposition tuning parameters
55#[derive(Clone, Copy, Debug)]
56pub struct SelfAdjointEvdParams {
57	/// tridiagonalization parameters
58	pub tridiag: TridiagParams,
59	/// threshold at which the implementation should stop recursing
60	pub recursion_threshold: usize,
61	#[doc(hidden)]
62	pub non_exhaustive: NonExhaustive,
63}
64impl<T: ComplexField> Auto<T> for EvdParams {
65	fn auto() -> Self {
66		Self {
67			hessenberg: auto!(T),
68			schur: auto!(T),
69			evd_from_schur: auto!(T),
70			non_exhaustive: NonExhaustive(()),
71		}
72	}
73}
74impl<T: ComplexField> Auto<T> for EvdFromSchurParams {
75	fn auto() -> Self {
76		Self {
77			recursion_threshold: 64,
78			non_exhaustive: NonExhaustive(()),
79		}
80	}
81}
82impl<T: ComplexField> Auto<T> for SelfAdjointEvdParams {
83	fn auto() -> Self {
84		Self {
85			tridiag: auto!(T),
86			recursion_threshold: 128,
87			non_exhaustive: NonExhaustive(()),
88		}
89	}
90}
91/// whether the eigenvectors should be computed
92#[derive(Copy, Clone, Debug, PartialEq, Eq)]
93pub enum ComputeEigenvectors {
94	/// do not compute eigenvectors
95	No,
96	/// compute eigenvectors
97	Yes,
98}
99/// computes the layout of the workspace required to compute a self-adjoint
100/// matrix's eigendecomposition
101pub fn self_adjoint_evd_scratch<T: ComplexField>(
102	dim: usize,
103	compute_u: ComputeEigenvectors,
104	par: Par,
105	params: Spec<SelfAdjointEvdParams, T>,
106) -> StackReq {
107	let n = dim;
108	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
109	let prologue = StackReq::all_of(&[
110		temp_mat_scratch::<T>(n, n),
111		temp_mat_scratch::<T>(bs, n),
112		StackReq::any_of(&[tridiag::tridiag_in_place_scratch::<T>(
113			n,
114			par,
115			params.tridiag.into(),
116		)]),
117		temp_mat_scratch::<T::Real>(n, 1).array(2),
118	]);
119	if compute_u == ComputeEigenvectors::No {
120		return prologue;
121	}
122	StackReq::all_of(&[
123		prologue,
124		temp_mat_scratch::<T::Real>(
125			n,
126			if const {
127				T::IS_REAL
128			} {
129				0
130			} else {
131				n
132			},
133		)
134		.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/// computes the matrix $A$'s eigendecomposition, assuming it is tridiagonal and
147/// self-adjoint
148///
149/// the eigenvalues are stored in $S$, and the eigenvectors in $U$ such that the
150/// eigenvalues are sorted in nondecreasing order
151pub fn tridiagonal_self_adjoint_evd<T: ComplexField>(
152	diag: DiagRef<'_, T>,
153	subdiag: DiagRef<'_, T>,
154	s: DiagMut<'_, T>,
155	u: Option<MatMut<'_, T>>,
156	par: Par,
157	stack: &mut MemStack,
158	params: Spec<SelfAdjointEvdParams, T>,
159) -> Result<(), EvdError> {
160	let n = diag.dim();
161	let (mut real_diag, stack) =
162		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
163	let (mut real_offdiag, stack) =
164		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
165	let mut real_diag = real_diag
166		.as_mat_mut()
167		.col_mut(0)
168		.try_as_col_major_mut()
169		.unwrap();
170	let mut real_offdiag = real_offdiag
171		.as_mat_mut()
172		.col_mut(0)
173		.try_as_col_major_mut()
174		.unwrap();
175	for i in 0..n {
176		real_diag[i] = diag[i].real();
177		if i + 1 < n {
178			if const { T::IS_REAL } {
179				real_offdiag[i] = subdiag[i].real();
180			} else {
181				real_offdiag[i] = subdiag[i].abs();
182			}
183		} else {
184			real_offdiag[i] = zero();
185		}
186	}
187	let mut s = s;
188	let mut u = match u {
189		Some(u) => u,
190		None => {
191			tridiag_evd::qr_algorithm(
192				real_diag.rb_mut(),
193				real_offdiag.rb_mut(),
194				None,
195			)?;
196			for i in 0..n {
197				s[i] = real_diag[i].to_cplx();
198			}
199			return Ok(());
200		},
201	};
202	let (mut u_real, stack) = unsafe {
203		temp_mat_uninit::<T::Real, _, _>(
204			n,
205			if T::IS_REAL { 0 } else { n },
206			stack,
207		)
208	};
209	let mut u_real = u_real.as_mat_mut();
210	let mut u_evd = if const { T::IS_REAL } {
211		unsafe { core::mem::transmute(u.rb_mut()) }
212	} else {
213		u_real.rb_mut()
214	};
215	if n < params.recursion_threshold {
216		tridiag_evd::qr_algorithm(
217			real_diag.rb_mut(),
218			real_offdiag.rb_mut(),
219			Some(u_evd.rb_mut()),
220		)?;
221	} else {
222		tridiag_evd::divide_and_conquer::<T::Real>(
223			real_diag.rb_mut(),
224			real_offdiag.rb_mut(),
225			u_evd.rb_mut(),
226			par,
227			stack,
228			params.recursion_threshold,
229		)?;
230	}
231	if const { !T::IS_REAL } {
232		let normalized = |x: T| {
233			if x == zero() {
234				one()
235			} else {
236				x.mul_real(x.abs().recip())
237			}
238		};
239		let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
240		let mut scale = scale
241			.as_mat_mut()
242			.col_mut(0)
243			.try_as_col_major_mut()
244			.unwrap();
245		let mut x = one::<T>();
246		scale[0] = one();
247		for i in 1..n {
248			x = normalized(&subdiag[i - 1] * x);
249			scale[i] = x.copy();
250		}
251		for j in 0..n {
252			z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(
253				|uz!(u, real, scale)| {
254					*u = scale.mul_real(real);
255				},
256			);
257		}
258	}
259	for i in 0..n {
260		s[i] = real_diag[i].to_cplx();
261	}
262	Ok(())
263}
264/// computes the matrix $A$'s eigendecomposition, assuming it is self-adjoint
265///
266/// the eigenvalues are stored in $S$, and the eigenvectors in $U$ such that the
267/// eigenvalues are sorted in nondecreasing order
268///
269/// only the lower triangular half of $A$ is accessed
270pub fn self_adjoint_evd<T: ComplexField>(
271	A: MatRef<'_, T>,
272	s: DiagMut<'_, T>,
273	u: Option<MatMut<'_, T>>,
274	par: Par,
275	stack: &mut MemStack,
276	params: Spec<SelfAdjointEvdParams, T>,
277) -> Result<(), EvdError> {
278	let n = A.nrows();
279	assert!(all(A.nrows() == A.ncols(), s.dim() == n));
280	if let Some(u) = u.rb() {
281		assert!(all(u.nrows() == n, u.ncols() == n));
282	}
283	let s = s.column_vector_mut();
284	if n == 0 {
285		return Ok(());
286	}
287	#[cfg(feature = "perf-warn")]
288	if let Some(matrix) = u.rb() {
289		if matrix.row_stride().unsigned_abs() != 1
290			&& crate::__perf_warn!(QR_WARN)
291		{
292			if matrix.col_stride().unsigned_abs() == 1 {
293				log::warn!(
294					target : "faer_perf",
295					"EVD prefers column-major eigenvector matrix. Found row-major matrix."
296				);
297			} else {
298				log::warn!(
299					target : "faer_perf",
300					"EVD prefers column-major eigenvector matrix. Found matrix with generic strides."
301				);
302			}
303		}
304	}
305	let (mut trid, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
306	let mut trid = trid.as_mat_mut();
307	trid.copy_from_triangular_lower(A);
308	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
309	let (mut householder, stack) =
310		unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
311	let mut householder = householder.as_mat_mut();
312	{
313		tridiag::tridiag_in_place(
314			trid.rb_mut(),
315			householder.rb_mut(),
316			par,
317			stack,
318			params.tridiag.into(),
319		);
320	}
321	let trid = trid.rb();
322	let (mut diag, stack) =
323		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
324	let (mut offdiag, stack) =
325		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
326	let mut diag = diag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
327	let mut offdiag = offdiag
328		.as_mat_mut()
329		.col_mut(0)
330		.try_as_col_major_mut()
331		.unwrap();
332	for i in 0..n {
333		diag[i] = trid[(i, i)].real();
334		if i + 1 < n {
335			if const { T::IS_REAL } {
336				offdiag[i] = trid[(i + 1, i)].real();
337			} else {
338				offdiag[i] = trid[(i + 1, i)].abs();
339			}
340		} else {
341			offdiag[i] = zero();
342		}
343	}
344	let mut s = s;
345	let mut u = match u {
346		Some(u) => u,
347		None => {
348			tridiag_evd::qr_algorithm(diag.rb_mut(), offdiag.rb_mut(), None)?;
349			for i in 0..n {
350				s[i] = diag[i].to_cplx();
351			}
352			return Ok(());
353		},
354	};
355	let (mut u_real, stack) = unsafe {
356		temp_mat_uninit::<T::Real, _, _>(
357			n,
358			if T::IS_REAL { 0 } else { n },
359			stack,
360		)
361	};
362	let mut u_real = u_real.as_mat_mut();
363	let mut u_evd = if const { T::IS_REAL } {
364		unsafe { core::mem::transmute(u.rb_mut()) }
365	} else {
366		u_real.rb_mut()
367	};
368	if n < params.recursion_threshold {
369		tridiag_evd::qr_algorithm(
370			diag.rb_mut(),
371			offdiag.rb_mut(),
372			Some(u_evd.rb_mut()),
373		)?;
374	} else {
375		tridiag_evd::divide_and_conquer::<T::Real>(
376			diag.rb_mut(),
377			offdiag.rb_mut(),
378			u_evd.rb_mut(),
379			par,
380			stack,
381			params.recursion_threshold,
382		)?;
383	}
384	if const { !T::IS_REAL } {
385		let normalized = |x: T| {
386			if x == zero() {
387				one()
388			} else {
389				x.mul_real(x.abs().recip())
390			}
391		};
392		let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
393		let mut scale = scale
394			.as_mat_mut()
395			.col_mut(0)
396			.try_as_col_major_mut()
397			.unwrap();
398		let mut x = one::<T>();
399		scale[0] = one();
400		for i in 1..n {
401			x = normalized(&trid[(i, i - 1)] * x);
402			scale[i] = x.copy();
403		}
404		for j in 0..n {
405			z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(
406				|uz!(u, real, scale)| {
407					*u = scale.mul_real(real);
408				},
409			);
410		}
411	}
412	linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
413		trid.submatrix(1, 0, n - 1, n - 1),
414		householder.rb(),
415		Conj::No,
416		u.rb_mut().subrows_mut(1, n - 1),
417		par,
418		stack,
419	);
420	for i in 0..n {
421		s[i] = diag[i].to_cplx();
422	}
423	Ok(())
424}
425/// computes the layout of the workspace required to compute a self-adjoint
426/// matrix's pseudoinverse, given the eigendecomposition
427pub fn pseudoinverse_from_self_adjoint_evd_scratch<T: ComplexField>(
428	dim: usize,
429	par: Par,
430) -> StackReq {
431	_ = par;
432	temp_mat_scratch::<T>(dim, dim).array(2)
433}
434/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition
435/// factors $S$ and $U$
436#[track_caller]
437pub fn pseudoinverse_from_self_adjoint_evd<T: ComplexField>(
438	pinv: MatMut<'_, T>,
439	s: DiagRef<'_, T>,
440	u: MatRef<'_, T>,
441	par: Par,
442	stack: &mut MemStack,
443) {
444	pseudoinverse_from_self_adjoint_evd_with_tolerance(
445		pinv,
446		s,
447		u,
448		zero(),
449		eps::<T::Real>() * from_f64::<T::Real>(u.ncols() as f64),
450		par,
451		stack,
452	);
453}
454/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition
455/// factors $S$ and $U$, and tolerance parameters for determining zero
456/// eigenvalues
457#[track_caller]
458pub fn pseudoinverse_from_self_adjoint_evd_with_tolerance<T: ComplexField>(
459	pinv: MatMut<'_, T>,
460	s: DiagRef<'_, T>,
461	u: MatRef<'_, T>,
462	abs_tol: T::Real,
463	rel_tol: T::Real,
464	par: Par,
465	stack: &mut MemStack,
466) {
467	let s = s.column_vector();
468	let mut pinv = pinv;
469	let n = u.ncols();
470	assert!(all(u.nrows() == n, u.ncols() == n, s.nrows() == n));
471	let smax = s.norm_max();
472	let tol = abs_tol.fmax(rel_tol * smax);
473	let (mut u_trunc, stack) =
474		unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
475	let (mut up_trunc, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
476	let mut u_trunc = u_trunc.as_mat_mut();
477	let mut up_trunc = up_trunc.as_mat_mut();
478	let mut len = 0;
479	for j in 0..n {
480		let x = s[j].absmax();
481		if x > tol {
482			let ref p = s[j].real().recip();
483			u_trunc.rb_mut().col_mut(len).copy_from(u.col(j));
484			z!(up_trunc.rb_mut().col_mut(len), u.col(j))
485				.for_each(|uz!(dst, src)| *dst = src.mul_real(p));
486			len += 1;
487		}
488	}
489	let u_trunc = u_trunc.get(.., ..len);
490	let up_trunc = up_trunc.get(.., ..len);
491	linalg::matmul::triangular::matmul(
492		pinv.rb_mut(),
493		BlockStructure::TriangularLower,
494		Accum::Replace,
495		up_trunc.rb(),
496		BlockStructure::Rectangular,
497		u_trunc.rb().adjoint(),
498		BlockStructure::Rectangular,
499		one(),
500		par,
501	);
502	for j in 0..n {
503		for i in 0..j {
504			pinv[(i, j)] = pinv[(j, i)].conj();
505		}
506	}
507}
508fn dot2x1<T: RealField>(
509	lhs0: RowRef<'_, T>,
510	lhs1: RowRef<'_, T>,
511	rhs: ColRef<'_, T>,
512) -> (T, T) {
513	let n = rhs.nrows();
514	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
515	let mut acc00 = zero::<T>();
516	let mut acc01 = zero::<T>();
517	let mut acc10 = zero::<T>();
518	let mut acc11 = zero::<T>();
519	let n2 = n / 2 * 2;
520	let mut i = 0;
521	while i < n2 {
522		acc00 += &lhs0[i] * &rhs[i];
523		acc10 += &lhs1[i] * &rhs[i];
524		acc01 += &lhs0[i + 1] * &rhs[i + 1];
525		acc11 += &lhs1[i + 1] * &rhs[i + 1];
526		i += 2;
527	}
528	while i < n {
529		acc00 += &lhs0[i] * &rhs[i];
530		acc10 += &lhs1[i] * &rhs[i];
531		i += 1;
532	}
533	(acc00 + acc01, acc10 + acc11)
534}
535fn dot2x2<T: RealField>(
536	lhs0: RowRef<'_, T>,
537	lhs1: RowRef<'_, T>,
538	rhs0: ColRef<'_, T>,
539	rhs1: ColRef<'_, T>,
540) -> (T, T, T, T) {
541	let n = rhs0.nrows();
542	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
543	let mut acc0 = zero::<T>();
544	let mut acc1 = zero::<T>();
545	let mut acc2 = zero::<T>();
546	let mut acc3 = zero::<T>();
547	let mut i = 0;
548	while i < n {
549		acc0 += &lhs0[i] * &rhs0[i];
550		acc1 += &lhs1[i] * &rhs0[i];
551		acc2 += &lhs0[i] * &rhs1[i];
552		acc3 += &lhs1[i] * &rhs1[i];
553		i += 1;
554	}
555	(acc0, acc1, acc2, acc3)
556}
557/// Normalizes the eigenvector columns of `u` to unit 2-norm. For a real Schur
558/// form `h`, a conjugate pair `j`, `j + 1` (`h[(j + 1, j)] != 0`) is scaled by a
559/// common factor so the represented complex eigenvector `re + i * im` has unit
560/// 2-norm. This is the normalization contract `xGEEV` provides.
561fn normalize_eigenvectors<T: ComplexField>(
562	mut u: MatMut<'_, T>,
563	h: MatRef<'_, T>,
564) {
565	let n = u.nrows();
566	let mut j = 0;
567	while j < n {
568		let paired = T::IS_REAL && j + 1 < n && h[(j + 1, j)] != zero::<T>();
569		let nrm = if paired {
570			u.rb().col(j).norm_l2().hypot(&u.rb().col(j + 1).norm_l2())
571		} else {
572			u.rb().col(j).norm_l2()
573		};
574		if nrm != zero::<T::Real>() {
575			let inv: T = nrm.recip().to_cplx();
576			for i in 0..n {
577				u[(i, j)] *= inv.copy();
578				if paired {
579					u[(i, j + 1)] *= inv.copy();
580				}
581			}
582		}
583		j += if paired { 2 } else { 1 };
584	}
585}
586/// Returns `false` if a returned eigenvector column is nonfinite or
587/// degenerate, which means the normalization contract could not be met.
588fn eigenvectors_ok<T: ComplexField>(u: MatRef<'_, T>) -> bool {
589	u.is_all_finite()
590		&& (0..u.ncols()).all(|j| u.col(j).norm_l2() > zero::<T::Real>())
591}
592pub(crate) fn evd_from_real_schur_imp<T: RealField>(
593	A: MatRef<'_, T>,
594	V: MatMut<'_, T>,
595	par: Par,
596	params: EvdFromSchurParams,
597) {
598	let one = one::<T>;
599	let zero = zero::<T>;
600	let mut V = V;
601	let n = A.nrows();
602	let mut norm = zero();
603	for j in 0..n {
604		for i in 0..Ord::min(j + 2, n) {
605			norm += A[(i, j)].abs1();
606		}
607	}
608	let mut k = n;
609	loop {
610		if k == 0 {
611			break;
612		}
613		k -= 1;
614		if k == 0 || A[(k, k - 1)] == zero() {
615			let ref p = A[(k, k)].copy();
616			V[(k, k)] = one();
617			for i in 0..k {
618				V[(i, k)] = -&A[(i, k)];
619			}
620			let scale = solve_real_shifted_upper_quasi_triangular_system(
621				A.submatrix(0, 0, k, k),
622				p.copy(),
623				V.rb_mut().subrows_mut(0, k).col_mut(k),
624				norm.copy(),
625				par,
626				params,
627			);
628			// The pivot is not part of the back-substituted slice, so it must
629			// carry the accumulated rescaling factor for the column direction
630			// to be preserved.
631			V[(k, k)] *= scale;
632		} else {
633			let ref p = A[(k, k)].copy();
634			let ref q = A[(k, k - 1)].abs().sqrt() * A[(k - 1, k)].abs().sqrt();
635			if A[(k - 1, k)].abs() >= A[(k, k - 1)].abs() {
636				V[(k - 1, k - 1)] = one();
637				V[(k, k)] = q / &A[(k - 1, k)];
638			} else {
639				V[(k - 1, k - 1)] = -q / &A[(k, k - 1)];
640				V[(k, k)] = one();
641			}
642			V[(k - 1, k)] = zero();
643			V[(k, k - 1)] = zero();
644			for i in 0..k - 1 {
645				V[(i, k - 1)] = -&V[(k - 1, k - 1)] * &A[(i, k - 1)];
646				V[(i, k)] = -&V[(k, k)] * &A[(i, k)];
647			}
648			let scale = solve_cplx_shifted_upper_quasi_triangular_system(
649				A.submatrix(0, 0, k - 1, k - 1),
650				p.copy(),
651				q.copy(),
652				V.rb_mut().submatrix_mut(0, k - 1, k - 1, 2),
653				norm.copy(),
654				par,
655				params,
656			);
657			V[(k - 1, k - 1)] *= scale.copy();
658			V[(k, k)] *= scale;
659			k -= 1;
660		}
661	}
662}
663pub(crate) fn evd_from_cplx_schur_imp<T: ComplexField>(
664	A: MatRef<'_, T>,
665	conj_A: Conj,
666	V: MatMut<'_, T>,
667	par: Par,
668	params: EvdFromSchurParams,
669) {
670	let one = one::<T>;
671	let mut V = V;
672	let n = A.nrows();
673	let mut norm = zero::<T::Real>();
674	for j in 0..n {
675		for i in 0..j + 1 {
676			norm += A[(i, j)].abs1();
677		}
678	}
679	let mut k = n;
680	loop {
681		if k == 0 {
682			break;
683		}
684		k -= 1;
685		let ref p = if conj_A == Conj::Yes {
686			A[(k, k)].conj()
687		} else {
688			A[(k, k)].copy()
689		};
690		V[(k, k)] = one();
691		if conj_A == Conj::Yes {
692			for i in 0..k {
693				V[(i, k)] = -A[(i, k)].conj();
694			}
695		} else {
696			for i in 0..k {
697				V[(i, k)] = -&A[(i, k)];
698			}
699		}
700		let scale = solve_shifted_upper_triangular_system(
701			A.submatrix(0, 0, k, k),
702			conj_A,
703			p.copy(),
704			V.rb_mut().subrows_mut(0, k).col_mut(k),
705			norm.copy(),
706			par,
707			params,
708		);
709		let scale: T = scale.to_cplx();
710		V[(k, k)] *= scale;
711	}
712}
713fn solve_real_shifted_upper_quasi_triangular_system<T: RealField>(
714	A: MatRef<'_, T>,
715	p: T,
716	x: ColMut<'_, T>,
717	norm: T,
718	par: Par,
719	params: EvdFromSchurParams,
720) -> T {
721	let n = A.nrows();
722	let one = one::<T>;
723	let zero = zero::<T>;
724	let ref eps = eps::<T>();
725	let ref p = p;
726	let mut x = x;
727	let ref norm = norm;
728	// While the shift `A[i, i] - p` is clamped to `eps * norm`, back
729	// substitution can grow the solution by `1 / (eps * norm)` per row and
730	// overflow for finite input. Whenever a computed entry exceeds this bound
731	// the whole slice is rescaled; the factor is also returned so the caller
732	// can apply it to the pivot entry, keeping the eigenvector direction.
733	let big = sqrt_max_positive::<T>();
734	let mut scale = one();
735	if par.degree() == 0 || n < params.recursion_threshold {
736		let mut i = n;
737		loop {
738			if i == 0 {
739				break;
740			}
741			i -= 1;
742			if i == 0 || A[(i, i - 1)] == zero() {
743				let dot = linalg::matmul::dot::inner_prod(
744					A.row(i).subcols(i + 1, n - i - 1),
745					Conj::No,
746					x.rb().subrows(i + 1, n - i - 1),
747					Conj::No,
748				);
749				x[i] -= dot;
750				let mut z = &A[(i, i)] - p;
751				if z.abs() < eps * norm {
752					z = eps * norm;
753				}
754				if x[i] != zero() {
755					x[i] /= z;
756				}
757				let m = x[i].abs();
758				if m > big {
759					let r = m.recip();
760					for j in 0..x.nrows() {
761						x[j] *= r.copy();
762					}
763					scale *= r;
764				}
765			} else {
766				let (dot0, dot1) = dot2x1(
767					A.row(i - 1).subcols(i + 1, n - i - 1),
768					A.row(i).subcols(i + 1, n - i - 1),
769					x.rb().subrows(i + 1, n - i - 1),
770				);
771				x[i - 1] -= dot0;
772				x[i] -= dot1;
773				let ref a = &A[(i, i)] - p;
774				let ref b = A[(i - 1, i)];
775				let ref c = A[(i, i - 1)];
776				let ref r0 = x[i - 1];
777				let ref r1 = x[i];
778				let ref inv_det = (a.abs2() - b * c).recip();
779				(x[i - 1], x[i]) =
780					((a * r0 - b * r1) * inv_det, (a * r1 - c * r0) * inv_det);
781				let a0 = x[i - 1].abs();
782				let a1 = x[i].abs();
783				let m = if a0 > a1 { a0 } else { a1 };
784				if m > big {
785					let r = m.recip();
786					for j in 0..x.nrows() {
787						x[j] *= r.copy();
788					}
789					scale *= r;
790				}
791				i -= 1;
792			}
793		}
794		scale
795	} else {
796		let mut mid = n / 2;
797		if A[(mid, mid - 1)] != zero() {
798			mid -= 1;
799		}
800		let (A00, A01, _, A11) = A.split_at(mid, mid);
801		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
802		let f1 = solve_real_shifted_upper_quasi_triangular_system(
803			A11,
804			p.copy(),
805			x1.rb_mut(),
806			norm.copy(),
807			par,
808			params,
809		);
810		// The right block may have been rescaled; the left block's pending
811		// right-hand side must carry the same factor for the coupled system to
812		// stay consistent.
813		for j in 0..x0.nrows() {
814			x0[j] *= f1.copy();
815		}
816		linalg::matmul::matmul(
817			x0.rb_mut().as_mat_mut(),
818			Accum::Add,
819			A01,
820			x1.rb().as_mat(),
821			-one(),
822			par,
823		);
824		let f0 = solve_real_shifted_upper_quasi_triangular_system(
825			A00,
826			p.copy(),
827			x0.rb_mut(),
828			norm.copy(),
829			par,
830			params,
831		);
832		for j in 0..x1.nrows() {
833			x1[j] *= f0.copy();
834		}
835		f0 * f1
836	}
837}
838fn solve_cplx_shifted_upper_quasi_triangular_system<T: RealField>(
839	A: MatRef<'_, T>,
840	p: T,
841	q: T,
842	x: MatMut<'_, T>,
843	norm: T,
844	par: Par,
845	params: EvdFromSchurParams,
846) -> T {
847	let n = A.nrows();
848	let one = one::<T>;
849	let zero = zero::<T>;
850	let ref eps = eps::<T>();
851	let ref norm = norm;
852	let ref p = p;
853	let ref q = q;
854	let mut x = x;
855	let big = sqrt_max_positive::<T>();
856	let mut scale = one();
857	if par.degree() == 0 || n < params.recursion_threshold {
858		let mut i = n;
859		loop {
860			if i == 0 {
861				break;
862			}
863			i -= 1;
864			if i == 0 || A[(i, i - 1)] == zero() {
865				let (re, im) = dot2x1(
866					x.rb().subrows(i + 1, n - i - 1).col(0).transpose(),
867					x.rb().subrows(i + 1, n - i - 1).col(1).transpose(),
868					A.row(i).subcols(i + 1, n - i - 1).transpose(),
869				);
870				x[(i, 0)] = &x[(i, 0)] - re;
871				x[(i, 1)] = &x[(i, 1)] - im;
872				let mut z_re = &A[(i, i)] - p;
873				let mut z_im = -q;
874				let mut z = z_re.hypot(&z_im);
875				if z < eps * norm {
876					z_re = eps * norm;
877					z_im = zero();
878					z = z_re.copy();
879				}
880				let ref z = z;
881				let ref z_re = (z_re / z) / z;
882				let ref z_im = (-z_im / z) / z;
883				let ref x_re = x[(i, 0)].copy();
884				let ref x_im = x[(i, 1)].copy();
885				x[(i, 0)] = x_re * z_re - x_im * z_im;
886				x[(i, 1)] = x_re * z_im + x_im * z_re;
887				let a0 = x[(i, 0)].abs();
888				let a1 = x[(i, 1)].abs();
889				let m = if a0 > a1 { a0 } else { a1 };
890				if m > big {
891					let r = m.recip();
892					for j in 0..x.nrows() {
893						x[(j, 0)] *= r.copy();
894						x[(j, 1)] *= r.copy();
895					}
896					scale *= r;
897				}
898			} else {
899				let (re0, re1, im0, im1) = dot2x2(
900					A.row(i - 1).subcols(i + 1, n - i - 1),
901					A.row(i).subcols(i + 1, n - i - 1),
902					x.rb().col(0).subrows(i + 1, n - i - 1),
903					x.rb().col(1).subrows(i + 1, n - i - 1),
904				);
905				x[(i - 1, 0)] = &x[(i - 1, 0)] - re0;
906				x[(i - 1, 1)] = &x[(i - 1, 1)] - im0;
907				x[(i, 0)] = &x[(i, 0)] - re1;
908				x[(i, 1)] = &x[(i, 1)] - im1;
909				let ref a_re = &A[(i, i)] - p;
910				let ref a_im = -q;
911				let ref b = A[(i - 1, i)];
912				let ref c = A[(i, i - 1)];
913				let ref r0_re = x[(i - 1, 0)];
914				let ref r0_im = x[(i - 1, 1)];
915				let ref r1_re = x[(i, 0)];
916				let ref r1_im = x[(i, 1)];
917				let mut z_re = a_re.abs2() - a_im.abs2() - b * c;
918				let mut z_im = (a_re * a_im).mul_pow2(from_f64::<T>(2.0));
919				let mut z = z_re.hypot(&z_im);
920				if z < eps * norm {
921					z_re = eps * norm;
922					z_im = zero();
923					z = z_re.copy();
924				}
925				let ref z = z;
926				let ref z_re = (z_re / z) / z;
927				let ref z_im = (-z_im / z) / z;
928				let ref x0_re = (a_re * r0_re - a_im * r0_im) - b * r1_re;
929				let ref x0_im = (a_re * r0_im + a_im * r0_re) - b * r1_im;
930				let ref x1_re = (a_re * r1_re - a_im * r1_im) - c * r0_re;
931				let ref x1_im = (a_re * r1_im + a_im * r1_re) - c * r0_im;
932				x[(i - 1, 0)] = x0_re * z_re - x0_im * z_im;
933				x[(i - 1, 1)] = x0_re * z_im + x0_im * z_re;
934				x[(i, 0)] = x1_re * z_re - x1_im * z_im;
935				x[(i, 1)] = x1_re * z_im + x1_im * z_re;
936				let mut m = x[(i - 1, 0)].abs();
937				let v = x[(i - 1, 1)].abs();
938				if v > m {
939					m = v;
940				}
941				let v = x[(i, 0)].abs();
942				if v > m {
943					m = v;
944				}
945				let v = x[(i, 1)].abs();
946				if v > m {
947					m = v;
948				}
949				if m > big {
950					let r = m.recip();
951					for j in 0..x.nrows() {
952						x[(j, 0)] *= r.copy();
953						x[(j, 1)] *= r.copy();
954					}
955					scale *= r;
956				}
957				i -= 1;
958			}
959		}
960		scale
961	} else {
962		let mut mid = n / 2;
963		if A[(mid, mid - 1)] != zero() {
964			mid -= 1;
965		}
966		let (A00, A01, _, A11) = A.split_at(mid, mid);
967		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
968		let f1 = solve_cplx_shifted_upper_quasi_triangular_system(
969			A11,
970			p.copy(),
971			q.copy(),
972			x1.rb_mut(),
973			norm.copy(),
974			par,
975			params,
976		);
977		for j in 0..x0.nrows() {
978			x0[(j, 0)] *= f1.copy();
979			x0[(j, 1)] *= f1.copy();
980		}
981		linalg::matmul::matmul(
982			x0.rb_mut(),
983			Accum::Add,
984			A01,
985			x1.rb(),
986			-one(),
987			par,
988		);
989		let f0 = solve_cplx_shifted_upper_quasi_triangular_system(
990			A00,
991			p.copy(),
992			q.copy(),
993			x0.rb_mut(),
994			norm.copy(),
995			par,
996			params,
997		);
998		for j in 0..x1.nrows() {
999			x1[(j, 0)] *= f0.copy();
1000			x1[(j, 1)] *= f0.copy();
1001		}
1002		f0 * f1
1003	}
1004}
1005fn solve_shifted_upper_triangular_system<T: ComplexField>(
1006	A: MatRef<'_, T>,
1007	conj_A: Conj,
1008	p: T,
1009	x: ColMut<'_, T>,
1010	norm: T::Real,
1011	par: Par,
1012	params: EvdFromSchurParams,
1013) -> T::Real {
1014	let n = A.nrows();
1015	let mut scale = one::<T::Real>();
1016	let one = one::<T>;
1017	let zero = zero::<T>;
1018	let ref eps = eps::<T::Real>();
1019	let ref norm = norm;
1020	let ref p = p;
1021	let mut x = x;
1022	let big = sqrt_max_positive::<T::Real>();
1023	if par.degree() == 0 || n < params.recursion_threshold {
1024		let mut i = n;
1025		loop {
1026			if i == 0 {
1027				break;
1028			}
1029			i -= 1;
1030			let dot = linalg::matmul::dot::inner_prod(
1031				A.row(i).subcols(i + 1, n - i - 1),
1032				conj_A,
1033				x.rb().subrows(i + 1, n - i - 1),
1034				Conj::No,
1035			);
1036			x[i] -= dot;
1037			let mut z = if conj_A == Conj::Yes {
1038				A[(i, i)].conj()
1039			} else {
1040				A[(i, i)].copy()
1041			} - p;
1042			if z.abs() < eps * norm {
1043				z = (eps * norm).to_cplx();
1044			}
1045			if x[i] != zero() {
1046				x[i] *= z.recip();
1047			}
1048			let m = x[i].abs();
1049			if m > big {
1050				let r = m.recip();
1051				let rc: T = r.to_cplx();
1052				for j in 0..x.nrows() {
1053					x[j] *= rc.copy();
1054				}
1055				scale *= r;
1056			}
1057		}
1058		scale
1059	} else {
1060		let mid = n / 2;
1061		let (A00, A01, _, A11) = A.split_at(mid, mid);
1062		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
1063		let f1 = solve_shifted_upper_triangular_system(
1064			A11,
1065			conj_A,
1066			p.copy(),
1067			x1.rb_mut(),
1068			norm.copy(),
1069			par,
1070			params,
1071		);
1072		let f1c: T = f1.to_cplx();
1073		for j in 0..x0.nrows() {
1074			x0[j] *= f1c.copy();
1075		}
1076		linalg::matmul::matmul_with_conj(
1077			x0.rb_mut().as_mat_mut(),
1078			Accum::Add,
1079			A01,
1080			conj_A,
1081			x1.rb().as_mat(),
1082			Conj::No,
1083			-one(),
1084			par,
1085		);
1086		let f0 = solve_shifted_upper_triangular_system(
1087			A00,
1088			conj_A,
1089			p.copy(),
1090			x0.rb_mut(),
1091			norm.copy(),
1092			par,
1093			params,
1094		);
1095		let f0c: T = f0.to_cplx();
1096		for j in 0..x1.nrows() {
1097			x1[j] *= f0c.copy();
1098		}
1099		f0 * f1
1100	}
1101}
1102/// computes the layout of the workspace required to compute a matrix's
1103/// eigendecomposition
1104pub fn evd_scratch<T: ComplexField>(
1105	dim: usize,
1106	eigen_left: ComputeEigenvectors,
1107	eigen_right: ComputeEigenvectors,
1108	par: Par,
1109	params: Spec<EvdParams, T>,
1110) -> StackReq {
1111	let n = dim;
1112	if n == 0 {
1113		return StackReq::EMPTY;
1114	}
1115	let compute_eigen = eigen_left == ComputeEigenvectors::Yes
1116		|| eigen_right == ComputeEigenvectors::Yes;
1117	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
1118		n - 1,
1119		n - 1,
1120	);
1121	let H = temp_mat_scratch::<T>(n, n);
1122	let X = H;
1123	let Z = temp_mat_scratch::<T>(n, if compute_eigen { n } else { 0 });
1124	let householder = temp_mat_scratch::<T>(bs, n);
1125	let apply = linalg::householder::apply_block_householder_sequence_on_the_right_in_place_scratch::<T>(n - 1, bs, n - 1);
1126	StackReq::all_of(&[
1127		H,
1128		Z,
1129		StackReq::any_of(&[
1130			householder.and(
1131				hessenberg::hessenberg_in_place_scratch::<T>(
1132					n,
1133					bs,
1134					par,
1135					params.hessenberg.into(),
1136				)
1137				.or(apply),
1138			),
1139			schur::multishift_qr_scratch::<T>(
1140				n,
1141				n,
1142				compute_eigen,
1143				compute_eigen,
1144				par,
1145				params.schur,
1146			),
1147			X,
1148		]),
1149	])
1150}
1151fn evd_imp<T: ComplexField>(
1152	A: MatRef<'_, T>,
1153	s: ColMut<'_, T>,
1154	s_im: Option<ColMut<'_, T>>,
1155	u_left: Option<MatMut<'_, T>>,
1156	u_right: Option<MatMut<'_, T>>,
1157	par: Par,
1158	stack: &mut MemStack,
1159	params: EvdParams,
1160) -> Result<(), EvdError> {
1161	let n = A.nrows();
1162	if n == 0 {
1163		return Ok(());
1164	}
1165	for j in 0..n {
1166		for i in 0..n {
1167			if !A[(i, j)].is_finite() {
1168				return Err(EvdError::NoConvergence);
1169			}
1170		}
1171	}
1172	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
1173		n - 1,
1174		n - 1,
1175	);
1176	let mut s = s;
1177	let mut s_im = s_im;
1178	let (mut H, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1179	let (mut Z, stack) = unsafe {
1180		temp_mat_uninit::<T, _, _>(
1181			n,
1182			if u_left.is_some() || u_right.is_some() {
1183				n
1184			} else {
1185				0
1186			},
1187			stack,
1188		)
1189	};
1190	let mut H = H.as_mat_mut();
1191	let mut Z = if u_left.is_some() || u_right.is_some() {
1192		Some(Z.as_mat_mut())
1193	} else {
1194		None
1195	};
1196	H.copy_from(A);
1197	{
1198		let (mut householder, stack) =
1199			unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
1200		let mut householder = householder.as_mat_mut();
1201		hessenberg::hessenberg_in_place(
1202			H.rb_mut(),
1203			householder.rb_mut(),
1204			par,
1205			stack,
1206			params.hessenberg.into(),
1207		);
1208		if let Some(mut Z) = Z.rb_mut() {
1209			Z.fill(zero());
1210			Z.rb_mut().diagonal_mut().fill(one());
1211			linalg::householder::apply_block_householder_sequence_on_the_right_in_place_with_conj(
1212				H.rb().submatrix(1, 0, n - 1, n - 1),
1213				householder.rb(),
1214				Conj::No,
1215				Z.rb_mut().submatrix_mut(1, 1, n - 1, n - 1),
1216				par,
1217				stack,
1218			);
1219		}
1220		for j in 0..n {
1221			for i in j + 2..n {
1222				H[(i, j)] = zero();
1223			}
1224		}
1225	}
1226	if const { T::IS_REAL } {
1227		let (info, _, _) = schur::real_schur::multishift_qr::<T::Real>(
1228			unsafe { core::mem::transmute(Z.is_some()) },
1229			unsafe { core::mem::transmute(H.rb_mut()) },
1230			unsafe { core::mem::transmute(Z.rb_mut()) },
1231			unsafe { core::mem::transmute(s.rb_mut()) },
1232			unsafe { core::mem::transmute(s_im.rb_mut().unwrap()) },
1233			0,
1234			n,
1235			par,
1236			stack,
1237			params.schur,
1238		);
1239		if info != 0 {
1240			return Err(EvdError::NoConvergence);
1241		}
1242	} else {
1243		let (info, _, _) = schur::complex_schur::multishift_qr::<T>(
1244			Z.is_some(),
1245			H.rb_mut(),
1246			Z.rb_mut(),
1247			s.rb_mut(),
1248			0,
1249			n,
1250			par,
1251			stack,
1252			params.schur,
1253		);
1254		if info != 0 {
1255			return Err(EvdError::NoConvergence);
1256		}
1257	}
1258	let H = H.rb();
1259	if let (Some(mut u), Some(Z)) = (u_right, Z.rb()) {
1260		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1261		let mut X = X.as_mat_mut();
1262		if const { T::IS_REAL } {
1263			evd_from_real_schur_imp::<T::Real>(
1264				unsafe { core::mem::transmute(H) },
1265				unsafe { core::mem::transmute(X.rb_mut()) },
1266				par,
1267				params.evd_from_schur,
1268			);
1269		} else {
1270			evd_from_cplx_schur_imp::<T>(
1271				H,
1272				Conj::No,
1273				X.rb_mut(),
1274				par,
1275				params.evd_from_schur,
1276			);
1277		}
1278		linalg::matmul::triangular::matmul(
1279			u.rb_mut(),
1280			BlockStructure::Rectangular,
1281			Accum::Replace,
1282			Z,
1283			BlockStructure::Rectangular,
1284			X.rb(),
1285			BlockStructure::TriangularUpper,
1286			one(),
1287			par,
1288		);
1289		normalize_eigenvectors(u.rb_mut(), H);
1290		if !eigenvectors_ok(u.rb()) {
1291			return Err(EvdError::NoConvergence);
1292		}
1293	}
1294	if let (Some(mut u), Some(Z)) = (u_left, Z.rb()) {
1295		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1296		let mut X = X.as_mat_mut().reverse_rows_mut();
1297		if const { T::IS_REAL } {
1298			evd_from_real_schur_imp::<T::Real>(
1299				unsafe {
1300					core::mem::transmute(H.transpose().reverse_rows_and_cols())
1301				},
1302				unsafe { core::mem::transmute(X.rb_mut()) },
1303				par,
1304				params.evd_from_schur,
1305			);
1306		} else {
1307			evd_from_cplx_schur_imp::<T>(
1308				H.transpose().reverse_rows_and_cols(),
1309				Conj::Yes,
1310				X.rb_mut(),
1311				par,
1312				params.evd_from_schur,
1313			);
1314		}
1315		linalg::matmul::triangular::matmul(
1316			u.rb_mut(),
1317			BlockStructure::Rectangular,
1318			Accum::Replace,
1319			Z,
1320			BlockStructure::Rectangular,
1321			X.rb().reverse_rows_and_cols(),
1322			BlockStructure::TriangularLower,
1323			one(),
1324			par,
1325		);
1326		normalize_eigenvectors(u.rb_mut(), H);
1327		if !eigenvectors_ok(u.rb()) {
1328			return Err(EvdError::NoConvergence);
1329		}
1330	}
1331	Ok(())
1332}
1333/// computes the matrix $A$'s eigendecomposition
1334///
1335/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1336/// right eigenvectors in $U_R$.
1337///
1338/// every eigenvector is scaled to unit 2-norm. if the Schur iteration does not
1339/// converge, or the back substitution would overflow for a finite input,
1340/// [`EvdError::NoConvergence`] is returned instead of nonfinite entries.
1341#[track_caller]
1342pub fn evd_cplx<T: RealField>(
1343	A: MatRef<'_, Complex<T>>,
1344	s: DiagMut<'_, Complex<T>>,
1345	u_left: Option<MatMut<'_, Complex<T>>>,
1346	u_right: Option<MatMut<'_, Complex<T>>>,
1347	par: Par,
1348	stack: &mut MemStack,
1349	params: Spec<EvdParams, Complex<T>>,
1350) -> Result<(), EvdError> {
1351	let n = A.nrows();
1352	assert!(all(A.nrows() == n, A.ncols() == n, s.dim() == n));
1353	if let Some(u) = u_left.rb() {
1354		assert!(all(u.nrows() == n, u.ncols() == n));
1355	}
1356	if let Some(u) = u_right.rb() {
1357		assert!(all(u.nrows() == n, u.ncols() == n));
1358	}
1359	evd_imp(
1360		A,
1361		s.column_vector_mut(),
1362		None,
1363		u_left,
1364		u_right,
1365		par,
1366		stack,
1367		params.config,
1368	)
1369}
1370/// computes the matrix $A$'s eigendecomposition
1371///
1372/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1373/// right eigenvectors in $U_R$.
1374///
1375/// every eigenvector is scaled to unit 2-norm: a real eigenvalue gets a unit
1376/// norm column, and the two real columns `re` and `im` of a conjugate pair are
1377/// scaled by a common factor so that `re + i * im` has unit 2-norm (the same
1378/// contract `xGEEV` provides). if the Schur iteration does not converge, or the
1379/// back substitution would overflow for a finite input,
1380/// [`EvdError::NoConvergence`] is returned instead of nonfinite entries.
1381#[track_caller]
1382pub fn evd_real<T: RealField>(
1383	A: MatRef<'_, T>,
1384	s_re: DiagMut<'_, T>,
1385	s_im: DiagMut<'_, T>,
1386	u_left: Option<MatMut<'_, T>>,
1387	u_right: Option<MatMut<'_, T>>,
1388	par: Par,
1389	stack: &mut MemStack,
1390	params: Spec<EvdParams, T>,
1391) -> Result<(), EvdError> {
1392	let n = A.nrows();
1393	assert!(all(
1394		A.nrows() == n,
1395		A.ncols() == n,
1396		s_re.dim() == n,
1397		s_im.dim() == n
1398	));
1399	if let Some(u) = u_left.rb() {
1400		assert!(all(u.nrows() == n, u.ncols() == n));
1401	}
1402	if let Some(u) = u_right.rb() {
1403		assert!(all(u.nrows() == n, u.ncols() == n));
1404	}
1405	evd_imp(
1406		A,
1407		s_re.column_vector_mut(),
1408		Some(s_im.column_vector_mut()),
1409		u_left,
1410		u_right,
1411		par,
1412		stack,
1413		params.config,
1414	)
1415}
1416#[cfg(test)]
1417mod general_tests {
1418	use super::*;
1419	use crate::assert;
1420	use crate::stats::prelude::*;
1421	use crate::utils::approx::*;
1422	use dyn_stack::MemBuffer;
1423	fn test_cplx_evd(mat: MatRef<'_, c64>) {
1424		let n = mat.nrows();
1425		let params = Spec::new(EvdParams {
1426			hessenberg: auto!(c64),
1427			schur: auto!(c64),
1428			evd_from_schur: EvdFromSchurParams {
1429				recursion_threshold: 8,
1430				..auto!(c64)
1431			},
1432			..auto!(c64)
1433		});
1434		let approx_eq = CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
1435		let mut s = Mat::zeros(n, n);
1436		{
1437			let mut ul = Mat::zeros(n, n);
1438			let mut ur = Mat::zeros(n, n);
1439			evd_cplx(
1440				mat.as_ref(),
1441				s.as_mut().diagonal_mut(),
1442				Some(ul.as_mut()),
1443				Some(ur.as_mut()),
1444				Par::Seq,
1445				MemStack::new(&mut MemBuffer::new(evd_scratch::<c64>(
1446					n,
1447					ComputeEigenvectors::Yes,
1448					ComputeEigenvectors::Yes,
1449					Par::Seq,
1450					params,
1451				))),
1452				params,
1453			)
1454			.unwrap();
1455			assert!(& ur * & s ~ mat * & ur);
1456			assert!(& s * ul.adjoint() ~ ul.adjoint() * mat);
1457		}
1458	}
1459	fn test_real_evd(mat: MatRef<'_, f64>) {
1460		let n = mat.nrows();
1461		let params = Spec::new(EvdParams {
1462			hessenberg: auto!(f64),
1463			schur: auto!(f64),
1464			evd_from_schur: EvdFromSchurParams {
1465				recursion_threshold: 8,
1466				..auto!(f64)
1467			},
1468			..auto!(f64)
1469		});
1470		let approx_eq =
1471			CwiseMat(ApproxEq::<f64>::eps() * (8.0 * n as f64).sqrt());
1472		let mut s_re = Diag::zeros(n);
1473		let mut s_im = Diag::zeros(n);
1474		{
1475			let mut ul = Mat::zeros(n, n);
1476			let mut ur = Mat::zeros(n, n);
1477			evd_real(
1478				mat.as_ref(),
1479				s_re.as_mut(),
1480				s_im.as_mut(),
1481				Some(ul.as_mut()),
1482				Some(ur.as_mut()),
1483				Par::Seq,
1484				MemStack::new(&mut MemBuffer::new(evd_scratch::<f64>(
1485					n,
1486					ComputeEigenvectors::Yes,
1487					ComputeEigenvectors::Yes,
1488					Par::Seq,
1489					params,
1490				))),
1491				params,
1492			)
1493			.unwrap();
1494			let mut i = 0;
1495			while i < n {
1496				if s_im[i] == 0.0 {
1497					let ur = ur.col(i);
1498					let ul = ul.col(i);
1499					let s = Scale(s_re[i]);
1500					assert!((& ur * s).as_mat() ~ (mat * & ur).as_mat());
1501					assert!(
1502						(& ul.adjoint() * s).as_mat() ~ (& ul.adjoint() * mat).as_mat()
1503					);
1504					i += 1;
1505				} else {
1506					let re = ur.col(i);
1507					let im = ur.col(i + 1);
1508					let ur = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1509					let re = ul.col(i);
1510					let im = ul.col(i + 1);
1511					let ul = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1512					let mat =
1513						&Mat::from_fn(n, n, |i, j| c64::from(mat[(i, j)]));
1514					let s = Scale(c64::new(s_re[i], s_im[i]));
1515					let approx_eq =
1516						CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
1517					assert!((ur * s).as_mat() ~ (mat * ur).as_mat());
1518					assert!((ul.adjoint() * s).as_mat() ~ (ul.adjoint() * mat).as_mat());
1519					i += 2;
1520				}
1521			}
1522		}
1523	}
1524	#[test]
1525	fn test_cplx() {
1526		let rng = &mut StdRng::seed_from_u64(1);
1527		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1528			let mat = CwiseMatDistribution {
1529				nrows: n,
1530				ncols: n,
1531				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1532			}
1533			.rand::<Mat<c64>>(rng);
1534			test_cplx_evd(mat.as_ref());
1535		}
1536	}
1537	#[test]
1538	fn test_real() {
1539		let rng = &mut StdRng::seed_from_u64(0);
1540		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1541			let mat = CwiseMatDistribution {
1542				nrows: n,
1543				ncols: n,
1544				dist: StandardNormal,
1545			}
1546			.rand::<Mat<f64>>(rng);
1547			test_real_evd(mat.as_ref());
1548		}
1549	}
1550}
1551#[cfg(test)]
1552mod eig_2131_tests {
1553	//! Regression tests for `tensor4all/tenferro-rs#2131`: the faer
1554	//! eigendecomposition used to return eigenvectors that were neither
1555	//! normalized nor overflow-safe. On the Jordan block `2 I + N` the back
1556	//! substitution grew by `1 / (eps * norm)` per row, overflowing to `NaN`
1557	//! for finite input (`f32` at `n >= 8`, `f64` at `n = 26`).
1558	use super::*;
1559	use crate::assert;
1560	use dyn_stack::MemBuffer;
1561
1562	/// `||A v - lambda v|| / (||A||_1 * ||v||)`, where `||A||_1` is the maximum
1563	/// absolute column sum.
1564	fn relative_residual<T: RealField>(
1565		mat: MatRef<'_, T>,
1566		v: ColRef<'_, T>,
1567		lambda: T,
1568	) -> T {
1569		let n = mat.nrows();
1570		let mut anorm = zero::<T>();
1571		for j in 0..n {
1572			let mut col = zero::<T>();
1573			for i in 0..n {
1574				col += mat[(i, j)].abs1();
1575			}
1576			if col > anorm {
1577				anorm = col;
1578			}
1579		}
1580		let mut vnorm2 = zero::<T>();
1581		let mut rnorm2 = zero::<T>();
1582		for i in 0..n {
1583			let mut acc = zero::<T>();
1584			for j in 0..n {
1585				acc += &mat[(i, j)] * &v[j];
1586			}
1587			acc -= &v[i] * &lambda;
1588			rnorm2 += acc.abs2();
1589			vnorm2 += v[i].abs2();
1590		}
1591		rnorm2.sqrt() / (anorm * vnorm2.sqrt())
1592	}
1593
1594	fn evd_real_vectors<T: RealField>(
1595		mat: MatRef<'_, T>,
1596		params: Spec<EvdParams, T>,
1597	) -> (Diag<T>, Diag<T>, Mat<T>, Mat<T>) {
1598		let n = mat.nrows();
1599		let mut s_re = Diag::zeros(n);
1600		let mut s_im = Diag::zeros(n);
1601		let mut u = Mat::zeros(n, n);
1602		let mut ul = Mat::zeros(n, n);
1603		let mut mem = MemBuffer::new(evd_scratch::<T>(
1604			n,
1605			ComputeEigenvectors::Yes,
1606			ComputeEigenvectors::Yes,
1607			Par::Seq,
1608			params,
1609		));
1610		evd_real(
1611			mat,
1612			s_re.as_mut(),
1613			s_im.as_mut(),
1614			Some(ul.as_mut()),
1615			Some(u.as_mut()),
1616			Par::Seq,
1617			MemStack::new(&mut mem),
1618			params,
1619		)
1620		.unwrap();
1621		(s_re, s_im, u, ul)
1622	}
1623
1624	/// The Jordan block `2 I + N` with ones on the superdiagonal.
1625	fn jordan<T: RealField>(n: usize) -> Mat<T> {
1626		let two = from_f64::<T>(2.0);
1627		let one = one::<T>();
1628		Mat::<T>::from_fn(n, n, |i, j| {
1629			if i == j {
1630				two.copy()
1631			} else if j == i + 1 {
1632				one.copy()
1633			} else {
1634				zero::<T>()
1635			}
1636		})
1637	}
1638
1639	fn check_jordan<T: RealField>(n: usize, threshold: usize, tol: f64) {
1640		let params = Spec::new(EvdParams {
1641			evd_from_schur: EvdFromSchurParams {
1642				recursion_threshold: threshold,
1643				..auto!(T)
1644			},
1645			..auto!(T)
1646		});
1647		let mat = jordan::<T>(n);
1648		let (s_re, _, u, _) = evd_real_vectors(mat.as_ref(), params);
1649		let tol = from_f64::<T>(tol);
1650		for j in 0..n {
1651			let v = u.col(j);
1652			assert!(v.iter().all(|x| x.is_finite()));
1653			assert!((v.norm_l2() - one::<T>()).abs() < from_f64::<T>(1e-5));
1654			assert!(relative_residual(mat.as_ref(), v, s_re[j].copy()) < tol);
1655		}
1656	}
1657
1658	#[test]
1659	fn jordan_eigenvectors_are_finite_and_unit_norm() {
1660		// `threshold = 3` forces the recursive block solver; the default `64`
1661		// keeps small sizes sequential and exercises the recursion for the
1662		// larger ones. Both must produce finite, normalized, backward-stable
1663		// eigenvectors.
1664		for threshold in [3usize, 64] {
1665			for n in [2, 4, 8, 20, 26, 30, 64, 65, 100] {
1666				check_jordan::<f64>(n, threshold, 1e-10);
1667			}
1668			for n in [2, 4, 8, 16, 30] {
1669				check_jordan::<f32>(n, threshold, 1e-3);
1670			}
1671		}
1672	}
1673
1674	#[test]
1675	fn conjugate_pair_eigenvector_has_unit_norm() {
1676		// A real Schur form with four real eigenvalues in front of a 2x2
1677		// conjugate pair, so the complex-shift quasi-triangular solver gets a
1678		// prefix large enough for its recursive branch at `threshold = 4`.
1679		fn run<T: RealField>(threshold: usize) {
1680			let n = 6;
1681			let mat = Mat::<T>::from_fn(n, n, |i, j| {
1682				if i == j {
1683					if i >= 4 {
1684						zero::<T>()
1685					} else {
1686						from_f64::<T>((i + 1) as f64)
1687					}
1688				} else if i == 4 && j == 5 {
1689					one::<T>()
1690				} else if i == 5 && j == 4 {
1691					-one::<T>()
1692				} else if i + 2 <= j {
1693					from_f64::<T>(0.25)
1694				} else {
1695					zero::<T>()
1696				}
1697			});
1698			let params = Spec::new(EvdParams {
1699				evd_from_schur: EvdFromSchurParams {
1700					recursion_threshold: threshold,
1701					..auto!(T)
1702				},
1703				..auto!(T)
1704			});
1705			let (s_re, s_im, u, ul) = evd_real_vectors(mat.as_ref(), params);
1706			let tol = from_f64::<T>(1e-5);
1707			// Right and left eigenvectors share the same conjugate-pair scaling.
1708			let mut j = 0;
1709			while j < n {
1710				let v = u.col(j);
1711				let vl = ul.col(j);
1712				if s_im[j] == zero::<T>() {
1713					assert!((v.norm_l2() - one::<T>()).abs() < tol);
1714					assert!((vl.norm_l2() - one::<T>()).abs() < tol);
1715					assert!(
1716						relative_residual(mat.as_ref(), v, s_re[j].copy())
1717							< from_f64::<T>(1e-4)
1718					);
1719					j += 1;
1720				} else {
1721					let nrm = v.norm_l2().hypot(&u.col(j + 1).norm_l2());
1722					assert!((nrm - one::<T>()).abs() < tol);
1723					let nrm = vl.norm_l2().hypot(&ul.col(j + 1).norm_l2());
1724					assert!((nrm - one::<T>()).abs() < tol);
1725					j += 2;
1726				}
1727			}
1728		}
1729		for threshold in [4usize, 64] {
1730			run::<f64>(threshold);
1731			run::<f32>(threshold);
1732		}
1733	}
1734
1735	#[test]
1736	fn complex_eigenvectors_have_unit_norm() {
1737		// `threshold = 4` drives the complex solver's recursive branch.
1738		fn run<T: RealField>(threshold: usize) {
1739			let n = 6;
1740			let mat = Mat::<Complex<T>>::from_fn(n, n, |i, j| {
1741				Complex::new(
1742					from_f64::<T>(i as f64)
1743						+ from_f64::<T>(j as f64) * from_f64::<T>(0.1),
1744					from_f64::<T>(i as f64 - j as f64) * from_f64::<T>(0.3),
1745				)
1746			});
1747			let params = Spec::new(EvdParams {
1748				evd_from_schur: EvdFromSchurParams {
1749					recursion_threshold: threshold,
1750					..auto!(Complex<T>)
1751				},
1752				..auto!(Complex<T>)
1753			});
1754			let mut s = Diag::zeros(n);
1755			let mut u = Mat::zeros(n, n);
1756			let mut mem = MemBuffer::new(evd_scratch::<Complex<T>>(
1757				n,
1758				ComputeEigenvectors::No,
1759				ComputeEigenvectors::Yes,
1760				Par::Seq,
1761				params,
1762			));
1763			evd_cplx(
1764				mat.as_ref(),
1765				s.as_mut(),
1766				None,
1767				Some(u.as_mut()),
1768				Par::Seq,
1769				MemStack::new(&mut mem),
1770				params,
1771			)
1772			.unwrap();
1773			let tol = from_f64::<T>(1e-5);
1774			for j in 0..n {
1775				assert!((u.col(j).norm_l2() - one::<T>()).abs() < tol);
1776			}
1777		}
1778		for threshold in [4usize, 64] {
1779			run::<f64>(threshold);
1780			run::<f32>(threshold);
1781		}
1782	}
1783}
1784#[cfg(test)]
1785mod self_adjoint_tests {
1786	use super::*;
1787	use crate::assert;
1788	use crate::stats::prelude::*;
1789	use crate::utils::approx::*;
1790	use dyn_stack::MemBuffer;
1791	fn test_self_adjoint_evd<T: ComplexField>(mat: MatRef<'_, T>) {
1792		let n = mat.nrows();
1793		let params = Spec::new(SelfAdjointEvdParams {
1794			recursion_threshold: 8,
1795			..auto!(T)
1796		});
1797		let approx_eq = CwiseMat(
1798			ApproxEq::<T::Real>::eps()
1799				* from_f64::<T::Real>(8.0 * n as f64).sqrt(),
1800		);
1801		let mut s = Mat::zeros(n, n);
1802		{
1803			let mut u = Mat::zeros(n, n);
1804			self_adjoint_evd(
1805				mat.as_ref(),
1806				s.as_mut().diagonal_mut(),
1807				Some(u.as_mut()),
1808				Par::Seq,
1809				MemStack::new(&mut MemBuffer::new(
1810					self_adjoint_evd_scratch::<T>(
1811						n,
1812						ComputeEigenvectors::Yes,
1813						Par::Seq,
1814						params,
1815					),
1816				)),
1817				params,
1818			)
1819			.unwrap();
1820			let reconstructed = &u * &s * u.adjoint();
1821			assert!(reconstructed ~ mat);
1822		}
1823		{
1824			let mut s2 = Mat::zeros(n, n);
1825			self_adjoint_evd(
1826				mat.as_ref(),
1827				s2.as_mut().diagonal_mut(),
1828				None,
1829				Par::Seq,
1830				MemStack::new(&mut MemBuffer::new(
1831					self_adjoint_evd_scratch::<T>(
1832						n,
1833						ComputeEigenvectors::No,
1834						Par::Seq,
1835						params,
1836					),
1837				)),
1838				params,
1839			)
1840			.unwrap();
1841			assert!(s2 ~ s);
1842		}
1843	}
1844	#[test]
1845	fn test_real() {
1846		let rng = &mut StdRng::seed_from_u64(1);
1847		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1848			let mat = CwiseMatDistribution {
1849				nrows: n,
1850				ncols: n,
1851				dist: StandardNormal,
1852			}
1853			.rand::<Mat<f64>>(rng);
1854			let mat = &mat + mat.adjoint();
1855			test_self_adjoint_evd(mat.as_ref());
1856		}
1857	}
1858	#[test]
1859	fn test_cplx() {
1860		let rng = &mut StdRng::seed_from_u64(1);
1861		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1862			let mat = CwiseMatDistribution {
1863				nrows: n,
1864				ncols: n,
1865				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1866			}
1867			.rand::<Mat<c64>>(rng);
1868			let mat = &mat + mat.adjoint();
1869			test_self_adjoint_evd(mat.as_ref());
1870		}
1871	}
1872	#[test]
1873	fn test_special() {
1874		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1875			test_self_adjoint_evd(Mat::full(n, n, 0.0).as_ref());
1876			test_self_adjoint_evd(Mat::full(n, n, c64::ZERO).as_ref());
1877			test_self_adjoint_evd(Mat::full(n, n, 1.0).as_ref());
1878			test_self_adjoint_evd(Mat::full(n, n, c64::ONE).as_ref());
1879			test_self_adjoint_evd(Mat::<f64>::identity(n, n).as_ref());
1880			test_self_adjoint_evd(Mat::<c64>::identity(n, n).as_ref());
1881		}
1882	}
1883	#[test]
1884	fn test_pinv() {
1885		let rng = &mut StdRng::seed_from_u64(0);
1886		let n = 36;
1887		let mat = CwiseMatDistribution {
1888			nrows: n,
1889			ncols: n,
1890			dist: StandardNormal,
1891		}
1892		.rand::<Mat<f64>>(rng);
1893		let mat = &mat + mat.adjoint();
1894		let pinv = mat.self_adjoint_eigen(Side::Lower).unwrap().pseudoinverse();
1895		let err = &mat * &pinv - Mat::<f64>::identity(n, n);
1896		assert!(err.norm_max() < 1e-10);
1897	}
1898}