Skip to main content

faer/linalg/evd/
mod.rs

1//! low level implementation of the eigenvalue decomposition of a square
2//! diagonalizable matrix.
3//!
4//! the eigenvalue decomposition of a square matrix $A$ of shape $(n, n)$ is a
5//! decomposition into two components $U$, $S$:
6//!
7//! - $U$ has shape $(n, n)$ and is invertible
8//! - $S$ has shape $(n, n)$ and is a diagonal matrix
9//! - and finally:
10//!
11//! $$A = U S U^{-1}$$
12//!
13//! if $A$ is self-adjoint, then $U$ can be made unitary ($U^{-1} = U^H$), and
14//! $S$ is real valued
15/// hessenberg decomposition
16pub mod hessenberg;
17#[doc(hidden)]
18pub mod schur;
19/// self-adjoint tridiagonalization
20pub mod tridiag;
21pub(crate) mod tridiag_evd;
22use crate::assert;
23use crate::internal_prelude::*;
24use hessenberg::HessenbergParams;
25use linalg::matmul::triangular::BlockStructure;
26pub use schur::SchurParams;
27use tridiag::TridiagParams;
28/// eigendecomposition error
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub enum EvdError {
31	/// reached max iterations
32	NoConvergence,
33}
34/// schur to eigendecomposition conversion parameters
35#[derive(Clone, Copy, Debug)]
36pub struct EvdFromSchurParams {
37	/// threshold at which the implementation should stop recursing
38	pub recursion_threshold: usize,
39	#[doc(hidden)]
40	pub non_exhaustive: NonExhaustive,
41}
42/// eigendecomposition tuning parameters
43#[derive(Clone, Copy, Debug)]
44pub struct EvdParams {
45	/// hessenberg parameters
46	pub hessenberg: HessenbergParams,
47	/// schur from hessenberg conversion parameters
48	pub schur: SchurParams,
49	/// eigendecomposition from schur conversion parameters
50	pub evd_from_schur: EvdFromSchurParams,
51	#[doc(hidden)]
52	pub non_exhaustive: NonExhaustive,
53}
54/// self-adjoint eigendecomposition tuning parameters
55#[derive(Clone, Copy, Debug)]
56pub struct SelfAdjointEvdParams {
57	/// tridiagonalization parameters
58	pub tridiag: TridiagParams,
59	/// threshold at which the implementation should stop recursing
60	pub recursion_threshold: usize,
61	#[doc(hidden)]
62	pub non_exhaustive: NonExhaustive,
63}
64impl<T: ComplexField> Auto<T> for EvdParams {
65	fn auto() -> Self {
66		Self {
67			hessenberg: auto!(T),
68			schur: auto!(T),
69			evd_from_schur: auto!(T),
70			non_exhaustive: NonExhaustive(()),
71		}
72	}
73}
74impl<T: ComplexField> Auto<T> for EvdFromSchurParams {
75	fn auto() -> Self {
76		Self {
77			recursion_threshold: 64,
78			non_exhaustive: NonExhaustive(()),
79		}
80	}
81}
82impl<T: ComplexField> Auto<T> for SelfAdjointEvdParams {
83	fn auto() -> Self {
84		Self {
85			tridiag: auto!(T),
86			recursion_threshold: 128,
87			non_exhaustive: NonExhaustive(()),
88		}
89	}
90}
91/// whether the eigenvectors should be computed
92#[derive(Copy, Clone, Debug, PartialEq, Eq)]
93pub enum ComputeEigenvectors {
94	/// do not compute eigenvectors
95	No,
96	/// compute eigenvectors
97	Yes,
98}
99/// computes the layout of the workspace required to compute a self-adjoint
100/// matrix's eigendecomposition
101pub fn self_adjoint_evd_scratch<T: ComplexField>(
102	dim: usize,
103	compute_u: ComputeEigenvectors,
104	par: Par,
105	params: Spec<SelfAdjointEvdParams, T>,
106) -> StackReq {
107	let n = dim;
108	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
109	let prologue = StackReq::all_of(&[
110		temp_mat_scratch::<T>(n, n),
111		temp_mat_scratch::<T>(bs, n),
112		StackReq::any_of(&[tridiag::tridiag_in_place_scratch::<T>(
113			n,
114			par,
115			params.tridiag.into(),
116		)]),
117		temp_mat_scratch::<T::Real>(n, 1).array(2),
118	]);
119	if compute_u == ComputeEigenvectors::No {
120		return prologue;
121	}
122	StackReq::all_of(&[
123		prologue,
124		temp_mat_scratch::<T::Real>(
125			n,
126			if const {
127				T::IS_REAL
128			} {
129				0
130			} else {
131				n
132			},
133		)
134		.array(2),
135		StackReq::any_of(&[
136			if n < params.recursion_threshold {
137				StackReq::empty()
138			} else {
139				tridiag_evd::divide_and_conquer_scratch::<T>(n, par)
140			},
141			temp_mat_scratch::<T>(n, 1),
142			linalg::householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(n - 1, bs, n),
143		]),
144	])
145}
146/// computes the matrix $A$'s eigendecomposition, assuming it is tridiagonal and
147/// self-adjoint
148///
149/// the eigenvalues are stored in $S$, and the eigenvectors in $U$ such that the
150/// eigenvalues are sorted in nondecreasing order
151pub fn tridiagonal_self_adjoint_evd<T: ComplexField>(
152	diag: DiagRef<'_, T>,
153	subdiag: DiagRef<'_, T>,
154	s: DiagMut<'_, T>,
155	u: Option<MatMut<'_, T>>,
156	par: Par,
157	stack: &mut MemStack,
158	params: Spec<SelfAdjointEvdParams, T>,
159) -> Result<(), EvdError> {
160	let n = diag.dim();
161	let (mut real_diag, stack) =
162		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
163	let (mut real_offdiag, stack) =
164		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
165	let mut real_diag = real_diag
166		.as_mat_mut()
167		.col_mut(0)
168		.try_as_col_major_mut()
169		.unwrap();
170	let mut real_offdiag = real_offdiag
171		.as_mat_mut()
172		.col_mut(0)
173		.try_as_col_major_mut()
174		.unwrap();
175	for i in 0..n {
176		real_diag[i] = diag[i].real();
177		if i + 1 < n {
178			if const { T::IS_REAL } {
179				real_offdiag[i] = subdiag[i].real();
180			} else {
181				real_offdiag[i] = subdiag[i].abs();
182			}
183		} else {
184			real_offdiag[i] = zero();
185		}
186	}
187	let mut s = s;
188	let mut u = match u {
189		Some(u) => u,
190		None => {
191			tridiag_evd::qr_algorithm(
192				real_diag.rb_mut(),
193				real_offdiag.rb_mut(),
194				None,
195			)?;
196			for i in 0..n {
197				s[i] = real_diag[i].to_cplx();
198			}
199			return Ok(());
200		},
201	};
202	let (mut u_real, stack) = unsafe {
203		temp_mat_uninit::<T::Real, _, _>(
204			n,
205			if T::IS_REAL { 0 } else { n },
206			stack,
207		)
208	};
209	let mut u_real = u_real.as_mat_mut();
210	let mut u_evd = if const { T::IS_REAL } {
211		unsafe { core::mem::transmute(u.rb_mut()) }
212	} else {
213		u_real.rb_mut()
214	};
215	if n < params.recursion_threshold {
216		tridiag_evd::qr_algorithm(
217			real_diag.rb_mut(),
218			real_offdiag.rb_mut(),
219			Some(u_evd.rb_mut()),
220		)?;
221	} else {
222		tridiag_evd::divide_and_conquer::<T::Real>(
223			real_diag.rb_mut(),
224			real_offdiag.rb_mut(),
225			u_evd.rb_mut(),
226			par,
227			stack,
228			params.recursion_threshold,
229		)?;
230	}
231	if const { !T::IS_REAL } {
232		let normalized = |x: T| {
233			if x == zero() {
234				one()
235			} else {
236				x.mul_real(x.abs().recip())
237			}
238		};
239		let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
240		let mut scale = scale
241			.as_mat_mut()
242			.col_mut(0)
243			.try_as_col_major_mut()
244			.unwrap();
245		let mut x = one::<T>();
246		scale[0] = one();
247		for i in 1..n {
248			x = normalized(&subdiag[i - 1] * x);
249			scale[i] = x.copy();
250		}
251		for j in 0..n {
252			z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(
253				|uz!(u, real, scale)| {
254					*u = scale.mul_real(real);
255				},
256			);
257		}
258	}
259	for i in 0..n {
260		s[i] = real_diag[i].to_cplx();
261	}
262	Ok(())
263}
264/// computes the matrix $A$'s eigendecomposition, assuming it is self-adjoint
265///
266/// the eigenvalues are stored in $S$, and the eigenvectors in $U$ such that the
267/// eigenvalues are sorted in nondecreasing order
268///
269/// only the lower triangular half of $A$ is accessed
270pub fn self_adjoint_evd<T: ComplexField>(
271	A: MatRef<'_, T>,
272	s: DiagMut<'_, T>,
273	u: Option<MatMut<'_, T>>,
274	par: Par,
275	stack: &mut MemStack,
276	params: Spec<SelfAdjointEvdParams, T>,
277) -> Result<(), EvdError> {
278	let n = A.nrows();
279	assert!(all(A.nrows() == A.ncols(), s.dim() == n));
280	if let Some(u) = u.rb() {
281		assert!(all(u.nrows() == n, u.ncols() == n));
282	}
283	let s = s.column_vector_mut();
284	if n == 0 {
285		return Ok(());
286	}
287	#[cfg(feature = "perf-warn")]
288	if let Some(matrix) = u.rb() {
289		if matrix.row_stride().unsigned_abs() != 1
290			&& crate::__perf_warn!(QR_WARN)
291		{
292			if matrix.col_stride().unsigned_abs() == 1 {
293				log::warn!(
294					target : "faer_perf",
295					"EVD prefers column-major eigenvector matrix. Found row-major matrix."
296				);
297			} else {
298				log::warn!(
299					target : "faer_perf",
300					"EVD prefers column-major eigenvector matrix. Found matrix with generic strides."
301				);
302			}
303		}
304	}
305	let (mut trid, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
306	let mut trid = trid.as_mat_mut();
307	trid.copy_from_triangular_lower(A);
308	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
309	let (mut householder, stack) =
310		unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
311	let mut householder = householder.as_mat_mut();
312	{
313		tridiag::tridiag_in_place(
314			trid.rb_mut(),
315			householder.rb_mut(),
316			par,
317			stack,
318			params.tridiag.into(),
319		);
320	}
321	let trid = trid.rb();
322	let (mut diag, stack) =
323		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
324	let (mut offdiag, stack) =
325		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
326	let mut diag = diag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
327	let mut offdiag = offdiag
328		.as_mat_mut()
329		.col_mut(0)
330		.try_as_col_major_mut()
331		.unwrap();
332	for i in 0..n {
333		diag[i] = trid[(i, i)].real();
334		if i + 1 < n {
335			if const { T::IS_REAL } {
336				offdiag[i] = trid[(i + 1, i)].real();
337			} else {
338				offdiag[i] = trid[(i + 1, i)].abs();
339			}
340		} else {
341			offdiag[i] = zero();
342		}
343	}
344	let mut s = s;
345	let mut u = match u {
346		Some(u) => u,
347		None => {
348			tridiag_evd::qr_algorithm(diag.rb_mut(), offdiag.rb_mut(), None)?;
349			for i in 0..n {
350				s[i] = diag[i].to_cplx();
351			}
352			return Ok(());
353		},
354	};
355	let (mut u_real, stack) = unsafe {
356		temp_mat_uninit::<T::Real, _, _>(
357			n,
358			if T::IS_REAL { 0 } else { n },
359			stack,
360		)
361	};
362	let mut u_real = u_real.as_mat_mut();
363	let mut u_evd = if const { T::IS_REAL } {
364		unsafe { core::mem::transmute(u.rb_mut()) }
365	} else {
366		u_real.rb_mut()
367	};
368	if n < params.recursion_threshold {
369		tridiag_evd::qr_algorithm(
370			diag.rb_mut(),
371			offdiag.rb_mut(),
372			Some(u_evd.rb_mut()),
373		)?;
374	} else {
375		tridiag_evd::divide_and_conquer::<T::Real>(
376			diag.rb_mut(),
377			offdiag.rb_mut(),
378			u_evd.rb_mut(),
379			par,
380			stack,
381			params.recursion_threshold,
382		)?;
383	}
384	if const { !T::IS_REAL } {
385		let normalized = |x: T| {
386			if x == zero() {
387				one()
388			} else {
389				x.mul_real(x.abs().recip())
390			}
391		};
392		let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
393		let mut scale = scale
394			.as_mat_mut()
395			.col_mut(0)
396			.try_as_col_major_mut()
397			.unwrap();
398		let mut x = one::<T>();
399		scale[0] = one();
400		for i in 1..n {
401			x = normalized(&trid[(i, i - 1)] * x);
402			scale[i] = x.copy();
403		}
404		for j in 0..n {
405			z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(
406				|uz!(u, real, scale)| {
407					*u = scale.mul_real(real);
408				},
409			);
410		}
411	}
412	linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
413		trid.submatrix(1, 0, n - 1, n - 1),
414		householder.rb(),
415		Conj::No,
416		u.rb_mut().subrows_mut(1, n - 1),
417		par,
418		stack,
419	);
420	for i in 0..n {
421		s[i] = diag[i].to_cplx();
422	}
423	Ok(())
424}
425/// computes the layout of the workspace required to compute a self-adjoint
426/// matrix's pseudoinverse, given the eigendecomposition
427pub fn pseudoinverse_from_self_adjoint_evd_scratch<T: ComplexField>(
428	dim: usize,
429	par: Par,
430) -> StackReq {
431	_ = par;
432	temp_mat_scratch::<T>(dim, dim).array(2)
433}
434/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition
435/// factors $S$ and $U$
436#[track_caller]
437pub fn pseudoinverse_from_self_adjoint_evd<T: ComplexField>(
438	pinv: MatMut<'_, T>,
439	s: DiagRef<'_, T>,
440	u: MatRef<'_, T>,
441	par: Par,
442	stack: &mut MemStack,
443) {
444	pseudoinverse_from_self_adjoint_evd_with_tolerance(
445		pinv,
446		s,
447		u,
448		zero(),
449		eps::<T::Real>() * from_f64::<T::Real>(u.ncols() as f64),
450		par,
451		stack,
452	);
453}
454/// computes a self-adjoint matrix's pseudoinverse, given the eigendecomposition
455/// factors $S$ and $U$, and tolerance parameters for determining zero
456/// eigenvalues
457#[track_caller]
458pub fn pseudoinverse_from_self_adjoint_evd_with_tolerance<T: ComplexField>(
459	pinv: MatMut<'_, T>,
460	s: DiagRef<'_, T>,
461	u: MatRef<'_, T>,
462	abs_tol: T::Real,
463	rel_tol: T::Real,
464	par: Par,
465	stack: &mut MemStack,
466) {
467	let s = s.column_vector();
468	let mut pinv = pinv;
469	let n = u.ncols();
470	assert!(all(u.nrows() == n, u.ncols() == n, s.nrows() == n));
471	let smax = s.norm_max();
472	let tol = abs_tol.fmax(rel_tol * smax);
473	let (mut u_trunc, stack) =
474		unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
475	let (mut up_trunc, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
476	let mut u_trunc = u_trunc.as_mat_mut();
477	let mut up_trunc = up_trunc.as_mat_mut();
478	let mut len = 0;
479	for j in 0..n {
480		let x = s[j].absmax();
481		if x > tol {
482			let ref p = s[j].real().recip();
483			u_trunc.rb_mut().col_mut(len).copy_from(u.col(j));
484			z!(up_trunc.rb_mut().col_mut(len), u.col(j))
485				.for_each(|uz!(dst, src)| *dst = src.mul_real(p));
486			len += 1;
487		}
488	}
489	let u_trunc = u_trunc.get(.., ..len);
490	let up_trunc = up_trunc.get(.., ..len);
491	linalg::matmul::triangular::matmul(
492		pinv.rb_mut(),
493		BlockStructure::TriangularLower,
494		Accum::Replace,
495		up_trunc.rb(),
496		BlockStructure::Rectangular,
497		u_trunc.rb().adjoint(),
498		BlockStructure::Rectangular,
499		one(),
500		par,
501	);
502	for j in 0..n {
503		for i in 0..j {
504			pinv[(i, j)] = pinv[(j, i)].conj();
505		}
506	}
507}
508fn dot2x1<T: RealField>(
509	lhs0: RowRef<'_, T>,
510	lhs1: RowRef<'_, T>,
511	rhs: ColRef<'_, T>,
512) -> (T, T) {
513	let n = rhs.nrows();
514	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
515	let mut acc00 = zero::<T>();
516	let mut acc01 = zero::<T>();
517	let mut acc10 = zero::<T>();
518	let mut acc11 = zero::<T>();
519	let n2 = n / 2 * 2;
520	let mut i = 0;
521	while i < n2 {
522		acc00 += &lhs0[i] * &rhs[i];
523		acc10 += &lhs1[i] * &rhs[i];
524		acc01 += &lhs0[i + 1] * &rhs[i + 1];
525		acc11 += &lhs1[i + 1] * &rhs[i + 1];
526		i += 2;
527	}
528	while i < n {
529		acc00 += &lhs0[i] * &rhs[i];
530		acc10 += &lhs1[i] * &rhs[i];
531		i += 1;
532	}
533	(acc00 + acc01, acc10 + acc11)
534}
535fn dot2x2<T: RealField>(
536	lhs0: RowRef<'_, T>,
537	lhs1: RowRef<'_, T>,
538	rhs0: ColRef<'_, T>,
539	rhs1: ColRef<'_, T>,
540) -> (T, T, T, T) {
541	let n = rhs0.nrows();
542	assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
543	let mut acc0 = zero::<T>();
544	let mut acc1 = zero::<T>();
545	let mut acc2 = zero::<T>();
546	let mut acc3 = zero::<T>();
547	let mut i = 0;
548	while i < n {
549		acc0 += &lhs0[i] * &rhs0[i];
550		acc1 += &lhs1[i] * &rhs0[i];
551		acc2 += &lhs0[i] * &rhs1[i];
552		acc3 += &lhs1[i] * &rhs1[i];
553		i += 1;
554	}
555	(acc0, acc1, acc2, acc3)
556}
557pub(crate) fn evd_from_real_schur_imp<T: RealField>(
558	A: MatRef<'_, T>,
559	V: MatMut<'_, T>,
560	par: Par,
561	params: EvdFromSchurParams,
562) {
563	let one = one::<T>;
564	let zero = zero::<T>;
565	let mut V = V;
566	let n = A.nrows();
567	let mut norm = zero();
568	for j in 0..n {
569		for i in 0..Ord::min(j + 2, n) {
570			norm += A[(i, j)].abs1();
571		}
572	}
573	let mut k = n;
574	loop {
575		if k == 0 {
576			break;
577		}
578		k -= 1;
579		if k == 0 || A[(k, k - 1)] == zero() {
580			let ref p = A[(k, k)].copy();
581			V[(k, k)] = one();
582			for i in 0..k {
583				V[(i, k)] = -&A[(i, k)];
584			}
585			solve_real_shifted_upper_quasi_triangular_system(
586				A.submatrix(0, 0, k, k),
587				p.copy(),
588				V.rb_mut().subrows_mut(0, k).col_mut(k),
589				norm.copy(),
590				par,
591				params,
592			);
593		} else {
594			let ref p = A[(k, k)].copy();
595			let ref q = A[(k, k - 1)].abs().sqrt() * A[(k - 1, k)].abs().sqrt();
596			if A[(k - 1, k)].abs() >= A[(k, k - 1)].abs() {
597				V[(k - 1, k - 1)] = one();
598				V[(k, k)] = q / &A[(k - 1, k)];
599			} else {
600				V[(k - 1, k - 1)] = -q / &A[(k, k - 1)];
601				V[(k, k)] = one();
602			}
603			V[(k - 1, k)] = zero();
604			V[(k, k - 1)] = zero();
605			for i in 0..k - 1 {
606				V[(i, k - 1)] = -&V[(k - 1, k - 1)] * &A[(i, k - 1)];
607				V[(i, k)] = -&V[(k, k)] * &A[(i, k)];
608			}
609			solve_cplx_shifted_upper_quasi_triangular_system(
610				A.submatrix(0, 0, k - 1, k - 1),
611				p.copy(),
612				q.copy(),
613				V.rb_mut().submatrix_mut(0, k - 1, k - 1, 2),
614				norm.copy(),
615				par,
616				params,
617			);
618			k -= 1;
619		}
620	}
621}
622pub(crate) fn evd_from_cplx_schur_imp<T: ComplexField>(
623	A: MatRef<'_, T>,
624	conj_A: Conj,
625	V: MatMut<'_, T>,
626	par: Par,
627	params: EvdFromSchurParams,
628) {
629	let one = one::<T>;
630	let mut V = V;
631	let n = A.nrows();
632	let mut norm = zero::<T::Real>();
633	for j in 0..n {
634		for i in 0..j + 1 {
635			norm += A[(i, j)].abs1();
636		}
637	}
638	let mut k = n;
639	loop {
640		if k == 0 {
641			break;
642		}
643		k -= 1;
644		let ref p = if conj_A == Conj::Yes {
645			A[(k, k)].conj()
646		} else {
647			A[(k, k)].copy()
648		};
649		V[(k, k)] = one();
650		if conj_A == Conj::Yes {
651			for i in 0..k {
652				V[(i, k)] = -A[(i, k)].conj();
653			}
654		} else {
655			for i in 0..k {
656				V[(i, k)] = -&A[(i, k)];
657			}
658		}
659		solve_shifted_upper_triangular_system(
660			A.submatrix(0, 0, k, k),
661			conj_A,
662			p.copy(),
663			V.rb_mut().subrows_mut(0, k).col_mut(k),
664			norm.copy(),
665			par,
666			params,
667		);
668	}
669}
670fn solve_real_shifted_upper_quasi_triangular_system<T: RealField>(
671	A: MatRef<'_, T>,
672	p: T,
673	x: ColMut<'_, T>,
674	norm: T,
675	par: Par,
676	params: EvdFromSchurParams,
677) {
678	let n = A.nrows();
679	let one = one::<T>;
680	let zero = zero::<T>;
681	let ref eps = eps::<T>();
682	let ref p = p;
683	let mut x = x;
684	let ref norm = norm;
685	if par.degree() == 0 || n < params.recursion_threshold {
686		let mut i = n;
687		loop {
688			if i == 0 {
689				break;
690			}
691			i -= 1;
692			if i == 0 || A[(i, i - 1)] == zero() {
693				let dot = linalg::matmul::dot::inner_prod(
694					A.row(i).subcols(i + 1, n - i - 1),
695					Conj::No,
696					x.rb().subrows(i + 1, n - i - 1),
697					Conj::No,
698				);
699				x[i] -= dot;
700				let mut z = &A[(i, i)] - p;
701				if z.abs() < eps * norm {
702					z = eps * norm;
703				}
704				if x[i] != zero() {
705					x[i] /= z;
706				}
707			} else {
708				let (dot0, dot1) = dot2x1(
709					A.row(i - 1).subcols(i + 1, n - i - 1),
710					A.row(i).subcols(i + 1, n - i - 1),
711					x.rb().subrows(i + 1, n - i - 1),
712				);
713				x[i - 1] -= dot0;
714				x[i] -= dot1;
715				let ref a = &A[(i, i)] - p;
716				let ref b = A[(i - 1, i)];
717				let ref c = A[(i, i - 1)];
718				let ref r0 = x[i - 1];
719				let ref r1 = x[i];
720				let ref inv_det = (a.abs2() - b * c).recip();
721				(x[i - 1], x[i]) =
722					((a * r0 - b * r1) * inv_det, (a * r1 - c * r0) * inv_det);
723				i -= 1;
724			}
725		}
726	} else {
727		let mut mid = n / 2;
728		if A[(mid, mid - 1)] != zero() {
729			mid -= 1;
730		}
731		let (A00, A01, _, A11) = A.split_at(mid, mid);
732		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
733		solve_real_shifted_upper_quasi_triangular_system(
734			A11,
735			p.copy(),
736			x1.rb_mut(),
737			norm.copy(),
738			par,
739			params,
740		);
741		linalg::matmul::matmul(
742			x0.rb_mut().as_mat_mut(),
743			Accum::Add,
744			A01,
745			x1.rb().as_mat(),
746			-one(),
747			par,
748		);
749		solve_real_shifted_upper_quasi_triangular_system(
750			A00,
751			p.copy(),
752			x0.rb_mut(),
753			norm.copy(),
754			par,
755			params,
756		);
757	}
758}
759fn solve_cplx_shifted_upper_quasi_triangular_system<T: RealField>(
760	A: MatRef<'_, T>,
761	p: T,
762	q: T,
763	x: MatMut<'_, T>,
764	norm: T,
765	par: Par,
766	params: EvdFromSchurParams,
767) {
768	let n = A.nrows();
769	let one = one::<T>;
770	let zero = zero::<T>;
771	let ref eps = eps::<T>();
772	let ref norm = norm;
773	let ref p = p;
774	let ref q = q;
775	let mut x = x;
776	if par.degree() == 0 || n < params.recursion_threshold {
777		let mut i = n;
778		loop {
779			if i == 0 {
780				break;
781			}
782			i -= 1;
783			if i == 0 || A[(i, i - 1)] == zero() {
784				let (re, im) = dot2x1(
785					x.rb().subrows(i + 1, n - i - 1).col(0).transpose(),
786					x.rb().subrows(i + 1, n - i - 1).col(1).transpose(),
787					A.row(i).subcols(i + 1, n - i - 1).transpose(),
788				);
789				x[(i, 0)] = &x[(i, 0)] - re;
790				x[(i, 1)] = &x[(i, 1)] - im;
791				let mut z_re = &A[(i, i)] - p;
792				let mut z_im = -q;
793				let mut z = z_re.hypot(&z_im);
794				if z < eps * norm {
795					z_re = eps * norm;
796					z_im = zero();
797					z = z_re.copy();
798				}
799				let ref z = z;
800				let ref z_re = (z_re / z) / z;
801				let ref z_im = (-z_im / z) / z;
802				let ref x_re = x[(i, 0)].copy();
803				let ref x_im = x[(i, 1)].copy();
804				x[(i, 0)] = x_re * z_re - x_im * z_im;
805				x[(i, 1)] = x_re * z_im + x_im * z_re;
806			} else {
807				let (re0, re1, im0, im1) = dot2x2(
808					A.row(i - 1).subcols(i + 1, n - i - 1),
809					A.row(i).subcols(i + 1, n - i - 1),
810					x.rb().col(0).subrows(i + 1, n - i - 1),
811					x.rb().col(1).subrows(i + 1, n - i - 1),
812				);
813				x[(i - 1, 0)] = &x[(i - 1, 0)] - re0;
814				x[(i - 1, 1)] = &x[(i - 1, 1)] - im0;
815				x[(i, 0)] = &x[(i, 0)] - re1;
816				x[(i, 1)] = &x[(i, 1)] - im1;
817				let ref a_re = &A[(i, i)] - p;
818				let ref a_im = -q;
819				let ref b = A[(i - 1, i)];
820				let ref c = A[(i, i - 1)];
821				let ref r0_re = x[(i - 1, 0)];
822				let ref r0_im = x[(i - 1, 1)];
823				let ref r1_re = x[(i, 0)];
824				let ref r1_im = x[(i, 1)];
825				let mut z_re = a_re.abs2() - a_im.abs2() - b * c;
826				let mut z_im = (a_re * a_im).mul_pow2(from_f64::<T>(2.0));
827				let mut z = z_re.hypot(&z_im);
828				if z < eps * norm {
829					z_re = eps * norm;
830					z_im = zero();
831					z = z_re.copy();
832				}
833				let ref z = z;
834				let ref z_re = (z_re / z) / z;
835				let ref z_im = (-z_im / z) / z;
836				let ref x0_re = (a_re * r0_re - a_im * r0_im) - b * r1_re;
837				let ref x0_im = (a_re * r0_im + a_im * r0_re) - b * r1_im;
838				let ref x1_re = (a_re * r1_re - a_im * r1_im) - c * r0_re;
839				let ref x1_im = (a_re * r1_im + a_im * r1_re) - c * r0_im;
840				x[(i - 1, 0)] = x0_re * z_re - x0_im * z_im;
841				x[(i - 1, 1)] = x0_re * z_im + x0_im * z_re;
842				x[(i, 0)] = x1_re * z_re - x1_im * z_im;
843				x[(i, 1)] = x1_re * z_im + x1_im * z_re;
844				i -= 1;
845			}
846		}
847	} else {
848		let mut mid = n / 2;
849		if A[(mid, mid - 1)] != zero() {
850			mid -= 1;
851		}
852		let (A00, A01, _, A11) = A.split_at(mid, mid);
853		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
854		solve_cplx_shifted_upper_quasi_triangular_system(
855			A11,
856			p.copy(),
857			q.copy(),
858			x1.rb_mut(),
859			norm.copy(),
860			par,
861			params,
862		);
863		linalg::matmul::matmul(
864			x0.rb_mut(),
865			Accum::Add,
866			A01,
867			x1.rb(),
868			-one(),
869			par,
870		);
871		solve_cplx_shifted_upper_quasi_triangular_system(
872			A00,
873			p.copy(),
874			q.copy(),
875			x0.rb_mut(),
876			norm.copy(),
877			par,
878			params,
879		);
880	}
881}
882fn solve_shifted_upper_triangular_system<T: ComplexField>(
883	A: MatRef<'_, T>,
884	conj_A: Conj,
885	p: T,
886	x: ColMut<'_, T>,
887	norm: T::Real,
888	par: Par,
889	params: EvdFromSchurParams,
890) {
891	let n = A.nrows();
892	let one = one::<T>;
893	let zero = zero::<T>;
894	let ref eps = eps::<T::Real>();
895	let ref norm = norm;
896	let ref p = p;
897	let mut x = x;
898	if par.degree() == 0 || n < params.recursion_threshold {
899		let mut i = n;
900		loop {
901			if i == 0 {
902				break;
903			}
904			i -= 1;
905			let dot = linalg::matmul::dot::inner_prod(
906				A.row(i).subcols(i + 1, n - i - 1),
907				conj_A,
908				x.rb().subrows(i + 1, n - i - 1),
909				Conj::No,
910			);
911			x[i] -= dot;
912			let mut z = if conj_A == Conj::Yes {
913				A[(i, i)].conj()
914			} else {
915				A[(i, i)].copy()
916			} - p;
917			if z.abs() < eps * norm {
918				z = (eps * norm).to_cplx();
919			}
920			if x[i] != zero() {
921				x[i] *= z.recip();
922			}
923		}
924	} else {
925		let mid = n / 2;
926		let (A00, A01, _, A11) = A.split_at(mid, mid);
927		let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
928		solve_shifted_upper_triangular_system(
929			A11,
930			conj_A,
931			p.copy(),
932			x1.rb_mut(),
933			norm.copy(),
934			par,
935			params,
936		);
937		linalg::matmul::matmul_with_conj(
938			x0.rb_mut().as_mat_mut(),
939			Accum::Add,
940			A01,
941			conj_A,
942			x1.rb().as_mat(),
943			Conj::No,
944			-one(),
945			par,
946		);
947		solve_shifted_upper_triangular_system(
948			A00,
949			conj_A,
950			p.copy(),
951			x0.rb_mut(),
952			norm.copy(),
953			par,
954			params,
955		);
956	}
957}
958/// computes the layout of the workspace required to compute a matrix's
959/// eigendecomposition
960pub fn evd_scratch<T: ComplexField>(
961	dim: usize,
962	eigen_left: ComputeEigenvectors,
963	eigen_right: ComputeEigenvectors,
964	par: Par,
965	params: Spec<EvdParams, T>,
966) -> StackReq {
967	let n = dim;
968	if n == 0 {
969		return StackReq::EMPTY;
970	}
971	let compute_eigen = eigen_left == ComputeEigenvectors::Yes
972		|| eigen_right == ComputeEigenvectors::Yes;
973	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
974		n - 1,
975		n - 1,
976	);
977	let H = temp_mat_scratch::<T>(n, n);
978	let X = H;
979	let Z = temp_mat_scratch::<T>(n, if compute_eigen { n } else { 0 });
980	let householder = temp_mat_scratch::<T>(bs, n);
981	let apply = linalg::householder::apply_block_householder_sequence_on_the_right_in_place_scratch::<T>(n - 1, bs, n - 1);
982	StackReq::all_of(&[
983		H,
984		Z,
985		StackReq::any_of(&[
986			householder.and(
987				hessenberg::hessenberg_in_place_scratch::<T>(
988					n,
989					bs,
990					par,
991					params.hessenberg.into(),
992				)
993				.or(apply),
994			),
995			schur::multishift_qr_scratch::<T>(
996				n,
997				n,
998				compute_eigen,
999				compute_eigen,
1000				par,
1001				params.schur,
1002			),
1003			X,
1004		]),
1005	])
1006}
1007fn evd_imp<T: ComplexField>(
1008	A: MatRef<'_, T>,
1009	s: ColMut<'_, T>,
1010	s_im: Option<ColMut<'_, T>>,
1011	u_left: Option<MatMut<'_, T>>,
1012	u_right: Option<MatMut<'_, T>>,
1013	par: Par,
1014	stack: &mut MemStack,
1015	params: EvdParams,
1016) -> Result<(), EvdError> {
1017	let n = A.nrows();
1018	if n == 0 {
1019		return Ok(());
1020	}
1021	for j in 0..n {
1022		for i in 0..n {
1023			if !A[(i, j)].is_finite() {
1024				return Err(EvdError::NoConvergence);
1025			}
1026		}
1027	}
1028	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(
1029		n - 1,
1030		n - 1,
1031	);
1032	let mut s = s;
1033	let mut s_im = s_im;
1034	let (mut H, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1035	let (mut Z, stack) = unsafe {
1036		temp_mat_uninit::<T, _, _>(
1037			n,
1038			if u_left.is_some() || u_right.is_some() {
1039				n
1040			} else {
1041				0
1042			},
1043			stack,
1044		)
1045	};
1046	let mut H = H.as_mat_mut();
1047	let mut Z = if u_left.is_some() || u_right.is_some() {
1048		Some(Z.as_mat_mut())
1049	} else {
1050		None
1051	};
1052	H.copy_from(A);
1053	{
1054		let (mut householder, stack) =
1055			unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
1056		let mut householder = householder.as_mat_mut();
1057		hessenberg::hessenberg_in_place(
1058			H.rb_mut(),
1059			householder.rb_mut(),
1060			par,
1061			stack,
1062			params.hessenberg.into(),
1063		);
1064		if let Some(mut Z) = Z.rb_mut() {
1065			Z.fill(zero());
1066			Z.rb_mut().diagonal_mut().fill(one());
1067			linalg::householder::apply_block_householder_sequence_on_the_right_in_place_with_conj(
1068				H.rb().submatrix(1, 0, n - 1, n - 1),
1069				householder.rb(),
1070				Conj::No,
1071				Z.rb_mut().submatrix_mut(1, 1, n - 1, n - 1),
1072				par,
1073				stack,
1074			);
1075		}
1076		for j in 0..n {
1077			for i in j + 2..n {
1078				H[(i, j)] = zero();
1079			}
1080		}
1081	}
1082	if const { T::IS_REAL } {
1083		schur::real_schur::multishift_qr::<T::Real>(
1084			unsafe { core::mem::transmute(Z.is_some()) },
1085			unsafe { core::mem::transmute(H.rb_mut()) },
1086			unsafe { core::mem::transmute(Z.rb_mut()) },
1087			unsafe { core::mem::transmute(s.rb_mut()) },
1088			unsafe { core::mem::transmute(s_im.rb_mut().unwrap()) },
1089			0,
1090			n,
1091			par,
1092			stack,
1093			params.schur,
1094		);
1095	} else {
1096		schur::complex_schur::multishift_qr::<T>(
1097			Z.is_some(),
1098			H.rb_mut(),
1099			Z.rb_mut(),
1100			s.rb_mut(),
1101			0,
1102			n,
1103			par,
1104			stack,
1105			params.schur,
1106		);
1107	}
1108	let H = H.rb();
1109	if let (Some(mut u), Some(Z)) = (u_right, Z.rb()) {
1110		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1111		let mut X = X.as_mat_mut();
1112		if const { T::IS_REAL } {
1113			evd_from_real_schur_imp::<T::Real>(
1114				unsafe { core::mem::transmute(H) },
1115				unsafe { core::mem::transmute(X.rb_mut()) },
1116				par,
1117				params.evd_from_schur,
1118			);
1119		} else {
1120			evd_from_cplx_schur_imp::<T>(
1121				H,
1122				Conj::No,
1123				X.rb_mut(),
1124				par,
1125				params.evd_from_schur,
1126			);
1127		}
1128		linalg::matmul::triangular::matmul(
1129			u.rb_mut(),
1130			BlockStructure::Rectangular,
1131			Accum::Replace,
1132			Z,
1133			BlockStructure::Rectangular,
1134			X.rb(),
1135			BlockStructure::TriangularUpper,
1136			one(),
1137			par,
1138		);
1139	}
1140	if let (Some(mut u), Some(Z)) = (u_left, Z.rb()) {
1141		let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
1142		let mut X = X.as_mat_mut().reverse_rows_mut();
1143		if const { T::IS_REAL } {
1144			evd_from_real_schur_imp::<T::Real>(
1145				unsafe {
1146					core::mem::transmute(H.transpose().reverse_rows_and_cols())
1147				},
1148				unsafe { core::mem::transmute(X.rb_mut()) },
1149				par,
1150				params.evd_from_schur,
1151			);
1152		} else {
1153			evd_from_cplx_schur_imp::<T>(
1154				H.transpose().reverse_rows_and_cols(),
1155				Conj::Yes,
1156				X.rb_mut(),
1157				par,
1158				params.evd_from_schur,
1159			);
1160		}
1161		linalg::matmul::triangular::matmul(
1162			u.rb_mut(),
1163			BlockStructure::Rectangular,
1164			Accum::Replace,
1165			Z,
1166			BlockStructure::Rectangular,
1167			X.rb().reverse_rows_and_cols(),
1168			BlockStructure::TriangularLower,
1169			one(),
1170			par,
1171		);
1172	}
1173	Ok(())
1174}
1175/// computes the matrix $A$'s eigendecomposition
1176///
1177/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1178/// right eigenvectors in $U_R$
1179#[track_caller]
1180pub fn evd_cplx<T: RealField>(
1181	A: MatRef<'_, Complex<T>>,
1182	s: DiagMut<'_, Complex<T>>,
1183	u_left: Option<MatMut<'_, Complex<T>>>,
1184	u_right: Option<MatMut<'_, Complex<T>>>,
1185	par: Par,
1186	stack: &mut MemStack,
1187	params: Spec<EvdParams, Complex<T>>,
1188) -> Result<(), EvdError> {
1189	let n = A.nrows();
1190	assert!(all(A.nrows() == n, A.ncols() == n, s.dim() == n));
1191	if let Some(u) = u_left.rb() {
1192		assert!(all(u.nrows() == n, u.ncols() == n));
1193	}
1194	if let Some(u) = u_right.rb() {
1195		assert!(all(u.nrows() == n, u.ncols() == n));
1196	}
1197	evd_imp(
1198		A,
1199		s.column_vector_mut(),
1200		None,
1201		u_left,
1202		u_right,
1203		par,
1204		stack,
1205		params.config,
1206	)
1207}
1208/// computes the matrix $A$'s eigendecomposition
1209///
1210/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1211/// right eigenvectors in $U_R$
1212#[track_caller]
1213pub fn evd_real<T: RealField>(
1214	A: MatRef<'_, T>,
1215	s_re: DiagMut<'_, T>,
1216	s_im: DiagMut<'_, T>,
1217	u_left: Option<MatMut<'_, T>>,
1218	u_right: Option<MatMut<'_, T>>,
1219	par: Par,
1220	stack: &mut MemStack,
1221	params: Spec<EvdParams, T>,
1222) -> Result<(), EvdError> {
1223	let n = A.nrows();
1224	assert!(all(
1225		A.nrows() == n,
1226		A.ncols() == n,
1227		s_re.dim() == n,
1228		s_im.dim() == n
1229	));
1230	if let Some(u) = u_left.rb() {
1231		assert!(all(u.nrows() == n, u.ncols() == n));
1232	}
1233	if let Some(u) = u_right.rb() {
1234		assert!(all(u.nrows() == n, u.ncols() == n));
1235	}
1236	evd_imp(
1237		A,
1238		s_re.column_vector_mut(),
1239		Some(s_im.column_vector_mut()),
1240		u_left,
1241		u_right,
1242		par,
1243		stack,
1244		params.config,
1245	)
1246}
1247#[cfg(test)]
1248mod general_tests {
1249	use super::*;
1250	use crate::assert;
1251	use crate::stats::prelude::*;
1252	use crate::utils::approx::*;
1253	use dyn_stack::MemBuffer;
1254	fn test_cplx_evd(mat: MatRef<'_, c64>) {
1255		let n = mat.nrows();
1256		let params = Spec::new(EvdParams {
1257			hessenberg: auto!(c64),
1258			schur: auto!(c64),
1259			evd_from_schur: EvdFromSchurParams {
1260				recursion_threshold: 8,
1261				..auto!(c64)
1262			},
1263			..auto!(c64)
1264		});
1265		let approx_eq = CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
1266		let mut s = Mat::zeros(n, n);
1267		{
1268			let mut ul = Mat::zeros(n, n);
1269			let mut ur = Mat::zeros(n, n);
1270			evd_cplx(
1271				mat.as_ref(),
1272				s.as_mut().diagonal_mut(),
1273				Some(ul.as_mut()),
1274				Some(ur.as_mut()),
1275				Par::Seq,
1276				MemStack::new(&mut MemBuffer::new(evd_scratch::<c64>(
1277					n,
1278					ComputeEigenvectors::Yes,
1279					ComputeEigenvectors::Yes,
1280					Par::Seq,
1281					params,
1282				))),
1283				params,
1284			)
1285			.unwrap();
1286			assert!(& ur * & s ~ mat * & ur);
1287			assert!(& s * ul.adjoint() ~ ul.adjoint() * mat);
1288		}
1289	}
1290	fn test_real_evd(mat: MatRef<'_, f64>) {
1291		let n = mat.nrows();
1292		let params = Spec::new(EvdParams {
1293			hessenberg: auto!(f64),
1294			schur: auto!(f64),
1295			evd_from_schur: EvdFromSchurParams {
1296				recursion_threshold: 8,
1297				..auto!(f64)
1298			},
1299			..auto!(f64)
1300		});
1301		let approx_eq =
1302			CwiseMat(ApproxEq::<f64>::eps() * (8.0 * n as f64).sqrt());
1303		let mut s_re = Diag::zeros(n);
1304		let mut s_im = Diag::zeros(n);
1305		{
1306			let mut ul = Mat::zeros(n, n);
1307			let mut ur = Mat::zeros(n, n);
1308			evd_real(
1309				mat.as_ref(),
1310				s_re.as_mut(),
1311				s_im.as_mut(),
1312				Some(ul.as_mut()),
1313				Some(ur.as_mut()),
1314				Par::Seq,
1315				MemStack::new(&mut MemBuffer::new(evd_scratch::<f64>(
1316					n,
1317					ComputeEigenvectors::Yes,
1318					ComputeEigenvectors::Yes,
1319					Par::Seq,
1320					params,
1321				))),
1322				params,
1323			)
1324			.unwrap();
1325			let mut i = 0;
1326			while i < n {
1327				if s_im[i] == 0.0 {
1328					let ur = ur.col(i);
1329					let ul = ul.col(i);
1330					let s = Scale(s_re[i]);
1331					assert!((& ur * s).as_mat() ~ (mat * & ur).as_mat());
1332					assert!(
1333						(& ul.adjoint() * s).as_mat() ~ (& ul.adjoint() * mat).as_mat()
1334					);
1335					i += 1;
1336				} else {
1337					let re = ur.col(i);
1338					let im = ur.col(i + 1);
1339					let ur = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1340					let re = ul.col(i);
1341					let im = ul.col(i + 1);
1342					let ul = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1343					let mat =
1344						&Mat::from_fn(n, n, |i, j| c64::from(mat[(i, j)]));
1345					let s = Scale(c64::new(s_re[i], s_im[i]));
1346					let approx_eq =
1347						CwiseMat(ApproxEq::eps() * (8.0 * n as f64).sqrt());
1348					assert!((ur * s).as_mat() ~ (mat * ur).as_mat());
1349					assert!((ul.adjoint() * s).as_mat() ~ (ul.adjoint() * mat).as_mat());
1350					i += 2;
1351				}
1352			}
1353		}
1354	}
1355	#[test]
1356	fn test_cplx() {
1357		let rng = &mut StdRng::seed_from_u64(1);
1358		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1359			let mat = CwiseMatDistribution {
1360				nrows: n,
1361				ncols: n,
1362				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1363			}
1364			.rand::<Mat<c64>>(rng);
1365			test_cplx_evd(mat.as_ref());
1366		}
1367	}
1368	#[test]
1369	fn test_real() {
1370		let rng = &mut StdRng::seed_from_u64(0);
1371		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1372			let mat = CwiseMatDistribution {
1373				nrows: n,
1374				ncols: n,
1375				dist: StandardNormal,
1376			}
1377			.rand::<Mat<f64>>(rng);
1378			test_real_evd(mat.as_ref());
1379		}
1380	}
1381}
1382#[cfg(test)]
1383mod self_adjoint_tests {
1384	use super::*;
1385	use crate::assert;
1386	use crate::stats::prelude::*;
1387	use crate::utils::approx::*;
1388	use dyn_stack::MemBuffer;
1389	fn test_self_adjoint_evd<T: ComplexField>(mat: MatRef<'_, T>) {
1390		let n = mat.nrows();
1391		let params = Spec::new(SelfAdjointEvdParams {
1392			recursion_threshold: 8,
1393			..auto!(T)
1394		});
1395		let approx_eq = CwiseMat(
1396			ApproxEq::<T::Real>::eps()
1397				* from_f64::<T::Real>(8.0 * n as f64).sqrt(),
1398		);
1399		let mut s = Mat::zeros(n, n);
1400		{
1401			let mut u = Mat::zeros(n, n);
1402			self_adjoint_evd(
1403				mat.as_ref(),
1404				s.as_mut().diagonal_mut(),
1405				Some(u.as_mut()),
1406				Par::Seq,
1407				MemStack::new(&mut MemBuffer::new(
1408					self_adjoint_evd_scratch::<T>(
1409						n,
1410						ComputeEigenvectors::Yes,
1411						Par::Seq,
1412						params,
1413					),
1414				)),
1415				params,
1416			)
1417			.unwrap();
1418			let reconstructed = &u * &s * u.adjoint();
1419			assert!(reconstructed ~ mat);
1420		}
1421		{
1422			let mut s2 = Mat::zeros(n, n);
1423			self_adjoint_evd(
1424				mat.as_ref(),
1425				s2.as_mut().diagonal_mut(),
1426				None,
1427				Par::Seq,
1428				MemStack::new(&mut MemBuffer::new(
1429					self_adjoint_evd_scratch::<T>(
1430						n,
1431						ComputeEigenvectors::No,
1432						Par::Seq,
1433						params,
1434					),
1435				)),
1436				params,
1437			)
1438			.unwrap();
1439			assert!(s2 ~ s);
1440		}
1441	}
1442	#[test]
1443	fn test_real() {
1444		let rng = &mut StdRng::seed_from_u64(1);
1445		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1446			let mat = CwiseMatDistribution {
1447				nrows: n,
1448				ncols: n,
1449				dist: StandardNormal,
1450			}
1451			.rand::<Mat<f64>>(rng);
1452			let mat = &mat + mat.adjoint();
1453			test_self_adjoint_evd(mat.as_ref());
1454		}
1455	}
1456	#[test]
1457	fn test_cplx() {
1458		let rng = &mut StdRng::seed_from_u64(1);
1459		for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1460			let mat = CwiseMatDistribution {
1461				nrows: n,
1462				ncols: n,
1463				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1464			}
1465			.rand::<Mat<c64>>(rng);
1466			let mat = &mat + mat.adjoint();
1467			test_self_adjoint_evd(mat.as_ref());
1468		}
1469	}
1470	#[test]
1471	fn test_special() {
1472		for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1473			test_self_adjoint_evd(Mat::full(n, n, 0.0).as_ref());
1474			test_self_adjoint_evd(Mat::full(n, n, c64::ZERO).as_ref());
1475			test_self_adjoint_evd(Mat::full(n, n, 1.0).as_ref());
1476			test_self_adjoint_evd(Mat::full(n, n, c64::ONE).as_ref());
1477			test_self_adjoint_evd(Mat::<f64>::identity(n, n).as_ref());
1478			test_self_adjoint_evd(Mat::<c64>::identity(n, n).as_ref());
1479		}
1480	}
1481	#[test]
1482	fn test_pinv() {
1483		let rng = &mut StdRng::seed_from_u64(0);
1484		let n = 36;
1485		let mat = CwiseMatDistribution {
1486			nrows: n,
1487			ncols: n,
1488			dist: StandardNormal,
1489		}
1490		.rand::<Mat<f64>>(rng);
1491		let mat = &mat + mat.adjoint();
1492		let pinv = mat.self_adjoint_eigen(Side::Lower).unwrap().pseudoinverse();
1493		let err = &mat * &pinv - Mat::<f64>::identity(n, n);
1494		assert!(err.norm_max() < 1e-10);
1495	}
1496}