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