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