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