1use crate::internal_prelude::*;
2use crate::perm::swap_rows_idx;
3use crate::{assert, debug_assert};
4#[inline]
5pub(crate) fn swap_elems<T: ComplexField>(
6 col: ColMut<'_, T>,
7 i: usize,
8 j: usize,
9) {
10 debug_assert!(all(i < col.nrows(), j < col.nrows()));
11 let rs = col.row_stride();
12 let col = col.as_ptr_mut();
13 unsafe {
14 let a = col.offset(i as isize * rs);
15 let b = col.offset(j as isize * rs);
16 core::ptr::swap(a, b);
17 }
18}
19fn lu_in_place_unblocked<I: Index, T: ComplexField>(
20 matrix: MatMut<'_, T>,
21 start: usize,
22 end: usize,
23 trans: &mut [I],
24) -> usize {
25 let mut matrix = matrix;
26 let m = matrix.nrows();
27 if start == end {
28 return 0;
29 }
30 let mut n_trans = 0;
31 for j in start..end {
32 let col = j;
33 let row = j - start;
34 let t = &mut trans[row];
35 let mut imax = row;
36 let mut max = zero();
37 for i in imax..m {
38 let abs = matrix[(i, col)].abs1();
39 if abs > max {
40 max = abs;
41 imax = i;
42 }
43 }
44 *t = I::truncate(imax - row);
45 if imax != row {
46 swap_rows_idx(matrix.rb_mut(), row, imax);
47 n_trans += 1;
48 }
49 let mut matrix = matrix.rb_mut().get_mut(.., start..end);
50 let pivot = matrix[(row, row)].copy();
53 if pivot != zero() {
54 if pivot.abs() >= min_positive() {
55 let ref inv = pivot.recip();
56 for i in row + 1..m {
57 matrix[(i, row)] *= inv;
58 }
59 } else {
60 let ref shift = max_positive::<T::Real>();
65 let ref pivot = pivot.mul_real(shift);
66 for i in row + 1..m {
67 matrix[(i, row)] =
68 &matrix[(i, row)].mul_real(shift) / pivot;
69 }
70 }
71 }
72 let (_, A01, A10, A11) = matrix.rb_mut().split_at_mut(row + 1, row + 1);
73 let A01 = A01.row(row);
74 let A10 = A10.col(row);
75 linalg::matmul::matmul(
76 A11,
77 Accum::Add,
78 A10.as_mat(),
79 A01.as_mat(),
80 -one::<T>(),
81 Par::Seq,
82 );
83 }
84 n_trans
85}
86const TRAILING_UPDATE_SECTIONS: u32 = 2;
90const SWAP_STAGE_SECTIONS: u32 = 4;
95const SWAP_ONE_SIDE_SECTIONS: u32 = 1;
98use crate::utils::cost::{gemm_ns_per_mac, rate, serial_time};
99
100pub use crate::utils::cost::ParThreshold;
101
102fn trailing_update_pays<T: ComplexField>(
108 shape: ParShape,
109 par_threshold: ParThreshold,
110 rows: usize,
111 cols: usize,
112 b: usize,
113) -> bool {
114 let floor = match par_threshold {
115 ParThreshold::Never => return false,
116 ParThreshold::Fixed(n) => n,
117 ParThreshold::Derived => {
118 let macs = cols.saturating_mul(
119 b.saturating_mul(b) / 2 + rows.saturating_mul(b),
120 );
121 return shape.pays_full(
122 serial_time(macs, gemm_ns_per_mac::<T>()),
123 TRAILING_UPDATE_SECTIONS,
124 );
125 },
126 };
127 let work = rows
128 .saturating_mul(cols)
129 .saturating_mul(if T::IS_REAL { 2 } else { 8 });
130 work > floor.saturating_mul(2)
131}
132
133fn swap_stage_pays<T: ComplexField>(
137 shape: ParShape,
138 par_threshold: ParThreshold,
139 m: usize,
140 n: usize,
141 cols: usize,
142 one_side: bool,
143) -> bool {
144 match par_threshold {
145 ParThreshold::Never => false,
146 ParThreshold::Fixed(floor) => m.saturating_mul(cols) > floor,
147 ParThreshold::Derived => shape.pays_full(
148 serial_time(n.saturating_mul(cols), rate::lu_swap().of::<T>()),
149 if one_side { SWAP_ONE_SIDE_SECTIONS } else { SWAP_STAGE_SECTIONS },
150 ),
151 }
152}
153pub(crate) fn lu_in_place_recursion<I: Index, T: ComplexField>(
154 A: MatMut<'_, T>,
155 start: usize,
156 end: usize,
157 trans: &mut [I],
158 mut par: Par,
159 params: Spec<PartialPivLuParams, T>,
160) -> usize {
161 let params = params.config;
162 let mut A = A;
163 let m = A.nrows();
164 let ncols = A.ncols();
165 let n = end - start;
166 if n <= params.recursion_threshold {
167 return lu_in_place_unblocked(A, start, end, trans);
168 }
169 let half = n / 2;
170 let pow = Ord::min(16, half.next_power_of_two());
171 let block_size = half.next_multiple_of(pow);
172 let mut n_trans = 0;
173 assert!(n <= m);
174 n_trans += lu_in_place_recursion(
175 A.rb_mut().get_mut(.., start..end),
176 0,
177 block_size,
178 &mut trans[..block_size],
179 par.reborrow(),
180 params.into(),
181 );
182 {
183 let mut A = A.rb_mut().get_mut(.., start..end);
184 let (A00, mut A01, A10, mut A11) =
185 A.rb_mut().split_at_mut(block_size, block_size);
186 let A00 = A00.rb();
187 let A10 = A10.rb();
188 let (rows1, cols1, b) = (A11.nrows(), A11.ncols(), A10.ncols());
198 let mut par_here = if trailing_update_pays::<T>(
199 par.shape(),
200 params.par_threshold,
201 rows1,
202 cols1,
203 b,
204 ) {
205 par.reborrow()
206 } else {
207 Par::Seq
208 };
209 {
210 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
211 A00.rb(),
212 A01.rb_mut(),
213 par_here.reborrow(),
214 );
215 }
216 linalg::matmul::matmul(
217 A11.rb_mut(),
218 Accum::Add,
219 A10.rb(),
220 A01.rb(),
221 -one::<T>(),
222 par_here,
223 );
224 n_trans += lu_in_place_recursion(
225 A.rb_mut().get_mut(block_size..m, ..),
226 block_size,
227 n,
228 &mut trans[block_size..n],
229 par.reborrow(),
230 params.into(),
231 );
232 }
233 let swap = |mat: MatMut<'_, T>| {
234 let mut mat = mat;
235 for j in 0..mat.ncols() {
236 let mut col = mat.rb_mut().col_mut(j);
237 if col.row_stride() == 1 {
238 for (j, &t) in trans[..n].iter().enumerate() {
239 swap_elems(col.rb_mut(), j, t.zx() + j);
240 }
241 } else {
242 for (j, &t) in trans[..n].iter().enumerate() {
243 swap_elems(col.rb_mut(), j, t.zx() + j);
244 }
245 }
246 }
247 };
248 let (A_left, A_right) = A.rb_mut().split_at_col_mut(start);
249 let A_right = A_right.get_mut(.., end - start..ncols - start);
250 let par = if swap_stage_pays::<T>(
251 par.shape(),
252 params.par_threshold,
253 m,
254 n,
255 ncols - n,
256 start == 0 || end == ncols,
257 ) {
258 par
259 } else {
260 Par::Seq
261 };
262 match par {
263 Par::Seq => {
264 swap(A_left);
265 swap(A_right);
266 },
267 #[cfg(feature = "rayon")]
268 _ => {
269 let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
270 let nthreads = nthreads.get();
271 let len = (A_left.ncols() + A_right.ncols()) as f64;
272 let left_threads = Ord::min(
273 (nthreads as f64 * (A_left.ncols() as f64 / len)) as usize,
274 nthreads,
275 );
276 let (left_cols, right_cols) = (A_left.ncols(), A_right.ncols());
277 if left_cols == 0 || right_cols == 0 {
278 let (A, _) = if left_cols == 0 { (A_right, A_left) } else { (A_left, A_right) };
280 crate::utils::thread::for_each(
281 par,
282 nthreads,
283 A.par_col_partition_mut(nthreads),
284 |A| swap(A),
285 );
286 } else {
287 crate::utils::thread::join_sized(
290 par,
291 left_threads,
292 |par| {
293 let n = par.degree();
294 crate::utils::thread::for_each(
295 par,
296 n,
297 A_left.par_col_partition_mut(n),
298 |A| swap(A),
299 )
300 },
301 |par| {
302 let n = par.degree();
303 crate::utils::thread::for_each(
304 par,
305 n,
306 A_right.par_col_partition_mut(n),
307 |A| swap(A),
308 )
309 },
310 );
311 }
312 },
313 }
314 n_trans
315}
316#[derive(Copy, Clone, Debug)]
318pub struct PartialPivLuParams {
319 pub recursion_threshold: usize,
321 pub block_size: usize,
323 pub par_threshold: ParThreshold,
330 #[doc(hidden)]
331 pub non_exhaustive: NonExhaustive,
332}
333#[derive(Copy, Clone, Debug)]
335pub struct PartialPivLuInfo {
336 pub transposition_count: usize,
339}
340#[derive(Copy, Clone, Debug)]
342pub enum LdltError {
343 ZeroPivot { index: usize },
344}
345impl<T: ComplexField> Auto<T> for PartialPivLuParams {
346 #[inline]
347 fn auto() -> Self {
348 Self {
349 recursion_threshold: 16,
350 block_size: 64,
351 par_threshold: ParThreshold::Derived,
352 non_exhaustive: NonExhaustive(()),
353 }
354 }
355}
356#[inline]
357pub fn lu_in_place_scratch<I: Index, T: ComplexField>(
358 nrows: usize,
359 ncols: usize,
360 par: ParShape,
361 params: Spec<PartialPivLuParams, T>,
362) -> StackReq {
363 _ = par;
364 _ = params;
365 StackReq::new::<I>(Ord::min(nrows, ncols))
366}
367pub fn lu_in_place<'out, I: Index, T: ComplexField>(
368 A: MatMut<'_, T>,
369 perm: &'out mut [I],
370 perm_inv: &'out mut [I],
371 mut par: Par,
372 stack: &mut MemStack,
373 params: Spec<PartialPivLuParams, T>,
374) -> (PartialPivLuInfo, PermRef<'out, I>) {
375 let _ = ¶ms;
376 let truncate = I::truncate;
377 #[cfg(feature = "perf-warn")]
378 if (A.col_stride().unsigned_abs() == 1
379 || A.row_stride().unsigned_abs() != 1)
380 && crate::__perf_warn!(LU_WARN)
381 {
382 log::warn!(
383 target : "faer_perf",
384 "LU with partial pivoting prefers column-major or row-major matrix. Found matrix with generic strides."
385 );
386 }
387 let mut matrix = A;
388 let mut stack = stack;
389 let m = matrix.nrows();
390 let n = matrix.ncols();
391 if matrix.row_stride().unsigned_abs() != 1
402 && matrix.nrows() >= matrix.ncols()
403 && matrix.ncols() > 1
404 && linalg::row_major_copy_pays(matrix.nrows(), matrix.ncols())
405 {
406 let mut work = alloc::vec![zero::<T>(); m * n];
407 let mut work = MatMut::from_column_major_slice_mut(&mut work, m, n);
408 linalg::copy_across_layouts(work.rb_mut(), matrix.rb(), par.reborrow());
409 let ret = lu_in_place(
410 work.rb_mut(),
411 perm,
412 perm_inv,
413 par.reborrow(),
414 stack,
415 params,
416 );
417 linalg::copy_across_layouts(matrix, work.rb(), par);
418 return ret;
419 }
420 let size = Ord::min(n, m);
421 for i in 0..m {
422 let p = &mut perm[i];
423 *p = truncate(i);
424 }
425 let (mut transpositions, _) =
426 stack.rb_mut().make_with(size, |_| truncate(0));
427 let transpositions = transpositions.as_mut();
428 let n_transpositions = lu_in_place_recursion(
429 matrix.rb_mut(),
430 0,
431 size,
432 transpositions.as_mut(),
433 par.reborrow(),
434 params,
435 );
436 for idx in 0..size {
437 let t = transpositions[idx];
438 perm.as_mut().swap(idx, idx + t.zx());
439 }
440 if m < n {
441 let (left, right) = matrix.split_at_col_mut(size);
442 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
443 left.rb(),
444 right,
445 par,
446 );
447 }
448 for i in 0..m {
449 perm_inv[perm[i].zx()] = truncate(i);
450 }
451 (
452 PartialPivLuInfo {
453 transposition_count: n_transpositions,
454 },
455 unsafe { PermRef::new_unchecked(perm, perm_inv, m) },
456 )
457}
458#[cfg(test)]
459mod tests {
460 use super::*;
461 use crate::stats::prelude::*;
462 use crate::utils::approx::*;
463 use crate::{Mat, assert, c32, c64};
464 use crate::utils::cost::{HostClass, Rates, outside_cost_of, pays_at, rate, serial_time};
465 use dyn_stack::MemBuffer;
466 #[test]
467 fn region_rules_follow_the_serial_time() {
468 use core::num::NonZeroUsize;
469 let seq = ParShape::Seq;
470 assert!(!trailing_update_pays::<f64>(seq, ParThreshold::Derived, 4096, 4096, 256));
472 assert!(!swap_stage_pays::<f64>(seq, ParThreshold::Derived, 4096, 256, 4096, false));
473 #[cfg(feature = "rayon")]
474 {
475 let par = ParShape::Rayon(NonZeroUsize::new(6).unwrap());
476 assert!(!trailing_update_pays::<f64>(par, ParThreshold::Derived, 16, 16, 8));
478 assert!(trailing_update_pays::<f64>(par, ParThreshold::Derived, 2048, 2048, 128));
479 assert!(!swap_stage_pays::<f64>(par, ParThreshold::Derived, 64, 16, 48, false));
480 assert!(swap_stage_pays::<f64>(par, ParThreshold::Derived, 4096, 256, 4096, false));
481 for w in [4, 8] {
486 let par = ParShape::Rayon(NonZeroUsize::new(w).unwrap());
487 let rule = |ns: f64| pays_at(serial_time(64 * 960, ns), SWAP_ONE_SIDE_SECTIONS, par.dispatch_cost(NonZeroUsize::new(w).unwrap()));
488 assert_eq!(swap_stage_pays::<f64>(par, ParThreshold::Derived, 64, 64, 960, true), rule(rate::lu_swap().of::<f64>()), "f64 {w}");
489 assert_eq!(swap_stage_pays::<c64>(par, ParThreshold::Derived, 64, 64, 960, true), rule(rate::lu_swap().of::<c64>()), "c64 {w}");
490 }
491 for w in [4, 8] {
494 let class = HostClass::X86_64Avx512;
495 let rt = Rates::for_class(class);
496 let cost = outside_cost_of(class, NonZeroUsize::new(w).unwrap());
497 assert!(pays_at(serial_time(64 * 960, rt.lu_swap.of::<f64>()), SWAP_ONE_SIDE_SECTIONS, cost), "f64 {w}");
498 assert!(pays_at(serial_time(64 * 960, rt.lu_swap.of::<c64>()), SWAP_ONE_SIDE_SECTIONS, cost), "c64 {w}");
499 assert!(!pays_at(serial_time(64 * 960, rt.lu_swap.of::<f64>()), SWAP_STAGE_SECTIONS, cost), "two sided {w}");
500 }
501 let (r, c) = (160, 160);
503 let real = trailing_update_pays::<f64>(par, ParThreshold::Derived, r, c, 32);
504 let cplx = trailing_update_pays::<c64>(par, ParThreshold::Derived, r, c, 32);
505 assert!(cplx >= real);
506 assert!(trailing_update_pays::<f64>(par, ParThreshold::Fixed(1), 64, 64, 8));
508 assert!(!trailing_update_pays::<f64>(par, ParThreshold::Never, 4096, 4096, 256));
509 assert!(!swap_stage_pays::<f64>(par, ParThreshold::Never, 4096, 256, 4096, false));
510 }
511 }
512
513 #[cfg(feature = "rayon")]
517 #[test]
518 fn wide_matrix_parallel_agrees_with_serial() {
519 fn check<T: ComplexField<Real = f64>>(m: usize, n: usize) {
520 let a = Mat::<T>::from_fn(m, n, |i, j| {
521 let h = (i * 2654435761usize).wrapping_add(j * 40503) % 1009;
522 from_f64::<T>(h as f64 / 504.0 - 1.0)
523 });
524 let run = |mut par: Par| {
525 let mut lu = a.clone();
526 let mut perm = vec![0usize; m];
527 let mut perm_inv = vec![0usize; m];
528 let mut mem = MemBuffer::new(lu_in_place_scratch::<usize, T>(m, n, par.shape(), default()));
529 lu_in_place(lu.as_mut(), &mut perm, &mut perm_inv, par.reborrow(), MemStack::new(&mut mem), default());
530 (lu, perm)
531 };
532 let (want, perm_want) = run(Par::Seq);
533 for threads in [4, 8] {
534 for held in [false, true] {
535 let (got, perm_got) = if held {
536 spindle::with_lock(threads, || run(Par::rayon(threads)))
537 } else {
538 run(Par::rayon(threads))
539 };
540 assert_eq!(perm_want, perm_got, "{m}x{n} {threads} threads, held {held}");
541 let mut d: f64 = 0.0;
542 for j in 0..n {
543 for i in 0..m {
544 d = d.max((want[(i, j)].clone() - got[(i, j)].clone()).abs());
545 }
546 }
547 assert!(d < 1e-9, "{m}x{n} {threads} threads, held {held}: {d}");
548 }
549 }
550 }
551 check::<f64>(64, 1024);
552 check::<c64>(64, 1024);
553 check::<f64>(48, 700);
554 }
555
556 #[cfg(feature = "rayon")]
557 #[test]
558 fn derived_gates_keep_the_factorization() {
559 let rng = &mut StdRng::seed_from_u64(7);
560 for n in [96, 200, 384] {
561 let A = CwiseMatDistribution {
562 nrows: n,
563 ncols: n,
564 dist: StandardNormal,
565 }
566 .rand::<Mat<f64>>(rng);
567 let mut want = A.clone();
568 let mut got = A.clone();
569 let mut perm_a = vec![0usize; n];
570 let mut perm_ai = vec![0usize; n];
571 let mut perm_b = vec![0usize; n];
572 let mut perm_bi = vec![0usize; n];
573 let mut mem = MemBuffer::new(lu_in_place_scratch::<usize, f64>(n, n, Par::Seq.shape(), default()));
574 lu_in_place(want.as_mut(), &mut perm_a, &mut perm_ai, Par::Seq, MemStack::new(&mut mem), default());
575 lu_in_place(got.as_mut(), &mut perm_b, &mut perm_bi, Par::rayon(4), MemStack::new(&mut mem), default());
576 assert_eq!(perm_a, perm_b, "n = {n}");
577 let mut d: f64 = 0.0;
578 for j in 0..n {
579 for i in 0..n {
580 d = d.max((want[(i, j)] - got[(i, j)]).abs());
581 }
582 }
583 assert!(d < 1e-9, "n = {n}: {d}");
584 }
585 }
586
587 #[test]
588 fn test_plu() {
589 let rng = &mut StdRng::seed_from_u64(0);
590 let approx_eq = CwiseMat(ApproxEq {
591 abs_tol: 1e-13,
592 rel_tol: 1e-13,
593 });
594 for n in [1, 2, 3, 128, 255, 256, 257] {
595 let A = CwiseMatDistribution {
596 nrows: n,
597 ncols: n,
598 dist: StandardNormal,
599 }
600 .rand::<Mat<f64>>(rng);
601 let A = A.as_ref();
602 let mut LU = A.cloned();
603 let perm = &mut *vec![0usize; n];
604 let perm_inv = &mut *vec![0usize; n];
605 let params = PartialPivLuParams {
606 recursion_threshold: 2,
607 block_size: 2,
608 ..auto!(f64)
609 };
610 let p = lu_in_place(
611 LU.as_mut(),
612 perm,
613 perm_inv,
614 Par::Seq,
615 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
616 usize,
617 f64,
618 >(
619 n,
620 n,
621 ParShape::Seq,
622 params.into(),
623 ))),
624 params.into(),
625 )
626 .1;
627 let mut L = LU.as_ref().cloned();
628 let mut U = LU.as_ref().cloned();
629 for j in 0..n {
630 for i in 0..j {
631 L[(i, j)] = 0.0;
632 }
633 L[(j, j)] = 1.0;
634 }
635 for j in 0..n {
636 for i in j + 1..n {
637 U[(i, j)] = 0.0;
638 }
639 }
640 let L = L.as_ref();
641 let U = U.as_ref();
642 assert!(p.inverse() * L * U ~ A);
643 }
644 for m in [8, 128, 255, 256, 257] {
645 let n = 8;
646 let A = CwiseMatDistribution {
647 nrows: m,
648 ncols: n,
649 dist: StandardNormal,
650 }
651 .rand::<Mat<f64>>(rng);
652 let A = A.as_ref();
653 let mut LU = A.cloned();
654 let perm = &mut *vec![0usize; m];
655 let perm_inv = &mut *vec![0usize; m];
656 let p = lu_in_place(
657 LU.as_mut(),
658 perm,
659 perm_inv,
660 Par::Seq,
661 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
662 usize,
663 f64,
664 >(
665 n, n, ParShape::Seq, default()
666 ))),
667 default(),
668 )
669 .1;
670 let mut L = LU.as_ref().cloned();
671 let mut U = LU.as_ref().cloned();
672 for j in 0..n {
673 for i in 0..j {
674 L[(i, j)] = 0.0;
675 }
676 L[(j, j)] = 1.0;
677 }
678 for j in 0..n {
679 for i in j + 1..m {
680 U[(i, j)] = 0.0;
681 }
682 }
683 let L = L.as_ref();
684 let U = U.as_ref();
685 let U = U.subrows(0, n);
686 assert!(p.inverse() * L * U ~ A);
687 }
688 }
689 fn factor_lu(A: &Mat<f64>, par: Par) -> (Mat<f64>, Vec<usize>, usize) {
690 let n = A.nrows();
691 let mut LU = A.cloned();
692 let perm = &mut *vec![0usize; n];
693 let perm_inv = &mut *vec![0usize; n];
694 let params: Spec<PartialPivLuParams, f64> = default();
695 let stack =
696 &mut MemBuffer::new(lu_in_place_scratch::<usize, f64>(n, n, par.shape(), params));
697 let stack = MemStack::new(stack);
698 let (info, _) = lu_in_place(LU.as_mut(), perm, perm_inv, par, stack, params);
699 (LU, perm.to_vec(), info.transposition_count)
700 }
701 fn factor_lu_c64(A: &Mat<c64>, par: Par) -> (Mat<c64>, Vec<usize>, usize) {
702 let n = A.nrows();
703 let mut LU = A.cloned();
704 let perm = &mut *vec![0usize; n];
705 let perm_inv = &mut *vec![0usize; n];
706 let params: Spec<PartialPivLuParams, c64> = default();
707 let stack =
708 &mut MemBuffer::new(lu_in_place_scratch::<usize, c64>(n, n, par.shape(), params));
709 let stack = MemStack::new(stack);
710 let (info, _) = lu_in_place(LU.as_mut(), perm, perm_inv, par, stack, params);
711 (LU, perm.to_vec(), info.transposition_count)
712 }
713 #[test]
721 fn threaded_lu_matches_the_sequential_one() {
722 let rng = &mut StdRng::seed_from_u64(0);
723 let pool = rayon::ThreadPoolBuilder::new()
724 .num_threads(4)
725 .build()
726 .unwrap();
727 let approx_eq = CwiseMat(ApproxEq {
728 abs_tol: 1e-12,
729 rel_tol: 1e-12,
730 });
731 for n in [96, 128, 192, 256, 512] {
732 let A = CwiseMatDistribution {
733 nrows: n,
734 ncols: n,
735 dist: StandardNormal,
736 }
737 .rand::<Mat<f64>>(rng);
738 let (seq, perm_seq, trans_seq) = factor_lu(&A, Par::Seq);
739 let (par, perm_par, trans_par) =
740 pool.install(|| factor_lu(&A, Par::rayon(4)));
741 assert!(seq.as_ref() ~ par.as_ref());
742 assert!(perm_seq == perm_par);
743 assert!(trans_seq == trans_par);
744 }
745 for n in [96, 128, 192, 256, 512] {
746 let real = CwiseMatDistribution {
747 nrows: n,
748 ncols: n,
749 dist: StandardNormal,
750 }
751 .rand::<Mat<f64>>(rng);
752 let imag = CwiseMatDistribution {
753 nrows: n,
754 ncols: n,
755 dist: StandardNormal,
756 }
757 .rand::<Mat<f64>>(rng);
758 let A = Mat::<c64>::from_fn(n, n, |i, j| c64::new(real[(i, j)], imag[(i, j)]));
759 let (seq, perm_seq, trans_seq) = factor_lu_c64(&A, Par::Seq);
760 let (par, perm_par, trans_par) =
761 pool.install(|| factor_lu_c64(&A, Par::rayon(4)));
762 for j in 0..n {
763 for i in 0..n {
764 let diff = (seq[(i, j)] - par[(i, j)]).abs();
765 assert!(diff <= 1e-10, "({i}, {j}) differs by {diff:e}");
766 }
767 }
768 assert!(perm_seq == perm_par);
769 assert!(trans_seq == trans_par);
770 }
771 }
772 #[test]
778 fn zero_pivot_column_reconstructs() {
779 fn zero_pivot<T: ComplexField>(n: usize) -> Mat<T> {
780 Mat::<T>::from_fn(n, n, |i, j| {
781 if j > 0 {
782 from_f64::<T>((i + 2 * j) as f64)
783 } else {
784 zero()
785 }
786 })
787 }
788 fn subnormal<T: ComplexField>(tiny: f64) -> Mat<T> {
791 let tiny = from_f64::<T>(tiny);
792 Mat::<T>::from_fn(2, 2, |i, j| match (i, j) {
793 (0, 0) => tiny.copy(),
794 (0, 1) => one(),
795 (1, 0) => tiny.mul_real(from_f64::<T::Real>(0.5)),
796 (1, 1) => from_f64::<T>(2.0),
797 _ => zero(),
798 })
799 }
800 for n in [1usize, 2, 3, 128] {
801 check_factor::<f32>(zero_pivot::<f32>(n).as_ref(), None);
802 check_factor::<c32>(zero_pivot::<c32>(n).as_ref(), None);
803 check_factor::<f64>(zero_pivot::<f64>(n).as_ref(), None);
804 check_factor::<c64>(zero_pivot::<c64>(n).as_ref(), None);
805 }
806 check_factor::<f32>(
809 subnormal::<f32>(2f64.powi(-147)).as_ref(),
810 Some(0.5f32),
811 );
812 check_factor::<c32>(
813 subnormal::<c32>(2f64.powi(-147)).as_ref(),
814 Some(c32::new(0.5, 0.0)),
815 );
816 check_factor::<f64>(
817 subnormal::<f64>(2f64.powi(-1072)).as_ref(),
818 Some(0.5f64),
819 );
820 check_factor::<c64>(
821 subnormal::<c64>(2f64.powi(-1072)).as_ref(),
822 Some(c64::new(0.5, 0.0)),
823 );
824 }
825 fn check_factor<T: ComplexField>(a: MatRef<'_, T>, l10: Option<T>) {
826 let tol = eps::<T::Real>() * from_f64::<T::Real>(64.0);
827 let approx_eq = CwiseMat(ApproxEq {
828 abs_tol: tol.copy(),
829 rel_tol: tol,
830 });
831 let n = a.nrows();
832 for mut par in [Par::Seq, Par::rayon(8)] {
833 let par_shape = par.shape();
834 let mut LU = a.cloned();
835 let perm = &mut *vec![0usize; n];
836 let perm_inv = &mut *vec![0usize; n];
837 let params = PartialPivLuParams {
838 recursion_threshold: 2,
839 block_size: 2,
840 ..auto!(T)
841 };
842 let p = lu_in_place(
843 LU.as_mut(),
844 perm,
845 perm_inv,
846 par.reborrow(),
847 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
848 usize,
849 T,
850 >(
851 n, n, par_shape, params.into()
852 ))),
853 params.into(),
854 )
855 .1;
856 assert!(LU.as_ref().is_all_finite());
857 let mut L = LU.as_ref().cloned();
858 let mut U = LU.as_ref().cloned();
859 for j in 0..n {
860 for i in 0..j {
861 L[(i, j)] = zero();
862 }
863 L[(j, j)] = one();
864 }
865 for j in 0..n {
866 for i in j + 1..n {
867 U[(i, j)] = zero();
868 }
869 }
870 assert!(p.inverse() * L.as_ref() * U.as_ref() ~ a);
871 if let Some(ref x) = l10 {
872 assert!(
873 (L[(1, 0)].copy() - x.copy()).abs()
874 <= eps::<T::Real>() * from_f64::<T::Real>(4.0)
875 );
876 }
877 }
878 }
879}