Skip to main content

faer/linalg/qr/col_pivoting/
factor.rs

1pub use super::super::no_pivoting::factor::recommended_block_size;
2use crate::assert;
3use crate::internal_prelude::*;
4use crate::perm::swap_cols_idx;
5use linalg::householder::{self, HouseholderInfo};
6use pulp::Simd;
7fn update_mat_and_dot_simd<T: ComplexField>(
8	norm: RowMut<'_, T>,
9	dot: RowMut<'_, T>,
10	B01: RowMut<'_, T>,
11	B11: MatMut<'_, T, usize, usize, ContiguousFwd>,
12	A10: ColRef<'_, T, usize, ContiguousFwd>,
13	B10: ColRef<'_, T, usize, ContiguousFwd>,
14	l: T,
15	tau_inv: T::Real,
16	align: usize,
17) {
18	struct Impl<'a, 'M, 'N, T: ComplexField> {
19		norm: RowMut<'a, T, Dim<'N>>,
20		dot: RowMut<'a, T, Dim<'N>>,
21		B01: RowMut<'a, T, Dim<'N>>,
22		B11: MatMut<'a, T, Dim<'M>, Dim<'N>, ContiguousFwd>,
23		A10: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
24		B10: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
25		l: T,
26		tau_inv: T::Real,
27		align: usize,
28	}
29	impl<'M, 'N, T: ComplexField> pulp::WithSimd for Impl<'_, 'M, 'N, T> {
30		type Output = ();
31
32		#[inline(always)]
33		fn with_simd<S: Simd>(self, simd: S) -> Self::Output {
34			let Self {
35				mut norm,
36				mut dot,
37				B01: mut u,
38				mut B11,
39				A10,
40				B10,
41				l,
42				ref tau_inv,
43				align,
44			} = self;
45			let m = B11.nrows();
46			let n = B11.ncols();
47			let simd =
48				SimdCtx::<'_, T, S>::new_align(T::simd_ctx(simd), m, align);
49			let indices4 = simd.batch_indices::<4>();
50
51			for j in n.indices() {
52				let b0 = dot[j].copy();
53				let rhs0 = simd.splat(&b0);
54				let mut acc = [simd.zero(); 4];
55				simd_iter!(for (IDX, i) in [indices4; 4] {
56					let lhs0 = simd.read(A10, i);
57					let lhs1 = simd.read(B10, i);
58					let mut dst0 = simd.read(B11.rb().col(j), i);
59					dst0 = simd.mul_add(lhs0, rhs0, dst0);
60					acc[IDX] = simd.conj_mul_add(lhs1, dst0, acc[IDX]);
61					simd.write(B11.rb_mut().col_mut(j), i, dst0);
62				});
63				let acc = simd
64					.add(simd.add(acc[0], acc[2]), simd.add(acc[1], acc[3]));
65				let tmp = &u[j] + &l * b0;
66				let d0 = (&tmp + simd.reduce_sum(acc)).mul_real(-tau_inv);
67				u[j] = tmp + &d0;
68				dot[j] = d0;
69				norm[j] = (norm[j].abs2() - u[j].abs2()).sqrt().to_cplx();
70			}
71		}
72	}
73	with_dim!(M, B11.nrows());
74	with_dim!(N, B11.ncols());
75	dispatch!(
76		Impl {
77			norm: norm.as_col_shape_mut(N),
78			dot: dot.as_col_shape_mut(N),
79			B01: B01.as_col_shape_mut(N),
80			B11: B11.as_shape_mut(M, N),
81			A10: A10.as_row_shape(M),
82			B10: B10.as_row_shape(M),
83			l,
84			tau_inv,
85			align
86		},
87		Impl,
88		T
89	)
90}
91/// $QR$ factorization with column pivoting tuning parameters
92#[derive(Copy, Clone, Debug)]
93pub struct ColPivQrParams {
94	/// threshold at which blocking algorithms should be disabled
95	pub blocking_threshold: usize,
96	/// threshold at which the parallelism should be disabled
97	pub par_threshold: usize,
98	#[doc(hidden)]
99	pub non_exhaustive: NonExhaustive,
100}
101impl<T: ComplexField> Auto<T> for ColPivQrParams {
102	#[inline]
103	fn auto() -> Self {
104		Self {
105			blocking_threshold: 48 * 48,
106			par_threshold: 192 * 256,
107			non_exhaustive: NonExhaustive(()),
108		}
109	}
110}
111#[track_caller]
112fn qr_in_place_unblocked<'out, I: Index, T: ComplexField>(
113	A: MatMut<'_, T>,
114	H: RowMut<'_, T>,
115	col_perm: &'out mut [I],
116	col_perm_inv: &'out mut [I],
117	par: Par,
118	stack: &mut MemStack,
119	params: Spec<ColPivQrParams, T>,
120) -> (ColPivQrInfo, PermRef<'out, I>) {
121	let m = A.nrows();
122	let n = A.ncols();
123	let size = H.ncols();
124	let params = params.config;
125	let mut A = A;
126	let mut H = H;
127	let mut par = par;
128	assert!(size == Ord::min(m, n));
129	for j in 0..n {
130		col_perm[j] = I::truncate(j);
131	}
132	let mut n_trans = 0;
133	'main: {
134		if size == 0 {
135			break 'main;
136		}
137		let (mut dot, stack) = temp_mat_zeroed::<T, _, _>(n, 1, stack);
138		let (mut norm, stack) = temp_mat_zeroed::<T, _, _>(n, 1, stack);
139		let _ = stack;
140		let mut dot = dot.as_mat_mut().col_mut(0).transpose_mut();
141		let mut norm = norm.as_mat_mut().col_mut(0).transpose_mut();
142		let mut best = zero();
143		let ref threshold = eps::<T::Real>().sqrt();
144		for j in 0..n {
145			let val = A.rb().col(j).norm_l2();
146			norm[j] = val.to_cplx();
147			if val > best {
148				best = val;
149			}
150		}
151		let ref scale_fwd = best.copy();
152		let ref scale_bwd = best.recip();
153		zip!(A.rb_mut()).for_each(|unzip!(a)| *a = a.mul_real(scale_bwd));
154		for j in 0..n {
155			norm[j] = (norm[j].real() * scale_bwd).to_cplx();
156		}
157		best = best * scale_bwd;
158		let mut best_threshold = best * threshold;
159		'unscale: {
160			for k in 0..size {
161				let mut new_best = zero::<T::Real>();
162				let mut best_col = k;
163				for j in k..n {
164					let val = norm[j].real();
165					if val > new_best {
166						new_best = val;
167						best_col = j;
168					}
169				}
170				let delayed_update = T::SIMD_CAPABILITIES.is_simd()
171					&& A.row_stride() == 1
172					&& k > 0 && new_best >= best_threshold;
173				if k > 0 && !delayed_update {
174					let (_, _, A10, mut A11) = A.rb_mut().split_at_mut(k, k);
175					let dot = dot.rb().get(k..);
176					let A10 = A10.rb().col(k - 1);
177					linalg::matmul::matmul(
178						A11.rb_mut(),
179						Accum::Add,
180						A10,
181						dot,
182						one(),
183						par,
184					);
185					best = zero();
186					for j in k..n {
187						let val = A11.rb().col(j - k).norm_l2();
188						norm[j] = val.to_cplx();
189						if val > best {
190							best = val;
191							best_col = j;
192						}
193					}
194					best_threshold = best * threshold;
195				}
196				if best_col != k {
197					n_trans += 1;
198					col_perm.as_mut().swap(best_col, k);
199					swap_cols_idx(A.rb_mut(), best_col, k);
200					swap_cols_idx(dot.rb_mut().as_mat_mut(), best_col, k);
201					swap_cols_idx(norm.rb_mut().as_mat_mut(), best_col, k);
202				}
203				let (_, _, A10, mut A11) = A.rb_mut().split_at_mut(k, k);
204				let A10 = A10.rb();
205				let dot0 = dot.rb_mut().get_mut(k..);
206				let (mut B00, B01, B10, mut B11) =
207					A11.rb_mut().split_at_mut(1, 1);
208				let B00 = &mut B00[(0, 0)];
209				let mut B01 = B01.row_mut(0);
210				let mut B10 = B10.col_mut(0);
211				let l = if delayed_update {
212					let A10 = A10.col(k - 1);
213					A10[0].copy()
214				} else {
215					zero()
216				};
217				let r = dot0[0].copy();
218				let mut dot = dot.rb_mut().get_mut(k + 1..);
219				let mut norm = norm.rb_mut().get_mut(k + 1..);
220				if delayed_update {
221					let A10 = A10.col(k - 1).get(1..);
222					*B00 += &l * &r;
223					zip!(B10.rb_mut(), A10).for_each(|unzip!(x, y)| {
224						*x += &r * y;
225					});
226				}
227				let HouseholderInfo { tau, .. } =
228					householder::make_householder_in_place(B00, B10.rb_mut());
229				let ref tau_inv = tau.recip();
230				H[k] = tau.to_cplx();
231				if k + 1 == size {
232					if delayed_update {
233						zip!(B01.rb_mut(), dot.rb()).for_each(
234							|unzip!(x, y)| {
235								*x += &l * y;
236							},
237						);
238					}
239					break 'unscale;
240				}
241				if (m - k - 1) * (n - k - 1) < params.par_threshold {
242					par = Par::Seq;
243				}
244				if delayed_update {
245					let A10 = A10.col(k - 1).get(1..);
246					match par {
247						Par::Seq => {
248							update_mat_and_dot_simd(
249								norm.rb_mut(),
250								dot.rb_mut(),
251								B01.rb_mut(),
252								B11.rb_mut().try_as_col_major_mut().unwrap(),
253								A10.try_as_col_major().unwrap(),
254								B10.rb().try_as_col_major().unwrap(),
255								l.copy(),
256								tau_inv.copy(),
257								simd_align(k + 1),
258							);
259						},
260						#[cfg(feature = "rayon")]
261						Par::Rayon(nthreads) => {
262							let nthreads = nthreads.get();
263							use rayon::prelude::*;
264							spindle::for_each(
265								nthreads,
266								norm.par_partition_mut(nthreads)
267									.zip(dot.par_partition_mut(nthreads))
268									.zip(B01.par_partition_mut(nthreads))
269									.zip(B11.par_col_partition_mut(nthreads)),
270								|(((norm, dot), B01), B11)| {
271									update_mat_and_dot_simd(
272										norm,
273										dot,
274										B01,
275										B11.try_as_col_major_mut().unwrap(),
276										A10.try_as_col_major().unwrap(),
277										B10.rb().try_as_col_major().unwrap(),
278										l.copy(),
279										tau_inv.copy(),
280										simd_align(k + 1),
281									);
282								},
283							);
284						},
285					}
286				} else {
287					dot.copy_from(B01.rb());
288					linalg::matmul::matmul(
289						dot.rb_mut(),
290						Accum::Add,
291						B10.rb().adjoint(),
292						B11.rb(),
293						one(),
294						par,
295					);
296					zip!(B01.rb_mut(), dot.rb_mut(), norm.rb_mut()).for_each(
297						|unzip!(a, dot, norm)| {
298							*dot = -dot.mul_real(tau_inv);
299							*a += &*dot;
300							*norm = (norm.abs2() - a.abs2()).sqrt().to_cplx();
301						},
302					);
303				}
304			}
305		}
306		zip!(A.rb_mut()).for_each_triangular_upper(
307			linalg::zip::Diag::Include,
308			|unzip!(a)| *a = a.mul_real(scale_fwd),
309		);
310	}
311	for j in 0..n {
312		col_perm_inv[col_perm[j].zx()] = I::truncate(j);
313	}
314	(
315		ColPivQrInfo {
316			transposition_count: n_trans,
317		},
318		unsafe { PermRef::new_unchecked(col_perm, col_perm_inv, n) },
319	)
320}
321/// computes the layout of required workspace for performing a qr decomposition
322/// with column pivoting
323pub fn qr_in_place_scratch<I: Index, T: ComplexField>(
324	nrows: usize,
325	ncols: usize,
326	block_size: usize,
327	par: Par,
328	params: Spec<ColPivQrParams, T>,
329) -> StackReq {
330	let _ = nrows;
331	let _ = ncols;
332	let _ = par;
333	let _ = block_size;
334	let _ = &params;
335	linalg::temp_mat_scratch::<T>(ncols, 2)
336}
337/// information about the resulting $QR$ factorization.
338#[derive(Copy, Clone, Debug)]
339pub struct ColPivQrInfo {
340	/// number of transpositions that were performed, can be used to compute
341	/// the determinant of $P$.
342	pub transposition_count: usize,
343}
344#[track_caller]
345pub fn qr_in_place<'out, I: Index, T: ComplexField>(
346	A: MatMut<'_, T>,
347	Q_coeff: MatMut<'_, T>,
348	col_perm: &'out mut [I],
349	col_perm_inv: &'out mut [I],
350	par: Par,
351	stack: &mut MemStack,
352	params: Spec<ColPivQrParams, T>,
353) -> (ColPivQrInfo, PermRef<'out, I>) {
354	let mut A = A;
355	let mut H = Q_coeff;
356	let size = H.ncols();
357	let block_size = H.nrows();
358	let ret = qr_in_place_unblocked(
359		A.rb_mut(),
360		H.rb_mut().row_mut(0),
361		col_perm,
362		col_perm_inv,
363		par,
364		stack,
365		params,
366	);
367	let mut j = 0;
368	while j < size {
369		let block_size = Ord::min(block_size, size - j);
370		let mut H = H
371			.rb_mut()
372			.subcols_mut(j, block_size)
373			.subrows_mut(0, block_size);
374		for j in 0..block_size {
375			H[(j, j)] = H[(0, j)].copy();
376		}
377		let A = A.rb().get(j.., j..j + block_size);
378		householder::upgrade_householder_factor(
379			H.rb_mut(),
380			A,
381			block_size,
382			1,
383			par,
384		);
385		j += block_size;
386	}
387	ret
388}
389#[cfg(test)]
390mod tests {
391	use super::*;
392	use crate::stats::prelude::*;
393	use crate::utils::approx::*;
394	use crate::{Mat, assert, c64};
395	use dyn_stack::MemBuffer;
396	#[test]
397	fn test_unblocked_qr() {
398		let rng = &mut StdRng::seed_from_u64(0);
399		for par in [Par::Seq, Par::rayon(8)] {
400			for n in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
401				let bs = 15;
402				let approx_eq = CwiseMat(ApproxEq {
403					abs_tol: 1e-10,
404					rel_tol: 1e-10,
405				});
406				let A = CwiseMatDistribution {
407					nrows: n,
408					ncols: n,
409					dist: ComplexDistribution::new(
410						StandardNormal,
411						StandardNormal,
412					),
413				}
414				.rand::<Mat<c64>>(rng);
415				let A = A.as_ref();
416				let mut QR = A.cloned();
417				let mut H = Mat::zeros(bs, n);
418				let col_perm = &mut *vec![0usize; n];
419				let col_perm_inv = &mut *vec![0usize; n];
420				let q = qr_in_place(
421					QR.as_mut(),
422					H.as_mut(),
423					col_perm,
424					col_perm_inv,
425					par,
426					MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
427						usize,
428						c64,
429					>(
430						n,
431						n,
432						bs,
433						par,
434						default(),
435					))),
436					default(),
437				)
438				.1;
439				let mut Q = Mat::<c64, _, _>::zeros(n, n);
440				let mut R = QR.as_ref().cloned();
441				for j in 0..n {
442					Q[(j, j)] = c64::ONE;
443				}
444				householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
445					QR.as_ref(),
446					H.as_ref(),
447					Conj::No,
448					Q.as_mut(),
449					Par::Seq,
450					MemStack::new(&mut MemBuffer::new(
451						householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(n, bs, n),
452					)),
453				);
454				for j in 0..n {
455					for i in j + 1..n {
456						R[(i, j)] = c64::ZERO;
457					}
458				}
459				assert!(Q * R * q ~ A);
460			}
461			let n = 20;
462			for m in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
463				let bs = 15;
464				let size = Ord::min(m, n);
465				let approx_eq = CwiseMat(ApproxEq {
466					abs_tol: 1e-10,
467					rel_tol: 1e-10,
468				});
469				let A = CwiseMatDistribution {
470					nrows: m,
471					ncols: n,
472					dist: ComplexDistribution::new(
473						StandardNormal,
474						StandardNormal,
475					),
476				}
477				.rand::<Mat<c64>>(rng);
478				let A = A.as_ref();
479				let mut QR = A.cloned();
480				let mut H = Mat::zeros(bs, size);
481				let col_perm = &mut *vec![0usize; n];
482				let col_perm_inv = &mut *vec![0usize; n];
483				let q = qr_in_place(
484					QR.as_mut(),
485					H.as_mut(),
486					col_perm,
487					col_perm_inv,
488					par,
489					MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
490						usize,
491						c64,
492					>(
493						m,
494						n,
495						bs,
496						par,
497						default(),
498					))),
499					default(),
500				)
501				.1;
502				let mut Q = Mat::<c64, _, _>::zeros(m, m);
503				let mut R = QR.as_ref().cloned();
504				for j in 0..m {
505					Q[(j, j)] = c64::ONE;
506				}
507				householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
508					QR.as_ref().subcols(0, size),
509					H.as_ref(),
510					Conj::No,
511					Q.as_mut(),
512					Par::Seq,
513					MemStack::new(&mut MemBuffer::new(
514						householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(m, bs, m),
515					)),
516				);
517				for j in 0..n {
518					for i in j + 1..m {
519						R[(i, j)] = c64::ZERO;
520					}
521				}
522				assert!(Q * R * q ~ A);
523			}
524		}
525	}
526}