Skip to main content

faer/linalg/gevd/
mod.rs

1//! low level implementation of the generalized eigenvalue decomposition of a
2//! square matrix pair.
3//!
4//! the generalized eigenvalue decomposition of a matrix pair $(A, B)$ of shape
5//! $(n, n)$ is a decomposition into components $S$, $U$ such that:
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 = B U S U^{-1}$$
12//!
13//! if $A$ is self-adjoint and $B$ is positive definite, then $U$ can be made
14//! unitary ($U^{-1} = U^H$), and $S$ is real valued.
15//!
16//! the implementation can choose to compute the left and/or right generalized
17//! eigenvectors separately, and the generalized eigenvalues (diagonal of $S$)
18//! are represented as a ratio $\alpha / \beta$ to improve numerical stability.
19use crate::internal_prelude::*;
20use complex::Complex;
21use equator::assert;
22pub use linalg::evd::ComputeEigenvectors;
23/// eigendecomposition error
24#[derive(Copy, Clone, Debug, PartialEq, Eq)]
25pub enum GevdError {
26	/// reached max iterations
27	NoConvergence,
28}
29/// eigendecomposition error
30#[derive(Copy, Clone, Debug, PartialEq, Eq)]
31pub enum SelfAdjointGevdError {
32	/// reached max iterations
33	NoConvergence,
34	/// B matrix not positive definite
35	NonPositiveDefinite,
36}
37/// generalized schur decomposition parameters
38#[derive(Clone, Copy, Debug)]
39pub struct GeneralizedSchurParams {
40	/// An estimate of the relative cost of flops within the near-the-diagonal
41	/// shift chase compared to flops within the matmul calls of a QZ sweep.
42	pub relative_cost_estimate_of_shift_chase_to_matmul: extern "C" fn(
43		matrix_dimension: usize,
44		active_block_dimension: usize,
45	)
46		-> usize,
47	/// Function that returns the number of shifts to use for a given matrix
48	/// size
49	pub recommended_shift_count: extern "C" fn(
50		matrix_dimension: usize,
51		active_block_dimension: usize,
52	) -> usize,
53	/// Function that returns the deflation window to use for a given matrix
54	/// size
55	pub recommended_deflation_window: extern "C" fn(
56		matrix_dimension: usize,
57		active_block_dimension: usize,
58	) -> usize,
59	/// Threshold to switch between blocked and unblocked code
60	pub blocking_threshold: usize,
61	/// Threshold of percent of aggressive-early-deflation window that must
62	/// converge to skip a sweep
63	pub nibble_threshold: usize,
64	#[doc(hidden)]
65	pub non_exhaustive: NonExhaustive,
66}
67/// schur to eigendecomposition conversion parameters
68#[derive(Clone, Copy, Debug)]
69pub struct GevdFromSchurParams {
70	#[doc(hidden)]
71	pub non_exhaustive: NonExhaustive,
72}
73/// eigendecomposition tuning parameters
74#[derive(Clone, Copy, Debug)]
75pub struct GevdParams {
76	/// hessenberg parameters
77	pub hessenberg: gen_hessenberg::GeneralizedHessenbergParams,
78	/// schur from hessenberg conversion parameters
79	pub schur: GeneralizedSchurParams,
80	/// eigendecomposition from schur conversion parameters
81	pub evd_from_schur: GevdFromSchurParams,
82	#[doc(hidden)]
83	pub non_exhaustive: NonExhaustive,
84}
85extern "C" fn default_relative_cost_estimate_of_shift_chase_to_matmul(
86	n: usize,
87	nh: usize,
88) -> usize {
89	_ = (n, nh);
90	10
91}
92impl<T: ComplexField> Auto<T> for GeneralizedSchurParams {
93	fn auto() -> Self {
94		let schur: linalg::evd::SchurParams = auto!(T);
95		Self {
96			relative_cost_estimate_of_shift_chase_to_matmul:
97				default_relative_cost_estimate_of_shift_chase_to_matmul,
98			recommended_shift_count: schur.recommended_shift_count,
99			recommended_deflation_window: schur.recommended_deflation_window,
100			blocking_threshold: schur.blocking_threshold,
101			nibble_threshold: schur.nibble_threshold,
102			non_exhaustive: NonExhaustive(()),
103		}
104	}
105}
106impl<T: ComplexField> Auto<T> for GevdParams {
107	fn auto() -> Self {
108		Self {
109			hessenberg: auto!(T),
110			schur: auto!(T),
111			evd_from_schur: auto!(T),
112			non_exhaustive: NonExhaustive(()),
113		}
114	}
115}
116impl<T: ComplexField> Auto<T> for GevdFromSchurParams {
117	fn auto() -> Self {
118		Self {
119			non_exhaustive: NonExhaustive(()),
120		}
121	}
122}
123/// hessenberg decomposition
124pub mod gen_hessenberg;
125/// $QZ$ decomposition for complex matrices
126pub mod qz_cplx;
127/// $QZ$ decomposition for real matrices
128pub mod qz_real;
129#[track_caller]
130fn compute_gevd_generic<T: ComplexField>(
131	A: MatMut<'_, T>,
132	B: MatMut<'_, T>,
133	alpha_re: ColMut<'_, T>,
134	alpha_im: ColMut<'_, T>,
135	beta: ColMut<'_, T>,
136	u_left: Option<MatMut<'_, T>>,
137	u_right: Option<MatMut<'_, T>>,
138	mut par: Par,
139	stack: &mut MemStack,
140	params: GevdParams,
141	hessenberg_to_qz: fn(
142		A: MatMut<'_, T>,
143		B: MatMut<'_, T>,
144		Q: Option<MatMut<'_, T>>,
145		Z: Option<MatMut<'_, T>>,
146		alphar: ColMut<'_, T>,
147		alphai: ColMut<'_, T>,
148		beta: ColMut<'_, T>,
149		eigenvectors: ComputeEigenvectors,
150		par: Par,
151		params: GeneralizedSchurParams,
152		stack: &mut MemStack,
153	),
154	qz_to_gevd: fn(
155		A: MatRef<'_, T>,
156		B: MatRef<'_, T>,
157		Q: Option<MatMut<'_, T>>,
158		Z: Option<MatMut<'_, T>>,
159		par: Par,
160		stack: &mut MemStack,
161	),
162) -> Result<(), GevdError> {
163	let n = A.nrows();
164	assert!(all(
165		A.nrows() == n,
166		A.ncols() == n,
167		B.nrows() == n,
168		B.ncols() == n,
169		alpha_re.nrows() == n,
170		if const { T::IS_REAL } {
171			alpha_im.nrows()
172		} else {
173			n
174		} == n,
175		beta.nrows() == n,
176	));
177	if let Some(u_left) = u_left.rb() {
178		assert!(all(u_left.nrows() == n, u_left.ncols() == n));
179	}
180	if let Some(u_right) = u_right.rb() {
181		assert!(all(u_right.nrows() == n, u_right.ncols() == n));
182	}
183	if n == 0 {
184		return Ok(());
185	}
186	#[cfg(feature = "perf-warn")]
187	{
188		for u in [u_left.rb(), u_right.rb()] {
189			if let Some(matrix) = u.rb() {
190				if matrix.row_stride().unsigned_abs() != 1
191					&& crate::__perf_warn!(GEVD_WARN)
192				{
193					if matrix.col_stride().unsigned_abs() == 1 {
194						log::warn!(
195							target : "faer_perf",
196							"GEVD prefers column-major eigenvector matrix. Found row-major matrix."
197						);
198					} else {
199						log::warn!(
200							target : "faer_perf",
201							"GEVD prefers column-major eigenvector matrix. Found matrix with generic strides."
202						);
203					}
204				}
205			}
206		}
207		for M in [A.rb(), B.rb()] {
208			if M.row_stride().unsigned_abs() != 1
209				&& crate::__perf_warn!(GEVD_WARN)
210			{
211				if M.col_stride().unsigned_abs() == 1 {
212					log::warn!(
213						target : "faer_perf",
214						"GEVD prefers column-major input matrix. Found row-major matrix."
215					);
216				} else {
217					log::warn!(
218						target : "faer_perf",
219						"GEVD prefers column-major input matrix. Found matrix with generic strides."
220					);
221				}
222			}
223		}
224	}
225	if !(A.is_all_finite() && B.is_all_finite()) {
226		{ alpha_re }.fill(nan());
227		{ alpha_im }.fill(nan());
228		{ beta }.fill(nan());
229		for u in [u_left, u_right] {
230			if let Some(mut u) = u {
231				u.fill(nan());
232			}
233		}
234		return Err(GevdError::NoConvergence);
235	}
236	let need_qz = u_left.is_some() || u_right.is_some();
237	let mut A = A;
238	let mut B = B;
239	let mut u_left = u_left;
240	let mut u_right = u_right;
241	let mut alpha_re = alpha_re;
242	let mut alpha_im = alpha_im;
243	let mut beta = beta;
244	for u in [u_left.rb_mut(), u_right.rb_mut()] {
245		if let Some(mut u) = u {
246			// the outputs may be reused buffers; initialize them as full identities,
247			// which is what LAPACK's `dgges` does with `dlaset` before `dhgeqz`
248			// accumulates the transformations
249			u.fill(zero());
250			u.diagonal_mut().column_vector_mut().fill(one());
251		}
252	}
253	{
254		let block_size =
255			linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
256		let (mut householder, stack) =
257			unsafe { linalg::temp_mat_uninit::<T, _, _>(block_size, n, stack) };
258		let mut householder = householder.as_mat_mut();
259		linalg::qr::no_pivoting::factor::qr_in_place(
260			B.rb_mut(),
261			householder.rb_mut(),
262			par.reborrow(),
263			stack,
264			default(),
265		);
266		linalg::householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
267			B.rb(),
268			householder.rb(),
269			Conj::Yes,
270			A.rb_mut(),
271			par.reborrow(),
272			stack,
273		);
274		if let Some(u_left) = u_left.rb_mut() {
275			linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
276				B.rb(),
277				householder.rb(),
278				Conj::No,
279				u_left,
280				par.reborrow(),
281				stack,
282			);
283		}
284		zip!(B.rb_mut())
285			.for_each_triangular_lower(linalg::zip::Diag::Skip, |unzip!(x)| {
286				*x = zero()
287			});
288	}
289	gen_hessenberg::generalized_hessenberg(
290		A.rb_mut(),
291		B.rb_mut(),
292		u_left.rb_mut(),
293		u_right.rb_mut(),
294		par.reborrow(),
295		stack,
296		params.hessenberg,
297	);
298	hessenberg_to_qz(
299		A.rb_mut(),
300		B.rb_mut(),
301		u_left.rb_mut(),
302		u_right.rb_mut(),
303		alpha_re.rb_mut(),
304		alpha_im.rb_mut(),
305		beta.rb_mut(),
306		if need_qz {
307			ComputeEigenvectors::Yes
308		} else {
309			ComputeEigenvectors::No
310		},
311		par.reborrow(),
312		params.schur,
313		stack,
314	);
315	qz_to_gevd(
316		A.rb(),
317		B.rb(),
318		u_left.rb_mut(),
319		u_right.rb_mut(),
320		par,
321		stack,
322	);
323	Ok(())
324}
325fn solve_shifted_1x1<T: ComplexField>(
326	smin: T::Real,
327	ca: T::Real,
328	A: T,
329	d0: T,
330	B: &mut T,
331	w: T,
332) {
333	let ref safmin = min_positive::<T::Real>();
334	let ref smlnum = safmin + safmin;
335	let ref smin = smin.fmax(smlnum);
336	let CR = A.mul_real(ca) - w * d0;
337	let cmax = CR.abs();
338	if cmax < *smin {
339		let smin_inv = smin.recip();
340		*B = B.mul_real(smin_inv);
341	}
342	let C = CR.recip();
343	*B *= C;
344}
345fn solve_complex_shifted_1x1<T: RealField>(
346	smin: T,
347	ca: T,
348	A: MatRef<'_, T>,
349	ref d0: T,
350	mut B: MatMut<'_, T>,
351	wr: T,
352	wi: T,
353) {
354	let nw = B.ncols();
355	let ref safmin = min_positive::<T>();
356	let ref smlnum = safmin + safmin;
357	let ref smin = smin.fmax(smlnum);
358	let CR = ca * &A[(0, 0)] - wr * d0;
359	let CI = -wi * d0;
360	let cmax = CR.abs().fmax(CI.abs());
361	if cmax < *smin {
362		let ref smin_inv = smin.recip();
363		zip!(B.rb_mut()).for_each(|unzip!(x)| *x *= smin_inv);
364	}
365	if nw == 1 {
366		let ref C = CR.recip();
367		zip!(B.rb_mut()).for_each(|unzip!(x)| *x *= C);
368	} else {
369		let (Br, Bi) = B.two_cols_mut(0, 1);
370		let C = Complex { re: CR, im: CI }.recip();
371		zip!(Br, Bi).for_each(|unzip!(re, im)| {
372			(*re, *im) =
373				(&*re * &C.re - &*im * &C.im, &*re * &C.im + &*im * &C.re);
374		});
375	}
376}
377fn solve_complex_shifted_2x2<T: RealField>(
378	smin: T,
379	ref ca: T,
380	A: MatRef<'_, T>,
381	ref d0: T,
382	ref d1: T,
383	mut B: MatMut<'_, T>,
384	ref wr: T,
385	ref wi: T,
386	stack: &mut MemStack,
387) {
388	let nw = B.ncols();
389	let zero = zero::<T>;
390	let ref safmin = min_positive::<T>();
391	let ref smlnum = safmin + safmin;
392	let ref smin = smin.fmax(smlnum);
393	if nw == 1 {
394		let (mut C, stack) =
395			unsafe { linalg::temp_mat_uninit::<T, _, _>(2, 2, stack) };
396		let mut C = C.as_mat_mut();
397		let mut row_perm_fwd = [0usize; 2];
398		let mut row_perm_inv = [0usize; 2];
399		let mut col_perm_fwd = [0usize; 2];
400		let mut col_perm_inv = [0usize; 2];
401		C[(0, 0)] = ca * &A[(0, 0)] - wr * d0;
402		C[(1, 0)] = ca * &A[(1, 0)];
403		C[(0, 1)] = ca * &A[(0, 1)];
404		C[(1, 1)] = ca * &A[(1, 1)] - wr * d1;
405		let cmax = C.norm_max();
406		if cmax < *smin {
407			let ref smin_inv = smin.recip();
408			zip!(B.rb_mut()).for_each(|unzip!(x)| *x *= smin_inv);
409		}
410		let (_, row_perm, col_perm) =
411			linalg::lu::full_pivoting::factor::lu_in_place(
412				C.rb_mut(),
413				&mut row_perm_fwd,
414				&mut row_perm_inv,
415				&mut col_perm_fwd,
416				&mut col_perm_inv,
417				Par::Seq,
418				stack,
419				Default::default(),
420			);
421		linalg::lu::full_pivoting::solve::solve_in_place(
422			C.rb(),
423			C.rb(),
424			row_perm,
425			col_perm,
426			B.rb_mut(),
427			Par::Seq,
428			stack,
429		);
430	} else {
431		let (mut C, stack) =
432			unsafe { linalg::temp_mat_uninit::<Complex<T>, _, _>(2, 2, stack) };
433		let mut C = C.as_mat_mut();
434		let mut row_perm_fwd = [0usize; 2];
435		let mut row_perm_inv = [0usize; 2];
436		let mut col_perm_fwd = [0usize; 2];
437		let mut col_perm_inv = [0usize; 2];
438		C[(0, 0)] = Complex {
439			re: ca * &A[(0, 0)] - wr * d0,
440			im: -wi * d0,
441		};
442		C[(1, 0)] = Complex {
443			re: ca * &A[(1, 0)],
444			im: zero(),
445		};
446		C[(0, 1)] = Complex {
447			re: ca * &A[(0, 1)],
448			im: zero(),
449		};
450		C[(1, 1)] = Complex {
451			re: ca * &A[(1, 1)] - wr * d1,
452			im: -wi * d1,
453		};
454		let cmax = C.norm_max();
455		if cmax < *smin {
456			let ref smin_inv = smin.recip();
457			zip!(B.rb_mut()).for_each(|unzip!(x)| *x *= smin_inv);
458		}
459		let (_, row_perm, col_perm) =
460			linalg::lu::full_pivoting::factor::lu_in_place(
461				C.rb_mut(),
462				&mut row_perm_fwd,
463				&mut row_perm_inv,
464				&mut col_perm_fwd,
465				&mut col_perm_inv,
466				Par::Seq,
467				stack,
468				Default::default(),
469			);
470		let n = B.nrows();
471		let (Br, Bi) = B.two_cols_mut(0, 1);
472		let (mut B, stack) =
473			unsafe { linalg::temp_mat_uninit::<Complex<T>, _, _>(n, 1, stack) };
474		let mut B = B.as_mat_mut().col_mut(0);
475		zip!(&mut B, &Br, &Bi).for_each(|unzip!(z, re, im)| {
476			*z = Complex {
477				re: re.copy(),
478				im: im.copy(),
479			}
480		});
481		linalg::lu::full_pivoting::solve::solve_in_place(
482			C.rb(),
483			C.rb(),
484			row_perm,
485			col_perm,
486			B.rb_mut().as_mat_mut(),
487			Par::Seq,
488			stack,
489		);
490		zip!(Br, Bi, &B).for_each(|unzip!(re, im, z)| {
491			(*re, *im) = (z.re.copy(), z.im.copy())
492		});
493	}
494}
495fn qz_to_gevd_real<T: RealField>(
496	A: MatRef<'_, T>,
497	B: MatRef<'_, T>,
498	Q: Option<MatMut<'_, T>>,
499	Z: Option<MatMut<'_, T>>,
500	mut par: Par,
501	stack: &mut MemStack,
502) {
503	let n = A.nrows();
504	if n == 0 {
505		return;
506	}
507	let one = one::<T>;
508	let zero = zero::<T>;
509	let ref ulp = eps::<T>();
510	let ref safmin = min_positive::<T>();
511	let ref smallnum = safmin * from_f64::<T::Real>(n as f64);
512	let ref small = smallnum * ulp.recip();
513	let ref bignum = smallnum.recip();
514	let ref big = small.recip();
515	let (mut acolnorm, stack) = linalg::temp_mat_zeroed::<T, _, _>(n, 1, stack);
516	let acolnorm = acolnorm.as_mat_mut();
517	let (mut bcolnorm, stack) = linalg::temp_mat_zeroed::<T, _, _>(n, 1, stack);
518	let bcolnorm = bcolnorm.as_mat_mut();
519	let mut acolnorm = acolnorm.col_mut(0);
520	let mut bcolnorm = bcolnorm.col_mut(0);
521	let mut anorm = zero();
522	let mut bnorm = zero();
523	let mut j = 0;
524	while j < n {
525		let cplx = j + 1 < n && A[(j + 1, j)] != zero();
526		if !cplx {
527			let a = A.rb().col(j).get(..j).norm_l1();
528			acolnorm[j] = a.copy();
529			anorm = anorm.fmax(a + A[(j, j)].abs());
530			j += 1;
531		} else {
532			for jc in j..j + 2 {
533				let a = A.rb().col(jc).get(..j).norm_l1();
534				acolnorm[jc] = a.copy();
535				anorm = anorm.fmax(a + A[(j, jc)].abs() + A[(j + 1, jc)].abs());
536			}
537			j += 2;
538		}
539	}
540	for j in 0..n {
541		let b = B.rb().col(j).get(..j).norm_l1();
542		bcolnorm[j] = b.copy();
543		bnorm = bnorm.fmax(b + B[(j, j)].abs());
544	}
545	let ref anorm = anorm;
546	let ref bnorm = bnorm;
547	let ref ascale = anorm.fmax(safmin).recip();
548	let ref bscale = bnorm.fmax(safmin).recip();
549	if let Some(mut u) = Q {
550		let mut je = 0usize;
551		while je < n {
552			let cplx_eigval = je + 1 < n && A[(je + 1, je)] != zero();
553			let nw = if cplx_eigval { 2 } else { 1 };
554			if !cplx_eigval
555				&& A[(je, je)].abs().fmax(&B[(je, je)].abs()) < *safmin
556			{
557				u.rb_mut().col_mut(je).fill(zero());
558				u[(je, je)] = one();
559				je += 1;
560				continue;
561			}
562			let mut acoef;
563			let mut acoefa;
564			let mut bcoefa;
565			let mut bcoefr;
566			let mut bcoefi;
567			let mut xmax;
568			let (mut rhs, stack) =
569				linalg::temp_mat_zeroed::<T, _, _>(n, nw, stack);
570			let mut rhs = rhs.as_mat_mut();
571			if !cplx_eigval {
572				let ref temp = safmin.fmax(
573					(A[(je, je)].abs() * ascale)
574						.fmax(B[(je, je)].abs() * bscale),
575				);
576				let ref salfar = A[(je, je)].mul_real(temp).mul_real(ascale);
577				let ref sbeta = B[(je, je)].real() * temp * bscale;
578				acoef = sbeta * ascale;
579				bcoefr = salfar * bscale;
580				bcoefi = zero();
581				let mut scale = one();
582				let lsa = sbeta.abs() >= *safmin && acoef.abs() < *small;
583				let lsb = salfar.abs() >= *safmin && bcoefr.abs() < *small;
584				if lsa {
585					scale = (small / sbeta.abs()) * anorm.fmin(big);
586				}
587				if lsb {
588					scale =
589						scale.fmax((small / salfar.abs()) * bnorm.fmin(big));
590				}
591				if lsa || lsb {
592					scale = scale.fmin(
593						one()
594							/ (safmin
595								* one().fmax(acoef.abs()).fmax(bcoefr.abs())),
596					);
597					if lsa {
598						acoef = ascale * (&scale * sbeta)
599					} else {
600						acoef = &scale * acoef
601					}
602					if lsb {
603						bcoefr = bscale * (&scale * salfar)
604					} else {
605						bcoefr = &scale * bcoefr
606					}
607				}
608				acoefa = acoef.abs();
609				bcoefa = bcoefr.abs();
610				rhs[(je, 0)] = one();
611				xmax = one();
612			} else {
613				let (ref scale, _, wr, _, wi) = qz_real::generalized_eigval_2x2(
614					(
615						A[(je, je)].copy(),
616						A[(je, je + 1)].copy(),
617						A[(je + 1, je)].copy(),
618						A[(je + 1, je + 1)].copy(),
619					),
620					(
621						B[(je, je)].copy(),
622						B[(je, je + 1)].copy(),
623						B[(je + 1, je)].copy(),
624						B[(je + 1, je + 1)].copy(),
625					),
626				);
627				acoef = scale.copy();
628				bcoefr = wr;
629				bcoefi = wi;
630				acoefa = acoef.abs();
631				bcoefa = bcoefr.abs() + bcoefi.abs();
632				let mut scale = one();
633				if &acoefa * ulp < *safmin && acoefa >= *safmin {
634					scale = (safmin / ulp) / &acoefa;
635				}
636				if &bcoefa * ulp < *safmin && bcoefa >= *safmin {
637					scale = scale.fmax((safmin / ulp) / &bcoefa);
638				}
639				if safmin * &acoefa > *ascale {
640					scale = ascale / (safmin * &acoefa);
641				}
642				if safmin * &bcoefa > *bscale {
643					scale = scale.fmin(bscale / (safmin * &bcoefa));
644				}
645				if scale != one() {
646					acoef = &scale * &acoef;
647					acoefa = acoef.abs();
648					bcoefr = &scale * &bcoefr;
649					bcoefi = &scale * &bcoefi;
650					bcoefa = bcoefr.abs() + bcoefi.abs();
651				}
652				let ref temp = &acoef * &A[(je + 1, je)];
653				let temp2r = &acoef * &A[(je, je)] - &bcoefr * &B[(je, je)];
654				let temp2i = -&bcoefi * &B[(je, je)];
655				if temp.abs() > temp2r.abs() + temp2i.abs() {
656					rhs[(je, 0)] = one();
657					rhs[(je, 1)] = zero();
658					rhs[(je + 1, 0)] = -temp2r / temp;
659					rhs[(je + 1, 1)] = -temp2i / temp;
660				} else {
661					rhs[(je + 1, 0)] = one();
662					rhs[(je + 1, 1)] = zero();
663					let ref temp = &acoef * &A[(je, je + 1)];
664					rhs[(je, 0)] = (&bcoefr * &B[(je + 1, je + 1)]
665						- &acoef * &A[(je + 1, je + 1)])
666						/ temp;
667					rhs[(je, 1)] = &bcoefi * &B[(je + 1, je + 1)] / temp;
668				}
669				xmax = (rhs[(je, 0)].abs() + rhs[(je, 1)].abs())
670					.fmax(rhs[(je + 1, 0)].abs() + rhs[(je + 1, 1)].abs());
671			}
672			let dmin = safmin
673				.fmax(ulp * &acoefa * anorm)
674				.fmax(ulp * &bcoefa * bnorm);
675			let mut j = je + nw;
676			while j < n {
677				let cplx = j + 1 < n && A[(j + 1, j)] != zero();
678				let na = if cplx { 2 } else { 1 };
679				let ref xscale = xmax.recip();
680				let mut temp = acolnorm[j]
681					.fmax(&bcolnorm[j])
682					.fmax(&acoefa * &acolnorm[j] + &bcoefa * &bcolnorm[j]);
683				let b0 = B[(j, j)].copy();
684				let b1;
685				if cplx {
686					temp = temp
687						.fmax(
688							&acoefa * &acolnorm[j + 1]
689								+ &bcoefa * &bcolnorm[j + 1],
690						)
691						.fmax(&acolnorm[j + 1])
692						.fmax(&acolnorm[j + 1]);
693					b1 = B[(j + 1, j + 1)].copy();
694				} else {
695					b1 = zero();
696				}
697				if temp > bignum * xscale {
698					for jw in 0..nw {
699						for jr in je..j {
700							rhs[(jr, jw)] = xscale * &rhs[(jr, jw)];
701						}
702					}
703					xmax = xmax * xscale;
704				}
705				let (mut sums, stack) = unsafe {
706					linalg::temp_mat_uninit::<T, _, _>(na, nw, stack)
707				};
708				let mut sums = sums.as_mat_mut();
709				let (mut sump, stack) = unsafe {
710					linalg::temp_mat_uninit::<T, _, _>(na, nw, stack)
711				};
712				let mut sump = sump.as_mat_mut();
713				for jw in 0..nw {
714					for ja in 0..na {
715						sums[(ja, jw)] = zero();
716						sump[(ja, jw)] = zero();
717						for jr in je..j {
718							sums[(ja, jw)] = &sums[(ja, jw)]
719								+ &A[(jr, j + ja)] * &rhs[(jr, jw)];
720							sump[(ja, jw)] = &sump[(ja, jw)]
721								+ &B[(jr, j + ja)] * &rhs[(jr, jw)];
722						}
723					}
724				}
725				for ja in 0..na {
726					if cplx_eigval {
727						rhs[(j + ja, 0)] = -&acoef * &sums[(ja, 0)]
728							+ &bcoefr * &sump[(ja, 0)]
729							- &bcoefi * &sump[(ja, 1)];
730						rhs[(j + ja, 1)] = -&acoef * &sums[(ja, 1)]
731							+ &bcoefr * &sump[(ja, 1)]
732							+ &bcoefi * &sump[(ja, 0)];
733					} else {
734						rhs[(j + ja, 0)] =
735							-&acoef * &sums[(ja, 0)] + &bcoefr * &sump[(ja, 0)];
736					}
737				}
738				if cplx {
739					solve_complex_shifted_2x2(
740						dmin.copy(),
741						acoef.copy(),
742						A.submatrix(j, j, na, na).transpose(),
743						b0,
744						b1,
745						rhs.rb_mut().subrows_mut(j, na),
746						bcoefr.copy(),
747						bcoefi.copy(),
748						stack,
749					);
750				} else {
751					solve_complex_shifted_1x1(
752						dmin.copy(),
753						acoef.copy(),
754						A.submatrix(j, j, na, na).transpose(),
755						b0,
756						rhs.rb_mut().subrows_mut(j, na),
757						bcoefr.copy(),
758						bcoefi.copy(),
759					);
760				}
761				j += na;
762			}
763			let (mut tmp, _) = linalg::temp_mat_zeroed::<T, _, _>(n, nw, stack);
764			let mut tmp = tmp.as_mat_mut();
765			linalg::matmul::matmul(
766				tmp.rb_mut(),
767				Accum::Replace,
768				u.rb().get(.., je..),
769				rhs.rb().get(je.., ..),
770				one(),
771				par.reborrow(),
772			);
773			let mut u = u.rb_mut().subcols_mut(je, nw);
774			u.copy_from(&tmp);
775			let ref scale = u.norm_l2().recip();
776			zip!(u).for_each(|unzip!(u)| {
777				*u *= scale;
778			});
779			je += nw;
780		}
781	}
782	if let Some(mut u) = Z {
783		let mut je = n;
784		while je > 0 {
785			je -= 1;
786			let cplx_eigval = je > 0 && A[(je, je - 1)] != zero();
787			let nw = if cplx_eigval { 2 } else { 1 };
788			je -= nw - 1;
789			if !cplx_eigval
790				&& A[(je, je)].abs().fmax(&B[(je, je)].abs()) < *safmin
791			{
792				u.rb_mut().col_mut(je).fill(zero());
793				u[(je, je)] = one();
794				continue;
795			}
796			let mut acoef;
797			let mut acoefa;
798			let mut bcoefa;
799			let mut bcoefr;
800			let mut bcoefi;
801			let (mut rhs, stack) =
802				linalg::temp_mat_zeroed::<T, _, _>(n, nw, stack);
803			let mut rhs = rhs.as_mat_mut();
804			if !cplx_eigval {
805				let ref temp = safmin.fmax(
806					(A[(je, je)].abs() * ascale)
807						.fmax(B[(je, je)].abs() * bscale),
808				);
809				let ref salfar = A[(je, je)].mul_real(temp).mul_real(ascale);
810				let ref sbeta = B[(je, je)].real() * temp * bscale;
811				acoef = sbeta * ascale;
812				bcoefr = salfar * bscale;
813				bcoefi = zero();
814				let mut scale = one();
815				let lsa = sbeta.abs() >= *safmin && acoef.abs() < *small;
816				let lsb = salfar.abs() >= *safmin && bcoefr.abs() < *small;
817				if lsa {
818					scale = (small / sbeta.abs()) * anorm.fmin(big);
819				}
820				if lsb {
821					scale =
822						scale.fmax((small / salfar.abs()) * bnorm.fmin(big));
823				}
824				if lsa || lsb {
825					scale = scale.fmin(
826						one()
827							/ (safmin
828								* one().fmax(acoef.abs()).fmax(bcoefr.abs())),
829					);
830					if lsa {
831						acoef = ascale * (&scale * sbeta)
832					} else {
833						acoef = &scale * &acoef
834					}
835					if lsb {
836						bcoefr = bscale * (&scale * salfar)
837					} else {
838						bcoefr = &scale * &bcoefr
839					}
840				}
841				acoefa = acoef.abs();
842				bcoefa = bcoefr.abs();
843				rhs[(je, 0)] = one();
844				for jr in 0..je {
845					rhs[(jr, 0)] =
846						&bcoefr * &B[(jr, je)] - &acoef * &A[(jr, je)];
847				}
848			} else {
849				let (scale, _, wr, _, wi) = qz_real::generalized_eigval_2x2(
850					(
851						A[(je, je)].copy(),
852						A[(je, je + 1)].copy(),
853						A[(je + 1, je)].copy(),
854						A[(je + 1, je + 1)].copy(),
855					),
856					(
857						B[(je, je)].copy(),
858						B[(je, je + 1)].copy(),
859						B[(je + 1, je)].copy(),
860						B[(je + 1, je + 1)].copy(),
861					),
862				);
863				acoef = scale;
864				bcoefr = wr;
865				bcoefi = wi;
866				acoefa = acoef.abs();
867				bcoefa = bcoefr.abs() + bcoefi.abs();
868				let mut scale = one();
869				if &acoefa * ulp < *safmin && acoefa >= *safmin {
870					scale = (safmin / ulp) / &acoefa;
871				}
872				if &bcoefa * ulp < *safmin && bcoefa >= *safmin {
873					scale = scale.fmax((safmin / ulp) / &bcoefa);
874				}
875				if safmin * &acoefa > *ascale {
876					scale = ascale / (safmin * &acoefa);
877				}
878				if safmin * &bcoefa > *bscale {
879					scale = scale.fmin(bscale / (safmin * &bcoefa));
880				}
881				if scale != one() {
882					acoef = &scale * &acoef;
883					acoefa = acoef.abs();
884					bcoefr = &scale * &bcoefr;
885					bcoefi = &scale * &bcoefi;
886					bcoefa = bcoefr.abs() + bcoefi.abs();
887				}
888				let ref temp = &acoef * &A[(je + 1, je)];
889				let ref temp2r = &acoef * &A[(je + 1, je + 1)]
890					- &bcoefr * &B[(je + 1, je + 1)];
891				let ref temp2i = -&bcoefi * &B[(je + 1, je + 1)];
892				if temp.abs() > temp2r.abs() + temp2i.abs() {
893					rhs[(je + 1, 0)] = one();
894					rhs[(je + 1, 1)] = zero();
895					rhs[(je, 0)] = -temp2r / temp;
896					rhs[(je, 1)] = -temp2i / temp;
897				} else {
898					rhs[(je, 0)] = one();
899					rhs[(je, 1)] = zero();
900					let ref temp = &acoef * &A[(je, je + 1)];
901					rhs[(je + 1, 0)] =
902						(&bcoefr * &B[(je, je)] - &acoef * &A[(je, je)]) / temp;
903					rhs[(je + 1, 1)] = &bcoefi * &B[(je, je)] / temp;
904				}
905				let ref creala = &acoef * &rhs[(je, 0)];
906				let ref cimaga = &acoef * &rhs[(je, 1)];
907				let ref crealb =
908					&bcoefr * &rhs[(je, 0)] - &bcoefi * &rhs[(je, 1)];
909				let ref cimagb =
910					&bcoefi * &rhs[(je, 0)] + &bcoefr * &rhs[(je, 1)];
911				let ref cre2a = &acoef * &rhs[(je + 1, 0)];
912				let ref cim2a = &acoef * &rhs[(je + 1, 1)];
913				let ref cre2b =
914					&bcoefr * &rhs[(je + 1, 0)] - &bcoefi * &rhs[(je + 1, 1)];
915				let ref cim2b =
916					&bcoefi * &rhs[(je + 1, 0)] + &bcoefr * &rhs[(je + 1, 1)];
917				for jr in 0..je {
918					rhs[(jr, 0)] = -creala * &A[(jr, je)]
919						+ crealb * &B[(jr, je)]
920						- cre2a * &A[(jr, je + 1)]
921						+ cre2b * &B[(jr, je + 1)];
922					rhs[(jr, 1)] = -cimaga * &A[(jr, je)]
923						+ cimagb * &B[(jr, je)]
924						- cim2a * &A[(jr, je + 1)]
925						+ cim2b * &B[(jr, je + 1)];
926				}
927			}
928			let dmin = safmin
929				.fmax(ulp * &acoefa * anorm)
930				.fmax(ulp * &bcoefa * bnorm);
931			let mut j = je;
932			while j > 0 {
933				j -= 1;
934				let cplx = j > 0 && A[(j, j - 1)] != zero();
935				let na = if cplx { 2 } else { 1 };
936				j -= na - 1;
937				let b0 = B[(j, j)].copy();
938				let b1;
939				if cplx {
940					b1 = B[(j + 1, j + 1)].copy();
941				} else {
942					b1 = zero();
943				}
944				if cplx {
945					solve_complex_shifted_2x2(
946						dmin.copy(),
947						acoef.copy(),
948						A.submatrix(j, j, na, na),
949						b0,
950						b1,
951						rhs.rb_mut().subrows_mut(j, na),
952						bcoefr.copy(),
953						bcoefi.copy(),
954						stack,
955					);
956				} else {
957					solve_complex_shifted_1x1(
958						dmin.copy(),
959						acoef.copy(),
960						A.submatrix(j, j, na, na),
961						b0,
962						rhs.rb_mut().subrows_mut(j, na),
963						bcoefr.copy(),
964						bcoefi.copy(),
965					);
966				}
967				for ja in 0..na {
968					if cplx_eigval {
969						let ref creala = &acoef * &rhs[(j + ja, 0)];
970						let ref cimaga = &acoef * &rhs[(j + ja, 1)];
971						let ref crealb = &bcoefr * &rhs[(j + ja, 0)]
972							- &bcoefi * &rhs[(j + ja, 1)];
973						let ref cimagb = &bcoefi * &rhs[(j + ja, 0)]
974							+ &bcoefr * &rhs[(j + ja, 1)];
975						for jr in 0..j {
976							rhs[(jr, 0)] -= creala * &A[(jr, j + ja)]
977								- crealb * &B[(jr, j + ja)];
978							rhs[(jr, 1)] -= cimaga * &A[(jr, j + ja)]
979								- cimagb * &B[(jr, j + ja)];
980						}
981					} else {
982						let ref creala = &acoef * &rhs[(j + ja, 0)];
983						let ref crealb = &bcoefr * &rhs[(j + ja, 0)];
984						for jr in 0..j {
985							rhs[(jr, 0)] -= creala * &A[(jr, j + ja)]
986								- crealb * &B[(jr, j + ja)];
987						}
988					}
989				}
990			}
991			let (mut tmp, _) = linalg::temp_mat_zeroed::<T, _, _>(n, nw, stack);
992			let mut tmp = tmp.as_mat_mut();
993			linalg::matmul::matmul(
994				tmp.rb_mut(),
995				Accum::Replace,
996				u.rb().get(.., ..je + nw),
997				rhs.rb().get(..je + nw, ..),
998				one(),
999				par.reborrow(),
1000			);
1001			let mut u = u.rb_mut().subcols_mut(je, nw);
1002			u.copy_from(&tmp);
1003			let ref scale = u.norm_l2().recip();
1004			zip!(u).for_each(|unzip!(u)| {
1005				*u *= scale;
1006			});
1007		}
1008	}
1009}
1010fn qz_to_gevd_cplx<T: ComplexField>(
1011	A: MatRef<'_, T>,
1012	B: MatRef<'_, T>,
1013	Q: Option<MatMut<'_, T>>,
1014	Z: Option<MatMut<'_, T>>,
1015	mut par: Par,
1016	stack: &mut MemStack,
1017) {
1018	let n = A.nrows();
1019	if n == 0 {
1020		return;
1021	}
1022	let one = one::<T::Real>;
1023	let ref ulp = eps::<T::Real>();
1024	let ref safmin = min_positive::<T::Real>();
1025	let ref smallnum = safmin * from_f64::<T::Real>(n as f64);
1026	let ref small = smallnum * ulp.recip();
1027	let ref bignum = smallnum.recip();
1028	let ref big = small.recip();
1029	let (mut acolnorm, stack) =
1030		linalg::temp_mat_zeroed::<T::Real, _, _>(n, 1, stack);
1031	let acolnorm = acolnorm.as_mat_mut();
1032	let (mut bcolnorm, stack) =
1033		linalg::temp_mat_zeroed::<T::Real, _, _>(n, 1, stack);
1034	let bcolnorm = bcolnorm.as_mat_mut();
1035	let mut acolnorm = acolnorm.col_mut(0);
1036	let mut bcolnorm = bcolnorm.col_mut(0);
1037	let mut anorm = zero::<T::Real>();
1038	let mut bnorm = zero::<T::Real>();
1039	let mut j = 0;
1040	while j < n {
1041		let a = A.rb().col(j).get(..j).norm_l1();
1042		acolnorm[j] = a.copy();
1043		anorm = anorm.fmax(a + A[(j, j)].abs());
1044		j += 1;
1045	}
1046	for j in 0..n {
1047		let ref b = B.rb().col(j).get(..j).norm_l1();
1048		bcolnorm[j] = b.copy();
1049		bnorm = bnorm.fmax(b + B[(j, j)].abs());
1050	}
1051	let ref anorm = anorm;
1052	let ref bnorm = bnorm;
1053	let ref ascale = anorm.fmax(safmin).recip();
1054	let ref bscale = bnorm.fmax(safmin).recip();
1055	if let Some(mut u) = Q {
1056		let mut je = 0usize;
1057		while je < n {
1058			if A[(je, je)].abs().fmax(&B[(je, je)].abs()) < *safmin {
1059				u.rb_mut().col_mut(je).fill(zero());
1060				u[(je, je)] = one().to_cplx();
1061				je += 1;
1062				continue;
1063			}
1064			let mut acoef;
1065			let acoefa;
1066			let bcoefa;
1067			let mut bcoef;
1068			let mut xmax;
1069			let (mut rhs, stack) =
1070				linalg::temp_mat_zeroed::<T, _, _>(n, 1, stack);
1071			let mut rhs = rhs.as_mat_mut().col_mut(0);
1072			{
1073				let ref temp = safmin.fmax(
1074					(A[(je, je)].abs() * ascale)
1075						.fmax(B[(je, je)].abs() * bscale),
1076				);
1077				let ref salfar = A[(je, je)].mul_real(temp).mul_real(ascale);
1078				let ref sbeta = B[(je, je)].real() * temp * bscale;
1079				acoef = sbeta * ascale;
1080				bcoef = salfar.mul_real(bscale);
1081				let mut scale = one();
1082				let lsa = sbeta.abs() >= *safmin && acoef.abs() < *small;
1083				let lsb = salfar.abs() >= *safmin && bcoef.abs() < *small;
1084				if lsa {
1085					scale = (small / sbeta.abs()) * anorm.fmin(big);
1086				}
1087				if lsb {
1088					scale =
1089						scale.fmax((small / salfar.abs()) * bnorm.fmin(big));
1090				}
1091				if lsa || lsb {
1092					scale = scale.fmin(
1093						one()
1094							/ (safmin
1095								* one().fmax(acoef.abs()).fmax(bcoef.abs())),
1096					);
1097					if lsa {
1098						acoef = sbeta * &scale * ascale
1099					} else {
1100						acoef = acoef * &scale
1101					}
1102					if lsb {
1103						bcoef = salfar.mul_real(&scale).mul_real(bscale)
1104					} else {
1105						bcoef = bcoef.mul_real(&scale)
1106					}
1107				}
1108				acoefa = acoef.abs();
1109				bcoefa = bcoef.abs();
1110				rhs[je] = one().to_cplx();
1111				xmax = one();
1112			}
1113			let dmin = safmin
1114				.fmax(ulp * &acoefa * anorm)
1115				.fmax(ulp * &bcoefa * bnorm);
1116			let mut j = je + 1;
1117			while j < n {
1118				let ref xscale = xmax.recip();
1119				let ref temp = acolnorm[j]
1120					.fmax(&bcolnorm[j])
1121					.fmax(&acoefa * &acolnorm[j] + &bcoefa * &bcolnorm[j]);
1122				let b0 = B[(j, j)].copy();
1123				if *temp > bignum * xscale {
1124					for jr in je..j {
1125						rhs[jr] = rhs[jr].mul_real(xscale);
1126					}
1127					xmax = xmax * xscale;
1128				}
1129				let mut sums = zero::<T>();
1130				let mut sump = zero::<T>();
1131				for jr in je..j {
1132					sums += A[(jr, j)].conj() * &rhs[jr];
1133					sump += B[(jr, j)].conj() * &rhs[jr];
1134				}
1135				rhs[j] = bcoef.conj() * &sump - sums.mul_real(&acoef);
1136				solve_shifted_1x1(
1137					dmin.copy(),
1138					acoef.copy(),
1139					A[(j, j)].conj(),
1140					b0.conj(),
1141					&mut rhs[j],
1142					bcoef.conj(),
1143				);
1144				j += 1;
1145			}
1146			let (mut tmp, _) = linalg::temp_mat_zeroed::<T, _, _>(n, 1, stack);
1147			let mut tmp = tmp.as_mat_mut().col_mut(0);
1148			linalg::matmul::matmul(
1149				tmp.rb_mut(),
1150				Accum::Replace,
1151				u.rb().get(.., je..),
1152				rhs.rb().get(je..),
1153				one().to_cplx(),
1154				par.reborrow(),
1155			);
1156			let mut u = u.rb_mut().col_mut(je);
1157			u.copy_from(&tmp);
1158			let ref scale = u.norm_l2().recip();
1159			zip!(u).for_each(|unzip!(u)| {
1160				*u = u.mul_real(scale);
1161			});
1162			je += 1;
1163		}
1164	}
1165	if let Some(mut u) = Z {
1166		let mut je = n;
1167		while je > 0 {
1168			je -= 1;
1169			if A[(je, je)].abs().fmax(&B[(je, je)].abs()) < *safmin {
1170				u.rb_mut().col_mut(je).fill(zero());
1171				u[(je, je)] = one().to_cplx();
1172				continue;
1173			}
1174			let mut acoef;
1175			let acoefa;
1176			let bcoefa;
1177			let mut bcoefr;
1178			let (mut rhs, stack) =
1179				linalg::temp_mat_zeroed::<T, _, _>(n, 1, stack);
1180			let mut rhs = rhs.as_mat_mut().col_mut(0);
1181			{
1182				let ref temp = safmin.fmax(
1183					(A[(je, je)].abs() * ascale)
1184						.fmax(B[(je, je)].abs() * bscale),
1185				);
1186				let ref salfar = A[(je, je)].mul_real(temp).mul_real(ascale);
1187				let ref sbeta = B[(je, je)].real() * temp * bscale;
1188				acoef = sbeta * ascale;
1189				bcoefr = salfar.mul_real(bscale);
1190				let mut scale = one();
1191				let lsa = sbeta.abs() >= *safmin && acoef.abs() < *small;
1192				let lsb = salfar.abs() >= *safmin && bcoefr.abs() < *small;
1193				if lsa {
1194					scale = (small / sbeta.abs()) * anorm.fmin(big);
1195				}
1196				if lsb {
1197					scale =
1198						scale.fmax((small / salfar.abs()) * bnorm.fmin(big));
1199				}
1200				if lsa || lsb {
1201					scale = scale.fmin(
1202						one()
1203							/ (safmin
1204								* one().fmax(acoef.abs()).fmax(bcoefr.abs())),
1205					);
1206					if lsa {
1207						acoef = sbeta * &scale * ascale
1208					} else {
1209						acoef = acoef * &scale
1210					}
1211					if lsb {
1212						bcoefr = salfar.mul_real(&scale).mul_real(bscale)
1213					} else {
1214						bcoefr = bcoefr.mul_real(&scale)
1215					}
1216				}
1217				acoefa = acoef.abs();
1218				bcoefa = bcoefr.abs();
1219				rhs[je] = one().to_cplx();
1220				for jr in 0..je {
1221					rhs[jr] =
1222						&bcoefr * &B[(jr, je)] - A[(jr, je)].mul_real(&acoef);
1223				}
1224			}
1225			let dmin = safmin
1226				.fmax(ulp * &acoefa * anorm)
1227				.fmax(ulp * &bcoefa * bnorm);
1228			let mut j = je;
1229			while j > 0 {
1230				j -= 1;
1231				let b0 = B[(j, j)].copy();
1232				solve_shifted_1x1(
1233					dmin.copy(),
1234					acoef.copy(),
1235					A[(j, j)].copy(),
1236					b0,
1237					&mut rhs[j],
1238					bcoefr.copy(),
1239				);
1240				let ref creala = rhs[j].mul_real(&acoef);
1241				let ref crealb = &bcoefr * &rhs[j];
1242				for jr in 0..j {
1243					rhs[jr] =
1244						&rhs[jr] - creala * &A[(jr, j)] + crealb * &B[(jr, j)];
1245				}
1246			}
1247			let (mut tmp, _) = linalg::temp_mat_zeroed::<T, _, _>(n, 1, stack);
1248			let mut tmp = tmp.as_mat_mut().col_mut(0);
1249			linalg::matmul::matmul(
1250				tmp.rb_mut(),
1251				Accum::Replace,
1252				u.rb().get(.., ..je + 1),
1253				rhs.rb().get(..je + 1),
1254				one().to_cplx(),
1255				par.reborrow(),
1256			);
1257			let mut u = u.rb_mut().col_mut(je);
1258			u.copy_from(&tmp);
1259			let ref scale = u.norm_l2().recip();
1260			zip!(u).for_each(|unzip!(u)| {
1261				*u = u.mul_real(scale);
1262			});
1263		}
1264	}
1265}
1266
1267fn qz_to_gevd_real_scratch<T: RealField>(n: usize, _: ParShape) -> StackReq {
1268	StackReq::all_of(&[
1269		temp_mat_scratch::<T>(n, 1),
1270		temp_mat_scratch::<T>(n, 1),
1271		temp_mat_scratch::<T>(n, 2),
1272		temp_mat_scratch::<T>(2, 2),
1273		temp_mat_scratch::<T>(2, 2),
1274		StackReq::all_of(&[
1275			temp_mat_scratch::<T>(2, 2),
1276			temp_mat_scratch::<T>(n, 1),
1277			linalg::lu::full_pivoting::factor::lu_in_place_scratch::<
1278				usize,
1279				Complex<T>,
1280			>(2, 2, ParShape::Seq, Default::default()),
1281			linalg::lu::full_pivoting::solve::solve_in_place_scratch::<
1282				usize,
1283				Complex<T>,
1284			>(2, 1, ParShape::Seq),
1285		]),
1286		temp_mat_scratch::<T>(n, 2),
1287	])
1288}
1289
1290fn qz_to_gevd_cplx_scratch<T: ComplexField>(n: usize, _: ParShape) -> StackReq {
1291	StackReq::all_of(&[temp_mat_scratch::<T>(n, 1); 4])
1292}
1293
1294/// computes the layout of the workspace required to compute a matrix pair's
1295/// generalized eigendecomposition
1296pub fn gevd_scratch<T: ComplexField>(
1297	dim: usize,
1298	left: ComputeEigenvectors,
1299	right: ComputeEigenvectors,
1300	par: ParShape,
1301	params: Spec<GevdParams, T>,
1302) -> StackReq {
1303	let _ = (left, right);
1304	let n = dim;
1305	let block_size =
1306		linalg::qr::no_pivoting::factor::recommended_block_size::<T>(n, n);
1307	StackReq::any_of(&[
1308		linalg::temp_mat_scratch::<T>(block_size, n).and(
1309			linalg::qr::no_pivoting::factor::qr_in_place_scratch::<T>(n, n, block_size, par, default())
1310				.or(linalg::householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<T>(n, block_size, n)),
1311		),
1312		gen_hessenberg::generalized_hessenberg_scratch::<T>(n, auto!(T)),
1313		if const { T::IS_REAL } {
1314			qz_real::hessenberg_to_qz_scratch::<T::Real>(n, par, params.schur)
1315		} else {
1316			qz_cplx::hessenberg_to_qz_scratch::<T>(n, par, params.schur)
1317		},
1318		if const { T::IS_REAL } {
1319			qz_to_gevd_real_scratch::<T::Real>(n, par)
1320		} else {
1321			qz_to_gevd_cplx_scratch::<T>(n, par)
1322		},
1323	])
1324}
1325/// computes the real matrix pair $(A, B)$'s eigendecomposition
1326///
1327/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1328/// right eigenvectors in $U_R$
1329///
1330/// the values in $A$ and $B$ after this function is called are unspecified.
1331#[track_caller]
1332pub fn gevd_real<T: RealField>(
1333	A: MatMut<'_, T>,
1334	B: MatMut<'_, T>,
1335	S_re: DiagMut<'_, T>,
1336	S_im: DiagMut<'_, T>,
1337	beta: DiagMut<'_, T>,
1338	U_left: Option<MatMut<'_, T>>,
1339	U_right: Option<MatMut<'_, T>>,
1340	par: Par,
1341	stack: &mut MemStack,
1342	params: Spec<GevdParams, T>,
1343) -> Result<(), GevdError> {
1344	compute_gevd_generic(
1345		A,
1346		B,
1347		S_re.column_vector_mut(),
1348		S_im.column_vector_mut(),
1349		beta.column_vector_mut(),
1350		U_left,
1351		U_right,
1352		par,
1353		stack,
1354		params.config,
1355		qz_real::hessenberg_to_qz,
1356		qz_to_gevd_real,
1357	)
1358}
1359/// computes the complex matrix pair $(A, B)$'s eigendecomposition
1360///
1361/// the eigenvalues are stored in $S$, the left eigenvectors in $U_L$, and the
1362/// right eigenvectors in $U_R$
1363///
1364/// the values in $A$ and $B$ after this function is called are unspecified.
1365#[track_caller]
1366pub fn gevd_cplx<T: ComplexField>(
1367	A: MatMut<'_, T>,
1368	B: MatMut<'_, T>,
1369	S: DiagMut<'_, T>,
1370	beta: DiagMut<'_, T>,
1371	U_left: Option<MatMut<'_, T>>,
1372	U_right: Option<MatMut<'_, T>>,
1373	par: Par,
1374	stack: &mut MemStack,
1375	params: Spec<GevdParams, T>,
1376) -> Result<(), GevdError> {
1377	compute_gevd_generic(
1378		A,
1379		B,
1380		S.column_vector_mut(),
1381		ColMut::from_slice_mut(&mut []),
1382		beta.column_vector_mut(),
1383		U_left,
1384		U_right,
1385		par,
1386		stack,
1387		params.config,
1388		|A: MatMut<'_, T>,
1389		 B: MatMut<'_, T>,
1390		 Q: Option<MatMut<'_, T>>,
1391		 Z: Option<MatMut<'_, T>>,
1392		 alphar: ColMut<'_, T>,
1393		 _: ColMut<'_, T>,
1394		 beta: ColMut<'_, T>,
1395		 eigenvectors: ComputeEigenvectors,
1396		 par: Par,
1397		 params: GeneralizedSchurParams,
1398		 stack: &mut MemStack| {
1399			qz_cplx::hessenberg_to_qz(
1400				A,
1401				B,
1402				Q,
1403				Z,
1404				alphar,
1405				beta,
1406				eigenvectors,
1407				par,
1408				params,
1409				stack,
1410			)
1411		},
1412		qz_to_gevd_cplx,
1413	)
1414}
1415#[cfg(test)]
1416mod tests {
1417	use super::*;
1418	use crate::stats::prelude::*;
1419	use crate::utils;
1420	use dyn_stack::MemBuffer;
1421	use equator::assert;
1422	#[test]
1423	fn test_lishen_() {
1424		let approx_eq = utils::approx::CwiseMat(
1425			utils::approx::ApproxEq::<f64>::eps() * 128.0,
1426		);
1427		let A = &[
1428			[
1429				0.0000000067116330731078674,
1430				-0.0000000024189842394753674,
1431				0.00000000032505193899740187,
1432				0.0,
1433				0.0,
1434				0.0,
1435			],
1436			[
1437				-0.0000000024189842394753674,
1438				0.000000011866543705551487,
1439				-0.00000001582083742901503,
1440				0.0,
1441				0.0,
1442				0.0,
1443			],
1444			[
1445				0.00000000032505193899740187,
1446				-0.00000001582083742901503,
1447				0.00000002523087227632826,
1448				0.0,
1449				0.0,
1450				0.0,
1451			],
1452			[0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
1453			[0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
1454			[0.0, 0.0, 0.0, 0.0, 0.0, 1.0f64],
1455		];
1456		let B = &[
1457			[
1458				0.00016038376001067668,
1459				-0.00003674235041182116,
1460				-0.0000029397921270058582,
1461				-1.0,
1462				-0.0,
1463				-0.0,
1464			],
1465			[
1466				-0.00003674235041182116,
1467				0.0002038454538304322,
1468				-0.00021213416901900504,
1469				-0.0,
1470				-1.0,
1471				-0.0,
1472			],
1473			[
1474				-0.0000029397921270058582,
1475				-0.00021213416901900504,
1476				0.0003954706173886255,
1477				-0.0,
1478				-0.0,
1479				-1.0,
1480			],
1481			[1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
1482			[0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
1483			[0.0, 0.0, 1.0, 0.0, 0.0, 0.0f64],
1484		];
1485		let A = MatRef::from_row_major_array(A);
1486		let B = MatRef::from_row_major_array(B);
1487		let n = A.nrows();
1488		{
1489			let mut H = A.to_owned();
1490			let mut T = B.to_owned();
1491			let mut alpha_re = Diag::<f64>::zeros(n);
1492			let mut alpha_im = Diag::<f64>::zeros(n);
1493			let mut beta = Diag::<f64>::zeros(n);
1494			let mut UL = Mat::<f64>::identity(n, n);
1495			let mut UR = Mat::<f64>::identity(n, n);
1496			gevd_real(
1497				H.as_mut(),
1498				T.as_mut(),
1499				alpha_re.rb_mut(),
1500				alpha_im.rb_mut(),
1501				beta.rb_mut(),
1502				Some(UL.as_mut()),
1503				Some(UR.as_mut()),
1504				Par::Seq,
1505				MemStack::new(&mut MemBuffer::new(gevd_scratch::<f64>(
1506					n,
1507					ComputeEigenvectors::Yes,
1508					ComputeEigenvectors::Yes,
1509					ParShape::Seq,
1510					default(),
1511				))),
1512				default(),
1513			)
1514			.unwrap();
1515			{
1516				let mut i = 0;
1517				while i < n {
1518					if alpha_im[i] == 0.0 {
1519						let u = UR.col(i);
1520						let a = alpha_re[i];
1521						let b = beta[i];
1522						assert!((b / a) * & A * u ~ B * u);
1523						i += 1;
1524					} else {
1525						let ur = UR.col(i);
1526						let ui = UR.col(i + 1);
1527						let ar = alpha_re[i];
1528						let ai = alpha_im[i];
1529						let b = beta[i];
1530						assert!(b * & A * ur ~ B * (ar * ur - ai * ui));
1531						assert!(b * & A * ui ~ B * (ar * ui + ai * ur));
1532						i += 2;
1533					}
1534				}
1535			}
1536			{
1537				let mut i = 0;
1538				while i < n {
1539					if alpha_im[i] == 0.0 {
1540						let u = UL.col(i);
1541						let a = alpha_re[i];
1542						let b = beta[i];
1543						assert!(b / a * u.adjoint() * & A ~ u.adjoint() * B);
1544						i += 1;
1545					} else {
1546						let ur = UL.col(i);
1547						let ui = UL.col(i + 1);
1548						let ar = alpha_re[i];
1549						let ai = alpha_im[i];
1550						let b = beta[i];
1551						assert!(
1552							b * ur.adjoint() * & A ~ (ar * ur - ai * ui).adjoint() * B
1553						);
1554						assert!(
1555							b * ui.adjoint() * & A ~ (ar * ui + ai * ur).adjoint() * B
1556						);
1557						i += 2;
1558					}
1559				}
1560			}
1561		}
1562	}
1563	#[test]
1564	fn test_cplx() {
1565		let rng = &mut StdRng::seed_from_u64(1);
1566		let approx_eq = utils::approx::CwiseMat(
1567			utils::approx::ApproxEq::<f64>::eps() * 128.0,
1568		);
1569		let n = 20;
1570		let A = &CwiseMatDistribution {
1571			nrows: n,
1572			ncols: n,
1573			dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1574		}
1575		.rand::<Mat<c64>>(rng);
1576		let B = &CwiseMatDistribution {
1577			nrows: n,
1578			ncols: n,
1579			dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1580		}
1581		.rand::<Mat<c64>>(rng);
1582		{
1583			let mut H = A.to_owned();
1584			let mut T = B.to_owned();
1585			let mut alpha_re = Diag::<c64>::zeros(n);
1586			let mut beta = Diag::<c64>::zeros(n);
1587			let mut UL = Mat::<c64>::identity(n, n);
1588			let mut UR = Mat::<c64>::identity(n, n);
1589			gevd_cplx(
1590				H.as_mut(),
1591				T.as_mut(),
1592				alpha_re.rb_mut(),
1593				beta.rb_mut(),
1594				Some(UL.as_mut()),
1595				Some(UR.as_mut()),
1596				Par::Seq,
1597				MemStack::new(&mut MemBuffer::new(gevd_scratch::<c64>(
1598					n,
1599					ComputeEigenvectors::Yes,
1600					ComputeEigenvectors::Yes,
1601					ParShape::Seq,
1602					default(),
1603				))),
1604				default(),
1605			)
1606			.unwrap();
1607			{
1608				let mut i = 0;
1609				while i < n {
1610					let u = UR.col(i);
1611					let a = alpha_re[i];
1612					let b = beta[i];
1613					assert!(Scale(b / a) * A * u ~ B * u);
1614					i += 1;
1615				}
1616			}
1617			{
1618				let mut i = 0;
1619				while i < n {
1620					let u = UL.col(i);
1621					let a = alpha_re[i];
1622					let b = beta[i];
1623					assert!(Scale(b / a) * u.adjoint() * A ~ u.adjoint() * B);
1624					i += 1;
1625				}
1626			}
1627		}
1628	}
1629}
1630
1631// tests by @sjoelund
1632#[cfg(test)]
1633mod t {
1634	use faer::dyn_stack::{MemBuffer, MemStack};
1635	use faer::linalg::evd::ComputeEigenvectors as CE;
1636	use faer::prelude::*;
1637	use faer::{Par, Spec, linalg};
1638
1639	fn mk(n: usize, seed: u64) -> Mat<f64> {
1640		let mut s = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
1641		let mut next = move || {
1642			s = s
1643				.wrapping_mul(6364136223846793005)
1644				.wrapping_add(1442695040888963407);
1645			((s >> 33) as f64 / (1u64 << 31) as f64) - 1.0
1646		};
1647		Mat::from_fn(n, n, |i, j| next() + if i == j { n as f64 } else { 0.0 })
1648	}
1649
1650	/// `gevd_real`, with the right-eigenvector flag as a parameter.
1651	fn gevd(a: &Mat<f64>, b: &Mat<f64>, vectors: bool) -> Vec<(f64, f64)> {
1652		let n = a.nrows();
1653		let (mut am, mut bm) = (a.clone(), b.clone());
1654		let mut sre = faer::diag::Diag::<f64>::zeros(n);
1655		let mut sim = faer::diag::Diag::<f64>::zeros(n);
1656		let mut bet = faer::diag::Diag::<f64>::zeros(n);
1657		let mut u = Mat::<f64>::zeros(n, n);
1658		let ce = if vectors { CE::Yes } else { CE::No };
1659		let mut mem = MemBuffer::new(linalg::gevd::gevd_scratch::<f64>(
1660			n,
1661			CE::No,
1662			ce,
1663			ParShape::Seq,
1664			Spec::default(),
1665		));
1666		linalg::gevd::gevd_real(
1667			am.as_mut(),
1668			bm.as_mut(),
1669			sre.as_mut(),
1670			sim.as_mut(),
1671			bet.as_mut(),
1672			None,
1673			vectors.then(|| u.as_mut()),
1674			Par::Seq,
1675			MemStack::new(&mut mem),
1676			Spec::default(),
1677		)
1678		.unwrap();
1679		(0..n).map(|k| (sre[k] / bet[k], sim[k] / bet[k])).collect()
1680	}
1681
1682	/// |det(A - lambda B)|, zero exactly at an eigenvalue.
1683	fn residual(a: &Mat<f64>, b: &Mat<f64>, lam: (f64, f64)) -> f64 {
1684		let n = a.nrows();
1685		let mut m: Vec<(f64, f64)> = (0..n * n)
1686			.map(|k| {
1687				let (i, j) = (k % n, k / n);
1688				(a[(i, j)] - lam.0 * b[(i, j)], -lam.1 * b[(i, j)])
1689			})
1690			.collect();
1691		let mut det = (1.0f64, 0.0f64);
1692		for col in 0..n {
1693			let p = (col..n)
1694				.max_by(|&x, &y| {
1695					let f = |r: usize| m[r + col * n].0.hypot(m[r + col * n].1);
1696					f(x).partial_cmp(&f(y)).unwrap()
1697				})
1698				.unwrap();
1699			if p != col {
1700				for j in 0..n {
1701					m.swap(col + j * n, p + j * n);
1702				}
1703				det = (-det.0, -det.1);
1704			}
1705			let d = m[col + col * n];
1706			det = (det.0 * d.0 - det.1 * d.1, det.0 * d.1 + det.1 * d.0);
1707			let dn = d.0 * d.0 + d.1 * d.1;
1708			if dn == 0.0 {
1709				continue;
1710			}
1711			for r in col + 1..n {
1712				let x = m[r + col * n];
1713				let f = (
1714					(x.0 * d.0 + x.1 * d.1) / dn,
1715					(x.1 * d.0 - x.0 * d.1) / dn,
1716				);
1717				for j in col..n {
1718					let y = m[col + j * n];
1719					m[r + j * n].0 -= f.0 * y.0 - f.1 * y.1;
1720					m[r + j * n].1 -= f.0 * y.1 + f.1 * y.0;
1721				}
1722			}
1723		}
1724		det.0.hypot(det.1)
1725	}
1726
1727	/// ISSUE 1 (documentation): `gevd_real` writes a complex conjugate pair's
1728	/// eigenvalue into the FIRST slot only. The second slot holds the 2x2
1729	/// block's other diagonal entry, which is not an eigenvalue -- reading it
1730	/// gives a value whose `det(A - lambda B)` is O(1e-2) rather than O(1e-16).
1731	///
1732	/// faer's own `Solvers` layer never reads it: `solvers.rs::real_to_cplx`
1733	/// builds `S[j + 1]` from `S_re[j]`, not `S_re[j + 1]`. Nothing on
1734	/// `gevd_real` says so, and the same applies to
1735	/// `qz_real::hessenberg_to_qz`.
1736	#[test]
1737	fn eigenvalues_only_gives_wrong_complex_pairs() {
1738		let (mut bad_novec, mut bad_vec) = (0, 0);
1739		for n in [3usize, 4, 5, 6, 8, 10, 12] {
1740			for seed in 0..40u64 {
1741				let a = mk(n, seed * 7919 + n as u64);
1742				let b = mk(n, seed * 7919 + n as u64 + 5000);
1743				let scale = (0..n * n)
1744					.map(|k| a[(k % n, k / n)].abs())
1745					.fold(1.0f64, f64::max);
1746				let tol = 1e-9 * scale.powi(n as i32);
1747				for (lams, vectors) in
1748					[(gevd(&a, &b, false), false), (gevd(&a, &b, true), true)]
1749				{
1750					for &l in &lams {
1751						if l.1 == 0.0 {
1752							continue;
1753						}
1754						if residual(&a, &b, l) > tol {
1755							if vectors {
1756								bad_vec += 1
1757							} else {
1758								bad_novec += 1
1759							}
1760						}
1761					}
1762				}
1763			}
1764		}
1765		assert_eq!(bad_novec, 0, "eigenvalues-only path is wrong");
1766		assert_eq!(bad_vec, 0, "eigenvalues-only path is wrong");
1767	}
1768
1769	/// BUG 2: `gevd_scratch` under-allocates, so `gevd_real` panics inside
1770	/// `temp_mat_zeroed`. n = 2 with eigenvectors requested.
1771	#[test]
1772	fn gevd_scratch_is_too_small_for_n2() {
1773		let mut panics = 0;
1774		for seed in 0..40u64 {
1775			let a = mk(2, seed * 7919 + 2);
1776			let b = mk(2, seed * 7919 + 2 + 5000);
1777			if std::panic::catch_unwind(|| gevd(&a, &b, true)).is_err() {
1778				panics += 1;
1779			}
1780		}
1781		assert_eq!(panics, 0, "gevd_scratch under-allocates");
1782	}
1783	/// `gevd_real` writes into caller-provided output buffers, so a reused buffer
1784	/// must not leak into the eigenvectors through the entries it leaves unwritten.
1785	#[test]
1786	fn reused_eigenvector_buffers() {
1787		fn check<T: faer::traits::RealField>(
1788			a: Mat<T>,
1789			b: Mat<T>,
1790			prefills: [T; 2],
1791			tol: T,
1792		) {
1793			for prefill in prefills {
1794				for (want_left, want_right) in
1795					[(false, true), (true, false), (true, true)]
1796				{
1797					let mut am = a.clone();
1798					let mut bm = b.clone();
1799					let mut sre = faer::diag::Diag::<T>::zeros(2);
1800					let mut sim = faer::diag::Diag::<T>::zeros(2);
1801					let mut bet = faer::diag::Diag::<T>::zeros(2);
1802					let mut ul = Mat::<T>::full(2, 2, prefill.clone());
1803					let mut ur = Mat::<T>::full(2, 2, prefill.clone());
1804					let ce = |v: bool| {
1805						if v { CE::Yes } else { CE::No }
1806					};
1807					linalg::gevd::gevd_real(
1808						am.as_mut(),
1809						bm.as_mut(),
1810						sre.as_mut(),
1811						sim.as_mut(),
1812						bet.as_mut(),
1813						want_left.then(|| ul.as_mut()),
1814						want_right.then(|| ur.as_mut()),
1815						Par::Seq,
1816						MemStack::new(&mut MemBuffer::new(
1817							linalg::gevd::gevd_scratch::<T>(
1818								2,
1819								ce(want_left),
1820								ce(want_right),
1821								ParShape::Seq,
1822								Spec::default(),
1823							),
1824						)),
1825						Spec::default(),
1826					)
1827					.unwrap();
1828					// the residual alone would also accept all-zero eigenvectors, and
1829					// `norm_max` skips NaN entries, so require real vectors and finite sums
1830					let nonzero = |v: &Mat<T>| {
1831						for j in 0..v.ncols() {
1832							assert!(v.col(j).norm_l2() > tol);
1833						}
1834					};
1835					if want_right {
1836						assert!(ur.is_all_finite());
1837						nonzero(&ur);
1838						let res =
1839							(&a * &ur) * bet.as_ref() - &ur * sre.as_ref();
1840						assert!(res.is_all_finite());
1841						assert!(res.norm_max() < tol);
1842					}
1843					if want_left {
1844						assert!(ul.is_all_finite());
1845						nonzero(&ul);
1846						let res = bet.as_ref() * (ul.adjoint() * &a)
1847							- sre.as_ref() * (ul.adjoint() * &b);
1848						assert!(res.is_all_finite());
1849						assert!(res.norm_max() < tol);
1850					}
1851				}
1852			}
1853		}
1854		check(
1855			Mat::<f32>::from_fn(2, 2, |i, j| [[2.0, 1.0], [0.0, 3.0]][i][j]),
1856			Mat::<f32>::identity(2, 2),
1857			[0.0, 2.0],
1858			1e-5,
1859		);
1860		check(
1861			Mat::<f64>::from_fn(2, 2, |i, j| [[2.0, 1.0], [0.0, 3.0]][i][j]),
1862			Mat::<f64>::identity(2, 2),
1863			[0.0, 2.0],
1864			1e-12,
1865		);
1866	}
1867}