Skip to main content

faer/linalg/evd/
hessenberg.rs

1use crate::assert;
2use crate::internal_prelude::*;
3use linalg::householder::{self, HouseholderInfo};
4use linalg::matmul::triangular::BlockStructure;
5use linalg::matmul::{self, dot, matmul};
6use linalg::triangular_solve;
7/// hessenberg factorization tuning parameters
8#[derive(Copy, Clone, Debug)]
9pub struct HessenbergParams {
10	/// threshold at which parallelism should be disabled
11	pub par_threshold: usize,
12	/// threshold at which blocking should be disabled
13	pub blocking_threshold: usize,
14	#[doc(hidden)]
15	pub non_exhaustive: NonExhaustive,
16}
17impl<T: ComplexField> Auto<T> for HessenbergParams {
18	fn auto() -> Self {
19		Self {
20			par_threshold: 192 * 256,
21			blocking_threshold: 256 * 256,
22			non_exhaustive: NonExhaustive(()),
23		}
24	}
25}
26/// computes the layout of the workspace required to compute a matrix's
27/// hessenberg decomposition
28pub fn hessenberg_in_place_scratch<T: ComplexField>(
29	dim: usize,
30	block_size: usize,
31	par: Par,
32	params: Spec<HessenbergParams, T>,
33) -> StackReq {
34	let params = params.config;
35	let _ = par;
36	let n = dim;
37	if n * n < params.blocking_threshold {
38		StackReq::any_of(&[StackReq::all_of(&[
39			temp_mat_scratch::<T>(n, 1).array(3),
40			temp_mat_scratch::<T>(n, par.degree()),
41		])])
42	} else {
43		StackReq::all_of(&[
44			temp_mat_scratch::<T>(n, block_size),
45			temp_mat_scratch::<T>(block_size, 1),
46			StackReq::any_of(&[
47				StackReq::all_of(&[
48					temp_mat_scratch::<T>(n, 1),
49					temp_mat_scratch::<T>(n, par.degree()),
50				]),
51				temp_mat_scratch::<T>(n, block_size),
52			]),
53		])
54	}
55}
56fn hessenberg_fused_op_simd<T: ComplexField>(
57	A: MatMut<'_, T, usize, usize, ContiguousFwd>,
58	l_out: RowMut<'_, T, usize>,
59	r_out: ColMut<'_, T, usize, ContiguousFwd>,
60	l_in: RowRef<'_, T, usize, ContiguousFwd>,
61	r_in: ColRef<'_, T, usize>,
62	l0: ColRef<'_, T, usize, ContiguousFwd>,
63	l1: ColRef<'_, T, usize, ContiguousFwd>,
64	r0: RowRef<'_, T, usize>,
65	r1: RowRef<'_, T, usize>,
66	align: usize,
67) {
68	struct Impl<'a, 'M, 'N, T: ComplexField> {
69		A: MatMut<'a, T, Dim<'M>, Dim<'N>, ContiguousFwd>,
70		l_out: RowMut<'a, T, Dim<'N>>,
71		r_out: ColMut<'a, T, Dim<'M>, ContiguousFwd>,
72		l_in: RowRef<'a, T, Dim<'M>, ContiguousFwd>,
73		r_in: ColRef<'a, T, Dim<'N>>,
74		l0: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
75		l1: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
76		r0: RowRef<'a, T, Dim<'N>>,
77		r1: RowRef<'a, T, Dim<'N>>,
78		align: usize,
79	}
80	impl<'a, 'M, 'N, T: ComplexField> pulp::WithSimd for Impl<'a, 'M, 'N, T> {
81		type Output = ();
82
83		#[inline(always)]
84		fn with_simd<S: pulp::Simd>(self, simd: S) -> Self::Output {
85			let Self {
86				mut A,
87				mut l_out,
88				mut r_out,
89				l_in,
90				r_in,
91				l0,
92				l1,
93				r0,
94				r1,
95				align,
96			} = self;
97			let (m, n) = A.shape();
98			let simd = SimdCtx::<T, S>::new_align(T::simd_ctx(simd), m, align);
99
100			simd_iter!(for i in [simd.indices()] {
101				simd.write(r_out.rb_mut(), i, simd.zero());
102			});
103
104			let indices = simd.batch_indices::<4>();
105			let l_in = l_in.transpose();
106			for j in n.indices() {
107				let mut A = A.rb_mut().col_mut(j);
108				let r_in = simd.splat(r_in.at(j));
109				let r0 = simd.splat(-&r0[j]);
110				let r1 = simd.splat(-&r1[j]);
111				let mut acc = [simd.zero(); 4];
112				simd_iter!(for (IDX, i) in [indices; 4] {
113					let mut a = simd.read(A.rb(), i);
114					a = simd.mul_add(simd.read(l0, i), r0, a);
115					a = simd.conj_mul_add(r1, simd.read(l1, i), a);
116					simd.write(A.rb_mut(), i, a);
117					acc[IDX] =
118						simd.conj_mul_add(simd.read(l_in, i), a, acc[IDX]);
119					let tmp = simd.read(r_out.rb(), i);
120					simd.write(r_out.rb_mut(), i, simd.mul_add(a, r_in, tmp));
121				});
122				let acc0 = simd.add(acc[0], acc[1]);
123				let acc2 = simd.add(acc[2], acc[3]);
124				let acc0 = simd.add(acc0, acc2);
125				let l_out = l_out.rb_mut().at_mut(j);
126				*l_out = simd.reduce_sum(acc0);
127			}
128		}
129	}
130	with_dim!(M, A.nrows());
131	with_dim!(N, A.ncols());
132	dispatch!(
133		Impl {
134			A: A.as_shape_mut(M, N),
135			l_out: l_out.as_col_shape_mut(N),
136			r_out: r_out.as_row_shape_mut(M),
137			l_in: l_in.as_col_shape(M),
138			r_in: r_in.as_row_shape(N),
139			l0: l0.as_row_shape(M),
140			l1: l1.as_row_shape(M),
141			r0: r0.as_col_shape(N),
142			r1: r1.as_col_shape(N),
143			align,
144		},
145		Impl,
146		T
147	)
148}
149fn hessenberg_fused_op_fallback<T: ComplexField>(
150	A: MatMut<'_, T>,
151	l_out: RowMut<'_, T>,
152	r_out: ColMut<'_, T>,
153	l_in: RowRef<'_, T>,
154	r_in: ColRef<'_, T>,
155	l0: ColRef<'_, T>,
156	l1: ColRef<'_, T>,
157	r0: RowRef<'_, T>,
158	r1: RowRef<'_, T>,
159) {
160	let mut A = A;
161	matmul(
162		A.rb_mut(),
163		Accum::Add,
164		l0.as_mat(),
165		r0.as_mat(),
166		-one::<T>(),
167		Par::Seq,
168	);
169	matmul(
170		A.rb_mut(),
171		Accum::Add,
172		l1.as_mat(),
173		r1.as_mat().conjugate(),
174		-one::<T>(),
175		Par::Seq,
176	);
177	matmul(
178		r_out.as_mat_mut(),
179		Accum::Replace,
180		A.rb(),
181		r_in.as_mat(),
182		one(),
183		Par::Seq,
184	);
185	matmul(
186		l_out.as_mat_mut(),
187		Accum::Replace,
188		l_in.as_mat().conjugate(),
189		A.rb(),
190		one(),
191		Par::Seq,
192	);
193}
194fn hessenberg_fused_op<T: ComplexField>(
195	A: MatMut<'_, T>,
196	l_out: RowMut<'_, T>,
197	r_out: ColMut<'_, T>,
198	l_in: RowRef<'_, T>,
199	r_in: ColRef<'_, T>,
200	l0: ColRef<'_, T>,
201	l1: ColRef<'_, T>,
202	r0: RowRef<'_, T>,
203	r1: RowRef<'_, T>,
204	align: usize,
205) {
206	let mut A = A;
207	let mut r_out = r_out;
208	if const { T::SIMD_CAPABILITIES.is_simd() } {
209		if let (Some(A), Some(r_out), Some(l_in), Some(l0), Some(l1)) = (
210			A.rb_mut().try_as_col_major_mut(),
211			r_out.rb_mut().try_as_col_major_mut(),
212			l_in.try_as_row_major(),
213			l0.try_as_col_major(),
214			l1.try_as_col_major(),
215		) {
216			hessenberg_fused_op_simd(
217				A, l_out, r_out, l_in, r_in, l0, l1, r0, r1, align,
218			);
219		} else {
220			hessenberg_fused_op_fallback(
221				A, l_out, r_out, l_in, r_in, l0, l1, r0, r1,
222			);
223		}
224	} else {
225		hessenberg_fused_op_fallback(
226			A, l_out, r_out, l_in, r_in, l0, l1, r0, r1,
227		);
228	}
229}
230fn hessenberg_rearranged_unblocked<T: ComplexField>(
231	A: MatMut<'_, T>,
232	H: MatMut<'_, T>,
233	par: Par,
234	stack: &mut MemStack,
235	params: HessenbergParams,
236) {
237	assert!(all(
238		A.nrows() == A.ncols(),
239		H.ncols() == A.ncols().saturating_sub(1)
240	));
241	let n = A.nrows();
242	let b = H.nrows();
243	if n == 0 {
244		return;
245	}
246	let mut A = A;
247	let mut H = H;
248	let mut par = par;
249	{
250		let mut H = H.rb_mut().row_mut(0);
251		alloca!('stack: {
252			let mut y = unsafe { row![uninit::<T>, n] };
253			let mut z = unsafe { col![uninit::<T>, n] };
254			let mut v = unsafe { row![uninit::<T>, n] };
255			let mut w = unsafe { mat![uninit::<T>, n, par.degree()] };
256		});
257		for k in 0..n {
258			let (_, A01, A10, A11) = A.rb_mut().split_at_mut(k, k);
259			let (_, mut A02) = A01.split_first_col_mut().unwrap();
260			let (_, A20) = A10.split_first_row_mut().unwrap();
261			let (mut A11, A12, A21, mut A22) = A11.split_at_mut(1, 1);
262			let mut A12 = A12.row_mut(0);
263			let mut A21 = A21.col_mut(0);
264			let A11 = &mut A11[(0, 0)];
265			let (y1, mut y2) =
266				y.rb_mut().split_at_col_mut(k).1.split_at_col_mut(1);
267			let (z1, mut z2) =
268				z.rb_mut().split_at_row_mut(k).1.split_at_row_mut(1);
269			let (_, mut v2) =
270				v.rb_mut().split_at_col_mut(k).1.split_at_col_mut(1);
271			let (mut w0, w12) = w.rb_mut().split_at_row_mut(k);
272			let (_, mut w2) = w12.split_at_row_mut(1);
273			if k > 0 {
274				let y1 = y1[0].copy();
275				let z1 = z1[0].copy();
276
277				let p = k - 1;
278				let u2 = A20.rb().col(p);
279				*A11 -= &y1 + &z1;
280				z!(&mut A12, &y2, u2.rb().transpose()).for_each(
281					|uz!(a, y, u): Zip!(&mut T, &T, &T)| {
282						*a -= y + &z1 * u.conj();
283					},
284				);
285				z!(&mut A21, &u2, &z2).for_each(
286					|uz!(a, u, z): Zip!(&mut T, &T, &T)| *a -= u * &y1 + z,
287				);
288			}
289			{
290				let n = n - k - 1;
291				if n * n < params.par_threshold {
292					par = Par::Seq;
293				}
294			}
295			if k + 1 == n {
296				break;
297			}
298			let beta;
299			let tau_inv;
300			{
301				let (mut A11, mut A21) = A21.rb_mut().split_at_row_mut(1);
302				let A11 = &mut A11[0];
303				let HouseholderInfo { tau, .. } =
304					householder::make_householder_in_place(A11, A21.rb_mut());
305				tau_inv = tau.recip();
306				beta = A11.copy();
307				*A11 = one();
308				H[k] = tau.to_cplx();
309			}
310			let x2 = A21.rb();
311			if k > 0 {
312				let p = k - 1;
313				let u2 = A20.rb().col(p);
314				hessenberg_fused_op(
315					A22.rb_mut(),
316					v2.rb_mut(),
317					w2.rb_mut().col_mut(0),
318					x2.transpose(),
319					x2,
320					u2,
321					z2.rb(),
322					y2.rb(),
323					u2.transpose(),
324					simd_align(k + 1),
325				);
326				y2.copy_from(v2.rb());
327				z2.copy_from(w2.rb().col(0));
328			} else {
329				matmul(
330					z2.rb_mut().as_mat_mut(),
331					Accum::Replace,
332					A22.rb(),
333					x2.as_mat(),
334					one(),
335					par,
336				);
337				matmul(
338					y2.rb_mut().as_mat_mut(),
339					Accum::Replace,
340					x2.adjoint().as_mat(),
341					A22.rb(),
342					one(),
343					par,
344				);
345			}
346			let u2 = x2;
347			let ref b = dot::inner_prod(
348				u2.rb().transpose(),
349				Conj::Yes,
350				z2.rb(),
351				Conj::No,
352			)
353			.mul_pow2(from_f64::<T::Real>(0.5))
354			.mul_real(&tau_inv);
355			z!(&mut y2, u2.transpose()).for_each(
356				|uz!(y, u): Zip!(&mut T, &T)| {
357					*y = (&*y - b * u.conj()).mul_real(&tau_inv);
358				},
359			);
360			z!(&mut z2, u2).for_each(|uz!(z, u): Zip!(&mut T, &T)| {
361				*z = (&*z - b * u).mul_real(&tau_inv);
362			});
363			let ref dot =
364				dot::inner_prod(A12.rb(), Conj::No, u2.rb(), Conj::No)
365					.mul_real(&tau_inv);
366			z!(&mut A12, u2.transpose())
367				.for_each(|uz!(a, u): Zip!(&mut T, &T)| *a -= dot * u.conj());
368			matmul(
369				w0.rb_mut().col_mut(0).as_mat_mut(),
370				Accum::Replace,
371				A02.rb(),
372				u2.as_mat(),
373				one(),
374				par,
375			);
376			matmul(
377				A02.rb_mut(),
378				Accum::Add,
379				w0.rb().col(0).as_mat(),
380				u2.adjoint().as_mat(),
381				(-&tau_inv).to_cplx(),
382				par,
383			);
384			A21[0] = beta;
385		}
386	}
387	if n > 0 {
388		let n = n - 1;
389		let A = A.rb().submatrix(1, 0, n, n);
390		let mut H = H.rb_mut().subcols_mut(0, n);
391		let mut j = 0;
392		while j < n {
393			let b = Ord::min(b, n - j);
394			let mut H = H.rb_mut().submatrix_mut(0, j, b, b);
395			for k in 0..b {
396				H[(k, k)] = H[(0, k)].copy();
397			}
398			householder::upgrade_householder_factor(
399				H.rb_mut(),
400				A.submatrix(j, j, n - j, b),
401				b,
402				1,
403				par,
404			);
405			j += b;
406		}
407	}
408}
409fn hessenberg_gqvdg_unblocked<T: ComplexField>(
410	A: MatMut<'_, T>,
411	Z: MatMut<'_, T>,
412	H: MatMut<'_, T>,
413	beta: ColMut<'_, T>,
414	par: Par,
415	stack: &mut MemStack,
416	params: HessenbergParams,
417) {
418	let n = A.nrows();
419	let b = H.nrows();
420	let mut A = A;
421	let mut H = H;
422	let mut Z = Z;
423	_ = params;
424	let (mut x, _) = unsafe { temp_mat_uninit(n, 1, stack) };
425	let mut x = x.as_mat_mut().col_mut(0);
426	let mut beta = beta;
427	for k in 0..b {
428		let mut x0 = x.rb_mut().subrows_mut(0, k);
429		let (T00, T01, _, T11) = H.rb_mut().split_at_mut(k, k);
430		let (mut T01, _) = T01.split_first_col_mut().unwrap();
431		let (mut T11, _, _, _) = T11.split_at_mut(1, 1);
432		let T11 = &mut T11[(0, 0)];
433		let (U0, A12) = A.rb_mut().split_at_col_mut(k);
434		let (mut A1, A2) = A12.split_first_col_mut().unwrap();
435		let (Z0, Z12) = Z.rb_mut().split_at_col_mut(k);
436		let (mut Z1, _) = Z12.split_first_col_mut().unwrap();
437		let U0 = U0.rb();
438		let Z0 = Z0.rb();
439		let T00 = T00.rb();
440		let (U00, U10) = U0.split_at_row(k);
441		let (U10, U20) = U10.split_first_row().unwrap();
442		x0.copy_from(U10.adjoint());
443		triangular_solve::solve_upper_triangular_in_place(
444			T00,
445			x0.rb_mut().as_mat_mut(),
446			par,
447		);
448		matmul::matmul(
449			A1.rb_mut().as_mat_mut(),
450			Accum::Add,
451			Z0,
452			x0.rb().as_mat(),
453			-one::<T>(),
454			par,
455		);
456		let (mut A01, A11) = A1.rb_mut().split_at_row_mut(k);
457		let (mut A11, mut A21) = A11.split_at_row_mut(1);
458		let A11 = &mut A11[0];
459		{
460			matmul::triangular::matmul(
461				x0.rb_mut().as_mat_mut(),
462				BlockStructure::Rectangular,
463				Accum::Replace,
464				U00.adjoint(),
465				BlockStructure::StrictTriangularUpper,
466				A01.rb().as_mat(),
467				BlockStructure::Rectangular,
468				one(),
469				par,
470			);
471			z!(x0.rb_mut(), U10.transpose())
472				.for_each(|uz!(x, u): Zip!(&mut T, &T)| *x += &*A11 * u.conj());
473			matmul::matmul(
474				x0.rb_mut().as_mat_mut(),
475				Accum::Add,
476				U20.adjoint(),
477				A21.rb().as_mat(),
478				one(),
479				par,
480			);
481		}
482		{
483			triangular_solve::solve_lower_triangular_in_place(
484				T00.adjoint(),
485				x0.rb_mut().as_mat_mut(),
486				par,
487			);
488		}
489		{
490			matmul::triangular::matmul(
491				A01.rb_mut().as_mat_mut(),
492				BlockStructure::Rectangular,
493				Accum::Add,
494				U00,
495				BlockStructure::StrictTriangularLower,
496				x0.rb().as_mat(),
497				BlockStructure::Rectangular,
498				-one::<T>(),
499				par,
500			);
501			*A11 -= dot::inner_prod(U10, Conj::No, x0.rb(), Conj::No);
502			matmul::matmul(
503				A21.rb_mut().as_mat_mut(),
504				Accum::Add,
505				U20,
506				x0.rb().as_mat(),
507				-one::<T>(),
508				par,
509			);
510		}
511		if k + 1 < n {
512			let (mut A11, mut A21) = A21.rb_mut().split_at_row_mut(1);
513			let A11 = &mut A11[0];
514			let HouseholderInfo { tau, .. } =
515				householder::make_householder_in_place(A11, A21.rb_mut());
516			beta[k] = A11.copy();
517			*A11 = one();
518			*T11 = tau.to_cplx();
519		} else {
520			*T11 = infinity();
521		}
522		matmul::matmul(
523			Z1.rb_mut().as_mat_mut(),
524			Accum::Replace,
525			A2.rb(),
526			A21.rb().as_mat(),
527			one(),
528			par,
529		);
530		matmul::matmul(
531			T01.rb_mut().as_mat_mut(),
532			Accum::Replace,
533			U20.adjoint(),
534			A21.rb().as_mat(),
535			one(),
536			par,
537		);
538	}
539}
540/// computes a matrix $A$'s hessenberg decomposition such that $A = Q H Q^H$
541///
542/// $H$ is a hessenberg matrix stored in the upper triangular half of $A$ (plus
543/// the subdiagonal)
544///
545/// $Q$ is a sequence of householder reflections stored in the unit lower
546/// triangular half of $A$ (excluding the diagonal), with the householder
547/// coefficients being stored in `householder`
548#[track_caller]
549pub fn hessenberg_in_place<T: ComplexField>(
550	A: MatMut<'_, T>,
551	householder: MatMut<'_, T>,
552	par: Par,
553	stack: &mut MemStack,
554	params: Spec<HessenbergParams, T>,
555) {
556	let params = params.config;
557	assert!(all(
558		A.nrows() == A.ncols(),
559		householder.ncols() == A.ncols().saturating_sub(1)
560	));
561	let n = A.nrows().unbound();
562	if n * n < params.blocking_threshold {
563		hessenberg_rearranged_unblocked(A, householder, par, stack, params);
564	} else {
565		hessenberg_gqvdg_blocked(A, householder, par, stack, params);
566	}
567}
568fn hessenberg_gqvdg_blocked<T: ComplexField>(
569	A: MatMut<'_, T>,
570	H: MatMut<'_, T>,
571	par: Par,
572	stack: &mut MemStack,
573	params: HessenbergParams,
574) {
575	let n = A.nrows();
576	let b = H.nrows();
577	let mut A = A;
578	let mut H = H;
579	let (mut Z, stack) = unsafe { temp_mat_uninit(n, b, stack) };
580	let mut Z = Z.as_mat_mut();
581	let mut j = 0;
582	while j < n {
583		let bs = Ord::min(b, n - j);
584		let bs_u = Ord::min(bs, n - j - 1);
585		let (mut beta, stack) = unsafe { temp_mat_uninit(bs, 1, stack) };
586		let mut beta = beta.as_mat_mut().col_mut(0);
587		{
588			let mut T11 = H.rb_mut().submatrix_mut(0, j, bs_u, bs_u);
589			{
590				let A11 = A.rb_mut().submatrix_mut(j, j, n - j, n - j);
591				let Z1 = Z.rb_mut().submatrix_mut(j, 0, n - j, bs);
592				hessenberg_gqvdg_unblocked(
593					A11,
594					Z1,
595					T11.rb_mut(),
596					beta.rb_mut(),
597					par,
598					stack,
599					params,
600				);
601			}
602			let (mut X, _) = unsafe { temp_mat_uninit(n, bs_u, stack) };
603			let mut X = X.as_mat_mut();
604			let (mut X0, X12) = X.rb_mut().split_at_row_mut(j);
605			let (_, mut X2) = X12.split_at_row_mut(bs_u);
606			let (_, Z12) = Z.rb_mut().subcols_mut(0, bs_u).split_at_row_mut(j);
607			let (mut Z1, mut Z2) = Z12.split_at_row_mut(bs_u);
608			let (_, A01, _, A11) = A.rb_mut().split_at_mut(j, j);
609			let (mut A01, mut A02) = A01.split_at_col_mut(bs_u);
610			let (A11, mut A12, A21, mut A22) = A11.split_at_mut(bs_u, bs_u);
611			let U1 = A11.rb();
612			let U2 = A21.rb();
613			let T1 = T11.rb();
614			matmul::triangular::matmul(
615				X0.rb_mut(),
616				BlockStructure::Rectangular,
617				Accum::Replace,
618				A01.rb(),
619				BlockStructure::Rectangular,
620				U1,
621				BlockStructure::StrictTriangularLower,
622				one(),
623				par,
624			);
625			matmul::matmul(X0.rb_mut(), Accum::Add, A02.rb(), U2, one(), par);
626			triangular_solve::solve_lower_triangular_in_place(
627				T1.transpose(),
628				X0.rb_mut().transpose_mut(),
629				par,
630			);
631			matmul::triangular::matmul(
632				A01.rb_mut(),
633				BlockStructure::Rectangular,
634				Accum::Add,
635				X0.rb(),
636				BlockStructure::Rectangular,
637				U1.adjoint(),
638				BlockStructure::StrictTriangularUpper,
639				-one::<T>(),
640				par,
641			);
642			matmul::matmul(
643				A02.rb_mut(),
644				Accum::Add,
645				X0.rb(),
646				U2.adjoint(),
647				-one::<T>(),
648				par,
649			);
650			triangular_solve::solve_lower_triangular_in_place(
651				T1.transpose(),
652				Z1.rb_mut().transpose_mut(),
653				par,
654			);
655			triangular_solve::solve_lower_triangular_in_place(
656				T1.transpose(),
657				Z2.rb_mut().transpose_mut(),
658				par,
659			);
660			matmul::matmul(
661				A12.rb_mut(),
662				Accum::Add,
663				Z1.rb(),
664				U2.adjoint(),
665				-one::<T>(),
666				par,
667			);
668			matmul::matmul(
669				A22.rb_mut(),
670				Accum::Add,
671				Z2.rb(),
672				U2.adjoint(),
673				-one::<T>(),
674				par,
675			);
676			let mut X = X2.rb_mut().transpose_mut();
677			matmul::triangular::matmul(
678				X.rb_mut(),
679				BlockStructure::Rectangular,
680				Accum::Replace,
681				U1.adjoint(),
682				BlockStructure::StrictTriangularUpper,
683				A12.rb(),
684				BlockStructure::Rectangular,
685				one(),
686				par,
687			);
688			matmul::matmul(
689				X.rb_mut(),
690				Accum::Add,
691				U2.adjoint(),
692				A22.rb(),
693				one(),
694				par,
695			);
696			triangular_solve::solve_lower_triangular_in_place(
697				T1.adjoint(),
698				X.rb_mut(),
699				par,
700			);
701			matmul::triangular::matmul(
702				A12.rb_mut(),
703				BlockStructure::Rectangular,
704				Accum::Add,
705				U1,
706				BlockStructure::StrictTriangularLower,
707				X.rb(),
708				BlockStructure::Rectangular,
709				-one::<T>(),
710				par,
711			);
712			matmul::matmul(
713				A22.rb_mut(),
714				Accum::Add,
715				U2,
716				X.rb(),
717				-one::<T>(),
718				par,
719			);
720		}
721		let n = n - j;
722		let mut A = A.rb_mut().submatrix_mut(j, j, n, bs);
723		for k in 0..bs {
724			if k + 1 < n {
725				A[(k + 1, k)] = beta[k].copy();
726			}
727		}
728		j += bs;
729	}
730}
731#[cfg(test)]
732mod tests {
733	use super::*;
734	use crate::stats::prelude::*;
735	use crate::utils::approx::*;
736	use crate::{Mat, assert, c64};
737	use dyn_stack::MemBuffer;
738	use std::mem::MaybeUninit;
739	#[test]
740	fn test_hessenberg_real() {
741		let rng = &mut StdRng::seed_from_u64(0);
742		for n in [3, 4, 8, 16] {
743			let A = CwiseMatDistribution {
744				nrows: n,
745				ncols: n,
746				dist: StandardNormal,
747			}
748			.rand::<Mat<f64>>(rng);
749			let b = 3;
750			let mut H = Mat::zeros(b, n - 1);
751			let mut V = A.clone();
752			let mut V = V.as_mut();
753			hessenberg_rearranged_unblocked(
754				V.rb_mut(),
755				H.as_mut(),
756				Par::Seq,
757				MemStack::new(&mut [MaybeUninit::uninit(); 1024]),
758				auto!(f64),
759			);
760			let mut A = A.clone();
761			let mut A = A.as_mut();
762			for iter in 0..2 {
763				let mut A = if iter == 0 {
764					A.rb_mut()
765				} else {
766					A.rb_mut().transpose_mut()
767				};
768				let n = n - 1;
769				let V = V.rb().submatrix(1, 0, n, n);
770				let mut A = A.rb_mut().subrows_mut(1, n);
771				let H = H.as_ref();
772				householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
773					V,
774					H.as_ref(),
775					if iter == 0 { Conj::Yes } else { Conj::No },
776					A.rb_mut(),
777					Par::Seq,
778					MemStack::new(&mut MemBuffer::new(
779						householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<f64>(n, b, n + 1),
780					)),
781				);
782			}
783			let approx_eq = CwiseMat(ApproxEq::<f64>::eps());
784			for j in 0..n {
785				for i in 0..n {
786					if i > j + 1 {
787						V[(i, j)] = 0.0;
788					}
789				}
790			}
791			assert!(V ~ A);
792		}
793	}
794	#[test]
795	fn test_hessenberg_cplx() {
796		let rng = &mut StdRng::seed_from_u64(0);
797		for n in [1, 2, 3, 4, 8, 16] {
798			for par in [Par::Seq, Par::rayon(4)] {
799				let A = CwiseMatDistribution {
800					nrows: n,
801					ncols: n,
802					dist: ComplexDistribution::new(
803						StandardNormal,
804						StandardNormal,
805					),
806				}
807				.rand::<Mat<c64>>(rng);
808				let b = 3;
809				let mut H = Mat::zeros(b, n - 1);
810				let mut V = A.clone();
811				let mut V = V.as_mut();
812				hessenberg_rearranged_unblocked(
813					V.rb_mut(),
814					H.as_mut(),
815					par,
816					MemStack::new(&mut [MaybeUninit::uninit(); 8 * 1024]),
817					HessenbergParams {
818						par_threshold: 0,
819						..auto!(c64)
820					}
821					.into(),
822				);
823				let mut A = A.clone();
824				let mut A = A.as_mut();
825				for iter in 0..2 {
826					let mut A = if iter == 0 {
827						A.rb_mut()
828					} else {
829						A.rb_mut().transpose_mut()
830					};
831					let n = n - 1;
832					let V = V.rb().submatrix(1, 0, n, n);
833					let mut A = A.rb_mut().subrows_mut(1, n);
834					let H = H.as_ref();
835					householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
836						V,
837						H.as_ref(),
838						if iter == 0 { Conj::Yes } else { Conj::No },
839						A.rb_mut(),
840						Par::Seq,
841						MemStack::new(&mut MemBuffer::new(
842							householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<c64>(n, b, n + 1),
843						)),
844					);
845				}
846				let approx_eq = CwiseMat(ApproxEq::eps());
847				for j in 0..n {
848					for i in 0..n {
849						if i > j + 1 {
850							V[(i, j)] = c64::ZERO;
851						}
852					}
853				}
854				assert!(V ~ A);
855			}
856		}
857	}
858	#[test]
859	fn test_hessenberg_cplx_gqvdg() {
860		let rng = &mut StdRng::seed_from_u64(0);
861		for n in [2, 3, 4, 8, 16, 21] {
862			for par in [Par::Seq, Par::rayon(4)] {
863				let b = 4;
864				let A = CwiseMatDistribution {
865					nrows: n,
866					ncols: n,
867					dist: ComplexDistribution::new(
868						StandardNormal,
869						StandardNormal,
870					),
871				}
872				.rand::<Mat<c64, _, _>>(rng);
873				let mut H = Mat::zeros(b, n - 1);
874				let mut V = A.clone();
875				let mut V = V.as_mut();
876				hessenberg_gqvdg_blocked(
877					V.rb_mut(),
878					H.as_mut(),
879					par,
880					MemStack::new(&mut [MaybeUninit::uninit(); 16 * 1024]),
881					HessenbergParams {
882						par_threshold: 0,
883						..auto!(c64)
884					}
885					.into(),
886				);
887				let mut A = A.clone();
888				let mut A = A.as_mut();
889				for iter in 0..2 {
890					let mut A = if iter == 0 {
891						A.rb_mut()
892					} else {
893						A.rb_mut().transpose_mut()
894					};
895					let n = n - 1;
896					let V = V.rb().submatrix(1, 0, n, n);
897					let mut A = A.rb_mut().subrows_mut(1, n);
898					let H = H.as_ref().subcols(0, n);
899					householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
900						V,
901						H.as_ref(),
902						if iter == 0 { Conj::Yes } else { Conj::No },
903						A.rb_mut(),
904						Par::Seq,
905						MemStack::new(&mut MemBuffer::new(
906							householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<c64>(n, b, n + 1),
907						)),
908					);
909				}
910				let approx_eq = CwiseMat(ApproxEq::eps());
911				for j in 0..n {
912					for i in 0..n {
913						if i > j + 1 {
914							V[(i, j)] = c64::ZERO;
915						}
916					}
917				}
918				assert!(V ~ A);
919			}
920		}
921	}
922}