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