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}
644/// `eps * (|re| + |im|)`, summed after the scaling so that it does not overflow for an entry
645/// near the top of the range.
646fn eps_abs1<T: ComplexField>(x: &T) -> T::Real {
647	let eps = eps::<T::Real>();
648	&eps * &x.real().abs() + &eps * &x.imag().abs()
649}
650/// The shift clamp of the back substitution for an eigenvalue `lambda` of a Schur form `A`:
651/// a shift `A[i, i] - lambda` smaller than this is replaced by it.
652///
653/// It is `eps * max(|lambda|, max |a_ij|)` (`|re| + |im|` for complex values, scaled by `eps`
654/// first so that it does not overflow), over the upper triangle and the `subdiag` subdiagonals
655/// below it (`1` for a real Schur form, `0` for a triangular one). It is relative to the
656/// eigenvalue and the largest entry and not to the size of the matrix. LAPACK's `?TREVC` uses
657/// `max(ulp * (|wr| + |wi|), safmin * n / ulp)`, on a matrix that `?GEEV` has scaled into range
658/// first. faer does not scale, so the floor follows the entries instead; it is at least
659/// `min_positive * eps`, the smallest positive subnormal, so a Schur form of subnormal entries
660/// still has a nonzero clamp.
661///
662/// This was `eps * sum |a_ij|` over the whole Schur form. That grows with `n`, and a clamped row
663/// leaves a residual of the size of the clamp in the eigenvector, so the GET22 residual ratio grew
664/// with `n` too: `(2n - 1) / 2` on a Jordan block, against LAPACK's 0.5 (#112). A clamp this small
665/// is only range safe on a Schur form whose entries are near one, which [`scaled_into_range`]
666/// provides, as `?GEEV`'s scaling does for `?TREVC`.
667fn entry_floor<T: ComplexField>(A: MatRef<'_, T>, subdiag: usize) -> T::Real {
668	let n = A.nrows();
669	let mut m = min_positive::<T::Real>() * eps::<T::Real>();
670	for j in 0..n {
671		for i in 0..Ord::min(j + 1 + subdiag, n) {
672			m = m.fmax(eps_abs1(&A[(i, j)]));
673		}
674	}
675	m
676}
677/// A Schur form whose largest entry is far from one (outside `[2^-20, 2^20]`), scaled by a power
678/// of two so that its largest entry is in `[1, 2)`; `None` when it is already in range. The
679/// eigenvectors do not change. The back substitution's clamp ([`entry_floor`]) is relative to the
680/// entries, so without this a Schur form near the top of the range overflowed in the products
681/// with the back-substituted entries (about `1 / eps`) before the solvers' rescaling ran, and one
682/// near the bottom had a clamp whose complex reciprocal overflows (#112). `?GEEV` scales its
683/// input for `?TREVC` the same way. The power is applied as two factors, so neither overflows.
684fn scaled_into_range<T: ComplexField>(A: MatRef<'_, T>, subdiag: usize) -> Option<Mat<T>> {
685	let n = A.nrows();
686	let rows = |j: usize| Ord::min(j + 1 + subdiag, n);
687	let mut m = zero::<T::Real>();
688	for j in 0..n {
689		for i in 0..rows(j) {
690			m = m.fmax(A[(i, j)].real().abs()).fmax(A[(i, j)].imag().abs());
691		}
692	}
693	let one = one::<T::Real>();
694	let two = from_f64::<T::Real>(2.0);
695	let half = from_f64::<T::Real>(0.5);
696	if m == zero::<T::Real>()
697		|| !m.is_finite()
698		// 2^-20 and 2^20, written out: `f64::powi` needs `std`
699		|| (m >= from_f64::<T::Real>(9.5367431640625e-7) && m <= from_f64::<T::Real>(1048576.0))
700	{
701		return None;
702	}
703	// `m * 2^k` in `[1, 2)`
704	let mut k = 0i32;
705	let mut mm = m;
706	while mm >= two {
707		mm = mm * &half;
708		k -= 1;
709	}
710	while mm < one {
711		mm = mm * &two;
712		k += 1;
713	}
714	let pow = |e: i32| {
715		let base = if e >= 0 { two.copy() } else { half.copy() };
716		let mut f = one.copy();
717		for _ in 0..e.unsigned_abs() {
718			f = f * &base;
719		}
720		f
721	};
722	let (fa, fb) = (pow(k / 2), pow(k - k / 2));
723	Some(Mat::from_fn(n, n, |i, j| {
724		if i < rows(j) {
725			A[(i, j)].mul_real(&fa).mul_real(&fb)
726		} else {
727			zero::<T>()
728		}
729	}))
730}
731pub(crate) fn evd_from_real_schur_imp<T: RealField>(
732	A: MatRef<'_, T>,
733	V: MatMut<'_, T>,
734	mut par: Par,
735	params: EvdFromSchurParams,
736) {
737	let one = one::<T>;
738	let zero = zero::<T>;
739	let mut V = V;
740	let n = A.nrows();
741	let scaled = scaled_into_range(A, 1);
742	let A = scaled.as_ref().map_or(A, |s| s.as_ref());
743	let floor = entry_floor(A, 1);
744	let mut k = n;
745	loop {
746		if k == 0 {
747			break;
748		}
749		k -= 1;
750		if k == 0 || A[(k, k - 1)] == zero() {
751			let ref p = A[(k, k)].copy();
752			V[(k, k)] = one();
753			for i in 0..k {
754				V[(i, k)] = -&A[(i, k)];
755			}
756			let scale = solve_real_shifted_upper_quasi_triangular_system(
757				A.submatrix(0, 0, k, k),
758				p.copy(),
759				V.rb_mut().subrows_mut(0, k).col_mut(k),
760				eps_abs1(p).fmax(floor.copy()),
761				par.reborrow(),
762				params,
763			);
764			// The pivot is not part of the back-substituted slice, so it must
765			// carry the accumulated rescaling factor for the column direction
766			// to be preserved.
767			V[(k, k)] *= scale;
768		} else {
769			let ref p = A[(k, k)].copy();
770			let ref q = A[(k, k - 1)].abs().sqrt() * A[(k - 1, k)].abs().sqrt();
771			if A[(k - 1, k)].abs() >= A[(k, k - 1)].abs() {
772				V[(k - 1, k - 1)] = one();
773				V[(k, k)] = q / &A[(k - 1, k)];
774			} else {
775				V[(k - 1, k - 1)] = -q / &A[(k, k - 1)];
776				V[(k, k)] = one();
777			}
778			V[(k - 1, k)] = zero();
779			V[(k, k - 1)] = zero();
780			for i in 0..k - 1 {
781				V[(i, k - 1)] = -&V[(k - 1, k - 1)] * &A[(i, k - 1)];
782				V[(i, k)] = -&V[(k, k)] * &A[(i, k)];
783			}
784			let scale = solve_cplx_shifted_upper_quasi_triangular_system(
785				A.submatrix(0, 0, k - 1, k - 1),
786				p.copy(),
787				q.copy(),
788				V.rb_mut().submatrix_mut(0, k - 1, k - 1, 2),
789				(eps_abs1(p) + eps_abs1(q)).fmax(floor.copy()),
790				par.reborrow(),
791				params,
792			);
793			V[(k - 1, k - 1)] *= scale.copy();
794			V[(k, k)] *= scale;
795			k -= 1;
796		}
797	}
798}
799pub(crate) fn evd_from_cplx_schur_imp<T: ComplexField>(
800	A: MatRef<'_, T>,
801	conj_A: Conj,
802	V: MatMut<'_, T>,
803	mut par: Par,
804	params: EvdFromSchurParams,
805) {
806	let one = one::<T>;
807	let mut V = V;
808	let n = A.nrows();
809	let scaled = scaled_into_range(A, 0);
810	let A = scaled.as_ref().map_or(A, |s| s.as_ref());
811	let floor = entry_floor(A, 0);
812	let mut k = n;
813	loop {
814		if k == 0 {
815			break;
816		}
817		k -= 1;
818		let ref p = if conj_A == Conj::Yes {
819			A[(k, k)].conj()
820		} else {
821			A[(k, k)].copy()
822		};
823		V[(k, k)] = one();
824		if conj_A == Conj::Yes {
825			for i in 0..k {
826				V[(i, k)] = -A[(i, k)].conj();
827			}
828		} else {
829			for i in 0..k {
830				V[(i, k)] = -&A[(i, k)];
831			}
832		}
833		let scale = solve_shifted_upper_triangular_system(
834			A.submatrix(0, 0, k, k),
835			conj_A,
836			p.copy(),
837			V.rb_mut().subrows_mut(0, k).col_mut(k),
838			eps_abs1(p).fmax(floor.copy()),
839			par.reborrow(),
840			params,
841		);
842		let scale: T = scale.to_cplx();
843		V[(k, k)] *= scale;
844	}
845}
846fn solve_real_shifted_upper_quasi_triangular_system<T: RealField>(
847	A: MatRef<'_, T>,
848	p: T,
849	x: ColMut<'_, T>,
850	eps_norm: T,
851	mut par: Par,
852	params: EvdFromSchurParams,
853) -> T {
854	let n = A.nrows();
855	let one = one::<T>;
856	let zero = zero::<T>;
857	let ref p = p;
858	let mut x = x;
859	// While the shift `A[i, i] - p` is clamped to `eps_norm`, back
860	// substitution can grow the solution by `1 / eps_norm` per row and
861	// overflow for finite input. Whenever a computed entry exceeds this bound
862	// the whole slice is rescaled; the factor is also returned so the caller
863	// can apply it to the pivot entry, keeping the eigenvector direction.
864	let big = sqrt_max_positive::<T>();
865	let mut scale = one();
866	if par.degree() == 0 || n < params.recursion_threshold {
867		let mut i = n;
868		loop {
869			if i == 0 {
870				break;
871			}
872			i -= 1;
873			if i == 0 || A[(i, i - 1)] == zero() {
874				let dot = linalg::matmul::dot::inner_prod(
875					A.row(i).subcols(i + 1, n - i - 1),
876					Conj::No,
877					x.rb().subrows(i + 1, n - i - 1),
878					Conj::No,
879				);
880				x[i] -= dot;
881				let mut z = &A[(i, i)] - p;
882				if z.abs() < eps_norm {
883					z = eps_norm.copy();
884				}
885				if x[i] != zero() {
886					x[i] /= z;
887				}
888				let m = x[i].abs();
889				if m > big {
890					let r = m.recip();
891					for j in 0..x.nrows() {
892						x[j] *= r.copy();
893					}
894					scale *= r;
895				}
896			} else {
897				let (dot0, dot1) = dot2x1(
898					A.row(i - 1).subcols(i + 1, n - i - 1),
899					A.row(i).subcols(i + 1, n - i - 1),
900					x.rb().subrows(i + 1, n - i - 1),
901				);
902				x[i - 1] -= dot0;
903				x[i] -= dot1;
904				// The same `?LALN2` solve as for a complex shift, with a zero
905				// imaginary part. The inverse through `a^2 - b c` underflowed to a
906				// zero determinant for a block whose entries are below the square
907				// root of the smallest positive number, and returned `inf`.
908				let ((y0, _), (y1, _)) = solve_shifted_2x2_cplx(
909					(&A[(i, i)] - p, zero()),
910					A[(i - 1, i)].copy(),
911					A[(i, i - 1)].copy(),
912					(x[i - 1].copy(), zero()),
913					(x[i].copy(), zero()),
914					&eps_norm,
915				);
916				(x[i - 1], x[i]) = (y0, y1);
917				let a0 = x[i - 1].abs();
918				let a1 = x[i].abs();
919				let m = if a0 > a1 { a0 } else { a1 };
920				if m > big {
921					let r = m.recip();
922					for j in 0..x.nrows() {
923						x[j] *= r.copy();
924					}
925					scale *= r;
926				}
927				i -= 1;
928			}
929		}
930		scale
931	} else {
932		let mut mid = n / 2;
933		if A[(mid, mid - 1)] != zero() {
934			mid -= 1;
935		}
936		let (A00, A01, _, A11) = A.split_at(mid, mid);
937		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
938		let f1 = solve_real_shifted_upper_quasi_triangular_system(
939			A11,
940			p.copy(),
941			x1.rb_mut(),
942			eps_norm.copy(),
943			par.reborrow(),
944			params,
945		);
946		// The right block may have been rescaled; the left block's pending
947		// right-hand side must carry the same factor for the coupled system to
948		// stay consistent.
949		for j in 0..x0.nrows() {
950			x0[j] *= f1.copy();
951		}
952		linalg::matmul::matmul(
953			x0.rb_mut().as_mat_mut(),
954			Accum::Add,
955			A01,
956			x1.rb().as_mat(),
957			-one(),
958			par.reborrow(),
959		);
960		let f0 = solve_real_shifted_upper_quasi_triangular_system(
961			A00,
962			p.copy(),
963			x0.rb_mut(),
964			eps_norm,
965			par,
966			params,
967		);
968		for j in 0..x1.nrows() {
969			x1[j] *= f0.copy();
970		}
971		f0 * f1
972	}
973}
974/// Solves `[[a, b], [c, a]] [x0, x1]^T = [r0, r1]^T` for complex `a` (as `(re, im)`), real
975/// `b`, `c` and complex right-hand sides, as LAPACK's `?LALN2` does for a complex shift of a
976/// 2 x 2 block: when every entry is below `smin` in `|re| + |im|`, the block is taken as
977/// `smin * I`; otherwise Gaussian elimination with complete pivoting, with the second pivot
978/// raised to `smin` when it is smaller. The caller rescales a large solution.
979fn solve_shifted_2x2_cplx<T: RealField>(
980	a: (T, T),
981	b: T,
982	c: T,
983	r0: (T, T),
984	r1: (T, T),
985	smin: &T,
986) -> ((T, T), (T, T)) {
987	type C<T> = (T, T);
988	fn abs1<T: RealField>(z: &C<T>) -> T {
989		z.0.abs() + z.1.abs()
990	}
991	fn mul<T: RealField>(x: &C<T>, y: &C<T>) -> C<T> {
992		(&x.0 * &y.0 - &x.1 * &y.1, &x.0 * &y.1 + &x.1 * &y.0)
993	}
994	fn sub<T: RealField>(x: &C<T>, y: &C<T>) -> C<T> {
995		(&x.0 - &y.0, &x.1 - &y.1)
996	}
997	fn div<T: RealField>(x: &C<T>, y: &C<T>) -> C<T> {
998		// scaled by the larger component of `y`, so `|y|^2` neither overflows nor underflows
999		let s = y.0.abs().fmax(y.1.abs());
1000		let (yr, yi) = (&y.0 / &s, &y.1 / &s);
1001		let (xr, xi) = (&x.0 / &s, &x.1 / &s);
1002		let d = &yr * &yr + &yi * &yi;
1003		((&xr * &yr + &xi * &yi) / &d, (&xi * &yr - &xr * &yi) / &d)
1004	}
1005	let zero = zero::<T>;
1006	let m = [[(a.0.copy(), a.1.copy()), (b, zero())], [(c, zero()), a]];
1007	let (mut p, mut q) = (0usize, 0usize);
1008	let mut cmax = abs1(&m[0][0]);
1009	for (i, j) in [(0, 1), (1, 0)] {
1010		let v = abs1(&m[i][j]);
1011		if v > cmax {
1012			cmax = v;
1013			(p, q) = (i, j);
1014		}
1015	}
1016	if cmax < *smin {
1017		let inv = smin.recip();
1018		return (
1019			(&r0.0 * &inv, &r0.1 * &inv),
1020			(&r1.0 * &inv, &r1.1 * &inv),
1021		);
1022	}
1023	// pivot row `p`, pivot column `q`; the other row and column are `1 - p`, `1 - q`
1024	let (rp, ro) = if p == 0 { (r0, r1) } else { (r1, r0) };
1025	let u11 = (m[p][q].0.copy(), m[p][q].1.copy());
1026	let u12 = (m[p][1 - q].0.copy(), m[p][1 - q].1.copy());
1027	let l21 = div(&m[1 - p][q], &u11);
1028	let mut u22 = sub(&m[1 - p][1 - q], &mul(&l21, &u12));
1029	if abs1(&u22) < *smin {
1030		u22 = (smin.copy(), zero());
1031	}
1032	let y2 = sub(&ro, &mul(&l21, &rp));
1033	let x2 = div(&y2, &u22);
1034	let x1 = div(&sub(&rp, &mul(&u12, &x2)), &u11);
1035	// unknown `q` is `x1`, the other is `x2`
1036	if q == 0 { (x1, x2) } else { (x2, x1) }
1037}
1038fn solve_cplx_shifted_upper_quasi_triangular_system<T: RealField>(
1039	A: MatRef<'_, T>,
1040	p: T,
1041	q: T,
1042	x: MatMut<'_, T>,
1043	eps_norm: T,
1044	mut par: Par,
1045	params: EvdFromSchurParams,
1046) -> T {
1047	let n = A.nrows();
1048	let one = one::<T>;
1049	let zero = zero::<T>;
1050	let ref p = p;
1051	let ref q = q;
1052	let mut x = x;
1053	let big = sqrt_max_positive::<T>();
1054	let mut scale = one();
1055	if par.degree() == 0 || n < params.recursion_threshold {
1056		let mut i = n;
1057		loop {
1058			if i == 0 {
1059				break;
1060			}
1061			i -= 1;
1062			if i == 0 || A[(i, i - 1)] == zero() {
1063				let (re, im) = dot2x1(
1064					x.rb().subrows(i + 1, n - i - 1).col(0).transpose(),
1065					x.rb().subrows(i + 1, n - i - 1).col(1).transpose(),
1066					A.row(i).subcols(i + 1, n - i - 1).transpose(),
1067				);
1068				x[(i, 0)] = &x[(i, 0)] - re;
1069				x[(i, 1)] = &x[(i, 1)] - im;
1070				let mut z_re = &A[(i, i)] - p;
1071				let mut z_im = -q;
1072				let mut z = z_re.hypot(&z_im);
1073				if z < eps_norm {
1074					z_re = eps_norm.copy();
1075					z_im = zero();
1076					z = z_re.copy();
1077				}
1078				let ref z = z;
1079				let ref z_re = (z_re / z) / z;
1080				let ref z_im = (-z_im / z) / z;
1081				let ref x_re = x[(i, 0)].copy();
1082				let ref x_im = x[(i, 1)].copy();
1083				x[(i, 0)] = x_re * z_re - x_im * z_im;
1084				x[(i, 1)] = x_re * z_im + x_im * z_re;
1085				let a0 = x[(i, 0)].abs();
1086				let a1 = x[(i, 1)].abs();
1087				let m = if a0 > a1 { a0 } else { a1 };
1088				if m > big {
1089					let r = m.recip();
1090					for j in 0..x.nrows() {
1091						x[(j, 0)] *= r.copy();
1092						x[(j, 1)] *= r.copy();
1093					}
1094					scale *= r;
1095				}
1096			} else {
1097				let (re0, re1, im0, im1) = dot2x2(
1098					A.row(i - 1).subcols(i + 1, n - i - 1),
1099					A.row(i).subcols(i + 1, n - i - 1),
1100					x.rb().col(0).subrows(i + 1, n - i - 1),
1101					x.rb().col(1).subrows(i + 1, n - i - 1),
1102				);
1103				x[(i - 1, 0)] = &x[(i - 1, 0)] - re0;
1104				x[(i - 1, 1)] = &x[(i - 1, 1)] - im0;
1105				x[(i, 0)] = &x[(i, 0)] - re1;
1106				x[(i, 1)] = &x[(i, 1)] - im1;
1107				let ref a_re = &A[(i, i)] - p;
1108				let ref a_im = -q;
1109				let ref b = A[(i - 1, i)];
1110				let ref c = A[(i, i - 1)];
1111				let ref r0_re = x[(i - 1, 0)];
1112				let ref r0_im = x[(i - 1, 1)];
1113				let ref r1_re = x[(i, 0)];
1114				let ref r1_im = x[(i, 1)];
1115				// `?LALN2`'s complex 2 x 2 solve (#145): complete pivoting, the
1116				// second pivot kept at least `eps_norm`, and `eps_norm * I` for a
1117				// block whose entries are all below it. Inverting the block through
1118				// its determinant compared that determinant (a product of two
1119				// entries) with the entry-sized `eps_norm`, and clamping only the
1120				// determinant does not give a perturbed system that is solved.
1121				let ((y0_re, y0_im), (y1_re, y1_im)) = solve_shifted_2x2_cplx(
1122					(a_re.copy(), a_im.copy()),
1123					b.copy(),
1124					c.copy(),
1125					(r0_re.copy(), r0_im.copy()),
1126					(r1_re.copy(), r1_im.copy()),
1127					&eps_norm,
1128				);
1129				x[(i - 1, 0)] = y0_re;
1130				x[(i - 1, 1)] = y0_im;
1131				x[(i, 0)] = y1_re;
1132				x[(i, 1)] = y1_im;
1133				let mut m = x[(i - 1, 0)].abs();
1134				let v = x[(i - 1, 1)].abs();
1135				if v > m {
1136					m = v;
1137				}
1138				let v = x[(i, 0)].abs();
1139				if v > m {
1140					m = v;
1141				}
1142				let v = x[(i, 1)].abs();
1143				if v > m {
1144					m = v;
1145				}
1146				if m > big {
1147					let r = m.recip();
1148					for j in 0..x.nrows() {
1149						x[(j, 0)] *= r.copy();
1150						x[(j, 1)] *= r.copy();
1151					}
1152					scale *= r;
1153				}
1154				i -= 1;
1155			}
1156		}
1157		scale
1158	} else {
1159		let mut mid = n / 2;
1160		if A[(mid, mid - 1)] != zero() {
1161			mid -= 1;
1162		}
1163		let (A00, A01, _, A11) = A.split_at(mid, mid);
1164		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
1165		let f1 = solve_cplx_shifted_upper_quasi_triangular_system(
1166			A11,
1167			p.copy(),
1168			q.copy(),
1169			x1.rb_mut(),
1170			eps_norm.copy(),
1171			par.reborrow(),
1172			params,
1173		);
1174		for j in 0..x0.nrows() {
1175			x0[(j, 0)] *= f1.copy();
1176			x0[(j, 1)] *= f1.copy();
1177		}
1178		linalg::matmul::matmul(
1179			x0.rb_mut(),
1180			Accum::Add,
1181			A01,
1182			x1.rb(),
1183			-one(),
1184			par.reborrow(),
1185		);
1186		let f0 = solve_cplx_shifted_upper_quasi_triangular_system(
1187			A00,
1188			p.copy(),
1189			q.copy(),
1190			x0.rb_mut(),
1191			eps_norm,
1192			par,
1193			params,
1194		);
1195		for j in 0..x1.nrows() {
1196			x1[(j, 0)] *= f0.copy();
1197			x1[(j, 1)] *= f0.copy();
1198		}
1199		f0 * f1
1200	}
1201}
1202fn solve_shifted_upper_triangular_system<T: ComplexField>(
1203	A: MatRef<'_, T>,
1204	conj_A: Conj,
1205	p: T,
1206	x: ColMut<'_, T>,
1207	eps_norm: T::Real,
1208	mut par: Par,
1209	params: EvdFromSchurParams,
1210) -> T::Real {
1211	let n = A.nrows();
1212	let mut scale = one::<T::Real>();
1213	let one = one::<T>;
1214	let zero = zero::<T>;
1215	let ref p = p;
1216	let mut x = x;
1217	let big = sqrt_max_positive::<T::Real>();
1218	if par.degree() == 0 || n < params.recursion_threshold {
1219		let mut i = n;
1220		loop {
1221			if i == 0 {
1222				break;
1223			}
1224			i -= 1;
1225			let dot = linalg::matmul::dot::inner_prod(
1226				A.row(i).subcols(i + 1, n - i - 1),
1227				conj_A,
1228				x.rb().subrows(i + 1, n - i - 1),
1229				Conj::No,
1230			);
1231			x[i] -= dot;
1232			let mut z = if conj_A == Conj::Yes {
1233				A[(i, i)].conj()
1234			} else {
1235				A[(i, i)].copy()
1236			} - p;
1237			if z.abs() < eps_norm {
1238				z = eps_norm.to_cplx();
1239			}
1240			if x[i] != zero() {
1241				x[i] *= z.recip();
1242			}
1243			let m = x[i].abs();
1244			if m > big {
1245				let r = m.recip();
1246				let rc: T = r.to_cplx();
1247				for j in 0..x.nrows() {
1248					x[j] *= rc.copy();
1249				}
1250				scale *= r;
1251			}
1252		}
1253		scale
1254	} else {
1255		let mid = n / 2;
1256		let (A00, A01, _, A11) = A.split_at(mid, mid);
1257		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
1258		let f1 = solve_shifted_upper_triangular_system(
1259			A11,
1260			conj_A,
1261			p.copy(),
1262			x1.rb_mut(),
1263			eps_norm.copy(),
1264			par.reborrow(),
1265			params,
1266		);
1267		let f1c: T = f1.to_cplx();
1268		for j in 0..x0.nrows() {
1269			x0[j] *= f1c.copy();
1270		}
1271		linalg::matmul::matmul_with_conj(
1272			x0.rb_mut().as_mat_mut(),
1273			Accum::Add,
1274			A01,
1275			conj_A,
1276			x1.rb().as_mat(),
1277			Conj::No,
1278			-one(),
1279			par.reborrow(),
1280		);
1281		let f0 = solve_shifted_upper_triangular_system(
1282			A00,
1283			conj_A,
1284			p.copy(),
1285			x0.rb_mut(),
1286			eps_norm,
1287			par,
1288			params,
1289		);
1290		let f0c: T = f0.to_cplx();
1291		for j in 0..x1.nrows() {
1292			x1[j] *= f0c.copy();
1293		}
1294		f0 * f1
1295	}
1296}
1297/// computes the layout of the workspace required to compute a matrix's
1298/// eigendecomposition
1299pub fn evd_scratch<T: ComplexField>(
1300	dim: usize,
1301	eigen_left: ComputeEigenvectors,
1302	eigen_right: ComputeEigenvectors,
1303	par: ParShape,
1304	params: Spec<EvdParams, T>,
1305) -> StackReq {
1306	let n = dim;
1307	if n == 0 {
1308		return StackReq::EMPTY;
1309	}
1310	let compute_eigen = eigen_left == ComputeEigenvectors::Yes
1311		|| eigen_right == ComputeEigenvectors::Yes;
1312	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
1313		n - 1,
1314		n - 1,
1315	);
1316	let H = temp_mat_scratch::<T>(n, n);
1317	let X = H;
1318	let Z = temp_mat_scratch::<T>(n, if compute_eigen { n } else { 0 });
1319	let householder = temp_mat_scratch::<T>(bs, n);
1320	let apply = linalg::householder::apply_block_householder_sequence_on_the_right_in_place_scratch::<T>(n - 1, bs, n - 1);
1321	StackReq::all_of(&[
1322		H,
1323		Z,
1324		StackReq::any_of(&[
1325			householder.and(
1326				hessenberg::hessenberg_in_place_scratch::<T>(
1327					n,
1328					bs,
1329					par,
1330					params.hessenberg.into(),
1331				)
1332				.or(apply),
1333			),
1334			schur::multishift_qr_scratch::<T>(
1335				n,
1336				n,
1337				compute_eigen,
1338				compute_eigen,
1339				par,
1340				params.schur,
1341			),
1342			X,
1343		]),
1344	])
1345}
1346/// Workspace for [`hessenberg_schur_real`] and
1347/// [`hessenberg_schur_cplx`].
1348///
1349/// # Examples
1350/// ```
1351/// use faer::Par;
1352/// use faer::linalg::evd::hessenberg_schur_scratch;
1353/// let req =
1354/// 	hessenberg_schur_scratch::<f64>(3, true, faer::ParShape::Seq, Default::default());
1355/// let _buffer = dyn_stack::MemBuffer::new(req);
1356/// ```
1357pub fn hessenberg_schur_scratch<T: ComplexField>(
1358	dim: usize,
1359	compute_z: bool,
1360	par: ParShape,
1361	params: Spec<SchurParams, T>,
1362) -> StackReq {
1363	schur::multishift_qr_scratch::<T>(dim, dim, true, compute_z, par, *params)
1364}
1365/// Overwrites an upper-Hessenberg `h` with its real Schur form `t`.
1366/// Updates optional `z` to `z * q`, where `h_original = q * t * q.transpose()`.
1367/// Real conjugate pairs have equal diagonal entries, opposite-sign
1368/// off-diagonals, and eigenvalues `wr ± i wi`, with the positive imaginary part
1369/// first. `wr` and `wi` each have length `n`; `z`, when present, is `n` by `n`.
1370///
1371/// # Panics
1372/// Panics on incompatible dimensions or insufficient scratch. The input must
1373/// be upper Hessenberg (entries below its first subdiagonal must be zero).
1374///
1375/// # Errors
1376/// Returns [`EvdError::NoConvergence`] for nonfinite input or QR
1377/// nonconvergence. After nonconvergence, `h` and `z` contain the accumulated
1378/// similarity updates.
1379///
1380/// # Examples
1381/// ```
1382/// use dyn_stack::{MemBuffer, MemStack};
1383/// use faer::linalg::evd::*;
1384/// use faer::{Col, Mat, Par};
1385/// let mut h = faer::mat![[1.0f64, 2.0], [-2.0, 1.0]];
1386/// let mut z = Mat::identity(2, 2);
1387/// let (mut wr, mut wi) = (Col::zeros(2), Col::zeros(2));
1388/// let mut mem = MemBuffer::new(hessenberg_schur_scratch::<f64>(
1389/// 	2,
1390/// 	true,
1391/// 	faer::ParShape::Seq,
1392/// 	Default::default(),
1393/// ));
1394/// hessenberg_schur_real(
1395/// 	h.as_mut(),
1396/// 	Some(z.as_mut()),
1397/// 	wr.as_mut(),
1398/// 	wi.as_mut(),
1399/// 	Par::Seq,
1400/// 	MemStack::new(&mut mem),
1401/// 	Default::default(),
1402/// )
1403/// .unwrap();
1404/// assert!(wi[0] > 0.0 && wi[1] < 0.0);
1405/// ```
1406pub fn hessenberg_schur_real<T: RealField>(
1407	mut h: MatMut<'_, T>,
1408	z: Option<MatMut<'_, T>>,
1409	mut wr: ColMut<'_, T>,
1410	mut wi: ColMut<'_, T>,
1411	par: Par,
1412	stack: &mut MemStack,
1413	params: Spec<SchurParams, T>,
1414) -> Result<(), EvdError> {
1415	let n = h.nrows();
1416	assert!(h.ncols() == n && wr.nrows() == n && wi.nrows() == n);
1417	if let Some(z) = z.rb() {
1418		assert!(z.nrows() == n && z.ncols() == n);
1419	}
1420	if !h.is_all_finite() {
1421		return Err(EvdError::NoConvergence);
1422	}
1423	if n <= 1 {
1424		if n == 1 {
1425			wr[0] = h[(0, 0)].copy();
1426			wi[0] = zero();
1427		}
1428		return Ok(());
1429	}
1430	let (info, _, _) = schur::real_schur::multishift_qr(
1431		true,
1432		h.rb_mut(),
1433		z,
1434		wr,
1435		wi,
1436		0,
1437		n,
1438		par,
1439		stack,
1440		*params,
1441	);
1442	if info != 0 {
1443		return Err(EvdError::NoConvergence);
1444	}
1445	// The QR kernel uses entries below the Hessenberg band as workspace.
1446	for j in 0..n {
1447		for i in j + 2..n {
1448			h[(i, j)] = zero();
1449		}
1450	}
1451	Ok(())
1452}
1453/// Complex counterpart of [`hessenberg_schur_real`]: `h` becomes upper
1454/// triangular, `w` receives its diagonal, and optional `z` becomes `z * q`.
1455/// The similarity is `h_original = q * t * q.adjoint()`.
1456///
1457/// # Panics
1458/// Panics on incompatible dimensions, a real scalar type, or insufficient
1459/// scratch; `h` must be upper Hessenberg.
1460///
1461/// # Errors
1462/// Returns [`EvdError::NoConvergence`] for nonfinite input or QR
1463/// nonconvergence; accumulated similarity updates remain in `h` and `z`.
1464///
1465/// # Examples
1466/// ```
1467/// use dyn_stack::{MemBuffer, MemStack};
1468/// use faer::linalg::evd::*;
1469/// use faer::{Col, Mat, Par, c64};
1470/// let mut h = Mat::<c64>::identity(2, 2);
1471/// let mut w = Col::zeros(2);
1472/// let mut mem = MemBuffer::new(hessenberg_schur_scratch::<c64>(
1473/// 	2,
1474/// 	false,
1475/// 	faer::ParShape::Seq,
1476/// 	Default::default(),
1477/// ));
1478/// hessenberg_schur_cplx(
1479/// 	h.as_mut(),
1480/// 	None,
1481/// 	w.as_mut(),
1482/// 	Par::Seq,
1483/// 	MemStack::new(&mut mem),
1484/// 	Default::default(),
1485/// )
1486/// .unwrap();
1487/// assert_eq!(w[0], c64::new(1.0, 0.0));
1488/// ```
1489pub fn hessenberg_schur_cplx<T: ComplexField>(
1490	mut h: MatMut<'_, T>,
1491	z: Option<MatMut<'_, T>>,
1492	mut w: ColMut<'_, T>,
1493	par: Par,
1494	stack: &mut MemStack,
1495	params: Spec<SchurParams, T>,
1496) -> Result<(), EvdError> {
1497	assert!(!T::IS_REAL);
1498	let n = h.nrows();
1499	assert!(h.ncols() == n && w.nrows() == n);
1500	if let Some(z) = z.rb() {
1501		assert!(z.nrows() == n && z.ncols() == n);
1502	}
1503	if !h.is_all_finite() {
1504		return Err(EvdError::NoConvergence);
1505	}
1506	if n <= 1 {
1507		if n == 1 {
1508			w[0] = h[(0, 0)].copy();
1509		}
1510		return Ok(());
1511	}
1512	let (info, _, _) = schur::complex_schur::multishift_qr(
1513		true,
1514		h.rb_mut(),
1515		z,
1516		w,
1517		0,
1518		n,
1519		par,
1520		stack,
1521		*params,
1522	);
1523	if info != 0 {
1524		return Err(EvdError::NoConvergence);
1525	}
1526	// Return a full triangular matrix, without the QR kernel's workspace.
1527	for j in 0..n {
1528		for i in j + 1..n {
1529			h[(i, j)] = zero();
1530		}
1531	}
1532	Ok(())
1533}
1534/// An adjacent real Schur block swap was rejected as ill-conditioned.
1535/// `position` is the current first row of the moving block.
1536///
1537/// # Examples
1538/// ```
1539/// use faer::linalg::evd::SchurReorderError;
1540/// let err = SchurReorderError { position: 2 };
1541/// assert_eq!(err.position, 2);
1542/// ```
1543#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1544pub struct SchurReorderError {
1545	/// Current first row of the moving block, after the last successful swap.
1546	pub position: usize,
1547}
1548/// Workspace for [`reorder_schur_real`] and [`reorder_schur_cplx`]: a copy of
1549/// the rows and columns of `t` and the columns of `q` that one adjacent swap
1550/// updates, so that a rejected swap can be undone.
1551///
1552/// # Examples
1553/// ```
1554/// let req = faer::linalg::evd::reorder_schur_scratch::<f64>(3);
1555/// let _buffer = dyn_stack::MemBuffer::new(req);
1556/// ```
1557pub fn reorder_schur_scratch<T: ComplexField>(dim: usize) -> StackReq {
1558	StackReq::all_of(&[
1559		temp_mat_scratch::<T>(4, dim),
1560		temp_mat_scratch::<T>(dim, 4),
1561		temp_mat_scratch::<T>(dim, 4),
1562	])
1563}
1564/// Moves a real Schur block from `ifst` to `ilst`, updating optional `q`.
1565/// Indices are zero-based rows; the second row of a pair denotes its first row.
1566/// Like `xTREXC`, when moving down across a differently sized destination
1567/// block, the final first row is adjusted so the moving block follows that
1568/// block. Returns the final first row; conjugate pairs are never split.
1569///
1570/// # Panics
1571/// Panics on invalid dimensions/indices. `t` must be in standardized real Schur
1572/// form.
1573///
1574/// # Errors
1575/// Returns [`SchurReorderError`] when an adjacent swap is rejected. As in
1576/// LAPACK `xTREXC`/`xLAEXC`, a swap involving a 2 by 2 block is performed
1577/// provisionally on a copy of its diagonal window and rejected when the
1578/// provisional entries that must vanish (and, for a moved 1 by 1 block, its
1579/// diagonal value) are off by more than `max(10 eps |window|_max,
1580/// safmin / eps)`. A swap whose update of `t` or `q` overflows is rejected
1581/// too. `t` and `q` are left exactly as after the last successful swap (in
1582/// valid Schur form, with the block standardization); the error gives the
1583/// moving block's current position. On success, `t` and `q` are finite.
1584///
1585/// # Examples
1586/// ```
1587/// use dyn_stack::{MemBuffer, MemStack};
1588/// use faer::Par;
1589/// use faer::linalg::evd::*;
1590/// let mut t = faer::mat![[1.0f64, 0.0], [0.0, 2.0]];
1591/// let mut mem = MemBuffer::new(reorder_schur_scratch::<f64>(2));
1592/// assert_eq!(
1593/// 	reorder_schur_real(
1594/// 		t.as_mut(),
1595/// 		None,
1596/// 		0,
1597/// 		1,
1598/// 		Par::Seq,
1599/// 		MemStack::new(&mut mem)
1600/// 	)
1601/// 	.unwrap(),
1602/// 	1
1603/// );
1604/// assert_eq!(t[(1, 1)], 1.0);
1605/// ```
1606pub fn reorder_schur_real<T: RealField>(
1607	mut t: MatMut<'_, T>,
1608	mut q: Option<MatMut<'_, T>>,
1609	mut ifst: usize,
1610	mut ilst: usize,
1611	_par: Par,
1612	stack: &mut MemStack,
1613) -> Result<usize, SchurReorderError> {
1614	let n = t.nrows();
1615	assert!(t.ncols() == n && ifst < n && ilst < n);
1616	if let Some(q) = q.rb() {
1617		assert!(q.nrows() == n && q.ncols() == n);
1618	}
1619	let (mut saved_rows, stack) = temp_mat_zeroed::<T, _, _>(4, n, stack);
1620	let (mut saved_cols, stack) = temp_mat_zeroed::<T, _, _>(n, 4, stack);
1621	let (mut saved_q, _) = temp_mat_zeroed::<T, _, _>(n, 4, stack);
1622	let mut saved_rows = saved_rows.as_mat_mut();
1623	let mut saved_cols = saved_cols.as_mat_mut();
1624	let mut saved_q = saved_q.as_mat_mut();
1625	if ifst > 0 && t[(ifst, ifst - 1)] != zero() {
1626		ifst -= 1;
1627	}
1628	if ilst > 0 && t[(ilst, ilst - 1)] != zero() {
1629		ilst -= 1;
1630	}
1631	let block_size = |t: MatRef<'_, T>, i: usize| {
1632		if i + 1 < n && t[(i + 1, i)] != zero() {
1633			2
1634		} else {
1635			1
1636		}
1637	};
1638	let moving = block_size(t.rb(), ifst);
1639	if ifst < ilst {
1640		let dest = block_size(t.rb(), ilst);
1641		ilst = ilst + dest - moving;
1642	}
1643	let mut here = ifst;
1644	while here != ilst {
1645		let down = here < ilst;
1646		let next = if down {
1647			block_size(t.rb(), here + moving)
1648		} else if here > 1 && t[(here - 1, here - 2)] != zero() {
1649			2
1650		} else {
1651			1
1652		};
1653		let start = if down { here } else { here - next };
1654		let (n1, n2) = if down { (moving, next) } else { (next, moving) };
1655		// A swap updates rows `start..start + k` (from column `start`) and
1656		// columns `start..start + k` (up to row `start + k`) of `t`, and
1657		// columns `start..start + k` of `q`. Keep a copy so that a rejected
1658		// or overflowing swap leaves the state of the last successful one.
1659		let k = n1 + n2;
1660		let w = n - start;
1661		let h = start + k;
1662		saved_rows
1663			.rb_mut()
1664			.submatrix_mut(0, 0, k, w)
1665			.copy_from(t.rb().submatrix(start, start, k, w));
1666		saved_cols
1667			.rb_mut()
1668			.submatrix_mut(0, 0, h, k)
1669			.copy_from(t.rb().submatrix(0, start, h, k));
1670		if let Some(q) = q.rb() {
1671			saved_q
1672				.rb_mut()
1673				.submatrix_mut(0, 0, n, k)
1674				.copy_from(q.submatrix(0, start, n, k));
1675		}
1676		let status = schur::real_schur::schur_swap_checked(
1677			t.rb_mut(),
1678			q.rb_mut(),
1679			start,
1680			n1,
1681			n2,
1682			true,
1683		);
1684		let finite = t.rb().submatrix(start, start, k, w).is_all_finite()
1685			&& t.rb().submatrix(0, start, h, k).is_all_finite()
1686			&& q.rb()
1687				.is_none_or(|q| q.submatrix(0, start, n, k).is_all_finite());
1688		if status != 0 || !finite {
1689			t.rb_mut()
1690				.submatrix_mut(0, start, h, k)
1691				.copy_from(saved_cols.rb().submatrix(0, 0, h, k));
1692			t.rb_mut()
1693				.submatrix_mut(start, start, k, w)
1694				.copy_from(saved_rows.rb().submatrix(0, 0, k, w));
1695			if let Some(q) = q.rb_mut() {
1696				q.submatrix_mut(0, start, n, k)
1697					.copy_from(saved_q.rb().submatrix(0, 0, n, k));
1698			}
1699			return Err(SchurReorderError { position: here });
1700		}
1701		here = if down { here + next } else { here - next };
1702	}
1703	Ok(here)
1704}
1705/// Moves a complex triangular Schur diagonal entry from `ifst` to `ilst`,
1706/// updating optional `q`. All blocks are 1 by 1; the complex swap cannot
1707/// reject.
1708///
1709/// # Panics
1710/// Panics on invalid dimensions/indices. `t` must be upper triangular.
1711///
1712/// # Examples
1713/// ```
1714/// use dyn_stack::{MemBuffer, MemStack};
1715/// use faer::linalg::evd::*;
1716/// use faer::{Mat, Par, c64};
1717/// let mut t = Mat::<c64>::identity(2, 2);
1718/// let mut mem = MemBuffer::new(reorder_schur_scratch::<c64>(2));
1719/// assert_eq!(
1720/// 	reorder_schur_cplx(
1721/// 		t.as_mut(),
1722/// 		None,
1723/// 		0,
1724/// 		1,
1725/// 		Par::Seq,
1726/// 		MemStack::new(&mut mem)
1727/// 	),
1728/// 	1
1729/// );
1730/// ```
1731pub fn reorder_schur_cplx<T: ComplexField>(
1732	mut t: MatMut<'_, T>,
1733	mut q: Option<MatMut<'_, T>>,
1734	ifst: usize,
1735	ilst: usize,
1736	_par: Par,
1737	_stack: &mut MemStack,
1738) -> usize {
1739	let n = t.nrows();
1740	assert!(t.ncols() == n && ifst < n && ilst < n);
1741	if let Some(q) = q.rb() {
1742		assert!(q.nrows() == n && q.ncols() == n);
1743	}
1744	let mut here = ifst;
1745	while here != ilst {
1746		let start = if here < ilst { here } else { here - 1 };
1747		schur::complex_schur::schur_swap(t.rb_mut(), q.rb_mut(), start);
1748		here = if here < ilst { here + 1 } else { here - 1 };
1749	}
1750	here
1751}
1752/// Workspace for [`eigenvectors_from_schur_real`] and
1753/// [`eigenvectors_from_schur_cplx`].
1754///
1755/// # Examples
1756/// ```
1757/// let req = faer::linalg::evd::eigenvectors_from_schur_scratch::<f64>(3);
1758/// let _buffer = dyn_stack::MemBuffer::new(req);
1759/// ```
1760pub fn eigenvectors_from_schur_scratch<T: ComplexField>(
1761	dim: usize,
1762) -> StackReq {
1763	temp_mat_scratch::<T>(dim, dim)
1764}
1765/// Computes all right eigenvectors of standardized real Schur `t` in diagonal
1766/// order. If `q` is supplied, returns eigenvectors of `q * t * q.transpose()`.
1767/// Each real column has unit 2-norm. A pair of columns encodes `re + i im` for
1768/// the positive-imaginary eigenvalue, normalized as one complex vector.
1769/// `x` is output only: its previous contents are never read.
1770///
1771/// # Panics
1772/// Panics on incompatible square dimensions or insufficient scratch.
1773///
1774/// # Errors
1775/// Returns [`EvdError::NoConvergence`] for nonfinite input or
1776/// nonfinite/degenerate output.
1777///
1778/// # Examples
1779/// ```
1780/// use dyn_stack::{MemBuffer, MemStack};
1781/// use faer::linalg::evd::*;
1782/// use faer::{Mat, Par};
1783/// let t = faer::mat![[1.0f64, 2.0], [0.0, 3.0]];
1784/// let mut x = Mat::zeros(2, 2);
1785/// let mut mem = MemBuffer::new(eigenvectors_from_schur_scratch::<f64>(2));
1786/// eigenvectors_from_schur_real(
1787/// 	t.as_ref(),
1788/// 	None,
1789/// 	x.as_mut(),
1790/// 	Par::Seq,
1791/// 	MemStack::new(&mut mem),
1792/// 	Default::default(),
1793/// )
1794/// .unwrap();
1795/// assert!((x.col(1).norm_l2() - 1.0).abs() < 1e-14);
1796/// ```
1797pub fn eigenvectors_from_schur_real<T: RealField>(
1798	t: MatRef<'_, T>,
1799	q: Option<MatRef<'_, T>>,
1800	mut x: MatMut<'_, T>,
1801	mut par: Par,
1802	stack: &mut MemStack,
1803	params: Spec<EvdFromSchurParams, T>,
1804) -> Result<(), EvdError> {
1805	let n = t.nrows();
1806	assert!(t.ncols() == n && x.nrows() == n && x.ncols() == n);
1807	if let Some(q) = q {
1808		assert!(q.nrows() == n && q.ncols() == n);
1809	}
1810	if !t.is_all_finite() {
1811		return Err(EvdError::NoConvergence);
1812	}
1813	let (mut v, _) = temp_mat_zeroed::<T, _, _>(n, n, stack);
1814	let mut v = v.as_mat_mut();
1815	evd_from_real_schur_imp(t, v.rb_mut(), par.reborrow(), *params);
1816	if let Some(q) = q {
1817		linalg::matmul::matmul(
1818			x.rb_mut(),
1819			Accum::Replace,
1820			q,
1821			v.rb(),
1822			one(),
1823			par,
1824		);
1825	} else {
1826		x.copy_from(v.rb());
1827	}
1828	normalize_eigenvectors(x.rb_mut(), t);
1829	if eigenvectors_ok(x.rb()) {
1830		Ok(())
1831	} else {
1832		Err(EvdError::NoConvergence)
1833	}
1834}
1835/// Complex counterpart of [`eigenvectors_from_schur_real`]. `t` is upper
1836/// triangular; every column is one unit-norm right eigenvector, in diagonal
1837/// order. Optional `q` back-transforms by `q * x` (for `q * t * q.adjoint()`).
1838/// `x` is output only: its previous contents are never read.
1839///
1840/// # Panics
1841/// Panics on incompatible square dimensions or insufficient scratch.
1842///
1843/// # Errors
1844/// Returns [`EvdError::NoConvergence`] for nonfinite input or
1845/// nonfinite/degenerate output.
1846///
1847/// # Examples
1848/// ```
1849/// use dyn_stack::{MemBuffer, MemStack};
1850/// use faer::linalg::evd::*;
1851/// use faer::{Mat, Par, c64};
1852/// let t = Mat::<c64>::identity(2, 2);
1853/// let mut x = Mat::zeros(2, 2);
1854/// let mut mem = MemBuffer::new(eigenvectors_from_schur_scratch::<c64>(2));
1855/// eigenvectors_from_schur_cplx(
1856/// 	t.as_ref(),
1857/// 	None,
1858/// 	x.as_mut(),
1859/// 	Par::Seq,
1860/// 	MemStack::new(&mut mem),
1861/// 	Default::default(),
1862/// )
1863/// .unwrap();
1864/// assert_eq!(x, t);
1865/// ```
1866pub fn eigenvectors_from_schur_cplx<T: ComplexField>(
1867	t: MatRef<'_, T>,
1868	q: Option<MatRef<'_, T>>,
1869	mut x: MatMut<'_, T>,
1870	mut par: Par,
1871	stack: &mut MemStack,
1872	params: Spec<EvdFromSchurParams, T>,
1873) -> Result<(), EvdError> {
1874	let n = t.nrows();
1875	assert!(t.ncols() == n && x.nrows() == n && x.ncols() == n);
1876	if let Some(q) = q {
1877		assert!(q.nrows() == n && q.ncols() == n);
1878	}
1879	if !t.is_all_finite() {
1880		return Err(EvdError::NoConvergence);
1881	}
1882	let (mut v, _) = temp_mat_zeroed::<T, _, _>(n, n, stack);
1883	let mut v = v.as_mat_mut();
1884	evd_from_cplx_schur_imp(t, Conj::No, v.rb_mut(), par.reborrow(), *params);
1885	if let Some(q) = q {
1886		linalg::matmul::matmul(
1887			x.rb_mut(),
1888			Accum::Replace,
1889			q,
1890			v.rb(),
1891			one(),
1892			par,
1893		);
1894	} else {
1895		x.copy_from(v.rb());
1896	}
1897	normalize_eigenvectors(x.rb_mut(), t);
1898	if eigenvectors_ok(x.rb()) {
1899		Ok(())
1900	} else {
1901		Err(EvdError::NoConvergence)
1902	}
1903}
1904/// `?GEEV`'s scaling of its input: when the largest entry is below
1905/// `smlnum = sqrt(min_positive) / eps` or above `bignum = 1 / smlnum`, the power of two that
1906/// brings it to just inside that bound, else `None`. LAPACK scales by `smlnum / anrm` (or
1907/// `bignum / anrm`); a power of two lands within a factor of two of the same bound and does not
1908/// round (an entry far below the largest can still underflow when scaling down, as in LAPACK).
1909/// The largest entry is the largest `|re|` or `|im|`, within a factor `sqrt(2)` of LAPACK's
1910/// modulus: faer's complex modulus is itself inf for `2^1023 + 0i` and zero for `2^-1074 + 0i`,
1911/// which would select a zero factor or none. Without the scaling, the Hessenberg reduction and
1912/// the Schur iteration of a matrix whose entries are near the bottom of the range work in
1913/// subnormals: on Grcar, random and near-Jordan matrices the GET22 ratio grew from about `2^-90`
1914/// (32-bit) and reached `1 / eps` by `2^-100` (32-bit) and `2^-960` (64-bit); near the top, the
1915/// complex Schur iteration lost a factor of three against `cgeev` (#112).
1916fn geev_scaling<T: ComplexField>(A: MatRef<'_, T>) -> Option<T::Real> {
1917	let n = A.nrows();
1918	let mut anrm = zero::<T::Real>();
1919	for j in 0..n {
1920		for i in 0..n {
1921			anrm = anrm.fmax(A[(i, j)].real().abs()).fmax(A[(i, j)].imag().abs());
1922		}
1923	}
1924	let smlnum = min_positive::<T::Real>().sqrt() / eps::<T::Real>();
1925	let bignum = smlnum.recip();
1926	let two = from_f64::<T::Real>(2.0);
1927	let half = from_f64::<T::Real>(0.5);
1928	if anrm == zero::<T::Real>() || (anrm >= smlnum && anrm <= bignum) {
1929		return None;
1930	}
1931	let mut f = one::<T::Real>();
1932	if anrm < smlnum {
1933		// `anrm * f` in `[smlnum, 2 smlnum)`
1934		while &anrm * &f < smlnum {
1935			f = f * &two;
1936		}
1937	} else {
1938		// `anrm * f` in `(bignum / 2, bignum]`
1939		while &anrm * &f > bignum {
1940			f = f * &half;
1941		}
1942	}
1943	Some(f)
1944}
1945fn evd_imp<T: ComplexField>(
1946	A: MatRef<'_, T>,
1947	s: ColMut<'_, T>,
1948	s_im: Option<ColMut<'_, T>>,
1949	u_left: Option<MatMut<'_, T>>,
1950	u_right: Option<MatMut<'_, T>>,
1951	mut par: Par,
1952	stack: &mut MemStack,
1953	params: EvdParams,
1954) -> Result<(), EvdError> {
1955	let n = A.nrows();
1956	if n == 0 {
1957		return Ok(());
1958	}
1959	for j in 0..n {
1960		for i in 0..n {
1961			if !A[(i, j)].is_finite() {
1962				return Err(EvdError::NoConvergence);
1963			}
1964		}
1965	}
1966	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
1967		n - 1,
1968		n - 1,
1969	);
1970	let mut s = s;
1971	let mut s_im = s_im;
1972	let (mut H, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1973	let (mut Z, stack) = unsafe {
1974		temp_mat_uninit::<T, _, _>(
1975			n,
1976			if u_left.is_some() || u_right.is_some() {
1977				n
1978			} else {
1979				0
1980			},
1981			stack,
1982		)
1983	};
1984	let mut H = H.as_mat_mut();
1985	let mut Z = if u_left.is_some() || u_right.is_some() {
1986		Some(Z.as_mat_mut())
1987	} else {
1988		None
1989	};
1990	H.copy_from(A);
1991	// the eigenvectors of `f A` are those of `A`; its eigenvalues are scaled back at the end
1992	let scaling = geev_scaling(A);
1993	if let Some(f) = &scaling {
1994		z!(H.rb_mut()).for_each(|uz!(h)| *h = h.mul_real(f));
1995	}
1996	{
1997		let (mut householder, stack) =
1998			unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
1999		let mut householder = householder.as_mat_mut();
2000		hessenberg::hessenberg_in_place(
2001			H.rb_mut(),
2002			householder.rb_mut(),
2003			par.reborrow(),
2004			stack,
2005			params.hessenberg.into(),
2006		);
2007		if let Some(mut Z) = Z.rb_mut() {
2008			Z.fill(zero());
2009			Z.rb_mut().diagonal_mut().fill(one());
2010			linalg::householder::apply_block_householder_sequence_on_the_right_in_place_with_conj(
2011				H.rb().submatrix(1, 0, n - 1, n - 1),
2012				householder.rb(),
2013				Conj::No,
2014				Z.rb_mut().submatrix_mut(1, 1, n - 1, n - 1),
2015				par.reborrow(),
2016				stack,
2017			);
2018		}
2019		for j in 0..n {
2020			for i in j + 2..n {
2021				H[(i, j)] = zero();
2022			}
2023		}
2024	}
2025	if const { T::IS_REAL } {
2026		let (info, _, _) = schur::real_schur::multishift_qr::<T::Real>(
2027			unsafe { core::mem::transmute(Z.is_some()) },
2028			unsafe { core::mem::transmute(H.rb_mut()) },
2029			unsafe { core::mem::transmute(Z.rb_mut()) },
2030			unsafe { core::mem::transmute(s.rb_mut()) },
2031			unsafe { core::mem::transmute(s_im.rb_mut().unwrap()) },
2032			0,
2033			n,
2034			par.reborrow(),
2035			stack,
2036			params.schur,
2037		);
2038		if info != 0 {
2039			return Err(EvdError::NoConvergence);
2040		}
2041	} else {
2042		let (info, _, _) = schur::complex_schur::multishift_qr::<T>(
2043			Z.is_some(),
2044			H.rb_mut(),
2045			Z.rb_mut(),
2046			s.rb_mut(),
2047			0,
2048			n,
2049			par.reborrow(),
2050			stack,
2051			params.schur,
2052		);
2053		if info != 0 {
2054			return Err(EvdError::NoConvergence);
2055		}
2056	}
2057	let H = H.rb();
2058	if let (Some(mut u), Some(Z)) = (u_right, Z.rb()) {
2059		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
2060		let mut X = X.as_mat_mut();
2061		if const { T::IS_REAL } {
2062			evd_from_real_schur_imp::<T::Real>(
2063				unsafe { core::mem::transmute(H) },
2064				unsafe { core::mem::transmute(X.rb_mut()) },
2065				par.reborrow(),
2066				params.evd_from_schur,
2067			);
2068		} else {
2069			evd_from_cplx_schur_imp::<T>(
2070				H,
2071				Conj::No,
2072				X.rb_mut(),
2073				par.reborrow(),
2074				params.evd_from_schur,
2075			);
2076		}
2077		linalg::matmul::triangular::matmul(
2078			u.rb_mut(),
2079			BlockStructure::Rectangular,
2080			Accum::Replace,
2081			Z,
2082			BlockStructure::Rectangular,
2083			X.rb(),
2084			BlockStructure::TriangularUpper,
2085			one(),
2086			par.reborrow(),
2087		);
2088		normalize_eigenvectors(u.rb_mut(), H);
2089		if !eigenvectors_ok(u.rb()) {
2090			return Err(EvdError::NoConvergence);
2091		}
2092	}
2093	if let (Some(mut u), Some(Z)) = (u_left, Z.rb()) {
2094		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
2095		let mut X = X.as_mat_mut().reverse_rows_mut();
2096		if const { T::IS_REAL } {
2097			evd_from_real_schur_imp::<T::Real>(
2098				unsafe {
2099					core::mem::transmute(H.transpose().reverse_rows_and_cols())
2100				},
2101				unsafe { core::mem::transmute(X.rb_mut()) },
2102				par.reborrow(),
2103				params.evd_from_schur,
2104			);
2105		} else {
2106			evd_from_cplx_schur_imp::<T>(
2107				H.transpose().reverse_rows_and_cols(),
2108				Conj::Yes,
2109				X.rb_mut(),
2110				par.reborrow(),
2111				params.evd_from_schur,
2112			);
2113		}
2114		linalg::matmul::triangular::matmul(
2115			u.rb_mut(),
2116			BlockStructure::Rectangular,
2117			Accum::Replace,
2118			Z,
2119			BlockStructure::Rectangular,
2120			X.rb().reverse_rows_and_cols(),
2121			BlockStructure::TriangularLower,
2122			one(),
2123			par,
2124		);
2125		normalize_eigenvectors(u.rb_mut(), H);
2126		if !eigenvectors_ok(u.rb()) {
2127			return Err(EvdError::NoConvergence);
2128		}
2129	}
2130	if let Some(f) = &scaling {
2131		let f = f.recip();
2132		z!(s.rb_mut()).for_each(|uz!(x)| *x = x.mul_real(&f));
2133		if let Some(s_im) = s_im.rb_mut() {
2134			z!(s_im).for_each(|uz!(x)| *x = x.mul_real(&f));
2135		}
2136	}
2137	Ok(())
2138}
2139/// computes the matrix $A$'s eigendecomposition
2140///
2141/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
2142/// right eigenvectors in $U_R$.
2143///
2144/// every eigenvector is scaled to unit 2-norm. if the Schur iteration does not
2145/// converge, or the back substitution would overflow for a finite input,
2146/// [`EvdError::NoConvergence`] is returned instead of nonfinite entries.
2147#[track_caller]
2148pub fn evd_cplx<T: RealField>(
2149	A: MatRef<'_, Complex<T>>,
2150	s: DiagMut<'_, Complex<T>>,
2151	u_left: Option<MatMut<'_, Complex<T>>>,
2152	u_right: Option<MatMut<'_, Complex<T>>>,
2153	par: Par,
2154	stack: &mut MemStack,
2155	params: Spec<EvdParams, Complex<T>>,
2156) -> Result<(), EvdError> {
2157	let n = A.nrows();
2158	assert!(all(A.nrows() == n, A.ncols() == n, s.dim() == n));
2159	if let Some(u) = u_left.rb() {
2160		assert!(all(u.nrows() == n, u.ncols() == n));
2161	}
2162	if let Some(u) = u_right.rb() {
2163		assert!(all(u.nrows() == n, u.ncols() == n));
2164	}
2165	evd_imp(
2166		A,
2167		s.column_vector_mut(),
2168		None,
2169		u_left,
2170		u_right,
2171		par,
2172		stack,
2173		params.config,
2174	)
2175}
2176/// computes the matrix $A$'s eigendecomposition
2177///
2178/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
2179/// right eigenvectors in $U_R$.
2180///
2181/// every eigenvector is scaled to unit 2-norm: a real eigenvalue gets a unit
2182/// norm column, and the two real columns `re` and `im` of a conjugate pair are
2183/// scaled by a common factor so that `re + i * im` has unit 2-norm (the same
2184/// contract `xGEEV` provides). if the Schur iteration does not converge, or the
2185/// back substitution would overflow for a finite input,
2186/// [`EvdError::NoConvergence`] is returned instead of nonfinite entries.
2187#[track_caller]
2188pub fn evd_real<T: RealField>(
2189	A: MatRef<'_, T>,
2190	s_re: DiagMut<'_, T>,
2191	s_im: DiagMut<'_, T>,
2192	u_left: Option<MatMut<'_, T>>,
2193	u_right: Option<MatMut<'_, T>>,
2194	par: Par,
2195	stack: &mut MemStack,
2196	params: Spec<EvdParams, T>,
2197) -> Result<(), EvdError> {
2198	let n = A.nrows();
2199	assert!(all(
2200		A.nrows() == n,
2201		A.ncols() == n,
2202		s_re.dim() == n,
2203		s_im.dim() == n
2204	));
2205	if let Some(u) = u_left.rb() {
2206		assert!(all(u.nrows() == n, u.ncols() == n));
2207	}
2208	if let Some(u) = u_right.rb() {
2209		assert!(all(u.nrows() == n, u.ncols() == n));
2210	}
2211	evd_imp(
2212		A,
2213		s_re.column_vector_mut(),
2214		Some(s_im.column_vector_mut()),
2215		u_left,
2216		u_right,
2217		par,
2218		stack,
2219		params.config,
2220	)
2221}
2222#[cfg(test)]
2223mod general_tests {
2224	use super::*;
2225	use crate::assert;
2226	use crate::stats::prelude::*;
2227	use crate::utils::approx::*;
2228	use dyn_stack::MemBuffer;
2229	fn test_cplx_evd(mat: MatRef<'_, c64>) {
2230		let n = mat.nrows();
2231		let params = Spec::new(EvdParams {
2232			hessenberg: auto!(c64),
2233			schur: auto!(c64),
2234			evd_from_schur: EvdFromSchurParams {
2235				recursion_threshold: 8,
2236				..auto!(c64)
2237			},
2238			..auto!(c64)
2239		});
2240		let approx_eq = CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
2241		let mut s = Mat::zeros(n, n);
2242		{
2243			let mut ul = Mat::zeros(n, n);
2244			let mut ur = Mat::zeros(n, n);
2245			evd_cplx(
2246				mat.as_ref(),
2247				s.as_mut().diagonal_mut(),
2248				Some(ul.as_mut()),
2249				Some(ur.as_mut()),
2250				Par::Seq,
2251				MemStack::new(&mut MemBuffer::new(evd_scratch::<c64>(
2252					n,
2253					ComputeEigenvectors::Yes,
2254					ComputeEigenvectors::Yes,
2255					ParShape::Seq,
2256					params,
2257				))),
2258				params,
2259			)
2260			.unwrap();
2261			assert!(& ur * & s ~ mat * & ur);
2262			assert!(& s * ul.adjoint() ~ ul.adjoint() * mat);
2263		}
2264	}
2265	fn test_real_evd(mat: MatRef<'_, f64>) {
2266		let n = mat.nrows();
2267		let params = Spec::new(EvdParams {
2268			hessenberg: auto!(f64),
2269			schur: auto!(f64),
2270			evd_from_schur: EvdFromSchurParams {
2271				recursion_threshold: 8,
2272				..auto!(f64)
2273			},
2274			..auto!(f64)
2275		});
2276		let approx_eq =
2277			CwiseMat(ApproxEq::<f64>::eps() * (8.0 * n as f64).sqrt());
2278		let mut s_re = Diag::zeros(n);
2279		let mut s_im = Diag::zeros(n);
2280		{
2281			let mut ul = Mat::zeros(n, n);
2282			let mut ur = Mat::zeros(n, n);
2283			evd_real(
2284				mat.as_ref(),
2285				s_re.as_mut(),
2286				s_im.as_mut(),
2287				Some(ul.as_mut()),
2288				Some(ur.as_mut()),
2289				Par::Seq,
2290				MemStack::new(&mut MemBuffer::new(evd_scratch::<f64>(
2291					n,
2292					ComputeEigenvectors::Yes,
2293					ComputeEigenvectors::Yes,
2294					ParShape::Seq,
2295					params,
2296				))),
2297				params,
2298			)
2299			.unwrap();
2300			let mut i = 0;
2301			while i < n {
2302				if s_im[i] == 0.0 {
2303					let ur = ur.col(i);
2304					let ul = ul.col(i);
2305					let s = Scale(s_re[i]);
2306					assert!((& ur * s).as_mat() ~ (mat * & ur).as_mat());
2307					assert!(
2308						(& ul.adjoint() * s).as_mat() ~ (& ul.adjoint() * mat).as_mat()
2309					);
2310					i += 1;
2311				} else {
2312					let re = ur.col(i);
2313					let im = ur.col(i + 1);
2314					let ur = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
2315					let re = ul.col(i);
2316					let im = ul.col(i + 1);
2317					let ul = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
2318					let mat =
2319						&Mat::from_fn(n, n, |i, j| c64::from(mat[(i, j)]));
2320					let s = Scale(c64::new(s_re[i], s_im[i]));
2321					let approx_eq =
2322						CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
2323					assert!((ur * s).as_mat() ~ (mat * ur).as_mat());
2324					assert!((ul.adjoint() * s).as_mat() ~ (ul.adjoint() * mat).as_mat());
2325					i += 2;
2326				}
2327			}
2328		}
2329	}
2330	#[test]
2331	fn test_cplx() {
2332		let rng = &mut StdRng::seed_from_u64(1);
2333		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
2334			let mat = CwiseMatDistribution {
2335				nrows: n,
2336				ncols: n,
2337				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
2338			}
2339			.rand::<Mat<c64>>(rng);
2340			test_cplx_evd(mat.as_ref());
2341		}
2342	}
2343	#[test]
2344	fn test_real() {
2345		let rng = &mut StdRng::seed_from_u64(0);
2346		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
2347			let mat = CwiseMatDistribution {
2348				nrows: n,
2349				ncols: n,
2350				dist: StandardNormal,
2351			}
2352			.rand::<Mat<f64>>(rng);
2353			test_real_evd(mat.as_ref());
2354		}
2355	}
2356}
2357#[cfg(test)]
2358mod eig_2131_tests {
2359	//! Regression tests for `tensor4all/tenferro-rs#2131`: the faer
2360	//! eigendecomposition used to return eigenvectors that were neither
2361	//! normalized nor overflow-safe. On the Jordan block `2 I + N` the back
2362	//! substitution grew by `1 / (eps * norm)` per row, overflowing to `NaN`
2363	//! for finite input (`f32` at `n >= 8`, `f64` at `n = 26`).
2364	use super::*;
2365	use crate::assert;
2366	use dyn_stack::MemBuffer;
2367
2368	/// `||A v - lambda v|| / (||A||_1 * ||v||)`, where `||A||_1` is the maximum
2369	/// absolute column sum.
2370	fn relative_residual<T: RealField>(
2371		mat: MatRef<'_, T>,
2372		v: ColRef<'_, T>,
2373		lambda: T,
2374	) -> T {
2375		let n = mat.nrows();
2376		let mut anorm = zero::<T>();
2377		for j in 0..n {
2378			let mut col = zero::<T>();
2379			for i in 0..n {
2380				col += mat[(i, j)].abs1();
2381			}
2382			if col > anorm {
2383				anorm = col;
2384			}
2385		}
2386		let mut vnorm2 = zero::<T>();
2387		let mut rnorm2 = zero::<T>();
2388		for i in 0..n {
2389			let mut acc = zero::<T>();
2390			for j in 0..n {
2391				acc += &mat[(i, j)] * &v[j];
2392			}
2393			acc -= &v[i] * &lambda;
2394			rnorm2 += acc.abs2();
2395			vnorm2 += v[i].abs2();
2396		}
2397		rnorm2.sqrt() / (anorm * vnorm2.sqrt())
2398	}
2399
2400	fn evd_real_vectors<T: RealField>(
2401		mat: MatRef<'_, T>,
2402		params: Spec<EvdParams, T>,
2403	) -> (Diag<T>, Diag<T>, Mat<T>, Mat<T>) {
2404		let n = mat.nrows();
2405		let mut s_re = Diag::zeros(n);
2406		let mut s_im = Diag::zeros(n);
2407		let mut u = Mat::zeros(n, n);
2408		let mut ul = Mat::zeros(n, n);
2409		let mut mem = MemBuffer::new(evd_scratch::<T>(
2410			n,
2411			ComputeEigenvectors::Yes,
2412			ComputeEigenvectors::Yes,
2413			ParShape::Seq,
2414			params,
2415		));
2416		evd_real(
2417			mat,
2418			s_re.as_mut(),
2419			s_im.as_mut(),
2420			Some(ul.as_mut()),
2421			Some(u.as_mut()),
2422			Par::Seq,
2423			MemStack::new(&mut mem),
2424			params,
2425		)
2426		.unwrap();
2427		(s_re, s_im, u, ul)
2428	}
2429
2430	/// The Jordan block `2 I + N` with ones on the superdiagonal.
2431	fn jordan<T: RealField>(n: usize) -> Mat<T> {
2432		let two = from_f64::<T>(2.0);
2433		let one = one::<T>();
2434		Mat::<T>::from_fn(n, n, |i, j| {
2435			if i == j {
2436				two.copy()
2437			} else if j == i + 1 {
2438				one.copy()
2439			} else {
2440				zero::<T>()
2441			}
2442		})
2443	}
2444
2445	fn check_jordan<T: RealField>(n: usize, threshold: usize, tol: f64) {
2446		let params = Spec::new(EvdParams {
2447			evd_from_schur: EvdFromSchurParams {
2448				recursion_threshold: threshold,
2449				..auto!(T)
2450			},
2451			..auto!(T)
2452		});
2453		let mat = jordan::<T>(n);
2454		let (s_re, _, u, _) = evd_real_vectors(mat.as_ref(), params);
2455		let tol = from_f64::<T>(tol);
2456		for j in 0..n {
2457			let v = u.col(j);
2458			assert!(v.iter().all(|x| x.is_finite()));
2459			assert!((v.norm_l2() - one::<T>()).abs() < from_f64::<T>(1e-5));
2460			assert!(relative_residual(mat.as_ref(), v, s_re[j].copy()) < tol);
2461		}
2462	}
2463
2464	#[test]
2465	fn jordan_eigenvectors_are_finite_and_unit_norm() {
2466		// `threshold = 3` forces the recursive block solver; the default `64`
2467		// keeps small sizes sequential and exercises the recursion for the
2468		// larger ones. Both must produce finite, normalized, backward-stable
2469		// eigenvectors.
2470		for threshold in [3usize, 64] {
2471			for n in [2, 4, 8, 20, 26, 30, 64, 65, 100] {
2472				check_jordan::<f64>(n, threshold, 1e-10);
2473			}
2474			for n in [2, 4, 8, 16, 30] {
2475				check_jordan::<f32>(n, threshold, 1e-3);
2476			}
2477		}
2478	}
2479
2480	#[test]
2481	fn conjugate_pair_eigenvector_has_unit_norm() {
2482		// A real Schur form with four real eigenvalues in front of a 2x2
2483		// conjugate pair, so the complex-shift quasi-triangular solver gets a
2484		// prefix large enough for its recursive branch at `threshold = 4`.
2485		fn run<T: RealField>(threshold: usize) {
2486			let n = 6;
2487			let mat = Mat::<T>::from_fn(n, n, |i, j| {
2488				if i == j {
2489					if i >= 4 {
2490						zero::<T>()
2491					} else {
2492						from_f64::<T>((i + 1) as f64)
2493					}
2494				} else if i == 4 && j == 5 {
2495					one::<T>()
2496				} else if i == 5 && j == 4 {
2497					-one::<T>()
2498				} else if i + 2 <= j {
2499					from_f64::<T>(0.25)
2500				} else {
2501					zero::<T>()
2502				}
2503			});
2504			let params = Spec::new(EvdParams {
2505				evd_from_schur: EvdFromSchurParams {
2506					recursion_threshold: threshold,
2507					..auto!(T)
2508				},
2509				..auto!(T)
2510			});
2511			let (s_re, s_im, u, ul) = evd_real_vectors(mat.as_ref(), params);
2512			let tol = from_f64::<T>(1e-5);
2513			// Right and left eigenvectors share the same conjugate-pair scaling.
2514			let mut j = 0;
2515			while j < n {
2516				let v = u.col(j);
2517				let vl = ul.col(j);
2518				if s_im[j] == zero::<T>() {
2519					assert!((v.norm_l2() - one::<T>()).abs() < tol);
2520					assert!((vl.norm_l2() - one::<T>()).abs() < tol);
2521					assert!(
2522						relative_residual(mat.as_ref(), v, s_re[j].copy())
2523							< from_f64::<T>(1e-4)
2524					);
2525					j += 1;
2526				} else {
2527					let nrm = v.norm_l2().hypot(&u.col(j + 1).norm_l2());
2528					assert!((nrm - one::<T>()).abs() < tol);
2529					let nrm = vl.norm_l2().hypot(&ul.col(j + 1).norm_l2());
2530					assert!((nrm - one::<T>()).abs() < tol);
2531					j += 2;
2532				}
2533			}
2534		}
2535		for threshold in [4usize, 64] {
2536			run::<f64>(threshold);
2537			run::<f32>(threshold);
2538		}
2539	}
2540
2541	#[test]
2542	fn complex_eigenvectors_have_unit_norm() {
2543		// `threshold = 4` drives the complex solver's recursive branch.
2544		fn run<T: RealField>(threshold: usize) {
2545			let n = 6;
2546			let mat = Mat::<Complex<T>>::from_fn(n, n, |i, j| {
2547				Complex::new(
2548					from_f64::<T>(i as f64)
2549						+ from_f64::<T>(j as f64) * from_f64::<T>(0.1),
2550					from_f64::<T>(i as f64 - j as f64) * from_f64::<T>(0.3),
2551				)
2552			});
2553			let params = Spec::new(EvdParams {
2554				evd_from_schur: EvdFromSchurParams {
2555					recursion_threshold: threshold,
2556					..auto!(Complex<T>)
2557				},
2558				..auto!(Complex<T>)
2559			});
2560			let mut s = Diag::zeros(n);
2561			let mut u = Mat::zeros(n, n);
2562			let mut mem = MemBuffer::new(evd_scratch::<Complex<T>>(
2563				n,
2564				ComputeEigenvectors::No,
2565				ComputeEigenvectors::Yes,
2566				ParShape::Seq,
2567				params,
2568			));
2569			evd_cplx(
2570				mat.as_ref(),
2571				s.as_mut(),
2572				None,
2573				Some(u.as_mut()),
2574				Par::Seq,
2575				MemStack::new(&mut mem),
2576				params,
2577			)
2578			.unwrap();
2579			let tol = from_f64::<T>(1e-5);
2580			for j in 0..n {
2581				assert!((u.col(j).norm_l2() - one::<T>()).abs() < tol);
2582			}
2583		}
2584		for threshold in [4usize, 64] {
2585			run::<f64>(threshold);
2586			run::<f32>(threshold);
2587		}
2588	}
2589}
2590#[cfg(test)]
2591mod range_limit_vector_tests {
2592	//! Regression tests for `tensor4all/faer#77`: the Schur eigenvector
2593	//! extraction used the unscaled 1-norm of the Schur form as its backward
2594	//! error scale, so a finite matrix near the top of the range made the shift
2595	//! clamp `eps * norm` infinite and returned a wrong direction with `Ok`.
2596	//! The 2x2 block solve of a conjugate pair still forms products of two
2597	//! block entries, which overflow at the top of the range; there the call
2598	//! must fail rather than return a wrong vector.
2599	//! Residuals are measured on the dimensionless matrix `t / scale`, which is
2600	//! exact for a power of two.
2601	use super::*;
2602	use crate::assert;
2603	use dyn_stack::MemBuffer;
2604
2605	/// the reported Schur form `[[1, 1], [0, 1/2]]`
2606	const SHIFT_2X2: [[f64; 2]; 2] = [[1.0, 1.0], [0.0, 0.5]];
2607	/// real Schur form with the conjugate pair `[[0, -1], [1, 0]]` in front of
2608	/// the real eigenvalue `1/2`
2609	const PAIR_3X3: [[f64; 3]; 3] =
2610		[[0.0, -1.0, 1.0], [1.0, 0.0, 0.0], [0.0, 0.0, 0.5]];
2611	/// two conjugate pairs, `+-i` and `+-i/2`
2612	const TWO_PAIRS_4X4: [[f64; 4]; 4] = [
2613		[0.0, -1.0, 1.0, 0.0],
2614		[1.0, 0.0, 0.0, 0.0],
2615		[0.0, 0.0, 0.0, -0.5],
2616		[0.0, 0.0, 0.5, 0.0],
2617	];
2618	/// whose repeated eigenvalue makes the shift clamp matter
2619	const JORDAN_2X2: [[f64; 2]; 2] = [[1.0, 1.0], [0.0, 1.0]];
2620
2621	fn unit<T: RealField, const N: usize>(
2622		mat: &[[f64; N]; N],
2623	) -> Mat<Complex<T>> {
2624		Mat::from_fn(N, N, |i, j| {
2625			Complex::new(from_f64::<T>(mat[i][j]), zero::<T>())
2626		})
2627	}
2628
2629	/// the dimensionless matrix scaled by the power of two `scale`
2630	fn scaled_real<T: RealField>(
2631		unit: MatRef<'_, Complex<T>>,
2632		scale: &T,
2633	) -> Mat<T> {
2634		Mat::from_fn(unit.nrows(), unit.ncols(), |i, j| {
2635			&unit[(i, j)].re * scale
2636		})
2637	}
2638
2639	/// complex counterpart of `scaled_real`
2640	fn scaled_cplx<T: RealField>(
2641		unit: MatRef<'_, Complex<T>>,
2642		scale: &T,
2643	) -> Mat<Complex<T>> {
2644		Mat::from_fn(unit.nrows(), unit.ncols(), |i, j| {
2645			Complex::new(&unit[(i, j)].re * scale, &unit[(i, j)].im * scale)
2646		})
2647	}
2648
2649	/// the complex analogue of `PAIR_3X3`: the imaginary eigenvalue of the pair
2650	/// sits on the diagonal, and the off-diagonal entries have a large real and
2651	/// imaginary part, so `abs1 = |re| + |im|` overflows on its own
2652	fn complex_unit<T: RealField>() -> Mat<Complex<T>> {
2653		let z = |re: f64, im: f64| {
2654			Complex::new(from_f64::<T>(re), from_f64::<T>(im))
2655		};
2656		Mat::from_fn(3, 3, |i, j| match (i, j) {
2657			(0, 1) => z(-1.0, 0.5),
2658			(0, 2) => z(1.0, -1.0),
2659			(1, 1) => z(0.5, 0.0),
2660			(2, 2) => z(0.25, 0.0),
2661			_ => z(0.0, 0.0),
2662		})
2663	}
2664
2665	/// `(||a v - lambda v||_2 / (||a||_1 * ||v||_2), ||v||_2)` for the
2666	/// dimensionless matrix `a`, so that no reference product overflows
2667	fn residual<T: RealField>(
2668		a: MatRef<'_, Complex<T>>,
2669		v: &[Complex<T>],
2670		lambda: &Complex<T>,
2671	) -> (T, T) {
2672		let n = a.nrows();
2673		let mut anorm = zero::<T>();
2674		for j in 0..n {
2675			let mut col = zero::<T>();
2676			for i in 0..n {
2677				col += &a[(i, j)].re.abs() + &a[(i, j)].im.abs();
2678			}
2679			anorm = anorm.fmax(col);
2680		}
2681		let mut rnorm2 = zero::<T>();
2682		let mut vnorm2 = zero::<T>();
2683		for i in 0..n {
2684			let mut acc = Complex::new(zero::<T>(), zero::<T>());
2685			for j in 0..n {
2686				acc += &a[(i, j)] * &v[j];
2687			}
2688			acc -= lambda * &v[i];
2689			rnorm2 += acc.re.abs2() + acc.im.abs2();
2690			vnorm2 += v[i].re.abs2() + v[i].im.abs2();
2691		}
2692		(rnorm2.sqrt() / (anorm * vnorm2.sqrt()), vnorm2.sqrt())
2693	}
2694
2695	fn try_real_vectors<T: RealField>(t: MatRef<'_, T>) -> Option<Mat<T>> {
2696		let n = t.nrows();
2697		let mut x = Mat::zeros(n, n);
2698		let mut mem = MemBuffer::new(eigenvectors_from_schur_scratch::<T>(n));
2699		eigenvectors_from_schur_real(
2700			t,
2701			None,
2702			x.as_mut(),
2703			Par::Seq,
2704			MemStack::new(&mut mem),
2705			Default::default(),
2706		)
2707		.ok()?;
2708		Some(x)
2709	}
2710
2711	fn real_vectors<T: RealField>(t: MatRef<'_, T>) -> Mat<T> {
2712		try_real_vectors(t).unwrap()
2713	}
2714
2715	fn cplx_vectors<T: ComplexField>(t: MatRef<'_, T>) -> Mat<T> {
2716		let n = t.nrows();
2717		let mut x = Mat::zeros(n, n);
2718		let mut mem = MemBuffer::new(eigenvectors_from_schur_scratch::<T>(n));
2719		eigenvectors_from_schur_cplx(
2720			t,
2721			None,
2722			x.as_mut(),
2723			Par::Seq,
2724			MemStack::new(&mut mem),
2725			Default::default(),
2726		)
2727		.unwrap();
2728		x
2729	}
2730
2731	/// the real column `j` as a complex vector, for the diagonal eigenvalue
2732	fn real_column<T: RealField>(
2733		x: MatRef<'_, T>,
2734		j: usize,
2735	) -> Vec<Complex<T>> {
2736		(0..x.nrows())
2737			.map(|i| Complex::new(x[(i, j)].copy(), zero::<T>()))
2738			.collect()
2739	}
2740
2741	/// the conjugate pair `j`, `j + 1` decoded as `re + i im`
2742	fn real_pair<T: RealField>(x: MatRef<'_, T>, j: usize) -> Vec<Complex<T>> {
2743		(0..x.nrows())
2744			.map(|i| Complex::new(x[(i, j)].copy(), x[(i, j + 1)].copy()))
2745			.collect()
2746	}
2747
2748	fn cplx_column<T: RealField>(
2749		x: MatRef<'_, Complex<T>>,
2750		j: usize,
2751	) -> Vec<Complex<T>> {
2752		(0..x.nrows()).map(|i| x[(i, j)].copy()).collect()
2753	}
2754
2755	fn assert_eigenvector<T: RealField>(
2756		unit: MatRef<'_, Complex<T>>,
2757		v: &[Complex<T>],
2758		lambda: &Complex<T>,
2759		tol: &T,
2760	) {
2761		let (res, nrm) = residual(unit, v, lambda);
2762		let nrm_err = (&nrm - &one::<T>()).abs();
2763		assert!(nrm_err <= *tol, "unit 2-norm {nrm:?}, expected 1");
2764		assert!(res <= *tol, "relative residual {res:?}");
2765	}
2766
2767	fn check_shift_2x2<T: RealField>(scale: T, tol: T) {
2768		let unit = unit::<T, 2>(&SHIFT_2X2);
2769		let lambda = Complex::new(from_f64::<T>(0.5), zero::<T>());
2770
2771		let x = real_vectors::<T>(scaled_real(unit.as_ref(), &scale).as_ref());
2772		let v = real_column(x.as_ref(), 1);
2773		assert_eigenvector(unit.as_ref(), &v, &lambda, &tol);
2774
2775		let x = cplx_vectors::<Complex<T>>(
2776			scaled_cplx(unit.as_ref(), &scale).as_ref(),
2777		);
2778		let v = cplx_column(x.as_ref(), 1);
2779		assert_eigenvector(unit.as_ref(), &v, &lambda, &tol);
2780	}
2781
2782	/// `Ok` must carry eigenvectors; at the top of the range the conjugate
2783	/// pair's 2x2 solve may instead fail with an error.
2784	fn check_pair_3x3<T: RealField>(scale: T, tol: T, may_fail: bool) {
2785		let unit = unit::<T, 3>(&PAIR_3X3);
2786		let Some(x) =
2787			try_real_vectors::<T>(scaled_real(unit.as_ref(), &scale).as_ref())
2788		else {
2789			assert!(may_fail, "extraction failed at scale {scale:?}");
2790			return;
2791		};
2792		// the positive imaginary part comes first
2793		let lambda = Complex::new(zero::<T>(), one::<T>());
2794		let v = real_pair(x.as_ref(), 0);
2795		assert_eigenvector(unit.as_ref(), &v, &lambda, &tol);
2796		let lambda = Complex::new(from_f64::<T>(0.5), zero::<T>());
2797		let v = real_column(x.as_ref(), 2);
2798		assert_eigenvector(unit.as_ref(), &v, &lambda, &tol);
2799	}
2800
2801	fn check_cplx_triangular<T: RealField>(scale: T, tol: T) {
2802		let unit = complex_unit::<T>();
2803		let t = scaled_cplx(unit.as_ref(), &scale);
2804		let x = cplx_vectors(t.as_ref());
2805		for j in 0..3 {
2806			let lambda = unit[(j, j)].copy();
2807			let v = cplx_column(x.as_ref(), j);
2808			assert_eigenvector(unit.as_ref(), &v, &lambda, &tol);
2809		}
2810	}
2811
2812	#[test]
2813	fn two_by_two_at_the_range_limit() {
2814		// `f32` and `c32`, then `f64` and `c64`
2815		for (scale, tol) in [(1.0f32, 1e-4f32), (2.0f32.powi(127), 1e-4)] {
2816			check_shift_2x2::<f32>(scale, tol);
2817		}
2818		for (scale, tol) in [(1.0f64, 1e-12f64), (2.0f64.powi(1023), 1e-12)] {
2819			check_shift_2x2::<f64>(scale, tol);
2820		}
2821	}
2822
2823	#[test]
2824	fn complex_triangular_at_the_range_limit() {
2825		for (scale, tol) in [(1.0f32, 1e-4f32), (2.0f32.powi(127), 1e-4)] {
2826			check_cplx_triangular::<f32>(scale, tol);
2827		}
2828		for (scale, tol) in [(1.0f64, 1e-12f64), (2.0f64.powi(1023), 1e-12)] {
2829			check_cplx_triangular::<f64>(scale, tol);
2830		}
2831	}
2832
2833	#[test]
2834	fn conjugate_pair_block_is_never_a_wrong_success() {
2835		check_pair_3x3::<f32>(1.0, 1e-4, false);
2836		check_pair_3x3::<f32>(2.0f32.powi(127), 1e-4, true);
2837		check_pair_3x3::<f64>(1.0, 1e-12, false);
2838		check_pair_3x3::<f64>(2.0f64.powi(1023), 1e-12, true);
2839		// the second pair's vector goes through the 2x2 solve of the first
2840		// pair with a complex shift
2841		for (scale, tol, may_fail) in
2842			[(1.0, 1e-12, false), (2.0f64.powi(1023), 1e-12, true)]
2843		{
2844			let unit = unit::<f64, 4>(&TWO_PAIRS_4X4);
2845			let t = scaled_real(unit.as_ref(), &scale);
2846			match try_real_vectors::<f64>(t.as_ref()) {
2847				Some(x) => {
2848					for (j, im) in [(0, 1.0), (2, 0.5)] {
2849						let v = real_pair(x.as_ref(), j);
2850						assert_eigenvector(
2851							unit.as_ref(),
2852							&v,
2853							&Complex::new(0.0, im),
2854							&tol,
2855						);
2856					}
2857				},
2858				None => assert!(may_fail, "two pairs failed at scale 1"),
2859			}
2860		}
2861		// the shifted diagonal `A[i, i] - p` of the pair overflows here
2862		let s = 2.0f64.powi(1023);
2863		let t = crate::mat![[s, -s, 1.0], [s, s, 0.0], [0.0, 0.0, -s]];
2864		if let Some(x) = try_real_vectors::<f64>(t.as_ref()) {
2865			let unit = Mat::from_fn(3, 3, |i, j| {
2866				Complex::new(t[(i, j)] / s, 0.0)
2867			});
2868			let v = real_pair(x.as_ref(), 0);
2869			assert_eigenvector(
2870				unit.as_ref(),
2871				&v,
2872				&Complex::new(1.0, 1.0),
2873				&1e-12,
2874			);
2875		}
2876	}
2877
2878	/// A pair whose off-diagonal entries span the whole range is solved by the
2879	/// ordinary block arithmetic, and must stay solved.
2880	#[test]
2881	fn conjugate_pair_with_a_wide_dynamic_range() {
2882		let t = crate::mat![
2883			[0.0, -2.0f64.powi(1023), 1.0],
2884			[2.0f64.powi(-1022), 0.0, 0.0],
2885			[0.0, 0.0, 0.5],
2886		];
2887		let x = real_vectors::<f64>(t.as_ref());
2888		assert!(x.is_all_finite());
2889		let unit = Mat::from_fn(3, 3, |i, j| Complex::new(t[(i, j)], 0.0));
2890		let lambda = Complex::new(0.5, 0.0);
2891		let v = real_column(x.as_ref(), 2);
2892		assert_eigenvector(unit.as_ref(), &v, &lambda, &1e-12);
2893	}
2894
2895	/// #112's relative clamp is small next to a Schur form near the top of the range, so the
2896	/// back-substituted entries (about `1 / eps`) times the next row's entries overflowed before
2897	/// the rescaling could run; a Schur form near the bottom of the range had a clamp whose complex
2898	/// reciprocal overflowed. Both are scaled into range before the substitution now.
2899	#[test]
2900	fn jordan_chains_at_both_ends_of_the_range() {
2901		const CHAIN_3X3: [[f64; 3]; 3] = [[1.0, 1.0, 0.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0]];
2902		fn real<T: RealField>(scale: T, tol: T) {
2903			let unit = unit::<T, 3>(&CHAIN_3X3);
2904			let x = real_vectors::<T>(scaled_real(unit.as_ref(), &scale).as_ref());
2905			let lambda = Complex::new(one::<T>(), zero::<T>());
2906			for j in 0..3 {
2907				let v = real_column(x.as_ref(), j);
2908				assert_eigenvector(unit.as_ref(), &v, &lambda, &tol);
2909			}
2910		}
2911		fn cplx<T: RealField>(scale: T, tol: T) {
2912			let unit = unit::<T, 2>(&JORDAN_2X2);
2913			let x = cplx_vectors(scaled_cplx(unit.as_ref(), &scale).as_ref());
2914			let lambda = Complex::new(one::<T>(), zero::<T>());
2915			for j in 0..2 {
2916				let v = cplx_column(x.as_ref(), j);
2917				assert_eigenvector(unit.as_ref(), &v, &lambda, &tol);
2918			}
2919		}
2920		real::<f64>(2.0f64.powi(972), 1e-12);
2921		real::<f32>(2.0f32.powi(105), 1e-4);
2922		cplx::<f64>(2.0f64.powi(-972), 1e-12);
2923		cplx::<f32>(2.0f32.powi(-105), 1e-4);
2924	}
2925
2926	/// A subnormal Schur form must keep the clamp the plain norm gives: summing
2927	/// the terms as `eps * |a_ij|` instead rounds the floor to zero and loses
2928	/// the only eigenvector of a repeated eigenvalue.
2929	#[test]
2930	fn subnormal_shift_clamp_is_unchanged() {
2931		let scale = 2.0f64.powi(-1023);
2932		let unit = unit::<f64, 2>(&JORDAN_2X2);
2933		let x =
2934			real_vectors::<f64>(scaled_real(unit.as_ref(), &scale).as_ref());
2935		let lambda = Complex::new(1.0, 0.0);
2936		let v = real_column(x.as_ref(), 1);
2937		assert_eigenvector(unit.as_ref(), &v, &lambda, &1e-12);
2938	}
2939}
2940#[cfg(test)]
2941mod self_adjoint_tests {
2942	use super::*;
2943	use crate::assert;
2944	use crate::stats::prelude::*;
2945	use crate::utils::approx::*;
2946	use dyn_stack::MemBuffer;
2947	fn test_self_adjoint_evd<T: ComplexField>(mat: MatRef<'_, T>) {
2948		let n = mat.nrows();
2949		let params = Spec::new(SelfAdjointEvdParams {
2950			recursion_threshold: 8,
2951			..auto!(T)
2952		});
2953		let approx_eq = CwiseMat(
2954			ApproxEq::<T::Real>::eps()
2955				* from_f64::<T::Real>(8.0 * n as f64).sqrt(),
2956		);
2957		let mut s = Mat::zeros(n, n);
2958		{
2959			let mut u = Mat::zeros(n, n);
2960			self_adjoint_evd(
2961				mat.as_ref(),
2962				s.as_mut().diagonal_mut(),
2963				Some(u.as_mut()),
2964				Par::Seq,
2965				MemStack::new(&mut MemBuffer::new(
2966					self_adjoint_evd_scratch::<T>(
2967						n,
2968						ComputeEigenvectors::Yes,
2969						ParShape::Seq,
2970						params,
2971					),
2972				)),
2973				params,
2974			)
2975			.unwrap();
2976			let reconstructed = &u * &s * u.adjoint();
2977			assert!(reconstructed ~ mat);
2978		}
2979		{
2980			let mut s2 = Mat::zeros(n, n);
2981			self_adjoint_evd(
2982				mat.as_ref(),
2983				s2.as_mut().diagonal_mut(),
2984				None,
2985				Par::Seq,
2986				MemStack::new(&mut MemBuffer::new(
2987					self_adjoint_evd_scratch::<T>(
2988						n,
2989						ComputeEigenvectors::No,
2990						ParShape::Seq,
2991						params,
2992					),
2993				)),
2994				params,
2995			)
2996			.unwrap();
2997			assert!(s2 ~ s);
2998		}
2999	}
3000	#[test]
3001	fn test_real() {
3002		let rng = &mut StdRng::seed_from_u64(1);
3003		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
3004			let mat = CwiseMatDistribution {
3005				nrows: n,
3006				ncols: n,
3007				dist: StandardNormal,
3008			}
3009			.rand::<Mat<f64>>(rng);
3010			let mat = &mat + mat.adjoint();
3011			test_self_adjoint_evd(mat.as_ref());
3012		}
3013	}
3014	#[test]
3015	fn test_cplx() {
3016		let rng = &mut StdRng::seed_from_u64(1);
3017		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
3018			let mat = CwiseMatDistribution {
3019				nrows: n,
3020				ncols: n,
3021				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
3022			}
3023			.rand::<Mat<c64>>(rng);
3024			let mat = &mat + mat.adjoint();
3025			test_self_adjoint_evd(mat.as_ref());
3026		}
3027	}
3028	#[test]
3029	fn test_special() {
3030		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
3031			test_self_adjoint_evd(Mat::full(n, n, 0.0).as_ref());
3032			test_self_adjoint_evd(Mat::full(n, n, c64::ZERO).as_ref());
3033			test_self_adjoint_evd(Mat::full(n, n, 1.0).as_ref());
3034			test_self_adjoint_evd(Mat::full(n, n, c64::ONE).as_ref());
3035			test_self_adjoint_evd(Mat::<f64>::identity(n, n).as_ref());
3036			test_self_adjoint_evd(Mat::<c64>::identity(n, n).as_ref());
3037		}
3038	}
3039	#[test]
3040	fn test_pinv() {
3041		let rng = &mut StdRng::seed_from_u64(0);
3042		let n = 36;
3043		let mat = CwiseMatDistribution {
3044			nrows: n,
3045			ncols: n,
3046			dist: StandardNormal,
3047		}
3048		.rand::<Mat<f64>>(rng);
3049		let mat = &mat + mat.adjoint();
3050		let pinv = mat.self_adjoint_eigen(Side::Lower, Par::Seq).unwrap().pseudoinverse(Par::Seq);
3051		let err = &mat * &pinv - Mat::<f64>::identity(n, n);
3052		assert!(err.norm_max() < 1e-10);
3053	}
3054}
3055#[cfg(test)]
3056mod scale_and_empty_tests {
3057	//! Regression tests for `tensor4all/faer#42` and `#44`: the 2x2 shortcut
3058	//! averaged the diagonal as `(a + d) * 0.5`, which overflows for a finite
3059	//! spectrum near the floating-point limit, and the scratch query computed
3060	//! `n - 1` on an empty matrix.
3061	use super::*;
3062	#[test]
3063	fn two_by_two_finite_spectrum_at_the_range_limit() {
3064		let a = crate::mat![[1e308f64, 0.0], [0.0, 1e308]];
3065		let eig = a.self_adjoint_eigen(Side::Lower, Par::Seq).unwrap();
3066		assert_eq!(eig.S()[0], 1e308);
3067		assert_eq!(eig.S()[1], 1e308);
3068		let a = crate::mat![[1e38f32, 0.0], [0.0, 1e38]];
3069		let eig = a.self_adjoint_eigen(Side::Lower, Par::Seq).unwrap();
3070		assert_eq!(eig.S()[0], 1e38);
3071		assert_eq!(eig.S()[1], 1e38);
3072	}
3073	/// halving a subnormal is not exact, so the direct trace form stays in
3074	/// charge wherever it does not overflow
3075	#[test]
3076	fn two_by_two_subnormal_diagonal() {
3077		let q = f64::from_bits(1);
3078		let a = Mat::<f64>::from_fn(2, 2, |i, j| if i == j { q } else { 0.0 });
3079		let eig = a.self_adjoint_eigen(Side::Lower, Par::Seq).unwrap();
3080		assert_eq!(eig.S()[0], q);
3081		assert_eq!(eig.S()[1], q);
3082	}
3083	#[test]
3084	fn empty_matrix() {
3085		let a = Mat::<f64>::zeros(0, 0);
3086		let eig = a.self_adjoint_eigen(Side::Lower, Par::Seq).unwrap();
3087		assert_eq!(eig.U().shape(), (0, 0));
3088		assert_eq!(eig.S().dim(), 0);
3089	}
3090	/// the tridiagonal entry point with eigenvectors, for an empty complex
3091	/// matrix (`tensor4all/faer#31`)
3092	#[test]
3093	fn empty_tridiagonal_with_vectors() {
3094		fn check<T: ComplexField>() {
3095			let empty = Diag::<T>::zeros(0);
3096			let mut s = Diag::<T>::zeros(0);
3097			let mut u = Mat::<T>::zeros(0, 0);
3098			let params = default();
3099			let mut mem = dyn_stack::MemBuffer::new(
3100				tridiagonal_self_adjoint_evd_scratch::<T>(
3101					0,
3102					ComputeEigenvectors::Yes,
3103					ParShape::Seq,
3104					params,
3105				),
3106			);
3107			tridiagonal_self_adjoint_evd(
3108				empty.as_ref(),
3109				empty.as_ref(),
3110				s.as_mut(),
3111				Some(u.as_mut()),
3112				Par::Seq,
3113				MemStack::new(&mut mem),
3114				params,
3115			)
3116			.unwrap();
3117		}
3118		check::<f64>();
3119		check::<c64>();
3120		check::<c32>();
3121	}
3122}
3123#[cfg(test)]
3124mod tridiagonal_range_tests {
3125	//! Regression tests for `tensor4all/faer#57`: the tridiagonal backend
3126	//! normalized by the reciprocal of the largest entry, which has no finite
3127	//! value for a subnormal spectrum, and the divide-and-conquer backend did
3128	//! not normalize at all, so a spectrum near the top of the range overflowed
3129	//! instead. Both spectra below are finite and exactly representable.
3130	use super::*;
3131	use crate::assert;
3132	use crate::diag::{DiagMut, DiagRef};
3133	use crate::dyn_stack::MemBuffer;
3134
3135	fn tridiagonal_evd(
3136		diag: &[f64],
3137		offdiag: &[f64],
3138		threshold: usize,
3139	) -> Result<Vec<f64>, EvdError> {
3140		let n = diag.len();
3141		let mut s = vec![0.0; n];
3142		let mut u = Mat::<f64>::zeros(n, n);
3143		let mut params = Spec::<SelfAdjointEvdParams, f64>::default();
3144		params.recursion_threshold = threshold;
3145		let mut mem =
3146			MemBuffer::new(tridiagonal_self_adjoint_evd_scratch::<f64>(
3147				n,
3148				ComputeEigenvectors::Yes,
3149				crate::ParShape::Seq,
3150				params,
3151			));
3152		tridiagonal_self_adjoint_evd(
3153			DiagRef::from_slice(diag),
3154			DiagRef::from_slice(offdiag),
3155			DiagMut::from_slice_mut(&mut s),
3156			Some(u.as_mut()),
3157			Par::Seq,
3158			MemStack::new(&mut mem),
3159			params,
3160		)?;
3161		Ok(s)
3162	}
3163
3164	/// the reported matrix, and the same 2x2 with a coupling that is not
3165	/// negligible
3166	#[test]
3167	fn two_by_two_at_the_range_limit() {
3168		let s = tridiagonal_evd(&[-1e308, 1e308], &[0.0], usize::MAX).unwrap();
3169		assert_eq!(s, [-1e308, 1e308]);
3170		let s =
3171			tridiagonal_evd(&[-1e308, 1e308], &[1e308], usize::MAX).unwrap();
3172		let sqrt2 = 2.0f64.sqrt();
3173		assert_eq!(s[0], -sqrt2 * 1e308);
3174		assert_eq!(s[1], sqrt2 * 1e308);
3175	}
3176
3177	/// the reported subnormal matrix, whose spectrum is `s * (2 - sqrt2, 2,
3178	/// 2 + sqrt2)`
3179	#[test]
3180	fn subnormal_three_by_three() {
3181		let s = 1e-310f64;
3182		let sqrt2 = 2.0f64.sqrt();
3183		let got =
3184			tridiagonal_evd(&[2.0 * s; 3], &[-s, -s], usize::MAX).unwrap();
3185		for (got, exact) in
3186			got.iter()
3187				.zip([s * (2.0 - sqrt2), 2.0 * s, s * (2.0 + sqrt2)])
3188		{
3189			assert!(
3190				(got - exact).abs() <= 1e-13 * s,
3191				"got {got:e}, exact {exact:e}"
3192			);
3193		}
3194	}
3195
3196	/// both backends, at both ends of the range, against the closed form of a
3197	/// constant tridiagonal matrix
3198	#[test]
3199	fn spectrum_at_both_range_limits() {
3200		let n = 5;
3201		for scale in [1e308f64, 1e-310] {
3202			let diag = vec![scale; n];
3203			let offdiag = vec![0.3 * scale; n - 1];
3204			// `usize::MAX` keeps the QR fallback in charge, `4` recurses
3205			for threshold in [usize::MAX, 4] {
3206				let s = tridiagonal_evd(&diag, &offdiag, threshold).unwrap();
3207				for (k, x) in s.iter().enumerate() {
3208					let exact = scale
3209						* (1.0
3210							+ 0.6
3211								* (core::f64::consts::PI * (n - k) as f64
3212									/ (n + 1) as f64)
3213									.cos());
3214					assert!(x.is_finite());
3215					assert!(
3216						(x - exact).abs() <= 1e-13 * scale,
3217						"threshold {threshold}: got {x:e}, exact {exact:e}"
3218					);
3219				}
3220			}
3221		}
3222	}
3223}
3224
3225#[cfg(test)]
3226mod geev_scaling_tests {
3227	//! Regression tests for the input scaling of `evd_imp` (#112). A matrix whose largest entry is
3228	//! near the bottom of the range went through the Hessenberg reduction and the Schur iteration
3229	//! in subnormals: a Grcar or random matrix scaled by `2^-120` (`f32`, `c32`) or `2^-1010`
3230	//! (`f64`, `c64`) had a GET22 ratio of about `1 / eps` (`4e6` and `2e15`). Near the top the
3231	//! complex Schur iteration lost a factor of three against `cgeev`. Residuals are measured in
3232	//! `f64` on the dimensionless matrix `a / scale`, exact for a power of two. The left side is
3233	//! checked as the right eigenvectors of `a^H`; the `u_left` branch of `evd_imp` itself is
3234	//! exercised by the LAPACK nonsymmetric EVD tests.
3235	use crate::{assert, c32, c64, Mat, Par};
3236	use num_complex::Complex64 as Z;
3237
3238	/// GET22's `||a u - u diag(w)||_1 / (||a||_1 ||u||_1 eps)` for one side
3239	fn get22(a: &[Vec<Z>], u: &[Vec<Z>], w: &[Z], eps: f64) -> f64 {
3240		let n = a.len();
3241		let (mut an, mut un, mut err) = (0.0f64, 0.0f64, 0.0f64);
3242		for j in 0..n {
3243			let (mut ac, mut uc, mut ec) = (0.0, 0.0, 0.0);
3244			for i in 0..n {
3245				ac += a[i][j].norm();
3246				uc += u[i][j].norm();
3247				let mut r = -u[i][j] * w[j];
3248				for k in 0..n {
3249					r += a[i][k] * u[k][j];
3250				}
3251				ec += r.norm();
3252			}
3253			an = an.max(ac);
3254			un = un.max(uc);
3255			err = err.max(ec);
3256		}
3257		err / an / un / eps
3258	}
3259
3260	/// the Grcar matrix (ones on the diagonal and the three superdiagonals, minus one on the
3261	/// subdiagonal) and a pseudo-random matrix, both with largest entry one
3262	fn unit(n: usize, cplx: bool) -> [Vec<Vec<Z>>; 2] {
3263		let grcar = (0..n)
3264			.map(|i| {
3265				(0..n)
3266					.map(|j| match j as isize - i as isize {
3267						-1 => Z::new(-1.0, 0.0),
3268						0..=3 => Z::new(1.0, 0.0),
3269						_ => Z::new(0.0, 0.0),
3270					})
3271					.collect()
3272			})
3273			.collect();
3274		let mut state = 0x2545_f491_4f6c_dd1du64;
3275		let mut next = move || {
3276			state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
3277			// a multiple of `2^-10` in `[-1, 1)`, exact in every precision
3278			((state >> 54) as f64 - 512.0) / 512.0
3279		};
3280		let rand = (0..n)
3281			.map(|_| (0..n).map(|_| Z::new(next(), if cplx { next() } else { 0.0 })).collect())
3282			.collect();
3283		[grcar, rand]
3284	}
3285
3286	macro_rules! check {
3287		($t:ty, $mk:expr, $z:expr, $eps:expr, $scales:expr, $cplx:expr) => {{
3288			let n = 32;
3289			// the GET22 threshold of the LAPACK nonsymmetric EVD tests
3290			let thresh = 20.0 * f64::max(1.0, (n as f64 / 16.0).sqrt());
3291			for unit in unit(n, $cplx) {
3292				for e in $scales {
3293					let scale = 2.0f64.powi(e);
3294					let a = Mat::<$t>::from_fn(n, n, |i, j| $mk(unit[i][j] * scale));
3295					for (side, m) in [("right", a.clone()), ("left", a.adjoint().to_owned())] {
3296						let dimless: Vec<Vec<Z>> = (0..n).map(|i| (0..n).map(|j| $z(m[(i, j)]) / scale).collect()).collect();
3297						let evd = m.eigen(Par::Seq).unwrap();
3298						let w: Vec<Z> = (0..n).map(|i| Z::new(evd.S()[i].re as f64, evd.S()[i].im as f64) / scale).collect();
3299						let u: Vec<Vec<Z>> = (0..n).map(|i| (0..n).map(|j| Z::new(evd.U()[(i, j)].re as f64, evd.U()[(i, j)].im as f64)).collect()).collect();
3300						// `f64::max` drops a NaN, so a nonfinite result is rejected first
3301						assert!(w.iter().all(|w| w.is_finite()) && u.iter().flatten().all(|u| u.is_finite()));
3302						let r = get22(&dimless, &u, &w, $eps);
3303						assert!(r <= thresh, "{} scale 2^{e} {side}: GET22 {r}", stringify!($t));
3304					}
3305				}
3306			}
3307		}};
3308	}
3309
3310	/// A defective double real eigenvalue, whose 2 x 2 block the Schur iteration returned lower
3311	/// triangular (`[[1, 0], [c, 1]]`) because `lahqr_schur22` lacked `dlanv2`'s `B = 0` case. The
3312	/// eigenvector back substitution then treated that block as a conjugate pair with a zero
3313	/// imaginary part: `[[2, 1], [-1, 0]]` returned an eigenvector with `|A v - v| = sqrt(2)`.
3314	/// `dgeev` gives two nearly parallel eigenvectors with small residuals.
3315	#[test]
3316	fn defective_double_eigenvalue_has_small_residuals() {
3317		let jordan = |a: [[f64; 3]; 3], n: usize| -> Vec<Vec<Z>> {
3318			(0..n).map(|i| (0..n).map(|j| Z::new(a[i][j], 0.0)).collect()).collect()
3319		};
3320		let cases = [
3321			(jordan([[2.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0; 3]], 2)),
3322			(jordan([[0.0, -1.0, 0.0], [1.0, 2.0, 0.0], [0.0; 3]], 2)),
3323			(jordan([[0.0, -1.0, 5.0], [1.0, 2.0, 1.0], [0.0, 0.0, 3.0]], 3)),
3324			(jordan([[2.0, 1.0, -1.0], [-1.0, 0.0, 2.0], [0.0, 0.0, 3.0]], 3)),
3325		];
3326		for a in &cases {
3327			let n = a.len();
3328			let m = Mat::<f64>::from_fn(n, n, |i, j| a[i][j].re);
3329			let evd = m.eigen(Par::Seq).unwrap();
3330			let w: Vec<Z> = (0..n).map(|i| Z::new(evd.S()[i].re, evd.S()[i].im)).collect();
3331			let u: Vec<Vec<Z>> = (0..n).map(|i| (0..n).map(|j| Z::new(evd.U()[(i, j)].re, evd.U()[(i, j)].im)).collect()).collect();
3332			assert!(w.iter().all(|w| w.is_finite()) && u.iter().flatten().all(|u| u.is_finite()));
3333			let r = get22(a, &u, &w, f64::EPSILON);
3334			assert!(r <= 20.0, "{a:?}: GET22 {r}");
3335		}
3336	}
3337
3338	/// The ends of the range for a complex matrix, where faer's complex modulus overflows
3339	/// (`2^1023 + 0i`, `2^127 + 0i`) or underflows (`2^-1074`, `2^-149`): the eigenvalues come
3340	/// back exactly.
3341	#[test]
3342	fn evd_scaling_at_the_complex_range_ends() {
3343		let top = c64::new(2.0f64.powi(1023), 0.0);
3344		let evd = Mat::from_fn(1, 1, |_, _| top).eigen(Par::Seq).unwrap();
3345		assert!(evd.S()[0] == top);
3346		let top = c32::new(2.0f32.powi(127), 0.0);
3347		let evd = Mat::from_fn(1, 1, |_, _| top).eigen(Par::Seq).unwrap();
3348		assert!(evd.S()[0] == top);
3349		// a rotation by the smallest subnormal: eigenvalues `+-i m`
3350		let m = f64::from_bits(1);
3351		let a = Mat::from_fn(2, 2, |i, j| match (i, j) {
3352			(0, 1) => c64::new(-m, 0.0),
3353			(1, 0) => c64::new(m, 0.0),
3354			_ => c64::new(0.0, 0.0),
3355		});
3356		let evd = a.eigen(Par::Seq).unwrap();
3357		let mut w = [evd.S()[0], evd.S()[1]];
3358		w.sort_by(|a, b| a.im.partial_cmp(&b.im).unwrap());
3359		assert!(w == [c64::new(0.0, -m), c64::new(0.0, m)]);
3360		let m = f32::from_bits(1);
3361		let a = Mat::from_fn(2, 2, |i, j| match (i, j) {
3362			(0, 1) => c32::new(-m, 0.0),
3363			(1, 0) => c32::new(m, 0.0),
3364			_ => c32::new(0.0, 0.0),
3365		});
3366		let evd = a.eigen(Par::Seq).unwrap();
3367		let mut w = [evd.S()[0], evd.S()[1]];
3368		w.sort_by(|a, b| a.im.partial_cmp(&b.im).unwrap());
3369		assert!(w == [c32::new(0.0, -m), c32::new(0.0, m)]);
3370	}
3371
3372	#[test]
3373	fn evd_scales_its_input_into_range() {
3374		check!(f32, |z: Z| z.re as f32, |x: f32| Z::new(x as f64, 0.0), f32::EPSILON as f64, [-120, -100, 0, 100, 120], false);
3375		check!(c32, |z: Z| c32::new(z.re as f32, z.im as f32), |x: c32| Z::new(x.re as f64, x.im as f64), f32::EPSILON as f64, [-120, -100, 0, 100, 120], true);
3376		check!(f64, |z: Z| z.re, |x: f64| Z::new(x, 0.0), f64::EPSILON, [-1010, -1000, -960, 0, 960, 1015], false);
3377		check!(c64, |z: Z| c64::new(z.re, z.im), |x: c64| Z::new(x.re, x.im), f64::EPSILON, [-1010, -1000, -960, 0, 960, 1015], true);
3378	}
3379}
3380
3381#[cfg(test)]
3382#[path = "tests/mod.rs"]
3383mod lapack;