1pub use super::super::no_pivoting::factor::recommended_block_size;
2use crate::assert;
3use crate::internal_prelude::*;
4use crate::utils::cost::{ParThreshold, rate};
5use crate::utils::thread::with_width;
6use crate::perm::swap_cols_idx;
7use linalg::householder::{self, HouseholderInfo};
8use pulp::Simd;
9fn update_mat_and_dot_simd<T: ComplexField>(
10 norm: RowMut<'_, T>,
11 dot: RowMut<'_, T>,
12 B01: RowMut<'_, T>,
13 B11: MatMut<'_, T, usize, usize, ContiguousFwd>,
14 A10: ColRef<'_, T, usize, ContiguousFwd>,
15 B10: ColRef<'_, T, usize, ContiguousFwd>,
16 l: T,
17 tau_inv: T::Real,
18 align: usize,
19) {
20 struct Impl<'a, 'M, 'N, T: ComplexField> {
21 norm: RowMut<'a, T, Dim<'N>>,
22 dot: RowMut<'a, T, Dim<'N>>,
23 B01: RowMut<'a, T, Dim<'N>>,
24 B11: MatMut<'a, T, Dim<'M>, Dim<'N>, ContiguousFwd>,
25 A10: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
26 B10: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
27 l: T,
28 tau_inv: T::Real,
29 align: usize,
30 }
31 impl<'M, 'N, T: ComplexField> pulp::WithSimd for Impl<'_, 'M, 'N, T> {
32 type Output = ();
33
34 #[inline(always)]
35 fn with_simd<S: Simd>(self, simd: S) -> Self::Output {
36 let Self {
37 mut norm,
38 mut dot,
39 B01: mut u,
40 mut B11,
41 A10,
42 B10,
43 l,
44 ref tau_inv,
45 align,
46 } = self;
47 let m = B11.nrows();
48 let n = B11.ncols();
49 let simd =
50 SimdCtx::<'_, T, S>::new_align(T::simd_ctx(simd), m, align);
51 let indices4 = simd.batch_indices::<4>();
52
53 for j in n.indices() {
54 let b0 = dot[j].copy();
55 let rhs0 = simd.splat(&b0);
56 let mut acc = [simd.zero(); 4];
57 simd_iter!(for (IDX, i) in [indices4; 4] {
58 let lhs0 = simd.read(A10, i);
59 let lhs1 = simd.read(B10, i);
60 let mut dst0 = simd.read(B11.rb().col(j), i);
61 dst0 = simd.mul_add(lhs0, rhs0, dst0);
62 acc[IDX] = simd.conj_mul_add(lhs1, dst0, acc[IDX]);
63 simd.write(B11.rb_mut().col_mut(j), i, dst0);
64 });
65 let acc = simd
66 .add(simd.add(acc[0], acc[2]), simd.add(acc[1], acc[3]));
67 let tmp = &u[j] + &l * b0;
68 let d0 = (&tmp + simd.reduce_sum(acc)).mul_real(-tau_inv);
69 u[j] = tmp + &d0;
70 dot[j] = d0;
71 norm[j] = (norm[j].abs2() - u[j].abs2()).sqrt().to_cplx();
72 }
73 }
74 }
75 with_dim!(M, B11.nrows());
76 with_dim!(N, B11.ncols());
77 dispatch!(
78 Impl {
79 norm: norm.as_col_shape_mut(N),
80 dot: dot.as_col_shape_mut(N),
81 B01: B01.as_col_shape_mut(N),
82 B11: B11.as_shape_mut(M, N),
83 A10: A10.as_row_shape(M),
84 B10: B10.as_row_shape(M),
85 l,
86 tau_inv,
87 align
88 },
89 Impl,
90 T
91 )
92}
93#[derive(Copy, Clone, Debug)]
95pub struct ColPivQrParams {
96 pub blocking_threshold: usize,
98 pub par_threshold: ParThreshold,
102 #[doc(hidden)]
103 pub non_exhaustive: NonExhaustive,
104}
105impl<T: ComplexField> Auto<T> for ColPivQrParams {
106 #[inline]
107 fn auto() -> Self {
108 Self {
109 blocking_threshold: 48 * 48,
110 par_threshold: ParThreshold::Derived,
111 non_exhaustive: NonExhaustive(()),
112 }
113 }
114}
115#[track_caller]
116fn qr_in_place_unblocked<'out, I: Index, T: ComplexField>(
117 A: MatMut<'_, T>,
118 H: RowMut<'_, T>,
119 col_perm: &'out mut [I],
120 col_perm_inv: &'out mut [I],
121 par: Par,
122 stack: &mut MemStack,
123 params: Spec<ColPivQrParams, T>,
124) -> (ColPivQrInfo, PermRef<'out, I>) {
125 let m = A.nrows();
126 let n = A.ncols();
127 let size = H.ncols();
128 let params = params.config;
129 let mut A = A;
130 let mut H = H;
131 let mut par = par;
132 if matches!(params.par_threshold, ParThreshold::Never) {
134 par = Par::Seq;
135 }
136 assert!(size == Ord::min(m, n));
137 for j in 0..n {
138 col_perm[j] = I::truncate(j);
139 }
140 let mut n_trans = 0;
141 'main: {
142 if size == 0 {
143 break 'main;
144 }
145 let (mut dot, stack) = temp_mat_zeroed::<T, _, _>(n, 1, stack);
146 let (mut norm, stack) = temp_mat_zeroed::<T, _, _>(n, 1, stack);
147 let _ = stack;
148 let mut dot = dot.as_mat_mut().col_mut(0).transpose_mut();
149 let mut norm = norm.as_mat_mut().col_mut(0).transpose_mut();
150 let mut best = zero();
151 let ref threshold = eps::<T::Real>().sqrt();
152 for j in 0..n {
153 let val = A.rb().col(j).norm_l2();
154 norm[j] = val.to_cplx();
155 if val > best {
156 best = val;
157 }
158 }
159 let ref scale_fwd = if best > zero() { best.copy() } else { one() };
163 let ref scale_bwd = scale_fwd.recip();
164 zip!(A.rb_mut()).for_each(|unzip!(a)| *a = a.mul_real(scale_bwd));
165 for j in 0..n {
166 norm[j] = (norm[j].real() * scale_bwd).to_cplx();
167 }
168 best = best * scale_bwd;
169 let mut best_threshold = best * threshold;
170 let mut width = par.degree();
173 'unscale: {
174 for k in 0..size {
175 let mut new_best = zero::<T::Real>();
176 let mut best_col = k;
177 for j in k..n {
178 let val = norm[j].real();
179 if val > new_best {
180 new_best = val;
181 best_col = j;
182 }
183 }
184 let delayed_update = T::SIMD_CAPABILITIES.is_simd()
185 && A.row_stride() == 1
186 && k > 0 && new_best >= best_threshold;
187 if k > 0 && !delayed_update {
188 let (_, _, A10, mut A11) = A.rb_mut().split_at_mut(k, k);
189 let dot = dot.rb().get(k..);
190 let A10 = A10.rb().col(k - 1);
191 with_width(par.reborrow(), width, |par| {
192 linalg::matmul::matmul(
193 A11.rb_mut(),
194 Accum::Add,
195 A10,
196 dot,
197 one(),
198 par,
199 );
200 });
201 best = zero();
202 for j in k..n {
203 let val = A11.rb().col(j - k).norm_l2();
204 norm[j] = val.to_cplx();
205 if val > best {
206 best = val;
207 best_col = j;
208 }
209 }
210 best_threshold = best * threshold;
211 }
212 if best_col != k {
213 n_trans += 1;
214 col_perm.as_mut().swap(best_col, k);
215 swap_cols_idx(A.rb_mut(), best_col, k);
216 swap_cols_idx(dot.rb_mut().as_mat_mut(), best_col, k);
217 swap_cols_idx(norm.rb_mut().as_mat_mut(), best_col, k);
218 }
219 let (_, _, A10, mut A11) = A.rb_mut().split_at_mut(k, k);
220 let A10 = A10.rb();
221 let dot0 = dot.rb_mut().get_mut(k..);
222 let (mut B00, B01, B10, mut B11) =
223 A11.rb_mut().split_at_mut(1, 1);
224 let B00 = &mut B00[(0, 0)];
225 let mut B01 = B01.row_mut(0);
226 let mut B10 = B10.col_mut(0);
227 let l = if delayed_update {
228 let A10 = A10.col(k - 1);
229 A10[0].copy()
230 } else {
231 zero()
232 };
233 let r = dot0[0].copy();
234 let mut dot = dot.rb_mut().get_mut(k + 1..);
235 let mut norm = norm.rb_mut().get_mut(k + 1..);
236 if delayed_update {
237 let A10 = A10.col(k - 1).get(1..);
238 *B00 += &l * &r;
239 zip!(B10.rb_mut(), A10).for_each(|unzip!(x, y)| {
240 *x += &r * y;
241 });
242 }
243 let HouseholderInfo { tau, .. } =
244 householder::make_householder_in_place(B00, B10.rb_mut());
245 let ref tau_inv = tau.recip();
246 H[k] = tau.to_cplx();
247 if k + 1 == size {
248 if delayed_update {
249 zip!(B01.rb_mut(), dot.rb()).for_each(
250 |unzip!(x, y)| {
251 *x += &l * y;
252 },
253 );
254 }
255 break 'unscale;
256 }
257 width = params.par_threshold.region_width::<T>(
261 &par,
262 (m - k - 1) * (n - k - 1),
263 rate::qr_col_pivot(),
264 1,
265 );
266 if delayed_update {
267 let A10 = A10.col(k - 1).get(1..);
268 with_width(par.reborrow(), width, |par| {
269 match par {
270 Par::Seq => {
271 update_mat_and_dot_simd(
272 norm.rb_mut(),
273 dot.rb_mut(),
274 B01.rb_mut(),
275 B11.rb_mut().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 #[cfg(feature = "rayon")]
284 _ => {
285 let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
286 let nthreads = nthreads.get();
287 use rayon::prelude::*;
288 crate::utils::thread::for_each(par,
289 nthreads,
290 norm.par_partition_mut(nthreads)
291 .zip(dot.par_partition_mut(nthreads))
292 .zip(B01.par_partition_mut(nthreads))
293 .zip(B11.par_col_partition_mut(nthreads)),
294 |(((norm, dot), B01), B11)| {
295 update_mat_and_dot_simd(
296 norm,
297 dot,
298 B01,
299 B11.try_as_col_major_mut().unwrap(),
300 A10.try_as_col_major().unwrap(),
301 B10.rb().try_as_col_major().unwrap(),
302 l.copy(),
303 tau_inv.copy(),
304 simd_align(k + 1),
305 );
306 },
307 );
308 },
309 }
310 });
311 } else {
312 dot.copy_from(B01.rb());
313 with_width(par.reborrow(), width, |par| {
314 linalg::matmul::matmul(
315 dot.rb_mut(),
316 Accum::Add,
317 B10.rb().adjoint(),
318 B11.rb(),
319 one(),
320 par,
321 );
322 });
323 zip!(B01.rb_mut(), dot.rb_mut(), norm.rb_mut()).for_each(
324 |unzip!(a, dot, norm)| {
325 *dot = -dot.mul_real(tau_inv);
326 *a += &*dot;
327 *norm = (norm.abs2() - a.abs2()).sqrt().to_cplx();
328 },
329 );
330 }
331 }
332 }
333 zip!(A.rb_mut()).for_each_triangular_upper(
334 linalg::zip::Diag::Include,
335 |unzip!(a)| *a = a.mul_real(scale_fwd),
336 );
337 }
338 for j in 0..n {
339 col_perm_inv[col_perm[j].zx()] = I::truncate(j);
340 }
341 (
342 ColPivQrInfo {
343 transposition_count: n_trans,
344 },
345 unsafe { PermRef::new_unchecked(col_perm, col_perm_inv, n) },
346 )
347}
348pub fn qr_in_place_scratch<I: Index, T: ComplexField>(
351 nrows: usize,
352 ncols: usize,
353 block_size: usize,
354 par: ParShape,
355 params: Spec<ColPivQrParams, T>,
356) -> StackReq {
357 let _ = nrows;
358 let _ = ncols;
359 let _ = par;
360 let _ = block_size;
361 let _ = ¶ms;
362 linalg::temp_mat_scratch::<T>(ncols, 2)
363}
364#[derive(Copy, Clone, Debug)]
366pub struct ColPivQrInfo {
367 pub transposition_count: usize,
370}
371#[track_caller]
372pub fn qr_in_place<'out, I: Index, T: ComplexField>(
373 A: MatMut<'_, T>,
374 Q_coeff: MatMut<'_, T>,
375 col_perm: &'out mut [I],
376 col_perm_inv: &'out mut [I],
377 mut par: Par,
378 stack: &mut MemStack,
379 params: Spec<ColPivQrParams, T>,
380) -> (ColPivQrInfo, PermRef<'out, I>) {
381 let mut A = A;
392 if matches!(params.config.par_threshold, ParThreshold::Never) {
394 par = Par::Seq;
395 }
396 if A.row_stride().unsigned_abs() != 1
397 && A.nrows() > 1
398 && A.ncols() > 1
399 && linalg::row_major_copy_pays(A.nrows(), A.ncols())
400 {
401 let (m, n) = A.shape();
402 let mut work = linalg::column_major_copy(A.rb(), par.reborrow());
403 let mut work = MatMut::from_column_major_slice_mut(&mut work, m, n);
404 let ret = qr_in_place(
405 work.rb_mut(),
406 Q_coeff,
407 col_perm,
408 col_perm_inv,
409 par.reborrow(),
410 stack,
411 params,
412 );
413 linalg::copy_across_layouts(A, work.rb(), par);
414 return ret;
415 }
416 let mut H = Q_coeff;
417 let size = H.ncols();
418 let block_size = H.nrows();
419 let ret = qr_in_place_unblocked(
420 A.rb_mut(),
421 H.rb_mut().row_mut(0),
422 col_perm,
423 col_perm_inv,
424 par.reborrow(),
425 stack,
426 params,
427 );
428 let mut j = 0;
429 while j < size {
430 let block_size = Ord::min(block_size, size - j);
431 let mut H = H
432 .rb_mut()
433 .subcols_mut(j, block_size)
434 .subrows_mut(0, block_size);
435 for j in 0..block_size {
436 H[(j, j)] = H[(0, j)].copy();
437 }
438 let A = A.rb().get(j.., j..j + block_size);
439 householder::upgrade_householder_factor(
440 H.rb_mut(),
441 A,
442 block_size,
443 1,
444 par.reborrow(),
445 );
446 j += block_size;
447 }
448 ret
449}
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::stats::prelude::*;
454 use crate::utils::approx::*;
455 use crate::{Mat, assert, c32, c64};
456 use dyn_stack::MemBuffer;
457 #[test]
458 fn test_unblocked_qr() {
459 let rng = &mut StdRng::seed_from_u64(0);
460 let mut run = |mut par: Par<'_>| {
461 let par_shape = par.shape();
462 for n in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
463 let bs = 15;
464 let approx_eq = CwiseMat(ApproxEq {
465 abs_tol: 1e-10,
466 rel_tol: 1e-10,
467 });
468 let A = CwiseMatDistribution {
469 nrows: n,
470 ncols: n,
471 dist: ComplexDistribution::new(
472 StandardNormal,
473 StandardNormal,
474 ),
475 }
476 .rand::<Mat<c64>>(rng);
477 let A = A.as_ref();
478 let mut QR = A.cloned();
479 let mut H = Mat::zeros(bs, n);
480 let col_perm = &mut *vec![0usize; n];
481 let col_perm_inv = &mut *vec![0usize; n];
482 let q = qr_in_place(
483 QR.as_mut(),
484 H.as_mut(),
485 col_perm,
486 col_perm_inv,
487 par.reborrow(),
488 MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
489 usize,
490 c64,
491 >(
492 n,
493 n,
494 bs,
495 par_shape,
496 default(),
497 ))),
498 default(),
499 )
500 .1;
501 let mut Q = Mat::<c64, _, _>::zeros(n, n);
502 let mut R = QR.as_ref().cloned();
503 for j in 0..n {
504 Q[(j, j)] = c64::ONE;
505 }
506 householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
507 QR.as_ref(),
508 H.as_ref(),
509 Conj::No,
510 Q.as_mut(),
511 Par::Seq,
512 MemStack::new(&mut MemBuffer::new(
513 householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(n, bs, n),
514 )),
515 );
516 for j in 0..n {
517 for i in j + 1..n {
518 R[(i, j)] = c64::ZERO;
519 }
520 }
521 assert!(Q * R * q ~ A);
522 }
523 let n = 20;
524 for m in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
525 let bs = 15;
526 let size = Ord::min(m, n);
527 let approx_eq = CwiseMat(ApproxEq {
528 abs_tol: 1e-10,
529 rel_tol: 1e-10,
530 });
531 let A = CwiseMatDistribution {
532 nrows: m,
533 ncols: n,
534 dist: ComplexDistribution::new(
535 StandardNormal,
536 StandardNormal,
537 ),
538 }
539 .rand::<Mat<c64>>(rng);
540 let A = A.as_ref();
541 let mut QR = A.cloned();
542 let mut H = Mat::zeros(bs, size);
543 let col_perm = &mut *vec![0usize; n];
544 let col_perm_inv = &mut *vec![0usize; n];
545 let q = qr_in_place(
546 QR.as_mut(),
547 H.as_mut(),
548 col_perm,
549 col_perm_inv,
550 par.reborrow(),
551 MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
552 usize,
553 c64,
554 >(
555 m,
556 n,
557 bs,
558 par_shape,
559 default(),
560 ))),
561 default(),
562 )
563 .1;
564 let mut Q = Mat::<c64, _, _>::zeros(m, m);
565 let mut R = QR.as_ref().cloned();
566 for j in 0..m {
567 Q[(j, j)] = c64::ONE;
568 }
569 householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
570 QR.as_ref().subcols(0, size),
571 H.as_ref(),
572 Conj::No,
573 Q.as_mut(),
574 Par::Seq,
575 MemStack::new(&mut MemBuffer::new(
576 householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(m, bs, m),
577 )),
578 );
579 for j in 0..n {
580 for i in j + 1..m {
581 R[(i, j)] = c64::ZERO;
582 }
583 }
584 assert!(Q * R * q ~ A);
585 }
586 };
587 run(Par::Seq);
588 crate::team::with_team(8, |par| run(par));
589 }
590 #[test]
593 fn zero_matrix_reconstructs() {
594 for (m, n) in [(1, 1), (3, 2), (8, 8), (64, 64)] {
596 let run = |mut par: Par<'_>| {
597 check_zero_matrix::<f32>(m, n, par.reborrow());
598 check_zero_matrix::<c32>(m, n, par.reborrow());
599 check_zero_matrix::<f64>(m, n, par.reborrow());
600 check_zero_matrix::<c64>(m, n, par);
601 };
602 run(Par::Seq);
603 crate::team::with_team(8, |par| run(par));
604 }
605 }
606 fn check_zero_matrix<T: ComplexField>(m: usize, n: usize, mut par: Par) {
607 let par_shape = par.shape();
608 let size = Ord::min(m, n);
609 let bs = 15;
610 let approx_eq = CwiseMat(ApproxEq {
611 abs_tol: from_f64::<T::Real>(1e-10),
612 rel_tol: from_f64::<T::Real>(1e-10),
613 });
614 let a = Mat::<T>::zeros(m, n);
615 let a = a.as_ref();
616 let mut QR = a.cloned();
617 let mut H = Mat::<T>::zeros(bs, size);
618 let col_perm = &mut *vec![0usize; n];
619 let col_perm_inv = &mut *vec![0usize; n];
620 let perm = qr_in_place(
621 QR.as_mut(),
622 H.as_mut(),
623 col_perm,
624 col_perm_inv,
625 par.reborrow(),
626 MemStack::new(&mut MemBuffer::new(
627 qr_in_place_scratch::<usize, T>(m, n, bs, par_shape, default()),
628 )),
629 default(),
630 )
631 .1;
632 assert!(QR.as_ref().is_all_finite());
633 assert!(QR.as_ref().norm_max() == zero::<T::Real>());
634 let mut q = Mat::<T, _, _>::zeros(m, m);
635 for j in 0..m {
636 q[(j, j)] = one();
637 }
638 householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
639 QR.as_ref().subcols(0, size),
640 H.as_ref(),
641 Conj::No,
642 q.as_mut(),
643 Par::Seq,
644 MemStack::new(&mut MemBuffer::new(
645 householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(m, bs, m),
646 )),
647 );
648 assert!(
651 (q.adjoint() * &q - Mat::<T>::identity(m, m)).norm_max()
652 <= eps::<T::Real>() * from_f64::<T::Real>(32.0)
653 );
654 assert!(q * QR.as_ref() * perm ~ a);
655 }
656}