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