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	// Row-major input: the pivot search walks every one of the `n` columns of
355	// length `m` (`for j in 0..n { A.col(j).norm_l2() }`, and again per step when
356	// the incremental-norm path is closed), and that path is column-major only --
357	// it is gated on `A.row_stride() == 1` and unwraps a column-major view. So a
358	// row-major matrix touches `m * n` cache lines where the copy below moves
359	// `m * n` elements contiguously twice, on every shape rather than only on the
360	// tall ones. Factor a column-major copy and write the factor back, which is
361	// what LAPACK's row-major interface pays internally; the arithmetic is the
362	// copy's, so the factor, the `tau` matrix and the permutations are the
363	// column-major ones.
364	let mut A = A;
365	if A.row_stride().unsigned_abs() != 1
366		&& A.nrows() > 1
367		&& A.ncols() > 1
368		&& linalg::row_major_copy_pays(A.nrows(), A.ncols())
369	{
370		let (m, n) = A.shape();
371		let mut work = alloc::vec![zero::<T>(); m * n];
372		let mut work = MatMut::from_column_major_slice_mut(&mut work, m, n);
373		linalg::copy_across_layouts(work.rb_mut(), A.rb(), par);
374		let ret = qr_in_place(
375			work.rb_mut(),
376			Q_coeff,
377			col_perm,
378			col_perm_inv,
379			par,
380			stack,
381			params,
382		);
383		linalg::copy_across_layouts(A, work.rb(), par);
384		return ret;
385	}
386	let mut H = Q_coeff;
387	let size = H.ncols();
388	let block_size = H.nrows();
389	let ret = qr_in_place_unblocked(
390		A.rb_mut(),
391		H.rb_mut().row_mut(0),
392		col_perm,
393		col_perm_inv,
394		par,
395		stack,
396		params,
397	);
398	let mut j = 0;
399	while j < size {
400		let block_size = Ord::min(block_size, size - j);
401		let mut H = H
402			.rb_mut()
403			.subcols_mut(j, block_size)
404			.subrows_mut(0, block_size);
405		for j in 0..block_size {
406			H[(j, j)] = H[(0, j)].copy();
407		}
408		let A = A.rb().get(j.., j..j + block_size);
409		householder::upgrade_householder_factor(
410			H.rb_mut(),
411			A,
412			block_size,
413			1,
414			par,
415		);
416		j += block_size;
417	}
418	ret
419}
420#[cfg(test)]
421mod tests {
422	use super::*;
423	use crate::stats::prelude::*;
424	use crate::utils::approx::*;
425	use crate::{Mat, assert, c64};
426	use dyn_stack::MemBuffer;
427	#[test]
428	fn test_unblocked_qr() {
429		let rng = &mut StdRng::seed_from_u64(0);
430		for par in [Par::Seq, Par::rayon(8)] {
431			for n in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
432				let bs = 15;
433				let approx_eq = CwiseMat(ApproxEq {
434					abs_tol: 1e-10,
435					rel_tol: 1e-10,
436				});
437				let A = CwiseMatDistribution {
438					nrows: n,
439					ncols: n,
440					dist: ComplexDistribution::new(
441						StandardNormal,
442						StandardNormal,
443					),
444				}
445				.rand::<Mat<c64>>(rng);
446				let A = A.as_ref();
447				let mut QR = A.cloned();
448				let mut H = Mat::zeros(bs, n);
449				let col_perm = &mut *vec![0usize; n];
450				let col_perm_inv = &mut *vec![0usize; n];
451				let q = qr_in_place(
452					QR.as_mut(),
453					H.as_mut(),
454					col_perm,
455					col_perm_inv,
456					par,
457					MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
458						usize,
459						c64,
460					>(
461						n,
462						n,
463						bs,
464						par,
465						default(),
466					))),
467					default(),
468				)
469				.1;
470				let mut Q = Mat::<c64, _, _>::zeros(n, n);
471				let mut R = QR.as_ref().cloned();
472				for j in 0..n {
473					Q[(j, j)] = c64::ONE;
474				}
475				householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
476					QR.as_ref(),
477					H.as_ref(),
478					Conj::No,
479					Q.as_mut(),
480					Par::Seq,
481					MemStack::new(&mut MemBuffer::new(
482						householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(n, bs, n),
483					)),
484				);
485				for j in 0..n {
486					for i in j + 1..n {
487						R[(i, j)] = c64::ZERO;
488					}
489				}
490				assert!(Q * R * q ~ A);
491			}
492			let n = 20;
493			for m in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
494				let bs = 15;
495				let size = Ord::min(m, n);
496				let approx_eq = CwiseMat(ApproxEq {
497					abs_tol: 1e-10,
498					rel_tol: 1e-10,
499				});
500				let A = CwiseMatDistribution {
501					nrows: m,
502					ncols: n,
503					dist: ComplexDistribution::new(
504						StandardNormal,
505						StandardNormal,
506					),
507				}
508				.rand::<Mat<c64>>(rng);
509				let A = A.as_ref();
510				let mut QR = A.cloned();
511				let mut H = Mat::zeros(bs, size);
512				let col_perm = &mut *vec![0usize; n];
513				let col_perm_inv = &mut *vec![0usize; n];
514				let q = qr_in_place(
515					QR.as_mut(),
516					H.as_mut(),
517					col_perm,
518					col_perm_inv,
519					par,
520					MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
521						usize,
522						c64,
523					>(
524						m,
525						n,
526						bs,
527						par,
528						default(),
529					))),
530					default(),
531				)
532				.1;
533				let mut Q = Mat::<c64, _, _>::zeros(m, m);
534				let mut R = QR.as_ref().cloned();
535				for j in 0..m {
536					Q[(j, j)] = c64::ONE;
537				}
538				householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
539					QR.as_ref().subcols(0, size),
540					H.as_ref(),
541					Conj::No,
542					Q.as_mut(),
543					Par::Seq,
544					MemStack::new(&mut MemBuffer::new(
545						householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(m, bs, m),
546					)),
547				);
548				for j in 0..n {
549					for i in j + 1..m {
550						R[(i, j)] = c64::ZERO;
551					}
552				}
553				assert!(Q * R * q ~ A);
554			}
555		}
556	}
557}