Skip to main content

faer/linalg/svd/
bidiag.rs

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