Skip to main content

faer/linalg/evd/
tridiag.rs

1use crate::assert;
2use crate::internal_prelude::*;
3use crate::utils::cost::{ParThreshold, rate};
4use linalg::householder;
5use linalg::matmul::triangular::BlockStructure;
6use linalg::matmul::{self, dot};
7/// tridiagonalization tuning parameters
8#[derive(Copy, Clone, Debug)]
9pub struct TridiagParams {
10	/// floor of the parallel regions: [`ParThreshold::Derived`] (the default) runs a step in
11	/// parallel when its serial time pays for one section; [`ParThreshold::Fixed`] counts
12	/// elements `rem*rem/2` of the triangle
13	pub par_threshold: ParThreshold,
14	#[doc(hidden)]
15	pub non_exhaustive: NonExhaustive,
16}
17impl<T: ComplexField> Auto<T> for TridiagParams {
18	fn auto() -> Self {
19		Self {
20			par_threshold: ParThreshold::Derived,
21			non_exhaustive: NonExhaustive(()),
22		}
23	}
24}
25/// computes the layout of the workspace required to compute a self-adjoint
26/// matrix's tridiagonalization
27pub fn tridiag_in_place_scratch<T: ComplexField>(
28	dim: usize,
29	par: ParShape,
30	params: Spec<TridiagParams, T>,
31) -> StackReq {
32	_ = par;
33	_ = params;
34	StackReq::all_of(&[
35		temp_mat_scratch::<T>(dim, 1).array(2),
36		temp_mat_scratch::<T>(dim, par.degree()),
37	])
38}
39fn tridiag_fused_op_simd<T: ComplexField>(
40	A: MatMut<'_, T, usize, usize, ContiguousFwd>,
41	y2: ColMut<'_, T, usize>,
42	z2: ColMut<'_, T, usize, ContiguousFwd>,
43	ry2: ColRef<'_, T, usize>,
44	rz2: ColRef<'_, T, usize, ContiguousFwd>,
45	u0: ColRef<'_, T, usize, ContiguousFwd>,
46	u1: ColRef<'_, T, usize>,
47	u2: ColRef<'_, T, usize>,
48	v2: ColRef<'_, T, usize, ContiguousFwd>,
49	f: T,
50	align: usize,
51) {
52	struct Impl<'a, 'M, 'N, T: ComplexField> {
53		A: MatMut<'a, T, Dim<'M>, Dim<'N>, ContiguousFwd>,
54		y2: ColMut<'a, T, Dim<'N>>,
55		z2: ColMut<'a, T, Dim<'M>, ContiguousFwd>,
56		ry2: ColRef<'a, T, Dim<'N>>,
57		rz2: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
58		u0: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
59		u1: ColRef<'a, T, Dim<'N>>,
60		u2: ColRef<'a, T, Dim<'N>>,
61		v2: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
62		f: T,
63		align: usize,
64	}
65	impl<'a, 'M, 'N, T: ComplexField> pulp::WithSimd for Impl<'a, 'M, 'N, T> {
66		type Output = ();
67
68		#[inline(always)]
69		fn with_simd<S: pulp::Simd>(self, simd: S) -> Self::Output {
70			let Self {
71				mut A,
72				mut y2,
73				mut z2,
74				ry2,
75				rz2,
76				u0,
77				u1,
78				u2,
79				v2,
80				ref f,
81				mut align,
82			} = self;
83			let simd = T::simd_ctx(simd);
84			let (m, n) = A.shape();
85			{
86				let simd = SimdCtx::<T, S>::new_align(simd, m, align);
87				simd_iter!(for i in [simd.indices()] {
88					simd.write(z2.rb_mut(), i, simd.zero());
89				});
90			}
91			for j in n.indices() {
92				let i = m.idx_inc(*j);
93				with_dim!(m, *m - *j);
94				let simd = SimdCtx::<T, S>::new_align(simd, m, align);
95				align -= 1;
96				let mut A = A.rb_mut().col_mut(j).subrows_mut(i, m);
97				let mut z = z2.rb_mut().subrows_mut(i, m);
98				let rz = rz2.subrows(i, m);
99				let ua = u0.subrows(i, m);
100				let v = v2.subrows(i, m);
101				let y = y2.rb_mut().at_mut(j);
102				let ry = simd.splat(&(-&ry2[j]));
103				let ub = simd.splat(&(-&u1[j]));
104				let uc = simd.splat(&(f * &u2[j]));
105				let mut acc = [simd.zero(); 4];
106				simd_iter!(for (IDX, i) in [simd.batch_indices(); 4] {
107					let mut a = simd.read(A.rb(), i);
108					a = simd.conj_mul_add(ry, simd.read(ua, i), a);
109					a = simd.conj_mul_add(ub, simd.read(rz, i), a);
110					simd.write(A.rb_mut(), i, a);
111					let tmp = simd.read(z.rb(), i);
112					simd.write(z.rb_mut(), i, simd.mul_add(a, uc, tmp));
113					acc[IDX] = simd.conj_mul_add(a, simd.read(v, i), acc[IDX]);
114				});
115				let acc0 = simd.add(acc[0], acc[1]);
116				let acc2 = simd.add(acc[2], acc[3]);
117				let acc0 = simd.add(acc0, acc2);
118				let acc0 = simd.reduce_sum(acc0);
119				let i0 = m.idx(0);
120				*y = f * (acc0 - &A[i0] * &v[i0]);
121			}
122		}
123	}
124	with_dim!(M, A.nrows());
125	with_dim!(N, A.ncols());
126	dispatch!(
127		Impl {
128			A: A.as_shape_mut(M, N),
129			y2: y2.as_row_shape_mut(N),
130			z2: z2.as_row_shape_mut(M),
131			ry2: ry2.as_row_shape(N),
132			rz2: rz2.as_row_shape(M),
133			u0: u0.as_row_shape(M),
134			u1: u1.as_row_shape(N),
135			u2: u2.as_row_shape(N),
136			v2: v2.as_row_shape(M),
137			f,
138			align,
139		},
140		Impl,
141		T
142	)
143}
144fn tridiag_fused_op<T: ComplexField>(
145	A: MatMut<'_, T>,
146	y2: ColMut<'_, T>,
147	z2: ColMut<'_, T>,
148	ry2: ColRef<'_, T>,
149	rz2: ColRef<'_, T>,
150	u0: ColRef<'_, T>,
151	u1: ColRef<'_, T>,
152	u2: ColRef<'_, T>,
153	v2: ColRef<'_, T>,
154	f: T,
155	align: usize,
156) {
157	let mut A = A;
158	let mut z2 = z2;
159	if const { T::SIMD_CAPABILITIES.is_simd() } {
160		if let (Some(A), Some(z2), Some(rz2), Some(u0), Some(v2)) = (
161			A.rb_mut().try_as_col_major_mut(),
162			z2.rb_mut().try_as_col_major_mut(),
163			rz2.try_as_col_major(),
164			u0.try_as_col_major(),
165			v2.try_as_col_major(),
166		) {
167			tridiag_fused_op_simd(
168				A, y2, z2, ry2, rz2, u0, u1, u2, v2, f, align,
169			);
170		} else {
171			tridiag_fused_op_fallback(A, y2, z2, ry2, rz2, u0, u1, u2, v2, f);
172		}
173	} else {
174		tridiag_fused_op_fallback(A, y2, z2, ry2, rz2, u0, u1, u2, v2, f);
175	}
176}
177fn tridiag_fused_op_fallback<T: ComplexField>(
178	A: MatMut<'_, T>,
179	y2: ColMut<'_, T>,
180	z2: ColMut<'_, T>,
181	ry2: ColRef<'_, T>,
182	rz2: ColRef<'_, T>,
183	u0: ColRef<'_, T>,
184	u1: ColRef<'_, T>,
185	u2: ColRef<'_, T>,
186	v2: ColRef<'_, T>,
187	f: T,
188) {
189	let mut par = Par::Seq;
190	let mut A = A;
191	let mut y2 = y2;
192	let n = A.ncols();
193	let (mut A0, mut A1) = A.rb_mut().split_at_row_mut(n);
194	let (u00, u01) = u0.split_at_row(n);
195	let (v20, v21) = v2.split_at_row(n);
196	let (mut z20, mut z21) = z2.split_at_row_mut(n);
197	let (rz20, rz21) = rz2.split_at_row(n);
198	matmul::triangular::matmul(
199		A0.rb_mut(),
200		BlockStructure::TriangularLower,
201		Accum::Add,
202		u00,
203		BlockStructure::Rectangular,
204		ry2.adjoint(),
205		BlockStructure::Rectangular,
206		-one::<T>(),
207		par.reborrow(),
208	);
209	matmul::triangular::matmul(
210		A0.rb_mut(),
211		BlockStructure::TriangularLower,
212		Accum::Add,
213		rz20,
214		BlockStructure::Rectangular,
215		u1.adjoint(),
216		BlockStructure::Rectangular,
217		-one::<T>(),
218		par.reborrow(),
219	);
220	matmul::matmul(
221		A1.rb_mut(),
222		Accum::Add,
223		u01,
224		ry2.adjoint(),
225		-one::<T>(),
226		par.reborrow(),
227	);
228	matmul::matmul(
229		A1.rb_mut(),
230		Accum::Add,
231		rz21,
232		u1.adjoint(),
233		-one::<T>(),
234		par.reborrow(),
235	);
236	matmul::triangular::matmul(
237		z20.rb_mut(),
238		BlockStructure::Rectangular,
239		Accum::Replace,
240		A0.rb(),
241		BlockStructure::TriangularLower,
242		u2,
243		BlockStructure::Rectangular,
244		f.clone(),
245		par.reborrow(),
246	);
247	matmul::triangular::matmul(
248		y2.rb_mut(),
249		BlockStructure::Rectangular,
250		Accum::Replace,
251		A0.rb().adjoint(),
252		BlockStructure::StrictTriangularUpper,
253		v20,
254		BlockStructure::Rectangular,
255		f.clone(),
256		par.reborrow(),
257	);
258	matmul::matmul(z21.rb_mut(), Accum::Replace, A1.rb(), u2, f.clone(), par.reborrow());
259	matmul::matmul(
260		y2.rb_mut(),
261		Accum::Add,
262		A1.rb().adjoint(),
263		v21,
264		f.clone(),
265		par,
266	);
267}
268/// computes a self-adjoint matrix $A$'s tridiagonalization such that $A = Q T
269/// Q^H$
270///
271/// $T$ is a self-adjoint tridiagonal matrix stored in $A$'s diagonal and
272/// subdiagonal
273///
274/// $Q$ is a sequence of householder reflections stored in the unit lower
275/// triangular half of $A$ (excluding the diagonal), with the householder
276/// coefficients being stored in `householder`
277pub fn tridiag_in_place<T: ComplexField>(
278	A: MatMut<'_, T>,
279	householder: MatMut<'_, T>,
280	par: Par,
281	stack: &mut MemStack,
282	params: Spec<TridiagParams, T>,
283) {
284	let params = params.config;
285	let mut A = A;
286	let mut H = householder;
287	let mut par = par;
288	let n = A.nrows();
289	let b = H.nrows();
290	assert!(H.ncols() == n.saturating_sub(1));
291	if n == 0 {
292		return;
293	}
294	alloca!('stack: {
295		let mut y = unsafe { col![uninit::<T>, n] };
296		let mut w = unsafe { col![uninit::<T>, n] };
297		let mut z = unsafe { mat![uninit::<T>, n, par.degree()] };
298	});
299	{
300		let mut H = H.rb_mut().row_mut(0);
301		for k in 0..n {
302			let (_, A01, A10, A11) = A.rb_mut().split_at_mut(k, k);
303			let (_, _) = A01.split_first_col().unwrap();
304			let (_, A20) = A10.split_first_row_mut().unwrap();
305			let (mut A11, _, A21, mut A22) = A11.split_at_mut(1, 1);
306			let mut A21 = A21.col_mut(0);
307			let a11 = &mut A11[(0, 0)];
308			let (y1, mut y2) =
309				y.rb_mut().split_at_row_mut(k).1.split_at_row_mut(1);
310			if k > 0 {
311				let y1 = y1[0].copy();
312				let p = k - 1;
313				let u2 = A20.rb().col(p);
314				*a11 -= &y1 + y1.conj();
315				z!(A21.rb_mut(), u2, y2.rb()).for_each(
316					|uz!(a, u, y): Zip!(&mut T, &T, &T)| {
317						*a -= y1.conj() * u + y;
318					},
319				);
320			}
321			if k + 1 == n {
322				break;
323			}
324			let rem = n - k - 1;
325			// One section per step: the symmetric fused update over column blocks; the unit is
326			// an element of the triangle.
327			if !params.par_threshold.region_pays::<T>(
328				par.shape(),
329				rem * rem / 2,
330				rate::tridiag(),
331				1,
332			) {
333				par = Par::Seq;
334			}
335			let k1 = k + 1;
336			let tau_inv;
337			{
338				let (mut a11, mut x2) = A21.rb_mut().split_at_row_mut(1);
339				let a11 = &mut a11[0];
340				let householder::HouseholderInfo { tau, .. } =
341					householder::make_householder_in_place(a11, x2.rb_mut());
342				tau_inv = tau.real().recip();
343				H[k] = tau.to_cplx();
344				let mut z2 = z.rb_mut().split_at_row_mut(k + 2).1;
345				let mut w2 = w.rb_mut().split_at_row_mut(k + 2).1;
346				let (mut y1, mut y2) = y2.rb_mut().split_at_row_mut(1);
347				let y1 = &mut y1[0];
348				let (A1, A2) = A22.rb_mut().split_at_row_mut(1);
349				let A1 = A1.row_mut(0);
350				let (mut a11, _) = A1.split_at_col_mut(1);
351				let a11 = &mut a11[0];
352				let (A21, mut A22) = A2.split_at_col_mut(1);
353				let mut A21 = A21.col_mut(0);
354				if k > 0 {
355					let p = k - 1;
356					let (u1, u2) = A20.rb().col(p).split_at_row(1);
357					let ref u1 = u1[0].copy();
358					*a11 -= u1 * y1.conj() + &*y1 * u1.conj();
359					z!(A21.rb_mut(), u2.rb(), y2.rb()).for_each(
360						|uz!(a, u, y): Zip!(&mut T, &T, &T)| {
361							*a -= u * y1.conj() + &*y * u1.conj();
362						},
363					);
364					w2.copy_from(y2.rb());
365					match par {
366						Par::Seq => {
367							let mut z2 = z2.rb_mut().col_mut(0);
368							tridiag_fused_op(
369								A22.rb_mut(),
370								y2.rb_mut(),
371								z2.rb_mut(),
372								w2.rb(),
373								w2.rb(),
374								u2.rb(),
375								u2.rb(),
376								x2.rb(),
377								x2.rb(),
378								tau_inv.to_cplx(),
379								simd_align(k1 + 1),
380							);
381							z!(y2.rb_mut(), z2.rb()).for_each(
382								|uz!(y, z): Zip!(&mut T, &T)| *y += z,
383							);
384						},
385						#[cfg(feature = "rayon")]
386						_ => {
387						let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
388							use rayon::prelude::*;
389							let nthreads = nthreads.get();
390							let mut z2 = z2.rb_mut().subcols_mut(0, nthreads);
391							let n2 = A22.ncols();
392							assert!((n2 as u64) < (1u64 << 50));
393							let idx_to_col_start = |idx: usize| {
394								let idx_as_percent =
395									idx as f64 / nthreads as f64;
396								let col_start_percent = 1.0f64
397									- libm::sqrt(1.0f64 - idx_as_percent);
398								(col_start_percent * n2 as f64) as usize
399							};
400							{
401								let A22 = A22.rb();
402								let y2 = y2.rb();
403								let ref f = tau_inv.to_cplx::<T>();
404								crate::utils::thread::for_each(par.reborrow(), 
405									nthreads,
406									z2.rb_mut().par_col_iter_mut().enumerate(),
407									|(idx, mut z2)| {
408										let first = idx_to_col_start(idx);
409										let last_col =
410											idx_to_col_start(idx + 1);
411										let nrows = n2 - first;
412										let ncols = last_col - first;
413										let mut A = unsafe {
414											A22.rb()
415												.subcols(first, ncols)
416												.subrows(first, nrows)
417												.const_cast()
418										};
419										{
420											let y2 = unsafe {
421												y2.subrows(first, ncols)
422													.const_cast()
423											};
424											let mut z2 = z2
425												.rb_mut()
426												.subrows_mut(first, nrows);
427											let ry2 =
428												w2.rb().subrows(first, ncols);
429											let rz2 =
430												w2.rb().subrows(first, nrows);
431											let u0 = u2.subrows(first, nrows);
432											let u1 = u2.subrows(first, ncols);
433											let u2 =
434												x2.rb().subrows(first, ncols);
435											let v2 =
436												x2.rb().subrows(first, nrows);
437											tridiag_fused_op(
438												A.rb_mut(),
439												y2,
440												z2.rb_mut(),
441												ry2,
442												rz2,
443												u0,
444												u1,
445												u2,
446												v2,
447												f.copy(),
448												n.next_power_of_two()
449													- (k1 + 1) - first,
450											);
451										}
452										z2.rb_mut()
453											.subrows_mut(0, first)
454											.fill(zero());
455									},
456								);
457							}
458							for z2 in z2.rb().col_iter() {
459								z!(y2.rb_mut(), z2.rb())
460									.for_each(|uz!(y, z)| *y += z);
461							}
462						},
463					}
464				} else {
465					matmul::triangular::matmul(
466						y2.rb_mut(),
467						BlockStructure::Rectangular,
468						Accum::Replace,
469						A22.rb(),
470						BlockStructure::TriangularLower,
471						x2.rb(),
472						BlockStructure::Rectangular,
473						tau_inv.to_cplx(),
474						par.reborrow(),
475					);
476					matmul::triangular::matmul(
477						y2.rb_mut(),
478						BlockStructure::Rectangular,
479						Accum::Add,
480						A22.rb().adjoint(),
481						BlockStructure::StrictTriangularUpper,
482						x2.rb(),
483						BlockStructure::Rectangular,
484						tau_inv.to_cplx(),
485						par.reborrow(),
486					);
487				}
488				z!(y2.rb_mut(), A21.rb()).for_each(
489					|uz!(y, a): Zip!(&mut T, &T)| {
490						*y += a.mul_real(&tau_inv);
491					},
492				);
493				*y1 = (&*a11
494					+ dot::inner_prod(
495						A21.rb().transpose(),
496						Conj::Yes,
497						x2.rb(),
498						Conj::No,
499					))
500				.mul_real(&tau_inv);
501				let ref b = (&*y1
502					+ dot::inner_prod(
503						x2.rb().transpose(),
504						Conj::Yes,
505						y2.rb(),
506						Conj::No,
507					))
508				.mul_pow2(from_f64::<T::Real>(0.5))
509				.mul_real(&tau_inv);
510				*y1 -= b;
511				z!(y2.rb_mut(), x2.rb()).for_each(
512					|uz!(y, u): Zip!(&mut T, &T)| {
513						*y -= b * u;
514					},
515				);
516			}
517		}
518	}
519	if n > 0 {
520		let n = n - 1;
521		let A = A.rb().submatrix(1, 0, n, n);
522		let mut H = H.rb_mut().subcols_mut(0, n);
523		let mut j = 0;
524		while j < n {
525			let b = Ord::min(b, n - j);
526			let mut H = H.rb_mut().submatrix_mut(0, j, b, b);
527			for k in 0..b {
528				H[(k, k)] = H[(0, k)].copy();
529			}
530			householder::upgrade_householder_factor(
531				H.rb_mut(),
532				A.submatrix(j, j, n - j, b),
533				b,
534				1,
535				par.reborrow(),
536			);
537			j += b;
538		}
539	}
540}
541#[cfg(test)]
542mod tests {
543	use super::*;
544	use crate::stats::prelude::*;
545	use crate::utils::approx::*;
546	use crate::{Mat, assert, c64};
547	use dyn_stack::MemBuffer;
548	#[test]
549	fn test_tridiag_real() {
550		let rng = &mut StdRng::seed_from_u64(0);
551		for n in [2, 3, 4, 8, 16] {
552			let A = CwiseMatDistribution {
553				nrows: n,
554				ncols: n,
555				dist: StandardNormal,
556			}
557			.rand::<Mat<f64>>(rng);
558			let A = A.rb() + A.adjoint();
559			let b = 3;
560			let mut H = Mat::zeros(b, n - 1);
561			let mut V = A.clone();
562			let mut V = V.as_mut();
563			tridiag_in_place(
564				V.rb_mut(),
565				H.rb_mut(),
566				Par::Seq,
567				MemStack::new(&mut MemBuffer::new(StackReq::all_of(&[
568					householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<f64>(n - 1, b, n),
569					tridiag_in_place_scratch::<f64>(n, ParShape::Seq, default()),
570				]))),
571				default(),
572			);
573			let mut A = A.clone();
574			let mut A = A.as_mut();
575			for iter in 0..2 {
576				let mut A = if iter == 0 {
577					A.rb_mut()
578				} else {
579					A.rb_mut().transpose_mut()
580				};
581				let n = n - 1;
582				let V = V.rb().submatrix(1, 0, n, n);
583				let mut A = A.rb_mut().subrows_mut(1, n);
584				let H = H.as_ref();
585				householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
586					V,
587					H.as_ref(),
588					if iter == 0 { Conj::Yes } else { Conj::No },
589					A.rb_mut(),
590					Par::Seq,
591					MemStack::new(&mut MemBuffer::new(
592						householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<f64>(n, b, n + 1),
593					)),
594				);
595			}
596			let approx_eq = CwiseMat(ApproxEq::<f64>::eps());
597			for j in 0..n {
598				for i in 0..n {
599					if i > j + 1 || j > i + 1 {
600						V[(i, j)] = 0.0;
601					}
602				}
603			}
604			for i in 0..n {
605				if i + 1 < n {
606					V[(i, i + 1)] = V[(i + 1, i)];
607				}
608			}
609			assert!(V ~ A);
610		}
611	}
612	#[test]
613	fn test_tridiag_cplx() {
614		let rng = &mut StdRng::seed_from_u64(0);
615		for n in [2, 3, 4, 8, 16] {
616			let A = CwiseMatDistribution {
617				nrows: n,
618				ncols: n,
619				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
620			}
621			.rand::<Mat<c64>>(rng);
622			let A = A.rb() + A.adjoint();
623			let b = 3;
624			let mut H = Mat::zeros(b, n - 1);
625			let mut V = A.clone();
626			let mut V = V.as_mut();
627			tridiag_in_place(
628				V.rb_mut(),
629				H.as_mut(),
630				Par::Seq,
631				MemStack::new(&mut MemBuffer::new(tridiag_in_place_scratch::<
632					c64,
633				>(
634					n, ParShape::Seq, default()
635				))),
636				default(),
637			);
638			let mut A = A.clone();
639			let mut A = A.as_mut();
640			for iter in 0..2 {
641				let mut A = if iter == 0 {
642					A.rb_mut()
643				} else {
644					A.rb_mut().transpose_mut()
645				};
646				let n = n - 1;
647				let V = V.rb().submatrix(1, 0, n, n);
648				let mut A = A.rb_mut().subrows_mut(1, n);
649				let H = H.as_ref();
650				householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
651					V,
652					H.as_ref(),
653					if iter == 0 { Conj::Yes } else { Conj::No },
654					A.rb_mut(),
655					Par::Seq,
656					MemStack::new(&mut MemBuffer::new(
657						householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<c64>(n, b, n + 1),
658					)),
659				);
660			}
661			let approx_eq = CwiseMat(ApproxEq::eps());
662			for j in 0..n {
663				for i in 0..n {
664					if i > j + 1 || j > i + 1 {
665						V[(i, j)] = c64::ZERO;
666					}
667				}
668			}
669			for i in 0..n {
670				if i + 1 < n {
671					V[(i, i + 1)] = V[(i + 1, i)].conj();
672				}
673			}
674			assert!(V ~ A);
675		}
676	}
677}