Skip to main content

faer/linalg/svd/
bidiag.rs

1use crate::assert;
2use crate::internal_prelude::*;
3use linalg::householder::*;
4use crate::utils::cost::{ParThreshold, rate};
5use linalg::matmul::{dot, matmul};
6/// computes the layout of the workspace required to compute a matrix's
7/// bidiagonalization
8pub fn bidiag_in_place_scratch<T: ComplexField>(
9	nrows: usize,
10	ncols: usize,
11	par: ParShape,
12	params: Spec<BidiagParams, T>,
13) -> StackReq {
14	_ = par;
15	_ = params;
16	StackReq::all_of(&[
17		temp_mat_scratch::<T>(nrows, 1),
18		temp_mat_scratch::<T>(ncols, 1),
19	])
20}
21/// bidiagonalization tuning parameters.
22#[derive(Debug, Copy, Clone)]
23pub struct BidiagParams {
24	/// floor of the parallel regions: [`ParThreshold::Derived`] (the default) runs a step in
25	/// parallel when its serial time pays for its two sections; [`ParThreshold::Fixed`] counts
26	/// elements of the trailing block `(m-k-1)*(n-k-1)`
27	pub par_threshold: ParThreshold,
28	#[doc(hidden)]
29	pub non_exhaustive: NonExhaustive,
30}
31impl<T: ComplexField> Auto<T> for BidiagParams {
32	fn auto() -> Self {
33		Self {
34			par_threshold: ParThreshold::Derived,
35			non_exhaustive: NonExhaustive(()),
36		}
37	}
38}
39/// computes a matrix $A$'s bidiagonalization such that $A = U B V^H$
40///
41/// $B$ is a bidiagonal matrix stored in $A$'s diagonal and superdiagonal
42///
43/// $U$ is a sequence of householder reflections stored in the unit lower
44/// triangular half of $A$, with the householder coefficients being stored in
45/// `H_left`
46///
47/// $V$ is a sequence of householder reflections stored in the unit upper
48/// triangular half of $A$ (excluding the diagonal), with the householder
49/// coefficients being stored in `H_right`
50pub fn bidiag_in_place<T: ComplexField>(
51	A: MatMut<'_, T>,
52	H_left: MatMut<'_, T>,
53	H_right: MatMut<'_, T>,
54	par: Par,
55	stack: &mut MemStack,
56	params: Spec<BidiagParams, T>,
57) {
58	let params = params.config;
59	let m = A.nrows();
60	let n = A.ncols();
61	let size = Ord::min(m, n);
62	let bl = H_left.nrows();
63	let br = H_right.nrows();
64	assert!(H_left.ncols() == size);
65	assert!(H_right.ncols() == size.saturating_sub(1));
66	alloca!('stack: {
67		let mut y = unsafe { row![uninit::<T>, n] };
68		let mut z = unsafe { col![uninit::<T>, m] };
69	});
70	let mut A = A;
71	let mut Hl = H_left;
72	let mut Hr = H_right;
73	let mut par = par;
74	{
75		let mut Hl = Hl.rb_mut().row_mut(0);
76		let mut Hr = Hr.rb_mut().row_mut(0);
77		for k in 0..size {
78			let mut A = A.rb_mut();
79			let (_, A01, A10, A11) = A.rb_mut().split_at_mut(k, k);
80			let (_, A02) = A01.split_first_col().unwrap();
81			let (A10, A20) = A10.split_first_row_mut().unwrap();
82			let (mut A11, A12, A21, mut A22) = A11.split_at_mut(1, 1);
83			let mut A12 = A12.row_mut(0);
84			let mut A21 = A21.col_mut(0);
85			let a11 = &mut A11[(0, 0)];
86			let (y1, mut y2) =
87				y.rb_mut().split_at_col_mut(k).1.split_at_col_mut(1);
88			let (z1, mut z2) =
89				z.rb_mut().split_at_row_mut(k).1.split_at_row_mut(1);
90			if k > 0 {
91				let ref y1 = y1[0].copy();
92				let ref z1 = z1[0].copy();
93				let k1 = k - 1;
94				let ref up0 = A10[k1];
95				let up = A20.rb().col(k1);
96				let vp = A02.rb().row(k1);
97				*a11 -= up0 * y1 + z1;
98				z!(A21.rb_mut(), up.rb(), z2.rb()).for_each(
99					|uz!(a, u, z): Zip!(&mut T, &T, &T)| *a -= u * y1 + z,
100				);
101				z!(A12.rb_mut(), y2.rb(), vp.rb()).for_each(
102					|uz!(a, y, v): Zip!(&mut T, &T, &T)| {
103						*a -= up0 * y + z1 * v;
104					},
105				);
106			}
107			let HouseholderInfo { tau: tl, .. } =
108				make_householder_in_place(a11, A21.rb_mut());
109			let ref tl_inv = tl.recip();
110			Hl[k] = tl.to_cplx();
111			// Two sections per step: the fused two-sided update (the first step: the product
112			// `y2`) and the product `z2`; the unit is an element of the trailing block.
113			if !params.par_threshold.region_pays::<T>(
114				par.shape(),
115				(m - k - 1) * (n - k - 1),
116				rate::bidiag(),
117				2,
118			) {
119				par = Par::Seq;
120			}
121			if k > 0 {
122				let k1 = k - 1;
123				let up = A20.rb().col(k1);
124				let vp = A02.row(k1);
125				match par {
126					Par::Seq => bidiag_fused_op(
127						A22.rb_mut(),
128						A21.rb(),
129						up.rb(),
130						z2.rb(),
131						y2.rb_mut(),
132						vp.rb(),
133						simd_align(k + 1),
134					),
135					#[cfg(feature = "rayon")]
136					_ => {
137					let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
138						use rayon::prelude::*;
139						let nthreads = nthreads.get();
140						crate::utils::thread::for_each(par.reborrow(), 
141							nthreads,
142							A22.rb_mut()
143								.par_col_partition_mut(nthreads)
144								.zip_eq(y2.rb_mut().par_partition_mut(nthreads))
145								.zip_eq(vp.par_partition(nthreads)),
146							|((A22, y2), vp)| {
147								bidiag_fused_op(
148									A22,
149									A21.rb(),
150									up.rb(),
151									z2.rb(),
152									y2,
153									vp.rb(),
154									simd_align(k + 1),
155								);
156							},
157						);
158					},
159				}
160			} else {
161				matmul(
162					y2.rb_mut(),
163					Accum::Replace,
164					A21.rb().adjoint(),
165					A22.rb(),
166					one(),
167					par.reborrow(),
168				);
169			}
170			z!(y2.rb_mut(), A12.rb_mut()).for_each(|uz!(y, a)| {
171				*y = (&*y + &*a).mul_real(tl_inv);
172				*a -= &*y;
173			});
174			let norm = A12.rb().norm_l2();
175			if norm != zero() {
176				// a subnormal row norm has an infinite reciprocal, so rescale the row
177				// first: reference `dlarfg` does the same when its normaliser underflows
178				let ref norm_inv = if norm >= min_positive() {
179					(&norm).recip()
180				} else {
181					let ref shift = from_f64::<T::Real>(4503599627370496.0);
182					z!(A12.rb_mut()).for_each(|uz!(a)| *a = a.mul_real(shift));
183					(&norm * shift).recip()
184				};
185				z!(A12.rb_mut()).for_each(|uz!(a)| *a = a.mul_real(norm_inv));
186			}
187			matmul(
188				z2.rb_mut(),
189				Accum::Replace,
190				A22.rb(),
191				A12.rb().adjoint(),
192				one(),
193				par.reborrow(),
194			);
195			if k + 1 == size {
196				break;
197			}
198			let (mut A12_a, mut A12_b) = A12.rb_mut().split_at_col_mut(1);
199			let A22_a = A22.rb().col(0);
200			let (y2_a, y2_b) = y2.rb().split_at_col(1);
201			let y2_a = &y2_a[0];
202			let a12_a = &mut A12_a[0];
203			let HouseholderInfo {
204				tau: tr,
205				head_with_beta_inv: m,
206				..
207			} = make_householder_in_place(a12_a, A12_b.rb_mut().transpose_mut());
208			let ref tr_inv = tr.recip();
209			Hr[k] = tr.to_cplx();
210			let beta = a12_a.copy();
211			*a12_a = a12_a.mul_real(norm);
212			let ref b = y2_a
213				+ dot::inner_prod(
214					y2_b,
215					Conj::No,
216					A12_b.rb().transpose(),
217					Conj::Yes,
218				);
219			if m != infinity() {
220				z!(z2.rb_mut(), A21.rb(), A22_a.rb()).for_each(
221					|uz!(z, u, a): Zip!(&mut T, &T, &T)| {
222						let w = &*z - a * beta.conj();
223						let w = w * m.conj();
224						let w = w - u * b;
225						*z = w.mul_real(tr_inv);
226					},
227				);
228			} else {
229				z!(z2.rb_mut(), A21.rb(), A22_a.rb()).for_each(
230					|uz!(z, u, a): Zip!(&mut T, &T, &T)| {
231						let w = a - u * b;
232						*z = w.mul_real(tr_inv);
233					},
234				);
235			}
236		}
237	}
238	let mut j = 0;
239	while j < size {
240		let bl = Ord::min(bl, size - j);
241		let mut Hl = Hl.rb_mut().get_mut(..bl, j..j + bl);
242		for k in 0..bl {
243			Hl[(k, k)] = Hl[(0, k)].copy();
244		}
245		upgrade_householder_factor(
246			Hl.rb_mut(),
247			A.rb().get(j.., j..j + bl),
248			bl,
249			1,
250			par.reborrow(),
251		);
252		j += bl;
253	}
254	if size > 0 {
255		let size = size - 1;
256		let A = A.rb().get(..size, 1..);
257		let mut Hr = Hr.rb_mut().get_mut(.., ..size);
258		let mut j = 0;
259		while j < size {
260			let br = Ord::min(br, size - j);
261			let mut Hr = Hr.rb_mut().get_mut(..br, j..j + br);
262			for k in 0..br {
263				Hr[(k, k)] = Hr[(0, k)].copy();
264			}
265			upgrade_householder_factor(
266				Hr.rb_mut(),
267				A.transpose().get(j.., j..j + br),
268				br,
269				1,
270				par.reborrow(),
271			);
272			j += br;
273		}
274	}
275}
276fn bidiag_fused_op<T: ComplexField>(
277	A22: MatMut<'_, T>,
278	u: ColRef<'_, T>,
279	up: ColRef<'_, T>,
280	z: ColRef<'_, T>,
281	y: RowMut<'_, T>,
282	vp: RowRef<'_, T>,
283	align: usize,
284) {
285	let mut A22 = A22;
286	if const { T::SIMD_CAPABILITIES.is_simd() } {
287		if let (Some(A22), Some(u), Some(up), Some(z)) = (
288			A22.rb_mut().try_as_col_major_mut(),
289			u.try_as_col_major(),
290			up.try_as_col_major(),
291			z.try_as_col_major(),
292		) {
293			bidiag_fused_op_simd(A22, u, up, z, y, vp, align);
294		} else {
295			bidiag_fused_op_fallback(A22, u, up, z, y, vp);
296		}
297	} else {
298		bidiag_fused_op_fallback(A22, u, up, z, y, vp);
299	}
300}
301fn bidiag_fused_op_fallback<T: ComplexField>(
302	A22: MatMut<'_, T>,
303	u: ColRef<'_, T>,
304	up: ColRef<'_, T>,
305	z: ColRef<'_, T>,
306	y: RowMut<'_, T>,
307	vp: RowRef<'_, T>,
308) {
309	let mut A22 = A22;
310	let mut y = y;
311	matmul(A22.rb_mut(), Accum::Add, up, y.rb(), -one::<T>(), Par::Seq);
312	matmul(A22.rb_mut(), Accum::Add, z, vp, -one::<T>(), Par::Seq);
313	matmul(
314		y.rb_mut(),
315		Accum::Replace,
316		u.adjoint(),
317		A22.rb(),
318		one(),
319		Par::Seq,
320	);
321}
322fn bidiag_fused_op_simd<'M, 'N, T: ComplexField>(
323	A22: MatMut<'_, T, usize, usize, ContiguousFwd>,
324	u: ColRef<'_, T, usize, ContiguousFwd>,
325	up: ColRef<'_, T, usize, ContiguousFwd>,
326	z: ColRef<'_, T, usize, ContiguousFwd>,
327	y: RowMut<'_, T, usize>,
328	vp: RowRef<'_, T, usize>,
329	align: usize,
330) {
331	struct Impl<'a, 'M, 'N, T: ComplexField> {
332		A22: MatMut<'a, T, Dim<'M>, Dim<'N>, ContiguousFwd>,
333		u: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
334		up: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
335		z: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
336		y: RowMut<'a, T, Dim<'N>>,
337		vp: RowRef<'a, T, Dim<'N>>,
338		align: usize,
339	}
340	impl<'a, 'M, 'N, T: ComplexField> pulp::WithSimd for Impl<'a, 'M, 'N, T> {
341		type Output = ();
342
343		#[inline(always)]
344		fn with_simd<S: pulp::Simd>(self, simd: S) -> Self::Output {
345			let Self {
346				mut A22,
347				u,
348				up,
349				z,
350				mut y,
351				vp,
352				align,
353			} = self;
354			let m = A22.nrows();
355			let n = A22.ncols();
356			let simd = SimdCtx::<T, S>::new_align(T::simd_ctx(simd), m, align);
357			let indices = simd.batch_indices::<4>();
358			for j in n.indices() {
359				let mut A = A22.rb_mut().col_mut(j);
360				let mut acc = [simd.zero(); 4];
361				let yj = simd.splat(-&y[j]);
362				let vj = simd.splat(-&vp[j]);
363				simd_iter!(for (IDX, i) in [indices; 4] {
364					let mut a = simd.read(A.rb(), i);
365					a = simd.mul_add(simd.read(up, i), yj, a);
366					a = simd.mul_add(simd.read(z, i), vj, a);
367					simd.write(A.rb_mut(), i, a);
368					acc[IDX] = simd.conj_mul_add(simd.read(u, i), a, acc[IDX]);
369				});
370				let acc0 = simd.add(acc[0], acc[1]);
371				let acc2 = simd.add(acc[2], acc[3]);
372				let acc0 = simd.add(acc0, acc2);
373				y[j] = simd.reduce_sum(acc0);
374			}
375		}
376	}
377	with_dim!(M, A22.nrows());
378	with_dim!(N, A22.ncols());
379	dispatch!(
380		Impl {
381			A22: A22.as_shape_mut(M, N),
382			u: u.as_row_shape(M),
383			up: up.as_row_shape(M),
384			z: z.as_row_shape(M),
385			y: y.as_col_shape_mut(N),
386			vp: vp.as_col_shape(N),
387			align,
388		},
389		Impl,
390		T
391	)
392}
393#[cfg(test)]
394mod tests {
395	use super::*;
396	use crate::stats::prelude::*;
397	use crate::utils::approx::*;
398	use crate::{Mat, assert, c64};
399	use dyn_stack::MemBuffer;
400	use std::mem::MaybeUninit;
401	#[test]
402	fn test_bidiag_real() {
403		let rng = &mut StdRng::seed_from_u64(0);
404		for (m, n) in [(8, 4), (8, 8)] {
405			let size = Ord::min(m, n);
406			let A = CwiseMatDistribution {
407				nrows: m,
408				ncols: n,
409				dist: StandardNormal,
410			}
411			.rand::<Mat<f64>>(rng);
412			let bl = 4;
413			let br = 3;
414			let mut Hl = Mat::zeros(bl, size);
415			let mut Hr = Mat::zeros(br, size - 1);
416			let mut UV = A.clone();
417			bidiag_in_place(
418				UV.rb_mut(),
419				Hl.rb_mut(),
420				Hr.rb_mut(),
421				Par::Seq,
422				MemStack::new(&mut [MaybeUninit::uninit(); 1024]),
423				default(),
424			);
425			let mut A = A.clone();
426			let mut A = A.as_mut();
427			apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
428				UV.rb().get(.., ..size),
429				Hl.rb(),
430				Conj::Yes,
431				A.rb_mut(),
432				Par::Seq,
433				MemStack::new(&mut MemBuffer::new(
434					apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<f64>(n - 1, 1, m),
435				)),
436			);
437			let V = UV.rb().get(..size - 1, 1..size);
438			let A1 = A.rb_mut().get_mut(.., 1..size);
439			let Hr = Hr.as_ref();
440			apply_block_householder_sequence_on_the_right_in_place_with_conj(
441				V.transpose(),
442				Hr.as_ref(),
443				Conj::Yes,
444				A1,
445				Par::Seq,
446				MemStack::new(&mut MemBuffer::new(
447					apply_block_householder_sequence_on_the_right_in_place_scratch::<f64>(n - 1, 1, m),
448				)),
449			);
450			let approx_eq = CwiseMat(ApproxEq::<f64>::eps());
451			for j in 0..n {
452				for i in 0..m {
453					if i > j || j > i + 1 {
454						UV[(i, j)] = 0.0;
455					}
456				}
457			}
458			assert!(UV ~ A);
459		}
460	}
461	#[test]
462	fn test_bidiag_cplx() {
463		let rng = &mut StdRng::seed_from_u64(0);
464		for (m, n) in [(8, 4), (8, 8)] {
465			let size = Ord::min(m, n);
466			let A = CwiseMatDistribution {
467				nrows: m,
468				ncols: n,
469				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
470			}
471			.rand::<Mat<c64>>(rng);
472			let bl = 4;
473			let br = 3;
474			let mut Hl = Mat::zeros(bl, size);
475			let mut Hr = Mat::zeros(br, size - 1);
476			let mut UV = A.clone();
477			let mut UV = UV.as_mut();
478			bidiag_in_place(
479				UV.rb_mut(),
480				Hl.rb_mut(),
481				Hr.rb_mut(),
482				Par::Seq,
483				MemStack::new(&mut [MaybeUninit::uninit(); 1024]),
484				default(),
485			);
486			let mut A = A.clone();
487			let mut A = A.as_mut();
488			apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
489				UV.rb().subcols(0, size),
490				Hl.rb(),
491				Conj::Yes,
492				A.rb_mut(),
493				Par::Seq,
494				MemStack::new(&mut MemBuffer::new(
495					apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<c64>(n - 1, 1, m),
496				)),
497			);
498			let V = UV.rb().get(..size - 1, 1..size);
499			let A1 = A.rb_mut().get_mut(.., 1..size);
500			let Hr = Hr.rb();
501			apply_block_householder_sequence_on_the_right_in_place_with_conj(
502				V.transpose(),
503				Hr,
504				Conj::Yes,
505				A1,
506				Par::Seq,
507				MemStack::new(&mut MemBuffer::new(
508					apply_block_householder_sequence_on_the_right_in_place_scratch::<c64>(n - 1, 1, m),
509				)),
510			);
511			let approx_eq = CwiseMat(ApproxEq::eps());
512			for j in 0..n {
513				for i in 0..m {
514					if i > j || j > i + 1 {
515						UV[(i, j)] = c64::ZERO;
516					}
517				}
518			}
519			assert!(UV ~ A);
520		}
521	}
522}