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#[derive(Copy, Clone, Debug)]
93pub struct ColPivQrParams {
94 pub blocking_threshold: usize,
96 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 = if best > zero() { best.copy() } else { one() };
155 let ref scale_bwd = scale_fwd.recip();
156 zip!(A.rb_mut()).for_each(|unzip!(a)| *a = a.mul_real(scale_bwd));
157 for j in 0..n {
158 norm[j] = (norm[j].real() * scale_bwd).to_cplx();
159 }
160 best = best * scale_bwd;
161 let mut best_threshold = best * threshold;
162 'unscale: {
163 for k in 0..size {
164 let mut new_best = zero::<T::Real>();
165 let mut best_col = k;
166 for j in k..n {
167 let val = norm[j].real();
168 if val > new_best {
169 new_best = val;
170 best_col = j;
171 }
172 }
173 let delayed_update = T::SIMD_CAPABILITIES.is_simd()
174 && A.row_stride() == 1
175 && k > 0 && new_best >= best_threshold;
176 if k > 0 && !delayed_update {
177 let (_, _, A10, mut A11) = A.rb_mut().split_at_mut(k, k);
178 let dot = dot.rb().get(k..);
179 let A10 = A10.rb().col(k - 1);
180 linalg::matmul::matmul(
181 A11.rb_mut(),
182 Accum::Add,
183 A10,
184 dot,
185 one(),
186 par,
187 );
188 best = zero();
189 for j in k..n {
190 let val = A11.rb().col(j - k).norm_l2();
191 norm[j] = val.to_cplx();
192 if val > best {
193 best = val;
194 best_col = j;
195 }
196 }
197 best_threshold = best * threshold;
198 }
199 if best_col != k {
200 n_trans += 1;
201 col_perm.as_mut().swap(best_col, k);
202 swap_cols_idx(A.rb_mut(), best_col, k);
203 swap_cols_idx(dot.rb_mut().as_mat_mut(), best_col, k);
204 swap_cols_idx(norm.rb_mut().as_mat_mut(), best_col, k);
205 }
206 let (_, _, A10, mut A11) = A.rb_mut().split_at_mut(k, k);
207 let A10 = A10.rb();
208 let dot0 = dot.rb_mut().get_mut(k..);
209 let (mut B00, B01, B10, mut B11) =
210 A11.rb_mut().split_at_mut(1, 1);
211 let B00 = &mut B00[(0, 0)];
212 let mut B01 = B01.row_mut(0);
213 let mut B10 = B10.col_mut(0);
214 let l = if delayed_update {
215 let A10 = A10.col(k - 1);
216 A10[0].copy()
217 } else {
218 zero()
219 };
220 let r = dot0[0].copy();
221 let mut dot = dot.rb_mut().get_mut(k + 1..);
222 let mut norm = norm.rb_mut().get_mut(k + 1..);
223 if delayed_update {
224 let A10 = A10.col(k - 1).get(1..);
225 *B00 += &l * &r;
226 zip!(B10.rb_mut(), A10).for_each(|unzip!(x, y)| {
227 *x += &r * y;
228 });
229 }
230 let HouseholderInfo { tau, .. } =
231 householder::make_householder_in_place(B00, B10.rb_mut());
232 let ref tau_inv = tau.recip();
233 H[k] = tau.to_cplx();
234 if k + 1 == size {
235 if delayed_update {
236 zip!(B01.rb_mut(), dot.rb()).for_each(
237 |unzip!(x, y)| {
238 *x += &l * y;
239 },
240 );
241 }
242 break 'unscale;
243 }
244 if (m - k - 1) * (n - k - 1) < params.par_threshold {
245 par = Par::Seq;
246 }
247 if delayed_update {
248 let A10 = A10.col(k - 1).get(1..);
249 match par {
250 Par::Seq => {
251 update_mat_and_dot_simd(
252 norm.rb_mut(),
253 dot.rb_mut(),
254 B01.rb_mut(),
255 B11.rb_mut().try_as_col_major_mut().unwrap(),
256 A10.try_as_col_major().unwrap(),
257 B10.rb().try_as_col_major().unwrap(),
258 l.copy(),
259 tau_inv.copy(),
260 simd_align(k + 1),
261 );
262 },
263 #[cfg(feature = "rayon")]
264 Par::Rayon(nthreads) => {
265 let nthreads = nthreads.get();
266 use rayon::prelude::*;
267 spindle::for_each(
268 nthreads,
269 norm.par_partition_mut(nthreads)
270 .zip(dot.par_partition_mut(nthreads))
271 .zip(B01.par_partition_mut(nthreads))
272 .zip(B11.par_col_partition_mut(nthreads)),
273 |(((norm, dot), B01), B11)| {
274 update_mat_and_dot_simd(
275 norm,
276 dot,
277 B01,
278 B11.try_as_col_major_mut().unwrap(),
279 A10.try_as_col_major().unwrap(),
280 B10.rb().try_as_col_major().unwrap(),
281 l.copy(),
282 tau_inv.copy(),
283 simd_align(k + 1),
284 );
285 },
286 );
287 },
288 }
289 } else {
290 dot.copy_from(B01.rb());
291 linalg::matmul::matmul(
292 dot.rb_mut(),
293 Accum::Add,
294 B10.rb().adjoint(),
295 B11.rb(),
296 one(),
297 par,
298 );
299 zip!(B01.rb_mut(), dot.rb_mut(), norm.rb_mut()).for_each(
300 |unzip!(a, dot, norm)| {
301 *dot = -dot.mul_real(tau_inv);
302 *a += &*dot;
303 *norm = (norm.abs2() - a.abs2()).sqrt().to_cplx();
304 },
305 );
306 }
307 }
308 }
309 zip!(A.rb_mut()).for_each_triangular_upper(
310 linalg::zip::Diag::Include,
311 |unzip!(a)| *a = a.mul_real(scale_fwd),
312 );
313 }
314 for j in 0..n {
315 col_perm_inv[col_perm[j].zx()] = I::truncate(j);
316 }
317 (
318 ColPivQrInfo {
319 transposition_count: n_trans,
320 },
321 unsafe { PermRef::new_unchecked(col_perm, col_perm_inv, n) },
322 )
323}
324pub fn qr_in_place_scratch<I: Index, T: ComplexField>(
327 nrows: usize,
328 ncols: usize,
329 block_size: usize,
330 par: Par,
331 params: Spec<ColPivQrParams, T>,
332) -> StackReq {
333 let _ = nrows;
334 let _ = ncols;
335 let _ = par;
336 let _ = block_size;
337 let _ = ¶ms;
338 linalg::temp_mat_scratch::<T>(ncols, 2)
339}
340#[derive(Copy, Clone, Debug)]
342pub struct ColPivQrInfo {
343 pub transposition_count: usize,
346}
347#[track_caller]
348pub fn qr_in_place<'out, I: Index, T: ComplexField>(
349 A: MatMut<'_, T>,
350 Q_coeff: MatMut<'_, T>,
351 col_perm: &'out mut [I],
352 col_perm_inv: &'out mut [I],
353 par: Par,
354 stack: &mut MemStack,
355 params: Spec<ColPivQrParams, T>,
356) -> (ColPivQrInfo, PermRef<'out, I>) {
357 let mut A = A;
368 if A.row_stride().unsigned_abs() != 1
369 && A.nrows() > 1
370 && A.ncols() > 1
371 && linalg::row_major_copy_pays(A.nrows(), A.ncols())
372 {
373 let (m, n) = A.shape();
374 let mut work = alloc::vec![zero::<T>(); m * n];
375 let mut work = MatMut::from_column_major_slice_mut(&mut work, m, n);
376 linalg::copy_across_layouts(work.rb_mut(), A.rb(), par);
377 let ret = qr_in_place(
378 work.rb_mut(),
379 Q_coeff,
380 col_perm,
381 col_perm_inv,
382 par,
383 stack,
384 params,
385 );
386 linalg::copy_across_layouts(A, work.rb(), par);
387 return ret;
388 }
389 let mut H = Q_coeff;
390 let size = H.ncols();
391 let block_size = H.nrows();
392 let ret = qr_in_place_unblocked(
393 A.rb_mut(),
394 H.rb_mut().row_mut(0),
395 col_perm,
396 col_perm_inv,
397 par,
398 stack,
399 params,
400 );
401 let mut j = 0;
402 while j < size {
403 let block_size = Ord::min(block_size, size - j);
404 let mut H = H
405 .rb_mut()
406 .subcols_mut(j, block_size)
407 .subrows_mut(0, block_size);
408 for j in 0..block_size {
409 H[(j, j)] = H[(0, j)].copy();
410 }
411 let A = A.rb().get(j.., j..j + block_size);
412 householder::upgrade_householder_factor(
413 H.rb_mut(),
414 A,
415 block_size,
416 1,
417 par,
418 );
419 j += block_size;
420 }
421 ret
422}
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use crate::stats::prelude::*;
427 use crate::utils::approx::*;
428 use crate::{Mat, assert, c32, c64};
429 use dyn_stack::MemBuffer;
430 #[test]
431 fn test_unblocked_qr() {
432 let rng = &mut StdRng::seed_from_u64(0);
433 for par in [Par::Seq, Par::rayon(8)] {
434 for n in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
435 let bs = 15;
436 let approx_eq = CwiseMat(ApproxEq {
437 abs_tol: 1e-10,
438 rel_tol: 1e-10,
439 });
440 let A = CwiseMatDistribution {
441 nrows: n,
442 ncols: n,
443 dist: ComplexDistribution::new(
444 StandardNormal,
445 StandardNormal,
446 ),
447 }
448 .rand::<Mat<c64>>(rng);
449 let A = A.as_ref();
450 let mut QR = A.cloned();
451 let mut H = Mat::zeros(bs, n);
452 let col_perm = &mut *vec![0usize; n];
453 let col_perm_inv = &mut *vec![0usize; n];
454 let q = qr_in_place(
455 QR.as_mut(),
456 H.as_mut(),
457 col_perm,
458 col_perm_inv,
459 par,
460 MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
461 usize,
462 c64,
463 >(
464 n,
465 n,
466 bs,
467 par,
468 default(),
469 ))),
470 default(),
471 )
472 .1;
473 let mut Q = Mat::<c64, _, _>::zeros(n, n);
474 let mut R = QR.as_ref().cloned();
475 for j in 0..n {
476 Q[(j, j)] = c64::ONE;
477 }
478 householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
479 QR.as_ref(),
480 H.as_ref(),
481 Conj::No,
482 Q.as_mut(),
483 Par::Seq,
484 MemStack::new(&mut MemBuffer::new(
485 householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(n, bs, n),
486 )),
487 );
488 for j in 0..n {
489 for i in j + 1..n {
490 R[(i, j)] = c64::ZERO;
491 }
492 }
493 assert!(Q * R * q ~ A);
494 }
495 let n = 20;
496 for m in [2, 3, 4, 8, 16, 24, 32, 128, 255] {
497 let bs = 15;
498 let size = Ord::min(m, n);
499 let approx_eq = CwiseMat(ApproxEq {
500 abs_tol: 1e-10,
501 rel_tol: 1e-10,
502 });
503 let A = CwiseMatDistribution {
504 nrows: m,
505 ncols: n,
506 dist: ComplexDistribution::new(
507 StandardNormal,
508 StandardNormal,
509 ),
510 }
511 .rand::<Mat<c64>>(rng);
512 let A = A.as_ref();
513 let mut QR = A.cloned();
514 let mut H = Mat::zeros(bs, size);
515 let col_perm = &mut *vec![0usize; n];
516 let col_perm_inv = &mut *vec![0usize; n];
517 let q = qr_in_place(
518 QR.as_mut(),
519 H.as_mut(),
520 col_perm,
521 col_perm_inv,
522 par,
523 MemStack::new(&mut MemBuffer::new(qr_in_place_scratch::<
524 usize,
525 c64,
526 >(
527 m,
528 n,
529 bs,
530 par,
531 default(),
532 ))),
533 default(),
534 )
535 .1;
536 let mut Q = Mat::<c64, _, _>::zeros(m, m);
537 let mut R = QR.as_ref().cloned();
538 for j in 0..m {
539 Q[(j, j)] = c64::ONE;
540 }
541 householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
542 QR.as_ref().subcols(0, size),
543 H.as_ref(),
544 Conj::No,
545 Q.as_mut(),
546 Par::Seq,
547 MemStack::new(&mut MemBuffer::new(
548 householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<c64>(m, bs, m),
549 )),
550 );
551 for j in 0..n {
552 for i in j + 1..m {
553 R[(i, j)] = c64::ZERO;
554 }
555 }
556 assert!(Q * R * q ~ A);
557 }
558 }
559 }
560 #[test]
563 fn zero_matrix_reconstructs() {
564 for (m, n) in [(1, 1), (3, 2), (8, 8), (64, 64)] {
566 for par in [Par::Seq, Par::rayon(8)] {
567 check_zero_matrix::<f32>(m, n, par);
568 check_zero_matrix::<c32>(m, n, par);
569 check_zero_matrix::<f64>(m, n, par);
570 check_zero_matrix::<c64>(m, n, par);
571 }
572 }
573 }
574 fn check_zero_matrix<T: ComplexField>(m: usize, n: usize, par: Par) {
575 let size = Ord::min(m, n);
576 let bs = 15;
577 let approx_eq = CwiseMat(ApproxEq {
578 abs_tol: from_f64::<T::Real>(1e-10),
579 rel_tol: from_f64::<T::Real>(1e-10),
580 });
581 let a = Mat::<T>::zeros(m, n);
582 let a = a.as_ref();
583 let mut QR = a.cloned();
584 let mut H = Mat::<T>::zeros(bs, size);
585 let col_perm = &mut *vec![0usize; n];
586 let col_perm_inv = &mut *vec![0usize; n];
587 let perm = qr_in_place(
588 QR.as_mut(),
589 H.as_mut(),
590 col_perm,
591 col_perm_inv,
592 par,
593 MemStack::new(&mut MemBuffer::new(
594 qr_in_place_scratch::<usize, T>(m, n, bs, par, default()),
595 )),
596 default(),
597 )
598 .1;
599 assert!(QR.as_ref().is_all_finite());
600 assert!(QR.as_ref().norm_max() == zero::<T::Real>());
601 let mut q = Mat::<T, _, _>::zeros(m, m);
602 for j in 0..m {
603 q[(j, j)] = one();
604 }
605 householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
606 QR.as_ref().subcols(0, size),
607 H.as_ref(),
608 Conj::No,
609 q.as_mut(),
610 Par::Seq,
611 MemStack::new(&mut MemBuffer::new(
612 householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(m, bs, m),
613 )),
614 );
615 assert!(
618 (q.adjoint() * &q - Mat::<T>::identity(m, m)).norm_max()
619 <= eps::<T::Real>() * from_f64::<T::Real>(32.0)
620 );
621 assert!(q * QR.as_ref() * perm ~ a);
622 }
623}