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
15mod balance;
16/// hessenberg decomposition
17pub mod hessenberg;
18#[doc(hidden)]
19pub mod schur;
20/// self-adjoint tridiagonalization
21pub mod tridiag;
22pub(crate) mod tridiag_evd;
23use crate::assert;
24use crate::internal_prelude::*;
25pub use balance::{
26	BalanceError, balance_eigenvectors_in_place, balance_eigenvectors_scratch,
27	balance_in_place, balance_scratch,
28};
29use hessenberg::HessenbergParams;
30use linalg::matmul::triangular::BlockStructure;
31pub use schur::SchurParams;
32use tridiag::TridiagParams;
33/// eigendecomposition error
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub enum EvdError {
36	/// reached max iterations
37	NoConvergence,
38}
39/// schur to eigendecomposition conversion parameters
40#[derive(Clone, Copy, Debug)]
41pub struct EvdFromSchurParams {
42	/// threshold at which the implementation should stop recursing
43	pub recursion_threshold: usize,
44	#[doc(hidden)]
45	pub non_exhaustive: NonExhaustive,
46}
47/// eigendecomposition tuning parameters
48#[derive(Clone, Copy, Debug)]
49pub struct EvdParams {
50	/// hessenberg parameters
51	pub hessenberg: HessenbergParams,
52	/// schur from hessenberg conversion parameters
53	pub schur: SchurParams,
54	/// eigendecomposition from schur conversion parameters
55	pub evd_from_schur: EvdFromSchurParams,
56	#[doc(hidden)]
57	pub non_exhaustive: NonExhaustive,
58}
59/// self-adjoint eigendecomposition tuning parameters
60#[derive(Clone, Copy, Debug)]
61pub struct SelfAdjointEvdParams {
62	/// tridiagonalization parameters
63	pub tridiag: TridiagParams,
64	/// threshold at which the implementation should stop recursing
65	pub recursion_threshold: usize,
66	#[doc(hidden)]
67	pub non_exhaustive: NonExhaustive,
68}
69impl<T: ComplexField> Auto<T> for EvdParams {
70	fn auto() -> Self {
71		Self {
72			hessenberg: auto!(T),
73			schur: auto!(T),
74			evd_from_schur: auto!(T),
75			non_exhaustive: NonExhaustive(()),
76		}
77	}
78}
79impl<T: ComplexField> Auto<T> for EvdFromSchurParams {
80	fn auto() -> Self {
81		Self {
82			recursion_threshold: 64,
83			non_exhaustive: NonExhaustive(()),
84		}
85	}
86}
87impl<T: ComplexField> Auto<T> for SelfAdjointEvdParams {
88	fn auto() -> Self {
89		Self {
90			tridiag: auto!(T),
91			recursion_threshold: 128,
92			non_exhaustive: NonExhaustive(()),
93		}
94	}
95}
96/// whether the eigenvectors should be computed
97#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98pub enum ComputeEigenvectors {
99	/// do not compute eigenvectors
100	No,
101	/// compute eigenvectors
102	Yes,
103}
104/// computes the layout of the workspace required to compute a self-adjoint
105/// matrix's eigendecomposition
106pub fn self_adjoint_evd_scratch<T: ComplexField>(
107	dim: usize,
108	compute_u: ComputeEigenvectors,
109	par: Par,
110	params: Spec<SelfAdjointEvdParams, T>,
111) -> StackReq {
112	let n = dim;
113	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
114	let prologue = StackReq::all_of(&[
115		temp_mat_scratch::<T>(n, n),
116		temp_mat_scratch::<T>(bs, n),
117		StackReq::any_of(&[tridiag::tridiag_in_place_scratch::<T>(
118			n,
119			par,
120			params.tridiag.into(),
121		)]),
122		temp_mat_scratch::<T::Real>(n, 1).array(2),
123	]);
124	if compute_u == ComputeEigenvectors::No {
125		return prologue;
126	}
127	StackReq::all_of(&[
128		prologue,
129		temp_mat_scratch::<T::Real>(
130			n,
131			if const {
132				T::IS_REAL
133			} {
134				0
135			} else {
136				n
137			},
138		)
139		.array(2),
140		StackReq::any_of(&[
141			if n < params.recursion_threshold {
142				StackReq::empty()
143			} else {
144				tridiag_evd::divide_and_conquer_scratch::<T>(n, par)
145			},
146			temp_mat_scratch::<T>(n, 1),
147			linalg::householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(
148				n.saturating_sub(1),
149				bs,
150				n,
151			),
152		]),
153	])
154}
155/// Workspace for [`tridiagonal_self_adjoint_evd`].
156///
157/// # Examples
158/// ```
159/// use faer::Par;
160/// use faer::linalg::evd::*;
161/// let _buffer =
162/// 	dyn_stack::MemBuffer::new(tridiagonal_self_adjoint_evd_scratch::<f64>(
163/// 		3,
164/// 		ComputeEigenvectors::Yes,
165/// 		Par::Seq,
166/// 		Default::default(),
167/// 	));
168/// ```
169pub fn tridiagonal_self_adjoint_evd_scratch<T: ComplexField>(
170	dim: usize,
171	compute_u: ComputeEigenvectors,
172	par: Par,
173	params: Spec<SelfAdjointEvdParams, T>,
174) -> StackReq {
175	let n = dim;
176	StackReq::all_of(&[
177		temp_mat_scratch::<T::Real>(n, 1).array(2),
178		if compute_u == ComputeEigenvectors::No {
179			StackReq::EMPTY
180		} else {
181			temp_mat_scratch::<T::Real>(n, if T::IS_REAL { 0 } else { n }).and(
182				temp_mat_scratch::<T>(n, 1).or(
183					if n < params.recursion_threshold {
184						StackReq::EMPTY
185					} else {
186						tridiag_evd::divide_and_conquer_scratch::<T::Real>(
187							n, par,
188						)
189					},
190				),
191			)
192		},
193	])
194}
195/// computes the matrix $A$'s eigendecomposition, assuming it is tridiagonal and
196/// self-adjoint
197///
198/// the eigenvalues are stored in $S$, and the eigenvectors in $U$ such that the
199/// eigenvalues are sorted in nondecreasing order
200pub fn tridiagonal_self_adjoint_evd<T: ComplexField>(
201	diag: DiagRef<'_, T>,
202	subdiag: DiagRef<'_, T>,
203	s: DiagMut<'_, T>,
204	u: Option<MatMut<'_, T>>,
205	par: Par,
206	stack: &mut MemStack,
207	params: Spec<SelfAdjointEvdParams, T>,
208) -> Result<(), EvdError> {
209	let n = diag.dim();
210	let (mut real_diag, stack) =
211		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
212	let (mut real_offdiag, stack) =
213		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
214	let mut real_diag = real_diag
215		.as_mat_mut()
216		.col_mut(0)
217		.try_as_col_major_mut()
218		.unwrap();
219	let mut real_offdiag = real_offdiag
220		.as_mat_mut()
221		.col_mut(0)
222		.try_as_col_major_mut()
223		.unwrap();
224	for i in 0..n {
225		real_diag[i] = diag[i].real();
226		if i + 1 < n {
227			if const { T::IS_REAL } {
228				real_offdiag[i] = subdiag[i].real();
229			} else {
230				real_offdiag[i] = subdiag[i].abs();
231			}
232		} else {
233			real_offdiag[i] = zero();
234		}
235	}
236	let mut s = s;
237	let mut u = match u {
238		Some(u) => u,
239		None => {
240			tridiag_evd::qr_algorithm(
241				real_diag.rb_mut(),
242				real_offdiag.rb_mut(),
243				None,
244			)?;
245			for i in 0..n {
246				s[i] = real_diag[i].to_cplx();
247			}
248			return Ok(());
249		},
250	};
251	let (mut u_real, stack) = unsafe {
252		temp_mat_uninit::<T::Real, _, _>(
253			n,
254			if T::IS_REAL { 0 } else { n },
255			stack,
256		)
257	};
258	let mut u_real = u_real.as_mat_mut();
259	let mut u_evd = if const { T::IS_REAL } {
260		unsafe { core::mem::transmute(u.rb_mut()) }
261	} else {
262		u_real.rb_mut()
263	};
264	if n < params.recursion_threshold {
265		tridiag_evd::qr_algorithm(
266			real_diag.rb_mut(),
267			real_offdiag.rb_mut(),
268			Some(u_evd.rb_mut()),
269		)?;
270	} else {
271		tridiag_evd::divide_and_conquer::<T::Real>(
272			real_diag.rb_mut(),
273			real_offdiag.rb_mut(),
274			u_evd.rb_mut(),
275			par,
276			stack,
277			params.recursion_threshold,
278		)?;
279	}
280	if const { !T::IS_REAL } {
281		let normalized = |x: T| {
282			if x == zero() {
283				one()
284			} else {
285				x.mul_real(x.abs().recip())
286			}
287		};
288		let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
289		let mut scale = scale
290			.as_mat_mut()
291			.col_mut(0)
292			.try_as_col_major_mut()
293			.unwrap();
294		let mut x = one::<T>();
295		scale[0] = one();
296		for i in 1..n {
297			x = normalized(&subdiag[i - 1] * x);
298			scale[i] = x.copy();
299		}
300		for j in 0..n {
301			z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(
302				|uz!(u, real, scale)| {
303					*u = scale.mul_real(real);
304				},
305			);
306		}
307	}
308	for i in 0..n {
309		s[i] = real_diag[i].to_cplx();
310	}
311	Ok(())
312}
313/// computes the matrix $A$'s eigendecomposition, assuming it is self-adjoint
314///
315/// the eigenvalues are stored in $S$, and the eigenvectors in $U$ such that the
316/// eigenvalues are sorted in nondecreasing order
317///
318/// only the lower triangular half of $A$ is accessed
319pub fn self_adjoint_evd<T: ComplexField>(
320	A: MatRef<'_, T>,
321	s: DiagMut<'_, T>,
322	u: Option<MatMut<'_, T>>,
323	par: Par,
324	stack: &mut MemStack,
325	params: Spec<SelfAdjointEvdParams, T>,
326) -> Result<(), EvdError> {
327	let n = A.nrows();
328	assert!(all(A.nrows() == A.ncols(), s.dim() == n));
329	if let Some(u) = u.rb() {
330		assert!(all(u.nrows() == n, u.ncols() == n));
331	}
332	let s = s.column_vector_mut();
333	if n == 0 {
334		return Ok(());
335	}
336	#[cfg(feature = "perf-warn")]
337	if let Some(matrix) = u.rb() {
338		if matrix.row_stride().unsigned_abs() != 1
339			&& crate::__perf_warn!(QR_WARN)
340		{
341			if matrix.col_stride().unsigned_abs() == 1 {
342				log::warn!(
343					target : "faer_perf",
344					"EVD prefers column-major eigenvector matrix. Found row-major matrix."
345				);
346			} else {
347				log::warn!(
348					target : "faer_perf",
349					"EVD prefers column-major eigenvector matrix. Found matrix with generic strides."
350				);
351			}
352		}
353	}
354	let (mut trid, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
355	let mut trid = trid.as_mat_mut();
356	trid.copy_from_triangular_lower(A);
357	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
358	let (mut householder, stack) =
359		unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
360	let mut householder = householder.as_mat_mut();
361	{
362		tridiag::tridiag_in_place(
363			trid.rb_mut(),
364			householder.rb_mut(),
365			par,
366			stack,
367			params.tridiag.into(),
368		);
369	}
370	let trid = trid.rb();
371	let (mut diag, stack) =
372		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
373	let (mut offdiag, stack) =
374		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
375	let mut diag = diag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
376	let mut offdiag = offdiag
377		.as_mat_mut()
378		.col_mut(0)
379		.try_as_col_major_mut()
380		.unwrap();
381	for i in 0..n {
382		diag[i] = trid[(i, i)].real();
383		if i + 1 < n {
384			if const { T::IS_REAL } {
385				offdiag[i] = trid[(i + 1, i)].real();
386			} else {
387				offdiag[i] = trid[(i + 1, i)].abs();
388			}
389		} else {
390			offdiag[i] = zero();
391		}
392	}
393	let mut s = s;
394	let mut u = match u {
395		Some(u) => u,
396		None => {
397			tridiag_evd::qr_algorithm(diag.rb_mut(), offdiag.rb_mut(), None)?;
398			for i in 0..n {
399				s[i] = diag[i].to_cplx();
400			}
401			return Ok(());
402		},
403	};
404	let (mut u_real, stack) = unsafe {
405		temp_mat_uninit::<T::Real, _, _>(
406			n,
407			if T::IS_REAL { 0 } else { n },
408			stack,
409		)
410	};
411	let mut u_real = u_real.as_mat_mut();
412	let mut u_evd = if const { T::IS_REAL } {
413		unsafe { core::mem::transmute(u.rb_mut()) }
414	} else {
415		u_real.rb_mut()
416	};
417	if n < params.recursion_threshold {
418		tridiag_evd::qr_algorithm(
419			diag.rb_mut(),
420			offdiag.rb_mut(),
421			Some(u_evd.rb_mut()),
422		)?;
423	} else {
424		tridiag_evd::divide_and_conquer::<T::Real>(
425			diag.rb_mut(),
426			offdiag.rb_mut(),
427			u_evd.rb_mut(),
428			par,
429			stack,
430			params.recursion_threshold,
431		)?;
432	}
433	if const { !T::IS_REAL } {
434		let normalized = |x: T| {
435			if x == zero() {
436				one()
437			} else {
438				x.mul_real(x.abs().recip())
439			}
440		};
441		let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
442		let mut scale = scale
443			.as_mat_mut()
444			.col_mut(0)
445			.try_as_col_major_mut()
446			.unwrap();
447		let mut x = one::<T>();
448		scale[0] = one();
449		for i in 1..n {
450			x = normalized(&trid[(i, i - 1)] * x);
451			scale[i] = x.copy();
452		}
453		for j in 0..n {
454			z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(
455				|uz!(u, real, scale)| {
456					*u = scale.mul_real(real);
457				},
458			);
459		}
460	}
461	linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
462		trid.submatrix(1, 0, n - 1, n - 1),
463		householder.rb(),
464		Conj::No,
465		u.rb_mut().subrows_mut(1, n - 1),
466		par,
467		stack,
468	);
469	for i in 0..n {
470		s[i] = diag[i].to_cplx();
471	}
472	Ok(())
473}
474/// computes the layout of the workspace required to compute a self-adjoint
475/// matrix's pseudoinverse, given the eigendecomposition
476pub fn pseudoinverse_from_self_adjoint_evd_scratch<T: ComplexField>(
477	dim: usize,
478	par: Par,
479) -> StackReq {
480	_ = par;
481	temp_mat_scratch::<T>(dim, dim).array(2)
482}
483/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition
484/// factors $S$ and $U$
485#[track_caller]
486pub fn pseudoinverse_from_self_adjoint_evd<T: ComplexField>(
487	pinv: MatMut<'_, T>,
488	s: DiagRef<'_, T>,
489	u: MatRef<'_, T>,
490	par: Par,
491	stack: &mut MemStack,
492) {
493	pseudoinverse_from_self_adjoint_evd_with_tolerance(
494		pinv,
495		s,
496		u,
497		zero(),
498		eps::<T::Real>() * from_f64::<T::Real>(u.ncols() as f64),
499		par,
500		stack,
501	);
502}
503/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition
504/// factors $S$ and $U$, and tolerance parameters for determining zero
505/// eigenvalues
506#[track_caller]
507pub fn pseudoinverse_from_self_adjoint_evd_with_tolerance<T: ComplexField>(
508	pinv: MatMut<'_, T>,
509	s: DiagRef<'_, T>,
510	u: MatRef<'_, T>,
511	abs_tol: T::Real,
512	rel_tol: T::Real,
513	par: Par,
514	stack: &mut MemStack,
515) {
516	let s = s.column_vector();
517	let mut pinv = pinv;
518	let n = u.ncols();
519	assert!(all(u.nrows() == n, u.ncols() == n, s.nrows() == n));
520	let smax = s.norm_max();
521	let tol = abs_tol.fmax(rel_tol * smax);
522	let (mut u_trunc, stack) =
523		unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
524	let (mut up_trunc, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
525	let mut u_trunc = u_trunc.as_mat_mut();
526	let mut up_trunc = up_trunc.as_mat_mut();
527	let mut len = 0;
528	for j in 0..n {
529		let x = s[j].absmax();
530		if x > tol {
531			let ref p = s[j].real().recip();
532			u_trunc.rb_mut().col_mut(len).copy_from(u.col(j));
533			z!(up_trunc.rb_mut().col_mut(len), u.col(j))
534				.for_each(|uz!(dst, src)| *dst = src.mul_real(p));
535			len += 1;
536		}
537	}
538	let u_trunc = u_trunc.get(.., ..len);
539	let up_trunc = up_trunc.get(.., ..len);
540	linalg::matmul::triangular::matmul(
541		pinv.rb_mut(),
542		BlockStructure::TriangularLower,
543		Accum::Replace,
544		up_trunc.rb(),
545		BlockStructure::Rectangular,
546		u_trunc.rb().adjoint(),
547		BlockStructure::Rectangular,
548		one(),
549		par,
550	);
551	for j in 0..n {
552		for i in 0..j {
553			pinv[(i, j)] = pinv[(j, i)].conj();
554		}
555	}
556}
557fn dot2x1<T: RealField>(
558	lhs0: RowRef<'_, T>,
559	lhs1: RowRef<'_, T>,
560	rhs: ColRef<'_, T>,
561) -> (T, T) {
562	let n = rhs.nrows();
563	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
564	let mut acc00 = zero::<T>();
565	let mut acc01 = zero::<T>();
566	let mut acc10 = zero::<T>();
567	let mut acc11 = zero::<T>();
568	let n2 = n / 2 * 2;
569	let mut i = 0;
570	while i < n2 {
571		acc00 += &lhs0[i] * &rhs[i];
572		acc10 += &lhs1[i] * &rhs[i];
573		acc01 += &lhs0[i + 1] * &rhs[i + 1];
574		acc11 += &lhs1[i + 1] * &rhs[i + 1];
575		i += 2;
576	}
577	while i < n {
578		acc00 += &lhs0[i] * &rhs[i];
579		acc10 += &lhs1[i] * &rhs[i];
580		i += 1;
581	}
582	(acc00 + acc01, acc10 + acc11)
583}
584fn dot2x2<T: RealField>(
585	lhs0: RowRef<'_, T>,
586	lhs1: RowRef<'_, T>,
587	rhs0: ColRef<'_, T>,
588	rhs1: ColRef<'_, T>,
589) -> (T, T, T, T) {
590	let n = rhs0.nrows();
591	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
592	let mut acc0 = zero::<T>();
593	let mut acc1 = zero::<T>();
594	let mut acc2 = zero::<T>();
595	let mut acc3 = zero::<T>();
596	let mut i = 0;
597	while i < n {
598		acc0 += &lhs0[i] * &rhs0[i];
599		acc1 += &lhs1[i] * &rhs0[i];
600		acc2 += &lhs0[i] * &rhs1[i];
601		acc3 += &lhs1[i] * &rhs1[i];
602		i += 1;
603	}
604	(acc0, acc1, acc2, acc3)
605}
606/// Normalizes the eigenvector columns of `u` to unit 2-norm. For a real Schur
607/// form `h`, a conjugate pair `j`, `j + 1` (`h[(j + 1, j)] != 0`) is scaled by a
608/// common factor so the represented complex eigenvector `re + i * im` has unit
609/// 2-norm. This is the normalization contract `xGEEV` provides.
610fn normalize_eigenvectors<T: ComplexField>(
611	mut u: MatMut<'_, T>,
612	h: MatRef<'_, T>,
613) {
614	let n = u.nrows();
615	let mut j = 0;
616	while j < n {
617		let paired = T::IS_REAL && j + 1 < n && h[(j + 1, j)] != zero::<T>();
618		let nrm = if paired {
619			u.rb().col(j).norm_l2().hypot(&u.rb().col(j + 1).norm_l2())
620		} else {
621			u.rb().col(j).norm_l2()
622		};
623		if nrm != zero::<T::Real>() {
624			let inv: T = nrm.recip().to_cplx();
625			for i in 0..n {
626				u[(i, j)] *= inv.copy();
627				if paired {
628					u[(i, j + 1)] *= inv.copy();
629				}
630			}
631		}
632		j += if paired { 2 } else { 1 };
633	}
634}
635/// Returns `false` if a returned eigenvector column is nonfinite or
636/// degenerate, which means the normalization contract could not be met.
637fn eigenvectors_ok<T: ComplexField>(u: MatRef<'_, T>) -> bool {
638	u.is_all_finite()
639		&& (0..u.ncols()).all(|j| u.col(j).norm_l2() > zero::<T::Real>())
640}
641pub(crate) fn evd_from_real_schur_imp<T: RealField>(
642	A: MatRef<'_, T>,
643	V: MatMut<'_, T>,
644	par: Par,
645	params: EvdFromSchurParams,
646) {
647	let one = one::<T>;
648	let zero = zero::<T>;
649	let mut V = V;
650	let n = A.nrows();
651	let mut norm = zero();
652	for j in 0..n {
653		for i in 0..Ord::min(j + 2, n) {
654			norm += A[(i, j)].abs1();
655		}
656	}
657	let mut k = n;
658	loop {
659		if k == 0 {
660			break;
661		}
662		k -= 1;
663		if k == 0 || A[(k, k - 1)] == zero() {
664			let ref p = A[(k, k)].copy();
665			V[(k, k)] = one();
666			for i in 0..k {
667				V[(i, k)] = -&A[(i, k)];
668			}
669			let scale = solve_real_shifted_upper_quasi_triangular_system(
670				A.submatrix(0, 0, k, k),
671				p.copy(),
672				V.rb_mut().subrows_mut(0, k).col_mut(k),
673				norm.copy(),
674				par,
675				params,
676			);
677			// The pivot is not part of the back-substituted slice, so it must
678			// carry the accumulated rescaling factor for the column direction
679			// to be preserved.
680			V[(k, k)] *= scale;
681		} else {
682			let ref p = A[(k, k)].copy();
683			let ref q = A[(k, k - 1)].abs().sqrt() * A[(k - 1, k)].abs().sqrt();
684			if A[(k - 1, k)].abs() >= A[(k, k - 1)].abs() {
685				V[(k - 1, k - 1)] = one();
686				V[(k, k)] = q / &A[(k - 1, k)];
687			} else {
688				V[(k - 1, k - 1)] = -q / &A[(k, k - 1)];
689				V[(k, k)] = one();
690			}
691			V[(k - 1, k)] = zero();
692			V[(k, k - 1)] = zero();
693			for i in 0..k - 1 {
694				V[(i, k - 1)] = -&V[(k - 1, k - 1)] * &A[(i, k - 1)];
695				V[(i, k)] = -&V[(k, k)] * &A[(i, k)];
696			}
697			let scale = solve_cplx_shifted_upper_quasi_triangular_system(
698				A.submatrix(0, 0, k - 1, k - 1),
699				p.copy(),
700				q.copy(),
701				V.rb_mut().submatrix_mut(0, k - 1, k - 1, 2),
702				norm.copy(),
703				par,
704				params,
705			);
706			V[(k - 1, k - 1)] *= scale.copy();
707			V[(k, k)] *= scale;
708			k -= 1;
709		}
710	}
711}
712pub(crate) fn evd_from_cplx_schur_imp<T: ComplexField>(
713	A: MatRef<'_, T>,
714	conj_A: Conj,
715	V: MatMut<'_, T>,
716	par: Par,
717	params: EvdFromSchurParams,
718) {
719	let one = one::<T>;
720	let mut V = V;
721	let n = A.nrows();
722	let mut norm = zero::<T::Real>();
723	for j in 0..n {
724		for i in 0..j + 1 {
725			norm += A[(i, j)].abs1();
726		}
727	}
728	let mut k = n;
729	loop {
730		if k == 0 {
731			break;
732		}
733		k -= 1;
734		let ref p = if conj_A == Conj::Yes {
735			A[(k, k)].conj()
736		} else {
737			A[(k, k)].copy()
738		};
739		V[(k, k)] = one();
740		if conj_A == Conj::Yes {
741			for i in 0..k {
742				V[(i, k)] = -A[(i, k)].conj();
743			}
744		} else {
745			for i in 0..k {
746				V[(i, k)] = -&A[(i, k)];
747			}
748		}
749		let scale = solve_shifted_upper_triangular_system(
750			A.submatrix(0, 0, k, k),
751			conj_A,
752			p.copy(),
753			V.rb_mut().subrows_mut(0, k).col_mut(k),
754			norm.copy(),
755			par,
756			params,
757		);
758		let scale: T = scale.to_cplx();
759		V[(k, k)] *= scale;
760	}
761}
762fn solve_real_shifted_upper_quasi_triangular_system<T: RealField>(
763	A: MatRef<'_, T>,
764	p: T,
765	x: ColMut<'_, T>,
766	norm: T,
767	par: Par,
768	params: EvdFromSchurParams,
769) -> T {
770	let n = A.nrows();
771	let one = one::<T>;
772	let zero = zero::<T>;
773	let ref eps = eps::<T>();
774	let ref p = p;
775	let mut x = x;
776	let ref norm = norm;
777	// While the shift `A[i, i] - p` is clamped to `eps * norm`, back
778	// substitution can grow the solution by `1 / (eps * norm)` per row and
779	// overflow for finite input. Whenever a computed entry exceeds this bound
780	// the whole slice is rescaled; the factor is also returned so the caller
781	// can apply it to the pivot entry, keeping the eigenvector direction.
782	let big = sqrt_max_positive::<T>();
783	let mut scale = one();
784	if par.degree() == 0 || n < params.recursion_threshold {
785		let mut i = n;
786		loop {
787			if i == 0 {
788				break;
789			}
790			i -= 1;
791			if i == 0 || A[(i, i - 1)] == zero() {
792				let dot = linalg::matmul::dot::inner_prod(
793					A.row(i).subcols(i + 1, n - i - 1),
794					Conj::No,
795					x.rb().subrows(i + 1, n - i - 1),
796					Conj::No,
797				);
798				x[i] -= dot;
799				let mut z = &A[(i, i)] - p;
800				if z.abs() < eps * norm {
801					z = eps * norm;
802				}
803				if x[i] != zero() {
804					x[i] /= z;
805				}
806				let m = x[i].abs();
807				if m > big {
808					let r = m.recip();
809					for j in 0..x.nrows() {
810						x[j] *= r.copy();
811					}
812					scale *= r;
813				}
814			} else {
815				let (dot0, dot1) = dot2x1(
816					A.row(i - 1).subcols(i + 1, n - i - 1),
817					A.row(i).subcols(i + 1, n - i - 1),
818					x.rb().subrows(i + 1, n - i - 1),
819				);
820				x[i - 1] -= dot0;
821				x[i] -= dot1;
822				let ref a = &A[(i, i)] - p;
823				let ref b = A[(i - 1, i)];
824				let ref c = A[(i, i - 1)];
825				let ref r0 = x[i - 1];
826				let ref r1 = x[i];
827				let ref inv_det = (a.abs2() - b * c).recip();
828				(x[i - 1], x[i]) =
829					((a * r0 - b * r1) * inv_det, (a * r1 - c * r0) * inv_det);
830				let a0 = x[i - 1].abs();
831				let a1 = x[i].abs();
832				let m = if a0 > a1 { a0 } else { a1 };
833				if m > big {
834					let r = m.recip();
835					for j in 0..x.nrows() {
836						x[j] *= r.copy();
837					}
838					scale *= r;
839				}
840				i -= 1;
841			}
842		}
843		scale
844	} else {
845		let mut mid = n / 2;
846		if A[(mid, mid - 1)] != zero() {
847			mid -= 1;
848		}
849		let (A00, A01, _, A11) = A.split_at(mid, mid);
850		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
851		let f1 = solve_real_shifted_upper_quasi_triangular_system(
852			A11,
853			p.copy(),
854			x1.rb_mut(),
855			norm.copy(),
856			par,
857			params,
858		);
859		// The right block may have been rescaled; the left block's pending
860		// right-hand side must carry the same factor for the coupled system to
861		// stay consistent.
862		for j in 0..x0.nrows() {
863			x0[j] *= f1.copy();
864		}
865		linalg::matmul::matmul(
866			x0.rb_mut().as_mat_mut(),
867			Accum::Add,
868			A01,
869			x1.rb().as_mat(),
870			-one(),
871			par,
872		);
873		let f0 = solve_real_shifted_upper_quasi_triangular_system(
874			A00,
875			p.copy(),
876			x0.rb_mut(),
877			norm.copy(),
878			par,
879			params,
880		);
881		for j in 0..x1.nrows() {
882			x1[j] *= f0.copy();
883		}
884		f0 * f1
885	}
886}
887fn solve_cplx_shifted_upper_quasi_triangular_system<T: RealField>(
888	A: MatRef<'_, T>,
889	p: T,
890	q: T,
891	x: MatMut<'_, T>,
892	norm: T,
893	par: Par,
894	params: EvdFromSchurParams,
895) -> T {
896	let n = A.nrows();
897	let one = one::<T>;
898	let zero = zero::<T>;
899	let ref eps = eps::<T>();
900	let ref norm = norm;
901	let ref p = p;
902	let ref q = q;
903	let mut x = x;
904	let big = sqrt_max_positive::<T>();
905	let mut scale = one();
906	if par.degree() == 0 || n < params.recursion_threshold {
907		let mut i = n;
908		loop {
909			if i == 0 {
910				break;
911			}
912			i -= 1;
913			if i == 0 || A[(i, i - 1)] == zero() {
914				let (re, im) = dot2x1(
915					x.rb().subrows(i + 1, n - i - 1).col(0).transpose(),
916					x.rb().subrows(i + 1, n - i - 1).col(1).transpose(),
917					A.row(i).subcols(i + 1, n - i - 1).transpose(),
918				);
919				x[(i, 0)] = &x[(i, 0)] - re;
920				x[(i, 1)] = &x[(i, 1)] - im;
921				let mut z_re = &A[(i, i)] - p;
922				let mut z_im = -q;
923				let mut z = z_re.hypot(&z_im);
924				if z < eps * norm {
925					z_re = eps * norm;
926					z_im = zero();
927					z = z_re.copy();
928				}
929				let ref z = z;
930				let ref z_re = (z_re / z) / z;
931				let ref z_im = (-z_im / z) / z;
932				let ref x_re = x[(i, 0)].copy();
933				let ref x_im = x[(i, 1)].copy();
934				x[(i, 0)] = x_re * z_re - x_im * z_im;
935				x[(i, 1)] = x_re * z_im + x_im * z_re;
936				let a0 = x[(i, 0)].abs();
937				let a1 = x[(i, 1)].abs();
938				let m = if a0 > a1 { a0 } else { a1 };
939				if m > big {
940					let r = m.recip();
941					for j in 0..x.nrows() {
942						x[(j, 0)] *= r.copy();
943						x[(j, 1)] *= r.copy();
944					}
945					scale *= r;
946				}
947			} else {
948				let (re0, re1, im0, im1) = dot2x2(
949					A.row(i - 1).subcols(i + 1, n - i - 1),
950					A.row(i).subcols(i + 1, n - i - 1),
951					x.rb().col(0).subrows(i + 1, n - i - 1),
952					x.rb().col(1).subrows(i + 1, n - i - 1),
953				);
954				x[(i - 1, 0)] = &x[(i - 1, 0)] - re0;
955				x[(i - 1, 1)] = &x[(i - 1, 1)] - im0;
956				x[(i, 0)] = &x[(i, 0)] - re1;
957				x[(i, 1)] = &x[(i, 1)] - im1;
958				let ref a_re = &A[(i, i)] - p;
959				let ref a_im = -q;
960				let ref b = A[(i - 1, i)];
961				let ref c = A[(i, i - 1)];
962				let ref r0_re = x[(i - 1, 0)];
963				let ref r0_im = x[(i - 1, 1)];
964				let ref r1_re = x[(i, 0)];
965				let ref r1_im = x[(i, 1)];
966				let mut z_re = a_re.abs2() - a_im.abs2() - b * c;
967				let mut z_im = (a_re * a_im).mul_pow2(from_f64::<T>(2.0));
968				let mut z = z_re.hypot(&z_im);
969				if z < eps * norm {
970					z_re = eps * norm;
971					z_im = zero();
972					z = z_re.copy();
973				}
974				let ref z = z;
975				let ref z_re = (z_re / z) / z;
976				let ref z_im = (-z_im / z) / z;
977				let ref x0_re = (a_re * r0_re - a_im * r0_im) - b * r1_re;
978				let ref x0_im = (a_re * r0_im + a_im * r0_re) - b * r1_im;
979				let ref x1_re = (a_re * r1_re - a_im * r1_im) - c * r0_re;
980				let ref x1_im = (a_re * r1_im + a_im * r1_re) - c * r0_im;
981				x[(i - 1, 0)] = x0_re * z_re - x0_im * z_im;
982				x[(i - 1, 1)] = x0_re * z_im + x0_im * z_re;
983				x[(i, 0)] = x1_re * z_re - x1_im * z_im;
984				x[(i, 1)] = x1_re * z_im + x1_im * z_re;
985				let mut m = x[(i - 1, 0)].abs();
986				let v = x[(i - 1, 1)].abs();
987				if v > m {
988					m = v;
989				}
990				let v = x[(i, 0)].abs();
991				if v > m {
992					m = v;
993				}
994				let v = x[(i, 1)].abs();
995				if v > m {
996					m = v;
997				}
998				if m > big {
999					let r = m.recip();
1000					for j in 0..x.nrows() {
1001						x[(j, 0)] *= r.copy();
1002						x[(j, 1)] *= r.copy();
1003					}
1004					scale *= r;
1005				}
1006				i -= 1;
1007			}
1008		}
1009		scale
1010	} else {
1011		let mut mid = n / 2;
1012		if A[(mid, mid - 1)] != zero() {
1013			mid -= 1;
1014		}
1015		let (A00, A01, _, A11) = A.split_at(mid, mid);
1016		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
1017		let f1 = solve_cplx_shifted_upper_quasi_triangular_system(
1018			A11,
1019			p.copy(),
1020			q.copy(),
1021			x1.rb_mut(),
1022			norm.copy(),
1023			par,
1024			params,
1025		);
1026		for j in 0..x0.nrows() {
1027			x0[(j, 0)] *= f1.copy();
1028			x0[(j, 1)] *= f1.copy();
1029		}
1030		linalg::matmul::matmul(
1031			x0.rb_mut(),
1032			Accum::Add,
1033			A01,
1034			x1.rb(),
1035			-one(),
1036			par,
1037		);
1038		let f0 = solve_cplx_shifted_upper_quasi_triangular_system(
1039			A00,
1040			p.copy(),
1041			q.copy(),
1042			x0.rb_mut(),
1043			norm.copy(),
1044			par,
1045			params,
1046		);
1047		for j in 0..x1.nrows() {
1048			x1[(j, 0)] *= f0.copy();
1049			x1[(j, 1)] *= f0.copy();
1050		}
1051		f0 * f1
1052	}
1053}
1054fn solve_shifted_upper_triangular_system<T: ComplexField>(
1055	A: MatRef<'_, T>,
1056	conj_A: Conj,
1057	p: T,
1058	x: ColMut<'_, T>,
1059	norm: T::Real,
1060	par: Par,
1061	params: EvdFromSchurParams,
1062) -> T::Real {
1063	let n = A.nrows();
1064	let mut scale = one::<T::Real>();
1065	let one = one::<T>;
1066	let zero = zero::<T>;
1067	let ref eps = eps::<T::Real>();
1068	let ref norm = norm;
1069	let ref p = p;
1070	let mut x = x;
1071	let big = sqrt_max_positive::<T::Real>();
1072	if par.degree() == 0 || n < params.recursion_threshold {
1073		let mut i = n;
1074		loop {
1075			if i == 0 {
1076				break;
1077			}
1078			i -= 1;
1079			let dot = linalg::matmul::dot::inner_prod(
1080				A.row(i).subcols(i + 1, n - i - 1),
1081				conj_A,
1082				x.rb().subrows(i + 1, n - i - 1),
1083				Conj::No,
1084			);
1085			x[i] -= dot;
1086			let mut z = if conj_A == Conj::Yes {
1087				A[(i, i)].conj()
1088			} else {
1089				A[(i, i)].copy()
1090			} - p;
1091			if z.abs() < eps * norm {
1092				z = (eps * norm).to_cplx();
1093			}
1094			if x[i] != zero() {
1095				x[i] *= z.recip();
1096			}
1097			let m = x[i].abs();
1098			if m > big {
1099				let r = m.recip();
1100				let rc: T = r.to_cplx();
1101				for j in 0..x.nrows() {
1102					x[j] *= rc.copy();
1103				}
1104				scale *= r;
1105			}
1106		}
1107		scale
1108	} else {
1109		let mid = n / 2;
1110		let (A00, A01, _, A11) = A.split_at(mid, mid);
1111		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
1112		let f1 = solve_shifted_upper_triangular_system(
1113			A11,
1114			conj_A,
1115			p.copy(),
1116			x1.rb_mut(),
1117			norm.copy(),
1118			par,
1119			params,
1120		);
1121		let f1c: T = f1.to_cplx();
1122		for j in 0..x0.nrows() {
1123			x0[j] *= f1c.copy();
1124		}
1125		linalg::matmul::matmul_with_conj(
1126			x0.rb_mut().as_mat_mut(),
1127			Accum::Add,
1128			A01,
1129			conj_A,
1130			x1.rb().as_mat(),
1131			Conj::No,
1132			-one(),
1133			par,
1134		);
1135		let f0 = solve_shifted_upper_triangular_system(
1136			A00,
1137			conj_A,
1138			p.copy(),
1139			x0.rb_mut(),
1140			norm.copy(),
1141			par,
1142			params,
1143		);
1144		let f0c: T = f0.to_cplx();
1145		for j in 0..x1.nrows() {
1146			x1[j] *= f0c.copy();
1147		}
1148		f0 * f1
1149	}
1150}
1151/// computes the layout of the workspace required to compute a matrix's
1152/// eigendecomposition
1153pub fn evd_scratch<T: ComplexField>(
1154	dim: usize,
1155	eigen_left: ComputeEigenvectors,
1156	eigen_right: ComputeEigenvectors,
1157	par: Par,
1158	params: Spec<EvdParams, T>,
1159) -> StackReq {
1160	let n = dim;
1161	if n == 0 {
1162		return StackReq::EMPTY;
1163	}
1164	let compute_eigen = eigen_left == ComputeEigenvectors::Yes
1165		|| eigen_right == ComputeEigenvectors::Yes;
1166	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
1167		n - 1,
1168		n - 1,
1169	);
1170	let H = temp_mat_scratch::<T>(n, n);
1171	let X = H;
1172	let Z = temp_mat_scratch::<T>(n, if compute_eigen { n } else { 0 });
1173	let householder = temp_mat_scratch::<T>(bs, n);
1174	let apply = linalg::householder::apply_block_householder_sequence_on_the_right_in_place_scratch::<T>(n - 1, bs, n - 1);
1175	StackReq::all_of(&[
1176		H,
1177		Z,
1178		StackReq::any_of(&[
1179			householder.and(
1180				hessenberg::hessenberg_in_place_scratch::<T>(
1181					n,
1182					bs,
1183					par,
1184					params.hessenberg.into(),
1185				)
1186				.or(apply),
1187			),
1188			schur::multishift_qr_scratch::<T>(
1189				n,
1190				n,
1191				compute_eigen,
1192				compute_eigen,
1193				par,
1194				params.schur,
1195			),
1196			X,
1197		]),
1198	])
1199}
1200/// Workspace for [`hessenberg_schur_real`] and
1201/// [`hessenberg_schur_cplx`].
1202///
1203/// # Examples
1204/// ```
1205/// use faer::Par;
1206/// use faer::linalg::evd::hessenberg_schur_scratch;
1207/// let req =
1208/// 	hessenberg_schur_scratch::<f64>(3, true, Par::Seq, Default::default());
1209/// let _buffer = dyn_stack::MemBuffer::new(req);
1210/// ```
1211pub fn hessenberg_schur_scratch<T: ComplexField>(
1212	dim: usize,
1213	compute_z: bool,
1214	par: Par,
1215	params: Spec<SchurParams, T>,
1216) -> StackReq {
1217	schur::multishift_qr_scratch::<T>(dim, dim, true, compute_z, par, *params)
1218}
1219/// Overwrites an upper-Hessenberg `h` with its real Schur form `t`.
1220/// Updates optional `z` to `z * q`, where `h_original = q * t * q.transpose()`.
1221/// Real conjugate pairs have equal diagonal entries, opposite-sign
1222/// off-diagonals, and eigenvalues `wr ± i wi`, with the positive imaginary part
1223/// first. `wr` and `wi` each have length `n`; `z`, when present, is `n` by `n`.
1224///
1225/// # Panics
1226/// Panics on incompatible dimensions or insufficient scratch. The input must
1227/// be upper Hessenberg (entries below its first subdiagonal must be zero).
1228///
1229/// # Errors
1230/// Returns [`EvdError::NoConvergence`] for nonfinite input or QR
1231/// nonconvergence. After nonconvergence, `h` and `z` contain the accumulated
1232/// similarity updates.
1233///
1234/// # Examples
1235/// ```
1236/// use dyn_stack::{MemBuffer, MemStack};
1237/// use faer::linalg::evd::*;
1238/// use faer::{Col, Mat, Par};
1239/// let mut h = faer::mat![[1.0f64, 2.0], [-2.0, 1.0]];
1240/// let mut z = Mat::identity(2, 2);
1241/// let (mut wr, mut wi) = (Col::zeros(2), Col::zeros(2));
1242/// let mut mem = MemBuffer::new(hessenberg_schur_scratch::<f64>(
1243/// 	2,
1244/// 	true,
1245/// 	Par::Seq,
1246/// 	Default::default(),
1247/// ));
1248/// hessenberg_schur_real(
1249/// 	h.as_mut(),
1250/// 	Some(z.as_mut()),
1251/// 	wr.as_mut(),
1252/// 	wi.as_mut(),
1253/// 	Par::Seq,
1254/// 	MemStack::new(&mut mem),
1255/// 	Default::default(),
1256/// )
1257/// .unwrap();
1258/// assert!(wi[0] > 0.0 && wi[1] < 0.0);
1259/// ```
1260pub fn hessenberg_schur_real<T: RealField>(
1261	mut h: MatMut<'_, T>,
1262	z: Option<MatMut<'_, T>>,
1263	mut wr: ColMut<'_, T>,
1264	mut wi: ColMut<'_, T>,
1265	par: Par,
1266	stack: &mut MemStack,
1267	params: Spec<SchurParams, T>,
1268) -> Result<(), EvdError> {
1269	let n = h.nrows();
1270	assert!(h.ncols() == n && wr.nrows() == n && wi.nrows() == n);
1271	if let Some(z) = z.rb() {
1272		assert!(z.nrows() == n && z.ncols() == n);
1273	}
1274	if !h.is_all_finite() {
1275		return Err(EvdError::NoConvergence);
1276	}
1277	if n <= 1 {
1278		if n == 1 {
1279			wr[0] = h[(0, 0)].copy();
1280			wi[0] = zero();
1281		}
1282		return Ok(());
1283	}
1284	let (info, _, _) = schur::real_schur::multishift_qr(
1285		true,
1286		h.rb_mut(),
1287		z,
1288		wr,
1289		wi,
1290		0,
1291		n,
1292		par,
1293		stack,
1294		*params,
1295	);
1296	if info != 0 {
1297		return Err(EvdError::NoConvergence);
1298	}
1299	// The QR kernel uses entries below the Hessenberg band as workspace.
1300	for j in 0..n {
1301		for i in j + 2..n {
1302			h[(i, j)] = zero();
1303		}
1304	}
1305	Ok(())
1306}
1307/// Complex counterpart of [`hessenberg_schur_real`]: `h` becomes upper
1308/// triangular, `w` receives its diagonal, and optional `z` becomes `z * q`.
1309/// The similarity is `h_original = q * t * q.adjoint()`.
1310///
1311/// # Panics
1312/// Panics on incompatible dimensions, a real scalar type, or insufficient
1313/// scratch; `h` must be upper Hessenberg.
1314///
1315/// # Errors
1316/// Returns [`EvdError::NoConvergence`] for nonfinite input or QR
1317/// nonconvergence; accumulated similarity updates remain in `h` and `z`.
1318///
1319/// # Examples
1320/// ```
1321/// use dyn_stack::{MemBuffer, MemStack};
1322/// use faer::linalg::evd::*;
1323/// use faer::{Col, Mat, Par, c64};
1324/// let mut h = Mat::<c64>::identity(2, 2);
1325/// let mut w = Col::zeros(2);
1326/// let mut mem = MemBuffer::new(hessenberg_schur_scratch::<c64>(
1327/// 	2,
1328/// 	false,
1329/// 	Par::Seq,
1330/// 	Default::default(),
1331/// ));
1332/// hessenberg_schur_cplx(
1333/// 	h.as_mut(),
1334/// 	None,
1335/// 	w.as_mut(),
1336/// 	Par::Seq,
1337/// 	MemStack::new(&mut mem),
1338/// 	Default::default(),
1339/// )
1340/// .unwrap();
1341/// assert_eq!(w[0], c64::new(1.0, 0.0));
1342/// ```
1343pub fn hessenberg_schur_cplx<T: ComplexField>(
1344	mut h: MatMut<'_, T>,
1345	z: Option<MatMut<'_, T>>,
1346	mut w: ColMut<'_, T>,
1347	par: Par,
1348	stack: &mut MemStack,
1349	params: Spec<SchurParams, T>,
1350) -> Result<(), EvdError> {
1351	assert!(!T::IS_REAL);
1352	let n = h.nrows();
1353	assert!(h.ncols() == n && w.nrows() == n);
1354	if let Some(z) = z.rb() {
1355		assert!(z.nrows() == n && z.ncols() == n);
1356	}
1357	if !h.is_all_finite() {
1358		return Err(EvdError::NoConvergence);
1359	}
1360	if n <= 1 {
1361		if n == 1 {
1362			w[0] = h[(0, 0)].copy();
1363		}
1364		return Ok(());
1365	}
1366	let (info, _, _) = schur::complex_schur::multishift_qr(
1367		true,
1368		h.rb_mut(),
1369		z,
1370		w,
1371		0,
1372		n,
1373		par,
1374		stack,
1375		*params,
1376	);
1377	if info != 0 {
1378		return Err(EvdError::NoConvergence);
1379	}
1380	// Return a full triangular matrix, without the QR kernel's workspace.
1381	for j in 0..n {
1382		for i in j + 1..n {
1383			h[(i, j)] = zero();
1384		}
1385	}
1386	Ok(())
1387}
1388/// An adjacent real Schur block swap was rejected as ill-conditioned.
1389/// `position` is the current first row of the moving block.
1390///
1391/// # Examples
1392/// ```
1393/// use faer::linalg::evd::SchurReorderError;
1394/// let err = SchurReorderError { position: 2 };
1395/// assert_eq!(err.position, 2);
1396/// ```
1397#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1398pub struct SchurReorderError {
1399	/// Current first row of the moving block, after the last successful swap.
1400	pub position: usize,
1401}
1402/// Workspace for [`reorder_schur_real`] and [`reorder_schur_cplx`]: a copy of
1403/// the rows and columns of `t` and the columns of `q` that one adjacent swap
1404/// updates, so that a rejected swap can be undone.
1405///
1406/// # Examples
1407/// ```
1408/// let req = faer::linalg::evd::reorder_schur_scratch::<f64>(3);
1409/// let _buffer = dyn_stack::MemBuffer::new(req);
1410/// ```
1411pub fn reorder_schur_scratch<T: ComplexField>(dim: usize) -> StackReq {
1412	StackReq::all_of(&[
1413		temp_mat_scratch::<T>(4, dim),
1414		temp_mat_scratch::<T>(dim, 4),
1415		temp_mat_scratch::<T>(dim, 4),
1416	])
1417}
1418/// Moves a real Schur block from `ifst` to `ilst`, updating optional `q`.
1419/// Indices are zero-based rows; the second row of a pair denotes its first row.
1420/// Like `xTREXC`, when moving down across a differently sized destination
1421/// block, the final first row is adjusted so the moving block follows that
1422/// block. Returns the final first row; conjugate pairs are never split.
1423///
1424/// # Panics
1425/// Panics on invalid dimensions/indices. `t` must be in standardized real Schur
1426/// form.
1427///
1428/// # Errors
1429/// Returns [`SchurReorderError`] when an adjacent swap is rejected. As in
1430/// LAPACK `xTREXC`/`xLAEXC`, a swap involving a 2 by 2 block is performed
1431/// provisionally on a copy of its diagonal window and rejected when the
1432/// provisional entries that must vanish (and, for a moved 1 by 1 block, its
1433/// diagonal value) are off by more than `max(10 eps |window|_max,
1434/// safmin / eps)`. A swap whose update of `t` or `q` overflows is rejected
1435/// too. `t` and `q` are left exactly as after the last successful swap (in
1436/// valid Schur form, with the block standardization); the error gives the
1437/// moving block's current position. On success, `t` and `q` are finite.
1438///
1439/// # Examples
1440/// ```
1441/// use dyn_stack::{MemBuffer, MemStack};
1442/// use faer::Par;
1443/// use faer::linalg::evd::*;
1444/// let mut t = faer::mat![[1.0f64, 0.0], [0.0, 2.0]];
1445/// let mut mem = MemBuffer::new(reorder_schur_scratch::<f64>(2));
1446/// assert_eq!(
1447/// 	reorder_schur_real(
1448/// 		t.as_mut(),
1449/// 		None,
1450/// 		0,
1451/// 		1,
1452/// 		Par::Seq,
1453/// 		MemStack::new(&mut mem)
1454/// 	)
1455/// 	.unwrap(),
1456/// 	1
1457/// );
1458/// assert_eq!(t[(1, 1)], 1.0);
1459/// ```
1460pub fn reorder_schur_real<T: RealField>(
1461	mut t: MatMut<'_, T>,
1462	mut q: Option<MatMut<'_, T>>,
1463	mut ifst: usize,
1464	mut ilst: usize,
1465	_par: Par,
1466	stack: &mut MemStack,
1467) -> Result<usize, SchurReorderError> {
1468	let n = t.nrows();
1469	assert!(t.ncols() == n && ifst < n && ilst < n);
1470	if let Some(q) = q.rb() {
1471		assert!(q.nrows() == n && q.ncols() == n);
1472	}
1473	let (mut saved_rows, stack) = temp_mat_zeroed::<T, _, _>(4, n, stack);
1474	let (mut saved_cols, stack) = temp_mat_zeroed::<T, _, _>(n, 4, stack);
1475	let (mut saved_q, _) = temp_mat_zeroed::<T, _, _>(n, 4, stack);
1476	let mut saved_rows = saved_rows.as_mat_mut();
1477	let mut saved_cols = saved_cols.as_mat_mut();
1478	let mut saved_q = saved_q.as_mat_mut();
1479	if ifst > 0 && t[(ifst, ifst - 1)] != zero() {
1480		ifst -= 1;
1481	}
1482	if ilst > 0 && t[(ilst, ilst - 1)] != zero() {
1483		ilst -= 1;
1484	}
1485	let block_size = |t: MatRef<'_, T>, i: usize| {
1486		if i + 1 < n && t[(i + 1, i)] != zero() {
1487			2
1488		} else {
1489			1
1490		}
1491	};
1492	let moving = block_size(t.rb(), ifst);
1493	if ifst < ilst {
1494		let dest = block_size(t.rb(), ilst);
1495		ilst = ilst + dest - moving;
1496	}
1497	let mut here = ifst;
1498	while here != ilst {
1499		let down = here < ilst;
1500		let next = if down {
1501			block_size(t.rb(), here + moving)
1502		} else if here > 1 && t[(here - 1, here - 2)] != zero() {
1503			2
1504		} else {
1505			1
1506		};
1507		let start = if down { here } else { here - next };
1508		let (n1, n2) = if down { (moving, next) } else { (next, moving) };
1509		// A swap updates rows `start..start + k` (from column `start`) and
1510		// columns `start..start + k` (up to row `start + k`) of `t`, and
1511		// columns `start..start + k` of `q`. Keep a copy so that a rejected
1512		// or overflowing swap leaves the state of the last successful one.
1513		let k = n1 + n2;
1514		let w = n - start;
1515		let h = start + k;
1516		saved_rows
1517			.rb_mut()
1518			.submatrix_mut(0, 0, k, w)
1519			.copy_from(t.rb().submatrix(start, start, k, w));
1520		saved_cols
1521			.rb_mut()
1522			.submatrix_mut(0, 0, h, k)
1523			.copy_from(t.rb().submatrix(0, start, h, k));
1524		if let Some(q) = q.rb() {
1525			saved_q
1526				.rb_mut()
1527				.submatrix_mut(0, 0, n, k)
1528				.copy_from(q.submatrix(0, start, n, k));
1529		}
1530		let status = schur::real_schur::schur_swap_checked(
1531			t.rb_mut(),
1532			q.rb_mut(),
1533			start,
1534			n1,
1535			n2,
1536			true,
1537		);
1538		let finite = t.rb().submatrix(start, start, k, w).is_all_finite()
1539			&& t.rb().submatrix(0, start, h, k).is_all_finite()
1540			&& q.rb()
1541				.is_none_or(|q| q.submatrix(0, start, n, k).is_all_finite());
1542		if status != 0 || !finite {
1543			t.rb_mut()
1544				.submatrix_mut(0, start, h, k)
1545				.copy_from(saved_cols.rb().submatrix(0, 0, h, k));
1546			t.rb_mut()
1547				.submatrix_mut(start, start, k, w)
1548				.copy_from(saved_rows.rb().submatrix(0, 0, k, w));
1549			if let Some(q) = q.rb_mut() {
1550				q.submatrix_mut(0, start, n, k)
1551					.copy_from(saved_q.rb().submatrix(0, 0, n, k));
1552			}
1553			return Err(SchurReorderError { position: here });
1554		}
1555		here = if down { here + next } else { here - next };
1556	}
1557	Ok(here)
1558}
1559/// Moves a complex triangular Schur diagonal entry from `ifst` to `ilst`,
1560/// updating optional `q`. All blocks are 1 by 1; the complex swap cannot
1561/// reject.
1562///
1563/// # Panics
1564/// Panics on invalid dimensions/indices. `t` must be upper triangular.
1565///
1566/// # Examples
1567/// ```
1568/// use dyn_stack::{MemBuffer, MemStack};
1569/// use faer::linalg::evd::*;
1570/// use faer::{Mat, Par, c64};
1571/// let mut t = Mat::<c64>::identity(2, 2);
1572/// let mut mem = MemBuffer::new(reorder_schur_scratch::<c64>(2));
1573/// assert_eq!(
1574/// 	reorder_schur_cplx(
1575/// 		t.as_mut(),
1576/// 		None,
1577/// 		0,
1578/// 		1,
1579/// 		Par::Seq,
1580/// 		MemStack::new(&mut mem)
1581/// 	),
1582/// 	1
1583/// );
1584/// ```
1585pub fn reorder_schur_cplx<T: ComplexField>(
1586	mut t: MatMut<'_, T>,
1587	mut q: Option<MatMut<'_, T>>,
1588	ifst: usize,
1589	ilst: usize,
1590	_par: Par,
1591	_stack: &mut MemStack,
1592) -> usize {
1593	let n = t.nrows();
1594	assert!(t.ncols() == n && ifst < n && ilst < n);
1595	if let Some(q) = q.rb() {
1596		assert!(q.nrows() == n && q.ncols() == n);
1597	}
1598	let mut here = ifst;
1599	while here != ilst {
1600		let start = if here < ilst { here } else { here - 1 };
1601		schur::complex_schur::schur_swap(t.rb_mut(), q.rb_mut(), start);
1602		here = if here < ilst { here + 1 } else { here - 1 };
1603	}
1604	here
1605}
1606/// Workspace for [`eigenvectors_from_schur_real`] and
1607/// [`eigenvectors_from_schur_cplx`].
1608///
1609/// # Examples
1610/// ```
1611/// let req = faer::linalg::evd::eigenvectors_from_schur_scratch::<f64>(3);
1612/// let _buffer = dyn_stack::MemBuffer::new(req);
1613/// ```
1614pub fn eigenvectors_from_schur_scratch<T: ComplexField>(
1615	dim: usize,
1616) -> StackReq {
1617	temp_mat_scratch::<T>(dim, dim)
1618}
1619/// Computes all right eigenvectors of standardized real Schur `t` in diagonal
1620/// order. If `q` is supplied, returns eigenvectors of `q * t * q.transpose()`.
1621/// Each real column has unit 2-norm. A pair of columns encodes `re + i im` for
1622/// the positive-imaginary eigenvalue, normalized as one complex vector.
1623/// `x` is output only: its previous contents are never read.
1624///
1625/// # Panics
1626/// Panics on incompatible square dimensions or insufficient scratch.
1627///
1628/// # Errors
1629/// Returns [`EvdError::NoConvergence`] for nonfinite input or
1630/// nonfinite/degenerate output.
1631///
1632/// # Examples
1633/// ```
1634/// use dyn_stack::{MemBuffer, MemStack};
1635/// use faer::linalg::evd::*;
1636/// use faer::{Mat, Par};
1637/// let t = faer::mat![[1.0f64, 2.0], [0.0, 3.0]];
1638/// let mut x = Mat::zeros(2, 2);
1639/// let mut mem = MemBuffer::new(eigenvectors_from_schur_scratch::<f64>(2));
1640/// eigenvectors_from_schur_real(
1641/// 	t.as_ref(),
1642/// 	None,
1643/// 	x.as_mut(),
1644/// 	Par::Seq,
1645/// 	MemStack::new(&mut mem),
1646/// 	Default::default(),
1647/// )
1648/// .unwrap();
1649/// assert!((x.col(1).norm_l2() - 1.0).abs() < 1e-14);
1650/// ```
1651pub fn eigenvectors_from_schur_real<T: RealField>(
1652	t: MatRef<'_, T>,
1653	q: Option<MatRef<'_, T>>,
1654	mut x: MatMut<'_, T>,
1655	par: Par,
1656	stack: &mut MemStack,
1657	params: Spec<EvdFromSchurParams, T>,
1658) -> Result<(), EvdError> {
1659	let n = t.nrows();
1660	assert!(t.ncols() == n && x.nrows() == n && x.ncols() == n);
1661	if let Some(q) = q {
1662		assert!(q.nrows() == n && q.ncols() == n);
1663	}
1664	if !t.is_all_finite() {
1665		return Err(EvdError::NoConvergence);
1666	}
1667	let (mut v, _) = temp_mat_zeroed::<T, _, _>(n, n, stack);
1668	let mut v = v.as_mat_mut();
1669	evd_from_real_schur_imp(t, v.rb_mut(), par, *params);
1670	if let Some(q) = q {
1671		linalg::matmul::matmul(
1672			x.rb_mut(),
1673			Accum::Replace,
1674			q,
1675			v.rb(),
1676			one(),
1677			par,
1678		);
1679	} else {
1680		x.copy_from(v.rb());
1681	}
1682	normalize_eigenvectors(x.rb_mut(), t);
1683	if eigenvectors_ok(x.rb()) {
1684		Ok(())
1685	} else {
1686		Err(EvdError::NoConvergence)
1687	}
1688}
1689/// Complex counterpart of [`eigenvectors_from_schur_real`]. `t` is upper
1690/// triangular; every column is one unit-norm right eigenvector, in diagonal
1691/// order. Optional `q` back-transforms by `q * x` (for `q * t * q.adjoint()`).
1692/// `x` is output only: its previous contents are never read.
1693///
1694/// # Panics
1695/// Panics on incompatible square dimensions or insufficient scratch.
1696///
1697/// # Errors
1698/// Returns [`EvdError::NoConvergence`] for nonfinite input or
1699/// nonfinite/degenerate output.
1700///
1701/// # Examples
1702/// ```
1703/// use dyn_stack::{MemBuffer, MemStack};
1704/// use faer::linalg::evd::*;
1705/// use faer::{Mat, Par, c64};
1706/// let t = Mat::<c64>::identity(2, 2);
1707/// let mut x = Mat::zeros(2, 2);
1708/// let mut mem = MemBuffer::new(eigenvectors_from_schur_scratch::<c64>(2));
1709/// eigenvectors_from_schur_cplx(
1710/// 	t.as_ref(),
1711/// 	None,
1712/// 	x.as_mut(),
1713/// 	Par::Seq,
1714/// 	MemStack::new(&mut mem),
1715/// 	Default::default(),
1716/// )
1717/// .unwrap();
1718/// assert_eq!(x, t);
1719/// ```
1720pub fn eigenvectors_from_schur_cplx<T: ComplexField>(
1721	t: MatRef<'_, T>,
1722	q: Option<MatRef<'_, T>>,
1723	mut x: MatMut<'_, T>,
1724	par: Par,
1725	stack: &mut MemStack,
1726	params: Spec<EvdFromSchurParams, T>,
1727) -> Result<(), EvdError> {
1728	let n = t.nrows();
1729	assert!(t.ncols() == n && x.nrows() == n && x.ncols() == n);
1730	if let Some(q) = q {
1731		assert!(q.nrows() == n && q.ncols() == n);
1732	}
1733	if !t.is_all_finite() {
1734		return Err(EvdError::NoConvergence);
1735	}
1736	let (mut v, _) = temp_mat_zeroed::<T, _, _>(n, n, stack);
1737	let mut v = v.as_mat_mut();
1738	evd_from_cplx_schur_imp(t, Conj::No, v.rb_mut(), par, *params);
1739	if let Some(q) = q {
1740		linalg::matmul::matmul(
1741			x.rb_mut(),
1742			Accum::Replace,
1743			q,
1744			v.rb(),
1745			one(),
1746			par,
1747		);
1748	} else {
1749		x.copy_from(v.rb());
1750	}
1751	normalize_eigenvectors(x.rb_mut(), t);
1752	if eigenvectors_ok(x.rb()) {
1753		Ok(())
1754	} else {
1755		Err(EvdError::NoConvergence)
1756	}
1757}
1758fn evd_imp<T: ComplexField>(
1759	A: MatRef<'_, T>,
1760	s: ColMut<'_, T>,
1761	s_im: Option<ColMut<'_, T>>,
1762	u_left: Option<MatMut<'_, T>>,
1763	u_right: Option<MatMut<'_, T>>,
1764	par: Par,
1765	stack: &mut MemStack,
1766	params: EvdParams,
1767) -> Result<(), EvdError> {
1768	let n = A.nrows();
1769	if n == 0 {
1770		return Ok(());
1771	}
1772	for j in 0..n {
1773		for i in 0..n {
1774			if !A[(i, j)].is_finite() {
1775				return Err(EvdError::NoConvergence);
1776			}
1777		}
1778	}
1779	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
1780		n - 1,
1781		n - 1,
1782	);
1783	let mut s = s;
1784	let mut s_im = s_im;
1785	let (mut H, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1786	let (mut Z, stack) = unsafe {
1787		temp_mat_uninit::<T, _, _>(
1788			n,
1789			if u_left.is_some() || u_right.is_some() {
1790				n
1791			} else {
1792				0
1793			},
1794			stack,
1795		)
1796	};
1797	let mut H = H.as_mat_mut();
1798	let mut Z = if u_left.is_some() || u_right.is_some() {
1799		Some(Z.as_mat_mut())
1800	} else {
1801		None
1802	};
1803	H.copy_from(A);
1804	{
1805		let (mut householder, stack) =
1806			unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
1807		let mut householder = householder.as_mat_mut();
1808		hessenberg::hessenberg_in_place(
1809			H.rb_mut(),
1810			householder.rb_mut(),
1811			par,
1812			stack,
1813			params.hessenberg.into(),
1814		);
1815		if let Some(mut Z) = Z.rb_mut() {
1816			Z.fill(zero());
1817			Z.rb_mut().diagonal_mut().fill(one());
1818			linalg::householder::apply_block_householder_sequence_on_the_right_in_place_with_conj(
1819				H.rb().submatrix(1, 0, n - 1, n - 1),
1820				householder.rb(),
1821				Conj::No,
1822				Z.rb_mut().submatrix_mut(1, 1, n - 1, n - 1),
1823				par,
1824				stack,
1825			);
1826		}
1827		for j in 0..n {
1828			for i in j + 2..n {
1829				H[(i, j)] = zero();
1830			}
1831		}
1832	}
1833	if const { T::IS_REAL } {
1834		let (info, _, _) = schur::real_schur::multishift_qr::<T::Real>(
1835			unsafe { core::mem::transmute(Z.is_some()) },
1836			unsafe { core::mem::transmute(H.rb_mut()) },
1837			unsafe { core::mem::transmute(Z.rb_mut()) },
1838			unsafe { core::mem::transmute(s.rb_mut()) },
1839			unsafe { core::mem::transmute(s_im.rb_mut().unwrap()) },
1840			0,
1841			n,
1842			par,
1843			stack,
1844			params.schur,
1845		);
1846		if info != 0 {
1847			return Err(EvdError::NoConvergence);
1848		}
1849	} else {
1850		let (info, _, _) = schur::complex_schur::multishift_qr::<T>(
1851			Z.is_some(),
1852			H.rb_mut(),
1853			Z.rb_mut(),
1854			s.rb_mut(),
1855			0,
1856			n,
1857			par,
1858			stack,
1859			params.schur,
1860		);
1861		if info != 0 {
1862			return Err(EvdError::NoConvergence);
1863		}
1864	}
1865	let H = H.rb();
1866	if let (Some(mut u), Some(Z)) = (u_right, Z.rb()) {
1867		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1868		let mut X = X.as_mat_mut();
1869		if const { T::IS_REAL } {
1870			evd_from_real_schur_imp::<T::Real>(
1871				unsafe { core::mem::transmute(H) },
1872				unsafe { core::mem::transmute(X.rb_mut()) },
1873				par,
1874				params.evd_from_schur,
1875			);
1876		} else {
1877			evd_from_cplx_schur_imp::<T>(
1878				H,
1879				Conj::No,
1880				X.rb_mut(),
1881				par,
1882				params.evd_from_schur,
1883			);
1884		}
1885		linalg::matmul::triangular::matmul(
1886			u.rb_mut(),
1887			BlockStructure::Rectangular,
1888			Accum::Replace,
1889			Z,
1890			BlockStructure::Rectangular,
1891			X.rb(),
1892			BlockStructure::TriangularUpper,
1893			one(),
1894			par,
1895		);
1896		normalize_eigenvectors(u.rb_mut(), H);
1897		if !eigenvectors_ok(u.rb()) {
1898			return Err(EvdError::NoConvergence);
1899		}
1900	}
1901	if let (Some(mut u), Some(Z)) = (u_left, Z.rb()) {
1902		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1903		let mut X = X.as_mat_mut().reverse_rows_mut();
1904		if const { T::IS_REAL } {
1905			evd_from_real_schur_imp::<T::Real>(
1906				unsafe {
1907					core::mem::transmute(H.transpose().reverse_rows_and_cols())
1908				},
1909				unsafe { core::mem::transmute(X.rb_mut()) },
1910				par,
1911				params.evd_from_schur,
1912			);
1913		} else {
1914			evd_from_cplx_schur_imp::<T>(
1915				H.transpose().reverse_rows_and_cols(),
1916				Conj::Yes,
1917				X.rb_mut(),
1918				par,
1919				params.evd_from_schur,
1920			);
1921		}
1922		linalg::matmul::triangular::matmul(
1923			u.rb_mut(),
1924			BlockStructure::Rectangular,
1925			Accum::Replace,
1926			Z,
1927			BlockStructure::Rectangular,
1928			X.rb().reverse_rows_and_cols(),
1929			BlockStructure::TriangularLower,
1930			one(),
1931			par,
1932		);
1933		normalize_eigenvectors(u.rb_mut(), H);
1934		if !eigenvectors_ok(u.rb()) {
1935			return Err(EvdError::NoConvergence);
1936		}
1937	}
1938	Ok(())
1939}
1940/// computes the matrix $A$'s eigendecomposition
1941///
1942/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1943/// right eigenvectors in $U_R$.
1944///
1945/// every eigenvector is scaled to unit 2-norm. if the Schur iteration does not
1946/// converge, or the back substitution would overflow for a finite input,
1947/// [`EvdError::NoConvergence`] is returned instead of nonfinite entries.
1948#[track_caller]
1949pub fn evd_cplx<T: RealField>(
1950	A: MatRef<'_, Complex<T>>,
1951	s: DiagMut<'_, Complex<T>>,
1952	u_left: Option<MatMut<'_, Complex<T>>>,
1953	u_right: Option<MatMut<'_, Complex<T>>>,
1954	par: Par,
1955	stack: &mut MemStack,
1956	params: Spec<EvdParams, Complex<T>>,
1957) -> Result<(), EvdError> {
1958	let n = A.nrows();
1959	assert!(all(A.nrows() == n, A.ncols() == n, s.dim() == n));
1960	if let Some(u) = u_left.rb() {
1961		assert!(all(u.nrows() == n, u.ncols() == n));
1962	}
1963	if let Some(u) = u_right.rb() {
1964		assert!(all(u.nrows() == n, u.ncols() == n));
1965	}
1966	evd_imp(
1967		A,
1968		s.column_vector_mut(),
1969		None,
1970		u_left,
1971		u_right,
1972		par,
1973		stack,
1974		params.config,
1975	)
1976}
1977/// computes the matrix $A$'s eigendecomposition
1978///
1979/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1980/// right eigenvectors in $U_R$.
1981///
1982/// every eigenvector is scaled to unit 2-norm: a real eigenvalue gets a unit
1983/// norm column, and the two real columns `re` and `im` of a conjugate pair are
1984/// scaled by a common factor so that `re + i * im` has unit 2-norm (the same
1985/// contract `xGEEV` provides). if the Schur iteration does not converge, or the
1986/// back substitution would overflow for a finite input,
1987/// [`EvdError::NoConvergence`] is returned instead of nonfinite entries.
1988#[track_caller]
1989pub fn evd_real<T: RealField>(
1990	A: MatRef<'_, T>,
1991	s_re: DiagMut<'_, T>,
1992	s_im: DiagMut<'_, T>,
1993	u_left: Option<MatMut<'_, T>>,
1994	u_right: Option<MatMut<'_, T>>,
1995	par: Par,
1996	stack: &mut MemStack,
1997	params: Spec<EvdParams, T>,
1998) -> Result<(), EvdError> {
1999	let n = A.nrows();
2000	assert!(all(
2001		A.nrows() == n,
2002		A.ncols() == n,
2003		s_re.dim() == n,
2004		s_im.dim() == n
2005	));
2006	if let Some(u) = u_left.rb() {
2007		assert!(all(u.nrows() == n, u.ncols() == n));
2008	}
2009	if let Some(u) = u_right.rb() {
2010		assert!(all(u.nrows() == n, u.ncols() == n));
2011	}
2012	evd_imp(
2013		A,
2014		s_re.column_vector_mut(),
2015		Some(s_im.column_vector_mut()),
2016		u_left,
2017		u_right,
2018		par,
2019		stack,
2020		params.config,
2021	)
2022}
2023#[cfg(test)]
2024mod general_tests {
2025	use super::*;
2026	use crate::assert;
2027	use crate::stats::prelude::*;
2028	use crate::utils::approx::*;
2029	use dyn_stack::MemBuffer;
2030	fn test_cplx_evd(mat: MatRef<'_, c64>) {
2031		let n = mat.nrows();
2032		let params = Spec::new(EvdParams {
2033			hessenberg: auto!(c64),
2034			schur: auto!(c64),
2035			evd_from_schur: EvdFromSchurParams {
2036				recursion_threshold: 8,
2037				..auto!(c64)
2038			},
2039			..auto!(c64)
2040		});
2041		let approx_eq = CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
2042		let mut s = Mat::zeros(n, n);
2043		{
2044			let mut ul = Mat::zeros(n, n);
2045			let mut ur = Mat::zeros(n, n);
2046			evd_cplx(
2047				mat.as_ref(),
2048				s.as_mut().diagonal_mut(),
2049				Some(ul.as_mut()),
2050				Some(ur.as_mut()),
2051				Par::Seq,
2052				MemStack::new(&mut MemBuffer::new(evd_scratch::<c64>(
2053					n,
2054					ComputeEigenvectors::Yes,
2055					ComputeEigenvectors::Yes,
2056					Par::Seq,
2057					params,
2058				))),
2059				params,
2060			)
2061			.unwrap();
2062			assert!(& ur * & s ~ mat * & ur);
2063			assert!(& s * ul.adjoint() ~ ul.adjoint() * mat);
2064		}
2065	}
2066	fn test_real_evd(mat: MatRef<'_, f64>) {
2067		let n = mat.nrows();
2068		let params = Spec::new(EvdParams {
2069			hessenberg: auto!(f64),
2070			schur: auto!(f64),
2071			evd_from_schur: EvdFromSchurParams {
2072				recursion_threshold: 8,
2073				..auto!(f64)
2074			},
2075			..auto!(f64)
2076		});
2077		let approx_eq =
2078			CwiseMat(ApproxEq::<f64>::eps() * (8.0 * n as f64).sqrt());
2079		let mut s_re = Diag::zeros(n);
2080		let mut s_im = Diag::zeros(n);
2081		{
2082			let mut ul = Mat::zeros(n, n);
2083			let mut ur = Mat::zeros(n, n);
2084			evd_real(
2085				mat.as_ref(),
2086				s_re.as_mut(),
2087				s_im.as_mut(),
2088				Some(ul.as_mut()),
2089				Some(ur.as_mut()),
2090				Par::Seq,
2091				MemStack::new(&mut MemBuffer::new(evd_scratch::<f64>(
2092					n,
2093					ComputeEigenvectors::Yes,
2094					ComputeEigenvectors::Yes,
2095					Par::Seq,
2096					params,
2097				))),
2098				params,
2099			)
2100			.unwrap();
2101			let mut i = 0;
2102			while i < n {
2103				if s_im[i] == 0.0 {
2104					let ur = ur.col(i);
2105					let ul = ul.col(i);
2106					let s = Scale(s_re[i]);
2107					assert!((& ur * s).as_mat() ~ (mat * & ur).as_mat());
2108					assert!(
2109						(& ul.adjoint() * s).as_mat() ~ (& ul.adjoint() * mat).as_mat()
2110					);
2111					i += 1;
2112				} else {
2113					let re = ur.col(i);
2114					let im = ur.col(i + 1);
2115					let ur = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
2116					let re = ul.col(i);
2117					let im = ul.col(i + 1);
2118					let ul = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
2119					let mat =
2120						&Mat::from_fn(n, n, |i, j| c64::from(mat[(i, j)]));
2121					let s = Scale(c64::new(s_re[i], s_im[i]));
2122					let approx_eq =
2123						CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
2124					assert!((ur * s).as_mat() ~ (mat * ur).as_mat());
2125					assert!((ul.adjoint() * s).as_mat() ~ (ul.adjoint() * mat).as_mat());
2126					i += 2;
2127				}
2128			}
2129		}
2130	}
2131	#[test]
2132	fn test_cplx() {
2133		let rng = &mut StdRng::seed_from_u64(1);
2134		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
2135			let mat = CwiseMatDistribution {
2136				nrows: n,
2137				ncols: n,
2138				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
2139			}
2140			.rand::<Mat<c64>>(rng);
2141			test_cplx_evd(mat.as_ref());
2142		}
2143	}
2144	#[test]
2145	fn test_real() {
2146		let rng = &mut StdRng::seed_from_u64(0);
2147		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
2148			let mat = CwiseMatDistribution {
2149				nrows: n,
2150				ncols: n,
2151				dist: StandardNormal,
2152			}
2153			.rand::<Mat<f64>>(rng);
2154			test_real_evd(mat.as_ref());
2155		}
2156	}
2157}
2158#[cfg(test)]
2159mod eig_2131_tests {
2160	//! Regression tests for `tensor4all/tenferro-rs#2131`: the faer
2161	//! eigendecomposition used to return eigenvectors that were neither
2162	//! normalized nor overflow-safe. On the Jordan block `2 I + N` the back
2163	//! substitution grew by `1 / (eps * norm)` per row, overflowing to `NaN`
2164	//! for finite input (`f32` at `n >= 8`, `f64` at `n = 26`).
2165	use super::*;
2166	use crate::assert;
2167	use dyn_stack::MemBuffer;
2168
2169	/// `||A v - lambda v|| / (||A||_1 * ||v||)`, where `||A||_1` is the maximum
2170	/// absolute column sum.
2171	fn relative_residual<T: RealField>(
2172		mat: MatRef<'_, T>,
2173		v: ColRef<'_, T>,
2174		lambda: T,
2175	) -> T {
2176		let n = mat.nrows();
2177		let mut anorm = zero::<T>();
2178		for j in 0..n {
2179			let mut col = zero::<T>();
2180			for i in 0..n {
2181				col += mat[(i, j)].abs1();
2182			}
2183			if col > anorm {
2184				anorm = col;
2185			}
2186		}
2187		let mut vnorm2 = zero::<T>();
2188		let mut rnorm2 = zero::<T>();
2189		for i in 0..n {
2190			let mut acc = zero::<T>();
2191			for j in 0..n {
2192				acc += &mat[(i, j)] * &v[j];
2193			}
2194			acc -= &v[i] * &lambda;
2195			rnorm2 += acc.abs2();
2196			vnorm2 += v[i].abs2();
2197		}
2198		rnorm2.sqrt() / (anorm * vnorm2.sqrt())
2199	}
2200
2201	fn evd_real_vectors<T: RealField>(
2202		mat: MatRef<'_, T>,
2203		params: Spec<EvdParams, T>,
2204	) -> (Diag<T>, Diag<T>, Mat<T>, Mat<T>) {
2205		let n = mat.nrows();
2206		let mut s_re = Diag::zeros(n);
2207		let mut s_im = Diag::zeros(n);
2208		let mut u = Mat::zeros(n, n);
2209		let mut ul = Mat::zeros(n, n);
2210		let mut mem = MemBuffer::new(evd_scratch::<T>(
2211			n,
2212			ComputeEigenvectors::Yes,
2213			ComputeEigenvectors::Yes,
2214			Par::Seq,
2215			params,
2216		));
2217		evd_real(
2218			mat,
2219			s_re.as_mut(),
2220			s_im.as_mut(),
2221			Some(ul.as_mut()),
2222			Some(u.as_mut()),
2223			Par::Seq,
2224			MemStack::new(&mut mem),
2225			params,
2226		)
2227		.unwrap();
2228		(s_re, s_im, u, ul)
2229	}
2230
2231	/// The Jordan block `2 I + N` with ones on the superdiagonal.
2232	fn jordan<T: RealField>(n: usize) -> Mat<T> {
2233		let two = from_f64::<T>(2.0);
2234		let one = one::<T>();
2235		Mat::<T>::from_fn(n, n, |i, j| {
2236			if i == j {
2237				two.copy()
2238			} else if j == i + 1 {
2239				one.copy()
2240			} else {
2241				zero::<T>()
2242			}
2243		})
2244	}
2245
2246	fn check_jordan<T: RealField>(n: usize, threshold: usize, tol: f64) {
2247		let params = Spec::new(EvdParams {
2248			evd_from_schur: EvdFromSchurParams {
2249				recursion_threshold: threshold,
2250				..auto!(T)
2251			},
2252			..auto!(T)
2253		});
2254		let mat = jordan::<T>(n);
2255		let (s_re, _, u, _) = evd_real_vectors(mat.as_ref(), params);
2256		let tol = from_f64::<T>(tol);
2257		for j in 0..n {
2258			let v = u.col(j);
2259			assert!(v.iter().all(|x| x.is_finite()));
2260			assert!((v.norm_l2() - one::<T>()).abs() < from_f64::<T>(1e-5));
2261			assert!(relative_residual(mat.as_ref(), v, s_re[j].copy()) < tol);
2262		}
2263	}
2264
2265	#[test]
2266	fn jordan_eigenvectors_are_finite_and_unit_norm() {
2267		// `threshold = 3` forces the recursive block solver; the default `64`
2268		// keeps small sizes sequential and exercises the recursion for the
2269		// larger ones. Both must produce finite, normalized, backward-stable
2270		// eigenvectors.
2271		for threshold in [3usize, 64] {
2272			for n in [2, 4, 8, 20, 26, 30, 64, 65, 100] {
2273				check_jordan::<f64>(n, threshold, 1e-10);
2274			}
2275			for n in [2, 4, 8, 16, 30] {
2276				check_jordan::<f32>(n, threshold, 1e-3);
2277			}
2278		}
2279	}
2280
2281	#[test]
2282	fn conjugate_pair_eigenvector_has_unit_norm() {
2283		// A real Schur form with four real eigenvalues in front of a 2x2
2284		// conjugate pair, so the complex-shift quasi-triangular solver gets a
2285		// prefix large enough for its recursive branch at `threshold = 4`.
2286		fn run<T: RealField>(threshold: usize) {
2287			let n = 6;
2288			let mat = Mat::<T>::from_fn(n, n, |i, j| {
2289				if i == j {
2290					if i >= 4 {
2291						zero::<T>()
2292					} else {
2293						from_f64::<T>((i + 1) as f64)
2294					}
2295				} else if i == 4 && j == 5 {
2296					one::<T>()
2297				} else if i == 5 && j == 4 {
2298					-one::<T>()
2299				} else if i + 2 <= j {
2300					from_f64::<T>(0.25)
2301				} else {
2302					zero::<T>()
2303				}
2304			});
2305			let params = Spec::new(EvdParams {
2306				evd_from_schur: EvdFromSchurParams {
2307					recursion_threshold: threshold,
2308					..auto!(T)
2309				},
2310				..auto!(T)
2311			});
2312			let (s_re, s_im, u, ul) = evd_real_vectors(mat.as_ref(), params);
2313			let tol = from_f64::<T>(1e-5);
2314			// Right and left eigenvectors share the same conjugate-pair scaling.
2315			let mut j = 0;
2316			while j < n {
2317				let v = u.col(j);
2318				let vl = ul.col(j);
2319				if s_im[j] == zero::<T>() {
2320					assert!((v.norm_l2() - one::<T>()).abs() < tol);
2321					assert!((vl.norm_l2() - one::<T>()).abs() < tol);
2322					assert!(
2323						relative_residual(mat.as_ref(), v, s_re[j].copy())
2324							< from_f64::<T>(1e-4)
2325					);
2326					j += 1;
2327				} else {
2328					let nrm = v.norm_l2().hypot(&u.col(j + 1).norm_l2());
2329					assert!((nrm - one::<T>()).abs() < tol);
2330					let nrm = vl.norm_l2().hypot(&ul.col(j + 1).norm_l2());
2331					assert!((nrm - one::<T>()).abs() < tol);
2332					j += 2;
2333				}
2334			}
2335		}
2336		for threshold in [4usize, 64] {
2337			run::<f64>(threshold);
2338			run::<f32>(threshold);
2339		}
2340	}
2341
2342	#[test]
2343	fn complex_eigenvectors_have_unit_norm() {
2344		// `threshold = 4` drives the complex solver's recursive branch.
2345		fn run<T: RealField>(threshold: usize) {
2346			let n = 6;
2347			let mat = Mat::<Complex<T>>::from_fn(n, n, |i, j| {
2348				Complex::new(
2349					from_f64::<T>(i as f64)
2350						+ from_f64::<T>(j as f64) * from_f64::<T>(0.1),
2351					from_f64::<T>(i as f64 - j as f64) * from_f64::<T>(0.3),
2352				)
2353			});
2354			let params = Spec::new(EvdParams {
2355				evd_from_schur: EvdFromSchurParams {
2356					recursion_threshold: threshold,
2357					..auto!(Complex<T>)
2358				},
2359				..auto!(Complex<T>)
2360			});
2361			let mut s = Diag::zeros(n);
2362			let mut u = Mat::zeros(n, n);
2363			let mut mem = MemBuffer::new(evd_scratch::<Complex<T>>(
2364				n,
2365				ComputeEigenvectors::No,
2366				ComputeEigenvectors::Yes,
2367				Par::Seq,
2368				params,
2369			));
2370			evd_cplx(
2371				mat.as_ref(),
2372				s.as_mut(),
2373				None,
2374				Some(u.as_mut()),
2375				Par::Seq,
2376				MemStack::new(&mut mem),
2377				params,
2378			)
2379			.unwrap();
2380			let tol = from_f64::<T>(1e-5);
2381			for j in 0..n {
2382				assert!((u.col(j).norm_l2() - one::<T>()).abs() < tol);
2383			}
2384		}
2385		for threshold in [4usize, 64] {
2386			run::<f64>(threshold);
2387			run::<f32>(threshold);
2388		}
2389	}
2390}
2391#[cfg(test)]
2392mod self_adjoint_tests {
2393	use super::*;
2394	use crate::assert;
2395	use crate::stats::prelude::*;
2396	use crate::utils::approx::*;
2397	use dyn_stack::MemBuffer;
2398	fn test_self_adjoint_evd<T: ComplexField>(mat: MatRef<'_, T>) {
2399		let n = mat.nrows();
2400		let params = Spec::new(SelfAdjointEvdParams {
2401			recursion_threshold: 8,
2402			..auto!(T)
2403		});
2404		let approx_eq = CwiseMat(
2405			ApproxEq::<T::Real>::eps()
2406				* from_f64::<T::Real>(8.0 * n as f64).sqrt(),
2407		);
2408		let mut s = Mat::zeros(n, n);
2409		{
2410			let mut u = Mat::zeros(n, n);
2411			self_adjoint_evd(
2412				mat.as_ref(),
2413				s.as_mut().diagonal_mut(),
2414				Some(u.as_mut()),
2415				Par::Seq,
2416				MemStack::new(&mut MemBuffer::new(
2417					self_adjoint_evd_scratch::<T>(
2418						n,
2419						ComputeEigenvectors::Yes,
2420						Par::Seq,
2421						params,
2422					),
2423				)),
2424				params,
2425			)
2426			.unwrap();
2427			let reconstructed = &u * &s * u.adjoint();
2428			assert!(reconstructed ~ mat);
2429		}
2430		{
2431			let mut s2 = Mat::zeros(n, n);
2432			self_adjoint_evd(
2433				mat.as_ref(),
2434				s2.as_mut().diagonal_mut(),
2435				None,
2436				Par::Seq,
2437				MemStack::new(&mut MemBuffer::new(
2438					self_adjoint_evd_scratch::<T>(
2439						n,
2440						ComputeEigenvectors::No,
2441						Par::Seq,
2442						params,
2443					),
2444				)),
2445				params,
2446			)
2447			.unwrap();
2448			assert!(s2 ~ s);
2449		}
2450	}
2451	#[test]
2452	fn test_real() {
2453		let rng = &mut StdRng::seed_from_u64(1);
2454		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
2455			let mat = CwiseMatDistribution {
2456				nrows: n,
2457				ncols: n,
2458				dist: StandardNormal,
2459			}
2460			.rand::<Mat<f64>>(rng);
2461			let mat = &mat + mat.adjoint();
2462			test_self_adjoint_evd(mat.as_ref());
2463		}
2464	}
2465	#[test]
2466	fn test_cplx() {
2467		let rng = &mut StdRng::seed_from_u64(1);
2468		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
2469			let mat = CwiseMatDistribution {
2470				nrows: n,
2471				ncols: n,
2472				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
2473			}
2474			.rand::<Mat<c64>>(rng);
2475			let mat = &mat + mat.adjoint();
2476			test_self_adjoint_evd(mat.as_ref());
2477		}
2478	}
2479	#[test]
2480	fn test_special() {
2481		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
2482			test_self_adjoint_evd(Mat::full(n, n, 0.0).as_ref());
2483			test_self_adjoint_evd(Mat::full(n, n, c64::ZERO).as_ref());
2484			test_self_adjoint_evd(Mat::full(n, n, 1.0).as_ref());
2485			test_self_adjoint_evd(Mat::full(n, n, c64::ONE).as_ref());
2486			test_self_adjoint_evd(Mat::<f64>::identity(n, n).as_ref());
2487			test_self_adjoint_evd(Mat::<c64>::identity(n, n).as_ref());
2488		}
2489	}
2490	#[test]
2491	fn test_pinv() {
2492		let rng = &mut StdRng::seed_from_u64(0);
2493		let n = 36;
2494		let mat = CwiseMatDistribution {
2495			nrows: n,
2496			ncols: n,
2497			dist: StandardNormal,
2498		}
2499		.rand::<Mat<f64>>(rng);
2500		let mat = &mat + mat.adjoint();
2501		let pinv = mat.self_adjoint_eigen(Side::Lower).unwrap().pseudoinverse();
2502		let err = &mat * &pinv - Mat::<f64>::identity(n, n);
2503		assert!(err.norm_max() < 1e-10);
2504	}
2505}
2506#[cfg(test)]
2507mod scale_and_empty_tests {
2508	//! Regression tests for `tensor4all/faer#42` and `#44`: the 2x2 shortcut
2509	//! averaged the diagonal as `(a + d) * 0.5`, which overflows for a finite
2510	//! spectrum near the floating-point limit, and the scratch query computed
2511	//! `n - 1` on an empty matrix.
2512	use super::*;
2513	#[test]
2514	fn two_by_two_finite_spectrum_at_the_range_limit() {
2515		let a = crate::mat![[1e308f64, 0.0], [0.0, 1e308]];
2516		let eig = a.self_adjoint_eigen(Side::Lower).unwrap();
2517		assert_eq!(eig.S()[0], 1e308);
2518		assert_eq!(eig.S()[1], 1e308);
2519		let a = crate::mat![[1e38f32, 0.0], [0.0, 1e38]];
2520		let eig = a.self_adjoint_eigen(Side::Lower).unwrap();
2521		assert_eq!(eig.S()[0], 1e38);
2522		assert_eq!(eig.S()[1], 1e38);
2523	}
2524	/// halving a subnormal is not exact, so the direct trace form stays in
2525	/// charge wherever it does not overflow
2526	#[test]
2527	fn two_by_two_subnormal_diagonal() {
2528		let q = f64::from_bits(1);
2529		let a = Mat::<f64>::from_fn(2, 2, |i, j| if i == j { q } else { 0.0 });
2530		let eig = a.self_adjoint_eigen(Side::Lower).unwrap();
2531		assert_eq!(eig.S()[0], q);
2532		assert_eq!(eig.S()[1], q);
2533	}
2534	#[test]
2535	fn empty_matrix() {
2536		let a = Mat::<f64>::zeros(0, 0);
2537		let eig = a.self_adjoint_eigen(Side::Lower).unwrap();
2538		assert_eq!(eig.U().shape(), (0, 0));
2539		assert_eq!(eig.S().dim(), 0);
2540	}
2541}